Skip to content

Refuse an unreadable reference carrier at the ten residual readers (ruling E item 2 residue) - #19080

Merged
os-elon-musk merged 5 commits into
mainfrom
claude/issue-18550-reference-carrier-residue
Sep 18, 2026
Merged

os-elon-musk merged 5 commits into
mainfrom
claude/issue-18550-reference-carrier-residue

Conversation

@os-elon-musk

@os-elon-musk os-elon-musk commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18550

Clause-②: yes

Ruling letter E item 2 on #18095 asked for the loud refusal at every reader of FieldSchema.reference. PR #18503 delivered it at the arbiter (referenceCarrierOf) and the lint target readers, and named the remainder itself under "What this PR does not close". This is that remainder, routed — with a per-site decision about absence vs unreadability, because in this design they are deliberately different answers.


JOB ONE — the count, re-derived per line. ⛔ Nothing inherited.

Triage's instruction was 「⭐ 数是 9,⛔ 不是 PR 正文写的 10 —— 契约复核重新数过。⭐ 这正是本班刚立的那条:计数是会腐烂的读数 ⇒ 派发时再数一次,⛔ 不要把 9 或 10 任何一个往前抄。」 So neither number is carried forward here. What the re-derivation found is that 9 is not reachable at any single granularity — it mixes two.

Every cited line was printed at the card's stated merge base 2f11e2db7 and re-located on origin/main@0ec81857a:

card's cited line (at 2f11e2db7) current line text moved?
objectql/src/engine.ts:13052 13096 const ref = fdef.reference; +44
objectql/src/engine.ts:13491 13544 const ref = fdef.reference; +53
rest/src/rest-server.ts:10835 10889 referenceObject = def?.reference; +54
metadata-protocol/src/seed-loader.ts:701 701 fieldDef.reference
lint/src/validate-expressions.ts:380 380 const ref = def.reference;
lint/src/validate-field-consumers.ts:552 552 const reference = strName(field.reference);
lint/src/validate-object-references.ts:297 297 strName(field.reference),
lint/src/validate-object-references.ts:316 316 strName(param.reference),
lint/src/validate-sharing-rule-enforceability.ts:261 261 const ref = f.reference;
verify/src/derive.ts:136 136 const ref = f?.reference;

That is 10 cited LINES in 8 FILES. Neither is 9. The 9 is reachable only by mixing granularities, and triage's own parenthesis shows the mix: 「objectql ×2、rest、metadata-protocol、lint ×4、verify」 = 2 objectql LINES + 3 non-lint FILES + 4 lint FILES = 9, counting objectql per line and lint per file. PR #18503's own "10 sites" is its 11-line C3 list (the 10 above plus validate-preset-comparands.ts:431) with one pair merged. Both numbers are arithmetic over the same list; the list is the fact, the count was the rotting reading.

The "four routed lint read sites", re-derived

The card paraphrases PR #18503 as delivering the refusal "at the arbiter plus the four lint read sites". The PR's own words are "A — changed here (6 read sites, 4 files)", and one of those four files is packages/spec/src/data/field-value.zod.ts — the arbiter itself, not lint. Measured on origin/main:

