Skip to content

docs(spec): the slot judges' door lists name the parse, and why it is not one of them (#23320) - #23383

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23320-slot-judges-at-parse
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-23320-slot-judges-at-parse

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23320

Clause-②: no

What this does

The card allowed two outcomes (triage 6152852815, unlock 6156025101). Either the value-slot and text-slot judges run in FlowSchema's refinement, or the seat measures why the parse must not run them and the module docs say so. This PR is the second outcome, and it changes comments only:

  • packages/spec/src/automation/flow-value-slot-template.ts: a new module-doc section, "The doors that run it — and why FlowSchema.parse is not one". It names every door that runs the judge and the doors that do not: FlowSchema.parse, and so defineStack, ObjectStackDefinitionSchema, the flow metadata type's schema and an artifact's parse. It gives the two measured reasons, says what the gap leaves, and states the condition under which the judges can join the parse.
  • packages/spec/src/automation/flow-text-slot-template.ts: the door list is corrected. HttpConfigSchema composes the judge too, and the doors are registerFlow and the validateStackExpressions rule, which objectstack validate, objectstack compile and the publish gate of an active metadata save all run. A new section covers the one text slot the parse does judge: a top-level end node's message, whose contract has no other door. It points to the reasons above.
  • packages/spec/src/automation/flow.zod.ts: one comment sentence at the contract arm, pointing to those docs.

The premise, at base 6d96721fa

The premise holds. A throwaway probe used FlowSchema.safeParse and defineStack with a manifest and one object:

probe FlowSchema.parse defineStack judge
create_record fields.title: '{record.name}' accepted accepted value-slot judge refuses fields.title
screen recordId: '{x}' accepted accepted value-slot judge refuses recordId
notify title template '{name}' accepted accepted text-slot judge refuses title
http url: '{id}' accepted accepted text-slot judge refuses url
controls: a CEL envelope, a {{ name }} hole accepted accepted none

PM hypothesis H1 holds. At base, flow-node-config-refusals.ts calls both judges only to build slotJudgeRefusedPaths, the exemption PR #23319 added. Neither judge adds an issue at the parse. The one exception is already there: a top-level end node's message ({x}) is refused at the parse, because parseEndNodeConfig applies EndConfigSchema, which composes the text-slot judge.

The measurement: route (a), applied and reverted

Commit ca829754b puts both judges in FlowSchema's refinement, as the dispatch's suggested route. It sits beside the flowNodeConfigRefusals arm and reports each slot token once at its own path, keeping #23319's exemption and skipping end, which its contract already judges. Commit d5b704543 reverts it. Both stay in this branch's history as the record. Spec dist/ was rebuilt, and the route's code marker was confirmed in dist/, before any consumer reading.

  • It works mechanically. All four probes are refused once each at defineStack (STACK_SCHEMA_INVALID, 422, at flows.0.nodes.1.config.SLOT), and both controls are accepted.
  • @objectstack/spec: src/automation src/conversions src/migrations src/stack, 74 files, 2877 tests passed. No spec pin depends on the parse accepting a slot token.
  • @objectstack/service-automation: the 13 test files that pin a slot refusal: 38 of 378 tests failed, across 11 files. 37 of the failures are registerFlow's located refusal, for example node 'w' (create_record) create_record field value at config.fields.total: … followed by source:, replaced by the parse's raw issue list ([{"code":"custom","path":["nodes",1,"config","fields","total"],…}]). That happens because registerFlow parses first and lets the parse's error through. The remaining failure is fix(spec,service-automation,metadata-protocol): the v18 stored preview lists every row protocol 18 refuses and no conversion rewrites #23315's pin "a braced operand parses, is not rewritten, and is reported by node": canonicalizeStoredFlow throws instead of listing the TODO.
  • @objectstack/lint: 19 of 6699 tests failed, across 3 files. 18 failures are objectstack validate's expression-invalid finding, preempted because the harness, like objectstack validate and objectstack compile, parses the stack before the rules run. The remaining failure is a stale fixture (see the Acceptance notes).
  • @objectstack/metadata-protocol: 238 files, 28353 tests passed. Its stored-migration pins use a stand-in canonicalizer that does not parse.
  • @objectstack/verify: 38 files, 306 tests passed.
  • @objectstack/cli (unit): not run under the route. A grep of the CLI's tests finds no flow that carries a slot token. The two brace hits are a dashboard filter placeholder and a connector input.

