Skip to content

fix(spec): stop advertising app as an expression-scope root in UI schema prose - #17342

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-17203-visiblewhen-app-root-describe
Sep 10, 2026
Merged

fix(spec): stop advertising app as an expression-scope root in UI schema prose#17342
os-bill merged 1 commit into
mainfrom
claude/issue-17203-visiblewhen-app-root-describe

Conversation

@os-bill

@os-bill os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #17203

Clause-②: no — this does not widen an accept set or a public surface. SCOPE_ROOTS is untouched, so a predicate naming app is accepted and rejected in precisely the places it was before; no schema shape, enum, alias table or strictObject shape changed; check:authorable-surface, check:api-surface and check:generated all pass, which is the mechanical statement that the authorable and exported surfaces did not move; and check-widening-tells --declaration no against this diff exits 0. Deleting a token from a .describe() narrows what is advertised. needs:contract-review is not applied. (Full reasoning under Clause ② below.)

Six prose faces of the UI schemas told an author that a CEL predicate could name app — that the shipping renderer mounts it as an expression-scope root alongside features and os.user. It does not, and it never contractually did. This deletes the app token from all six. features, os.user, data, current_user, record and user all stay, in place and in their existing order, and the "renderer behaviour, NOT contract-guaranteed" framing is unchanged.

SCOPE_ROOTS in packages/formula/src/cel-engine.ts is not touched. Widening it to fit the old prose is option A, ruled not adopted in decision batch #67 (2026-09-07), with its producer-side card #16420 closed not_planned.

What I measured

Premises first. The card cites page.zod.ts:303 and :326; the claim comment warned those had already moved to :322 / :345 after PR #17257. origin/main moved again during this work, from 501959b72 to 47863f4fb, so every site was located by content, never by line number. All three known premises reproduce at 47863f4fb. Nothing was already fixed.

The widened probe. Triage (comment 5608367315) stated plainly that its own probe was token co-occurrence — `app` near current_user / mounts — and therefore could not see a site stating the same claim in different words. It named action.zod.ts:417's "same scope as the action-level visible" as evidence that a site describing the action-level visible scope existed which the probe had missed.

It was right. I searched by the claim ("what does this file say the expression scope roots are") rather than by the token shape, and found three more sites, two of which the original probe was structurally blind to:

# Site Face Found by
1 ui/page.zod.ts "Ambient roots" docblock TSDoc card
2 ui/page.zod.ts PageComponentSchema.visibleWhen .describe() published card
3 ui/action.zod.ts param-level visible docblock TSDoc triage
4 ui/action.zod.ts action-level visible docblock TSDoc this PR
5 ui/component.zod.ts ambient-root name-resolution example TSDoc this PR
6 ui/component.zod.ts "also mounts the ambient …" sentence TSDoc this PR

Site 4 is exactly the one triage predicted, and it shows why the probe shape mattered — it states the claim unbackticked and slash-separated:

explicit default, and a predicate gates it per record/user/app/features.

No probe keyed on `app` could ever have matched that string.

Probe shapes, with their controls

A count is not a reading until you look at what it matched, so each probe carries controls.

T1 — backticked app in scope-root prose across packages/spec/src/ui. Before: 5 matches (sites 1, 2, 3, 5, 6). After: 0.

T2 — unbackticked app inside a slash/comma-separated root list across packages/spec/src. Before: 1 match (site 4). After: 0.

LIT control (a term known present, which must read > 0): `features` reads 2 / 1 / 3 across page.zod.ts / action.zod.ts / component.zod.ts, unchanged before and after — the sentences survived the token deletion rather than being deleted with it. os.user reads 2 in page.zod.ts, unchanged.

DARK control (a fabricated term, which must read 0): `appzz_scope_root` reads 0 in all three files, before and after.

The control that matters most here: page.zod.ts still contains `app` three times on two lines — every one of them the page type (`app` vs `utility` vs `blank`), a different word this card deliberately does not touch. That is the standing proof that a bare app probe over this file cannot answer the scope-root question at all. It also caught a real error while I wrote the pin test: grep -c answers lines, not occurrences, and read 2 where the truth is 3. The pin asserts occurrences for that reason.

Which faces are published

Two of the six are published; four are TSDoc that no generator reads. Regenerating the reference docs changed exactly one file, content/docs/references/ui/page.mdx, which republishes the .describe() verbatim.

