Repository navigation
Commit 9e9bb46
fix(spec): os migrate meta guidance for the datasource-*, filter-*, action-*, data-* and element-* migration entries states each lesson in words, not tracker numbers (stage 4) (#20509)
Part of #20233
Stage 4: the datasource-, filter-, action-, data- and element- families.
Clause-②: no
**Stage 4 of a staged card.** The card stays open for later stages; this
PR carries no closing keyword. Text only: no entry id, `surface`, `from`
/ `to`, conversion or matching logic moves, and the chain rewrites
exactly what it rewrote before.
## What this does
`os migrate meta` prints every ADR-0087 semantic entry it crosses as one
block: `⚠ [protocol N] SURFACE → REPLACEMENT`, then `why:` (the entry's
`reason`) and `verify:` (its `acceptanceCriteria`). AGENTS.md's
runtime-string rule applies to all of it: 「Runtime strings — refusal
prose, prescriptions, anything an author is shown — carry no tracker
number (`pnpm check:doc-authoring`): the lesson goes into the text.」
Form **D** of ruling C+D on #19123 (`5749154545`) sets the shape: the
lesson in words, and no number, dead or alive; a cross-repo number
(`cloud#N`, `objectui#N`) is still a tracker number.
This stage covers the next five families, `datasource-`, `filter-`,
`action-`, `data-` and `element-`: **150 sites → 0** in the three prose
fields, across 33 entry files. None of the 37 entries in these families
carries a tracker id in `surface` (ruling A of the stage-1 ACCEPT,
`5858839916`, is checked and has nothing to do here). Each site now says
what the cited ruling, measurement or fix decided. ADR ids stay.
`registry.ts`, `spec-changes.json` and `docs/protocol-upgrade-guide.md`
are regenerated from the entries (`gen:migration-registry`,
`gen:spec-changes`, `gen:upgrade-guide`), never hand-edited. The pin now
holds eleven families.
## Census — tracker ids in the author-shown fields
**Instrument.** The stage-2 AST instrument, re-written to its published
description (the stage-2 and stage-3 copies lived in removed
scratchpads): a TypeScript-AST walk over every
`packages/spec/src/migrations/entries/**/*.ts`. For each entry object
literal it evaluates the string value of `replacement`, `reason`,
`acceptanceCriteria` and (counted separately) `surface`, joining string
literals with `+`, then counts `#` followed by 4 or 5 digits at a word
boundary. **Validated first** by reproducing earlier readings on their
own trees (extracted with `git archive`): on `443b2f4fdc` (stage 1)
`engine-` 67, `datasource-` 43, `filter-` 30, `action-` 26, `element-`
25, `data-` 24, whole tree 266 entries / 1,016 sites / 9 `surface`; on
`0d7ed5a378` (stage 3 landed) whole tree **711** sites / 7 `surface` —
every figure equal to the stage-1 census and to stage 3's after-count.
Unevaluable fields: 0.
**Controls, same run.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), before and after.
- **Dark (comment lines):** 823 `//` / docblock lines in entry files
carry a tracker id, and none is counted; 823 before and after. Comment
lines are the sibling card #20234's surface, and this PR touches none
(proved below).
- **Dark (field boundary):** the 7 `surface` sites left in the tree
(other families) count 0 in the three-field total.
**Base `fc0db22b`:** `datasource-` 9 entries, **43** sites (3 / 38 / 2);
`filter-` 11, **32** (1 / 30 / 1); `action-` 6, **26** (0 / 23 / 3);
`element-` 5, **25** (2 / 19 / 4); `data-` 6, **24** (0 / 24 / 0).
**150** sites (6 / 134 / 10) in 33 of the 37 entries; 88 distinct ids
(80 bare, 8 `objectui#`, 2 `cloud#`). Whole tree: 310 entries, **721**
sites (711 after stage 3, plus entries `main` added since in other
families), 7 `surface`.
**After this PR:** all five families **0**; the six earlier families
still 0; whole tree **721 → 571**; `surface` 7 (other families). The
PM's rough line count (156 sites, 33 files) is a wider instrument; the
AST reading is 150 in the same 33 files.
| entry | sites (replacement / reason / acceptanceCriteria) | short
`#NN` |
|---|---|---|
| `17.action-descriptor-is-async-retired` | 2 (0 / 2 / 0) | |
| `17.action-descriptor-resume-authority-default-flip` | 10 (0 / 7 / 3)
| |
| `17.action-session-roles-to-positions` | 12 (0 / 12 / 0) | |
| `17.data-driver-find-stream-retired` | 1 (0 / 1 / 0) | |
| `17.data-driver-query-omit-object` | 9 (0 / 9 / 0) | |
| `17.data-engine-batch-retired` | 1 (0 / 1 / 0) | |
| `17.data-field-changed-event-retired` | 4 (0 / 4 / 0) | |
| `17.datasource-config-inline-credential-refused` | 1 (0 / 1 / 0) | |
| `17.datasource-config-placeholder-refused` | 5 (0 / 5 / 0) | |
| `17.datasource-config-url-userinfo-refused` | 4 (0 / 4 / 0) | |
| `17.filter-regex-options-retired` | 8 (0 / 8 / 0) | |
| `18.action-bulk-dispatch-contract-undeclared` | 1 (0 / 1 / 0) | |
| `18.action-engine-facade-find-query-envelope` | 1 (0 / 1 / 0) | 1 |
| `18.data-file-value-duration-unit-in-key` | 6 (0 / 6 / 0) | 1 |
| `18.data-nosql-query-options-timeout-unit-in-key` | 3 (0 / 3 / 0) | 1
|
| `18.datasource-config-mongo-options-credential-refused` | 5 (0 / 5 /
0) | |
| `18.datasource-config-postgres-url-unparseable-refused` | 4 (0 / 4 /
0) | |
| `18.datasource-config-url-query-credential-refused` | 4 (0 / 4 / 0) |
|
| `18.datasource-credentialsref-mongo-composed-no-username-refused` | 10
(2 / 7 / 1) | |
| `18.datasource-credentialsref-mongo-url-no-user-refused` | 10 (1 / 8 /
1) | |
| `18.element-data-source-and-object-block-filter-rule-array` | 11 (1 /
9 / 1) | 1 |
| `18.element-number-filter-rule-array` | 7 (0 / 6 / 1) | |
| `18.element-record-picker-filter-rule-array` | 7 (1 / 4 / 2) | |
| `18.filter-between-blank-endpoint-refused` | 3 (0 / 3 / 0) | 1 |
| `18.filter-between-field-reference-endpoint-refused` | 5 (1 / 4 / 0) |
|
| `18.filter-comparand-types-and-widget-nested-slots-refused-at-save` |
2 (0 / 2 / 0) | |
| `18.filter-equality-array-comparand-refused` | 1 (0 / 1 / 0) | |
| `18.filter-equality-array-comparand-refused-at-save` | 1 (0 / 1 / 0) |
|
| `18.filter-icontains-comparand-refused-at-parse` | 3 (0 / 3 / 0) | |
| `18.filter-ne-array-comparand-refused` | 1 (0 / 1 / 0) | |
| `18.filter-preset-ordering-comparand-refused` | 4 (0 / 4 / 0) | |
| `18.filter-query-face-comparands-refused-at-save` | 1 (0 / 1 / 0) | |
| `18.filter-text-operator-declared-type-refused` | 3 (0 / 2 / 1) | 1 |
| **total, 33 entries** | **150 (6 / 134 / 10)** | **6** |
## Text only — proved by a base-vs-head AST comparison
For every entry file this PR changes, both versions (`fc0db22b` and the
head) are parsed and three things are compared token for token: every
import declaration, every property other than the three prose fields (so
`id`, `surface`, `from` / `to` and any matcher, by evaluated value), and
every comment token in the file. **33 files compared, 0 with a non-prose
change.** The instrument is shown able to fail first: on an in-memory
copy it reports DETECTED for a mutated `id`, a mutated comment and a
mutated `surface`. So none of #20234's comment lines moved, and no
entry's identity or matching moved.
## Every citation read, and what the text now says
Each cited id was read with a single-card REST read (body plus the
ruling, measurement or landing comments), resolved against the
repository its sentence names. Ids are in code spans so this body posts
no cross-references. **10 bare ids answer 404** on both the issues and
the pulls endpoint (re-probed with a 200 control, `14478`), and **2
`cloud#` ids are unreadable from this session** (403); those sentences
are rewritten from what `main` records, listed in Acceptance notes.
**`datasource-` (14 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `7990` | Maintainer, 2026-08-12, Option A: close inline credentials
per artefact (each schema refuses at publish and diverts to its existing
mechanism); the heuristic `sys_metadata` write guard parked. | "The
maintainer ruled on 2026-08-12 to close that per artefact: each schema
that admitted an inline credential refuses it at publish …"; "the
inline-credential closure" |
| `8078` (PR) | The spec half of that ruling; measured that `${…}`
placeholders reach the client verbatim and that `config.url` still took
the secret. | "measured by the credential census while the
inline-credential refusal was being built"; "measured when the
inline-credential refusal was built" |
| `8082` | Maintainer, 2026-08-12, option A: refuse URL userinfo at
publish through one shared value-level parse; runtime DSNs unaffected. |
"The maintainer ruled on 2026-08-12 (Option A) to refuse the URL
userinfo password at publish, through one value-level parse the driver
schemas share" |
| `8336` | Maintainer, 2026-08-13, direction 2 of two: refuse `${…}` at
publish; implementing resolution rejected. | "The maintainer ruled on
2026-08-13 for the second of two directions: refuse the syntax loudly at
publish" |
| `8337` | The credential-bearing URL query parameter refusal, one
syntax over from userinfo. | "the credential-bearing URL query
parameters"; "the query string was the third spelling of the identical
secret, one syntax over" |
| `8876` | **404** — see Acceptance notes. | "the asymmetry the URL
grammar keeps between its two userinfo halves — a username is not
credential material" |
| `8696` | **404** — see Acceptance notes. | "measured when the bound
secret was made to reach the mongo client on its URL branch"; "`new
URL()` rejects the multi-host form outright, and the mongo arm hands the
authored URL to its client untouched"; "the defect class closed when
each DSN branch was made to inject the bound secret its composed branch
already used" |
| `9041` | **404** — see Acceptance notes. | "the sibling URL-branch
entry, `datasource-credentialsref-mongo-url-no-user-refused`"; "this
URL-branch refusal" |
| `9147` | The composed-branch twin: bound secret + no `url` + no
`username` refused, inheriting the URL-branch ruling. | "this
composed-branch refusal" |
| `7314`, `7385`, `8152`, `8875` | The driver-factory arms that dropped
something declared: the turso loader's install remedy and half its
config, the other optional arms' missing-package remedy, turso's
never-read bound secret, and (`8875`, **404**) the mysql DSN branch. |
"the family of driver-factory arms, closed one driver at a time, that
each dropped something declared without a word: the optional-driver arms
that answered a missing package with no remedy, the turso arm that never
read its bound secret, and the mysql and mongo DSN branches that
discarded one" |
| `8873` | **404** — see Acceptance notes. | "the postgres equivalent is
judged on its own client's measurement, never inherited" |
**`filter-` (24 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4706` | Maintainer, 2026-08-06, option B: retire `$regex` loudly and
add `$icontains`; five-backend true regex (A) excluded (turso remote
cannot, no business pull). | "the maintainer's 2026-08-06 ruling (option
B: retire `$regex` loudly and add `$icontains`, rather than make five
backends agree on one regex dialect)" |
| `5701`, `5702` | The contract half and the driver half of that ruling.
| "the retirement's own contract-half change"; "the contract half (the
`$icontains` declaration, …)"; "the driver half" |
| `6148` | **404** — see Acceptance notes. | "the gate that makes a
breaking changeset state its ADR-0087 disposition" |
| `18012` | **404** — see Acceptance notes. | "Maintainer ruling A of
2026-09-17: a blank `$between` endpoint is refused at the authoring
door, and the refusal names the blank side." |
| `13495` | driver-memory's reference matcher answered a null-bounded
range with every valued row. | "The reference matcher had already been
taught to survive the null-bound form of exactly this" |
| `objectui#9695` | The console filter builder stops padding a
half-typed pair with an empty string. | "is a change to the console's
own filter builder and lands on its own schedule" |
| `7596` | Maintainer, 2026-08-11, ADR-0049 REMOVE: no `{ $field }`
endpoint in either `$between` union. | "Maintainer ruling of 2026-08-11
on column-reference range endpoints, ADR-0049 enforce-or-remove:
REMOVE"; "reviewed and accepted with that ruling" |
| `7713` (PR) | Shipped that removal with a not-required disposition. |
"the 2026-08-11 changeset carried the disposition not-required" |
| `5222` | `$field` compiled to a column-to-column comparison on the SQL
faces. | "the position the column-to-column comparison compiles on every
face" |
| `19377` | **404** — see Acceptance notes. | "filed as an entry of its
own rather than as an already-registered rider" |
| `20116` | The collector for the family "the save door accepts
comparand shapes the query faces refuse", closed in stages. | "the
second stage of closing the family of comparand shapes the save door
accepted and the query faces refused"; "the family of comparand shapes
…" |
| `7872` | Maintainer, 2026-08-12: the shared face accepts `string` /
`number` / `bigint` / `boolean` / `null` / `Date` and refuses everything
else. | "the accepted set the maintainer ruled on 2026-08-12: string,
number, bigint, boolean, null and Date" |
| `19889` | Director seat, 2026-09-24, option A: the schema door refuses
what the compile face refuses (the standing array-equality refusal
applied to it). | "Ruled on 2026-09-24 (option A), applying the standing
refusal of an array in the equality slot to the schema door" |
| `19757` | Maintainer, 2026-09-23, option 乙: refuse an equality-slot
array at the shared face; 甲 (declare array equality) and 丙 (document the
divergence) rejected. | "Maintainer ruling of 2026-09-23 (option 乙 —
rather than declaring an array equality the SQL-family backends would
have to invent, or documenting a divergence that stays silent on one
backend)" |
| `19514`, `objectui#9050` | The protocol half of the maintainer's
2026-09-20 ruling C′ on the console's filter converter, rule 1 「the
differences are the protocol's to close」. | "The protocol half of the
maintainer's 2026-09-20 ruling (option C-prime) on the console's filter
converter, whose first rule reads, verbatim and untranslated: 「the
differences are the protocol's to close」" |
| `18113` | Published the text-comparand refusal predicate and reason
beside `FILTER_TEXT_CASES`. | "the pair published in this package beside
FILTER_TEXT_CASES for exactly this reason" |
| `19886` | Director seat, 2026-09-24, option A on standing text: `$ne`
with an array refused at the shared face and the schema door. | "Ruled
on 2026-09-24 by the director seat, on the standing contract text
(option A)" |
| `8690` | Ruled 2026-08-15: option B (engine door refuses an
uninterpretable temporal comparand) with option C (the preset refusal at
authoring) alongside. | "The authoring half (option C) of the
maintainer's 2026-08-15 ruling on uninterpretable temporal comparands,
ruled alongside the engine door (option B)"; "measured on the defect
report" |
| `8808` (PR) | The engine door. | "before the engine door and a 400
after" |
| `15661`, `15773` | Maintainer, 2026-09-05, C-deny: refuse a text
operator over a never-string declared type, over the existing type sets;
landed at the engine seam. | "Maintainer ruling of 2026-09-05 (option
C-deny: refuse now, over the type sets the contract already declares,
minting no new vocabulary), landed at the engine seam" |
| `14079` | Ruling A: a stored non-string value never satisfies a
positive text operator and satisfies `$notContains`. | "(a stored value
that is not a string never satisfies a positive text operator and
satisfies `$notContains`)" |
**`action-` (19 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `6748` | Retire `isAsync`: zero readers on a fresh three-repo
measurement. | "a fresh three-repo measurement (taken when the key was
filed for retirement, and re-run at pickup)" |
| `6667` | Enforce `supportsPause` at runtime. | "The sibling took the
ENFORCE leg of the same ruling" |
| `3801` | The generic resume route needs an authorization gate keyed on
the suspended node. | "The generic resume route's authorization gate
keys on the SUSPENDED NODE" |
| `3823` | The revise-window `wait` pause is service-owned but inherited
`'any'`. | "The revise-window incident decided the direction" |
| `4484`, `5540`, `6011` | The three retirements whose disposition this
one shares. | named by their entry ids, which the sentence already
carried |
| `5561` | `resumeAuthority` defaulted to `'any'` — fail-open by
omission. | the sentence already states it; ADR-0019's resume-seam
addendum named by date and title |
| `5703` | `supportsPause` / `isAsync` were zero-reader declarations. |
"no longer the declaration nothing enforced" |
| `5613` | Maintainer, 2026-08-06, contract-first ("C skeleton + A
semantics"): declare the shape as it stands, then rename on the typed
face. | "The maintainer ruled contract-first on 2026-08-06 (…: declare
the shape as it stands first, then rename on the typed face)"; "the
runtime half of the same ruling" |
| `5697`, `5779` | Phase 1 (the schema) and phase 2's spec half (the
canonical key and the alias). | "phase 1 declared …"; "`positions` is
now the canonical key …" |
| `5050` | The hook-side `roles` removed outright. | "(removed
outright)" |
| `3280`, `3290` | The hook `ctx.session.tenantId` alias: deprecated,
then removed in the next major. | "the hook `ctx.session.tenantId`
alias: deprecated first, removed in the next major" |
| `4579`, `4657` | The `openApi31` and `activationEvents` retirements. |
named by their surfaces, which the sentence already carried |
| `17319` | Ruled 2026-09-12, A: an action declares its dispatch
contract; B (unify the two wirings) refused. | "the 2026-09-12 ruling
that made an action declare its dispatch contract refused exactly that
option" |
| `14175` | The earlier typing fix that gave `find` the filter alone. |
"the parameter shape an earlier typing fix chose (the filter alone),
ruled by the director seat on 2026-09-12, with the maintainer's
agreement" |
**`data-` (15 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `4484`, `4618`, `4673` | The three retirements, each already stated by
its own entry. | trailing ids dropped (ADR ids kept) |
| `4639` | Multi-record predicate writes got `data.records.*` events. |
"since multi-record predicate writes were given events of their own";
"the way bulk writes were given theirs" |
| `3196` | Webhook triggers trimmed to producers that exist. | the
sentence already states it |
| `cloud#1053`, `cloud#1030` | **403 here** — see Acceptance notes. |
"20 such sites were measured in the downstream cloud codebase, and a
`$like` the type layer would have caught reached runtime there through
exactly that hole" |
| `6350` | The stock reconciliation of the v17 train's breaking
changesets against the ledger; this change was its control sample and
was itself an omission. | "Registered by the stock reconciliation that
compared the breaking changesets already on the v17 release train
against this ledger. This change was that audit's CONTROL sample …";
"(backfilled by that reconciliation)" |
| `5181` | The `DriverQuery` narrowing itself. | "this change"; "this
narrowing" |
| `6321`, `6083` | Two later call-parameter changes, both registered
(`6083` **404**; `main` records it as ADR-0122 phase 2). | "Two later,
smaller driver call-parameter changes both registered" |
| `18669` | Maintainer, 2026-09-17, ruling A: rename the last two
duration keys no closed duration type could express, no new closed type,
no narrowing. | "Maintainer ruling A of 2026-09-17 on the last two
duration keys no closed duration type could express" |
| `18122` | Published `DurationMs` / `DurationSeconds` beside `EpochMs`,
the unit set derived from six genuine duration rows. | "published beside
`EpochMs` as a closed duration type"; "one of the six genuine durations
the closed types' unit set was derived from" |
| `14478`, `15680` | Ruling B of 2026-09-02 on duration units; the
data-directory rename round. | "Maintainer ruling B on duration units
(2026-09-02)"; trailing ids dropped |
**`element-` (17 ids)**
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `objectui#6206` | Maintainer, 2026-08-25, option B: one filter
orthography platform-wide — the `ViewFilterRule` array, not a
record-shaped exception; measurement-first binding. | "the maintainer's
2026-08-25 ruling, option B: …" (three entries); "the console-side half
of this convergence" |
| `15442`, `15449` | Ruled 2026-09-06, option A: converge the binding
and the four `object-*` doors family-wide, one entry; exception and
mixed rejected. | "ruled 2026-09-06, option A: converge the binding and
the four block doors family-wide, under one entry, rather than record an
exception"; "a gap measured on its own" |
| `7751` | Maintainer, 2026-08-12: the `object-*` block props enter
`ComponentPropsMap`. | "a read-point record derived from the renderers
on 2026-08-13, when the `object-*` blocks first got props schemas in the
map" |
| `objectui#6948` | The console's filter converter had no branch for
`$and` / `$or` / `$not`. | "(the console's filter converter had no
branch for them)" |
| `15828` | `POST /analytics/query` refused the array `where` the
adapter sent. | the sentence already states it; "the runtime route's
refusal of the array corrects it here" |
| `16626` | **404** — see Acceptance notes. | "parked behind a bump of
that pin" |
| `objectui#7754`, `objectui#7752`, `objectui#6828` | The console
adapter lowers an array analytics filter before the wire; `aggregate()`
runs `translateFilterArray`. | "the console adapter was changed so
`ObjectStackAdapter.aggregate()` runs the same `translateFilterArray`";
"The adapter-side lowering lands in the console's own repository." |
| `5158` | Maintainer, 2026-08-04, ruling C: `FilterArray` is input-only
sugar with one lowering seam (`parseFilterAST`). | "since the
maintainer's 2026-08-04 ruling C declared the array input-only sugar
with one lowering seam" |
| `5334` | The in-process analytics door, added when an array `where`
was silently dropped. | "(added when an array `where` was found silently
dropped on the analytics path)" |
| `17321` | Ruled 2026-09-12, B: a partial D2 conversion for the
losslessly mappable record forms; combinators pass through, named. |
"(ruled 2026-09-12, option B: convert what maps losslessly and name what
does not, rather than leave every stored row to its next save or flatten
combinators)" |
| `14406` | Converged `element:record_picker`; its census pin asks
whether any `filter` door refuses the array. | "the twin of the census
pin that asks whether any `filter` door still refuses the array"; the
2026-08-25 Option-A ordering ruling stated as "measure the consumer's
read path before the contract moves" |
| `12039` | `element:number` converged. | "after `element:number`
converged" |
| `objectui#7663` | The console registry's `inputs.filter` flip for the
picker. | "a console-side change filed in the objectui repository,
blocked on that release" |
| `15829` | The dashboard widget `filter` location, a finding of its
own. | "a different family, judged on its own" |
## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`,
widened
`COVERED_PREFIXES` is now `engine-`, `ui-`, `plugin-`, `driver-`,
`kernel-`, `system-`, `datasource-`, `filter-`, `action-`, `data-`,
`element-` (`data-` does not select `datasource-` or `dataset-`: the
match is `startsWith('data-')`). The `REWRITTEN` floor rises from **55
to 88** ids: the 33 entries of this stage that carried a tracker id. The
three `it` blocks are textually unchanged: the detector is exercised on
both sides, every covered prefix must select an entry, and each covered
block is found verbatim in the real CLI's stdout before it is asserted
clean. The file keeps its stage-1 name; the header lists the eleven
covered families.
## Ablation — the widened pin can fail on a new-family block
From committed state, HEAD `d8bcc46a`, in one lock turn, with
`scripts/ablation-replace.mjs` in wrap mode (it owns the mutation's
restore trap; the leg script adds its own `trap … EXIT INT TERM` that
restores `registry.ts` from `HEAD` by absolute path and checks the blob)
and `scripts/ablation-dist-preflight.mjs` gating each leg. The bundle is
built from the generated `registry.ts`, so that is the file mutated.
- **Mutation.** In `registry.ts`, the `acceptanceCriteria` of
`datasource-credentialsref-mongo-url-no-user-refused`: anchor `parse
reports this URL-branch refusal.` → `parse reports the #9041 refusal.`
The tool read anchor 1 → 0 and replacement 0 → 1, blob `7001c102` →
`6f3f81ff`.
- **Mutate leg.** Spec build exit 0. Preflight: marker present in 4
built files. Pin: **red**, `1 failed | 2 passed` —
`datasource-credentialsref-mongo-url-no-user-refused: the printed
guidance cites a tracker id: expected '#9041' to be undefined`.
- **Restore.** Tool-proven: blob `7001c102` == HEAD, `git diff HEAD`
empty.
- **Restore leg.** Spec build exit 0. The `--absent` preflight found the
marker in none of 224 built files, with the working tree clean against
HEAD. Pin: **green**, `3 passed`. Whole tree afterwards: 0 dirty paths.
## Verification
Final head **`d8bcc46a`**. Every heavy run went through
`scripts/pm/os-verify-lock.sh` (`VERDICT command-exit 0` on each turn;
per-step exit codes recorded separately). Where a line was taken at
`95fb97ea` it says so: the one commit after it (`d8bcc46a`) changes one
entry sentence and the three generated projections, and every reader of
that text was re-run at `d8bcc46a`.
- **Build:** `pnpm exec turbo run build --concurrency=2
--filter='@objectstack/cli^...'` gives `Tasks: 58 successful, 58 total`
(at `95fb97ea`); the spec package was rebuilt at `d8bcc46a` in both
ablation legs (exit 0).
- **Pin:** at `d8bcc46a`, the ablation's restore leg:
`test/migrate-meta-engine-guidance.test.ts` `3 passed`. With its
neighbour at `95fb97ea`: `pnpm --filter @objectstack/cli exec vitest run
--project integration --maxWorkers=2
test/migrate-meta-engine-guidance.test.ts
test/migrate-meta-default-range.test.ts` gives `Test Files 2 passed`,
`Tests 10 passed | 1 skipped` (the skip is the default-range file's own
`skipIf`). Re-run with its neighbour at `d8bcc46a`: `Test Files 2
passed`, `Tests 10 passed | 1 skipped`. One earlier run at this head is
void and is not counted: it spawned the CLI while a concurrent lock-free
gate was rebuilding the package `dist/` trees (`MODULE_NOT_FOUND`, both
files); the re-run is the reading.
- **Spec, the whole `local` project** (at `95fb97ea`): `pnpm --filter
@objectstack/spec exec vitest run --project local --maxWorkers=2` gives
`Test Files 572 passed (572)`, `Tests 16789 passed | 1 todo`. **The
`repo` project** (at `d8bcc46a`): `Test Files 38 passed`, `Tests 690
passed`. **The readers of the changed sentence** (at `d8bcc46a`):
`src/migrations`, `src/ui/action-params.test.ts`,
`src/ui/filter-rule-array-guidance.test.ts` give `Test Files 5 passed`,
`Tests 229 passed`.
- **CLI unit** (at `95fb97ea`, no CLI file changed after):
`test/vitest-tiers-partition.test.ts` and
`src/utils/spec-release-changes.test.ts` give `Test Files 2 passed`,
`Tests 28 passed`.
- **The call-spelling census that reads `registry.ts`:** `pnpm --filter
@objectstack/driver-sql exec vitest run --maxWorkers=2
src/sql-driver-query-signature.test.ts` gives 15 passed.
- **Typecheck** (at `95fb97ea`; the last commit edits string literals
only): `pnpm --filter @objectstack/spec typecheck` exits 0 (test layer:
53 files / 251 errors held in its ledger); `pnpm --filter
@objectstack/cli typecheck` exits 0 (3 files / 28 errors held).
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derives **90** families at `d8bcc46a`
(the same set it derived at `95fb97ea`). `--ran` over the recorded exit
codes reads **90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN**, all exit 0.
They include `check:doc-authoring` ("16735 customer-facing string(s)
across 1167 spec sources clean"), `check:issue-citations`,
`check:migration-registry` ("registry.ts is current (310 semantic, 230
retired-key, 206 retired-def)"), `check:spec-changes`,
`check:upgrade-guide`, `check:generated` ("All 15 generated artifacts
are up to date"), `check:org-identifier`, `check:nul-bytes`,
`check:dual-build-cjs-loads` (104 require entry points across 66
packages load), `check:type-check-debt`, `check:adr-0087-registration`
and `check:changeset-no-major`.
- How the reading was assembled: a container restart cut the pass at
`d8bcc46a` after 30 families; the other 60 were run afterwards on the
same head, and `check:type-check-debt`, whose first resumed run lost a
package build to an OOM kill (exit 3, `PREREQUISITE NOT MET`), was
re-run alone and exited 0 ("4 ledger entr(ies) re-measured … none above
its recorded number").
- The earlier full pass at `95fb97ea` read 88 exit 0,
`check:dual-build-cjs-loads` exit 3 (dists not yet built) and
**`check:org-identifier` exit 1**: my rewrite of the action-session
entry had spelled the removed hook read literally. `d8bcc46a` names that
alias in words instead, and the gate reads OK there.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 36 changed `.ts`
files at `d8bcc46a` reports 36 files, 0 errors, 0 warnings.
- The population is read from `eslint.config.mjs`:
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus `NEVER_LINTED`, and all 36
are in it (no file-ignored warning).
- Invariance: the config enables no type-aware linting (no
`parserOptions.project`, no typed rules), so a text edit cannot move the
verdict on a file it does not touch.
- **Mergeability:** `main` moved 10 commits past the base, to
`4a1df196`; the only ones under `packages/spec/src/migrations/` are the
landed `20488` (two entries in the `field-` and `ui-` families, plus
`registry.ts`). A driver-free bare-clone `merge-tree --write-tree` of
`d8bcc46a` against `4a1df196` exits 0 with no conflicted path, and the
census over that merged tree reads 0 sites in all eleven covered
families, so `main` was not merged in; CI's merge ref runs the registry
gates on the merged tree.
## Acceptance notes
- **Ten dead ids, rewritten from the code on `main`.** Each answers 404
on both the issues and the pulls endpoint, with a 200 control (`14478`).
- `8696` / `8875` / `8873` (the bound secret reaching the mongo, mysql
and postgres clients): `service-datasource`'s
`default-datasource-driver-factory.ts`, its CHANGELOG (the mongo
URL-branch entry says it "closes the last arm of the family" the other
cards closed one driver at a time; the mysql DSN fix is `{ uri, password
}`) and `bound-secret-dsn-branches.test.ts`, which pins the bound secret
outranking `options.auth`. `common.zod.ts` and
`pg-url-grammar.server.ts` record why the shared helpers must not parse
(mongo's multi-host form, which `new URL()` rejects).
- `8876`: `common.zod.ts` (`urlUserinfoUsername`, the username half of
the same grammar; a username is not credential material).
- `9041`: `datasource.zod.ts`
(`CREDENTIALS_REF_MONGO_URL_NO_USER_REFUSED` and its docblock); the text
now names the sibling entry by id.
- `6148`: `scripts/check-adr-0087-registration.mjs`'s header (a
declared-breaking changeset must state its ADR-0087 disposition in
writing).
- `18012`: `.changeset/18012-between-blank-endpoint-refused.md` and
`filter.zod.ts` (ruling A of 2026-09-17: a blank bound refused at the
authoring door, the blank side named).
- `19377`: `.changeset/19377-between-field-endpoint-runtime-door.md` and
`filter-comparand-shape.ts` (the runtime door brought under the
2026-08-11 removal).
- `16626`: `.changeset/filter-orthography-binding-and-object-blocks.md`
on `main` records it as the objectui pin bump the element convergence
was parked behind; the text now says "a bump of that pin", and the pin
sha it names is unchanged.
- `6083`: `main` records it as ADR-0122 phase 2 (the spec CHANGELOG);
the sentence keeps its claim ("two later, smaller driver call-parameter
changes both registered") without the numbers.
- **Two cross-repo ids this session cannot read.** `cloud#1053` and
`cloud#1030` answer 403 (`objectstack-ai/cloud` is not reachable here,
and attaching it was refused). The sentence was rewritten from what this
repository records: `packages/spec/CHANGELOG.md` (the published
`DriverQuery` entry: 20 measured `as any` sites downstream, and a
`$like` that reached runtime through that hole) and the `5181` card body
("cloud 实测 20 处 cast … cloud#1030 的 `$like` 本可在类型层拦住"). Nothing the
cloud cards add beyond that is claimed.
- **Short numbers and ruling-record ids went too (invisible to the
regex).** Six decision-batch numbers (`#43` ×2, `#55`, `#123`, `#146`,
`#151`), three ruling-record comment ids and one `batch 217 item 3`
spelling are numbers an author is shown and cannot follow, so each is
dropped. So are the maintainer's batch acknowledgements 「146 同意」 and
「217 同意」 and the bare 「同意」 beside `objectui#6206` (×3), `15442` and
`14175`: they record only that a batch was approved, and each sentence
now states the ruling's date and content instead. The one verbatim quote
that carries a lesson, 「the differences are the protocol's to close」, is
kept untranslated.
- **"issue NNNN" / "PR NNNN" spellings, checked by hand.** A scan of the
five families' evaluated prose for any run of three or more digits and
for `issue` / `card` / `PR` / `batch` / `record` / `summon` plus a
number now finds only HTTP statuses, ports and example values. The two
`PR #…` citations were `#`-spelled, so the instrument saw them.
- **One test pinned a removed tracker number.**
`packages/spec/src/ui/action-params.test.ts` asserted `reason` matches
the `5613` number; it is re-pinned on the sentence that carries the
ruling (`/ruled contract-first/`). No other test reads these entries'
prose for a tracker id (a grep of test files for the 88 cited numbers
finds only comment lines and assertions on other runtime strings).
- **No open PR touches these five families.** Read twice. At
2026-09-28T19:04Z: 11 open PRs' file lists (227 rows). Again at 20:28Z,
just before opening this one: 8 open PRs (195 rows; the Version Packages
PR `17076` skipped both times), none carrying a
`migrations/entries/semantic/NN.(datasource|filter|action|data|element)-*`
file. PRs `20504`, `20503`, `20460` and `20458` add or edit entries in
other families (`turso-`, `view-`, `stack-`, `cube-`) and regenerate
`registry.ts`: ordinary concurrency, regenerate on merge. Widening the
pin reds no sibling.
- **Generated projections** (`spec-changes.json`,
`docs/protocol-upgrade-guide.md`) are regenerated, as in stages 1–3;
only the protocol-17 entries appear in them.
- **What later stages pick up** (whole tree at this head, same
instrument): **571** prose-field sites in the other families, **61**
short `#NN` sites, **7** `surface` sites, and three `cloud#` sites
(`storage-service-list-retired`, `cloud-subpath-retired`,
`cluster-driver-dangling-values-removed`; the fourth,
`data-driver-query-omit-object`, is done here).
- **Noted, not filed — the same rule outside this card's fields.**
Runtime strings outside the migration entries still carry tracker
numbers. One read in passing: `AutomationEngine`'s boot warning for a
pausing node type that never declares `resumeAuthority`
(`packages/services/service-automation/src/engine.ts`, the line that
reads `so the #3801 resume gate REFUSES every pause it creates`) is
printed to a plugin author, and `resume-authority-declaration.test.ts`
asserts the `3801` and `3823` numbers are in it. That is the
runtime-string rule this card applies, on a producer outside its
surface; it is reported to the seat, not fixed here.
## Line budget
Entry files: **367 changed lines** (+214 / −153) across 33 files,
against the stage-1 ≈400 budget. The whole diff is **871 lines** (+527 /
−344) in 39 files. Of the rest, `registry.ts` is 367, the two
projections are 68 (`spec-changes.json` 44, the upgrade guide 24), the
widened pin is 42, the re-pinned test 2 and the changeset 25.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9449512 commit 9e9bb46
39 files changed
Lines changed: 527 additions & 344 deletions
File tree
- .changeset
- docs
- packages
- cli/test
- spec
- src
- migrations
- entries/semantic
- ui
| 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 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
7 | | - | |
| 6 | + | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
76 | | - | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
77 | 80 | | |
78 | 81 | | |
79 | 82 | | |
80 | 83 | | |
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
84 | 106 | | |
85 | 107 | | |
86 | 108 | | |
87 | 109 | | |
88 | 110 | | |
89 | 111 | | |
90 | 112 | | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
91 | 116 | | |
92 | 117 | | |
93 | 118 | | |
94 | 119 | | |
95 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
96 | 132 | | |
97 | 133 | | |
98 | 134 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
16 | | - | |
| 15 | + | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
24 | | - | |
| 23 | + | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
30 | | - | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
39 | | - | |
40 | | - | |
| 40 | + | |
| 41 | + | |
41 | 42 | | |
42 | 43 | | |
43 | 44 | | |
| |||
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
50 | | - | |
| 51 | + | |
| 52 | + | |
51 | 53 | | |
52 | 54 | | |
53 | 55 | | |
| |||
56 | 58 | | |
57 | 59 | | |
58 | 60 | | |
59 | | - | |
| 61 | + | |
60 | 62 | | |
61 | 63 | | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
65 | 67 | | |
66 | | - | |
| 68 | + | |
67 | 69 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
27 | | - | |
28 | | - | |
| 29 | + | |
| 30 | + | |
29 | 31 | | |
30 | 32 | | |
31 | 33 | | |
| |||
38 | 40 | | |
39 | 41 | | |
40 | 42 | | |
41 | | - | |
| 43 | + | |
42 | 44 | | |
43 | 45 | | |
44 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
33 | | - | |
| 33 | + | |
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
23 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | | - | |
33 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
34 | 36 | | |
35 | 37 | | |
36 | 38 | | |
37 | | - | |
38 | | - | |
39 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
40 | 42 | | |
41 | 43 | | |
42 | 44 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| |||
0 commit comments