Skip to content

Commit 9e9bb46

Browse files
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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the `datasource-*`, `filter-*`, `action-*`, `data-*` and `element-*` migration entries states each lesson in words instead of citing tracker numbers
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries of the `datasource-*` family (the publish-time credential,
10+
placeholder and URL refusals, and the bound-secret pairs a mongo datasource cannot use),
11+
the `filter-*` family (the retired `$regex`, the `$between` endpoint refusals and the
12+
comparand shapes the save door now refuses), the `action-*` family (the retired
13+
descriptor key, the `resumeAuthority` default flip, the action-session rename, the bulk
14+
dispatch contract and the engine facade's query envelope), the `data-*` family (the
15+
retired driver and engine contract members, the retired field-changed event and two
16+
duration keys renamed with their unit) and the `element-*` family (the filter rule array
17+
at the page binding and the element and block doors) are printed by `os migrate meta` as
18+
the header, `why:` and `verify:` lines of a manual change. Their text sent the reader to
19+
issue-tracker, decision-batch and ruling-record numbers — some of which no longer
20+
resolve, and some in other repositories — for what a ruling, measurement or fix had
21+
decided; it now says what was decided, in the sentence being read. ADR ids are kept.
22+
23+
Text only: no entry id, `surface`, `from` / `to`, conversion or matching logic changes,
24+
and the chain rewrites exactly what it rewrote before. The generated migration registry,
25+
`spec-changes.json` and the protocol upgrade guide carry the same text.

‎docs/protocol-upgrade-guide.md‎

Lines changed: 12 additions & 12 deletions
Large diffs are not rendered by default.

‎packages/cli/test/migrate-meta-engine-guidance.test.ts‎

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
/**
44
* `os migrate meta` — the guidance it prints for the ADR-0087 semantic entries
55
* of the COVERED families (`engine-*`, `ui-*`, `plugin-*`, `driver-*`,
6-
* `kernel-*`, `system-*`) states each lesson in words and carries no tracker
7-
* number.
6+
* `kernel-*`, `system-*`, `datasource-*`, `filter-*`, `action-*`, `data-*`,
7+
* `element-*`) states each lesson in words and carries no tracker number.
88
*
99
* ## What this pins
1010
*
@@ -73,26 +73,62 @@ const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');
7373
const TRACKER_ID = /#\d{4,5}\b/;
7474

7575
/** The families this pin holds, selected by entry-id prefix. */
76-
const COVERED_PREFIXES = ['engine-', 'ui-', 'plugin-', 'driver-', 'kernel-', 'system-'];
76+
const COVERED_PREFIXES = [
77+
'engine-', 'ui-', 'plugin-', 'driver-', 'kernel-', 'system-',
78+
'datasource-', 'filter-', 'action-', 'data-', 'element-',
79+
];
7780

