Skip to content

fix(spec): a value-slot remedy reads a CEL-claimed head through vars (list.0 → vars["list"][0]) - #22524

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-22290-cel-type-name-head
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-22290-cel-type-name-head

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22290
Clause-②: no (the remedy a refusal prints, and anything else that shares celPath, now evaluates; nothing is newly accepted or refused)

What was wrong

A value-slot {…} refusal prints the CEL spelling of a path token through celPath. That function passed every head through bare except a $-named one. When the head variable is named like an identifier CEL binds for itself, the bare name reads CEL's binding and not the variable.

Measured at 4e9fe9ff6 through the built @objectstack/spec and @objectstack/formula, with a flow variable list = ['first', 'second'] in the flow CEL scope:

template remedy printed before objectstack validate on that remedy evaluation
{list.0} list[0] refused, expression-invalid: invalid CEL value: Cannot index type 'type' with type 'int' Cannot index type 'type' with type 'int'
{list} list accepted the CEL type list (a cel-js Type object), not the variable. Nothing reports it.
{list.tags} list.tags not probed fails on the type

The card's open question. The objectstack validate reading is from the built CLI, on a scratch stack with three flows: the old remedy as an envelope, the new remedy as an envelope, and the {list.0} template. validate refuses source: 'list[0]' before run time, because the envelope's own CEL check catches it. So the door that refuses {list.0} was telling the author to write a spelling the same door then refused. The bare {list} → list remedy is worse: it passes validate and writes the wrong value. After the fix, the same run prints Write {list.0} as { dialect: 'cel', source: 'vars["list"][0]' }, and the flow carrying vars["list"][0] draws no issue.

The set, read off the engine this repo builds

@objectstack/formula resolves @marcbachmann/cel-js 8.0.0. One set, CEL_CLAIMED_IDENTIFIERS, now sits beside celPath in flow-template-token.ts, in four groups:

  • Type identifiers: bool, bytes, double, int, list, map, null_type, string, type, uint. These are lib/registry.js TYPES, bound as constants when an environment is built (for (const n in TYPES) this.registerConstant(n, 'type', TYPES[n])).
  • Namespace constants: google (lib/functions.js, registerConstant('google', …)), cel (lib/macros.js) and optional (lib/optional.js, bound because the engine sets enableOptionalTypes: true).
  • Reserved words: lib/globals.js RESERVED, refused by the parser as Reserved identifier: …. They are as, break, const, continue, else, for, function, if, import, let, loop, namespace, package, return, var, void, while, __proto__ and prototype.
  • Keywords: true, false, null (literals) and in (operator), as the lexer reads them.

The first two groups equal getDefinitions().variables of an environment built with the engine's options plus the exported registerStdLib (13 names, measured). timestamp, duration and dyn are not in the set. The card expected timestamp and duration to collide, but here they are functions, not bindings, and a variable of either name already reads correctly (control rows). The card's list also missed cel, google and optional.

The fix

  • celPath reads a claimed head through vars, the route it already took for a $-named head: {list.0} → vars["list"][0], {list} → vars["list"].
  • In-place, same defect class, same file surface:
    1. A later keyword segment is indexed by name: {record.in} printed record.in, which CEL refuses (Expected IDENTIFIER, got IN). It now prints record["in"].
    2. guardOf prints the has() guard only where has() accepts it. CEL refuses has() over an index when it runs (has() invalid argument, measured). Before, {items.1.key} printed has(items[1].key) ? …, which evaluateValueEnvelope's author-time check refused on the stale-dist run of the new pins. A path with an index anywhere, a $ head or a keyword segment now gets no guard. A claimed head is guarded as has(vars.list.tags) ? vars.list.tags : null.
  • Ordinary heads print what they printed before.
  • The same example swap elsewhere (list[0] → items[0], the claim amended in 6087190357):
    • content/docs/automation/flows.mdx, the template table row;
    • the semantic migration prose 18.flow-value-slot-template-dialect-refused.ts;
    • that prose's generated copies (registry.ts, spec-changes.json, docs/protocol-upgrade-guide.md), regenerated through check:generated --fix.

Callers of celPath (git grep): only flow-value-slot-template.ts, in guardOf, in remedyFor's whole-path branch and in its text-with-holes branch. flow-text-slot-template.ts imports celExpression and templateTokensOf, not celPath. No ADR-0087 conversion or migration entry calls it, so Clause-②: no stands. The semantic entry 18.flow-value-slot-template-dialect-refused carries list[0] as prose (see Acceptance notes), not as celPath output.

