You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] cli(migrate meta): on a composeStacks preserve project the authored-source load is refused with STACK_PROVENANCE_MISSING ("not built by defineStack") although every input is wrapped #22289
Filing gate: ① a product defect, class (a), public door measured. Measured by #22238's dev (PR #22285, report on #22238, out_of_scope_findings[1]). Filed by the domain:cli seat (seat post #6024, session_01RWZbGvPFcRKvUqASZtunCU). ⛔ Not a claim. Triage sets the grade and the lane.
What was measured
os migrate meta --from 17 --json was run on two projects:
Composed project:composeStacks([…], { manifest: 'preserve' }), whose service package carries a retired spelling (a time field defaultValue: '10:00Z'). The command exits 1 with STACK_PROVENANCE_MISSING: "'com.probe.svc' (stack #0) was not built by defineStack … Wrap each input". Every input IS wrapped in defineStack.
One-package control: the same spelling exits 0 and applies time-default-utc-suffix-dropped.
So the command a retired spelling's refusal prescribes cannot run on a composed project. Its refusal tells the author to do something they already did.
Why, as the dev reads it
The authored-source load (loadConfig's authored-source path, which migrate meta uses so it can replay conversions against the raw source) hands composeStacks a stack that was not built by defineStack. composeStacks' provenance check then refuses it, with a sentence that does not fit this path. The fix's design is not decided here.
Related, not measured through the door: the shared conversion walker (packages/spec/src/conversions/walk.ts) does not descend into packages[] bodies; manifest-level entries do. Whether migrate meta would miss package-body conversions can only be measured once this refusal is fixed. That is noted in PR #22285's Acceptance notes.
Who reaches it
Any multi-package app being migrated across a major. The upgrade path's own prescription (Run: objectstack migrate meta --from N, ADR-0087 P2) fails on such an app.
Reader who acts
Triage grades and routes. The command is packages/cli's (domain:cli); the provenance check and the conversion walker are packages/spec's, so the fix may cross lanes.
Dedupe
MCP search_issues, repo-scoped, closed included: 「migrate meta composeStacks STACK_PROVENANCE_MISSING not built by defineStack authoredSource composed project」 gives 38 hits. The nearest:
Triage: first grade, bug · priority:p1 · target:v18 · domain:cli · area:devpath · pm:queue (finding removed). Direction: migrate meta runs on a composed project
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T12:59Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/cli (loadConfig's authored-source path, as migrate meta uses it) ⇒ domain:cli; rationale: that lane's. If the only route is packages/spec's composeStacks provenance check, the seat says so on this card first.
Also measure, once the refusal is fixed: whether the conversion walker (spec/src/conversions/walk.ts) misses packages[] bodies, as the card notes. If it does, that is part of this card's acceptance: a composed project's body conversions are applied and listed.
Pins: a composed project with a retired spelling in a package body migrates and lists the conversion. Control: the one-package project is unchanged.
Claim: PM loop round 14
Session: session_01RWZbGvPFcRKvUqASZtunCU
Account: os-warren (the seat's linked user as get_me answers it; the card's assignee)
Branch: claude/issue-22289-migrate-meta-composed
Worktree: objectstack-issue-22289
Domain: domain:cli
Seat: domain:cli#1
File surface, per the card body and triage 6060414246, read on origin/main4e4111ca:
packages/cli/src/utils/config.ts, the authored-source shim (the __tolerant prelude at :398, authoredSourcePlugin at :495).
When the real defineStack refuses, __tolerant hands back authored[0] unmarked.
composeStacks is not a define* helper, so it runs for real and refuses that input with STACK_PROVENANCE_MISSING (packages/spec/src/stack.zod.ts:5443–:5458).
packages/cli/src/commands/migrate/meta.ts and/or packages/cli/src/utils/authored-source-codemod.ts, only if the composed stack's package bodies need the chain run over them (the walker half triage names).
Pins in packages/cli/test/:
a composed project with a retired spelling in a package body migrates and lists the conversion;
⛔ No packages/spec (composeStacks, stack-provenance.ts, conversions/walk.ts), and no content/docs beyond the one paragraph added at review. (Stop on breach and explain in the report.)
Triage's first question, answered before dispatch. Triage asked whether the only route is packages/spec's provenance check. It is not:
defineStack's own strict: false mode marks its output inside the producer (stack.zod.ts:3904–:3914: "The output is still this producer's, so it carries the provenance mark").
So the shim's fallback can ask the real producer for a marked, unvalidated stack. That needs no packages/spec change and no hand-copied mark.
Container & model: M, mode:subagent, model: default (opus). dispatch-gates --tier over the path gives no path-derived mandate.
Clause-②: no
No accepted input of a contract, export or published shape changes. os migrate meta runs on a composed project that its own refusals prescribe it for, instead of refusing with a prescription the author has already followed. A new --json key would change this line, and the dev reports it before landing.
Responsibility: platform code: os migrate meta's authored-source shim hands a refused defineStack's input back unmarked, and composeStacks refuses it | none known: the shim predates the composeStacks provenance check (#20367 ruling B) | any multi-package app migrated across a major: the command every retired spelling's refusal prescribes exits 1 on it (hotcrm main is composed, objectstack-ai/hotcrm#2011; the v18 upgrade path)
Thread-read: 6060414246
Serial constraints cleared: read 2026-10-08T16:16Z:
Open PRs (12 besides the release PR, each file list read by name against the merge base):
os-dev-report
{
"issue": 22289,
"status": "done",
"branch": "claude/issue-22289-migrate-meta-composed",
"pr": "#22326",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "os migrate meta now runs on a composeStacks([defineStack(...), ...], { manifest: 'preserve' }) project, all in packages/cli with no packages/spec change and no hand-written provenance mark. The authored-source shim records each stack it hands through for a refused defineStack call, and a generated composeStacks wrap re-produces only those inputs via the real defineStack(input, { ...options, strict: false }), so the producer marks its own output. A truly unwrapped input is still refused, and the one-package path keeps its raw hand-back, because H3 measured that a strict:false fallback there changes the control. The chain now also runs over each packages[i].manifest body (applyMetaMigrationsToPackages, used at all three call sites), lists body edits under packages[i].manifest paths, and --write traces them through composeStacks([...]) into the input's own file.",
"readings": {
"H1": "reproduced on 4e4111c build: composed --from 17 --json exit 1, code STACK_PROVENANCE_MISSING, "composeStacks provenance check failed (1 input): 'com.probe.svc' (stack #0) was not built by defineStack ..."; one-package control exit 0, applied [time-default-utc-suffix-dropped @ objects[0].fields.starts_at.defaultValue]",
"H2": "confirmed both halves; refuses ONLY when a body's defineStack refused: the same composed project with '10:00' gives exit 0, applied [], schemaValid true",
"H3": "variant A (strict:false fallback for every refused defineStack, measured on mutated dist then md5-restored) CHANGES the control: c2 (refused stack + datasources[0].driver 'mongo') at --from 16 went from applied [mongo-to-mongodb @ datasources[0].driver, time @ objects[0]...] / write.files [{objectstack.config.ts, sites 2}] / source 'mongodb' to applied [time] / sites 1 / source keeps 'mongo'; c1 unchanged. So the one-package path keeps the raw hand-back; the route applies only via the shim-wrapped composeStacks over shim-handed-through inputs. After the change c1 and c2 at --from 16 and 17: --json summary (applied, schemaValid, whole write object) and written source bytes md5-identical to baseline.",
"H4": "confirmed: after the shim fix alone the composed project gave exit 0 but applied [] and schemaValid false (refusal at packages.0.manifest.objects.0.fields.starts_at.defaultValue; artifact top keys manifest+packages only). Fixed in packages/cli by running the chain over each body; walker untouched. After: applied [time-default-utc-suffix-dropped @ packages[0].manifest.objects[0].fields.starts_at.defaultValue], schemaValid true, --write rewrites the authored literal, verification.ok true, re-run applies nothing.",
"H5": "holds: a plain object beside a wrapped input is still refused STACK_PROVENANCE_MISSING naming only 'com.probe.app' (stack #1); refusal text unchanged",
"route_cost": "measured: a refused composed body carrying 'mongo' has it applied at load by strict:false, not listed, not written (#22256's class, now also on the composed path); stated in the changeset as a known limit",
"real_project": "examples/app-multi-package --from 17 --json: exit 0, applied [], schemaValid true"
},
"tests": "At 122ae3b: pnpm --filter @objectstack/cli exec vitest run --project unit → Test Files 267 passed (267), Tests 3936 passed (3936), lock VERDICT command-exit 0; vitest list --filesOnly puts test/migrate-meta-composed.test.ts in unit (1) and not integration (0); integration tier declared to CI (no integration file or spawn entry touched). pnpm --filter @objectstack/cli typecheck exit 0 (check:test-typecheck: OK). New pins test/migrate-meta-composed.test.ts, 8 tests, unit tier: composed load+listing; --write rewrites src/service.stack.ts only, re-run applies nothing, strict loadConfig accepts (stackProvenance true); untraceable body listed as helper, bytes unchanged; one-package controls (applied conversion/path/from/to and write.files and written bytes equal to the 4e4111c measurement); unwrapped-input boundary; merge contracts. Ablations via scripts/ablation-replace.mjs at fb129c3 (7 tests then), each anchor hit 1 → 0, restore proven blob == HEAD and git diff HEAD empty: (1) __composable returns its input → 3 failed | 4 passed (both composed pins red with the original STACK_PROVENANCE_MISSING; boundary red because the refusal then names 'com.probe.svc' too), restored blob 982786a6180d; (2) body pass returns the plain result → 3 failed | 4 passed (applied []), restored e1aa2f776836; (3) codemod composeStacks walk disabled → 1 failed | 6 passed (write.files []), restored 359b722e7b4c. Subject imported from src, so no dist leg.",
"gates": "At 122ae3b: the order's 65 commands, re-derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands over this diff (identical 65), all exit 0; dispatch-gates --ran: '65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN'. check:dual-build-cjs-loads and check:i18n-coverage exited 3 (PREREQUISITE NOT MET) on a first pass at 0413028 with only the CLI closure built, then passed after a full turbo run build ('106 published require entry point(s) across 66 package(s) load'; 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)'). check:nul-bytes OK. pnpm lint (eslint . --no-inline-config) exit 0, no findings. CI not awaited: in_progress at report time.",
"line_budget": "7 files, +679 / -13 (692 changed lines; human-merge threshold 5000: under). No skills/** or governed surface touched.",
"files_changed": [
".changeset/22289-migrate-meta-composed-project.md",
"packages/cli/src/commands/migrate/meta.ts",
"packages/cli/src/utils/authored-source-codemod.ts",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-collections.ts",
"packages/cli/test/migrate-meta-composed.test.ts",
"packages/cli/test/normalized-call-sites.test.ts"
],
"deviations": [
"file surface: packages/cli/src/utils/stack-collections.ts (one export of packageOwnedCollectionKeys plus a doc paragraph, so the codemod reads the CLI's one derivation of the package-owned key set) and packages/cli/test/normalized-call-sites.test.ts (the ledger row for the renamed call site, top-level → packages) are outside the claim's listed files; declared in the PR body",
"the H3 variant-A measurement mutated the untracked built dist/utils/config.js (copy-aside, trap restore, md5 proven equal) instead of scripts/ablation-replace.mjs, which restores from HEAD and cannot restore an untracked file",
"attribution: commits carry the model-free pair (Co-Authored-By: Claude, Claude-Session) and the PR body the AGENTS.md session-URL footer, not the harness reminder's model-named trailer and footer, per the order and AGENTS.md"
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create POST /repos/objectstack-ai/objectstack/pulls (#22326, draft, body read back byte-identical); assign POST /repos//issues/22326/assignees (os-warren, read back); comment POST /repos//issues/22289/comments (this report). Plus 6 git push of the branch (not REST). No label written (the order names none; skip-changeset does not apply).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22256 (queued) · within its scope, not a new card: on a composed project a refused input is re-produced by defineStack strict:false, whose load-time D2 pass applies a still-load-path conversion (datasources[].driver 'mongo') before the chain runs, so it is not listed and not written; measured with a composed body carrying '10:00Z' + 'mongo'. #22256's fix needs to cover this path as well as the one-package one",
"carrier: domain:devx via the seat · noted, not filed: content/docs/upgrading.mdx lines 236-240 list what --write traces through and say a value built by a function call is listed, not written; a package body under composeStacks([...]) is now written, so that sentence is incomplete",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes): a composed body authored in map form (objects: { probe_slot: {...} }) is converted and listed, but --write lists it as mismatch ('an object where the loaded value is an array') because strict:false normalized the body; measured; the one composed example uses array form",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes), not measured: planProtocolRange reads only the top-level manifest's declared range; whether a package body's own declared range needs the same rewrite was not measured"
]
}
Keep the export, and one paragraph that names both readers (the enumeration pin and the codemod's body tracing).
That landing also added a second enumeration to test/normalized-call-sites.test.ts: every top-level read of a package-owned key in packages/cli/src, classified. It now runs over this PR's new code, so re-run the full @objectstack/cli unit and integration tiers on the merged head and classify any read it names.
A merge commit only. ⛔ No rebase, no force-push.
One changeset sentence states the write condition too narrowly..changeset/22289-migrate-meta-composed-project.md says --write writes a body change "when the input is written as a defineStack({ … }) literal". The codemod's composedPackages step requires more: input i and every input before it must be a stack literal with a manifest and no packages of its own. Otherwise package i is not one input's body.
To change: state that condition. No code edit.
content/docs/upgrading.mdx (:236–:242 on 28bff18d) goes false with this PR.
It names what --write traces through: "define* calls and .create factories, const bindings, relative imports and re-exports, and Object.values()". It also says a change in "a value built by a function call" is listed, not written.
A package-body change through composeStacks([…]) is now written.
To change: add the composeStacks([…]) case and its condition (item 2) to that paragraph, and keep the rest of the list true. Prose only.
The walker half (applyMetaMigrationsToPackages, migrate/meta.ts):
the chain runs over each packages[i].manifest body as a stack;
each body's edits are listed under the body's path;
todos is the stack's own list;
absentTodos keeps an entry only when every run names it absent;
a stack with no packages list gets the plain result back.
All three call sites use it. packages/spec/src/conversions/walk.ts is untouched.
The codemod follows composeStacks([…]) into package bodies only. It refuses whenever input i's body cannot be one literal's (a non-literal input, an input with its own packages, a key composed from every input), so an uncertain site is listed, never written.
Clause-②: no stands. No flag or --json key is new; body paths are new values under the existing path. applyMetaMigrationsToPackages is exported from a command module, not a package entry.
the map-form body (--write lists it as a mismatch) and the per-package protocol range stay acceptance notes. The first is measured with no reach in this repo; the second is not measured.
os-dev-report
{
"issue": 22289,
"round": 2,
"status": "done",
"branch": "claude/issue-22289-migrate-meta-composed",
"pr": "#22326",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "Round 2 closes the seat's REWORK 6065347099. (1) Merged origin/main with merge commit 82f3988 (parents 122ae3b and 28bff18), with no rebase and no force-push. The stack-collections.ts conflict kept the single export of packageOwnedCollectionKeys, with one doc paragraph naming both readers (the enumeration pin and the codemod's body tracing). The meta.ts auto-merge is clean. The new package-owned-read enumeration in normalized-call-sites.test.ts is green over this PR's code and names no read to classify. (2) The changeset now states the exact --write condition: input i and every input before it must be a stack literal that writes its own manifest and no packages. (3) The --write paragraph of content/docs/upgrading.mdx adds the composeStacks([...]) case with that condition, and its clause now reads 'any other function call' so it stays true. No code change this round; the PR stays a draft.",
"tests": "On the merged head: vitest run test/normalized-call-sites.test.ts test/migrate-meta-composed.test.ts at 82f3988: 2 files, 19 tests passed. At db3e20d (final), under the lock (VERDICT command-exit 0): pnpm --filter @objectstack/cli exec vitest run --project unit: Test Files 268 passed (268), Tests 3951 passed (3951). --project integration: Test Files 97 passed (97), Tests 922 passed and 2 skipped (924). pnpm --filter @objectstack/cli typecheck: exit 0 (check:test-typecheck OK). Round 1's pins and ablations are unchanged; no code moved this round.",
"gates": "At db3e20d, after a full turbo run build (72 tasks): node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands over the final diff (8 paths vs merge base 28bff18) gave 95 commands: round 1's 65 plus 30 docs-family gates that content/docs adds. All 95 exit 0. dispatch-gates --ran: 'Run reconciliation — 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.' Among them: check:doc-authoring ✓; check:doc-anchors '466 internal #fragment link(s) across 417 source file(s) all resolve'; check:docs-single-h1 ✓; spec check:docs '225 generated files in sync'; check:nul-bytes OK. pnpm lint (eslint . --no-inline-config): exit 0, no findings. CI was not awaited.",
"line_budget": "8 files, +687 / -18 vs merge base 28bff18 (705 changed lines; human-merge threshold 5000: under). This round: stack-collections.ts doc paragraph (merge resolution), the changeset (1 line), and content/docs/upgrading.mdx (+10/-6, one paragraph).",
"files_changed": [
".changeset/22289-migrate-meta-composed-project.md",
"content/docs/upgrading.mdx",
"packages/cli/src/commands/migrate/meta.ts",
"packages/cli/src/utils/authored-source-codemod.ts",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-collections.ts",
"packages/cli/test/migrate-meta-composed.test.ts",
"packages/cli/test/normalized-call-sites.test.ts"
],
"deviations": [
"none from the order or the REWORK. The first post-stamped --body=22326 call was refused before any write: the github-actions docs-drift advisory 6065243708 was newer than the body's last write. I re-derived its omitted page list on this tree, receipted it in the body's Acceptance notes (only the upgrading.mdx paragraph goes false), and re-sent with --ack-through=6065243708. A stamp token rendered by post-stamped in the attribution line lets later refreshes measure from this write.",
"PR body footer: sent with no footer block, per the edit-side reading in platform-readings.md; the stored body has none, and attribution is in body prose (session id)"
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 this round — each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): issue_patch PATCH /repos//issues/22326 (the PR body; 14672 bytes read back identical; still draft, head db3e20d, assignee os-warren); comment POST /repos//issues/22289/comments (this report). Plus 2 git push (82f3988, db3e20d), which are not REST. The refused first body call dispatched nothing.",
"open_questions": [],
"out_of_scope_findings": [
"none new this round. Carried from round 1, as the seat ruled in 6065347099: the composed-path load-path cost is a pointer to #22256; the map-form body and the per-package protocol range stay PR Acceptance notes"
]
}
The changeset now states the exact --write condition for a body change: input i and every input before it must be a stack literal that writes its own manifest and no packages. This matches the codemod's composedPackages step.
It is an ancestor of origin/main; the pre-merge head db3e20d1 is not.
It entered the merge queue 2026-10-08T18:33:49Z and merged 2026-10-08T19:01:41Z on that first entry.
Fixes #22289 closed this card as completed.
Content on origin/main:
packages/cli/src/utils/config.ts: the authored-source shim records each stack it hands through for a refused defineStack call, and wraps composeStacks (:287) to re-produce only those inputs through the real defineStack(input, { strict: false }).
packages/cli/src/commands/migrate/meta.ts:150: applyMetaMigrationsToPackages runs the chain over each packages[i].manifest body, at all three call sites.
authored-source-codemod.ts traces a body change through composeStacks([…]) (:431).
content/docs/upgrading.mdx:241 describes that case.
The pin is packages/cli/test/migrate-meta-composed.test.ts, with the changeset .changeset/22289-migrate-meta-composed-project.md (@objectstack/clipatch).
os migrate meta runs on a composeStacks([defineStack(…), …], { manifest: 'preserve' }) project. It no longer refuses with a STACK_PROVENANCE_MISSING prescription the author had already followed.
It converts and lists retired spellings inside package bodies.
--write writes them into the input's own file when every input up to that one is a stack literal.
A one-package project is byte-for-byte unchanged. A truly unwrapped input is still refused. No packages/spec file moved, and no provenance mark is written outside the producer.
Activity
objectstack-fleet commentedon Oct 8, 2026
Triage: first grade,
bug·priority:p1·target:v18·domain:cli·area:devpath·pm:queue(findingremoved). Direction:migrate metaruns on a composed projectTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T12:59Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/cli(loadConfig's authored-source path, asmigrate metauses it) ⇒domain:cli; rationale: that lane's. If the only route ispackages/spec'scomposeStacksprovenance check, the seat says so on this card first.target:v18: the command every retired spelling's refusal prescribes cannot run on a composed project, and hotcrmmainis now composed (objectstack-ai/hotcrm PR feat(auth): password-policy & session settings — live, enforced (P0 security) #2011). So the flagship app's v18 upgrade path is blocked. Release first (「发版本优先的都p1」), as with cli(migrate meta):os migrate meta --from 17 --writeleavesengines.protocol: '^17', so the load refusal that names this command as its resolution still refuses the migrated manifest #22219.spec/src/conversions/walk.ts) missespackages[]bodies, as the card notes. If it does, that is part of this card's acceptance: a composed project's body conversions are applied and listed.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 14
Session:
session_01RWZbGvPFcRKvUqASZtunCUAccount:
os-warren(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-22289-migrate-meta-composedWorktree:
objectstack-issue-22289Domain:
domain:cliSeat:
domain:cli#1File surface, per the card body and triage
6060414246, read onorigin/main4e4111ca:packages/cli/src/utils/config.ts, the authored-source shim (the__tolerantprelude at:398,authoredSourcePluginat:495).defineStackrefuses,__toleranthands backauthored[0]unmarked.composeStacksis not adefine*helper, so it runs for real and refuses that input withSTACK_PROVENANCE_MISSING(packages/spec/src/stack.zod.ts:5443–:5458).packages/cli/src/commands/migrate/meta.tsand/orpackages/cli/src/utils/authored-source-codemod.ts, only if the composed stack's package bodies need the chain run over them (the walker half triage names).packages/cli/test/:.changeset/*.md:@objectstack/clipatch.os migrate metaruns on acomposeStacksproject and migrates its package bodies #22326), amended in place 2026-10-08T17:23Z:packages/cli/src/utils/stack-collections.ts(packageOwnedCollectionKeysexported for the codemod's body tracing, not a package-entry export);packages/cli/test/normalized-call-sites.test.ts(the renamedmigrate/meta.tscall site's row); andcontent/docs/upgrading.mdx(the--writeparagraph, about:236–:243, which this change made false; declared todomain:devxon [PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #6023 and [PM seat] domain:devx · seat 2 — ⏳ vacant #20163). Nothing else moves.packages/spec(composeStacks,stack-provenance.ts,conversions/walk.ts), and nocontent/docsbeyond the one paragraph added at review. (Stop on breach and explain in the report.)Triage's first question, answered before dispatch. Triage asked whether the only route is
packages/spec's provenance check. It is not:defineStack's ownstrict: falsemode marks its output inside the producer (stack.zod.ts:3904–:3914: "The output is still this producer's, so it carries the provenance mark").packages/specchange and no hand-copied mark.--writenever writes a conversion the authored-source load already applied (e.g.driver: 'mongo') — the chain sees a pre-converted stack and reportsapplied: []#22256's class. The dev measures it against the one-package control and stops if no route inpackages/clikeeps that control unchanged.Container & model:
M,mode:subagent,model: default (opus).dispatch-gates --tierover the path gives no path-derived mandate.Clause-②: no
os migrate metaruns on a composed project that its own refusals prescribe it for, instead of refusing with a prescription the author has already followed. A new--jsonkey would change this line, and the dev reports it before landing.Responsibility:
platform code: os migrate meta's authored-source shim hands a refused defineStack's input back unmarked, and composeStacks refuses it|none known: the shim predates the composeStacks provenance check (#20367 ruling B)|any multi-package app migrated across a major: the command every retired spelling's refusal prescribes exits 1 on it (hotcrm main is composed, objectstack-ai/hotcrm#2011; the v18 upgrade path)Thread-read: 6060414246
Serial constraints cleared: read 2026-10-08T16:16Z:
os serveresolves capability providers from a multi-package artifact's top-levelrequiresonly — a package'srequires: ['automation']is not loaded at boot #22288) touchesmigrate/meta.ts, at one call site (pendingDataMigrations, about:1066–:1075). This card edits that file only if the walker half needs it, in the authored-source chain. If both edit it, ordinary concurrency applies and the later lander resolves.utils/config.ts,authored-source-codemod.ts, orpackages/spec'sstack-provenance.tsorconversions/walk.ts.domain:clibesides [finding] cli(serve):os serveresolves capability providers from a multi-package artifact's top-levelrequiresonly — a package'srequires: ['automation']is not loaded at boot #22288 (PR fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321, accepted, landing).--writenever writes a conversion the authored-source load already applied (e.g.driver: 'mongo') — the chain sees a pre-converted stack and reportsapplied: []#22256 (p2, queued, unclaimed) is the load-path conversion class this card's route may touch. It stays its own card. The dev reports any interaction.domain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T16:16Zobjectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22289,
"status": "done",
"branch": "claude/issue-22289-migrate-meta-composed",
"pr": "#22326",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "os migrate meta now runs on a composeStacks([defineStack(...), ...], { manifest: 'preserve' }) project, all in packages/cli with no packages/spec change and no hand-written provenance mark. The authored-source shim records each stack it hands through for a refused defineStack call, and a generated composeStacks wrap re-produces only those inputs via the real defineStack(input, { ...options, strict: false }), so the producer marks its own output. A truly unwrapped input is still refused, and the one-package path keeps its raw hand-back, because H3 measured that a strict:false fallback there changes the control. The chain now also runs over each packages[i].manifest body (applyMetaMigrationsToPackages, used at all three call sites), lists body edits under packages[i].manifest paths, and --write traces them through composeStacks([...]) into the input's own file.",
"readings": {
"H1": "reproduced on 4e4111c build: composed --from 17 --json exit 1, code STACK_PROVENANCE_MISSING, "composeStacks provenance check failed (1 input): 'com.probe.svc' (stack #0) was not built by
defineStack..."; one-package control exit 0, applied [time-default-utc-suffix-dropped @ objects[0].fields.starts_at.defaultValue]","H2": "confirmed both halves; refuses ONLY when a body's defineStack refused: the same composed project with '10:00' gives exit 0, applied [], schemaValid true",
"H3": "variant A (strict:false fallback for every refused defineStack, measured on mutated dist then md5-restored) CHANGES the control: c2 (refused stack + datasources[0].driver 'mongo') at --from 16 went from applied [mongo-to-mongodb @ datasources[0].driver, time @ objects[0]...] / write.files [{objectstack.config.ts, sites 2}] / source 'mongodb' to applied [time] / sites 1 / source keeps 'mongo'; c1 unchanged. So the one-package path keeps the raw hand-back; the route applies only via the shim-wrapped composeStacks over shim-handed-through inputs. After the change c1 and c2 at --from 16 and 17: --json summary (applied, schemaValid, whole write object) and written source bytes md5-identical to baseline.",
"H4": "confirmed: after the shim fix alone the composed project gave exit 0 but applied [] and schemaValid false (refusal at packages.0.manifest.objects.0.fields.starts_at.defaultValue; artifact top keys manifest+packages only). Fixed in packages/cli by running the chain over each body; walker untouched. After: applied [time-default-utc-suffix-dropped @ packages[0].manifest.objects[0].fields.starts_at.defaultValue], schemaValid true, --write rewrites the authored literal, verification.ok true, re-run applies nothing.",
"H5": "holds: a plain object beside a wrapped input is still refused STACK_PROVENANCE_MISSING naming only 'com.probe.app' (stack #1); refusal text unchanged",
"route_cost": "measured: a refused composed body carrying 'mongo' has it applied at load by strict:false, not listed, not written (#22256's class, now also on the composed path); stated in the changeset as a known limit",
"real_project": "examples/app-multi-package --from 17 --json: exit 0, applied [], schemaValid true"
},
"tests": "At 122ae3b:
pnpm --filter @objectstack/cli exec vitest run --project unit→ Test Files 267 passed (267), Tests 3936 passed (3936), lock VERDICT command-exit 0;vitest list --filesOnlyputs test/migrate-meta-composed.test.ts in unit (1) and not integration (0); integration tier declared to CI (no integration file or spawn entry touched).pnpm --filter @objectstack/cli typecheckexit 0 (check:test-typecheck: OK). New pins test/migrate-meta-composed.test.ts, 8 tests, unit tier: composed load+listing; --write rewrites src/service.stack.ts only, re-run applies nothing, strict loadConfig accepts (stackProvenance true); untraceable body listed as helper, bytes unchanged; one-package controls (applied conversion/path/from/to and write.files and written bytes equal to the 4e4111c measurement); unwrapped-input boundary; merge contracts. Ablations via scripts/ablation-replace.mjs at fb129c3 (7 tests then), each anchor hit 1 → 0, restore proven blob == HEAD and git diff HEAD empty: (1) __composable returns its input → 3 failed | 4 passed (both composed pins red with the original STACK_PROVENANCE_MISSING; boundary red because the refusal then names 'com.probe.svc' too), restored blob 982786a6180d; (2) body pass returns the plain result → 3 failed | 4 passed (applied []), restored e1aa2f776836; (3) codemod composeStacks walk disabled → 1 failed | 6 passed (write.files []), restored 359b722e7b4c. Subject imported from src, so no dist leg.","gates": "At 122ae3b: the order's 65 commands, re-derived by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsover this diff (identical 65), all exit 0;dispatch-gates --ran: '65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN'. check:dual-build-cjs-loads and check:i18n-coverage exited 3 (PREREQUISITE NOT MET) on a first pass at 0413028 with only the CLI closure built, then passed after a fullturbo run build('106 published require entry point(s) across 66 package(s) load'; 'check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new)'). check:nul-bytes OK.pnpm lint(eslint . --no-inline-config) exit 0, no findings. CI not awaited: in_progress at report time.","line_budget": "7 files, +679 / -13 (692 changed lines; human-merge threshold 5000: under). No skills/** or governed surface touched.",
"files_changed": [
".changeset/22289-migrate-meta-composed-project.md",
"packages/cli/src/commands/migrate/meta.ts",
"packages/cli/src/utils/authored-source-codemod.ts",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-collections.ts",
"packages/cli/test/migrate-meta-composed.test.ts",
"packages/cli/test/normalized-call-sites.test.ts"
],
"deviations": [
"file surface: packages/cli/src/utils/stack-collections.ts (one export of packageOwnedCollectionKeys plus a doc paragraph, so the codemod reads the CLI's one derivation of the package-owned key set) and packages/cli/test/normalized-call-sites.test.ts (the ledger row for the renamed call site, top-level → packages) are outside the claim's listed files; declared in the PR body",
"the H3 variant-A measurement mutated the untracked built dist/utils/config.js (copy-aside, trap restore, md5 proven equal) instead of scripts/ablation-replace.mjs, which restores from HEAD and cannot restore an untracked file",
"attribution: commits carry the model-free pair (Co-Authored-By: Claude, Claude-Session) and the PR body the AGENTS.md session-URL footer, not the harness reminder's model-named trailer and footer, per the order and AGENTS.md"
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "3 — each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): pr_create POST /repos/objectstack-ai/objectstack/pulls (#22326, draft, body read back byte-identical); assign POST /repos//issues/22326/assignees (os-warren, read back); comment POST /repos//issues/22289/comments (this report). Plus 6
git pushof the branch (not REST). No label written (the order names none; skip-changeset does not apply).","open_questions": [],
"out_of_scope_findings": [
"carrier: #22256 (queued) · within its scope, not a new card: on a composed project a refused input is re-produced by defineStack strict:false, whose load-time D2 pass applies a still-load-path conversion (datasources[].driver 'mongo') before the chain runs, so it is not listed and not written; measured with a composed body carrying '10:00Z' + 'mongo'. #22256's fix needs to cover this path as well as the one-package one",
"carrier: domain:devx via the seat · noted, not filed: content/docs/upgrading.mdx lines 236-240 list what --write traces through and say a value built by a function call is listed, not written; a package body under composeStacks([...]) is now written, so that sentence is incomplete",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes): a composed body authored in map form (objects: { probe_slot: {...} }) is converted and listed, but --write lists it as mismatch ('an object where the loaded value is an array') because strict:false normalized the body; measured; the one composed example uses array form",
"carrier: 承接者:无 · noted, not filed (PR Acceptance notes), not measured: planProtocolRange reads only the top-level manifest's declared range; whether a package body's own declared range needs the same rewrite was not measured"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
REWORK — PR #22326 at
122ae3bd: a merge withmain, one changeset sentence and one docs paragraph, then ACCEPTdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T17:23ZThe three items:
maininto the branch. PR fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321 ([finding] cli(serve):os serveresolves capability providers from a multi-package artifact's top-levelrequiresonly — a package'srequires: ['automation']is not loaded at boot #22288) landed as28bff18d0c.git merge-treeagainstorigin/mainconflicts inpackages/cli/src/utils/stack-collections.ts: both sides exportpackageOwnedCollectionKeysand add a paragraph naming their reader.test/normalized-call-sites.test.ts: every top-level read of a package-owned key inpackages/cli/src, classified. It now runs over this PR's new code, so re-run the full@objectstack/cliunit and integration tiers on the merged head and classify any read it names..changeset/22289-migrate-meta-composed-project.mdsays--writewrites a body change "when the input is written as adefineStack({ … })literal". The codemod'scomposedPackagesstep requires more: input i and every input before it must be a stack literal with amanifestand nopackagesof its own. Otherwise package i is not one input's body.content/docs/upgrading.mdx(:236–:242on28bff18d) goes false with this PR.--writetraces through: "define*calls and.createfactories,constbindings, relative imports and re-exports, andObject.values()". It also says a change in "a value built by a function call" is listed, not written.composeStacks([…])is now written.composeStacks([…])case and its condition (item 2) to that paragraph, and keep the rest of the list true. Prose only.domain:devxon [PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #6023 and [PM seat] domain:devx · seat 2 — ⏳ vacant #20163 before the PR enqueues, as for feat(cli):os migrate meta --write— the AST codemod that rewrites authored sources for the mechanicalappliedset (v18) #9591's edit of the same page.Accepted as is, checked in the diff:
The route (
utils/config.ts) holds the cut:defineStackcall, in oneWeakMapmodule bundled once per load;composeStackswrap re-produces only those inputs, through the realdefineStack(input, { ...options, strict: false });Symbol.foris written frompackages/cli, and nopackages/specfile moves.composeStacksuntouched and is refused as before (H5, pinned).H3: the dev measured that a
strict: falsefallback on the one-package path changes the control. A refused stack withdriver: 'mongo'lost that conversion fromappliedandwrite.files. So the one-package path keeps its raw hand-back. The JSDoc says so, and the changeset names the cost on the composed path as a known limit ([finding] cli(migrate meta):--writenever writes a conversion the authored-source load already applied (e.g.driver: 'mongo') — the chain sees a pre-converted stack and reportsapplied: []#22256's class).The walker half (
applyMetaMigrationsToPackages,migrate/meta.ts):packages[i].manifestbody as a stack;todosis the stack's own list;absentTodoskeeps an entry only when every run names it absent;packageslist gets the plain result back.packages/spec/src/conversions/walk.tsis untouched.The codemod follows
composeStacks([…])into package bodies only. It refuses whenever input i's body cannot be one literal's (a non-literal input, an input with its ownpackages, a key composed from every input), so an uncertain site is listed, never written.Clause-②: nostands. No flag or--jsonkey is new; body paths are new values under the existingpath.applyMetaMigrationsToPackagesis exported from a command module, not a package entry.Pins:
test/migrate-meta-composed.test.ts(unit tier, 8 tests) covers:--writeintosrc/service.stack.tsonly, a clean re-run, and a strict reload with provenance;4e4111cameasurement;The dev's three ablations each turned it red and were restored to blobs equal to HEAD.
Owed before landing:
dispatch-gates --self-testat the reworked head, for the newmkdtempSyncsite;domain:devxdeclaration.Carried, not filed:
--writenever writes a conversion the authored-source load already applied (e.g.driver: 'mongo') — the chain sees a pre-converted stack and reportsapplied: []#22256 as a pointer: its fix must cover that path too;--writelists it as a mismatch) and the per-package protocol range stay acceptance notes. The first is measured with no reach in this repo; the second is not measured.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22289,
"round": 2,
"status": "done",
"branch": "claude/issue-22289-migrate-meta-composed",
"pr": "#22326",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "Round 2 closes the seat's REWORK 6065347099. (1) Merged origin/main with merge commit 82f3988 (parents 122ae3b and 28bff18), with no rebase and no force-push. The stack-collections.ts conflict kept the single export of packageOwnedCollectionKeys, with one doc paragraph naming both readers (the enumeration pin and the codemod's body tracing). The meta.ts auto-merge is clean. The new package-owned-read enumeration in normalized-call-sites.test.ts is green over this PR's code and names no read to classify. (2) The changeset now states the exact --write condition: input i and every input before it must be a stack literal that writes its own manifest and no packages. (3) The --write paragraph of content/docs/upgrading.mdx adds the composeStacks([...]) case with that condition, and its clause now reads 'any other function call' so it stays true. No code change this round; the PR stays a draft.",
"tests": "On the merged head:
vitest run test/normalized-call-sites.test.ts test/migrate-meta-composed.test.tsat 82f3988: 2 files, 19 tests passed. At db3e20d (final), under the lock (VERDICT command-exit 0):pnpm --filter @objectstack/cli exec vitest run --project unit: Test Files 268 passed (268), Tests 3951 passed (3951).--project integration: Test Files 97 passed (97), Tests 922 passed and 2 skipped (924).pnpm --filter @objectstack/cli typecheck: exit 0 (check:test-typecheck OK). Round 1's pins and ablations are unchanged; no code moved this round.","gates": "At db3e20d, after a full
turbo run build(72 tasks):node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsover the final diff (8 paths vs merge base 28bff18) gave 95 commands: round 1's 65 plus 30 docs-family gates that content/docs adds. All 95 exit 0.dispatch-gates --ran: 'Run reconciliation — 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN.' Among them: check:doc-authoring ✓; check:doc-anchors '466 internal #fragment link(s) across 417 source file(s) all resolve'; check:docs-single-h1 ✓; spec check:docs '225 generated files in sync'; check:nul-bytes OK.pnpm lint(eslint . --no-inline-config): exit 0, no findings. CI was not awaited.","line_budget": "8 files, +687 / -18 vs merge base 28bff18 (705 changed lines; human-merge threshold 5000: under). This round: stack-collections.ts doc paragraph (merge resolution), the changeset (1 line), and content/docs/upgrading.mdx (+10/-6, one paragraph).",
"files_changed": [
".changeset/22289-migrate-meta-composed-project.md",
"content/docs/upgrading.mdx",
"packages/cli/src/commands/migrate/meta.ts",
"packages/cli/src/utils/authored-source-codemod.ts",
"packages/cli/src/utils/config.ts",
"packages/cli/src/utils/stack-collections.ts",
"packages/cli/test/migrate-meta-composed.test.ts",
"packages/cli/test/normalized-call-sites.test.ts"
],
"deviations": [
"none from the order or the REWORK. The first
post-stamped --body=22326call was refused before any write: the github-actions docs-drift advisory 6065243708 was newer than the body's last write. I re-derived its omitted page list on this tree, receipted it in the body's Acceptance notes (only the upgrading.mdx paragraph goes false), and re-sent with --ack-through=6065243708. A stamp token rendered by post-stamped in the attribution line lets later refreshes measure from this write.","PR body footer: sent with no footer block, per the edit-side reading in platform-readings.md; the stored body has none, and attribution is in body prose (session id)"
],
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "2 this round — each one fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): issue_patch PATCH /repos//issues/22326 (the PR body; 14672 bytes read back identical; still draft, head db3e20d, assignee os-warren); comment POST /repos//issues/22289/comments (this report). Plus 2
git push(82f3988, db3e20d), which are not REST. The refused first body call dispatched nothing.","open_questions": [],
"out_of_scope_findings": [
"none new this round. Carried from round 1, as the seat ruled in 6065347099: the composed-path load-path cost is a pointer to #22256; the map-form body and the per-package protocol range stay PR Acceptance notes"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
ACCEPT — PR #22326 at
db3e20d1, pending CI and the seat's self-testdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T18:17ZRound 2 closes REWORK
6065347099. Checked in the diff againstmain, not from the report:82f39883is a merge commit with parents122ae3bdand28bff18d0c(PR fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321). There was no rebase and no force-push.stack-collections.tskeeps the oneexportofpackageOwnedCollectionKeys, with one paragraph naming both readers.cleanagainstmain.@objectstack/cliunit tier (268 files / 3951) and integration tier (97 files / 922, 2 skipped) on the final head. fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321's package-owned-read enumeration is green over this PR's code.--writecondition for a body change: input i and every input before it must be a stack literal that writes its ownmanifestand nopackages. This matches the codemod'scomposedPackagesstep.content/docs/upgrading.mdx(:236–:248): the--writeparagraph adds thecomposeStacks([…])case with that condition. The clause becomes "a value built by any other function call", so the paragraph is true again. It is prose only, and was declared todomain:devx([PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #60236065356317, [PM seat] domain:devx · seat 2 — ⏳ vacant #201636065363774).Round 1's review stands: no code moved this round. It covered:
strict: falsemode;applyMetaMigrationsToPackages;Clause-②: nostands.The gates: 95 derived over the final diff (round 1's 65 plus 30 the docs page adds), 95 run, all exit 0, and the full
pnpm lintexits 0.Owed before landing:
dispatch-gates --self-testatdb3e20d1, for the newmkdtempSyncsite (running);db3e20d1: green (35 runs), read before this post.objectstack-fleet commentedon Oct 8, 2026
Landed: PR #22326 →
59fb299c54, a single-parent queue squashdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T19:02Z59fb299c54has one parent.origin/main; the pre-merge headdb3e20d1is not.Fixes #22289closed this card as completed.origin/main:packages/cli/src/utils/config.ts: the authored-source shim records each stack it hands through for a refuseddefineStackcall, and wrapscomposeStacks(:287) to re-produce only those inputs through the realdefineStack(input, { strict: false }).packages/cli/src/commands/migrate/meta.ts:150:applyMetaMigrationsToPackagesruns the chain over eachpackages[i].manifestbody, at all three call sites.authored-source-codemod.tstraces a body change throughcomposeStacks([…])(:431).content/docs/upgrading.mdx:241describes that case.packages/cli/test/migrate-meta-composed.test.ts, with the changeset.changeset/22289-migrate-meta-composed-project.md(@objectstack/clipatch).6065347099(the merge with PR fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321, one changeset sentence, one docs paragraph). ACCEPT6066267460atdb3e20d1.Clause-②: no; no contract review was owed.dispatch-gates --self-testpassed (1976 cases) for the newmkdtempSyncsite.content/docsedit was declared todomain:devx([PM seat] domain:devx @ objectstack — 🟢 zhuangjianguo · session_012zwsLMxQBwHUYJgPyrpEvx #60236065356317, [PM seat] domain:devx · seat 2 — ⏳ vacant #201636065363774).os migrate metaruns on acomposeStacks([defineStack(…), …], { manifest: 'preserve' })project. It no longer refuses with aSTACK_PROVENANCE_MISSINGprescription the author had already followed.--writewrites them into the input's own file when every input up to that one is a stack literal.packages/specfile moved, and no provenance mark is written outside the producer.--writenever writes a conversion the authored-source load already applied (e.g.driver: 'mongo') — the chain sees a pre-converted stack and reportsapplied: []#22256's class; a pointer is on that card.fix(cli): `os migrate meta` runs on a `composeStacks` project and mig…