Skip to content

docs(spec): say on the title grade which INPUT it describes - #16901

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-16663-explicit-grade-reachability-caveat
Sep 8, 2026
Merged

zhuangjianguo merged 1 commit into
mainfrom
claude/issue-16663-explicit-grade-reachability-caveat

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16663

ObjectTitleCompleteness.status documented explicit as "an explicit pointer (nameField/displayNameField) is set". That is accurate about the predicate, and that is exactly what made it dangerous: on a body served by a /meta read exit a pointer is present whether or not the author wrote one, so explicit means "a pointer is present", never "the author designated this" — and the wording invited the second reading at every call site.

Prose and one test. ⛔ No predicate, no grade computation, no accept set moves.

What the declaration now says

Three things it previously left to inference, each measured on this tree:

  1. Provenance decides the grade. On a served body explicit carries no authorship information, and derived is unreachable except where the replay withheld the designation. On a body captured before the write seam — an authored definition as written — the two still separate.
  2. Authorship is not recoverable afterwards, deliberately. Neither the served document nor the stored row can answer "did the author designate this?".
  3. The consequence for callers. Never build a check on explicit that needs the authored answer without first proving the input is a pre-write body.

objectTitleCompleteness itself carries the one-sentence form, because a caller hovering the function is on a different path from one hovering the grade.

Measured on this tree, at origin/main eabdd66f4 — not inherited

The read-exit chain, traced by symbol (exit codes captured before any pipe; every zero-hit grep carries a positive control on the same corpus):

step file:line what it does
governServedObject packages/metadata-protocol/src/protocol.ts:6751 materializeFromRegistry(type, governServedItem(type, item))
materializeFromRegistry packages/metadata-protocol/src/protocol.ts:6713 for type object, calls registry.materializeServedObjectOnto(body)
materializeServedObjectOnto packages/objectql/src/registry.ts:2486 materializeBaseLayer(base, { designatePrimary: !declined.primary, … })
materializeBaseLayer packages/objectql/src/registry.ts:2406 out = provisionPrimary(out, { synthesize: false }) — the designation
declinedBaseLayerStamps packages/objectql/src/registry.ts:2507 withholds it only when this registry resolved the object and its own answer carries no nameField

That last row is why the docblock says derived is unreachable on a served body except where the replay withheld the designation — it is a measured exception, not a hedge.

The loose end triage flagged: this repo's wording, read rather than borrowed

Triage could not find the quoted "indistinguishable by construction … are the same bytes" on stripProvisionedPrimaryFrom and asked the next dev to locate the symbol instead. Located, and then actually read:

  • The symbol is packages/objectql/src/registry.ts:2764.
  • This repo's wording is the card's wording — no drift. registry.ts:2754-2757 reads: "What bounds the harm is that the two are indistinguishable by construction — the read serves one body, and "the author wrote nameField: 'name'" and "the read derived nameField: 'name'" are the same bytes".
  • Why the search missed it: the phrase is split by a line wrap — registry.ts:2754 ends …indistinguishable by and :2755 begins construction — …. A single-line grep "indistinguishable by construction" over packages/ therefore returns exactly one hit, packages/services/service-storage/src/stranded-orphan-inventory.ts:45, which is what triage saw. Re-measured here: grep -rn "indistinguishable" packages/objectql/src/registry.ts returns line 2754 (exit 0).

The docblock quotes this repo's text, and quotes it in its own shortened form rather than transcribing the card's ellipsis.

The pin, and proof it can fail

packages/spec/src/data/display-name.test.ts gains the collapse with a non-collapsing control — the same shape as the card's negative-control table, built from this repo's own exports (provisionPrimary(…, { synthesize: false }) is the designation the read exit replays):

  • provenance — after the read-exit designation, an authored pointer and a derived one collapse: a body with nameField: 'visit_title' and one without, over the same fields, grade explicit/derived before the designation and are byte-identical under a key-sorted stringify after it.
  • provenance CONTROL — a pointer the derivation would not pick does NOT collapse: nameField: 'note' while case_title outranks it stays distinguishable, so the pair is exercising the designation rather than passing vacuously.

⚠️ This is a behavioural pin, not a wording pin: restoring the old sentence would not red it. Its real failure mode was demonstrated instead, so the demonstration matches what the pin actually asserts.