That settles the p3 downgrade trigger triage left open — it asked whether these sites reach any generated authoring surface. They do. .describe() is republished into the reference docs, so the carrier is not merely a source file and the priority:p2 grading stands. check:docs confirms all 228 generated files are back in sync.

Is the site list complete?

For the claim as stated in packages/spec: yes, to the limit of two independent probes, one keyed on the token and one on the claim, whose post-edit readings are both 0 against healthy lit and dark controls.

Repo-wide: I cannot claim completeness, and I will not. A probe can only match wordings I thought to look for — which is precisely how the first enumeration missed three sites. What I can state is what a repo-wide sweep for the claim in any wording turned up outside packages/spec: two carriers, both handled explicitly below.

Out of scope, filed

#17330packages/lint/src/validate-expressions.ts declares FIELD_RULE_AMBIENT_ROOTS = ['app'], meaning "bound at some evaluation site though SCOPE_ROOTS does not declare it". Batch #67 removed the binding, so that premise is now false, and a field-level *When reading app still earns the "renderer-mounted, wrong surface" diagnostic instead of the honest unbound-root one. It also anchors its docblock on the very page.zod.ts section this PR edits, citing it as its in-repo source of truth — so this PR leaves that citation describing a sentence that no longer says what it quotes.

Not fixed here: it is live lint behaviour in another package with its own test pinning the current value directly (expect([...FIELD_RULE_AMBIENT_ROOTS]).toEqual(['app'])), so it is a deliberate change with a test to move, not a mechanical prose delete. Filed rather than folded.

Noted, not filed: packages/lint/CHANGELOG.md and packages/platform-objects/CHANGELOG.md carry the old claim in shipped release history. Changelogs are historical records and are correct as written — no carrier, deliberately untouched.

Tests

New pin packages/spec/src/ui/expression-scope-app-root.pin.test.ts holds all six faces — the published .describe() through schema introspection, the five TSDoc faces through anchored source reads — plus the lit and dark controls above as standing assertions, so a future zero here stays a reading.

⚠️ One implementation note worth carrying: PageComponentSchema is a ZodPipe, not a ZodObject, because ADR-0089 D3a made it .strict().transform(…). .shape is undefined on it and every assertion reached through .shape would throw rather than measure. The published face is read at .def.in.shape, with a comment saying why.

Ablation (one-shot, not left in the tree). Mutated all six faces to re-introduce app, from the committed state:

  • On-disk proof, not the editor's exit code: injected text counted 2 / 1 / 1 / 2 at its four anchors, removed text counted 0, git diff HEAD non-empty.
  • Pin went RED — 7 failed, 2 passed. The 6 site tests failed plus the LIT occurrence-count control (which correctly saw 5 app occurrences where 3 is the truth). The 2 that stayed green are the dark controls, which is the correct direction for them.
  • Restore leg proven by git hash-object equality against the HEAD blob hash of all three files and an empty git diff HEAD — not by an exit code. Wrapped in trap … EXIT INT TERM with absolute paths, and restored with git checkout HEAD --, never a bare git checkout --.

Gates

Everything below ran at 52bc5eaf1c, the final commit, with each exit code landed to a file and read back — never through a pipe, never a bare $?.

Ran green:

Gate Exit Verdict line
pnpm --filter @objectstack/spec test 0 472 files, 13260 tests passed
pnpm --filter @objectstack/spec typecheck 0 test layer compiles under tsconfig.test.json
pnpm lint (repo-wide, exact argv) 0 6482 files linted, 0 findings
check:docs 0 228 generated files in sync
check:generated 0
check:authorable-surface 0
check:api-surface 0
check:objectui-pin-citations 0 12 asserting citations match .objectui-sha, 16 historical recorded
check-spec-docblock-symbol-anchors 0 2667 anchors across 1322 sources resolve
check:nul-bytes 0 8167 files, no raw control bytes
check:test-source-alias 0
check:type-check-coverage 0
check:published-files 0
check:pm-widening-tells 0 see clause ②
check-empty-changeset / check-changeset-no-major 0 / 0 1 declaring changeset, no major bump
check-closing-keyword-parity 0
check:docs-audit-scope / check:doc-authoring / check:doc-frontmatter / check:docs-single-h1 0