7881
/**
7982
* The entries rewritten when each family was brought to this line — the
8083
* anti-vacuity floor. A covered entry that carried no tracker id to begin with
8184
* is held by its prefix and needs no row here.
8285
*/
8386
const REWRITTEN = [
87+
'action-bulk-dispatch-contract-undeclared',
88+
'action-descriptor-is-async-retired',
89+
'action-descriptor-resume-authority-default-flip',
90+
'action-engine-facade-find-query-envelope',
91+
'action-session-roles-to-positions',
92+
'data-driver-find-stream-retired',
93+
'data-driver-query-omit-object',
94+
'data-engine-batch-retired',
95+
'data-field-changed-event-retired',
96+
'data-file-value-duration-unit-in-key',
97+
'data-nosql-query-options-timeout-unit-in-key',
98+
'datasource-config-inline-credential-refused',
99+
'datasource-config-mongo-options-credential-refused',
100+
'datasource-config-placeholder-refused',
101+
'datasource-config-postgres-url-unparseable-refused',
102+
'datasource-config-url-query-credential-refused',
103+
'datasource-config-url-userinfo-refused',
104+
'datasource-credentialsref-mongo-composed-no-username-refused',
105+
'datasource-credentialsref-mongo-url-no-user-refused',
84106
'driver-aggregate-undeclared-key-aliases-removed',
85107
'driver-capabilities-inert-bits-removed',
86108
'driver-options-timeout-to-timeout-ms',
87109
'driver-sql-distinct-bare-filter-typed',
88110
'driver-sql-unresolvable-where-column-refused',
89111
'driver-sql-upsert-cross-row-identity-merge-refused',
90112
'driver-turso-config-local-path-wasm-retired',
113+
'element-data-source-and-object-block-filter-rule-array',
114+
'element-number-filter-rule-array',
115+
'element-record-picker-filter-rule-array',
91116
'engine-dotted-filter-refused',
92117
'engine-dotted-projection-refused',
93118
'engine-find-formula-filter-refused',
94119
'engine-find-formula-order-by-refused',
95120
'engine-update-upsert-retired',
121+
'filter-between-blank-endpoint-refused',
122+
'filter-between-field-reference-endpoint-refused',
123+
'filter-comparand-types-and-widget-nested-slots-refused-at-save',
124+
'filter-equality-array-comparand-refused',
125+
'filter-equality-array-comparand-refused-at-save',
126+
'filter-icontains-comparand-refused-at-parse',
127+
'filter-ne-array-comparand-refused',
128+
'filter-preset-ordering-comparand-refused',
129+
'filter-query-face-comparands-refused-at-save',
130+
'filter-regex-options-retired',
131+
'filter-text-operator-declared-type-refused',
96132
'kernel-compatibility-matrix-estimated-migration-time-unit-in-key',
97133
'kernel-context-preview-mode-retired',
98134
'kernel-event-bus-retention-unit-in-key',

‎packages/spec/spec-changes.json‎

Lines changed: 22 additions & 22 deletions
Large diffs are not rendered by default.

‎packages/spec/src/migrations/entries/semantic/17.action-descriptor-is-async-retired.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ export const entry: SemanticMigration = {
1212
reason:
1313
'ADR-0049 enforce-or-remove. `isAsync` declared "this action suspends the flow '
1414
+ 'awaiting an external reply" and NOTHING read it: a fresh three-repo measurement '
15-
+ '(#6748, re-run at pickup) found zero property reads across objectstack, objectui '
16-
+ 'and cloud — every hit was the declaration itself, a generated baseline, one of '
15+
+ '(taken when the key was filed for retirement, and re-run at pickup) found zero '
16+
+ 'property reads across objectstack, objectui and cloud — every hit was the declaration itself, a generated baseline, one of '
1717
+ 'five shipped descriptors WRITING it, a test fixture pinning the shape, or prose. '
1818
+ 'So declaring it never made a node suspend and omitting it never stopped one, '
1919
+ 'which is the silently-inert declaration ADR-0049 exists to end. It was always a '
2020
+ 'second, weaker spelling of the capability `supportsPause` states, and the two '
2121
+ 'diverged in exactly the way a duplicated declaration does: `screen` declared '
2222
+ 'both, `map` and `wait` declared `isAsync` alongside `supportsPause`, and nothing '
23-
+ 'anywhere reconciled them. The sibling took the ENFORCE leg of the same ruling in '
24-
+ '#6667 — `AutomationEngine` now refuses a suspension whose type does not declare '
23+
+ 'anywhere reconciled them. The sibling took the ENFORCE leg of the same ruling — '
24+
+ '`AutomationEngine` now refuses a suspension whose type does not declare '
2525
+ '`supportsPause: true` — so the capability this key gestured at is now a real, '
2626
+ 'enforced fact under one name. This one had no consumer to grow into and takes '
2727
+ 'the remove leg. '

‎packages/spec/src/migrations/entries/semantic/17.action-descriptor-resume-authority-default-flip.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ export const entry: SemanticMigration = {
2222
'A SECURE-DEFAULT FLIP with no metadata shape to rewrite — the same category as '
2323
+ "protocol 12's `rest-requireauth-default-flip`, and it is registered here for the "
2424
+ 'same reason: whether a given pause is genuinely open to the generic route is a '
25-
+ 'trust judgment no transform can make. The #3801 resume gate keys on the SUSPENDED '
25+
+ 'trust judgment no transform can make. The generic resume route\'s authorization gate '
26+
+ 'keys on the SUSPENDED '
2627
+ "NODE, and `ActionDescriptor.resumeAuthority` used to default to `'any'`, so a "
2728
+ 'pausing node type shipped raw-resumable unless its author remembered the field. '
2829
+ "It now resolves to `'service'` when absent: an unclaimed pause is refused on the "
2930
+ 'generic route with `PERMISSION_DENIED` / 403 until its descriptor states who may '
30-
+ 'continue it. #3823 is the incident that decided the direction — ADR-0044 pointed '
31+
+ 'continue it. The revise-window incident decided the direction — ADR-0044 pointed '
3132
+ "an approval's revise edge at a generic `wait`, `wait` is legitimately `'any'`, and "
3233
+ 'the pause standing in a service-owned position inherited a fail-open value nobody '
3334
+ 'chose; the demonstrated cost was an unaudited resubmit plus a destroyed remote '
@@ -36,8 +37,8 @@ export const entry: SemanticMigration = {
3637
+ "guessing `'service'` returns a refusal naming the missing field. ⚠️ The surface "
3738
+ 'is a DESCRIPTOR FIELD set in plugin CODE, never stack metadata, so there is no '
3839
+ 'source for a D2 conversion to rewrite and deliberately no schema tombstone — the '
39-
+ 'disposition `data-driver-find-stream-retired` (#4484), `storage-service-list-retired` '
40-
+ '(#5540) and `actor-user-roles-to-positions` (#6011) already carry. It differs from '
40+
+ 'disposition `data-driver-find-stream-retired`, `storage-service-list-retired` '
41+
+ 'and `actor-user-roles-to-positions` already carry. It differs from '
4142
+ 'those in one way a reader should not have to infer: nothing is REMOVED, so tsc '
4243
+ 'reports nothing at all — the field was already optional after step one and an '
4344
+ 'omission still compiles. The enforced channels are all run-time: a registration '
@@ -47,7 +48,8 @@ export const entry: SemanticMigration = {
4748
+ 'channel that arrives BEFORE a user hits a run that will not continue. In-tree the '
4849
+ 'flip moves nothing: all six shipped pausing types (screen, wait, subflow, map, '
4950
+ 'approval, approval_revise) declare their authority explicitly. ADR-0044 amendment '
50-
+ '(2026-07-28) and its 2026-08-08 landing section, ADR-0019 #3801 addendum, #5561.',
51+
+ '(2026-07-28) and its 2026-08-08 landing section, and the 2026-07-28 resume-seam '
52+
+ 'addendum to ADR-0019 (approval as a flow node).',
5153
acceptanceCriteria:
5254
'Every action descriptor your plugin registers for a node type that can suspend '
5355
+ 'declares `resumeAuthority`. Booting the stack logs no `declares supportsPause but '
@@ -56,12 +58,12 @@ export const entry: SemanticMigration = {
5658
+ "through the generic route succeeds for the ones you declared `'any'`, and answers "
5759
+ "403 (`PERMISSION_DENIED`) for the ones you declared `'service'`, which continue "
5860
+ 'through your own service API instead. ⚠️ `supportsPause` is no longer the '
59-
+ 'declaration nothing enforced (#5703, closed by #6667): an executor whose '
61+
+ 'declaration nothing enforced: an executor whose '
6062
+ '`execute()` returns `suspend: true` while leaving `supportsPause` false is still '
6163
+ 'warned about by neither warning channel, but '
6264
+ '`AutomationEngine.refuseUndeclaredSuspension` now refuses that suspension at the '
6365
+ 'one seam every suspension passes through — a guard-class failure no `fault` edge '
6466
+ 'routes — so it needs no hand-check. The residue that does: an executor registering '
6567
+ 'NO descriptor declares nothing for either warning or the refusal to read, so its '
66-
+ 'pauses are still created and refused only later, on the resume route (#5561).',
68+
+ 'pauses are still created and refused only later, on the resume route.',
6769
};

‎packages/spec/src/migrations/entries/semantic/17.action-session-roles-to-positions.ts‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,25 @@ export const entry: SemanticMigration = {
99
reason:
1010
'The MIRROR-IMAGE sibling of `actor-user-roles-to-positions`, and the reason both are in this '
1111
+ 'step: the hook `ctx.session` carried `roles` declared-and-never-produced (removed '
12-
+ 'outright, #5050), while the ACTION body\'s `ctx.session` carries it '
12+
+ 'outright), while the ACTION body\'s `ctx.session` carries it '
1313
+ 'produced-and-really-populated. `buildActionSession()` '
1414
+ '(`packages/runtime/src/action-execution.ts`) copies `ExecutionContext.positions` '
1515
+ 'into a key spelled `roles` — the ADR-0090 D3 vocabulary handed to the author under '
1616
+ 'the one spelling that ADR bans — so a body author met two different answers to one '
1717
+ 'key name on one platform: rejected in a hook, live and full of values in an action. '
18-
+ '#5613 ruled contract-first (maintainer, 2026-08-06: "C skeleton + A semantics"): '
19-
+ 'phase 1 (#5697) declared the previously undeclared shape as `ActionSessionSchema`, '
20-
+ 'and phase 2 renames the key. `positions` is now the canonical key on that schema '
21-
+ 'and `roles` a deprecated alias of it (#5779); the producer emits both for one '
22-
+ 'deprecation window (#5613 runtime half), after which `roles` is removed on the path '
23-
+ 'the v16 session-alias removal already walked (#3280 deprecated → #3290 removed). '
18+
+ 'The maintainer ruled contract-first on 2026-08-06 ("C skeleton + A semantics": declare '
19+
+ 'the shape as it stands first, then rename on the typed face): phase 1 declared the '
20+
+ 'previously undeclared shape as `ActionSessionSchema`, and phase 2 renames the key. '
21+
+ '`positions` is now the canonical key on that schema and `roles` a deprecated alias of '
22+
+ 'it; the producer emits both for one deprecation window (the runtime half of the same '
23+
+ 'ruling), after which `roles` is removed on the path the v16 session-alias removal '
24+
+ 'already walked (the hook session\'s `tenantId` alias: deprecated first, removed in the '
25+
+ 'next major). '
2426
+ 'Why this is a D3 semantic TODO and not a D2 conversion, on two independent grounds: '
2527
+ 'FIRST, there is no source to convert — an action `ctx.session` is constructed per '
2628
+ 'dispatch and never persisted, so no `sys_metadata` row, example or template can '
27-
+ 'carry the key — the `openApi31` (#4579) / `activationEvents` (#4657) / '
28-
+ '`hook-context-session-roles-retired` (#5050) shape. SECOND, the only place the key '
29+
+ 'carry the key — the `openApi31` / `activationEvents` / '
30+
+ '`hook-context-session-roles-retired` shape. SECOND, the only place the key '
2931
+ 'is ever SPELLED is inside an action body: author-written JS/TS, or a sandboxed '
3032
+ 'script whose `ScriptContext.session` is still `unknown`. A declarative transform '
3133
+ 'cannot safely rewrite an identifier inside free-form code — exactly the reason the '
@@ -38,7 +40,7 @@ export const entry: SemanticMigration = {
3840
+ 'authorable-surface ratchet adjudicates) belongs to the release that closes the '
3941
+ 'window. Until then this entry IS the channel: `spec-changes.json` and the generated '
4042
+ 'upgrade guide are how a reader learns the rename before the removal reaches them. '
41-
+ 'ADR-0090 D3, ADR-0087, #5613 / #5779.',
43+
+ 'ADR-0090 D3, ADR-0087.',
4244
acceptanceCriteria:
4345
'No action body reads `ctx.session.roles`; every such read is `ctx.session.positions` '
4446
+ 'and observes the same array (the rename is a rename — the VALUE is '

‎packages/spec/src/migrations/entries/semantic/17.data-driver-find-stream-retired.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const entry: SemanticMigration = {
3030
+ 'no schema tombstone either: nothing ever ran a driver object through '
3131
+ '`DriverInterfaceSchema.parse()`, so a prescription there would have no one to '
3232
+ 'reach. The enforced channel is tsc, and it points at callers. ADR-0049 / '
33-
+ 'ADR-0078, #4484.',
33+
+ 'ADR-0078.',
3434
acceptanceCriteria:
3535
'No code calls `driver.findStream(...)`; large reads page through `find()` with '
3636
+ '`limit`/`offset` (which guarantees a total order across the whole walk) or go '

‎packages/spec/src/migrations/entries/semantic/17.data-driver-query-omit-object.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ export const entry: SemanticMigration = {
1919
+ 'layer spends a named 400 (`QUERY_OBJECT_MISMATCH`) refusing the inconsistency. The '
2020
+ 'driver side paid in blanket casts: a direct caller holding only a `where` could not '
2121
+ 'name the type, wrote `as any`, and switched off checking for `where` / `orderBy` / '
22-
+ '`fields` along with it — 20 such sites measured in cloud#1053, and cloud#1030\'s '
23-
+ '`$like` reached runtime through exactly that hole. This is a TS contract surface with '
22+
+ '`fields` along with it — 20 such sites were measured in the downstream cloud codebase, '
23+
+ 'and a `$like` the type layer would have caught reached runtime there through exactly '
24+
+ 'that hole. This is a TS contract surface with '
2425
+ 'no authored source for the chain to rewrite, which is why it is a semantic entry and '
2526
+ 'not a D2 conversion; for a typed caller the compiler names every site (TS2353 '
2627
+ '`\'object\' does not exist in type \'DriverQuery\'`), and for an untyped JS caller '
@@ -29,14 +30,15 @@ export const entry: SemanticMigration = {
2930
+ 'unchanged (excess properties are only rejected on fresh literals), and an '
3031
+ 'implementation still declaring `query: QueryAST` keeps compiling under parameter '
3132
+ 'bivariance. What an implementation may no longer do is READ `query.object` — callers '
32-
+ 'are now entitled to omit it. Registered by the #6350 stock reconciliation. #5181 was '
33-
+ 'the audit\'s CONTROL sample, drawn to show that not every flagged candidate is an '
33+
+ 'are now entitled to omit it. Registered by the stock reconciliation that compared the '
34+
+ 'breaking changesets already on the v17 release train against this ledger. This change '
35+
+ 'was that audit\'s CONTROL sample, drawn to show that not every flagged candidate is an '
3436
+ 'omission, and it was one: the seven `IDataDriver` hits in the ledger are all prose '
3537
+ 'inside other entries, and the only subject-level hit on this interface is '
3638
+ '`data-driver-find-stream-retired` — a DIFFERENT member. Two later, smaller driver '
37-
+ 'call-parameter changes (#6321, #6083) both registered, and both cite #5181 as '
38-
+ 'background; the larger sibling they derive from never got its own entry. ADR-0087, '
39-
+ '#5181 (backfilled #6350).',
39+
+ 'call-parameter changes both registered, and both cite this narrowing as '
40+
+ 'background; the larger sibling they derive from never got its own entry. ADR-0087 '
41+
+ '(backfilled by that reconciliation).',
4042
acceptanceCriteria:
4143
'No `IDataDriver` call site passes an inline literal carrying `object:` — `driver.find('
4244
+ '"account", { object: "account", where: … })` becomes `driver.find("account", { where: '

‎packages/spec/src/migrations/entries/semantic/17.data-engine-batch-retired.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const entry: SemanticMigration = {
3636
+ '`DataEngineBatchRequestSchema`, so a `retiredKey()` prescription would have no one '
3737
+ 'to reach; its three `authorable-surface.json` baseline lines and its '
3838
+ '`json-schema.manifest.json` entry are dropped in the same change, deliberately. '
39-
+ 'The enforced channel is tsc. ADR-0049 / ADR-0078, #4618.',
39+
+ 'The enforced channel is tsc. ADR-0049 / ADR-0078.',
4040
acceptanceCriteria:
4141
'No code calls `engine.batch(...)` and no type references `DataEngineBatchRequest`; '
4242
+ 'in-process multi-write atomicity goes through `IObjectQLEngine.transaction(cb)`, a '

0 commit comments

Comments
 (0)