Re-measured after merging main at dc393266f (#23356). Route (a) was applied over this branch's head 28da9bd3e with scripts/ablation-replace.mjs and checked on disk against the head plus the route's patch. Spec was rebuilt, and ablation-dist-preflight found the route's marker in 20 built files. In canonicalize-stored-flow.test.ts, builtin/text-slot-template.test.ts and builtin/http-slots.test.ts, 13 of 74 tests failed:

The source was then restored from HEAD (blob equal to HEAD, git diff HEAD empty) and spec rebuilt. The marker is absent from all 232 built files, and the tree is clean.

Two earlier attempts at this leg are recorded here:

  • The first was refused by the tool before anything was built. Its anchor reappeared inside the replacement, so the count stayed at 1: an empty operation.
  • The second landed and built, but stopped at the preflight's tree reading, because the source spells 'end' and dist/ spells "end". Its trap restored the source, and the third run's restore leg rebuilt dist/.

Why the parse does not run them

  1. The located refusal. registerFlow and objectstack validate / objectstack compile each report a slot token with its node, its slot's label, its config path and its remedy. A refusal at the parse comes first at all three, and replaces that report with the parse's issue list. PR fix(spec)!: a builtin config value carrying a {token} is judged for its type at the build doors (#23260) #23319 named the same reason for its exemption.
  2. The stored pass. The flow parse is also AutomationEngine.canonicalizeStoredFlow, which os migrate meta --stored runs on each flow row. When the parse refuses a row, the row is reported failed, and that flips storedMigrationClean. The result is a non-zero exit that no run of the command can clear, because no conversion rewrites a slot token (ADR-0087 D2, "refused, not converted"). A refusal made after the parse is listed as a TODO instead, and the verdict does not move. A value-slot site is listed under its step-18 D3 entry flow-value-slot-template-dialect-refused (fix(spec,service-automation,metadata-protocol): the v18 stored preview lists every row protocol 18 refuses and no conversion rewrites #23315). A text-slot site is listed under registerFlow (fix(spec,service-automation): the v18 stored preview lists page components on the retired API source and every refusal registerFlow makes after the parse #23356, which landed while this card was in flight).

Every author-facing door already refuses the token: registerFlow and every run, objectstack validate and objectstack compile (expression-invalid, error, gating), and the publish gate of an active metadata save (INVALID_METADATA, 422, pinned in runtime-authoring-gate.flow-filter-operand.test.ts). What the gap leaves: a stack built by defineStack that goes through none of those doors. os dev and os serve load it and boot it, registerFlow refuses the flow at boot with its located line, and the flow is not registered. The module doc states when this can change: once registerFlow reports a parse refusal as precisely as its own, and once the stored pass lists a slot site the parse refuses instead of failing the row.

ADR-0087 and stored rows

Nothing narrows, so no stored-row disposition and no D3 registration is owed. node scripts/check-adr-0087-registration.mjs --base origin/main reads: "this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen)".

Changeset: none (skip-changeset)

The diff is comments only. Measured on a spec build at this head: dist/ carries none of the new text (the phrase "The doors that run it" is absent, while the control phrase from an exported docblock is present in dist/automation/index.d.ts), and the route-(a) code marker is absent from dist/. @objectstack/spec's files[] ships src/**/*.zod.ts, so the tarball's src/automation/flow.zod.ts changes by one comment sentence. Comment-only changes are on the no-publish fast track.

Tests and gates

All at head c7a71df7e.

  • @objectstack/spec: vitest run --project local src/automation src/conversions src/migrations passed 54 files, 2284 tests. pnpm run typecheck exited 0: tsc --noEmit, check:scripts-typecheck, and check:test-typecheck: OK.
  • node scripts/pm/dispatch-gates.mjs --commands (no paths) derived 71 commands, the same set as the dispatch's list. Every command was run with its exit code captured before any pipe. 70 exited 0.
  • pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET). It needs every package's dist/, and 10 packages are unbuilt in this worktree. Two attempts to build them queued 9 minutes each on the shared verify lock without getting it. So this one is NOT MEASURED. The diff changes no built output (see Changeset above).
  • Reconcile: ✓ dispatch-gates --ran: 71 derived famil(ies) accounted for — 70 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).
  • The five spec gates that read dist/ first refused with exit 3 on a stale digest after the last comment commit. They were run again after a rebuild at c7a71df7e, and all exited 0. check:api-surface reads "public API surface + factory signatures unchanged".
  • NOT MEASURED: type-check-debt. --re-measure re-runs every DEBT and EXEMPT package's tsc. This diff is comments in packages/spec, whose own typecheck exited 0. CI's lint job runs it.
  • Consumer readings at this head: the diff carries no built output, so no consumer can read a difference. The consumer readings in "The measurement" above were taken under route (a), where they mean something.

Acceptance notes

  • Stale fixture, not filed (drift, not reached). packages/lint/src/lint-flow-patterns.test.ts, "per-iteration containment … parses under FlowSchema — the docs example is authorable", copies the example from content/docs/automation/flows.mdx. The copy writes recipients: ['{currentCase.owner}'], while the page itself writes { dialect: 'cel', source: 'currentCase.owner' }. The test passes only because the parse leaves value slots to their judge. Owner: none.
  • The stored pass's words for a parse-refused flow row, not filed (read, not reproduced). A flow row the parse refuses is reported failed. Examples today: a top-level end message carrying {x}, a typed-key token (spec(automation): the #21898 token carve-out (builtinValueJudged / carriesInterpolationToken) outlived every interpolation — a built-in config value carrying a {token} registers past its type, then fails every run #23260), an undeclared builtin config key. os migrate meta --stored then prints that such rows "keep reading canonically through the chain, so nothing is broken". For a flow, the boot refuses that row. Owner: the stored-preview lane (domain:spec seat 2).
  • The triage's defineStack pins are not added. They belong to outcome (a). Under this outcome they would pin the gap itself. The pins that hold the reasons already exist: registerFlow's located refusal in service-automation, objectstack validate's in @objectstack/lint, and the stored listing in canonicalize-stored-flow.test.ts. Each went red under route (a).

Generated by Claude Code

…t of route (a) is recorded, not landed

Route (a) (ca82975) put flowNodeValueTemplateRefusals and the text-slot
judge in FlowSchema's refinement. Measured against it: registerFlow's located
slot refusal and objectstack validate's expression-invalid finding are both
preempted by the raw schema issue list, and the stored flow canonicalization
fails a row the v18 stored pass lists as a TODO today.

Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy
Co-authored-by: Claude <noreply@anthropic.com>
…not one of them

The value-slot and text-slot judges run at registerFlow, at the
validateStackExpressions rule (objectstack validate, objectstack compile,
the runtime publish gate of an active save) and at every run. FlowSchema.parse
runs neither, but for an end node's message through EndConfigSchema. Both
module docs now say so, with the two measured reasons: a parse refusal would
replace registerFlow's and the validate rule's located refusal with the
parse's issue list, and would fail the row in the stored pass, whose verdict a
refusal after the parse leaves unmoved.

Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy
Co-authored-by: Claude <noreply@anthropic.com>
…ot-judges-at-parse

Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy
Co-authored-by: Claude <noreply@anthropic.com>
… site under its D3 entry, a text-slot site under registerFlow

Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy
Co-authored-by: Claude <noreply@anthropic.com>
…own call, and the measured pins as those doors' located refusals

Claude-Session: https://claude.ai/code/session_01YRp5z4kK6vgYkrdH97ztsy
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/src/automation/flow-text-slot-template.ts, packages/spec/src/automation/flow-value-slot-template.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via FlowSchema (symbol, a top-level const))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via FlowSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-4.mdx (via FlowSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-7.mdx (via FlowSchema (symbol, a top-level const))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/src/automation/flow-text-slot-template.ts, packages/spec/src/automation/flow-value-slot-template.ts) — pages documenting those are invisible to this run
  • 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 — 142 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 713f231854cfbb115179096b2af1971afff14dbc → packageMentionDocs.

Which tree this was computed on

This run read content/docs from aed8c24ce3397dd3039f9cfe7efb3d73804705fd — the merge of head c7a71df7ecce8d8b9c6ff83bca00bfacd7cf2f4a into base 713f231854cfbb115179096b2af1971afff14dbc, 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 aed8c24ce3397dd3039f9cfe7efb3d73804705fd && git checkout aed8c24ce3397dd3039f9cfe7efb3d73804705fd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 713f231854cfbb115179096b2af1971afff14dbc c7a71df7ecce8d8b9c6ff83bca00bfacd7cf2f4a && git checkout -B drift-repro 713f231854cfbb115179096b2af1971afff14dbc && git merge --no-ff c7a71df7ecce8d8b9c6ff83bca00bfacd7cf2f4a

node scripts/docs-audit/affected-docs.mjs --json 713f231854cfbb115179096b2af1971afff14dbc

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 713f231854cfbb115179096b2af1971afff14dbc → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

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

Inputs read: card #23320 (body, triage 6152852815, unlock 6156025101, claim 6158190909, os-dev-report 6160033902); PR #23383 (body, 3-file list, net diff against main: +70/-4, comments only); the 43 check-runs on the head; the committed source at the head via git show / git grep on origin/claude/issue-23320-slot-judges-at-parse (tip = the head).