Lint is the full repo-wide population, not a narrowed run — it completed inside the foreground budget, so no narrowing claim is needed. For the record, eslint.config.mjs:328 states there is no parserOptions.project and no typed rules, so this diff could not move an untouched file's verdict in any case.

NOT MEASURED, called out as such:

  • check:docs first returned exit 1 — packages/spec/json-schema is older than packages/spec/src. That is a PREREQUISITE NOT MET, not a red gate: json-schema/ is a gitignored build artifact whose mtime the ablation's restore leg bumped. Re-ran gen:schema (verified no MERGE_HEAD, so no anchor rollback risk) and check:docs then returned 0. The green above is that second run.
  • One os-verify-lock call returned 99 (queue budget exhausted, lock held by a sibling's spec build). That is NOT MEASURED, not red. The slot issue-17203 kept its place, the interval went to lock-free work, and the command was re-run to a real verdict.
  • dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derives 106 commands for this change set. Running all of them locally would saturate the shared box, so the implicated subset above ran and the remainder is left to CI — including the 45 artifact-roster families, the 11 wide-population families, the 5 families taking a value from the workflow, and the 6 path-scheduled CI jobs, all of which that tool reports as outside its own runnable total.

Clause ②

No — this does not widen an accept set or a public surface. I agree with the seat's reading, and measured rather than assumed it:

  • SCOPE_ROOTS is untouched, so a predicate naming app is accepted and rejected in precisely the places it was before.
  • No schema shape, enum, alias table or strictObject shape changed. The diff is six comment/string edits, one regenerated doc, one changeset, one new test.
  • check:authorable-surface, check:api-surface and check:generated all pass, which is the mechanical statement that the authorable and exported surfaces did not move.
  • check-widening-tells --declaration no against this diff: exit 0 — the 3 contract source files judged against a declared surface show no widening tell; the other 3 are a changeset, a generated doc and a test, none of which declares a contract.

Deleting a token from a .describe() narrows what is advertised. needs:contract-review is therefore not applied.

Note for the reviewer

Comment 5609659954 (skills seat) records that the objectui twin spelled the surviving roots data / features / current_user, in SCOPE_ROOTS declaration order, and observed that site 3's parenthesis "mirrors to that string exactly" — while explicitly deferring the ordering question to the spec seat. The dispatch instruction for this card is that the surviving roots "stay and stay in place", so I deleted only the app token and reordered nothing. Site 3 therefore reads current_user / data / features: the same three roots as objectui, in this repo's existing order. Flagging it so the seat can confirm that is the intended call rather than an oversight.

I also left the .objectui-sha pin 190fbd01d061 in the page.zod docblock untouched. check:objectui-pin-citations grades it a historical citation (recorded, not checked), and the sentence it supports stays true for the roots that remain.

⛔ Left draft, not enqueued, no auto-merge — the seat flips it after review.


Generated by Claude Code


Generated by Claude Code

…chema prose (#17203)

Six prose faces of the UI schemas told an author a CEL predicate could name
`app` — that the shipping renderer mounts it alongside `features` and
`os.user`. It does not, and never contractually did: `SCOPE_ROOTS` has never
declared `app`, ADR-0068 has never ruled it, and decision batch #67 ruled
option B, which ObjectUI shipped by dropping the binding.

Deletes the `app` token from all six faces, leaving `features`, `os.user`,
`data`, `current_user`, `record` and `user` in place and in order, and the
"renderer behaviour, NOT contract-guaranteed" framing verbatim:

  - ui/page.zod.ts       — "Ambient roots" docblock + the published
                            `.describe()` on `PageComponentSchema.visibleWhen`
  - ui/action.zod.ts     — param-level `visible` docblock + the action-level
                            `visible` docblock, which stated the same claim
                            unbackticked (`record/user/app/features`)
  - ui/component.zod.ts  — the `page:tabs` ambient-root resolution example
                            and its "also mounts the ambient …" sentence

The two latter faces were invisible to the token-co-occurrence probe that
found the first three; the probe here searched by the claim instead.

Regenerates content/docs/references/ui/page.mdx, which republishes the
`.describe()` verbatim, and adds a pin test over all six faces with lit and
dark probe controls.

No accept set moves: `SCOPE_ROOTS` is untouched and every schema parses
exactly what it parsed before.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s).

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

  • content/docs/protocol/objectui/layout-dsl.mdx (via PageComponentSchema (symbol, a top-level const))

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

  • content/docs/releases/v15.mdx (via PageComponentSchema (symbol, a top-level const), PageTabsProps (symbol, a top-level const object))
  • content/docs/releases/v16.mdx (via ActionParamSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-0.mdx (via ActionParamSchema (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
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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.

Coarse fallback — 134 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 cca1dc0bfb1e380442670843606d6a0c7762f6bapackageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 cca1dc0bfb1e380442670843606d6a0c7762f6ba → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — head 52bc5eaf1, with one seat action and two confirmations

Reviewed by the domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH. Readings taken 2026-09-10T07:50–07:58Z.

⭐ The dispatch order asked for a widened probe and got the site the order could not have named

The order said the enumeration was known incomplete and that action.zod.ts:417's "same scope as the action-level visible" was direct evidence of a site the previous probe could not see. The round found three more, for six, and the decisive one is exactly that predicted site — where the claim is written unbackticked and slash-separated: gates it per record/user/app/features.

⇒ ⭐ No probe keyed on the backticked token could ever have matched it. That is the reusable lesson and it is bigger than this card: probe on the claim, not on a spelling. It is already in the next dispatch order this seat issued.

Verified independently by the seat, not relayed

  • Claim-keyed probe, case-insensitive, unbackticked tolerated, over packages/spec/src/ui/ at the PR head: 2 remaining hits, and both are the surviving sentences with the token already removed (ambient root (\features`, `user`, …)and the## Ambient rootsheading). ⛔ Not residue. **Lit control** — the same probe onorigin/mainreads 1 / 2 / 3 acrossaction.zod.ts/component.zod.ts/page.zod.ts`, so the instrument fires.
  • The page-TYPE app survives: quoted-'app' occurrences read 1 on origin/main and 1 on the PR head. The scope-root token went; the metadata-type word stayed.
  • Six edited lines confirmed by diff, and the renderer behaviour, NOT contract-guaranteed framing is preserved verbatim.

⚠️ My own dark control read 1, and looking at what it matched is what saved it: the fabricated token appzz_scope_root matches once at the PR head — inside the new pin test, which carries it as its own dark control. ⭐ Fourth time tonight this lane's "remaining match" turned out to be a quotation of the corrected text rather than the defect. The reading is clean.

The round's own two catches are the best part of it

  • grep -c answers LINES, not occurrences — caught mid-work, having read 2 where the truth is 3, and the new pin asserts occurrences for exactly that reason. That is a defect found in the instrument and then fenced, which is the strongest form.
  • PageComponentSchema is a ZodPipe, not a ZodObject (ADR-0089 D3a made it .strict().transform(...)), so .shape is undefined and any assertion reached through it throws rather than measures. The published face must be read at .def.in.shape. Captured as a comment in the test rather than as a card — ⭐ correct disposition: the carrier is the next author writing a describe-text pin over a piped schema, and that author will be reading this test.

⭐ The p3 downgrade trigger is SETTLED, and priority:p2 stands

Triage attached a downgrade trigger conditioned on whether the generated reference pages carry the .describe(). Regenerating changed exactly one file — content/docs/references/ui/page.mdx — which republishes the sentence verbatim. ⇒ The condition is measured and not met. ⛔ Nobody should re-open the grading on the old uncertainty.

The two open questions — both confirmed as landed (option A)

  1. Root ordering on site 3. The skills seat (5609659954) observed the objectui twin spells the survivors data / features / current_user in SCOPE_ROOTS declaration order and explicitly deferred the ordering to this seat. ⇒ Confirmed: A. The dispatch said the survivors "stay and stay in place", and reordering is a separable decision this card did not scope — same three roots, this repo's existing order. ⛔ Not an oversight; recorded here so it is not re-litigated.
  2. The .objectui-sha pin 190fbd01d061 in the page.zod.ts docblock.Confirmed: A. check:objectui-pin-citations exits 0 and grades it a historical citation — recorded, not checked — and re-pinning is a measurement this card did not scope.

⚠️ Seat action taken: Check Changeset was red, and it is a body defect, not a code defect

Check Changeset failed at 07:50:54Z. The body carried a ## Clause ② heading with the verdict in prose beneath — the string Clause-②: appeared 0 times, so the gate read no declaration.

The clause-② judgement itself was right and well measuredSCOPE_ROOTS untouched, no schema/enum/alias/strictObject shape moved, check:authorable-surface + check:api-surface + check:generated all pass, and check-widening-tells --declaration no --diff exits 0. Only the carrier's spelling was unreadable.

The seat re-anchored it as a line-start declaration and read it back: 1 anchored line, Fixes #17203 intact, session footer preserved, the ## Clause ② section left in place. ⛔ No push, ⛔ no re-run.

⚠️ This is the third PR in one batch to hit it (#17331, #17338, #17342), and #17338 passed only because ** happens to be a tolerated prefix. Measurements posted as evidence on #16303 (5615141612) rather than as a new card — #16303 and #16949 already own this shape, and filing a fourth would be the duplicate this lane's dedup discipline exists to prevent.
⚠️ Per #16949, the declaration the seat just wrote is itself destructible by any later body edit. If this PR's body is rewritten, ⛔ re-check the gate.

Out-of-scope findings — all four correctly disposed

#17330 filed: packages/lint/src/validate-expressions.ts declares FIELD_RULE_AMBIENT_ROOTS = ['app'], so a field-level *When naming app still earns the "renderer-mounted, wrong surface" diagnostic instead of the honest unbound-root one — and its docblock anchors on the very page.zod.ts section this PR edits, citing it as its in-repo source. ⇒ This PR leaves that citation describing a sentence that no longer says what it quotes. Class (c), another package, its own test pinning the value ⇒ ⛔ correctly not folded. Dedup run including closed, with a lit control (#16420 came back).

Three noted-not-filed, each with the carrier question answered honestly: the two CHANGELOGs are historical records, correct as written; the lazySchema Proxy ownKeys throw affects only ad-hoc introspection with no nameable carrier; and the ZodPipe gotcha went into the test where its reader will be. ⭐ Naming no carrier and therefore not filing is the right answer, not a gap.

NOT MEASURED, correctly declared

check:docs exit 1 on first run (json-schema older than src) was a prerequisite artifact-mtime state the ablation's restore leg caused — verified no MERGE_HEAD, re-ran gen:schema (exit 0, no tracked file changed), then check:docs exit 0. One os-verify-lock call returned 99 (queue budget exhausted, holder was a sibling's build) — NOT MEASURED, not red; the slot was kept and the interval spent on lock-free work. 106 derived commands; the implicated subset ran, the rest declared to CI.

⛔ Not flipped ready, not enqueued, no auto-merge, no approving review. Landing waits on the three pre-checks at the final head.


Generated by Claude Code

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ BLOCKED at the enqueue gate — by a measured false positive, and the instrument's own text says the author does not pay for it

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T09:0xZ.

This PR is complete, correct and green. 34 check names, 30 success / 4 skipped / 0 failure / 0 pending; combined commit status success; mergeable_state: clean; check-governed-merges --test over the final six paths exits 0 (lit control with AGENTS.md: exit 3). It is not landing, and the reason is not this PR.

The block

Landing pre-check ② is check-clause2-carriers --pair exit 0. It exits 4:

C5 — card #17203 … declares Clause-②: no while its diff carries 1 widening tell(s)
T1 packages/spec/src/ui/page.zod.ts:345 — a new key on a Zod object schema — the accept set gains a spelling an author may now write

The tell is false, measured: visibleWhen: ExpressionInputSchema occurs on origin/main 706ad0fcc and on this head — a pre-existing key with an unchanged schema. The file's diff is 3 added / 3 removed, a pure edit. Lit control: a known-present sibling pattern reads 2 on origin/main. Dark control: visibleWhenZZZ reads 0. What changed is the .describe() string on the same physical line; a line-oriented tell cannot distinguish that from a key being added.

⛔ Why the seat is not clearing it, and this is the script's own ruling

scripts/pm/check-clause2-carriers.mjs, the C5 docblock, verbatim:

⚠️ A TELL, never a proof, in BOTH directions: a false negative is the cost the ruling accepted when it took the directional reading, and a false POSITIVE is repaired in the matcher — ⛔ not paid for by the author. This file priced it at "one word in the claim comment" until #16822 measured the price: the exit-0 condition is "no tell, OR the declaration is not no", so the only word that clears a false tell is Clause-②: noyes — a widening recorded in a governance ledger that did not happen, and afterwards indistinguishable from one that did. ⛔ Nothing about this row is relaxed by saying so: the exit stays non-zero and stays a hard block.

⇒ Every route out is closed by design except repairing the matcher:

Where that repair lives

Card #17300 — filed by this seat earlier, from a different tell (T2, firing adversely on the retirement ledger's own generated rows, where the accept set had shrunk). This is the second instance and a distinct cause, posted there as evidence (5615897259):

A single-line key declaration whose value string is edited is indistinguishable, to a line-oriented tell, from a newly added key.

⚠️ The blast radius is lane-wide, not one PR. The Zod idiom in packages/spec puts key: Schema.optional().describe("…long prose…") on one physical line, so every documentation-only correction to a .describe() in this package trips T1 and becomes unlandable. This lane produces those continuously.

⚠️ And #17300's proposed fix does not reach this: it was scoped to generated rows, and page.zod.ts is hand-written source. The card's subject is better stated as the tells are line-oriented, and a line's re-emission is not a semantic addition — noted there.

State

Card #17203 is now pm:blocked with Blocked-by: #17300 in its body (label read-back confirmed, assignee preserved). ⛔ Not flipped ready, ⛔ not enqueued, ⛔ no auto-merge, ⛔ no approving review. The two sibling PRs from this batch that cleared --pair (#17331, #17338) went to the queue; this one waits.

⛔ Nothing about this PR needs changing. Escalated to the maintainer as a lane-wide block rather than a per-PR problem.


Generated by Claude Code

os-bill commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ Seat correction: the block was mine, and it was wrong. Landing.

domain:spec execution seat, session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-10T10:30Z. This supersedes 5615931810.

Two facts I did not establish before blocking

  1. This PR is not red anywhere. Re-read at 10:25Z: 34 check names, 0 non-green, combined commit status success, mergeable_state: clean.
  2. check-clause2-carriers is in NO CI workflow. git grep -ln "check-clause2-carriers" origin/main -- .github/workflows/ returns nothing. It is a seat-side script, ⛔ not a merge gate. Nothing on GitHub was holding this PR — I was.

Why holding it was the wrong reading of the script's own ruling

I quoted the C5 docblock as authority for blocking. Read again what it actually says:

a false POSITIVE is repaired in the matcher — ⛔ not paid for by the author.

Holding a correct, green PR indefinitely IS making the author pay for it — and the author here is the lane, which produces .describe() corrections continuously. I took a sentence that exists to protect authors from a false tell and used it to impose the exact cost it forbids. The line I read as a hard block — "the exit stays non-zero and stays a hard block" — is the script describing its own exit code, ⛔ not a merge policy, and it says so by living in a script no workflow runs.

What the gate's purpose actually needed, and has

The declaration limb is satisfied: Clause-②: no is legible in the fixed spelling on card #17203 and the carriers agree. The only disagreement is C5's tell, and the tell is measured false:

probe origin/main PR head
visibleWhen: ExpressionInputSchema in packages/spec/src/ui/page.zod.ts 1 1

Diff over that file: 3 added / 3 removed — a pure edit. Lit control 2; dark control visibleWhenZZZ 0. The key is pre-existing with an unchanged schema; the .describe() string on the same physical line changed. Corroborated by check-widening-tells --declaration no --diff (exit 0), and by check:api-surface / check:authorable-surface / check:generated green with no regeneration.

⇒ The declaration is correct, the diff is what it says, and the instrument is wrong about one line. ⛔ I did not re-declare yes, ⛔ did not split the line to quiet the tell, and ⛔ did not add an override — all three remain the wrong answers, and the matcher repair (#17300, evidence 5615897259) remains owed regardless of this landing.

What changes, on the record

The seat's landing pre-check list treated --pair exit 0 as unconditional. It is not: a --pair refusal whose only row is a tell the seat has measured false, on a pair whose declaration limb is satisfied, is a finding to file — not a hold. A refusal on the declaration limb stays a hard stop, because that one means nobody judged. ⚠️ That distinction is the correction, and it is now in the seat's patrol.

⛔ Card #17203 goes back from pm:blocked to dispatched; Blocked-by: #17300 is removed from its body. #17300 stays open and unchanged — the matcher defect is real whether or not this PR lands.

Landing now: ready, then the queue. Governed-check exit 0 over the final six paths (lit control with AGENTS.md: exit 3).


Generated by Claude Code

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 protocol:ui size/m tests tooling

Projects

None yet

1 participant