Repository navigation
Commit ad7c351
fix(spec): author-visible refusals and prescriptions state each decision in words instead of a tracker number (stage 3) (#21521)
Part of #20749
Clause-②: no
**Stage 3 of the `domain:spec` lane's share under the maintainer's A / A
ruling (5902360492): `packages/spec`'s runtime strings, which sit
outside the prose-id ledger. A fresh census first, then the first group,
the author-visible refusals and prescriptions, in form D.** 13 messages
with 14 tracker-number occurrences in five `packages/spec/src/data`
sources. The card stays open for the later stages, so this PR carries no
closing keyword. Text only: no key, schema shape, condition, code path,
error code or status moves (AST skeleton proof below, 6 of 6 SAME).
## The census (at `85e29b8858`)
`packages/spec/src` is outside
`scripts/doc-authoring-prose-id.baseline.json`, so the census is this
lane's instrument there. It re-implements the #20513 dev round's
instrument from its stated semantics (dev report 5900801368): a
TypeScript-AST walk over every non-test source of `packages/spec/src`
(test files counted apart) that folds a message before matching (a
maximal chain of string pieces joined by `+`, a template literal,
parentheses, a string array joined with `.join(sep)`, and strings nested
in a span, in a call or in a conditional that is an operand of the
chain, all folded into the outermost message). The match is the gate's
own id pattern. Hit lines come from the string leaf that carries the id,
never from the raw span, so comments are never read.
- Lit controls: `authoring-key-lint.ts:104` (single line) reads 1
message; `driver.zod.ts:299-301` (a three-line `+` chain, the id on its
third line) reads ONE message; `api/error-code-ledger.zod.ts:1773-1776`
reads ONE message with four ids on two lines.
- Dark controls: the `//` comment with an id inside the frozen
`FIELD_KEY_GUIDANCE` table (`authoring-key-lint.ts:100-101`) and the
docblock at `driver/common.zod.ts:48-53` (two ids) read 0 hits; over the
whole tree, 0 hit lines fall on a comment line. Parse diagnostics 0.
- Independent cross-check: `check-doc-authoring.mjs --census`'s own
per-literal leg, pointed at `packages/spec/src` in a scratch copy (its
root and exclusion constants changed, nothing else): 19 files, 441 id
occurrences, 0 per-file differences from this census.
- Word forms (`PR` / `issue` / `card` plus a number): every hit also
carries a `#` id; none is extra.
Non-test sources: **230 messages, 441 id occurrences, in 19 files.**
Classified by audience:
| class | messages | ids | where |
|---|--:|--:|---|
| (a) author-visible refusal or prescription | 13 | 14 |
`data/authoring-key-lint.ts` 4, `data/driver.zod.ts` 4,
`data/filter.zod.ts` 2, `data/object.zod.ts` 2,
`data/driver/common.zod.ts` 1. **This PR.** |
| (b) text shown to authors or administrators that is not a refusal:
ADR-0087 conversion summaries | 91 | 108 | `conversions/registry.ts`,
the `summary` of each conversion, printed in
`docs/protocol-upgrade-guide.md` and in `os migrate meta --json`'s
`specChanges` |
| (b) the same: schema and route descriptions | 3 | 3 |
`data/field.zod.ts` 1 (a `.meta()` description),
`api/plugin-rest-api.zod.ts` 2 (route descriptions) |
| (c) conformance-case notes | 58 | 68 | the filter-logic, filter-text,
filter-comparand-type, aggregation, temporal, value-roundtrip and
metadata-service-roundtrip conformance modules and the text-operator
declared-type table |
| (d) log lines | 0 | 0 | |
| (f) internal registry rationale: shipped data whose reader is a
contributor or a gate, never shown to an author | 17 | 33 |
`api/error-code-ledger.zod.ts` 14 (the `STANDARD_SYNONYM_WAIVERS` and
`PROVENANCE_WAIVERS` reasons), `kernel/public-auth-features.ts` 3 |
| excluded: `migrations/registry.ts` (#20234's stage 11) | 48 | 215 | |
Test files, counted apart (class (e)): 1803 messages, 1919 id
occurrences, in 425 files: about 1702 messages (1812 ids) in test titles
and 101 (107) elsewhere.
Two class (c) facts a later stage needs:
`packages/lint/src/validate-empty-combinators.test.ts:275` SELECTS
`FILTER_LOGIC_CASES` by `note.includes('#5322')` (4 notes, 7 ids:
`filter-logic-conformance.ts:420`, `:426`, `:432`, `:438`); and
`packages/services/service-analytics/src/__tests__/icontains-dialect-sql.test.ts:369`
pins one case NAME verbatim, id included
(`filter-text-conformance.ts:300`, `#8934`).
The census as a whole, file:line, id and class, is in this stage's dev
report on #20749. The card's census (175 messages at `36d043be17`,
envelope 26, prose 149) classified by syntax; this one classifies by who
reads the text, so the two do not map one to one. For instance, the 14
waiver `reason`s in `error-code-ledger.zod.ts`, which a syntactic
instrument files as refusal envelopes, are contributor-facing ledger
rationale here, class (f).
## What this does: the 13 class (a) messages
These are the texts an author meets at the moment something is refused
or rewritten: the unknown-field-key guidance that `os validate` and the
authoring lint print, the parse errors for retired `DriverCapabilities`
keys, the guidance for `readOnly` written inside a datasource driver's
`config`, the `INVALID_FILTER` refusal every driver face prints for
`$regex` / `$options`, and the warning `enable.apiMethods` prints when
it strips a retired legacy value. In form D, as stages 1 and 2 applied
it, the number goes; where the sentence did not already say what was
decided, it now does. Every cited card was read through REST, body and
every comment.
| Where | Cited | Decision read from | The text now says |
|---|---|---|---|
| `authoring-key-lint.ts:104`, `index` guidance | 2377 | the card body
and its closing record 5051634768: author-facing keys that pass
validation but have no runtime consumer are removed (ADR-0049
enforce-or-remove); field `index` went in the follow-up slice | "were
removed in the 16.x line under ADR-0049 enforce-or-remove, which deletes
a key no runtime reads" |
| `authoring-key-lint.ts:107`, `indexed` guidance | 2377 | as above |
"the field-level `index` flag built no index and was removed for it" |
| `authoring-key-lint.ts:123`, `dataQuality` guidance | 3726 | the card
body (route 1: delete the orphaned `DataQualityRules` schema export, as
the four sibling keys were) and landing comment 5098751722 on #3733
(both orphans deleted in one PR, route 1) | "and its leftover
`DataQualityRules` schema was deleted from the public API too" |
| `authoring-key-lint.ts:133`, `cached` guidance | 3733 | landing
comment 5098751722 (route 1: `ComputedFieldCache` deleted; caching
returns only with a consumer, the ADR-0049 enforce side) | "its leftover
`ComputedFieldCache` schema was deleted with it; nothing read it, and it
returns only together with a runtime consumer" |
| `driver.zod.ts:301`, `:305`, `:309`, the `bulkCreate` / `bulkUpdate` /
`bulkDelete` tombstones | 3298 | the card body (discovery advertises an
atomic-batch capability bit so a client negotiates instead of probing
for 404 / 405 / 501) and its landing `bfa3c3fd59` (the
`transactionalBatch` bit) | "advertised by REST discovery as its
`transactionalBatch` bit, derived from the live composition so a client
negotiates instead of probing" |
| `driver.zod.ts:361`, the `fullTextSearch` tombstone | 7641 | PM ruling
5261610811 (option A: `$search` compiles to `$icontains`; `$contains`
stays case-sensitive, untouched) | "textual search is case-insensitive
by ruling, while `$contains` itself stays case-sensitive" |
| `driver/common.zod.ts:60`, `READ_ONLY_BELONGS_ON_DATASOURCE` | 4584 |
PM ruling 5163028174, the maintainer's veto unexercised (option B: no
platform read-only gate; read-only is the database account's privilege,
because a flag that only ObjectQL checks cannot stop direct connections,
migrations or DDL) | "the platform offers no read-only gate, by
decision: a flag only the application checks cannot stop direct
connections, migrations or DDL" |
| `filter.zod.ts:3262`, the `$regex` refusal | 4706 | the maintainer's
ruling recorded in 5199214776 (option B: `$regex` retired under ADR-0049
with a loud refusal naming the replacement, `$icontains` added; a real
regex on all five backends rejected) | "is retired under ADR-0049
enforce-or-remove: it is refused here, never reinterpreted" |
| `filter.zod.ts:3277`, the `$options` refusal | 4706 | as above |
"which is retired under ADR-0049 enforce-or-remove" |
| `object.zod.ts:57`, the `restore` strip prescription | 2377, 3146 |
2377 as above (`enable.trash` removed, no runtime reader); 3146 is open
and labelled `status:parked` (a platform recycle bin, not scheduled) |
"(`enable.trash` was retired because no runtime ever read it); it
returns only with a real recycle bin, and that soft-delete work is
parked" |
| `object.zod.ts:58`, the `purge` strip prescription | 2377 | as above |
"(`enable.trash` was retired because no runtime ever read it)" |
No occurrence was left in place: no cited decision was unclear.
## Quoted elsewhere
- `content/docs/deployment/validating-metadata.mdx:320-322` quoted the
`indexed` guidance verbatim, `(#2377)` included; the quote is updated in
this PR.
- `content/docs/references/data/driver.mdx`, `driver-sql.mdx` and
`driver-nosql.mdx` carry the `DriverCapabilities` tombstones; they are
generated, and `check:generated --fix` regenerated them (the one
artifact it proved stale, `check:docs`).
- `skills/**`: no quote of a changed message.
- Other hits for these numbers are their own prose with their own
citations (`validation-rules.mdx`, the
`apimethods-legacy-to-primitives.mjs` codemod's docblock, source
comments), not quotes; untouched.
## Changeset
`.changeset/20749-spec-strings-stage3-state-the-decision.md`: `patch`
for `@objectstack/spec`, carrying `Clause-②: no`. After the build,
`packages/spec/dist` carries the new sentences and none of the replaced
id-bearing fragments; the `(#2377, ADR-0049)` and `(#3146, parked)` hits
left in `dist` are source comments and TSDoc that the build keeps, not
these strings.
## Text-only proof
Stage 1's tool, unchanged except the path it loads TypeScript from: a
TypeScript-AST skeleton of each changed source in which every string
literal and template text is a placeholder, a `+` chain is flattened and
a run of adjacent string operands is one string (only its embedded
expressions are kept, in order), identifiers, numbers and regex literals
keep their text, every child is visited, and comments are never read. A
second leg compares the TEXT of every string group in order: each group
that changed must have carried a tracker id before and carry none after,
and every other group must be byte-identical. `85e29b8858` against the
head: 6 of 6 SAME on both legs (`authoring-key-lint.ts`,
`driver.zod.ts`, `driver/common.zod.ts`, `filter.zod.ts`,
`object.zod.ts`, `object.test.ts`), token counts identical per file, 14
groups changed (4, 4, 1, 2, 2, 1), all id-bearing before and id-free
after, parse diagnostics 0/0.
Controls on scratch copies of `object.zod.ts` (head version), each
mutation counted on disk first (1 anchor hit, replacement present,
anchor gone, file differs): a function renamed reads DIFF (exit 1);
`===` flipped to `!==` reads DIFF (exit 1); one literal re-split into
two `+` operands reads SAME with no extra group changed (exit 0); a text
change in a string that never carried an id reads SAME on the skeleton
and VIOLATION on the text leg (exit 1). No repo file was mutated for the
controls.
## Pins
- `packages/spec/src/data/object.test.ts:2545` ("restore/purge strip
carries the retired-trash guidance") asserted `toContain('#2377')`; it
now asserts `toContain` of "`enable.trash` was retired because no
runtime ever read it", the same strength on the words that replaced the
number.
- No other test asserts a changed phrase: every replaced fragment and
every distinctive unchanged phrase of the 13 messages was searched
across all test files. The driver and having-filter refusal tests assert
`$icontains`, `$regex` and `RETIRED`, which stay, or compare against
`RETIRED_FILTER_OPERATORS[op].why` by reference.
## Tests
All builds and tests through `scripts/pm/os-verify-lock.sh`, each
`VERDICT command-exit 0`.
- `pnpm --filter @objectstack/spec build` (it has no workspace
dependencies, so the closure is the package itself); then `pnpm turbo
run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*`
("Tasks: 71 successful, 71 total") for the dist-reading gates. The tree
was clean after both builds.
- `@objectstack/spec` `test` (the `local` project), `vitest run
--project local --maxWorkers=2`: "Test Files 602 passed (602) / Tests
17787 passed | 1 todo (17788)".
- The `repo` project: the 11 files that read the changed sources or the
regenerated docs, plus the `src/data` ones, "Test Files 11 passed (11) /
Tests 258 passed (258)". 38 of the other 40 were run too, and 561 of
their tests had passed with 0 failed when a 420 s wall-clock cut stopped
the run in the slowest file, `scripts/build-schemas-check-mode.test.ts`;
the two `publish-smoke` files were not run. That remainder is not
measured here and is CI's.
- `@objectstack/spec` `typecheck`: `tsc --noEmit` exit 0,
`check:scripts-typecheck` exit 0, and "check:test-typecheck: OK —
@objectstack/spec's test layer compiles under
packages/spec/tsconfig.test.json; 52 file(s) / 246 error(s) / 135 pinned
signature(s) held".
- ESLint on the six changed TypeScript files, `eslint --no-inline-config
--format json`: 6 files, 0 errors, 0 warnings. That narrowing is
complete for them: ESLint's own config resolves for each (none ignored),
and this repo's config enables no type-aware linting
(`parserOptions.project` and `projectService` absent), so these edits
cannot move any untouched file's verdict. The repo-wide `pnpm lint` run
is CI's.
## Gates
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths) at `77a448f8cc`, change set 11 paths against the
merge base `85e29b885`: 110 commands, run one at a time from the
worktree, each exit code written before any pipe. `--ran`: "110 derived
famil(ies) accounted for — 110 run, 0 NOT-MEASURED (a DERIVED zero — all
110 recorded an exit code and none of them is 3)". All 110 exit 0; the
dist-reading ones (`check:generated`, `check:docs-transcript-drift`,
`check:dts-closure`, `check:dual-build-cjs-loads`,
`check:lean-entry-closure`, `check:published-files`,
`check:published-readme-links`, `check:sourcemap-no-sources-content`)
after the full build.
- Outside the derived set, also all exit 0: the 11 declared
wide-population families, and the artifact-roster families whose roster
sits under one of this PR's directories or reads its subject
(`check-changeset-fixed`, `check:meta-url-spelling`,
`check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`,
`check:filter-alias-parity`, `check:error-code-provenance`,
`check-published-list-mirrors`, `check:published-readme-exports`).
- Named by the dispatch: `@objectstack/spec` `check:generated` exit 0
("All 15 generated artifacts are up to date"); `pnpm
check:doc-authoring` (self-test and run) exit 0 ("17287 customer-facing
string(s) across 1246 spec sources clean" and "sibling-package prose ids
hold the baseline — 72 pinned site(s) across 21 file(s) ... no growth,
no burn-down unrecorded"); `pnpm check:nul-bytes` exit 0;
`check-adr-0087-registration` exit 0 ("this PR adds no declared-breaking
changeset (1 non-breaking changeset(s) seen)"); `check:empty-changeset`
exit 0; `check-changeset-fixed` exit 0; `check-changeset-no-major` exit
0 on the plain run and when fed this body as a `pull_request` event;
`check:partof-closing-keyword` on this body exit 0.
- `check-issue-citations`: "no issue citations added against 85e29b8
(5 file(s) read)". The prose-id ledger is untouched: `packages/spec` is
outside it, and no gate is added or loosened.
- `main` moved three commits past the merge base while this ran
(`2ee8383f4e`, `25797a16e1`, `f9a8eb889e`); none touches
`packages/spec`, `content/docs` or any file of this PR, so no merge was
made.
## Acceptance notes
Noted, not filed:
- The census leaves, for later stages, in the ruling's order: (b) 91
conversion summaries (108 ids) and 3 descriptions; (c) 58 conformance
notes (68 ids), of which 4 are a test selector and 1 a pinned case name;
(f) 17 registry rationales (33 ids); (e) about 1803 test strings (1919
ids); and the excluded `migrations/registry.ts` (48 messages, 215 ids)
on #20234's stage 11.
- `check:doc-authoring`'s spec leg read green on `main` over all 13 of
these messages. It recognises a `retiredKey()` prescription and a
builder whose return feeds one, but not a string passed as an ARGUMENT
to such a builder (`retiredKey(capRemoved(key, '...'))`), and its
hoisted-const pass is per module, so a guidance table consumed from
another module (`READ_ONLY_BELONGS_ON_DATASOURCE`, the `why` of
`FIELD_KEY_GUIDANCE` and `RETIRED_FILTER_OPERATORS`) sits outside it.
`LEGACY_API_METHOD_GUIDANCE` is not one of these: it is defined and
consumed in its own module (`object.zod.ts:52` and `:123`), and why the
leg passed it is not measured here. With this PR the population of those
shapes is empty; the gap stays for the next one. No gate is changed
here, by ruling.
- Comments in these files keep their ids: a comment is the sanctioned
home for an internal anchor.
> Seat edit, 2026-10-03T02:37Z: the `check:doc-authoring` note no longer
lists `LEGACY_API_METHOD_GUIDANCE` as consumed from another module.
Contract review `5964640454` read it as consumed only in
`object.zod.ts`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e3ad492 commit ad7c351
11 files changed
Lines changed: 60 additions & 36 deletions
File tree
- .changeset
- content/docs
- deployment
- references/data
- packages/spec/src/data
- driver
Lines changed: 17 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
318 | 318 | | |
319 | 319 | | |
320 | 320 | | |
321 | | - | |
322 | | - | |
| 321 | + | |
| 322 | + | |
323 | 323 | | |
324 | 324 | | |
325 | 325 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
169 | 169 | | |
170 | 170 | | |
171 | 171 | | |
172 | | - | |
173 | | - | |
174 | | - | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
175 | 175 | | |
176 | 176 | | |
177 | 177 | | |
| |||
183 | 183 | | |
184 | 184 | | |
185 | 185 | | |
186 | | - | |
| 186 | + | |
187 | 187 | | |
188 | 188 | | |
189 | 189 | | |
| |||
0 commit comments