Context
Graduated from Discussion #12049 ("Worktree config-substrate workflow: operator-edit propagation vs fresh-materialized configs") with cross-family [GRADUATION_APPROVED by @neo-gpt @ Discussion #12049 body updated 2026-05-26T20:53:08Z]. C-lite convergent shape — see Discussion body for §5.1 Double Diamond matrix, §5.1.1 Reflective Pause documentation, and OQ1-5 resolutions.
The Problem
ai/scripts/migrations/bootstrapWorktree.mjs copies the canonical checkout's per-server ai/mcp/server/*/config.mjs files verbatim into the worktree via fs.copyFile. Two gaps:
- No materialize-on-copy: if canonical's per-server
config.mjs was generated before materializeServerConfigTemplate() landed (or --migrate-config was never re-run), the un-materialized state propagates into every worktree. The transitive ai/services.mjs import then loads ai/config.template.mjs instead of the operator overlay, double-registering Neo.ai.Config under unitTestMode (empirical anchor: PR #12044 cycle-2 → cycle-2.1).
- Tier-1
ai/config.mjs not in BOOTSTRAP_CONFIGS: once materialized, per-server config.mjs imports point at '../../../config.mjs' (operator overlay); if Tier-1 isn't also bootstrapped, the chain breaks in the worktree.
The Architectural Reality
The Fix
C-lite shape per Discussion #12049 convergence:
- Add
'ai/config.mjs' to BOOTSTRAP_CONFIGS so Tier-1 is copied alongside the four per-server configs.
- After
fs.copyFile, apply materializeServerConfigTemplate() (imported from initServerConfigs.mjs) to each copied per-server config.mjs so the AiConfig import is rewritten from ../../../config.template.mjs → ../../../config.mjs when needed. The function is idempotent — already-materialized files are unchanged.
- Operator-edit propagation semantic stays intact: worktree gets canonical's
tenantRepos[] and other operator edits exactly as before; only the import-path layer gets normalized.
- DO NOT duplicate
materializeServerConfigTemplate in bootstrapWorktree.mjs — import from initServerConfigs.mjs (single source of truth for the rewrite logic).
Decision Record impact
aligned-with ADR 0014 — narrow bootstrap-script behavior change within existing topology.
Contract Ledger
Backfilled post-filing per @neo-gpt's intake-blocker on this ticket — ticket-create-workflow.md §5 + learn/agentos/contract-ledger.md require a formal Contract Ledger matrix for any ticket modifying an agent-consumed bootstrap surface. Matrix structure follows @neo-gpt's suggested rows verbatim:
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
BOOTSTRAP_CONFIGS (the array at bootstrapWorktree.mjs:113-118) |
Discussion #12049 C-lite scope; this ticket #12051 |
Includes top-level ai/config.mjs plus the four existing per-server ai/mcp/server/<name>/config.mjs paths |
Existing destination configs stay skipped/preserved per current idempotence semantics (bootstrapWorktree.mjs:226-253 fs.copyFile short-circuit on already-existing target) |
bootstrapWorktree.mjs JSDoc updated to describe Tier-1 inclusion |
Unit test proves copied Tier-1 ai/config.mjs exists in the fake worktree post-bootstrap |
Per-server config copy path in bootstrapWorktree() (copy loop at lines 226-253) |
initServerConfigs.mjs#materializeServerConfigTemplate() (single source of truth — imported, not duplicated) |
After fs.copyFile, materialize stale ../../../config.template.mjs AiConfig imports to ../../../config.mjs |
Already-materialized configs remain unchanged (function is idempotent); missing source configs retain current skip behavior |
bootstrapWorktree.mjs JSDoc updated to describe materialize-on-copy semantic |
Unit test proves no copied per-server config retains ../../../config.template.mjs AiConfig import |
| Operator-edit propagation |
Discussion #12049 OQ1 resolution (C-lite preserves current operator-edit propagation semantic) |
Preserve canonical operator edits (tenantRepos[], custom config keys, etc.) exactly — the only transformation is the AiConfig import-path normalization on per-server configs |
Existing destination configs remain untouched (idempotence preserved) |
JSDoc only — no operator-facing behavior change worth surfacing in learn/agentos/cloud-deployment/ |
Unit test proves non-import content (custom keys, tenantRepos[] arrays, comments) survives the copy + materialization step verbatim |
| Non-overlap with #11976 (direct test-file imports) + #12047 (canonical-checkout materialization-migration) |
Discussion #12049 OQ5 + this ticket's ## Out of Scope section |
This ticket/PR does NOT change direct test-file import behavior (#11976's surface) and does NOT add the canonical-checkout migration tooling (#12047's surface) |
#11976 + #12047 surfaces remain owned by their respective tickets — orthogonal layers |
PR body cross-links to #11976 + #12047; explicit AC enforces the non-overlap |
PR body audits + tests scope-limited to bootstrapWorktree.mjs + a focused test fixture; neither modifies test-import-path conventions nor adds migration commands |
Acceptance Criteria
Out of Scope
- Option A (delegate to initServerConfigs / fresh-materialized worktree semantic): explicitly deferred to a future Discussion if anyone wants to pursue the workflow-semantic change. Per Discussion #12049 classification challenge, that would be high-blast and require the §5.2 step-back sweep — orthogonal to this bounded fix.
- The materialization-migration concern for existing canonical checkouts (covered by #12047).
- Direct test-file imports of
config.mjs (covered by #11976); see Discussion #12049 OQ5 for the explicit non-overlap constraint.
Avoided Traps
- Pure Option B (materialize-on-copy without Tier-1): surfaced by @neo-gpt's peer-role as under-specified — materialized per-server imports require Tier-1 to exist in the worktree. C-lite includes both.
- Duplicating the materialize function: would create substrate drift if the rewrite logic changes later. Import from the canonical owner (
initServerConfigs.mjs) instead.
Related
- Graduates from Discussion #12049 (full design history + matrix + OQ resolutions).
- Empirical anchor: PR #12044 cycle-2 → cycle-2.1 (the materialization gap surfaced).
- Sibling substrate concerns (orthogonal layers): #12047 (materialization-migration for existing checkouts), #11976 (direct test-file imports).
- Closed predecessor (wrong-shape ticket): #12048.
- Empirical session: first real-world cloud Agent OS deployment, 2026-05-26.
Context
Graduated from Discussion #12049 ("Worktree config-substrate workflow: operator-edit propagation vs fresh-materialized configs") with cross-family
[GRADUATION_APPROVED by @neo-gpt @ Discussion #12049 body updated 2026-05-26T20:53:08Z]. C-lite convergent shape — see Discussion body for §5.1 Double Diamond matrix, §5.1.1 Reflective Pause documentation, and OQ1-5 resolutions.The Problem
ai/scripts/migrations/bootstrapWorktree.mjscopies the canonical checkout's per-serverai/mcp/server/*/config.mjsfiles verbatim into the worktree viafs.copyFile. Two gaps:config.mjswas generated beforematerializeServerConfigTemplate()landed (or--migrate-configwas never re-run), the un-materialized state propagates into every worktree. The transitiveai/services.mjsimport then loadsai/config.template.mjsinstead of the operator overlay, double-registeringNeo.ai.ConfigunderunitTestMode(empirical anchor: PR #12044 cycle-2 → cycle-2.1).ai/config.mjsnot inBOOTSTRAP_CONFIGS: once materialized, per-serverconfig.mjsimports point at'../../../config.mjs'(operator overlay); if Tier-1 isn't also bootstrapped, the chain breaks in the worktree.The Architectural Reality
ai/scripts/migrations/bootstrapWorktree.mjs:113-118—BOOTSTRAP_CONFIGSlist (4 per-server configs only).ai/scripts/migrations/bootstrapWorktree.mjs:226-253— copy-from-canonical loop usingfs.copyFile.ai/scripts/setup/initServerConfigs.mjs:129-133—materializeServerConfigTemplate(the canonical rewrite logic; export-ready for reuse).The Fix
C-lite shape per Discussion #12049 convergence:
'ai/config.mjs'toBOOTSTRAP_CONFIGSso Tier-1 is copied alongside the four per-server configs.fs.copyFile, applymaterializeServerConfigTemplate()(imported frominitServerConfigs.mjs) to each copied per-serverconfig.mjsso the AiConfig import is rewritten from../../../config.template.mjs→../../../config.mjswhen needed. The function is idempotent — already-materialized files are unchanged.tenantRepos[]and other operator edits exactly as before; only the import-path layer gets normalized.materializeServerConfigTemplateinbootstrapWorktree.mjs— import frominitServerConfigs.mjs(single source of truth for the rewrite logic).Decision Record impact
aligned-with ADR 0014— narrow bootstrap-script behavior change within existing topology.Contract Ledger
Backfilled post-filing per @neo-gpt's intake-blocker on this ticket —
ticket-create-workflow.md §5+learn/agentos/contract-ledger.mdrequire a formal Contract Ledger matrix for any ticket modifying an agent-consumed bootstrap surface. Matrix structure follows @neo-gpt's suggested rows verbatim:BOOTSTRAP_CONFIGS(the array atbootstrapWorktree.mjs:113-118)ai/config.mjsplus the four existing per-serverai/mcp/server/<name>/config.mjspathsbootstrapWorktree.mjs:226-253fs.copyFileshort-circuit on already-existing target)bootstrapWorktree.mjsJSDoc updated to describe Tier-1 inclusionai/config.mjsexists in the fake worktree post-bootstrapbootstrapWorktree()(copy loop at lines 226-253)initServerConfigs.mjs#materializeServerConfigTemplate()(single source of truth — imported, not duplicated)fs.copyFile, materialize stale../../../config.template.mjsAiConfig imports to../../../config.mjsbootstrapWorktree.mjsJSDoc updated to describe materialize-on-copy semantic../../../config.template.mjsAiConfig importtenantRepos[], custom config keys, etc.) exactly — the only transformation is the AiConfig import-path normalization on per-server configslearn/agentos/cloud-deployment/## Out of ScopesectionbootstrapWorktree.mjs+ a focused test fixture; neither modifies test-import-path conventions nor adds migration commandsAcceptance Criteria
BOOTSTRAP_CONFIGScovers top-level'ai/config.mjs'plus the four existing per-serverconfig.mjspaths.config.mjsfrom canonical, bootstrap appliesmaterializeServerConfigTemplate()(imported frominitServerConfigs.mjs) to rewrite any stale../../../config.template.mjsAiConfig import to../../../config.mjs.ai/config.mjs;../../../config.mjs;../../../config.template.mjsAiConfig import in copied per-server configs;bootstrapWorktree.mjsJSDoc to describe the materialize-on-copy + Tier-1 behavior. Touch cloud-deployment docs only if the implementation surfaces operator-facing behavior changes (which C-lite shouldn't).Out of Scope
config.mjs(covered by #11976); see Discussion #12049 OQ5 for the explicit non-overlap constraint.Avoided Traps
initServerConfigs.mjs) instead.Related