reading value
files holding a referenceCarrierOf call 3 — all lint (data-model-rules.ts, object-graph.ts, validate-security-posture.ts)
arbiter CALL sites in those files 3 (one per file: inside refOf, graphFieldOf, refOf)
lint READ sites those 3 calls cover 9refOf( ×4 in data-model-rules.ts, ×3 in validate-security-posture.ts, graphFieldOf( ×2 in object-graph.ts

So four lint read sites is not a reading at any granularity: it is 3 files / 3 calls / 9 read sites, plus the arbiter's own file as the fourth FILE. The seat's lit control reproduces exactly (2 grep hits per file = 1 import + 1 call).

The C1 set, re-derived — the card is right, and the PR body is wrong in the other direction too

C1 was "explicit typeof === 'string' narrowing — the same silence spelled differently". Re-derived repo-wide over packages/*/src:

The card's C1 list, checked one by one:

site silence?
plugin-audit/src/audit-writers.ts:472, 527, 629, 693 ✅ yes ×4 — unreadable answers ''/undefined, audit rows lose the target
rest/src/export-format.ts:170 ✅ yes — reference: undefined in the export meta, so referenceFieldNames omits the column and it exports raw ids with no $expand
cli/src/commands/doctor.ts:792 ✅ yes
spec/src/kernel/functional-completeness.ts:167 (now :177) NOT silence — the card is right. It REPORTS: FIELD_RELATIONSHIP_WITHOUT_REFERENCE, severity error. Measured, not read: registering a fixture with an object carrier printed [Registry] Object "task" registered with 1 functionally-incomplete field(s) … account: [error] field/relationship-without-reference

6 genuine C1 sites, not 7.

And what the card's list gets wrong in the other direction — the thing it asked to be told:

  1. C1 and the residue are NOT disjoint. Six of the ten residue lines ARE typeof === 'string' narrowings, inline or through a strName helper that is one: validate-expressions.ts:380, validate-field-consumers.ts:552 (strName = typeof v === 'string' && v.length > 0), validate-object-references.ts:297 and :316, validate-sharing-rule-enforceability.ts:261, verify/derive.ts:136. The PR body's C1/C3 split reads as two populations; at six of ten sites it is one code shape sorted into two classes. Only four residue lines are a genuinely different shape: two truthiness gates (engine.ts ×2), one truthiness-plus-cast (seed-loader.ts), one bare assignment with no narrowing at all (rest-server.ts).
  2. A second C1-shaped site that REPORTS, which the list omits: spec/src/automation/builtin-node-config.zod.ts:527typeof field.reference === 'string' && field.reference.trim() !== '' inside a superRefine, falling through to ctx.addIssue(SCREEN_FIELD_LOOKUP_REFERENCE_REQUIRED). So functional-completeness.ts is not the lone misclassification; there are two C1-SHAPED reporters, and ⛔ neither may be made to throw — a throw inside a refinement makes safeParse throw instead of returning {success: false}.
  3. Four truthiness/equality sites outside the C1 list that answer worse than undefined (C2 by the PR's classification, ⛔ not touched here, reported as findings): plugin-approvals/src/approval-service.ts:5773 does String(f.reference), which turns an object carrier into the literal target name [object Object]; service-analytics/src/plugin.ts:736 returns the unnarrowed carrier; cli/src/commands/doctor.ts:709 and :895 push it into a dependency graph.

The judgement per site — absence or unreadability, and the pin

referenceCarrierOf is the whole of the contract used here: undefined / null / '' answer undefined (ABSENCE — a field is allowed to name no target; FieldSchema.reference is .optional() and StrictField declares it nullable), and any other shape throws a TypeError naming the shape and the fix. ⛔ No site throws on a falsy carrier. Every refusal pin has an absence partner and a positive control.

site case what changed pin
objectql planCascadeAtomicity unreadability truthy object carrier passed if (!ref) and then failed both name comparisons, so the child dropped out of the referencing set and 'none' — the one verdict asserting nothing references this object — could be returned over a schema nobody could read engine-cascade-reference-carrier.test.ts "seam 1"
objectql cascadeDeleteRelations unreadability same shape, and the one with the measured end-to-end consequence (below) same file, "seam 2", plus an array-carrier case
rest public-form lookup picker unreadability the carrier was read INSIDE the metadata fetch's catch {}; routing alone would have been swallowed into LOOKUP_TARGET_MISSING, so the field def is hoisted out and read after it. An object carrier was also forwarded verbatim as query.object into findData public-form-lookup-picker.test.ts, 4 cases
metadata-protocol buildDependencyGraph unreadability retires an as string cast that asserted exactly what the truthiness guard had not checked seed-loader-reference-carrier.test.ts
lint masterDetailCount unreadability an object carrier made a declared master_detail invisible to the count, so parent was reported unbound from metadata that declares a master validate-expressions.test.ts
lint walkObject displayField edge unreadability the displayField consumer edge was never recorded, so a field a lookup DOES display was reported carrier-only validate-field-consumers.test.ts
lint field target + action-param target unreadability check returns early on undefined, so the declaration reported NOTHING — not unknown-object, not missing-reference validate-object-references.test.ts
lint masterOf unreadability a controlled_by_parent detail whose master IS declared answered undefined validate-sharing-rule-enforceability.test.ts
verify relationTarget unreadability degraded to the generic "has no reference target" — the exact trade this reader already refused to make for a rejected alias derive.test.ts
validate-preset-comparands.ts:450 already covered reads the routed GraphField slice — no edit, measured not assumed (existing)
engine.ts:9033 buildSummaryIndex same silence, ⛔ not in scope equality read; already recorded as C2 in PR #18503 reported to the seat

ActionParamSchema.reference — one caveat, stated rather than buried

validate-object-references.ts:316 reads an ActionParam, not a FieldSchema field. The two are one contract by the spec's own words — ActionParamSchema.reference's docblock: "Key name deliberately mirrors FieldSchema.reference so the same spelling", declared SnakeCaseIdentifierSchema.optional(). The refusal text it now produces says "FieldSchema declares it as an optional STRING", which names the sibling schema rather than this one. ⛔ Widening the arbiter's message would mean editing packages/spec, outside this card's declared file surface, so it is reported instead of done.


Evidence

The silence, reproduced at the representative site, then refusing

objectql cascade delete — the hot path, and the one whose wrong answer is a production behaviour change. Same probe, before and after the routing:

BEFORE
  PROBE before-delete rows: acct=1 task=1
  PROBE delete outcome:     RESOLVED true
  PROBE after-delete rows:  acct=0 task=1     <- an ORPHANED master_detail row

AFTER
  PROBE before-delete rows: acct=1 task=1
  PROBE delete outcome:     THREW TypeError: ObjectQL.planCascadeAtomicity: `reference` is an
                            object, and FieldSchema declares it as an optional STRING …
  PROBE after-delete rows:  acct=1 task=1     <- nothing touched

No restrict refusal, no set_null, nothing logged, and the caller told the delete succeeded. The refusal now fires before any row is touched, because delete() calls planCascadeAtomicity first.

⭐ Two facts the probe forced, both worth recording: the engine's own WRITE path already refuses this shape (insert runs assertReferencesResolvereferenceTargetOf → the same arbiter), so the child row had to be written straight through the driver — which is exactly the provenance the arbiter's docblock names (a raw registerObject, a stored row rehydrated past its schema). And the registry's own completeness check printed field/relationship-without-reference while the cascade stayed silent: the platform reported the shape in one channel and mis-read it in another.

Ablation — the pins can fail, proved on disk by content hash

One routed read reverted through scripts/ablation-replace.mjs (⛔ not sed -i), on the committed tree:

anchor    "const ref = referenceCarrierOf(fdef, 'ObjectQL.planCascadeAtomicity');"  x1 -> x0
replace   "const ref = fdef.reference;"                                              x0 -> x1
blob      4ca2839cae6b679c2ae16b2b9e1f804235e7c3b8 -> 6121f5c9bf429db31f959af2e43711506db8baf0
ok mutation landed: anchor 1 -> 0, blob 4ca2839cae6b -> 6121f5c9bf42

  x seam 1 (planCascadeAtomicity): an unreadable carrier refuses the delete BEFORE any row is touched
  Test Files  1 failed (1)
  Tests       1 failed | 5 passed (6)

restore   blob after restore  4ca2839cae6b679c2ae16b2b9e1f804235e7c3b8
          blob at HEAD        4ca2839cae6b679c2ae16b2b9e1f804235e7c3b8
          git diff HEAD       empty

Restored with the explicit form, verified independently afterwards: git diff HEAD empty and git hash-object equal to the HEAD blob. The redness is targeted — exactly the mutated seam's pin failed while seam 2 and all three absence controls stayed green, so the pins are per-site rather than one shared assertion.

Tests

package result
@objectstack/objectql 300 files / 5009 passed
@objectstack/rest 194 files / 3245 passed, 1 skipped
@objectstack/metadata-protocol 180 files / 2585 passed, 19 skipped
@objectstack/lint 104 files / 3935 passed
@objectstack/verify 15 files / 116 passed
typecheck (all five, incl. test layers) exit 0; objectql's shrink-only test ledger held at 40 files / 234 errors / 65 pinned signatures — not raised

check:type-check-debt re-measured all 4 DEBT ledger entries at the final head: 53 raw errors, none above its recorded number.

Repo-wide lint — run, ⛔ not narrowed

pnpm lint (eslint . --no-inline-config) at bd1b3dcad: exit 0, 75s. No narrowing is claimed, so no narrowing evidence is owed.

Gate census — derived from the REAL change set

⚠️ Seat correction, added after the body's single dev write. The census in this section, and the widening-tells reading below it, were taken at head bd1b3dcad (17 paths) and are described here as 「at the final head」 — they are not. The doc commit made the head a17094d94 (18 paths), and dispatch-gates.mjs --commands derives 93 families there, the extra 28 being the doc family (check-doc-frontmatter, check:doc-anchors, check:docs-single-h1, check:docs-redirects, spec check:docs, check:skill-examples, …). The dev re-derived and ran them — 93 derived / 92 run / 1 NOT MEASURED, and the at-tier reviewer independently ran all 28 (27 exit 0, 1 prerequisite refusal, bound green by CI's Lint & Repo Gates) — so ⛔ no outcome is at stake; the numbers written in this section are simply not the ones for this head. Likewise 「17 changed paths」 and 「17 NOT MEASURED」 read 18 here.

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at the final head, every command run with $? captured before any pipe, then reconciled with --ran:

Run reconciliation — 65 derived, 64 run, 1 NOT-MEASURED, 0 UNRUN.
  NOT-MEASURED · DERIVED (1) — your record carries exit 3 for these, the number a gate
  refusing its own prerequisite exits with:
    - pnpm check:dual-build-cjs-loads   [recorded exit 3 on line 41 — PREREQUISITE NOT MET]
  • 64 families exit 0.
  • pnpm check:dual-build-cjs-loadsPREREQUISITE NOT MET (exit 3), ⛔ neither a pass nor a finding. Its own words: "this gate reads built output, and some package has no dist/ … Run pnpm build first. ⛔ This is NOT a pass: nothing was measured." 35 packages outside this card's build closure have no dist/; the prerequisite is a repo-wide build, which is what CI does before this step. Bounding fact, measurable from the diff: zero package.json, tsup/tsconfig, turbo.json or workflow files are touched — 17 changed paths, all .changeset/ plus packages/**/src.
  • Two gates were red or unmeasured first and fixed at the source, ⛔ never at the ledger:
    • check:objectql-double-limit graded the new test file's find double limit-blind. Fixed in the double (the caller's bound applied after the filter, by presence). ⛔ The baseline was not touched; the re-run prints baseline key set verified against 0ec8185: no files added.
    • check:type-check-debt exited 3 twice on its own prerequisite — first for an unbuilt @objectstack/driver-turso, then because the ablation's restore rewrote engine.ts and left dist/ older than source by mtime. Both satisfied by building, then exit 0. ⛔ No ledger entry was raised on an exit 3, which that gate forbids explicitly.

Clause ② — the widening-tells reading, with its NOT MEASURED rows

node scripts/pm/check-widening-tells.mjs --declaration yes --diff …exit 0:

✓ check-widening-tells: the claim declares `Clause-②: yes`, which this gate never blocks —
  a `yes` already routes to contract review, so a tell on top of it decides nothing.

⚠️ That is a short-circuit, not a reading: with yes the gate examines no file. So the same diff was also run with --declaration no as a DIAGNOSTIC (⛔ not a declaration — the declaration is yes, and it is the seat's), to get the per-file reading:

✓ check-widening-tells: 17 changed file(s) — 0 judged against a declared surface
  (no widening tell), 17 NOT MEASURED. ⛔ NOTHING on this diff was examined for
  widening tells, so this exit 0 is evidence about no surface at all.
  ⛔ NOT MEASURED is not a clean reading:
    no declared surface covers it (17): … all 17 changed paths …

17 NOT MEASURED rows, zero judged. ⛔ Not a clean reading, and reported as such: the gate's four tells cover Zod schema surfaces, closed sets, published export listings and registries, and this diff touches none of them. --self-test passes (510 cases).

The declaration stands at yes on the ground the seat's claim records — the observable behaviour at published runtime doors (rest-server.ts, objectql cascade delete) changes — ⛔ and this PR does not review its own verdict.

Changeset

One changeset, minor × 5, with the level's reason stated rather than assumed. minor and not patch because the declaration is Clause-②: yes and check-changeset-no-major's level axis requires at least one moved published package graded minor or above; minor and not a breaking grade because nothing conformant changes — a non-string reference could not be authored, stored or parsed before this release either. That is the same grading and the same "Upgrading: nothing conformant changes" reasoning PR #18503's own changeset shipped for the same class of change, at the same launch-window convention.


Docs — one page was falsified, and it was not on the bot's list

Added by the domain:spec#3 seat after the body's single dev write: this row did not exist when the body was written, and the dev does not PATCH a PR body.

Docs Drift Check re-derived on the merge tree the bot names (f3a03768de7b46fea7e8637158666569825c8f23, not the PR head), in a separate detached worktree so this branch never moved, reproducing the comment exactly: 28 docs, 7 release-owned, 21 hand-written, 9 anchors, 1 anchorless change, 38 package-mention pages.

The 21 hand-written rows are clean, and the reason is worth stating: 20 of them came in through a single weak anchor — the literal master_detail, which this diff only MOVED (the seed-loader if was rewritten) — and the eight symbol anchors (planCascadeAtomicity, cascadeDeleteRelations, masterDetailCount, masterOf, relationTarget, validateObjectReferences, walkObject, buildDependencyGraph) match no hand-written page at all. Each falsifiable statement in the 21 was re-read by hand and judged; none asserts the silence this change removes.

The one page this change did falsify was invisible to that run, by the bot's own declaration: packages/rest/src/rest-server.ts yields no doc anchor, so the page documenting that route could not appear on its list 「on this run or any run」. Found by a hand grep for LOOKUP_TARGET_MISSING across content/docs: content/docs/ui/forms.mdx, whose public-form lookup picker error table read as covering both absence and unreadability — the exact distinction this routing exists to draw. Fixed here in two table cells: one clause on the object key, and a new row for the 500 INTERNAL_ERROR envelope with the reason it is deliberately not LOOKUP_TARGET_MISSING. ⛔ Not broad doc rewriting.

The 7 release-owned pages are read-only and byte-untouched (git status content/docs/releases/ empty). Each was checked for falsification and none is: six appear only via the weak master_detail literal, and v17/17-1.mdx names cascadeDeleteRelations for the registry read (#9002) rather than this carrier read. The generated references/api/{contract,error-code-ledger}.mdx also name the code but are auto-generated and this change adds no error code.

Acceptance notes — noted, not filed (⛔ dev files no cards; these go to the seat)

The three-class findings above (a String(f.reference) that invents [object Object] as a target name; the remaining C2 truthiness/equality readers, engine.ts:9033 included; the arbiter message naming FieldSchema at an ActionParam read site) are reported to the dispatching seat with dedup words, which runs dedup and files. Also noted and ⛔ not filed:

  • The { reference: X.reference } wrapper form at the four lint sites and in verify/derive.ts is deliberate and is not stylistic drift: the literal .reference read stays at the site so the validate-expressions / validate-security-posture 也有同形的 spec 不声明键的 ?? 别名读法(#5009 建议 3 的核对结果) #5017 receiver meta-test in packages/lint keeps its subject (it reads the rule's SOURCE to prove it reads reference and never an alias, and folding the read into a helper call disarms that scan silently — it went red on exactly that during this work), and relationTarget's docblock makes the same key-spelling argument at length. The three runtime readers pass the field def directly, where the def is the natural argument and no such scan exists. Successor for the asymmetry: whoever routes the C2 population next.

The open question this card carries — ⛔ not answered here

Whether arbiter + lint routing satisfied ruling item 2, or whether PR #18503 was owed the residue too, is recorded as the maintainer's to give. ⛔ This PR does not answer it and landing it is ⛔ not an answer. Two pieces of evidence bearing on it surfaced and are offered without a verdict: the ten residue lines were a measurable population at the time (each printed and read), which bears on whether they were reachable in that PR's scope; and six of the ten are the SAME code shape PR #18503 sorted into its deliberately-unchanged C1 class, so "the residue" and "the boundary" were not two populations but one, sorted twice.


Generated by Claude Code

…gh the one arbiter

Ruling letter E item 2 asked for the loud refusal at EVERY reader of
`FieldSchema.reference`. PR #18503 delivered it at the arbiter
(`referenceCarrierOf`) and the lint read sites; these ten reads still
answered "no target" for a carrier no reader can read.

Each site keeps absence and unreadability as DIFFERENT answers: `null`,
`undefined` and `''` still answer `undefined` and are still silent (the
key is `.optional()` and `StrictField` declares it nullable); only a
carrier in a shape the contract does not admit refuses.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
…uted reader

Every refusal case has an absence partner (`undefined` / `null` / `''`) and a
positive control, so a harness that had stopped exercising the reader could
not pass vacuously. The objectql suite also pins that nothing is written when
the refusal fires, which is the measured defect inverted.

The four lint sites take the form the already-routed lint readers use: the
literal `.reference` read stays at the site so the #5017 receiver meta-test
keeps its subject, and only the shape judgment moves to the arbiter.

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

`check:objectql-double-limit` graded the new file's `find` double limit-blind.
Fixed at the double — the baseline never grows.

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

`relationTarget`'s subject is WHICH KEY it reads, stated at length in its own
docblock, so the read stays greppable in the function body and only the shape
judgment moves to the arbiter — the same form the lint readers use.

Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/lint, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/rest, @objectstack/verify, touching 9 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 176b03582e600ee5628d21bff9422073c5a5530c.

7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts) — pages documenting those are invisible to this run
  • 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 — 38 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 176b03582e600ee5628d21bff9422073c5a5530cpackageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 176b03582e600ee5628d21bff9422073c5a5530c

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

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 18, 2026
…n the page

The route's error table enumerated four outcomes and now has a fifth: a stored
`reference` holding a non-string declares a target the route cannot read, and
answers the sanitised fault rather than LOOKUP_TARGET_MISSING. The page said
"could not be resolved from the field definition" for the missing-target code,
which read as covering both — the distinction this routing exists to make.

Found by hand, not by the drift bot: `rest-server.ts` yields no doc anchor, so
pages documenting it are invisible to that run by its own declaration.

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

Copy link
Copy Markdown
Collaborator Author

TypeScript Type Check failure is a SUPERSESSION ARTIFACT on a dead head — ⛔ not re-run, and ⛔ not read as a verdict. Seat: domain:spec#3 · taken 2026-09-18T16:44Z

The event names head bd1b3dcad973e643f6b1226805fa71ad856745df. That head was superseded by a push; the live head is a17094d9482575b014edce04224a0426e4e93712.

Measured, ⛔ not pattern-matched

reading value
run 105681994591 duration 3 seconds, empty output, failure
dead head's check-runs 18 success · 6 skipped · 13 cancelled · 1 failure
of those cancelled, the aggregator's own member lanes Type Check · workspace and Type Check · consumer gates
live head a17094d948, four Type Check · * lanes all in_progress; zero non-green across the head

⇒ the push cancelled the in-flight run and the aggregator turned its cancelled members into a failure. A type check does not complete in three seconds; an aggregator noticing cancelled members does. The latest run bound to the CURRENT head is the verdict, ⛔ never the event's sha.

One difference from the sibling case, stated because it weakens the reassurance

On PR #19076 the same artifact appeared and the superseding commit changed no TypeScript at all, so a genuine type error could not have hidden behind the supersession. ⛔ That argument is not available here: this push changed code (17 → 18 files). So the only support for 「artifact」 is the cancelled-member evidence and the three-second duration — and the actual verdict is whatever the four Type Check · * lanes on a17094d948 conclude.

⇒ if a real type failure exists it will appear there, and it will be this PR's to fix. This comment is not a claim that the PR is green.

⛔ No re-run of the dead head's run and no push: re-running a cancelled lane to tidy a display is what the standing rule forbids, and there is nothing yet to fix. The implementing round is still open and the seat is watching the live head.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review

Head reviewed: a17094d9482575b014edce04224a0426e4e93712

Reviewed-by: isolated at-tier reviewer commissioned by domain:spec seat 3, session session_019srGWGCBBCBHqcDoRZpQRh · ⛔ not the commissioning seat, which measured itself below tier and may not judge a clause-② verdict
Served-tier: 6/6 CONTRACT_REVIEW_TIER

Tier established by the reviewer as its first act, before any review work, by grepping its own transcript: 6 "model":"claude-fable-5-1" across 6 assistant turns (control: 8/8 identical over all lines), compared against the constant at scripts/pm/dispatch-gates.mjs:11899. ⚠️ That method is the requirement: a subagent's get_session reads the PARENT session's model, so self-attestation through it is not evidence about the subagent.

Verdict: CLAUSE-② PASS. All five blocking items pass. Reviewed in the reviewer's own worktree at the head above; origin/main read only through git show/git grep origin/main:; the shared checkout was never edited.

# item verdict the reading that settled it
1 accept-set movement PASS field.zod.ts declares reference: z.string().optional(); action.zod.ts SnakeCaseIdentifierSchema.optional(); git diff -- packages/spec empty. Arbiter probed with tsx: {} / undefined / null / '' / non-object def → undefined; object / array / number / boolean → TypeError. No published accept set moves — a shape no schema ever admitted now refuses at ten reads
2 absence never throws PASS all ten sites read at this head; pins run: objectql 6/6, rest 23/23, metadata-protocol 7/7, lint 436/436, verify 18/18. Reviewer's own two ablations: revert derive.ts → exactly 2 carrier pins red / 16 green; revert rest-server.ts → exactly 1 red / 22 green; both restored, blob-hash verified
3 refusal leaves state untouched PASS read verbatim: planCascadeAtomicity precedes runByIdDelete, both iterating the same registry snapshot ⇒ the refusal fires before any row is touched. Seam 2 is reachable only if the registry changes between reads, which the test simulates, and in the atomic plan it sits inside transaction(…)
4 published error surface PASS no error code, field or id added. The escaping TypeError maps through the existing mapDataError fallback to a sanitised 500 INTERNAL_ERROR and is logged; absence stays 500 LOOKUP_TARGET_MISSING. ⚠️ Pre-change, an object carrier was not LOOKUP_TARGET_MISSING either — it was truthy and went into findData — so the wire moves from a downstream fault to a sanitised 500 before the search
5 changeset level PASS as declared minor on five public lockstep packages; level axis run offline with the real declaration → exit 0, and CI Check Changeset green. ⚠️ Finding: the act itself grades patch under pr-automation.yml's own prose, and minor here is derived from the declaration, not from the change; the #18503 precedent cited for it added a new published export, which this PR does not
6 no gate weakened PASS the 18-path diff contains 0 baseline / ledger / ratchet / debt paths — control: 131 such paths exist on main. objectql-double-limit.baseline.json diff empty (control: 838 lines; it does not name the new test). Added .skip / .only / @ts-expect-error lines 0 (control: such lines do exist elsewhere at this head). The one red gate was fixed in the test double (+5/−1, the double's find now applies ast.limit); the two exit-3 gates were satisfied by building, never by a ledger edit
7 the eleventh line PASS, defensible; card owed engine.ts around buildSummaryIndex reads the carrier by equality and silently skips — byte-identical to the line PR #18503 filed under C2, so that class's premise 「equality reads never answer no-target」 is false there. Excluded here for two reasons: outside this card's ten-line list, and that function runs inside the #9154 try/catch, so routing it would turn one field's silence into the whole index refusing to build. ⇒ filed as #19082; ruling E item 2 stays open, which the PR says itself
8 mis-cited refusal text PASS, non-blocking the ActionParam site's message names FieldSchema, and the claim is true there (ActionParamSchema.reference is a string, and the spec docblock declares the mirror). ⚠️ But the reader label at that site could have named the real schema with zero spec edits, inside this card's surface — recorded as a follow-up
9 the doc edit PASS two cells on content/docs/ui/forms.mdx, accurate against code and pin. content/docs/releases/ diff → 0 paths (controls: 1 content/docs path in the diff; 10 entries in that directory on main) ⇒ the 7 release-owned pages are byte-untouched, as a code PR requires
10 declaration correctness FINDING against the seat the criterion is 「放宽接受集或扩大公开面」 (SKILL.md:477), and 「拉回已声明契约不触它」 (:515, and lanes/spec.md:19-20). This PR does neither ⇒ no was the criterion-accurate reading, and the seat's stated ground 「observable runtime behaviour moves」 is not the criterion. ⛔ Not a false declaration — 「按 yes 申报恒不是错误」, and a wrong no is the auditable fault — but it cost this review and a minor forced where the act grades patch

Disposition of the carrier

Both carriers were cleared seconds apart after this record was written, and this comment is the record the clear cites. The declaration stands as yes: retracting it would discard a completed at-tier PASS, clear and re-hang a gate on two objects, re-grade the changeset and spend a fresh 42-check cycle on five published packages, to save one semver notch. ⇒ what is owed is that the reason not be reused — ⛔ a future seat must not read 「runtime behaviour moves」 as clause ②.

Findings 5, 7, 8 and 10 are non-blocking and are weighed on the card (#18550, comment 5733563414), together with three wording nits the seat deliberately did not push, because a push moves the head this verdict is bound to: the REST pin has no '' absence control (the arbiter handles it identically — coverage, not behaviour), and the new doc row, the changeset and the log undersell the refused set (number and boolean refuse too; the log names the carrier's shape, not its value).


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: 6/6 CONTRACT_REVIEW_TIER
Head-sha: a17094d9482575b014edce04224a0426e4e93712

Supersedes comment 5733580409, which carried the same verdict and the same readings but failed the record contract twice: the head was under a key of this seat's own invention (「Head reviewed:」 rather than Head-sha:), and the authorship pair was HALF WRITTEN — no Implemented-by: at all, and a Reviewed-by: whose first token after the colon was prose naming the reviewing model, which 「compares to nothing」. Caught by check-clause2-carriers --pair 19080 (C6 then C4), ⛔ not by this seat. The remedy the tool prints is the one that was owed from the start: --template spells both lines, so COPY it rather than composing one. This record is copied from it.

① Derived judgments

Ten items reviewed by an isolated at-tier reviewer in its own worktree at the head above; origin/main read only through git show / git grep origin/main:; the shared checkout never edited. Tier established as the reviewer's first act by grepping its own transcript (6 "model":"claude-fable-5-1" over 6 assistant turns, control 8/8 over all lines) against the constant at scripts/pm/dispatch-gates.mjs:11899⚠️ the required method, because a subagent's get_session reads the PARENT session's model and so attests nothing about the subagent.

Blocking items, all PASS:

  1. Accept-set movementfield.zod.ts declares reference: z.string().optional(), action.zod.ts SnakeCaseIdentifierSchema.optional(), and git diff -- packages/spec is empty. The arbiter probed with tsx: absent / null / '' / non-object def answer undefined; object, array, number and boolean refuse. No published accept set moves — a shape no schema ever admitted now refuses at ten reads.
  2. Absence never throws — all ten sites read at this head; pins run: objectql 6/6, rest 23/23, metadata-protocol 7/7, lint 436/436, verify 18/18. The reviewer's own two ablations: reverting derive.ts reddens exactly 2 carrier pins of 18, reverting rest-server.ts exactly 1 of 23; both restored, blob-hash verified.
  3. Refusal leaves state untouchedplanCascadeAtomicity precedes runByIdDelete, both iterating one registry snapshot, so the refusal fires before any row is touched; the second seam is reachable only under a registry change between reads and sits inside transaction(…) in the atomic plan.
  4. No gate weakened — the 18-path diff contains 0 baseline / ledger / ratchet / debt paths, against a control of 131 such paths on main; the objectql-double-limit baseline diff is empty (control: 838 lines, and it does not name the new test); added .skip / .only / @ts-expect-error lines 0, with a control proving such lines exist elsewhere at this head. The one red gate was fixed in the test double; the two exit-3 gates were satisfied by building, never by a ledger edit.
  5. The doc edit — two cells on content/docs/ui/forms.mdx, accurate against code and pin; the content/docs/releases/ diff is 0 paths (controls: 1 content/docs path in the diff, 10 entries in that directory on main), so the 7 release-owned pages are byte-untouched as a code PR requires.

Non-blocking findings: item 4 (no error code added; an existing sanitised 500 INTERNAL_ERROR now answers a new input, and pre-change an object carrier was not LOOKUP_TARGET_MISSING either) · item 7 (the eleventh reader, left out defensibly and filed as #19082) · item 8 (the ActionParam refusal text names FieldSchema; true there, but the real schema was nameable with zero spec edits) · item 10, below.

② Semver level

minor on five public lockstep packages: PASS as declared — the level axis was run offline with the real declaration and exits 0, and CI Check Changeset is green. ⚠️ Finding: the act itself grades patch under the workflow's own prose, and this minor is derived from the declaration rather than from the change; the PR #18503 precedent cited for it added a new published export, which this PR does not.

③ Boundary flags

No security or permission boundary moves. No published contract surface is widened, no export, key or error code is added, and content/docs/releases/ is untouched. Ruling E item 2 (「every reader」) stays open after this PR, by the PR's own statement and the reviewer's item 7.

⚠️ Item 10, the finding against the commissioning seat: the clause-② criterion is 「放宽接受集或扩大公开面」 (SKILL.md:477), and 「拉回已声明契约不触它」 (:515, with lanes/spec.md:19-20). This PR does neither, so no was the criterion-accurate reading, and the seat's stated ground 「observable runtime behaviour moves」 is not the criterion. ⛔ Not a false declaration — 「按 yes 申报恒不是错误」 and a wrong no is the auditable fault — but it cost this review and the forced minor above. The declaration stands; what is owed is that the reason not be reused.

Implemented-by: claude/issue-18550-reference-carrier-residue
Reviewed-by: session_019srGWGCBBCBHqcDoRZpQRh

VERDICT: PASS


Generated by Claude Code

@os-elon-musk
os-elon-musk marked this pull request as ready for review September 18, 2026 17:19
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit a675ad4 Sep 18, 2026
54 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-18550-reference-carrier-residue branch September 18, 2026 21:52
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/xl tests tooling

Projects

None yet

2 participants