Pins

  • packages/spec/src/automation/flow-value-slot-template.test.ts: the set equals the enumeration; one row per claimed name (36 rows), covering celPath for a bare head, an indexed head and an index-then-key head, plus the printed remedy for {NAME.0}; guard rows; keyword segments; an index in the middle; text with holes; controls. The controls are the ordinary heads items, record, timestamp, duration, dyn, lists, map_of and vars, and the $error head.
  • packages/services/service-automation/src/builtin/value-slot-template-grammar.test.ts (declared on [PM seat] domain:services — ⏳ vacant #6021, test only). The spec cannot import the engine, so this file holds the evaluation pins. It already pins the judge's reading against the interpolator's. The new describe adds a third reading: every CEL spelling the refusal prints (the envelope source, and the guard when one is printed) goes through AutomationEngine.evaluateValueEnvelope, meaning the author-time envelope check and then the built formula engine over the real flow CEL scope. It must give the value interpolateString read from the template. The rows:
    • 35 name rows, each over {NAME}, {NAME.0}, {NAME.1.key} and {NAME.tags}. __proto__ is claimed but left out: the flow CEL scope is a plain object, where __proto__ names the prototype rather than a key, so no CEL spelling reads a variable of that name.
    • One row that pins the guard being read off the message.
    • One row for keyword segments and an index in the middle of a path.
    • Four ordinary-head controls (items, timestamp, duration, dyn).
    • One $-head control.

Ablation. Run at 347d12026 (fix committed) through scripts/ablation-replace.mjs: the celPath head branch was reverted to the $-only test. Anchor ×1 → ×0, marker ×0 → ×1, blob 9b18b790d96f → 26df4bf13885. The spec was rebuilt, and ablation-dist-preflight found the marker in 20 built files. Spec pin file: 37 failed | 46 passed (83). Grammar pin file: 35 failed | 26 passed (61), for example list: expected {} to deeply equal [ 'first', { key: 'second' } ]. Restore: blob after restore 9b18b790d96f equals HEAD, git diff HEAD is empty and the whole-tree porcelain is clean. The spec was rebuilt, and --absent found the marker in none of 232 built files. Spec 83 passed, grammar 61 passed. After the ablation, 07a6a4dc0 added one line to each name row ({NAME.tags}) and the guard-read row. Those were not ablated.

Changesets

  • .changeset/22290-value-slot-remedy-cel-claimed-head.md: @objectstack/spec patch.
  • .changeset/19939-flow-value-slot-template-dialect-refused.md: one row corrected, '{list.0}' / list[0] → '{items.0}' / items[0]. Nothing else in the file changed. This edits another card's pending release note on purpose: the DELIBERATE CORRECTION class. Check Changeset's step Reject an empty-frontmatter changeset added by this PR (scripts/check-empty-changeset.mjs, the foreign-changeset rule) is red by design on this edit, and stays red. Locally, node scripts/check-empty-changeset.mjs --base origin/main exits 1 and names .changeset/19939-flow-value-slot-template-dialect-refused.md. pr-automation.yml runs on pull_request only, not merge_group, and Check Changeset is not one of the seven required contexts. The correction needs confirming on this PR. Confirmed as a DELIBERATE CORRECTION in 6087200290.

Local verification

At head b828131c6. This is the patch round on 07a6a4dc0, with no merge of main; the PR stays mergeable.

  • Spec local tier, vitest run --project local, under the shared lock: Test Files 630 passed (630); Tests 18840 passed, 1 todo. Lock verdict command-exit 0.
  • Spec repo tier, vitest run --project repo: 54 files / 915 tests. src/api/error-catalog-docs.test.ts first failed to load on a transient file that a concurrent check:skill-examples run writes and deletes. Re-run alone under the lock, it passed 5/5.
  • Generated artifacts: check:generated (15 up to date), check:migration-registry, check:spec-changes and check:upgrade-guide all exit 0.
  • The pins are unchanged this round. At 07a6a4dc0: value-slot-template-grammar.test.ts 62 passed; flow-value-slot-template.test.ts and flow-text-slot-template.test.ts 97 passed. The ablation turned the spec pin file red (37 failed) and the grammar pin file red (35 failed), and the restore was blob == HEAD.
  • Gates. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at b828131c6 derived 115 commands: round 1's 87 plus the docs and migration-registry families.
    • 114 exit 0, after a full workspace build that cleared seven prerequisite refusals on the fresh worktree.
    • 1 exits 1 by design: node scripts/check-empty-changeset.mjs --base origin/main, the confirmed deliberate correction.
    • --ran: 115 derived, 115 run, 0 NOT-MEASURED, 0 UNRUN.
    • check:api-surface: public API surface + factory signatures unchanged.
  • CI on b828131c6: all seven required contexts are green. Check Changeset is red by design (confirmed in 6087200290). There is no other red.

Acceptance notes

  • The three other texts that taught list[0] (the docs table row, the semantic migration prose and its generated copies) are corrected in this PR, in commits 650e87277 and b828131c6.
  • Expression tokens are not celPath. celExpression rewrites divisors only. {int * 2} prints int * 2, refused (no such overload), and {items.0 * 2} prints items.0 * 2, refused (Expected IDENTIFIER); both measured through evaluateValueEnvelope. Fixing them means tokenising the expression, which is not a mechanical change. They are reported to the seat.
  • A flow variable named vars is shadowed by the scope's own vars namespace (vars[0] → No such key: 0; vars["vars"][0] reads it). That binding belongs to the flow runtime, not CEL, so it is not in this set.
  • An envelope an author writes directly as source: 'list' still reads the CEL type. That is CEL's meaning of the identifier, not a remedy this module prints.

Generated by Claude Code

claude added 4 commits October 9, 2026 17:45
A `{list.0}` path whose head variable is named like a CEL type printed the
remedy `list[0]`, which CEL reads as its own `list` type. `celPath` now reads
every head the CEL engine claims (cel-js 8.0.0: type identifiers, namespace
constants, reserved words, keywords) through `vars`, the route a `$`-named
head already took, and indexes a later keyword segment by name. `has()`
guards are printed only where `has()` accepts the argument.

The #19939 changeset row's example now uses a head that is not a type name.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…tor's value through CEL

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ee8751d41e61a18f7819e4d3ad2c340f51ab2418 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a5a78639b09e402adf5a079472d1c42a359544ea — the merge of head b828131c612bc3713834ca7544c048a04a5a4faf into base ee8751d41e61a18f7819e4d3ad2c340f51ab2418, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a5a78639b09e402adf5a079472d1c42a359544ea && git checkout a5a78639b09e402adf5a079472d1c42a359544ea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ee8751d41e61a18f7819e4d3ad2c340f51ab2418 b828131c612bc3713834ca7544c048a04a5a4faf && git checkout -B drift-repro ee8751d41e61a18f7819e4d3ad2c340f51ab2418 && git merge --no-ff b828131c612bc3713834ca7544c048a04a5a4faf

node scripts/docs-audit/affected-docs.mjs --json ee8751d41e61a18f7819e4d3ad2c340f51ab2418

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat confirmation: the edit to .changeset/19939-flow-value-slot-template-dialect-refused.md is a DELIBERATE CORRECTION. Check Changeset is red by design on this PR

domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-09T18:49Z · holder of the claim on #22290.


Generated by Claude Code

claude added 2 commits October 9, 2026 18:52
…ype-name head

The flows guide's template table and the protocol-18 semantic migration
prose taught list[0] for a variable named list, which CEL reads as its
own type. Same swap as the #19939 changeset row.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
…ade guide

Generated by check:generated --fix from the semantic entry's items[0] swap.

Claude-Session: https://claude.ai/code/session_01DhTqaEHqPVSVnAkjG3jywn
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b828131c612bc3713834ca7544c048a04a5a4faf
Local-runs: none

Rendered 2026-10-09T19:45Z on PR #22524 (card #22290), from the card's body and its six comments, the PR's body, file list and net diff against main (merge base da989bbb2, 11 files, +240/-18, identical to the PR's file list), and the 42 check-runs on the head as read 2026-10-09T19:43Z. The dependency was read as the lockfile-pinned package: @marcbachmann/cel-js@8.0.0, whose tarball integrity equals pnpm-lock.yaml's sha512-oaTrAziGr3zDLFiMt/yLU3nZuRMawyWQB5X1a0I7s9eGy3JYzX9l8ZXXHyMd64nzbeVFZTewuUShwsZQdK6UCA==. Nothing was built, run or re-run.

① Derived judgments

CEL_CLAIMED_IDENTIFIERS (36 names) is a faithful reading of cel-js 8.0.0 as @objectstack/formula builds it — RIGHT, no invented member, no missing one.

  • Type identifiers (10): lib/registry.js TYPES has exactly string, bool, int, uint, double, map, list, bytes, null_type, type; the root registry constructor binds each with registerConstant(n, 'type', TYPES[n]). A constant wins over any context value: OPERATORS.id.check substitutes variable.value whenever variable.constant (VariableDeclaration.constant = value !== undefined), and getVariable consults the registry map before the unlistedVariablesAreDyn fallback. So a flow variable named list is unreachable bare. Right.
  • Namespace constants (3): every registerConstant( call in lib/*.js is the TYPES loop plus google (functions.js:440), cel (macros.js:192) and optional (optional.js:62/71, bound to a value only when enableOptionalTypes). The engine's CEL_ENV_OPTIONS (packages/formula/src/cel-engine.ts:51-52) sets unlistedVariablesAreDyn: true, enableOptionalTypes: true, so optional is a bound constant here. registerStdLib and registerNumericCoercions (stdlib.ts) register functions only, no variable or constant. 10 + 3 = 13 bound names, which is what the PR says getDefinitions().variables lists. Right.
  • Reserved words (19): lib/globals.js RESERVED is exactly as, break, const, continue, else, for, function, if, import, let, loop, package, namespace, return, var, void, while, __proto__, prototype. The parser refuses one in head position (#parseIdentifierPrimary, Reserved identifier: …) and the dyn fallback excludes them. Right, namespace, __proto__ and prototype included.
  • Keywords (4): the lexer's readIdentifier switch turns exactly true, false, null and in into non-identifier tokens. Right.
  • Not claimed, correctly: timestamp, duration and dyn are function overloads (functions.js) or a type kind, never a registered variable, so a variable of that name resolves through the dyn fallback to the context. The card's guess that timestamp and duration collide was wrong and the PR's controls say so. Right.

Every printed remedy is valid CEL for the scope the flow engine builds — RIGHT. AutomationEngine.celScope (engine.ts:11852) returns extra: { ...vars, vars }: every flow variable both spread at top level and under the vars map, and evaluateValueEnvelope evaluates the envelope's source over that scope after the author-time envelope check. So vars["list"][0], vars["list"], vars["for"].tags, vars["$error"].message and the text-with-holes form 'Hi ' + vars["list"][0] each read the variable. __proto__ is claimed (it is in RESERVED) and its remedy vars["__proto__"]… is valid CEL; that a plain-object scope cannot hold that key is the scope's property, declared in both pin files, and no spelling could do better.

The two in-place additions are bounded and right.

  1. A later keyword segment indexed by name (record["in"]): parsePostfix consumes TOKEN.IDENTIFIER after a dot, and in/true/false/null never lex as one, so record.in cannot parse. The reserved check lives only in head position, so record.list.for stays record.list.for (pinned). Right, and bounded to CEL_KEYWORDS.
  2. has() guards printed only where has() accepts them: createHasExpander().typeCheck (macros.js) requires the outer node to be a . access and the chain below it to be ./.? nodes down to an id, so any index ([0], vars["$x"], record["in"]) anywhere in the argument is invalid_macro_argument. guardOf now withholds the guard on a numeric segment, a $ head or a keyword segment and selects a claimed or bare head off vars (has(vars.list.tags), has(vars.for.tags), both parse). Right. Before this PR {items.1.key} printed a guard the engine refuses, so this is the same defect class.

Accept-set: unchanged — RIGHT. The diff to flow-value-slot-template.ts touches imports and guardOf only; valueSlotTemplateRefusals, remedyFor's branching, KEPT_KINDS and flowNodeValueTemplateRefusals are untouched, so which strings are refused and which kept is identical; only message text moves. The dispatch's three prohibitions (no accept-set change, no {var} refusal change, no touch of current_user / {$User.*}) hold.

Public surface: unchanged — RIGHT. CEL_KEYWORDS, CEL_CLAIMED_IDENTIFIERS and celHeadReadsThroughVars are exports of flow-template-token.ts, which automation/index.ts deliberately does not re-export (its docblock says so); check:api-surface ran green in Type Check · consumer gates, a lane of the required TypeScript Type Check context. celPath has one caller module at the head, flow-value-slot-template.ts (guardOf, remedyFor whole-path, text-with-holes), confirmed by git grep over packages/ and scripts/; flow-text-slot-template.ts imports celExpression and templateTokensOf only.

The example swaps (list[0] → items[0]) — RIGHT. The #19939 changeset row (one line, nothing else in that file), content/docs/automation/flows.mdx:269, the semantic entry 18.flow-value-slot-template-dialect-refused.ts:19, and its three generated copies (registry.ts 1 line, spec-changes.json 2 lines, docs/protocol-upgrade-guide.md 1 line) carry the identical swap; items is not a claimed name; check:migration-registry ran green in Lint & Repo Gates, and check:spec-changes and check:upgrade-guide ran green in Type Check · source gates, a lane of the required TypeScript Type Check context.

Pins. The spec pin restates the 36-name set and fails when the constant drifts; the grammar pin evaluates every printed spelling (source and guard, parsed off the message) through evaluateValueEnvelope over the real scope for 35 names times four shapes, plus keyword-segment, mid-path-index, four ordinary-head and one $-head controls. The ablation (37 and 35 failures, blob-equal restore) is the dev's reported reading, not re-run here. Both pin files ran green in Test Core (1/6..6/6).

Check-runs on the head (42): 36 success, 4 skipped, 2 failure, 0 pending. The seven required contexts are green: Lint & Repo Gates, TypeScript Type Check (and its four lanes), Test Core (and 1/6..6/6), Dogfood Regression Gate (and 1/3..3/3), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. The two reds are both Check Changeset (113978640659, and 113991755652 from the edited re-fire), each with the single failing annotation on .changeset/19939-flow-value-slot-template-dialect-refused.md ("exists on the merge base and was not added by this PR"); the clause-② and major-guard steps of that job raised no annotation. The four skips are Console Pin Gate, Packed-tarball smoke (opt-in) and the second-run Auto Label / Check PR Size, each a conditional skip, not a verdict. No governed path is in the file list; head repo equals base repo; 258 changed lines; the PR is draft and not armed.

② Semver level

.changeset/22290-value-slot-remedy-cel-claimed-head.md: @objectstack/spec patch — RIGHT. The diff publishes a bug fix in a released package (refusal text in the spec dist, plus the prose swap compiled into registry.ts), adds no public export, widens and narrows nothing an author can write. service-automation changes a test file only; content/docs and docs/protocol-upgrade-guide.md publish nothing; no skip-changeset, correctly.

Clause-②: no — RIGHT, in the PR body and in the changeset, with no arm, as the closed pair requires for no. celPath feeds the one caller module above and no ADR-0087 conversion or migration entry; the semantic entry carries items[0] as prose, not as celPath output.

The edit to .changeset/19939-flow-value-slot-template-dialect-refused.md is a DELIBERATE CORRECTION and Check Changeset is red by design. The confirmation's three conditions hold:

  1. the source names the class: scripts/check-empty-changeset.mjs FOREIGN_TWO_CLASS_LINES spells DELIBERATE CORRECTION, and FOREIGN_CORRECTION_REMEDY (line 551) is "do NOT restore it -- say so on the PR and get it confirmed";
  2. the step does not run on merge_group: .github/workflows/pr-automation.yml has on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled, edited] and nothing else, and the file is absent from the fifteen workflows that subscribe to merge_group; Check Changeset is not one of the seven required contexts;
  3. the PR comment records the gate and the reason: 6087200290 names the step, the script, the rule, the file, the false row (list[0] refused by objectstack validate and by the engine) and the no-skip-changeset rule.

③ Boundary flags

Implemented-by: claude/issue-22290-cel-type-name-head
Reviewed-by: session_01DhTqaEHqPVSVnAkjG3jywn

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 19:48
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 19:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 40a6ee5 Oct 9, 2026
42 of 44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22290-cel-type-name-head branch October 9, 2026 20:15
os-elon-musk pushed a commit that referenced this pull request Oct 9, 2026
…e merged tree (main f782f17)

The os-regen text merge kept this branch's stale copy of main's
flow-value-slot-template-dialect-refused replacement text (list[0]); main's
source entry reads items[0] since 40a6ee5 (#22524). Generators only:
pnpm --filter @objectstack/spec gen:spec-changes and gen:upgrade-guide.

Claude-Session: https://claude.ai/code/session_01BmsuLyUeuG5CNpZFMH1jzS
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants