Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/record-validator-citations-anchored-by-symbol.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 7 additions & 5 deletions packages/rest/src/rest-hook-script-fault-envelope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down Expand Up @@ -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');
Expand Down
11 changes: 6 additions & 5 deletions packages/spec/src/kernel/functional-completeness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 10 additions & 8 deletions packages/spec/src/kernel/functional-completeness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string> = new Set(['select', 'radio']);
Expand Down
Loading