① Derived judgments

Accept set: unchanged — right. The diff edits three docblocks and no schema, refinement, export or test. FlowSchema's contract arm still exempts a slot token at slotJudgeRefusedPaths and adds no slot-judge issue; parseEndNodeConfig still applies EndConfigSchema to a top-level end. Nothing FlowSchema.parse, defineStack, ObjectStackDefinitionSchema, the flow metadata type or the environment artifact accepted or refused at base moves at this head.

Public surface: one comment sentence published — right, and declared. packages/spec/package.json files[] ships src/**/*.zod.ts at this head, so flow.zod.ts's new sentence reaches the tarball; the two judge modules are .ts, not .zod.ts, so their module docs are repo-internal. The PR body states exactly this. The dev's local check:api-surface read "unchanged", and the CI spec gates on the head are green (Lint and Repo Gates, Spec property liveness: success).

Factual claims in the new comments, each read at the head:

  • registerFlow parses first and a parse refusal reaches its caller as the parse's own issue list — TRUE. engine.ts registerFlow calls canonicalizeFlow (which runs FlowSchema.parse, throwing the ZodError) and only then iterates registrationChecks, throwing new Error(refusal.message) on the first refusal; the slot judges run inside validateFlowExpressions, one of those checks (flowNodeValueTemplateRefusals and flowNodeTextSlotSources plus textSlotTemplateRefusal, walked per graph with the node's id, type, slot label, config path and source).
  • The validateStackExpressions rule, expression-invalid at error, run by objectstack validate, objectstack compile and the publish gate of an active save — TRUE. authoring-rules.ts registers it at tier gating, input parsed, surfaces CLI_AND_RUNTIME, runtime types including flow, findings mapped to EXPRESSION_INVALID = 'expression-invalid'; validate-expressions.ts pushes both slot refusals at severity: 'error'; validate.ts and compile.ts each ObjectStackDefinitionSchema.safeParse before runAuthoringRules; runtime-authoring-gate.ts returns an empty verdict unless state === 'active'. The cited pin runtime-authoring-gate.flow-filter-operand.test.ts exists and asserts INVALID_METADATA / 422 on an active save with VALUE_SLOT_TEMPLATE_REFUSAL.
  • Every run, through the contracts composing FlowValueSlotSchema and the assignment executor's own call — TRUE. The CRUD, screen, subflow, map, notify and http executors call parseNodeConfig against their contract; logic-nodes.ts calls flowNodeValueTemplateRefusals('assignment', config) itself (no executor contract parses an assignment config, as flow.zod.ts already says).
  • The doors that reach FlowSchema — TRUE. ObjectStackDefinitionSchema spreads STACK_DEFINITION_COLLECTIONS_SHAPE, whose flows is z.array(FlowSchema); defineStack builds through it; metadata-type-schemas.ts maps flow to FlowSchema; environment-artifact.zod.ts wraps ObjectStackDefinitionSchema.
  • The contract arm stands aside at slotJudgeRefusedPaths — TRUE. flow-node-config-refusals.ts computes the set from both judges and builtinValueJudged's last exemption skips those paths.
  • The stored pass: a parse refusal reports the row failed and flips storedMigrationClean; a refusal after the parse is listed as a TODO and the verdict does not move — TRUE. canonicalizeStoredFlow runs the same canonicalizeFlow (so the parse throws out of it), protocol.ts catches into outcome: 'failed', storedMigrationClean is pending === 0 and failed === 0, and stored-migration.ts states TODOs never move it; migrate/meta.ts reads that verdict for its exit code. The TODO listing is as written: registration refusals under FLOW_REGISTRATION_TODO_ID = 'registerFlow', skipping a site a chain TODO already names, and refused-shapes.ts carries flow-value-slot-template-dialect-refused at toMajor: 18.
  • What the gap leaves (os dev / os serve boot, registerFlow refuses with its located line and does not register) — TRUE. Neither dev.ts nor serve.ts runs the authoring rules; plugin.ts wraps the boot-pull registerFlow in a try whose catch logs [Automation] failed to register flow and continues.
  • Text-slot contracts NotifyConfigSchema, HttpConfigSchema, ScreenConfigSchema, EndConfigSchema — TRUE and complete. The four sites are io-node-config.zod.ts (notify templates and actionUrl; http url and each header) and builtin-node-config.zod.ts (screen title / description; end message); no other composer exists under packages/spec/src/automation. Adding HttpConfigSchema and the compile / publish-gate doors corrects the old list.
  • "The flow parse runs this judge on one slot: a top-level end node's message" — TRUE as scoped. parseEndNodeConfig's own docblock measures that a nested end is not refused at the flow parse, and end is structural with no executor, so EndConfigSchema has no door but the parse.