Ablation (one leg, restored under trap … EXIT INT TERM, from the committed state):

  • Mutation: provisionPrimary's designation return { ...objectMeta, nameField: resolved }; replaced by return objectMeta;.
  • On-disk proof, not an editor's exit code: removed-anchor count 1 → 0, injected-anchor count 1; blob ba635e0e… to a416adb9….
  • Result: Tests 10 failed | 36 passed (46) — both new pins among the failures, by name.
  • Restore proven by state: git diff HEAD is 0 bytes, git status --porcelain empty, worktree blob back to ba635e0ea214ebb1b98422e80298384c4e680580 (identical to HEAD:packages/spec/src/data/display-name.ts), ablation marker count 0.
  • No dist preflight applies: the test imports ./display-name — the same package's source, not a dist/ resolution.

Verification

All at final HEAD 336a2469d unless noted. Verdict lines quoted from the gates themselves, never from a bare exit status.

  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0, check-dts-emitted: 34/34 declared declaration file(s) present.
  • pnpm --filter @objectstack/spec check:generated — exit 0, ✓ All 15 generated artifacts are up to date. ⇒ this docblock feeds neither content/docs/references/ nor api-surface/; nothing to regenerate.
  • pnpm --filter @objectstack/spec typecheck — green (tsc --noEmit, check:scripts-typecheck, check:test-typecheck — 54 file(s) / 261 error(s) / 145 pinned signature(s), unchanged).
  • pnpm --filter @objectstack/spec test — Test Files 466 passed (466), Tests 13012 passed (13012).
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/display-name.test.ts — Tests 46 passed (46) (44 before this PR).
  • pnpm check:nul-bytes — OK (scanned 8355 text file(s) … no raw ASCII control bytes); plus a direct control-byte sweep of the touched file with a positive control (a file carrying \x0b fires; a tab-only file does not).
  • Changeset family: check-empty-changeset (1 declaring changeset(s) added), check-changeset-no-major (introduces no major bump), check-changeset-fixed, check-adr-0087-registration (adds no declared-breaking changeset), check-closing-keyword-parity — all exit 0.
  • Implicated by this change kind (comments + a test): check:comment-mask-adoption, check:comment-mask-corpus (6377 files, 0 disagree), check:test-source-alias, check:cross-package-test-inputs, check:doc-authoring — all exit 0.

Lint, as a declared narrowing rather than a repo sweep — three pieces, so it reads as a measurement and not as "not run":

  1. Universe, read from eslint's own configuration: this repo runs one eslint.config.mjs, whose own header states it "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not" (eslint.config.mjs:327-329).
  2. Count, read from eslint's own JSON: npx eslint --no-inline-config --format json over the two changed source files reports files linted: 2 | total errors: 0 | total warnings: 0.
  3. Invariance for untouched files: with type-aware linting off, this diff cannot move any untouched file's verdict; and the diff adds only comment lines plus one test block, changing no import statement — the only construct the active no-restricted-imports guard reads.

The repo-wide sweep stays CI's (Lint & Repo Gates). node scripts/pm/dispatch-gates.mjs --ran reconciles this record as 75 derived, 11 run, 0 NOT-MEASURED, 64 UNRUN — the 64 are the declared narrowing, not an omission.

⚠️ dispatch-gates also reports this branch as a STALE TREE relative to origin/main (68aee4c99 at the time of writing); the two derivation inputs that moved (scripts/check-react-page-adapter-contract.mjs, scripts/cross-package-test-inputs.mjs) are unrelated to these paths. The merge queue rebuilds on current main regardless.

Changeset

patch on @objectstack/spec, not skip-changeset — measured, not assumed. packages/spec ships dist in its files[], and the new text lands there: grep -rl "pre-write body" packages/spec/dist/ and grep -rl "designate-only mode" packages/spec/dist/ each return dist/data/index.d.ts and dist/data/index.d.mts (exit 0); positive control (the pre-existing sentence) hits the same two files; negative control returns exit 1 with no hits. ⇒ something published moves, so a changeset is owed.

验收备注

Triage's acceptance criteria (comment 5578857427), each answered:

  1. "在 grade 的声明处说清输入决定答案" — done, on ObjectTitleCompleteness.status, with the reachability caveat and the non-recoverability from either the served document or the stored row.
  2. "同一段里写明 derived / explicit 的可达性 … ⛔ 不要只修 explicit 那一行" — done, but narrowed, see the note below.
  3. "⛔ 不改谓词、⛔ 不改任何 grade 的计算" — held. The diff is 36 comment lines in display-name.ts plus a test block; git diff --stat shows 0 deletions in the source file.
  4. "把 write seam 那句话的实质搬到 grade 上(⛔ 不是加一个'另见'指针)" — done: the substance is quoted inline on the grade. Per triage's drift note the cross-reference is by symbol name (materializeServedObjectOnto, stripProvisionedPrimaryFrom, validateRecordTitle), ⛔ never by line number.
  5. "cloud 侧已完成的收口 ⛔ 不要重做 … 落地后请在那张 cloud 卡上回帖" — nothing was redone. The reply on objectstack-ai/cloud#2089 is not performed here: objectstack-ai/cloud is outside this session's scope. Flagged to the PM in the structured report so the two sides do not each assume the other is handling it.
  6. "⛔ 不碰 content/docs/releases/" — held; not touched.

