docs(spec,rest): anchor record-validator citations by symbol, not line - #16956
Merged
zhuangjianguo merged 2 commits intoSep 8, 2026
Merged
Conversation
Seven prose/comment citations of
`packages/objectql/src/validation/record-validator.ts` pointed at line
numbers that no longer resolve: the file has moved under them, and a
`path:NNN` anchor rots silently because the line it lands on still looks
like plausible code.
Re-anchor each on something that cannot rot without saying so — the
enclosing symbol `validateOne`, plus the verbatim snippet where one is
load-bearing:
- `packages/spec/src/kernel/functional-completeness.ts` (3 sites): the
`select`/`radio` rule and the `multiselect` NON-rule now cite
`validateOne` and quote `allowed.length > 0 && !allowed.includes(…)`
and `// free-form (tags without options)` verbatim.
- `packages/spec/src/kernel/functional-completeness.test.ts` (2 sites):
same two anchors on the pinned NON-rule's comment.
- `packages/rest/src/rest-hook-script-fault-envelope.test.ts` (2 sites):
the text-coercion citation now names `validateOne` and its
bounded-string branch (`BOUNDED_STRING_FIELD_TYPES.has(t)`, of which
`text` is a member) and quotes the coercion line verbatim.
Comments only. No rule, assertion, accept set or runtime behaviour moves;
the quoted runtime text is unchanged and still present exactly once each.
`record-validator.ts` itself is not touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
The functional-completeness module doc is emitted verbatim into `packages/spec/dist/kernel/index.d.ts`, which `files[]` publishes, so the citation text that moved ships to consumers. Half 1 of the published-text test says no (the source path is not in `files[]` and is not a `*.zod.ts`); half 2 says yes, measured on a fresh build. `@objectstack/rest` answers no on both halves — the changed file is a `.test.ts`, and its text is absent from `packages/rest/dist` while a positive control from the same file's module is present. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 8, 2026
zhuangjianguo
marked this pull request as ready for review
September 8, 2026 19:57
zhuangjianguo
enabled auto-merge
September 8, 2026 19:58
zhuangjianguo
deleted the
claude/issue-16441-repoint-record-validator-anchors
branch
September 8, 2026 20:23
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16441
Clause-②: no
Comments only. No accept set moves, no export is added or removed, no schema arm,
bound, severity or rule id changes, and the pinned NON-rule (
multiselectwithoutoptionsdeliberately unflagged) and its assertion are untouched. 拉回已声明契约 ⇒ 常规档.The fence triage set held: the quoted runtime text MOVED, it is not gone, so no
re-decision was required. Evidence is the per-site table below — every quoted snippet
still occurs exactly once in
record-validator.ts.What this is
Seven prose/comment citations of
packages/objectql/src/validation/record-validator.tspointed at line numbers that no longer resolve. Each is now anchored on the enclosing
symbol
validateOneplus, where one is load-bearing, a verbatim snippet — theconvention
packages/spec/liveness/field.jsonalready uses. A snippet anchor cannot rotsilently the way a
path:NNNanchor does: it either still matches or it does not.packages/objectql/src/validation/record-validator.tsis not touched by this PR. It isthe file being cited.
Re-measured on this branch's base,
f36eef55dThe card's own table is stale — the validator moved again after the card was written. These
are this branch's numbers, not the card's.
f36eef55drecord-validator.ts:471—free-form (tags without options)record-validator.ts:452— theallowed.lengthgaterecord-validator.ts:503-504— the text-coercion branchPositive control reproduced rather than assumed:
free-form (tags without options)occursexactly once in the file (
grep -c= 1, line 844). A zero or a multi-hit would havechanged the job; it did neither.
What the stale numbers land on today, which is the whole point of the card — both are
plausible-looking prose inside a comment block, so nothing reads as broken:
The seven sites, before and after, with the evidence each new anchor resolves
All four snippets below occur exactly once in
record-validator.ts, and lines 677, 678,826 and 844 all sit inside
validateOne, which spans 585 to 932 (the next top-leveldeclaration is
VALUE_SHAPE_STRICTat 933).grep -c '^function validateOne'= 1.packages/spec/src/kernel/functional-completeness.ts:38record-validator.ts:452+ elidedallowed.length > 0 && …record-validator.ts'svalidateOne, verbatimallowed.length > 0 && !allowed.includes(String(value))packages/spec/src/kernel/functional-completeness.ts:41record-validator.ts:471record-validator.ts'svalidateOne, verbatim// free-form (tags without options)packages/spec/src/kernel/functional-completeness.ts:108record-validator.ts:452record-validator.ts'svalidateOne+ the same verbatim gate snippetpackages/spec/src/kernel/functional-completeness.test.ts:12record-validator.ts:471record-validator.ts'svalidateOne, verbatim quote retainedpackages/spec/src/kernel/functional-completeness.test.ts:103record-validator.ts:471record-validator.ts'svalidateOne, verbatim quote retainedpackages/rest/src/rest-hook-script-fault-envelope.test.ts:45record-validator.ts:503-504+ a hand-elided `if (t === 'text'packages/rest/src/rest-hook-script-fault-envelope.test.ts:401record-validator.ts:503-504record-validator.ts'svalidateOneEvery site got a symbol or a verbatim snippet. No site fell back to a fresh
path:NNN,so there is no residual line-number rot risk introduced by this PR, and nothing to declare
under that heading.
Sites 6 and 7 need one extra sentence, because the guard's SPELLING changed
The old comment illustrated the branch as
if (t === 'text' || …). That was already anauthor's elision rather than a quote, and the runtime has since generalised the guard to a
spec-owned set:
BOUNDED_STRING_FIELD_TYPES.has(t).textis a member of that set(
packages/spec/src/data/field.zod.ts:136), so what the pin asserts is unchanged — anumber in a
textfield is still coerced byString(value)and still breaks no declaredcontract, which is exactly what the two tests assert. The load-bearing half of the old
citation, the coercion line, is present verbatim and was moved, not deleted. This is
therefore inside the repointing fence, not a re-decision; the comment now names the real
guard so the next reader is not sent looking for a disjunction that no longer exists.
Changeset — both halves of the test, for both packages
@objectstack/spec— patch changeset added.files[]?) — no.files[]is['dist','json-schema','liveness','prompts','llms.txt','README.md','src/**/*.zod.ts','CHANGELOG.md','api-surface','spec-changes.json']and
functional-completeness.tsis not a*.zod.ts.dist?) — yes, and this is what decidesit. The module doc block is emitted verbatim into
packages/spec/dist/kernel/index.d.tsand
dist/kernel/index.d.mts, both of whichdistpublishes. Measured on a fresh build:grep -rlFfinds the new text in 2 dist files, the oldrecord-validator.ts:452/:471strings in 0, and the untouched sibling lineobjectql/engine.ts:3191from thesame block sits three lines above the edit at
dist/kernel/index.d.ts:5192— so the blockships verbatim and these lines shipped in their old form. Positive controls from the same
source file:
field/summary-without-operations6 hits,FUNCTIONAL_COMPLETENESS_RULES6 hits.
functional-completeness.test.tsis not emitted; the spec changeset already covers thepackage.
@objectstack/rest— no changeset, on both halves.files[]is['dist','README.md','CHANGELOG.md']; the changed file issrc/rest-hook-script-fault-envelope.test.ts.record-validator.ts:503-504= 0 hits inpackages/rest/dist,rest-hook-script-fault-envelope= 0, the changed sentencethe honest half= 0. Positive control from the same module:mapDataError= 2 hits,VALIDATION_FAILED= 4 hits, so the grep can see published rest text when it is there.BOUNDED_STRING_FIELD_TYPES.has(t)DOES appear in
packages/rest/dist/index.js:60800. That isrecord-validator.ts's ownbundled runtime source, not this comment. The comment's copy lives behind a
//in atest file that is not bundled.
Checks run, by name
Nothing below is reported from absence. Each ran in the foreground on this branch's tree;
the tests and builds went through the shared verify lock.
Build (dependency closure, then both packages):
pnpm --filter '@objectstack/spec^...' --filter '@objectstack/rest^...' --workspace-concurrency=2 build—VERDICT command-exit 0pnpm --filter @objectstack/spec --filter @objectstack/rest --workspace-concurrency=2 build—VERDICT command-exit 0Tests and typecheck (per-part exit codes, not a batch tail):
pnpm --filter @objectstack/spec exec vitest run src/kernel/functional-completeness.test.ts—Test Files 1 passed (1) · Tests 50 passed (50), exit 0pnpm --filter @objectstack/rest exec vitest run src/rest-hook-script-fault-envelope.test.ts—Test Files 1 passed (1) · Tests 18 passed (18), exit 0pnpm --filter @objectstack/spec typecheck— exit 0 (tsc --noEmit+check:scripts-typecheck+check:test-typecheck; the last reportsOK — @objectstack/spec's test layer compiles)pnpm --filter @objectstack/rest typecheck— exit 0 (check:test-typecheck: OK — 0 file(s) / 0 error(s))Lint — the WHOLE-TREE run, not a narrowed one:
pnpm lint(eslint . --no-inline-config) — exit 0, zero findings. It completed inside theforeground budget, so no narrowing was declared and none is being claimed.
Gates, chosen because they read what this diff changes, plus the four that the changeset's
arrival adds to the derived family (
node scripts/pm/dispatch-gates.mjs, re-derived afterthe changeset existed). All exit 0:
pnpm check:nul-bytes—OK (scanned 8384 text file(s) … no raw ASCII control bytes)node scripts/check-comment-mask-adoption.mjs—OK … 14 recordednode scripts/check-comment-mask-corpus.mjs—6384 files, 0 disagree, 0 unparseablenode scripts/check-system-context-census.mjs—OK — 107 elevation read sites … over 128 anchorspnpm check:adr-anchors— exit 0 (this file is anchored atscripts/adr-anchors/packages__spec__src__kernel__functional-completeness.ts.json;ADR-0078still appears 11 times in the file)node scripts/check-keyed-text-bounds.mjs—148 keyed text-family columns judged, 148 boundednode scripts/check-changeset-no-major.mjs --base origin/main—introduces no major bumpnode scripts/check-closing-keyword-parity.mjs—OK (3 parsers agree on all 9 keywords)node scripts/check-empty-changeset.mjs --base origin/main—No empty-frontmatter changeset introducednode scripts/check-adr-0087-registration.mjs --base origin/main—adds no declared-breaking changesetpnpm check:changeset-gate-self-tests— 3 self-tests passpnpm check:doc-authoring—398 files clean·15159 customer-facing string(s) … cleanpnpm --filter @objectstack/spec run check:api-surface—public API surface + factory signatures unchangedpnpm --filter @objectstack/spec run check:authorable-surface— exit 0, and it wrote nothing:git statusis clean apart from this PR's own filespnpm --filter @objectstack/spec run check:liveness— exit 0, includingevery path:NNN citation names a line that file actually haspnpm --filter @objectstack/spec run check:llms-txt—97 claim(s) re-derivedpnpm --filter @objectstack/spec run check:docs—228 generated files in syncpnpm check:published-files—70 publishable package(s) … declare a files whitelistDeclared narrowing.
dispatch-gatesderives 75 families for this changeset. 17 were runhere; the remaining 58 are declared to CI, which runs the full farm. That is an announced
narrowing, not an omission — the ones run are the ones that read comment text, the spec
published surface, the changeset, or the anchored-file register.
验收备注
Out of scope, deliberately not in this diff, recorded for the reviewer:
docs/audits/**anchors named in the card are untouched. They are datedsnapshots; rewriting them to today's line numbers would falsify the record. Triage fenced
them out by name and this PR honours that fence.
objectql/engine.ts:NNNNanchors that are rottedharder than the ones this card is about, and they are NOT fixed here. They were never in
the card's census, which enumerated
record-validator.tsanchors only, and fixing themwould widen a seven-site repointing. Measured read-only on
f36eef55d, for whoever picksthem up:
engine.tsis 15020 lines;:3001citesif (d?.type !== 'summary' || !d.summaryOperations) continue;which is at 8520;
:3191citesif (!referenceObject) continue;which is at 8868;:346cites the formula-plan build, whosedef?.type === 'formula' && def.expressionis at 859. Line 3001 today is a sentence of prose, and 3191 is the head of a plain indexed
forloop overrows— the same plausible-neighbour shape. Same class, same file, same doc block, one card away.path:NNNanchors deserve arepo-wide gate — is not answered here and no gate is added. It needs its own population
count and its own card. One datum for that card, since this PR produced it for free: the
seven sites repointed here all had a symbol or a quotable snippet available, so a
convention rule ("cite a symbol or a snippet, never a line") would have been satisfiable at
every one of them without loss of precision. That is an argument for a convention gate
rather than a line-number-freshness gate, and it is an observation, not a recommendation
this PR acts on.
Generated by Claude Code