Repository navigation
Commit 59fb299
Fixes #22289
Clause-②: no
`os migrate meta` now runs on a project whose config exports
`composeStacks([defineStack({ … }), …], { manifest: 'preserve' })`, and
it converts the retired spellings inside the package bodies. The
one-package path is unchanged. Every change is in `packages/cli`. There
is no `packages/spec` change, and nothing in `packages/cli` writes the
provenance mark: the producer marks its own output.
## What changed
1. **A refused composed input is produced again by the producer**
(`src/utils/config.ts`, the authored-source shim). The shim records each
stack it hands through for a refused `defineStack` call, with the
options it was called with. A generated `composeStacks` wrap maps only
those recorded inputs through the real `defineStack(input, { ...options,
strict: false })`, then calls the real `composeStacks`. Every other
input reaches the real `composeStacks` untouched, including a plain
object the author never wrapped, so that one is still refused. The
one-package path keeps handing the authored argument through raw (H3
below says why). The record is one bundled module that every shim module
imports, so a stack handed through by one `@objectstack/spec` entrypoint
is recognised by another, and a record lasts for one load only.
2. **The chain runs over each package body**
(`src/commands/migrate/meta.ts`, `applyMetaMigrationsToPackages`, used
at all three chain call sites: the main run, the `--write` re-run and
the empty-range probe). An option-B artifact keeps each definition only
under `packages[i].manifest`, and the conversions walk a stack's own
collections. So the chain now also runs over each body as a stack, and
lists each body edit under the body's own path
(`packages[0].manifest.objects[0].fields.starts_at.defaultValue`).
`applied` keeps hop order, `todos` is the stack's own list, and
`absentTodos` keeps an entry only when every run names it. A stack with
no `packages` list gets the plain chain result back.
3. **`--write` traces a body edit to the input that authored it**
(`src/utils/authored-source-codemod.ts`). The walk now follows
`composeStacks([…])`, but only into `packages[i].manifest.KEY`, and only
when every input up to i is a stack literal with a `manifest` and no
`packages` of its own. Then entry i is input i's body. `KEY` is read the
way composition assembles a body: from the input when it is a
package-owned collection the input writes, otherwise from the input's
`manifest`. Any other composed key is refused as `helper` and listed.
4. `src/utils/stack-collections.ts`: `packageOwnedCollectionKeys()` is
exported, unchanged, so the codemod reads the CLI's one derivation of
that key set instead of writing a second one. `main` exported it too,
for the enumeration pin (PR #22321). After the merge it is one export,
and one doc paragraph names both readers.
5. `test/normalized-call-sites.test.ts`: the ledger row for the renamed
call site moves from `top-level` to `packages`, with the measurement
named.
6. **The `--write` condition, stated where authors read it.** The
changeset and the `--write` paragraph of `content/docs/upgrading.mdx`
both say exactly when a package-body change is written: input i and
every input before it must be a stack literal that writes its own
`manifest` and no `packages`. Otherwise package i is not one input's
body, and the change is listed. The docs paragraph keeps its list true:
the clause that a value built by a function call is listed now reads
"any other function call". The docs edit is prose only, and the seat
declared it to `domain:devx`.
## Round 2: the seat's REWORK `6065347099`
1. **Merged `main`** with a merge commit, `82f398833` (parents
`122ae3bd9` and `28bff18d0c`). There was no rebase and no force-push.
`src/utils/stack-collections.ts` conflicted, because both sides exported
`packageOwnedCollectionKeys` and each added a paragraph naming its own
reader. The resolution keeps the export and writes one paragraph naming
both. The `meta.ts` auto-merge (main folds `pendingDataMigrations` onto
the union stack) is clean. The second enumeration `main` added to
`test/normalized-call-sites.test.ts` (every top-level read of a
package-owned key in `packages/cli/src`) is green over this PR's code.
Its "no read is unclassified" test names nothing new, so there was
nothing to classify.
2. **The changeset** states the condition above, and the code did not
change.
3. **`content/docs/upgrading.mdx`**: the `--write` paragraph is edited
as described in item 6.
## PM readings, measured
Measured with the built CLI against temp projects that link this
worktree's built `@objectstack/spec`. "Before" is `origin/main`
`4e4111ca0`.
- **H1, reproduced.** The composed project (every input wrapped in
`defineStack`, a `time` default `'10:00Z'` in the service package) gave
`--from 17 --json` exit 1 with `"code":"STACK_PROVENANCE_MISSING"` and
the message `composeStacks provenance check failed (1 input):
'com.probe.svc' (stack #0) was not built by \`defineStack\`. … Wrap each
input: …`. The one-package control gave exit 0 with `applied` =
`[time-default-utc-suffix-dropped @
objects[0].fields.starts_at.defaultValue]`.
- **H2, confirmed.** The shim hands back `authored[0]` unmarked,
`composeStacks` is not wrapped, and its step 0 refuses the unmarked
input. The composed project is refused **only when a body's
`defineStack` refused**: the same project with `'10:00'` gave exit 0,
`applied: []`, `schemaValid: true`.
- **H3, measured. The plain route changes the control, so it was not
taken.** Variant A, a `strict: false` fallback for every refused
`defineStack` (applied to the built dist for the measurement only, then
restored by md5), was run on two one-package controls at `--from 16` and
`--from 17`:
- c1, the refused stack: unchanged.
- c2, the refused stack plus `datasources[0].driver: 'mongo'`, at
`--from 16`: before, `applied` = `[datasource-driver-mongo-to-mongodb @
datasources[0].driver, time-default-utc-suffix-dropped @ objects[0]…]`
and `write.files` = `[{ objectstack.config.ts, sites: 2 }]`, with the
source written to `'mongodb'`. Under variant A, `applied` =
`[time-default-utc-suffix-dropped]` only, `sites: 1`, and the source
keeps `'mongo'`.
So the one-package path keeps the raw hand-back, and the route applies
only where `composeStacks` needs a marked input (change 1). Measured
after the change: c1 and c2 at both majors give a `--json` summary
(`applied`, `schemaValid`, the whole `write` object) and written source
bytes **identical by md5** to the baseline.
- **H4, confirmed, and fixed in `packages/cli`.** After change 1 alone,
the composed project gave exit 0, but `applied: []` and `schemaValid:
false`, with the schema refusing
`packages.0.manifest.objects.0.fields.starts_at.defaultValue`. The
artifact's top level held only `manifest` and `packages`. After change
2: `applied` = `[time-default-utc-suffix-dropped @
packages[0].manifest.objects[0].fields.starts_at.defaultValue]` and
`schemaValid: true`. With `--write`, the authored literal is rewritten,
`verification.ok` is true, and a second run applies nothing. The walker
is unchanged.
- **H5, holds.** A truly unwrapped input (a plain object beside a
wrapped one) is still refused with `STACK_PROVENANCE_MISSING`, and the
refusal names only `'com.probe.app' (stack #1)`. The refusal text is
unchanged.
- **Real project.** `examples/app-multi-package` (composed, preserve,
valid) under `--from 17 --json` gives exit 0, `applied: []`,
`schemaValid: true`.
## Pins — `test/migrate-meta-composed.test.ts`, unit tier
The test runs in-process over `MigrateMeta.run` against temp projects
that link the real spec. It spawns no process and boots no kernel, so it
runs in `Test Core` with the rest of the CLI unit tier. 8 tests:
- **The fix.** A two-file composed project (the service stack in
`src/service.stack.ts`) loads, and `applied` is exactly the body site
above, with `schemaValid: true`.
- **`--write`.** `write.files` = `[{ src/service.stack.ts, sites: 1 }]`.
The tree equals its old bytes with only that literal edited. The re-run
gives `applied: []`, and a strict `loadConfig` (no shim) accepts the
sources and reports `stackProvenance: true`.
- **`--write` on a body it cannot trace.** For an input built by a local
function, the change is listed as `helper` and every byte is left alone.
- **Control.** For both one-package stacks above at `--from 16`,
`applied` (conversion, path, from, to) and `write.files` equal the
values measured on `4e4111ca0`, and so do the written bytes.
- **Boundary.** The unwrapped input is refused with
`STACK_PROVENANCE_MISSING`, naming `'com.probe.app'` and not
`'com.probe.svc'`.
- **Merge contracts.** With no `packages`, the result equals
`applyMetaMigrations`. With bodies: copy-on-write (the input is
unmutated, and an untouched body keeps its identity), `applied` equals
the hops' concatenation, and each `absentTodos` is a subset of its
`todos` by identity.
## Ablations
Each ablation used `scripts/ablation-replace.mjs`: the anchor hit once,
the mutation was proven on disk, and the restore was proven (blob equals
HEAD, `git diff HEAD` empty). The suite imports `src` directly, so no
dist is involved. The runs were at `fb129c3e1`, when the file had 7
tests.
1. **The raw hand-back restored on the composed path** (`__composable`
returns its input). 3 red, 4 green. The two composed pins went red with
the original false `STACK_PROVENANCE_MISSING`. The boundary pin also
went red, because the refusal then names `'com.probe.svc'` too (2
inputs). The controls and the merge pins stayed green. Restored: blob
`982786a6180d`.
2. **The body pass removed** (`applyMetaMigrationsToPackages` returns
the plain result). 3 red (both composed pins and the merge pin,
`applied` `[]`), 4 green. Restored: blob `e1aa2f776836`.
3. **The codemod's `composeStacks` walk disabled.** 1 red (the `--write`
pin: `write.files` `[]`), 6 green. Restored: blob `359b722e7b4c`.
## Local verification at `122ae3bd9` (round 1)
- **CLI unit tier** (`pnpm --filter @objectstack/cli exec vitest run
--project unit`): 267 files and 3936 tests pass. `vitest list --project
unit` lists `test/migrate-meta-composed.test.ts`, and `--project
integration` does not. The integration tier is CI's: this diff touches
no integration file and no spawn entry.
- **Typecheck** (`pnpm --filter @objectstack/cli typecheck`, which is
`tsc --noEmit` plus `check:test-typecheck`): exit 0.
- **Gates.** `dispatch-gates.mjs --commands`, re-derived over this diff,
gives the same 65 commands the order listed. All 65 exit 0, and
`dispatch-gates --ran` reports 65 derived, 65 run, 0 NOT-MEASURED, 0
UNRUN. On an earlier pass with only the CLI closure built,
`check:dual-build-cjs-loads` and `check:i18n-coverage` exited 3
(prerequisite not met). After a full `turbo run build` both pass: `106
published require entry point(s) across 66 package(s) load` and `13
config(s) … none new`.
- **Lint** (`pnpm lint`, which is `eslint . --no-inline-config`): exit
0, no findings.
## Local verification at `db3e20d1c` (round 2, merged head)
- **CLI unit tier**: 268 files and 3951 tests pass.
- **CLI integration tier** (`--project integration`): 97 files pass,
with 922 tests passed and 2 skipped.
- **Typecheck** (`pnpm --filter @objectstack/cli typecheck`): exit 0.
- **Gates.** `dispatch-gates.mjs --commands`, re-derived over the final
diff, gives 95 commands: the 65 from round 1 plus 30 docs-family gates
that `content/docs` adds. All 95 exit 0, after a full `turbo run build`.
`dispatch-gates --ran` reports 95 derived, 95 run, 0 NOT-MEASURED, 0
UNRUN. Among them: `check:doc-authoring` ✓, `check:doc-anchors` ("466
internal #fragment link(s) … all resolve"), `check:docs-single-h1` ✓,
spec `check:docs` ("225 generated files in sync") and `check:nul-bytes`
OK.
- **Lint** (`pnpm lint`): exit 0, no findings.
## Acceptance notes
- **The route's one cost is #22256's class, now also reached on a
composed project.** For a refused composed input, `strict: false` still
runs the load-time D2 pass. Measured: a composed body carrying both
`'10:00Z'` and `driver: 'mongo'` lists and writes the time default,
while `'mongo'` is applied at load, is not listed, and is not written.
That is the same as for any input the current schema accepts. #22256
remains open, and its fix will need to cover this path as well as the
one-package one. The changeset states the limit.
- **A body authored in map form** (`objects: { probe_slot: { … } }`) is
converted and listed. `--write` lists it as `mismatch` ("an object where
the loaded value is an array") and writes nothing, because `strict:
false` has already normalized the body to an array. Measured. The one
composed example in this repo uses array form. Carrier: none.
- **`absentTodos`.** On today's registry the intersection agrees with
the top-level verdict: every relevance question is `stack-declares`,
which already reads `packages[]`, and the two entries that judge a
conversion both ask about `agents`. No fixture separates the two, so no
pin claims to. The intersection is kept because a body's own
applications are visible only to that body's run.
- **Manifest-level conversions.** One exists
(`manifest-permissions-string-list-removed`), and it already walks
`packages[].manifest`. A body run finds no `manifest` key, so nothing is
applied twice.
- **Not measured.** `planProtocolRange` still reads only the top-level
`manifest`'s declared range. Whether a package body's own declared range
needs the same rewrite was not measured here.
- **File surface.** `src/utils/stack-collections.ts`,
`test/normalized-call-sites.test.ts` and the one
`content/docs/upgrading.mdx` paragraph were added to the claim's file
surface at review. Nothing else moves.
- **Docs.** The `--write` paragraph of `content/docs/upgrading.mdx`
would have gone false with this change, so it is edited here (round 2,
prose only). The seat declared the edit to `domain:devx`.
- **The docs-drift advisory** (comment `6065243708`) lists 18
hand-written pages; its list is omitted above 15 rows. I re-derived it
on this tree with `node scripts/docs-audit/affected-docs.mjs --json
28bff18`, which gave the same 18 pages plus 9 release-owned ones. I
read each page for a statement about `--write` tracing,
`STACK_PROVENANCE_MISSING` or the authored-source load. Only the
`upgrading.mdx` paragraph above goes false, and it is edited here.
`deployment/cli.mdx` line 1433 ("writes the ones it can trace to one
literal") and line 1896 (`os validate`'s provenance refusal) stay true.
Written by session `session_01RWZbGvPFcRKvUqASZtunCU`, rounds 1 and 2;
body refreshed 2026-10-08T18:14Z.
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2f70c22 commit 59fb299
8 files changed
Lines changed: 687 additions & 18 deletions
File tree
- .changeset
- content/docs
- packages/cli
- src
- commands/migrate
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
237 | 237 | | |
238 | 238 | | |
239 | 239 | | |
240 | | - | |
241 | | - | |
242 | | - | |
243 | | - | |
244 | | - | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
245 | 249 | | |
246 | 250 | | |
247 | 251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
103 | 105 | | |
104 | 106 | | |
105 | 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 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
106 | 211 | | |
107 | 212 | | |
108 | 213 | | |
| |||
222 | 327 | | |
223 | 328 | | |
224 | 329 | | |
225 | | - | |
| 330 | + | |
226 | 331 | | |
227 | 332 | | |
228 | 333 | | |
| |||
1060 | 1165 | | |
1061 | 1166 | | |
1062 | 1167 | | |
1063 | | - | |
| 1168 | + | |
1064 | 1169 | | |
1065 | 1170 | | |
1066 | 1171 | | |
| |||
1237 | 1342 | | |
1238 | 1343 | | |
1239 | 1344 | | |
1240 | | - | |
| 1345 | + | |
1241 | 1346 | | |
1242 | 1347 | | |
1243 | 1348 | | |
| |||
0 commit comments