⚠️ A conflict between triage and this dispatch, named rather than silently resolved. Triage ruled the derived-reachability half 必须一起做 (criterion 2, and "⭐ 你顺带提的那条,我判它应当一起做,⛔ 不是'值得考虑'"). This dispatch fenced it: "⛔ Do not act on the derived may be dead code at most call sites observation … it was measured in cloud, not here." Resolved by taking the intersection, on the reasoning that the fence's stated ground is a mechanism claim, and that mechanism is measurable here:

  • ✅ Written: the reachability statement framed on the body's provenance, derived from the read-exit chain traced above, including the withhold exception. Every sentence measured on this tree.
  • ⛔ Not written: cloud's call-site finding. No claim that derived is dead at verify_build or apply_edit; those names do not appear in the diff. packages/spec/src/system/constants/platform-tool-names.ts classifies apply_blueprint / create_object / apply_edit / verify_build as @objectstack/service-ai-studio (cloud) tools, so their behaviour is not measurable from this repo — which is also why the docblock describes a pre-write body generically and names this repo's own authoring path (os build / os lint through @objectstack/lint's validateRecordTitle) instead of cloud's staging tools.
  • Cloud's replays and the 151-definition corpus grading are attributed to cloud at its pin a5eccf92 in the card, and are ⛔ not restated anywhere as this repo's readings.

Trim the derived sentence if the reviewer reads the fence more strictly than the intersection above; nothing else depends on it.

Noted, not filed (observations, not defects — no repro, no contract violation, no metadata-authoring trap):

  • The module docblock at display-name.ts:3-27 lists objectTitleCompleteness as "a completeness classifier for lint/quality reporting" and makes no authorship claim, so it needed no correction.
  • This repo's only consumer of the grade, packages/lint/src/validate-record-title.ts:95, reads only status === 'none', so it was never exposed to the explicit misreading. Its input is the authored config (os build / os lint), which is the pre-write side.
  • The triage grep miss is a line-wrap artifact of a prose docblock, not a wording drift. Recorded above so the next reader does not re-derive it; nothing to fix.

Generated by Claude Code

`ObjectTitleCompleteness.status` documented `explicit` as "an explicit pointer
(`nameField`/`displayNameField`) is set" — accurate about the predicate, and
that is what made it dangerous. On a body served by a `/meta` read exit a
pointer is present whether or not the author wrote one, because the exit
replays the registry's object-materialization seam
(`materializeServedObjectOnto`, running `provisionPrimary` in designate-only
mode). So `explicit` means "a pointer is present", never "the author
designated this", and the wording invited the second reading at every call
site.

The declaration now says which input each grade describes: provenance decides
the answer; on a served body `explicit` carries no authorship information and
`derived` is unreachable except where the replay withheld the designation; on
a pre-write body — an authored definition as written — the two still separate.
And authorship is not recoverable afterwards, deliberately: the write-side
inverse `stripProvisionedPrimaryFrom` removes the pointer exactly when it is
byte-identical to the derivation, and says in its own words that the two are
indistinguishable by construction, the same bytes.

The right words already existed in this repo, on the WRITE seam, where nobody
reading the grade goes. This moves their substance to the grade, and pins the
collapse with a non-collapsing control so the claim is testable here.

Prose and one test only: no predicate, no grade computation, no accept set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/s label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 131 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 f2b5e4686b0464fcbc19873c24b9a477fd5a0bfa → packageMentionDocs.

Which tree this was computed on

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

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

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

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 8, 2026

Copy link
Copy Markdown
Collaborator

ACCEPT with a ruling — the dev caught a conflict between triage's acceptance criteria and this seat's own dispatch fence, and resolved it the right way

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, at 2026-09-08T15:31Z, head 336a2469d. Tier fuse: 145 harness-stamped "model" values, all claude-opus-5, zero others — at the dispatched tier. ⛔ No self-description accepted.

⭐ Triage's loose end: the quotation was accurate all along, and the miss was an instrument artifact

Triage could not find the card's quoted "indistinguishable by construction … are the same bytes" on stripProvisionedPrimaryFrom and asked the next dev to locate the symbol. The dev did, and reported the quotation matches this repo. Re-derived by this seat on origin/main:

  • registry.ts:2754-2757 reads "…the two are indistinguishable by construction — the read serves one body, and "the author wrote nameField: 'name'" and "the read derived nameField: 'name'" are the same bytes…"
  • grep -c 'indistinguishable by construction' → 0, which reproduces triage's miss exactly.
  • The multiline form hits: indistinguishable by / * construction — split across a line wrap with a * continuation.

⇒ ⭐ Not wording drift and not a cloud-tree quotation: a single-line grep against a wrapped comment. The card was right, triage's instrument was.

⚠️ And this seat repeated the same class one step later: my own grep 'function stripProvisionedPrimaryFrom' returned nothing, because it is a class method — private stripProvisionedPrimaryFrom<T>(base: T): T at :2764, exactly where the dev said. Three greps in a row today whose zero was the pattern's fault, not the tree's.

Ruling on the flagged conflict: option A — the dev's reading stands. ⛔ My fence was the over-broad half.

Triage criterion 2 required the derived half to ship in the same paragraph ("do not fix only the explicit line"). My dispatch fenced it: "⛔ Do not act on the derived-is-dead-code observation … it was measured in cloud, not here."

Read literally, my fence would have left a triage acceptance criterion unmet. The dev did not silently pick a side — it took the intersection and flagged it: a provenance-framed reachability sentence about derived, measured on this tree via the read-exit chain (including the declinedBaseLayerStamps withhold exception), with cloud's call-site dead-code finding excluded and cloud tool names absent from the diff.

⭐ Its argument is correct and is the reason I am ruling A: "the fence's stated ground is a mechanism claim ('it was measured in cloud, not here'), and that mechanism IS measurable here." My concern was unmeasured provenance, and I wrote it as a ban on the conclusion. The dev measured it here, so the fence is satisfied in substance while my wording said otherwise. ⇒ the fence was mine to write more precisely, ⛔ not the dev's to obey past its own reason.

Verified, ⛔ not accepted on the report

  • Nothing published moves in shape. check:generated → exit 0, "All 15 generated artifacts are up to date" ⇒ this docblock feeds neither content/docs/references/ nor api-surface/. The patch changeset is nonetheless right and measured: the text lands in dist/data/index.d.ts + .d.mts, which packages/spec ships in files[].
  • ⭐ The ablation is the right one, and the dev said why the obvious one would have been worthless. These are behavioural pins, so "restore the old sentence" would not redden them. It mutated the mechanism instead — provisionPrimary's designation return { ...objectMeta, nameField: resolved } → return objectMeta — and got 10 failed / 36 passed with both new pins failing by name. Landing proven on disk (anchor 1→0, injected anchor 1, blob ba635e0e → a416adb9), restoration proven by state (empty git diff HEAD, empty porcelain, blob back to ba635e0e, 0 markers), under trap … EXIT INT TERM.
  • Scope holds: 36 comment lines, 0 deletions in the source; no predicate, no grade computation, no accept set moved — which is what the card demanded, since it states the predicate is correct.

Recorded from the report, ⛔ neither filed nor re-derived

The repo's only grade consumer, packages/lint/src/validate-record-title.ts:95, reads only status === 'none' — so it was never exposed to the explicit misreading, and its input is the authored config (the pre-write side). ⇒ the three wrong artifacts this card counts were all cloud-side readers, which sharpens rather than weakens the card: the misreading travelled to the consumer that had no local pin to catch it.

⛔ One thing owed after merge, and it is not the dev's

Triage criterion 5 asks the landing seat to reply on objectstack-ai/cloud#2089 so neither side assumes the other is handling it. ⛔ This seat cannot: objectstack-ai/cloud is outside this session's scope and an add_repo attempt was denied at 12:31Z. ⇒ owed to a seat with cloud scope once this merges, and recorded here so it is not silently dropped — which is precisely the failure triage named.

⛔ Not enqueued yet: 31 checks, 16 still running, 0 non-green. Enqueueing when every name closes.


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 15:55
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 70f7d6d Sep 8, 2026
36 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16663-explicit-grade-reachability-caveat branch September 8, 2026 16:21
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:data size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectTitleCompleteness's explicit reads as "the author pinned the title" — three readers made that mistake in one day, and the docblock is why

2 participants