@tea-agent/loop-agent 0.38.2 → 0.38.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +171 -96
  3. package/README.md +11 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/advance.js +4 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +428 -27
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/commands/task-advance.js +31 -0
  17. package/dist/executors/dag-pi-executor.js +76 -10
  18. package/dist/executors/pi-event-serializer.js +42 -1
  19. package/dist/executors/pi-executor.js +51 -0
  20. package/dist/executors/pi-extension-resolver.js +233 -0
  21. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  22. package/dist/executors/pi-sdk-executor.js +187 -59
  23. package/dist/executors/shell-executor.js +265 -15
  24. package/dist/executors/shell-write-guard.js +7 -0
  25. package/dist/shared/dag-prompt-override.js +28 -0
  26. package/dist/shared/operator/capabilities.js +32 -0
  27. package/dist/shared/playwright-cli-command-policy.js +15 -0
  28. package/dist/shared/update/console-notifier.js +100 -0
  29. package/dist/{cli → shared}/update/npm-client.js +41 -8
  30. package/dist/{cli → shared}/update/state.js +41 -13
  31. package/dist/task/contract/constants.js +1 -0
  32. package/dist/task/contract/diff.js +26 -0
  33. package/dist/task/contract/project.js +5 -0
  34. package/dist/task/contract/schema.js +6 -1
  35. package/dist/task/source-prepare/build-draft.js +15 -0
  36. package/dist/task/source-prepare/parse-intent.js +24 -4
  37. package/dist/worker/cli.js +31 -1
  38. package/dist/worker/console/chat/routes.js +1 -0
  39. package/dist/worker/console/dag-execution-receipt.js +33 -0
  40. package/dist/worker/console/operator-actions.js +116 -0
  41. package/dist/worker/console/operator-user-error.js +2 -2
  42. package/dist/worker/console/pi-readiness.js +4 -4
  43. package/dist/worker/console/routes.js +43 -2
  44. package/dist/worker/console/security.js +63 -9
  45. package/dist/worker/console/server.js +36 -5
  46. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4bmo3iY.js → abnfDiagram-N423BO3Z-gauhD9kD.js} +1 -1
  47. package/dist/worker/console/static/assets/{arc-SAD7McOS.js → arc-pm3c2Rsl.js} +1 -1
  48. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DAVLUdK1.js → architectureDiagram-T3A2C74G-D_JAg3wH.js} +1 -1
  49. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CPPdv4vj.js → blockDiagram-VBNYF7ZC-Dk7-ex52.js} +1 -1
  50. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D1GRGZax.js → c4Diagram-5PPSVZJV-Uval9oRI.js} +1 -1
  51. package/dist/worker/console/static/assets/channel-CVrnUdZl.js +1 -0
  52. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D_Bq2ZqK.js → chunk-2GRJ4B5K-BSYjHs-v.js} +1 -1
  53. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CPLVtamK.js → chunk-2Q5K7J3B-KGUuBXI8.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DnaZWcL3.js → chunk-5RXB4S5H-JqAtxD9N.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BRTmFgnq.js → chunk-5VM5RSS4-D6EJOVQK.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BRlfYZoH.js → chunk-6Q2QTUOP-B1JriSjm.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CjNybD4u.js → chunk-GF5L2VYU-D62b96Jd.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-D3e-74fG.js → chunk-JWPE2WC7-BsNKEC_r.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Br3ir6f2.js → chunk-KBJHAD2P-B56v6-ZP.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-1NgVuRbh.js → chunk-RYQCIY6F-D2KPnd8K.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BTYuvuWN.js → chunk-XXDRQBXY-kzKSCaaL.js} +1 -1
  62. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-aX91WTya.js +1 -0
  63. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-aX91WTya.js +1 -0
  64. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BC6Bgosf.js → cose-bilkent-JH36ORCC-C6N6KNJd.js} +1 -1
  65. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DV-HypfL.js → cynefin-VYW2F7L2-DiyymT2D.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DhTaddO-.js → cynefinDiagram-MW4NZA55-BeKAXvf3.js} +1 -1
  67. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BnEcOmiI.js → dagre-VZM6K2ZE-TkIdXfPF.js} +1 -1
  68. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-xphGRVKp.js → diagram-7IWD3JNH-BFmSG3ks.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CrdD1mRG.js → diagram-B4RE2ZJO-DqpJT2Fs.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-D4YdLsaF.js → diagram-LBJQPF4R-r5ATN43t.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-LCKyGZ5U.js → diagram-Q27KOJAE-DEcSCMuR.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BeiTe7t0.js → diagram-UB23O5K3-VOvYRLVc.js} +1 -1
  73. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BO2cq8GS.js → ebnfDiagram-BXEA7PRR-D7f4Cb1u.js} +1 -1
  74. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmWww3Nt.js → erDiagram-JOGREHBK-ziUf3U0G.js} +1 -1
  75. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C2kkUYg5.js → flowDiagram-UKHOOZJN-BoRdQ-1y.js} +1 -1
  76. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DY_JZ2iQ.js → ganttDiagram-PKOTCBZU-B1-GncNm.js} +1 -1
  77. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BizhCSw9.js → gitGraphDiagram-DS77QQ5N-CTW-ph8W.js} +1 -1
  78. package/dist/worker/console/static/assets/index-C8rOIAJr.css +1 -0
  79. package/dist/worker/console/static/assets/{index-BXN89VPX.js → index-DEbyrEfW.js} +147 -68
  80. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Dlo2eK_d.js → infoDiagram-6WML65LV-BFA337tZ.js} +1 -1
  81. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-3EIlIIuZ.js → ishikawaDiagram-WSZJBQD7-BtEujgRR.js} +1 -1
  82. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DiMDe5Yh.js → journeyDiagram-NVQOT4AX-C8ZwD2Jp.js} +1 -1
  83. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-sN553oIN.js → kanban-definition-27J2QSJJ-Ck-z8B11.js} +1 -1
  84. package/dist/worker/console/static/assets/{linear-D9Py31Ld.js → linear-CAyXjQjN.js} +1 -1
  85. package/dist/worker/console/static/assets/{mermaid.core-D7Occzk_.js → mermaid.core-CcWKn6tS.js} +5 -5
  86. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DjDRboiR.js → mindmap-definition-FAOFIHXS-C2NuDO7E.js} +1 -1
  87. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Cgc4qlzp.js → pegDiagram-VL7TDLO6-BFub3PDz.js} +1 -1
  88. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DxZj8pKd.js → pieDiagram-7S7Q4E2Y-BJx5THZ5.js} +1 -1
  89. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CJ50MngR.js → quadrantDiagram-CIZ2JOQS-DhalkDWV.js} +1 -1
  90. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BY-OD8Tr.js → railroadDiagram-AXF67PYL-CVcBM36o.js} +1 -1
  91. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQFButfK.js → requirementDiagram-LRYGKXZP-D_vy4n0w.js} +1 -1
  92. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BB_QUK_b.js → sankeyDiagram-W5VNT64P-DGYdJL1D.js} +1 -1
  93. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CqhXZjyx.js → sequenceDiagram-SI44F4Z6-C1xJFg3k.js} +1 -1
  94. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-2l1U4rgJ.js → sizeCapture-X5ZJPWSS-D_Fc43df.js} +1 -1
  95. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BX1FbrhJ.js → stateDiagram-OKZ733FA-BhGbIjbu.js} +1 -1
  96. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DUt1lyFL.js +1 -0
  97. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DOSnT7hb.js → swimlanes-SLNWSIFB-DVkNcO-V.js} +2 -2
  98. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BM6SGr7j.js +8 -0
  99. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DVJQA7AN.js → timeline-definition-Z64GVDOM-BQU9ufV0.js} +1 -1
  100. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-74uxl9gx.js → vennDiagram-T6HMQDX7-BCQhRmYB.js} +1 -1
  101. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DsBxlcg_.js → wardleyDiagram-T6FBY63Y-DWerOyHO.js} +1 -1
  102. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-snuW9Mjw.js → xychartDiagram-ELKLHX3M-DxLeebQU.js} +1 -1
  103. package/dist/worker/console/static/index.html +2 -2
  104. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  105. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
  106. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  107. package/dist/worker/observe/node-input.js +74 -3
  108. package/dist/worker/observe/node-process.js +377 -0
  109. package/dist/worker/observe/routes.js +36 -3
  110. package/dist/worker/observe/shell-handler-keys.js +34 -0
  111. package/dist/worker/observe/static/api.js +66 -0
  112. package/dist/worker/observe/static/constants.js +8 -1
  113. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  114. package/dist/worker/observe/static/format.js +23 -0
  115. package/dist/worker/observe/static/markdown-render.js +20 -1
  116. package/dist/worker/observe/static/operator-chrome.js +4 -0
  117. package/dist/worker/observe/static/state.js +13 -2
  118. package/dist/worker/observe/static/styles.css +73 -0
  119. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  120. package/dist/worker/observe/static/views/dag.js +8 -1
  121. package/dist/worker/observe/static/views/failures.js +5 -2
  122. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  123. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +76 -6
  124. package/dist/workflows/dag/backend-test-scenario-partitions.js +73 -1
  125. package/dist/workflows/dag/contract-output-registry.js +15 -0
  126. package/dist/workflows/dag/failure-category.js +12 -0
  127. package/dist/workflows/dag/frontend-implementation-contract.js +145 -40
  128. package/dist/workflows/dag/frontend-prewrite-gate.js +87 -135
  129. package/dist/workflows/dag/frontend-repair.js +14 -0
  130. package/dist/workflows/dag/frontend-risk.js +15 -2
  131. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  132. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  133. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  134. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  135. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  136. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  137. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  138. package/dist/workflows/dag/frontend-verification-trace.js +29 -4
  139. package/dist/workflows/dag/init-hybrid.js +196 -117
  140. package/dist/workflows/dag/node-execution.js +139 -10
  141. package/dist/workflows/dag/prompt.js +15 -1
  142. package/dist/workflows/dag/rerun-plan.js +134 -3
  143. package/dist/workflows/dag/rerun-run.js +58 -6
  144. package/dist/workflows/dag/retry-policy.js +91 -0
  145. package/dist/workflows/dag/structured-output-repair.js +712 -0
  146. package/dist/workflows/dag/types.js +32 -1
  147. package/dist/workflows/dag/validate.js +41 -1
  148. package/docs/init-surface.manifest.json +5 -0
  149. package/docs/operations/README.md +1 -1
  150. package/docs/operations/local-development-environment.md +1 -5
  151. package/docs/skills/vetted-skill-registry.md +14 -0
  152. package/docs/templates/README.md +1 -1
  153. package/docs/templates/agent-dag.schema.json +44 -0
  154. package/docs/templates/backend-test-dag.json +2 -2
  155. package/docs/templates/frontend-test-dag.json +8 -10
  156. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  157. package/docs/templates/init-managed-agents.md +13 -3
  158. package/harness.json +2 -2
  159. package/package.json +12 -4
  160. package/scripts/next-info.mjs +356 -0
  161. package/scripts/next-publish-gate.mjs +238 -0
  162. package/scripts/release-source-binding.mjs +251 -0
  163. package/skills/analyze-product-requirements/SKILL.md +8 -5
  164. package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
  165. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
  166. package/skills/analyze-product-requirements/references/example.md +50 -0
  167. package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
  168. package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
  169. package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
  170. package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
  171. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
  172. package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
  173. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
  174. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
  175. package/skills/codebase-scout/SKILL.md +1 -1
  176. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  177. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  178. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  179. package/skills/improve-codebase-architecture/SKILL.md +81 -0
  180. package/skills/improve-codebase-architecture/deepening.md +37 -0
  181. package/skills/improve-codebase-architecture/html-report.md +123 -0
  182. package/skills/improve-codebase-architecture/interface-design.md +44 -0
  183. package/skills/improve-codebase-architecture/language.md +53 -0
  184. package/skills/loop-agent/references/command-reference.md +11 -4
  185. package/skills/playwright-cli/SKILL.md +22 -0
  186. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  187. package/dist/worker/console/static/assets/channel-R6oQIDkw.js +0 -1
  188. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BPXlmPWI.js +0 -1
  189. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BPXlmPWI.js +0 -1
  190. package/dist/worker/console/static/assets/index-CuH5CNFu.css +0 -1
  191. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DJKi0Qel.js +0 -1
  192. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-YYe7EEm5.js +0 -8
@@ -6,7 +6,7 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { resolveAdapter } from "../adapters/index.js";
9
- import { createNpmUpdateClient } from "../cli/update/npm-client.js";
9
+ import { createNpmUpdateClient, } from "../shared/update/npm-client.js";
10
10
  import { auditDocs } from "../governance/checks.js";
11
11
  import { harnessManifestSchema } from "../governance/manifest-types.js";
12
12
  import { isInitRuntimeActive, } from "../shared/runtime-activity.js";
@@ -126,6 +126,7 @@ function controllerStateProjection(state) {
126
126
  phase: state.phase,
127
127
  status: state.status,
128
128
  mergeTasks: cloneJson(state.mergeTasks),
129
+ queuedMergeTasks: cloneJson(state.queuedMergeTasks),
129
130
  ...(state.mergeGuard ? { mergeGuard: cloneJson(state.mergeGuard) } : {}),
130
131
  mergeReceipts: cloneJson(state.mergeReceipts),
131
132
  humanDecisions: cloneJson(state.humanDecisions),
@@ -202,10 +203,16 @@ function assertStateShape(state, repoRoot, runId) {
202
203
  !isControllerIdentity(candidate.controllerIdentity) ||
203
204
  !isRecord(candidate.versionGate) ||
204
205
  !Array.isArray(candidate.mergeTasks) ||
206
+ !Array.isArray(candidate.queuedMergeTasks) ||
205
207
  !Array.isArray(candidate.mergeReceipts) ||
206
208
  !Array.isArray(candidate.humanDecisions) ||
207
209
  !Array.isArray(candidate.actionReceipts) ||
208
- !isHomeFingerprint(candidate.homeFingerprint)) {
210
+ !isHomeFingerprint(candidate.homeFingerprint) ||
211
+ (candidate.modelCatalog !== undefined &&
212
+ (!isRecord(candidate.modelCatalog) ||
213
+ candidate.modelCatalog.schemaVersion !== 1 ||
214
+ !Array.isArray(candidate.modelCatalog.models) ||
215
+ !Array.isArray(candidate.modelCatalog.eligibleReferences)))) {
209
216
  throw new Error("invalid init upgrade state or repoRoot authority");
210
217
  }
211
218
  }
@@ -432,6 +439,7 @@ async function writeControllerAnchor(input) {
432
439
  }
433
440
  let authoritativeHomeFingerprint;
434
441
  let authoritativeGitignoreMigrationSha256;
442
+ let authoritativeModelCatalogSha256;
435
443
  if (input.initialize) {
436
444
  if (await exists(location.file)) {
437
445
  throw new Error("init upgrade controller authority anchor already exists");
@@ -440,6 +448,9 @@ async function writeControllerAnchor(input) {
440
448
  throw new Error("init upgrade controller home fingerprint is invalid");
441
449
  }
442
450
  authoritativeHomeFingerprint = cloneJson(input.state.homeFingerprint);
451
+ authoritativeModelCatalogSha256 = input.state.modelCatalog
452
+ ? canonicalSha256(input.state.modelCatalog)
453
+ : undefined;
443
454
  }
444
455
  else {
445
456
  const current = await readControllerAnchor(location.repoRoot, input.state);
@@ -452,6 +463,7 @@ async function writeControllerAnchor(input) {
452
463
  throw new Error("init upgrade controller authority gitignore migration digest binding is invalid");
453
464
  }
454
465
  authoritativeGitignoreMigrationSha256 = digest;
466
+ authoritativeModelCatalogSha256 = current.modelCatalogSha256;
455
467
  }
456
468
  await writeJsonAtomic(location.file, {
457
469
  schemaVersion: 1,
@@ -459,6 +471,9 @@ async function writeControllerAnchor(input) {
459
471
  runId: input.state.runId,
460
472
  controllerIdentity: input.state.controllerIdentity,
461
473
  homeFingerprint: authoritativeHomeFingerprint,
474
+ ...(authoritativeModelCatalogSha256 !== undefined
475
+ ? { modelCatalogSha256: authoritativeModelCatalogSha256 }
476
+ : {}),
462
477
  ...(authoritativeGitignoreMigrationSha256 !== undefined
463
478
  ? { gitignoreMigrationSha256: authoritativeGitignoreMigrationSha256 }
464
479
  : {}),
@@ -483,6 +498,12 @@ async function readControllerAnchor(repoRoot, state) {
483
498
  !sameControllerIdentity(parsed.controllerIdentity, state.controllerIdentity)) {
484
499
  throw new Error("init upgrade controller authority anchor binding is invalid");
485
500
  }
501
+ const expectedModelCatalogSha256 = state.modelCatalog
502
+ ? canonicalSha256(state.modelCatalog)
503
+ : undefined;
504
+ if (parsed.modelCatalogSha256 !== expectedModelCatalogSha256) {
505
+ throw new Error("init upgrade model catalog differs from controller authority");
506
+ }
486
507
  if (!isHomeFingerprint(parsed.homeFingerprint) ||
487
508
  !sameJsonValue(parsed.homeFingerprint, state.homeFingerprint)) {
488
509
  throw new Error("init upgrade controller authority home fingerprint binding is invalid");
@@ -660,6 +681,7 @@ function toResult(state, verification, gitignoreMigration) {
660
681
  status: state.status,
661
682
  phase: state.phase,
662
683
  controllerIdentity: state.controllerIdentity,
684
+ versionGate: state.versionGate,
663
685
  deterministicActions: state.actionReceipts,
664
686
  modelMergeTasks: state.mergeTasks,
665
687
  humanDecisions: state.humanDecisions,
@@ -677,12 +699,18 @@ function renderReport(state, verification, gitignoreMigration) {
677
699
  `- phase: \`${state.phase}\``,
678
700
  `- controller: \`${state.controllerIdentity.portableIdentity}\``,
679
701
  `- version gate: \`${state.versionGate.outcome}\``,
702
+ ...(state.versionGate.failureReason
703
+ ? [`- version check failure: \`${state.versionGate.failureReason}\``]
704
+ : []),
705
+ ...(state.versionGate.diagnostic
706
+ ? [`- version check diagnostic: ${state.versionGate.diagnostic}`]
707
+ : []),
680
708
  `- version choice: \`${state.versionGate.choice ?? "pending"}\``,
681
709
  `- deterministic actions applied: ${state.actionReceipts.length}`,
682
710
  `- semantic merges accepted: ${state.mergeReceipts.length}`,
683
711
  `- preserved user modifications: ${state.mergeReceipts.length}`,
684
712
  `- retired path actions: ${state.actionReceipts.filter((action) => action.type === "migrate-owned-file" || action.type === "remove-owned-file" || action.type === "remove-empty-directory").length}`,
685
- `- remaining model merge tasks: ${state.mergeTasks.length}`,
713
+ `- remaining model merge tasks: ${state.mergeTasks.length + state.queuedMergeTasks.length}`,
686
714
  `- remaining human decisions: ${state.humanDecisions.length}`,
687
715
  `- gitignore migration assessment: \`.harness/init-upgrades/${state.runId}/gitignore-migration.json\` (index-only untrack guidance; the controller never modifies the Git index)`,
688
716
  ...(gitignoreMigration
@@ -836,22 +864,45 @@ async function acquireGlobalLock(input) {
836
864
  }
837
865
  async function currentVersionGate(input, identity) {
838
866
  try {
839
- const latest = input.latestVersion
840
- ? await input.latestVersion()
841
- : await createNpmUpdateClient({
867
+ let latest;
868
+ if (input.latestVersion) {
869
+ latest = await input.latestVersion();
870
+ if (!latest) {
871
+ return registryUnavailable("invalid-response");
872
+ }
873
+ }
874
+ else {
875
+ const checked = await createNpmUpdateClient({
842
876
  currentVersion: identity.version,
843
877
  packageRoot: identity.packageRoot,
844
- }).latestVersion();
845
- if (!latest)
846
- return { outcome: "registry-unavailable" };
878
+ cwd: path.resolve(input.repoRoot),
879
+ env: process.env,
880
+ }).latestVersionCheck();
881
+ if (!checked.ok)
882
+ return registryUnavailable(checked.reason);
883
+ latest = checked.version;
884
+ }
847
885
  return versionsNewer(latest, identity.version)
848
886
  ? { outcome: "newer-available", latestVersion: latest }
849
887
  : { outcome: "latest", latestVersion: latest };
850
888
  }
851
889
  catch {
852
- return { outcome: "registry-unavailable" };
890
+ return registryUnavailable("command-failed");
853
891
  }
854
892
  }
893
+ function registryUnavailable(reason) {
894
+ const diagnostics = {
895
+ timeout: "npm version check timed out while using the configured npm settings. Verify the registry/proxy and retry.",
896
+ "spawn-failure": "npm could not be started. Verify that npm is installed and available on PATH.",
897
+ "command-failed": "npm version check failed using the configured registry. Run `npm config get registry` and `npm view @tea-agent/loop-agent dist-tags.latest` to diagnose.",
898
+ "invalid-response": "npm returned no valid stable latest version. Verify that the configured registry mirrors @tea-agent/loop-agent and its dist-tags.",
899
+ };
900
+ return {
901
+ outcome: "registry-unavailable",
902
+ failureReason: reason,
903
+ diagnostic: diagnostics[reason],
904
+ };
905
+ }
855
906
  function toRepoPath(repoRoot, absolutePath) {
856
907
  return path.relative(repoRoot, absolutePath).split(path.sep).join("/");
857
908
  }
@@ -1729,7 +1780,8 @@ async function validateContinueMerge(directory, state, anchor) {
1729
1780
  state.mergeTasks = [];
1730
1781
  state.mergeGuard = undefined;
1731
1782
  state.phase = "PLAN";
1732
- state.nextAction = "Accepted the bounded semantic merge; replan and verify the current workspace.";
1783
+ state.nextAction =
1784
+ "Accepted the bounded semantic merge; continue with the next queued merge or run final verification.";
1733
1785
  await saveState(directory, state);
1734
1786
  await rm(path.join(directory, "merge-acceptance-pending.json"), {
1735
1787
  force: true,
@@ -2397,10 +2449,13 @@ async function verifyRecoveryFiles(repoRoot) {
2397
2449
  }
2398
2450
  return verification;
2399
2451
  }
2400
- async function runFinalVerification(state, authoritativeHomeFingerprint, surface) {
2452
+ async function runFinalVerification(state, authoritativeHomeFingerprint, surface, modelCatalog) {
2401
2453
  await assertFrozenVerificationSurface(state.repoRoot, surface);
2402
- const finalCheck = await checkInitUpdate({ repoRoot: state.repoRoot });
2403
- const doctor = await runInitDoctor({ repoRoot: state.repoRoot });
2454
+ const finalCheck = await checkInitUpdate({
2455
+ repoRoot: state.repoRoot,
2456
+ modelCatalog,
2457
+ });
2458
+ const doctor = await runInitDoctor({ repoRoot: state.repoRoot, modelCatalog });
2404
2459
  let inspect;
2405
2460
  try {
2406
2461
  const adapter = await resolveAdapter(state.repoRoot);
@@ -2474,6 +2529,11 @@ async function runFinalVerification(state, authoritativeHomeFingerprint, surface
2474
2529
  mergeReceiptCount: state.mergeReceipts.length,
2475
2530
  },
2476
2531
  initSurfaceClean: finalCheck.ok,
2532
+ initSurface: {
2533
+ deterministicActions: finalCheck.deterministicActions,
2534
+ modelMergeTasks: finalCheck.modelMergeTasks,
2535
+ humanDecisions: finalCheck.humanDecisions,
2536
+ },
2477
2537
  doctor: { ok: doctor.ok, checks: doctor.checks },
2478
2538
  inspect,
2479
2539
  docsAudit,
@@ -2488,7 +2548,7 @@ async function runFinalVerification(state, authoritativeHomeFingerprint, surface
2488
2548
  projectPi.reason === "enabled-false",
2489
2549
  },
2490
2550
  userHomeUnchanged: sameJsonValue(homeAfter, authoritativeHomeFingerprint),
2491
- mergeBoundaryClean: state.mergeTasks.length === 0,
2551
+ mergeBoundaryClean: state.mergeTasks.length === 0 && state.queuedMergeTasks.length === 0,
2492
2552
  humanDecisionClean: state.humanDecisions.length === 0,
2493
2553
  };
2494
2554
  }
@@ -2589,6 +2649,7 @@ function assertAnchorCompletion(anchor, state) {
2589
2649
  !isSha256(completion.actionReceiptsSha256) ||
2590
2650
  !isSha256(completion.mergeReceiptsSha256) ||
2591
2651
  !isSha256(completion.stateProjectionSha256) ||
2652
+ !isSha256(completion.verificationSha256) ||
2592
2653
  !isSha256(completion.finalReportSha256) ||
2593
2654
  !isFrozenVerificationSurface(completion.verificationSurface) ||
2594
2655
  completion.actionReceiptsSha256 !== sha256(serializeJson(state.actionReceipts)) ||
@@ -2609,7 +2670,19 @@ async function readFreshCompletedVerification(repoRoot, state) {
2609
2670
  if (!anchor.completion?.verificationSurface) {
2610
2671
  throw new Error("completed verification surface authority is missing");
2611
2672
  }
2612
- const verification = await runFinalVerification(state, anchor.homeFingerprint, anchor.completion.verificationSurface);
2673
+ const verificationBytes = await readFile(path.join(runDirectory(repoRoot, state.runId), "verification.json")).catch(() => {
2674
+ throw new Error("completed init upgrade verification evidence is missing or unreadable");
2675
+ });
2676
+ if (sha256(verificationBytes) !== anchor.completion.verificationSha256) {
2677
+ throw new Error("completed init upgrade verification evidence is not authorized by the controller anchor");
2678
+ }
2679
+ let verification;
2680
+ try {
2681
+ verification = JSON.parse(verificationBytes.toString("utf-8"));
2682
+ }
2683
+ catch {
2684
+ throw new Error("completed init upgrade verification evidence is invalid JSON");
2685
+ }
2613
2686
  assertCompletedVerification(state, verification);
2614
2687
  return { verification, reportMarkdown: report.toString("utf-8") };
2615
2688
  }
@@ -2683,6 +2756,119 @@ async function readStatusResult(repoRoot, runId) {
2683
2756
  const authenticated = await readFreshCompletedVerification(repoRoot, state);
2684
2757
  return toResult(state, authenticated.verification, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256));
2685
2758
  }
2759
+ async function activateModelMergeTask(input) {
2760
+ const { directory, state, task, gitignoreMigration } = input;
2761
+ const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2762
+ assertTaskOutsideVerificationSurface(task, verificationSurface);
2763
+ if (verificationSurface.files.some((entry) => entry.path === normalizeAllowedPath(task.path))) {
2764
+ await validateControllerAuthorizedVerificationMerge({
2765
+ repoRoot: state.repoRoot,
2766
+ task,
2767
+ surface: verificationSurface,
2768
+ });
2769
+ }
2770
+ state.mergeTasks = [task];
2771
+ state.mergeGuard = await createMergeGuard(state.repoRoot, task);
2772
+ state.phase = "MODEL_MERGE";
2773
+ state.status = "in-progress";
2774
+ await writeControllerAnchor({
2775
+ state,
2776
+ mergeAuthority: {
2777
+ ...state.mergeGuard,
2778
+ priorMergeReceiptCount: state.mergeReceipts.length,
2779
+ priorMergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2780
+ stateProjection: controllerStateProjection(state),
2781
+ stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2782
+ verificationSurface,
2783
+ },
2784
+ });
2785
+ state.nextAction = `Complete only task ${task.taskId} within allowedPaths=[${task.allowedPaths.join(", ")}], then call init upgrade --continue. The controller will reject every other workspace change and all symlink/realpath escapes. ${state.queuedMergeTasks.length} semantic merge task(s) remain queued without replanning.`;
2786
+ await writeJsonAtomic(path.join(directory, "merge-tasks.json"), [state.mergeTasks[0], ...state.queuedMergeTasks]);
2787
+ await saveState(directory, state);
2788
+ await writeReport(directory, state, undefined, gitignoreMigration);
2789
+ return toResult(state, undefined, gitignoreMigration);
2790
+ }
2791
+ async function verifyAndCompleteUpgrade(input) {
2792
+ const { directory, state, gitignoreMigration, modelCatalog } = input;
2793
+ state.mergeTasks = [];
2794
+ state.queuedMergeTasks = [];
2795
+ state.mergeGuard = undefined;
2796
+ state.humanDecisions = [];
2797
+ if (state.mergeReceipts.length > 0) {
2798
+ let postMerge = await checkInitUpdate({
2799
+ repoRoot: state.repoRoot,
2800
+ modelCatalog,
2801
+ });
2802
+ const seen = new Set();
2803
+ for (let pass = 0; postMerge.deterministicActions.length > 0 && pass < 2; pass += 1) {
2804
+ const digest = canonicalSha256(postMerge.deterministicActions);
2805
+ if (seen.has(digest)) {
2806
+ throw new Error(`post-merge deterministic init update did not converge: ${postMerge.deterministicActions.map((action) => `${action.type}:${action.path}`).join(", ")}`);
2807
+ }
2808
+ seen.add(digest);
2809
+ const converged = await applyInitUpdate({
2810
+ repoRoot: state.repoRoot,
2811
+ applySafe: true,
2812
+ clientRecovery: "project",
2813
+ modelCatalog,
2814
+ });
2815
+ state.actionReceipts.push(...converged.applied);
2816
+ await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
2817
+ if (converged.applied.length === 0)
2818
+ break;
2819
+ postMerge = await checkInitUpdate({
2820
+ repoRoot: state.repoRoot,
2821
+ modelCatalog,
2822
+ });
2823
+ }
2824
+ if (postMerge.deterministicActions.length > 0) {
2825
+ throw new Error(`post-merge deterministic init update exceeded the 2-pass convergence limit: ${postMerge.deterministicActions.map((action) => `${action.type}:${action.path}`).join(", ")}`);
2826
+ }
2827
+ if (postMerge.modelMergeTasks.length > 0 ||
2828
+ postMerge.humanDecisions.length > 0) {
2829
+ throw new Error("accepted semantic merges introduced new non-deterministic init decisions; start a new bounded upgrade run");
2830
+ }
2831
+ }
2832
+ state.phase = "VERIFY";
2833
+ const anchor = await readControllerAnchor(state.repoRoot, state);
2834
+ const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2835
+ const verification = await runFinalVerification(state, anchor.homeFingerprint, verificationSurface, modelCatalog);
2836
+ await writeJsonAtomic(path.join(directory, "verification.json"), verification);
2837
+ const verificationSha256 = sha256(await readFile(path.join(directory, "verification.json")));
2838
+ if (verificationPassed(verification)) {
2839
+ state.status = "completed";
2840
+ state.phase = "COMPLETED";
2841
+ state.nextAction =
2842
+ "Init upgrade completed; reload the host session so updated AGENTS, skills, and recovery extensions are discovered.";
2843
+ await writeReport(directory, state, verification, gitignoreMigration);
2844
+ const finalReportSha256 = sha256(await readFile(path.join(directory, "final-report.md")));
2845
+ await writeControllerAnchor({
2846
+ state,
2847
+ completion: {
2848
+ status: "completed",
2849
+ phase: "COMPLETED",
2850
+ actionReceiptCount: state.actionReceipts.length,
2851
+ mergeReceiptCount: state.mergeReceipts.length,
2852
+ actionReceiptsSha256: sha256(serializeJson(state.actionReceipts)),
2853
+ mergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2854
+ stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2855
+ verificationSurface,
2856
+ verificationSha256,
2857
+ finalReportSha256,
2858
+ },
2859
+ });
2860
+ }
2861
+ else {
2862
+ state.status = "failed";
2863
+ const authorityReason = authoritativeVerificationFailureReason(verification);
2864
+ state.nextAction = authorityReason
2865
+ ? `Verification failed: ${authorityReason}. Inspect verification.json, repair only the controller-returned concrete merge path, then use --continue.`
2866
+ : "Verification failed. Inspect verification.json; repair only the controller-returned concrete merge path or address the reported external blocker, then use --continue.";
2867
+ }
2868
+ await saveState(directory, state);
2869
+ await writeReport(directory, state, verification, gitignoreMigration);
2870
+ return toResult(state, verification, gitignoreMigration);
2871
+ }
2686
2872
  async function executeUpgrade(directory, state, input) {
2687
2873
  const identity = await readIdentity();
2688
2874
  if (!sameControllerIdentity(identity, state.controllerIdentity)) {
@@ -2711,8 +2897,7 @@ async function executeUpgrade(directory, state, input) {
2711
2897
  }
2712
2898
  if (state.versionGate.outcome === "registry-unavailable" && !choice) {
2713
2899
  state.status = "needs-human-decision";
2714
- state.nextAction =
2715
- "npm registry is unavailable. Choose --version-choice current, retry, or cancel.";
2900
+ state.nextAction = `${state.versionGate.diagnostic ?? "npm registry is unavailable."} Choose --version-choice current, retry, or cancel.`;
2716
2901
  await saveState(directory, state);
2717
2902
  await writeReport(directory, state);
2718
2903
  return toResult(state);
@@ -2742,15 +2927,17 @@ async function executeUpgrade(directory, state, input) {
2742
2927
  if (choice === "retry" &&
2743
2928
  state.versionGate.outcome === "registry-unavailable") {
2744
2929
  state.status = "needs-human-decision";
2745
- state.nextAction =
2746
- "npm registry remains unavailable after retry. Choose --version-choice current, retry, or cancel.";
2930
+ state.nextAction = `${state.versionGate.diagnostic ?? "npm registry remains unavailable after retry."} Choose --version-choice current, retry, or cancel.`;
2747
2931
  await saveState(directory, state);
2748
2932
  await writeReport(directory, state);
2749
2933
  return toResult(state);
2750
2934
  }
2751
2935
  state.status = "in-progress";
2752
2936
  state.phase = "PLAN";
2753
- const pre = await checkInitUpdate({ repoRoot: state.repoRoot });
2937
+ const pre = await checkInitUpdate({
2938
+ repoRoot: state.repoRoot,
2939
+ modelCatalog: state.modelCatalog,
2940
+ });
2754
2941
  await writeJsonAtomic(path.join(directory, "pre-update-report.json"), pre);
2755
2942
  await writeJsonAtomic(path.join(directory, "upgrade-plan.json"), {
2756
2943
  deterministicActions: pre.deterministicActions,
@@ -2791,29 +2978,42 @@ async function executeUpgrade(directory, state, input) {
2791
2978
  repoRoot: state.repoRoot,
2792
2979
  applySafe: true,
2793
2980
  clientRecovery: "project",
2981
+ modelCatalog: state.modelCatalog,
2794
2982
  });
2795
2983
  state.actionReceipts.push(...safe.applied);
2796
2984
  await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
2797
- let post = await checkInitUpdate({ repoRoot: state.repoRoot });
2985
+ let post = await checkInitUpdate({
2986
+ repoRoot: state.repoRoot,
2987
+ modelCatalog: state.modelCatalog,
2988
+ });
2798
2989
  // Converge remaining safe deterministic init actions before the final check.
2799
2990
  // Project/governance-derived drift (e.g. a personalized project rename after
2800
2991
  // an accepted merge) settles here instead of failing finalCheck. Model merge
2801
2992
  // tasks and human decisions are never auto-applied.
2802
- while (post.deterministicActions.length > 0) {
2993
+ const deterministicPassDigests = new Set();
2994
+ for (let pass = 0; post.deterministicActions.length > 0 && pass < 2; pass += 1) {
2995
+ const actionDigest = canonicalSha256(post.deterministicActions);
2996
+ if (deterministicPassDigests.has(actionDigest)) {
2997
+ throw new Error(`deterministic init update did not converge; repeated actions: ${post.deterministicActions.map((action) => `${action.type}:${action.path}`).join(", ")}`);
2998
+ }
2999
+ deterministicPassDigests.add(actionDigest);
2803
3000
  const converged = await applyInitUpdate({
2804
3001
  repoRoot: state.repoRoot,
2805
3002
  applySafe: true,
2806
3003
  clientRecovery: "project",
3004
+ modelCatalog: state.modelCatalog,
2807
3005
  });
2808
3006
  state.actionReceipts.push(...converged.applied);
2809
3007
  await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
2810
3008
  if (converged.applied.length === 0)
2811
3009
  break;
2812
- post = await checkInitUpdate({ repoRoot: state.repoRoot });
2813
- if (post.humanDecisions.length > 0 ||
2814
- post.modelMergeTasks.length > 0) {
2815
- break;
2816
- }
3010
+ post = await checkInitUpdate({
3011
+ repoRoot: state.repoRoot,
3012
+ modelCatalog: state.modelCatalog,
3013
+ });
3014
+ }
3015
+ if (post.deterministicActions.length > 0) {
3016
+ throw new Error(`deterministic init update exceeded the 2-pass convergence limit: ${post.deterministicActions.map((action) => `${action.type}:${action.path}`).join(", ")}`);
2817
3017
  }
2818
3018
  state.humanDecisions = post.humanDecisions;
2819
3019
  // Gitignore migration assessment: after the managed block has converged,
@@ -2851,6 +3051,7 @@ async function executeUpgrade(directory, state, input) {
2851
3051
  state.humanDecisions = post.humanDecisions;
2852
3052
  if (post.humanDecisions.length > 0) {
2853
3053
  state.mergeTasks = [];
3054
+ state.queuedMergeTasks = [];
2854
3055
  state.mergeGuard = undefined;
2855
3056
  state.status = "needs-human-decision";
2856
3057
  state.phase = "MODEL_MERGE";
@@ -2863,78 +3064,21 @@ async function executeUpgrade(directory, state, input) {
2863
3064
  }
2864
3065
  if (post.modelMergeTasks.length > 0) {
2865
3066
  const activeTask = post.modelMergeTasks[0];
2866
- const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2867
- assertTaskOutsideVerificationSurface(activeTask, verificationSurface);
2868
- if (verificationSurface.files.some((entry) => entry.path === normalizeAllowedPath(activeTask.path))) {
2869
- await validateControllerAuthorizedVerificationMerge({
2870
- repoRoot: state.repoRoot,
2871
- task: activeTask,
2872
- surface: verificationSurface,
2873
- });
2874
- }
2875
- state.mergeTasks = [activeTask];
2876
- state.mergeGuard = await createMergeGuard(state.repoRoot, activeTask);
2877
- // Bind the Git-dir anchor to the state actually handed to the model.
2878
- state.phase = "MODEL_MERGE";
2879
- state.status = "in-progress";
2880
- await writeControllerAnchor({
2881
- state,
2882
- mergeAuthority: {
2883
- ...state.mergeGuard,
2884
- priorMergeReceiptCount: state.mergeReceipts.length,
2885
- priorMergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2886
- stateProjection: controllerStateProjection(state),
2887
- stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2888
- verificationSurface,
2889
- },
2890
- });
2891
- state.nextAction = `Complete only task ${activeTask.taskId} within allowedPaths=[${activeTask.allowedPaths.join(", ")}], then call init upgrade --continue. The controller will reject every other workspace change and all symlink/realpath escapes.`;
2892
- await writeJsonAtomic(path.join(directory, "merge-tasks.json"), state.mergeTasks);
2893
- await saveState(directory, state);
2894
- await writeReport(directory, state, undefined, gitignoreMigration);
2895
- return toResult(state, undefined, gitignoreMigration);
2896
- }
2897
- state.mergeTasks = [];
2898
- state.mergeGuard = undefined;
2899
- state.humanDecisions = [];
2900
- state.phase = "VERIFY";
2901
- const anchor = await readControllerAnchor(state.repoRoot, state);
2902
- const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2903
- const verification = await runFinalVerification(state, anchor.homeFingerprint, verificationSurface);
2904
- await writeJsonAtomic(path.join(directory, "verification.json"), verification);
2905
- if (verificationPassed(verification)) {
2906
- state.status = "completed";
2907
- state.phase = "COMPLETED";
2908
- state.nextAction =
2909
- "Init upgrade completed; reload the host session so updated AGENTS, skills, and recovery extensions are discovered.";
2910
- // The report is controller-produced before its exact bytes are anchored.
2911
- await writeReport(directory, state, verification, gitignoreMigration);
2912
- const finalReportSha256 = sha256(await readFile(path.join(directory, "final-report.md")));
2913
- await writeControllerAnchor({
3067
+ state.queuedMergeTasks = post.modelMergeTasks.slice(1);
3068
+ return await activateModelMergeTask({
3069
+ directory,
2914
3070
  state,
2915
- completion: {
2916
- status: "completed",
2917
- phase: "COMPLETED",
2918
- actionReceiptCount: state.actionReceipts.length,
2919
- mergeReceiptCount: state.mergeReceipts.length,
2920
- actionReceiptsSha256: sha256(serializeJson(state.actionReceipts)),
2921
- mergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2922
- stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2923
- verificationSurface,
2924
- finalReportSha256,
2925
- },
3071
+ task: activeTask,
3072
+ gitignoreMigration,
2926
3073
  });
2927
3074
  }
2928
- else {
2929
- state.status = "failed";
2930
- const authorityReason = authoritativeVerificationFailureReason(verification);
2931
- state.nextAction = authorityReason
2932
- ? `Verification failed: ${authorityReason}. Inspect verification.json, repair only the controller-returned concrete merge path, then use --continue.`
2933
- : "Verification failed. Inspect verification.json; repair only the controller-returned concrete merge path or address the reported external blocker, then use --continue.";
2934
- }
2935
- await saveState(directory, state);
2936
- await writeReport(directory, state, verification, gitignoreMigration);
2937
- return toResult(state, verification, gitignoreMigration);
3075
+ state.queuedMergeTasks = [];
3076
+ return await verifyAndCompleteUpgrade({
3077
+ directory,
3078
+ state,
3079
+ gitignoreMigration,
3080
+ modelCatalog: state.modelCatalog,
3081
+ });
2938
3082
  }
2939
3083
  export async function runInitUpgrade(input) {
2940
3084
  const repoRoot = path.resolve(input.repoRoot);
@@ -3031,6 +3175,28 @@ export async function runInitUpgrade(input) {
3031
3175
  force: true,
3032
3176
  });
3033
3177
  }
3178
+ if (mergeOutcome === "accepted") {
3179
+ const anchor = await readControllerAnchor(repoRoot, state);
3180
+ const gitignoreMigration = await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256);
3181
+ if (!gitignoreMigration) {
3182
+ throw new Error("accepted init upgrade merge lacks its migration assessment");
3183
+ }
3184
+ const nextTask = state.queuedMergeTasks.shift();
3185
+ if (nextTask) {
3186
+ return await activateModelMergeTask({
3187
+ directory,
3188
+ state,
3189
+ task: nextTask,
3190
+ gitignoreMigration,
3191
+ });
3192
+ }
3193
+ return await verifyAndCompleteUpgrade({
3194
+ directory,
3195
+ state,
3196
+ gitignoreMigration,
3197
+ modelCatalog: state.modelCatalog,
3198
+ });
3199
+ }
3034
3200
  }
3035
3201
  else {
3036
3202
  directory = runDirectory(repoRoot, runId);
@@ -3049,10 +3215,12 @@ export async function runInitUpgrade(input) {
3049
3215
  versionGate: { outcome: "latest" },
3050
3216
  nextAction: "Evaluate controller version and init surface.",
3051
3217
  mergeTasks: [],
3218
+ queuedMergeTasks: [],
3052
3219
  mergeReceipts: [],
3053
3220
  humanDecisions: [],
3054
3221
  actionReceipts: [],
3055
3222
  homeFingerprint: await homeFingerprint(),
3223
+ modelCatalog: input.modelCatalog,
3056
3224
  };
3057
3225
  await writeControllerAnchor({ state, initialize: true });
3058
3226
  await writeJsonAtomic(path.join(directory, "controller-identity.json"), {