One wording imprecision, not a false sentence: flow.zod.ts says the parse "runs neither slot judge itself" and the text-slot doc says the parse "calls no slot judge of its own". Mechanically the parse does invoke both judges, inside slotJudgeRefusedPaths, to compute the exemption set; what is true, and what both sentences mean in context, is that no refusal of either judge is the parse's own (the end message excepted, through its contract). Both docs name slotJudgeRefusedPaths in the same breath, so a reader is not misled; a tighter verb ("emits neither slot judge's refusal itself") would close it. Non-blocking; a follow-up wording edit is enough.

② Semver level

skip-changeset is right for this diff. Nothing published changes in behaviour, accept set or export: the dev measured dist/ free of the new text with a control phrase present, and the CI spec gates on the head are green. The tarball differs by one comment sentence in a shipped .zod.ts source. AGENTS.md reserves skip-changeset for "a diff that publishes nothing from any released package" and demands a patch for a bug fix; this PR fixes no code — the card closes by outcome (b), a measurement and its documentation — so the bug-fix clause is not triggered. Note for the seat: the dev's phrase "comment-only changes are on the no-publish fast track" has no AGENTS.md sentence I could find; the judgment above rests on the rule's own wording, not on a named fast track. Check Changeset on the head: success.

Clause-②: no (the PR body) is right; no (narrowing) (the claim) is not, for this diff. Per AGENTS.md the (narrowing) arm is BREAKING and summons check:adr-0087-registration's disposition. The claim's arm was written for route (a); the landed diff narrows nothing, and check-adr-0087-registration --base origin/main reads "adds no declared-breaking changeset". The seat amends its claim line rather than the dev restoring (narrowing).

③ Boundary flags

