Repository navigation
Commit 2aa25ef
fix(spec): os migrate meta guidance for the engine-* migration entries states each lesson in words, not tracker numbers (stage 1) (#20285)
Part of #20233
Clause-②: no
**Stage 1 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`). Those three fields
are text an author is shown, and AGENTS.md's runtime-string rule applies
to them: 「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 the parent
card (comment `5749154545`, 「同意」) sets the shape: the lesson in words,
and no number, dead or alive.
This stage rewrites the busiest entry family, the five `engine-*`
entries: **67 sites → 0**. Each site now says what the cited ruling,
measurement or fix decided. ADR ids stay, because an ADR lives in this
repository. `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. One new pin holds the printed output tracker-free.
## Census — tracker ids in the three author-shown fields
**Instrument.** 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` and
`acceptanceCriteria` (string literals joined with `+`), then counts `#`
followed by 4 or 5 digits at a word boundary. **Tree:**
`objectstack-ai/objectstack` at `443b2f4fdc` (this branch's base).
**Controls.**
- **Lit:** `17.aggregation-node-distinct-retired.ts` reads 7 sites
(replacement 1, reason 6), the ids a reader sees in its text.
- **Dark (comment lines):** 733 `//` lines in entry files carry a
tracker id, and none is counted. For example,
`18.client-envelope-convergence-analytics-automation.ts` has 5 such
comment lines and counts 0. Those comment sites belong to the sibling
card, and ⛔ this PR does not touch them.
- **Dark (other fields):** `surface` is not in the three-field count.
`17.authoring-schemas-strict-unknown-keys.ts` carries one id in
`surface`, and it is absent from the table. The `surface` field is
counted separately under Acceptance notes.
- **Dark (other tables):** the 413 `retired-keys/` and `retired-defs/`
files have none of the three fields. They count 0, while carrying 687
comment-line hits.
**Totals at `443b2f4fdc`:** **1,016 sites** in **266** semantic entries,
**460** distinct ids, split major 17: 417 and major 18: 599. By field:
replacement 61, reason 901, acceptanceCriteria 54. The line-level upper
bound over all entry files is 1,524 lines in 679 files.
**Why this is not the relayed 2,060.** That figure scanned
`packages/spec/src/migrations/**`, where the generated `registry.ts`
repeats every entry's prose. At the base, `registry.ts` alone holds
2,113 tracker-shaped occurrences. The entry files, which are the source
the generator copies, hold 1,016 sites in these three fields.
**Dead ids.** All 22 distinct ids in the chosen family answer 200, so
none is dead. The other 438 ids are not re-probed at this stage.
**After this PR:** 1,016 → **949** (the `engine-` family 67 → 0, every
other family unchanged).
Families are grouped by the entry-id prefix: the first word of the id,
which is the name family. All three-field sites live in the one
`semantic/` directory, so the directory does not separate them.
| # | family (entry-id prefix) | entries | sites | major 17 / 18 |
replacement / reason / acceptanceCriteria | distinct ids |
|---:|---|---:|---:|---|---|---:|
| 1 | `engine-` **(this PR)** | 5 | 67 | 50 / 17 | 11 / 55 / 1 | 22 |
| 2 | `ui-` | 17 | 65 | 29 / 36 | 6 / 58 / 1 | 42 |
| 3 | `plugin-` | 11 | 46 | 12 / 34 | 1 / 39 / 6 | 31 |
| 4 | `driver-` | 7 | 44 | 19 / 25 | 0 / 44 / 0 | 25 |
| 5 | `kernel-` | 9 | 44 | 0 / 44 | 1 / 41 / 2 | 11 |
| 6 | `system-` | 10 | 44 | 0 / 44 | 0 / 42 / 2 | 6 |
| 7 | `datasource-` | 9 | 43 | 10 / 33 | 3 / 38 / 2 | 14 |
| 8 | `filter-` | 10 | 30 | 8 / 22 | 1 / 28 / 1 | 23 |
| 9 | `field-` | 8 | 28 | 9 / 19 | 5 / 20 / 3 | 20 |
| 10 | `action-` | 5 | 26 | 24 / 2 | 0 / 23 / 3 | 19 |
| 11 | `element-` | 4 | 25 | 0 / 25 | 2 / 19 / 4 | 17 |
| 12 | `data-` | 6 | 24 | 15 / 9 | 0 / 24 / 0 | 15 |
| 13 | `export-` | 3 | 21 | 15 / 6 | 0 / 21 / 0 | 15 |
| 14 | `hook-` | 3 | 17 | 14 / 3 | 0 / 17 / 0 | 12 |
| 15 | `rest-` | 4 | 17 | 2 / 15 | 0 / 17 / 0 | 14 |
| 16 | `api-` | 4 | 16 | 9 / 7 | 0 / 14 / 2 | 11 |
| 17 | `metadata-` | 7 | 16 | 0 / 16 | 1 / 15 / 0 | 11 |
| 18 | `view-` | 7 | 15 | 7 / 8 | 0 / 13 / 2 | 10 |
| 19 | `analytics-` | 5 | 14 | 1 / 13 | 2 / 12 / 0 | 10 |
| 20 | `audit-` | 2 | 14 | 14 / 0 | 2 / 12 / 0 | 8 |
| 21 | `sharing-` | 2 | 14 | 14 / 0 | 1 / 13 / 0 | 11 |
| 22 | `actor-` | 1 | 13 | 13 / 0 | 0 / 13 / 0 | 9 |
| 23 | `http-` | 2 | 13 | 13 / 0 | 0 / 13 / 0 | 11 |
| 24 | `object-` | 4 | 13 | 0 / 13 | 1 / 11 / 1 | 11 |
| 25 | `dataset-` | 3 | 12 | 0 / 12 | 2 / 8 / 2 | 9 |
| 26 | `hot-` | 2 | 12 | 0 / 12 | 0 / 10 / 2 | 5 |
| 27 | `external-` | 1 | 11 | 11 / 0 | 0 / 11 / 0 | 8 |
| 28 | `package-` | 5 | 11 | 3 / 8 | 0 / 10 / 1 | 8 |
| 29 | `query-` | 6 | 11 | 11 / 0 | 4 / 7 / 0 | 6 |
| 30 | `delete-` | 1 | 10 | 10 / 0 | 0 / 10 / 0 | 4 |
| 31 | `stack-` | 2 | 10 | 0 / 10 | 3 / 4 / 3 | 9 |
| 32 | `etl-` | 1 | 9 | 9 / 0 | 1 / 8 / 0 | 7 |
| 33 | `flow-` | 3 | 9 | 1 / 8 | 0 / 9 / 0 | 8 |
| 34 | `storage-` | 1 | 9 | 9 / 0 | 1 / 5 / 3 | 7 |
| 35 | `apimethod-` | 1 | 8 | 8 / 0 | 0 / 7 / 1 | 5 |
| 36 | `dashboard-` | 5 | 8 | 1 / 7 | 0 / 8 / 0 | 8 |
| 37 | `notification-` | 1 | 8 | 8 / 0 | 0 / 7 / 1 | 7 |
| 38 | `record-` | 2 | 8 | 6 / 2 | 0 / 8 / 0 | 4 |
| 39 | `runtime-` | 1 | 8 | 8 / 0 | 0 / 8 / 0 | 6 |
| 40 | `scim-` | 1 | 8 | 0 / 8 | 2 / 5 / 1 | 4 |
| 41 | `aggregation-` | 1 | 7 | 7 / 0 | 1 / 6 / 0 | 6 |
| 42 | `automation-` | 2 | 7 | 0 / 7 | 0 / 5 / 2 | 4 |
| 43 | `cache-` | 1 | 7 | 0 / 7 | 1 / 5 / 1 | 4 |
| 44 | `tenant-` | 2 | 7 | 0 / 7 | 0 / 7 / 0 | 3 |
| 45 | `authoring-` | 1 | 6 | 6 / 0 | 0 / 6 / 0 | 5 |
| 46 | `cli-` | 1 | 6 | 0 / 6 | 0 / 5 / 1 | 5 |
| 47 | `client-` | 4 | 6 | 4 / 2 | 0 / 6 / 0 | 4 |
| 48 | `evaluated-` | 1 | 6 | 0 / 6 | 2 / 3 / 1 | 3 |
| 49 | `identity-` | 1 | 6 | 0 / 6 | 0 / 6 / 0 | 6 |
| 50 | `spec-` | 1 | 6 | 6 / 0 | 0 / 6 / 0 | 6 |
| 51 | `advanced-` | 1 | 5 | 0 / 5 | 1 / 4 / 0 | 5 |
| 52 | `cloud-` | 1 | 5 | 0 / 5 | 2 / 3 / 0 | 5 |
| 53 | `import-` | 1 | 5 | 5 / 0 | 0 / 4 / 1 | 5 |
| 54 | `startup-` | 1 | 5 | 0 / 5 | 0 / 5 / 0 | 5 |
| 55 | `sys-` | 1 | 5 | 0 / 5 | 0 / 3 / 2 | 5 |
| 56 | `tool-` | 1 | 5 | 5 / 0 | 0 / 4 / 1 | 3 |
| 57 | `address-` | 1 | 4 | 0 / 4 | 0 / 4 / 0 | 4 |
| 58 | `declarative-` | 1 | 4 | 4 / 0 | 0 / 4 / 0 | 3 |
| 59 | `packages-` | 1 | 4 | 0 / 4 | 0 / 4 / 0 | 3 |
| 60 | `platform-` | 1 | 4 | 0 / 4 | 0 / 4 / 0 | 3 |
| 61 | `session-` | 2 | 4 | 0 / 4 | 0 / 4 / 0 | 3 |
| 62 | `sort-` | 1 | 4 | 4 / 0 | 0 / 4 / 0 | 2 |
| 63 | `strategy-` | 1 | 4 | 0 / 4 | 1 / 2 / 1 | 3 |
| 64 | `admin-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 3 |
| 65 | `ai-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 66 | `assembled-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 3 |
| 67 | `auth-` | 1 | 3 | 3 / 0 | 0 / 3 / 0 | 2 |
| 68 | `change-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 69 | `device-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 70 | `epoch-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 71 | `incident-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 72 | `logging-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 73 | `memory-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 74 | `rls-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 75 | `send-` | 1 | 3 | 0 / 3 | 1 / 2 / 0 | 2 |
| 76 | `standard-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 77 | `training-` | 2 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 78 | `turso-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 3 |
| 79 | `websocket-` | 1 | 3 | 0 / 3 | 0 / 3 / 0 | 2 |
| 80 | `autonumber-` | 1 | 2 | 0 / 2 | 0 / 2 / 0 | 2 |
| 81 | `batch-` | 2 | 2 | 2 / 0 | 0 / 2 / 0 | 2 |
| 82 | `cbp-` | 1 | 2 | 0 / 2 | 1 / 1 / 0 | 1 |
| 83 | `schedule-` | 1 | 2 | 0 / 2 | 1 / 1 / 0 | 2 |
| 84 | `structured-` | 1 | 2 | 0 / 2 | 0 / 2 / 0 | 2 |
| 85 | `time-` | 1 | 2 | 0 / 2 | 0 / 2 / 0 | 2 |
| 86 | `workflow-` | 1 | 2 | 2 / 0 | 0 / 2 / 0 | 2 |
| 87 | `approval-` | 1 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| 88 | `audience-` | 1 | 1 | 0 / 1 | 0 / 1 / 0 | 1 |
| 89 | `branded-` | 1 | 1 | 0 / 1 | 0 / 1 / 0 | 1 |
| 90 | `cluster-` | 1 | 1 | 0 / 1 | 0 / 1 / 0 | 1 |
| 91 | `connector-` | 2 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| 92 | `enhanced-` | 1 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| 93 | `esignature-` | 1 | 1 | 0 / 1 | 0 / 1 / 0 | 1 |
| 94 | `event-` | 1 | 1 | 0 / 1 | 0 / 1 / 0 | 1 |
| 95 | `job-` | 1 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| 96 | `position-` | 1 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| 97 | `ups-` | 1 | 1 | 1 / 0 | 0 / 1 / 0 | 1 |
| | zero-site families: `cel-` (2), `cube-` (1), `execution-` (1),
`inline-` (1), `list-` (1), `manifest-` (2), `observability-` (1),
`page-` (1), `saved-` (1), `screen-` (1), `translation-` (1), `wait-`
(1) | 14 | 0 | | | 0 |
| | **total** | **266** | **1016** | 417 / 599 | 61 / 901 / 54 | 460 |
## Stage 1 = the `engine-` family
It is the busiest family: 67 sites in 5 entries, 22 distinct ids. It
also fits a reviewable stage, at **112 changed lines in entry files**
(+64 / −48) against the ≈400 budget, with generated `registry.ts`
excluded. The five entries are the data engine's query and write option
refusals:
| entry | sites (replacement / reason / acceptanceCriteria) |
|---|---|
| `17.engine-dotted-projection-refused` | 17 (3 / 14 / 0) |
| `17.engine-find-formula-filter-refused` | 17 (4 / 13 / 0) |
| `17.engine-find-formula-order-by-refused` | 14 (2 / 12 / 0) |
| `17.engine-update-upsert-retired` | 2 (0 / 1 / 1) |
| `18.engine-dotted-filter-refused` | 17 (2 / 15 / 0) |
## Every citation read, and what the text now says
I read each cited issue or PR myself: the body, and the comments where a
ruling or a measurement lives. The ids are in code spans so that this
body does not post 22 cross-references. There are no unresolved sites:
every citation's decision was established from what it says.
| cited | what it decided (read) | how the text now carries it |
|---|---|---|
| `#3821` | The SQL driver's `find` must not turn an unknown column into
"no rows": it retries without the projection or ORDER BY. That is the
unknown-column recovery ladder. The GitHub title names the sharing-rule
recipient picker, which is where the defect surfaced. The driver's
`sql-driver-unknown-column-recovery.test.ts` header ties the two
together. | "the driver's unknown-column recovery ladder — there so an
unknown column never reads as "no rows"", and later "the recovery
ladder" / "the unknown-column backstop" |
| `#4226` | At the REST list route, a `sort` naming a nonexistent field
answers `400 INVALID_SORT` instead of being dropped. | "The SORT axis is
closed at the REST ingress for an unknown field, …" |
| `#4256` | A dotted `sort` path is refused at the ingress, rather than
silently unsorted. | "… a dotted path …" / "SORT refuses it" / "the SORT
axis prescribes when it refuses the dotted spelling" |
| `#5918` | Maintainer ruling, 2026-08-07: the analytics ad-hoc path
refuses a relation-traversing dotted measure loudly, with a 400 naming
the caller's spelling. It had silently aggregated a base-table column. |
"the line analytics already takes for a relation-traversing dotted
measure, refused with a 400 naming the caller's spelling rather than
computed against the wrong column" |
| `#6674` | A `formula` field declared in `searchableFields` is refused
loudly, never admitted as search coverage. | "SEARCH refuses it by name"
/ "the SEARCH axis prescribes when it refuses a formula search field" |
| `#6924` | The dotted-sort hint prescribes a STORED field, not a
"formula or rollup" field. A formula has no column. | "the ingress sort
hint's stored-field prescription" / "the sort axis when it refuses a
dotted sort" |
| `#6994` | A non-dotted `orderBy` on a `formula` field is refused at
the ingress (`400 INVALID_SORT`). | "… and a `formula` field alike" /
"at the REST ingress" |
| `#7095` | Maintainer ruling, 2026-08-10: the engine refuses a formula
ORDER BY it cannot apply, at the public boundary. The internal tolerance
survives only on a measured caller, and none was found. Its PR
registered the change as a semantic entry (disposition `registered`)
although no stored row is rewritten. | "Ruled by the maintainer on
2026-08-10: …" / "The sweep of every in-tree `orderBy` …" / "the
formula-sort refusal (`engine-find-formula-order-by-refused`)" /
"Registered on the ruling inherited from the SORT axis — its engine
refusal was registered in this ledger although no stored row needs
rewriting …" |
| `#7532` | The REST ingress refuses a dotted projection with `400
INVALID_FIELD` instead of widening the response to every field.
Resolving the path was explicitly not authorised. | "The REST ingress
closed the PROJECTION axis' dotted leg first, refusing a dotted entry
instead of widening the response to every field" |
| `#7534` | An unknown field inside `where` / `$filter` / a filter AST
is refused with `400 INVALID_FIELD`. | "cleared the unknown-name check
(which refuses a key naming no field of the object)" |
| `#7537` | A nested `expand` projection that omitted `id` was a silent
no-op. The engine now keeps the join key in the sub-read and strips it
from the output. | "the same silent no-op a nested projection omitting
the related `id` produced before the engine began keeping that join key
itself" |
| `#7588` | The PR that implemented `#7532`. | folded into the `#7532`
sentence |
| `#7589` | Maintainer ruling, 2026-08-12 (Option B): the engine refuses
a dotted projection at its own head-only filter. The
unknown-plain-column tolerance is kept, and a driver-side carve-out
waits for measured need. The flow `get_record` chain was measured end to
end. | "Ruled by the maintainer on 2026-08-12: …" / "that caller set was
measured, not assumed:" / "PROJECTION refuses it at both doors" |
| `#7601` | A measurement found that no populate step exists. The spec
and docs stop prescribing a dotted `fields` path. | "a measurement found
that NO populate step exists" |
| `#7617` | The PR that made the spec and docs stop prescribing the
dotted path. | "once the spec and docs stopped prescribing a dotted
`fields` path" |
| `#7867` | A by-id update whose id names no row throws
`RECORD_NOT_FOUND`: the engine's not-found gate. | "the engine's
not-found gate — a by-id update whose id names no row throws
RECORD_NOT_FOUND" |
| `#8057` | `options.upsert` is removed (ADR-0049). One prescription
constant is quoted by the engine gate and by both schemas. | "the engine
gate and both schemas quote one removal prescription" |
| `#8296` | The FILTER axis gets its formula verdict at both doors: `400
INVALID_FIELD`, judged by the spec's own virtual-field predicate. |
"Both doors now refuse it with `400 INVALID_FIELD`" / "The formula
verdict deliberately skipped dotted keys" / "the one-source move the
formula verdict made with `isVirtualSearchField`" |
| `#8369` | The PR that implemented `#8296`. | folded into the `#8296`
sentence |
| `#8370` | Triage, 2026-08-13: register the FILTER refusal as a
semantic entry, inheriting the SORT-axis answer. | "re-affirmed for this
axis at triage on 2026-08-13" |
| `#8371` | Maintainer ruling (delegated), 2026-08-15: refuse a dotted
filter key whose head is a relation, a formula or a plain scalar, at
both doors. A structured/JSON head is left unjudged. The ruling was
preceded by a three-driver measurement. | "Measured across all THREE
drivers before ruling" / "per the maintainer's ruling" |
| `#8790` | Maintainer ruling, 2026-08-15: an unresolvable WHERE column
is refused by both the list and the count half, with `INVALID_FILTER` /
400. It has its own entry. | "a divergence with its own entry,
`driver-sql-unresolvable-where-column-refused`, that now refuses it on
both" |
The only call-shaped token the rewrite touches is `find()`: one is
removed and one is added, so textual call-spelling ratchets that read
`registry.ts` count the same.
## Pin — `packages/cli/test/migrate-meta-engine-guidance.test.ts`
The test spawns the real CLI (`os migrate meta --from 16 --to 18`) over
a stack authoring the shapes the family is about: a lookup and a virtual
`formula` field. It locates each `engine-*` block **verbatim** in the
printed output, then asserts that the printed block carries no `#` plus
4 or 5 digits. Three things keep it from passing vacuously:
- The family is derived from the registry by id prefix, with the five
rewritten ids as its floor.
- Presence in stdout is asserted before cleanliness.
- The detector is exercised on both sides first: it is lit on 4 and 5
digits, and dark on 3 digits, 6 digits and `ADR-0112`.
The file follows the existing `migrate-meta-default-range.test.ts`
pattern: queue tier by name (not `.e2e`, which is nightly-only),
`integration` project by behaviour.
## Ablation — the pin can fail
The ablation ran from committed state, HEAD `1fa8251067`, with
`scripts/ablation-replace.mjs` in wrap mode and
`scripts/ablation-dist-preflight.mjs` gating each leg.
- **Attempts 1 and 2 are VOID and are not readings.**
- In attempt 1, the spec build never got the verify lock (queue-timeout,
exit 99). The preflight reported the marker ABSENT from `dist/`, so the
green pin run after it measured the pre-mutation build.
- Attempt 2 mutated the entry file, and the build ran. But the published
bundle is built from the generated `registry.ts`, not from the entry
files, so the preflight again reported ABSENT and the pin was not run.
- **Attempt 3, the reading:**
- **Mutation.** The mutation went into `registry.ts`, the same line the
generator emits for the entry: anchor `quote one removal prescription`
becomes `quote the #8057 removal prescription`. Anchor went 1 → 0 and
marker 0 → 1, and the blob changed from `b956ae88` to `cef8c6e2`.
- **Mutate leg.** The spec build ran (command-exit 0). The preflight
found the marker in 4 built files. The pin went **red**, 1 failed | 2
passed: `engine-update-upsert-retired: the printed guidance cites a
tracker id: expected '#8057' to be undefined`.
- **Restore.** The tool proved the restore: blob `b956ae88` == HEAD, and
`git diff HEAD` is empty.
- **Restore leg.** The spec build was rerun (command-exit 0). `--absent`
found the marker in none of 222 built files, and the tree was clean. The
pin went **green**, 3 passed.
## Verification (all at HEAD `1fa8251067`)
- **Pin:** `pnpm --filter @objectstack/cli exec vitest run --project
integration --maxWorkers=2 test/migrate-meta-engine-guidance.test.ts`,
with `Tests 3 passed (3)`. That is the restore-leg run, after the spec
rebuild.
- **Spec migrations:** `pnpm --filter @objectstack/spec exec vitest run
--project local --maxWorkers=2 src/migrations`, with `Test Files 3
passed (3)` and `Tests 149 passed (149)`.
- **Typecheck:**
- `pnpm --filter @objectstack/spec typecheck` exits 0.
- `pnpm --filter @objectstack/cli typecheck` exits 0. The test layer
holds its recorded 28 errors in 3 files, unchanged. `tsc --listFiles -p
packages/cli/tsconfig.test.json` puts the new file in the program with 0
errors in it.
- **CLI tiers:** `test/vitest-tiers-partition.test.ts` passes, 22 tests.
The rest of the CLI `unit` / `integration` suites are declared to CI:
the diff touches no CLI source, and adds only this one integration-tier
file.
- **Build:** the build closure is `pnpm exec turbo run build
--filter="@objectstack/cli^..." --concurrency=2`, with 55/55 tasks.
- **Gate families:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 88 families from this
change set. `--ran` over the recorded exit codes reads **88 derived, 87
run, 1 NOT MEASURED, 0 unrun**.
- The 87 exit 0. They include `check:migration-registry`,
`check:spec-changes`, `check:upgrade-guide`, `check:generated` (15
artifacts up to date), `check:doc-authoring`, `check:issue-citations`,
`check:nul-bytes`, `check:adr-0087-registration` and
`check:changeset-no-major`.
- **NOT MEASURED:** `check:dual-build-cjs-loads`, `PREREQUISITE NOT MET`
(exit 3). It reads every package's `dist/`, and this worktree built only
the CLI's dependency closure. CI builds the whole repo.
- **Lint (a proven narrowing, not the repo-wide run, which is CI's):**
`eslint --no-inline-config --format json` over the 7 changed `.ts` files
reports 7 files, 0 errors and 0 warnings.
- The population is read from `eslint.config.mjs`, which lints
`**/*.{ts,tsx,mts,cts,js,…}` minus `NEVER_LINTED`, and all 7 files are
in it.
- Invariance: the config never enables type-aware linting (its own
header says so: no `parserOptions.project`, no typed rules). A text-only
edit therefore cannot move the verdict on any file it does not touch.
- **Mergeability:** the branch is 7 commits behind `origin/main`
`89f87f2344`. `git merge-tree --write-tree HEAD origin/main` exits 0,
and `registry.ts` auto-merges. Main's one new semantic entry is not an
`engine-*` entry.
## Acceptance notes
- **`surface` is printed too, and it is outside this card's three
fields.** The block header `⚠ [protocol N] surface → …` shows the
`surface` text to the author. By the same instrument, 9 tracker-id sites
sit in the `surface` of 6 entries:
`authoring-schemas-strict-unknown-keys` (1),
`dataset-measure-aggregate-field-type-refused` (2),
`evaluated-expression-slots-source-required` (2),
`flow-edge-condition-evaluated-slot-source-required` (2),
`plugin-manifest-contributes-routes-retired` (1) and
`ui-react-list-view-binding-aliases-retired` (1). None is in the
`engine-` family, and the pin already holds the whole printed block,
`surface` included. This is noted for the card's later stages, not
filed.
- The pin selects its family by id prefix. A later stage can widen the
same file to its own family, rather than add a second spawn.
- **Generated projections regenerated beyond the claim's listed
surface:** `packages/spec/spec-changes.json` and
`docs/protocol-upgrade-guide.md` carry the same entry text, and their
`--check` gates are red until regenerated. Both are generator output
only.
## Line budget
Entry files: **112 changed lines** (+64 / −48) across 5 files, against
≈400. The whole diff is 471 lines (+346 / −125) in 10 files. Of the
rest, `registry.ts` is 114, the two projections are 56, the pin is 169
and the changeset is 20.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2dccb7d commit 2aa25ef
10 files changed
Lines changed: 346 additions & 125 deletions
File tree
- .changeset
- docs
- packages
- cli/test
- spec
- src/migrations
- entries/semantic
Lines changed: 20 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
Large diffs are not rendered by default.
| 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 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
0 commit comments