From 5c978f82369cb5d22ec49ca1bffdaf1aa8c98fe9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 19:12:13 +0000 Subject: [PATCH 1/2] docs(spec,rest): anchor record-validator citations by symbol, not line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- .../rest-hook-script-fault-envelope.test.ts | 12 +++++++----- .../src/kernel/functional-completeness.test.ts | 11 ++++++----- .../spec/src/kernel/functional-completeness.ts | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/rest/src/rest-hook-script-fault-envelope.test.ts b/packages/rest/src/rest-hook-script-fault-envelope.test.ts index 8cf1ef70aa..f4fbda78f9 100644 --- a/packages/rest/src/rest-hook-script-fault-envelope.test.ts +++ b/packages/rest/src/rest-hook-script-fault-envelope.test.ts @@ -42,10 +42,12 @@ // // The card's accept bar says `{"title": 12345}` must JOIN its neighbours — // `{}` → `400 VALIDATION_FAILED` with `fields[]`. It cannot, and asserting that -// it does would pin a false statement. `record-validator.ts:503-504` accepts a -// number in a `text` field: +// it does would pin a false statement. `record-validator.ts`'s `validateOne` +// accepts a number in a `text` field: its bounded-string branch is entered by +// set membership (`BOUNDED_STRING_FIELD_TYPES.has(t)`, and `text` is a member), +// and the first thing it does is coerce, verbatim: // -// if (t === 'text' || …) { const s = typeof value === 'string' ? value : String(value); … } +// const s = typeof value === 'string' ? value : String(value); // // — the value is COERCED, every length/format check runs against `"12345"`, and // the branch returns `null`. The shape guard above it (`invalid_value_shape`) @@ -398,8 +400,8 @@ describe('[#7543] the control table from the report, guarded as one family', () const r = mapDataError(REPORTED(), 'showcase_task'); // It does NOT join the 400 VALIDATION_FAILED family, and pinning that it - // does not is the honest half: `record-validator.ts:503-504` COERCES a - // number in a `text` field via `String(value)`, so this request breaks + // does not is the honest half: `record-validator.ts`'s `validateOne` + // COERCES a number in a `text` field via `String(value)`, so this breaks // no declared contract and names no offending field. What was wrong was // the raw fault text and the missing `code`; both are fixed. expect(r.body.code).toBe('INTERNAL_ERROR'); diff --git a/packages/spec/src/kernel/functional-completeness.test.ts b/packages/spec/src/kernel/functional-completeness.test.ts index 47a7d330c4..75f9687797 100644 --- a/packages/spec/src/kernel/functional-completeness.test.ts +++ b/packages/spec/src/kernel/functional-completeness.test.ts @@ -9,10 +9,10 @@ * completeness gate that cannot fail on a known-inert instance is the * hollow-probe defect reproduced in the instrument built against it. * 2. The deliberate NON-rules are pinned as hard as the rules. `multiselect` - * without options is runtime-blessed free-form (`record-validator.ts:471`, - * verbatim: "free-form (tags without options)") — if someone "completes" - * this module by flagging it, that is a false prescription, and this test - * is where the attempt fails first. + * without options is runtime-blessed free-form (`record-validator.ts`'s + * `validateOne`, verbatim: "free-form (tags without options)") — if someone + * "completes" this module by flagging it, that is a false prescription, and + * this test is where the attempt fails first. */ import { describe, expect, it } from 'vitest'; @@ -100,7 +100,8 @@ describe('checkFieldCompleteness — the verified inert shapes go red', () => { }); it('does NOT flag multiselect without options — the pinned NON-rule', () => { - // record-validator.ts:471, verbatim: "free-form (tags without options)". + // record-validator.ts's `validateOne`, verbatim: + // "free-form (tags without options)". // The runtime blesses this as a mode; flagging it would be a false // prescription. If product direction ever changes, change the runtime // first — this pin makes the lint follow the code, never lead it. diff --git a/packages/spec/src/kernel/functional-completeness.ts b/packages/spec/src/kernel/functional-completeness.ts index f8e42912e9..ea075484f7 100644 --- a/packages/spec/src/kernel/functional-completeness.ts +++ b/packages/spec/src/kernel/functional-completeness.ts @@ -35,12 +35,13 @@ * - `lookup`/`master_detail` w/o `reference` → `objectql/engine.ts:3191` * `$expand` `if (!referenceObject) continue;` — the relationship silently * never resolves, and the record picker has no target to search. - * - `select`/`radio` w/o `options` → `record-validator.ts:452` - * `allowed.length > 0 && …`: an empty option list disables server-side - * value validation entirely, while the form control offers nothing to pick. - * - **NON-rule:** `multiselect` w/o `options` — `record-validator.ts:471` - * says, verbatim, `// free-form (tags without options)`. The runtime - * blesses it as a deliberate mode, which makes it ADR-0078 case (3) + * - `select`/`radio` w/o `options` → `record-validator.ts`'s `validateOne`, + * verbatim `allowed.length > 0 && !allowed.includes(String(value))`: an + * empty option list disables server-side value validation entirely, while + * the form control offers nothing to pick. + * - **NON-rule:** `multiselect` w/o `options` — `record-validator.ts`'s + * `validateOne` says, verbatim, `// free-form (tags without options)`. The + * runtime blesses it as a deliberate mode, which makes it ADR-0078 case (3) * "genuinely optional", not an omission. Flagging it would be this * campaign's own false-prescription mistake again. * - `checkboxes` w/o `options` sits between the two: it shares the multi @@ -105,8 +106,9 @@ const hasEntries = (v: unknown): boolean => Array.isArray(v) && v.length > 0; /** * Field types whose single-choice control is dead without `options` - * (`record-validator.ts:452` skips validation on an empty list, and the form - * control has nothing to offer). `multiselect` is deliberately absent — see + * (`record-validator.ts`'s `validateOne` skips validation on an empty list — + * verbatim `allowed.length > 0 && !allowed.includes(String(value))` — and the + * form control has nothing to offer). `multiselect` is deliberately absent — see * the NON-rule note in the module doc. */ const DEAD_WITHOUT_OPTIONS_ERROR: ReadonlySet = new Set(['select', 'radio']); From 85bb9dad9f69a893763429091226ea34ddc8a742 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 19:36:39 +0000 Subject: [PATCH 2/2] chore(changeset): patch @objectstack/spec for the re-anchored doc block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x --- .../record-validator-citations-anchored-by-symbol.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/record-validator-citations-anchored-by-symbol.md diff --git a/.changeset/record-validator-citations-anchored-by-symbol.md b/.changeset/record-validator-citations-anchored-by-symbol.md new file mode 100644 index 0000000000..bac7bc4363 --- /dev/null +++ b/.changeset/record-validator-citations-anchored-by-symbol.md @@ -0,0 +1,11 @@ +--- +"@objectstack/spec": patch +--- + +`functional-completeness`'s published doc block now anchors its `record-validator.ts` citations on a SYMBOL and a verbatim snippet instead of a line number. + +The module doc for the functional-completeness predicate is emitted verbatim into `dist/kernel/index.d.ts`, so the citations that justify its rules ship to consumers. Three of them cited `packages/objectql/src/validation/record-validator.ts` by line — `:452` for the `select`/`radio` rule and `:471` for the `multiselect` NON-rule — and the validator has moved twice since those numbers were written. Both landed hundreds of lines away, on unrelated prose inside a comment block, which is the failure mode a `path:NNN` anchor has by construction: the line it lands on still looks like plausible code, so nothing reads as broken. + +Each citation now names the enclosing function `validateOne` and quotes the runtime text it relies on — `allowed.length > 0 && !allowed.includes(String(value))` for the empty-option-list gate, `// free-form (tags without options)` for the NON-rule. A snippet anchor cannot rot silently the way a line number does: it either still matches the file or it does not. + +No rule, severity, accept set or exported symbol changes, and the pinned NON-rule (`multiselect` without `options` is deliberately not flagged) is untouched — the runtime text it quotes is unchanged and still present. Documentation only.