Outcome inside the triage direction: yes. The triage's direction reads "If the seat finds the parse must not run the slot judges … it says why in that doc and closes the card with the measurement." The module docs say why, with the two measured reasons and the condition for revisiting; the PR body carries the measurement (route (a) applied at ca829754b, reverted at d5b704543, both present in the branch history; re-measured after main dc393266f). The file surface stays inside the claim's list (three of its named files; no test, no changeset — each declared).

Dev deviations, each answered:

  1. Clause-② line differs from the claim — answered in ②: the PR body is right; the seat amends the claim.
  2. No .changeset/23320-*.md — answered in ②: right for a comment-only diff.
  3. The triage's defineStack pins are not added — ESCALATED to the seat. The triage's last direction sentence and the card's second "Wanted" bullet state the pins unconditionally; the dev declined them as "they would pin the gap itself". My reading: under outcome (b) the gap is now a documented, deliberate door split, and a pin of a deliberate contract is exactly what a pin is for — one value-slot token and one text-slot token accepted at defineStack and refused at registerFlow, with a CEL envelope and a {{ }} hole as the controls accepted at both. That would hold the module doc's claim mechanically at the door the card names. Against that: the split is already held indirectly, since route (a) turned the located-refusal pins red in @objectstack/service-automation and @objectstack/lint and the stored-listing pins in canonicalize-stored-flow.test.ts, so the doors cannot silently move. Not a contract defect; the seat decides whether to accept the deviation or ask for the two pins on this branch before the queue.
  4. Measurement and revert commits kept in history — fine: the net diff against main is the three comment edits; the squash merge carries none of the measurement.
  5. origin/main merged mid-run without pnpm install — fine for this diff: nothing in three comment edits depends on the lock delta, and the merge brought fix(spec,service-automation): the v18 stored preview lists page components on the retired API source and every refusal registerFlow makes after the parse #23356, which the text-slot doc's registerFlow TODO listing cites. The branch sits behind origin/main at my read (28e294f45); the queue re-bases.
  6. @objectstack/cli unit and check:dual-build-cjs-loads NOT MEASURED locally — the head's check-runs are the gate verdicts: Build Core success; Lint and Repo Gates, Lint · ESLint, Lint · CI/release/engine gates, Lint · PM dispatch-gates, Lint · repo and tooling gates, Lint · docs and code guards, the three scoped Lint jobs, Type Check · source gates, Spec property liveness, Governed Surface Queue Guard, the three claim/closing guards, Check Documentation Links, Flag docs affected, Check Changeset, Check PR Size, Auto Label, Dogfood Regression Gate, filter: success. Still in progress at my read: Test Core 1/6 through 6/6, Type Check · debt ledger, Type Check · consumer gates, Type Check · workspace. Skipped (opt-in or matrix placeholders): Packed-tarball smoke, Temporal Conformance, Console Pin Gate, Dogfood Verify CLI, Build Docs, the Dogfood matrix placeholder, and one duplicate each of Check PR Size, Auto Label and Check Changeset. No check-run failed.
  7. The text-slot door list also gained HttpConfigSchema and the compile / publish-gate doors — verified true at the head (①) and inside the claim's "module docs' door lists" surface.

open_questions (A: leave as documented; B: file the two engine-side preconditions, then a spec card): A, answered, with one addition. Every authoring door already refuses the token with its remedy; B costs an engine.ts change in a file other claims hold plus about fifty re-pins, for a residue no measured author hits. The module doc records the condition under which the judges can join the parse, which is where the next reader of this question will look. The addition: the dev's first out-of-scope finding ("noted, not filed") reads as a reachable prose defect — os migrate meta --stored prints "keep reading canonically through the chain, so nothing is broken" for a flow row FlowSchema.parse refuses, which the boot then refuses — owner the stored-preview lane (domain:spec seat 2). ESCALATED: the seat decides whether it is filed as a card. The second finding (the lint-flow-patterns.test.ts copy of the docs example drifting from the page's CEL envelope) is low and may be filed or left at the seat's call.

Implemented-by: claude/issue-23320-slot-judges-at-parse
Reviewed-by: session_01YRp5z4kK6vgYkrdH97ztsy

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants