@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2

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 (395) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/CONTRIBUTING.md +11 -7
  3. package/DOCS_INDEX.md +37 -4
  4. package/ENG/c-development-eng.md +2 -2
  5. package/ENG/cpp-development-eng.md +2 -2
  6. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  7. package/ENG/flutter-development-eng.md +4 -2
  8. package/ENG/go-development-eng.md +2 -2
  9. package/ENG/java-development-eng.md +3 -3
  10. package/ENG/nodejs-backend-development-eng.md +10 -6
  11. package/ENG/php-development-eng.md +3 -3
  12. package/ENG/python-development-eng.md +205 -0
  13. package/ENG/rust-development-eng.md +42 -1
  14. package/ENG/sec-code-eng.md +7 -7
  15. package/ENG/sql-development-eng.md +4 -4
  16. package/ENG/swift-development-eng.md +5 -4
  17. package/ENG/test-code-eng.md +1 -1
  18. package/ENG/typescript-development-eng.md +4 -4
  19. package/EXECUTION_STATE.md +12 -0
  20. package/GUIDE_ROUTER.md +6 -0
  21. package/LICENSE +133 -21
  22. package/LOOP_ENGINEERING.md +28 -2
  23. package/LOOP_SYSTEM_DESIGN.md +10 -10
  24. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  25. package/PROTOCOL_INTEGRATION.md +55 -2
  26. package/README.md +63 -59
  27. package/TERMINOLOGY.md +2 -0
  28. package/THIRD_PARTY_NOTICES.md +3 -4
  29. package/THREAT_MODEL.md +140 -1
  30. package/completions/_forgeloop +29 -3
  31. package/completions/forgeloop.bash +55 -3
  32. package/completions/forgeloop.fish +183 -1
  33. package/docs/ADVISORY_CONTEXT.md +25 -0
  34. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  35. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  36. package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
  37. package/docs/AGENT_SKILL.md +66 -0
  38. package/docs/ARTIFACT_REFERENCE.md +171 -35
  39. package/docs/AUDIT_UX.md +46 -0
  40. package/docs/BROWSER_VERIFICATION.md +136 -0
  41. package/docs/CLI_REFERENCE.md +512 -8
  42. package/docs/CODE_ATTESTATION.md +2 -2
  43. package/docs/DOCUMENTATION_GUIDE.md +34 -11
  44. package/docs/GETTING_STARTED.md +3 -3
  45. package/docs/JEV_BENCHMARKS.md +31 -0
  46. package/docs/MODEL_ROUTING.md +37 -0
  47. package/docs/OPENSRC_ADAPTER.md +241 -0
  48. package/docs/PACKAGE_CONTENTS.md +46 -18
  49. package/docs/PROVIDERS.md +126 -0
  50. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  51. package/docs/RECIPES.md +9 -0
  52. package/docs/RELEASE_CHECKLIST.md +116 -12
  53. package/docs/SECURITY_REVIEW.md +71 -0
  54. package/docs/SELF_HOSTED_TESTS.md +121 -0
  55. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  56. package/docs/TEST_INTELLIGENCE.md +29 -0
  57. package/docs/TEST_PRUNING.md +14 -0
  58. package/docs/TROUBLESHOOTING.md +198 -1
  59. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  60. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  61. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  62. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  63. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  64. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  65. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  66. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  67. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  68. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  69. package/docs/diagrams/README.md +13 -9
  70. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  71. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  72. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  73. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  74. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  75. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  76. package/docs/documentation-manifest.json +774 -16
  77. package/docs/protocol-requirements.json +24 -0
  78. package/package.json +43 -11
  79. package/schemas/config.schema.json +14 -0
  80. package/schemas/context-plan.schema.json +18 -0
  81. package/schemas/semantic-decision.schema.json +46 -0
  82. package/schemas/task-bundle.schema.json +7 -2
  83. package/schemas/test-utility.schema.json +44 -0
  84. package/scripts/CI_VALIDATORS.md +6 -6
  85. package/scripts/benchmark-jev.mjs +5 -0
  86. package/scripts/benchmark-test-intelligence.mjs +4 -0
  87. package/scripts/generate-agent-protocol-summary.mjs +6 -3
  88. package/scripts/generate-forgeloop-skill.mjs +133 -0
  89. package/scripts/jev-smoke.mjs +19 -0
  90. package/scripts/run-mcp-clean-tests.mjs +30 -0
  91. package/skills/forgeloop/README.md +9 -0
  92. package/skills/forgeloop/SKILL.md +77 -0
  93. package/skills/forgeloop/references/lifecycle.md +9 -0
  94. package/skills/forgeloop/references/recovery.md +7 -0
  95. package/skills/forgeloop/references/verification.md +7 -0
  96. package/src/adapters/agent-browser/assertions.js +47 -0
  97. package/src/adapters/agent-browser/commands.js +54 -0
  98. package/src/adapters/agent-browser/index.js +3 -0
  99. package/src/adapters/agent-browser/locator.js +40 -0
  100. package/src/adapters/agent-browser/process.js +215 -0
  101. package/src/adapters/agent-browser/provider.js +316 -0
  102. package/src/adapters/emulated-services/constants.js +24 -0
  103. package/src/adapters/emulated-services/index.js +7 -0
  104. package/src/adapters/emulated-services/process.js +162 -0
  105. package/src/adapters/emulated-services/provider.js +289 -0
  106. package/src/adapters/opensrc/normalize.js +90 -0
  107. package/src/adapters/opensrc/process.js +248 -0
  108. package/src/adapters/opensrc/provider.js +338 -0
  109. package/src/adapters/opensrc/search.js +264 -0
  110. package/src/adapters/temporary-paths.js +18 -0
  111. package/src/adapters/typesafe/client.js +28 -0
  112. package/src/adapters/typesafe/engine.js +63 -0
  113. package/src/adapters/typesafe/normalize.js +41 -0
  114. package/src/cli.js +218 -174
  115. package/src/commands/action-show.js +5 -2
  116. package/src/commands/advance.js +5 -1
  117. package/src/commands/baseline.js +13 -11
  118. package/src/commands/checkpoint-revalidate.js +192 -0
  119. package/src/commands/complete.js +19 -3
  120. package/src/commands/context-plan.js +38 -0
  121. package/src/commands/contract-create.js +266 -0
  122. package/src/commands/contract-revise.js +236 -0
  123. package/src/commands/decision-show.js +14 -0
  124. package/src/commands/decision-status.js +22 -0
  125. package/src/commands/discover.js +41 -0
  126. package/src/commands/doctor.js +51 -13
  127. package/src/commands/gate-record.js +194 -0
  128. package/src/commands/gate-revalidate.js +135 -0
  129. package/src/commands/handoff-list.js +6 -1
  130. package/src/commands/handoff-show.js +6 -1
  131. package/src/commands/migrate-protocol.js +2 -2
  132. package/src/commands/model-route.js +40 -0
  133. package/src/commands/next.js +7 -1
  134. package/src/commands/policy-diff.js +25 -4
  135. package/src/commands/progress.js +17 -11
  136. package/src/commands/record-diagnosis.js +3 -0
  137. package/src/commands/route.js +146 -18
  138. package/src/commands/semantic-plan.js +41 -0
  139. package/src/commands/status.js +6 -1
  140. package/src/commands/storage-backup.js +15 -0
  141. package/src/commands/storage-migrate.js +5 -0
  142. package/src/commands/storage-migration-resume.js +5 -0
  143. package/src/commands/storage-migration-status.js +44 -0
  144. package/src/commands/storage-restore-resume.js +7 -0
  145. package/src/commands/storage-restore.js +8 -0
  146. package/src/commands/task-abandon.js +224 -0
  147. package/src/commands/task-list.js +8 -43
  148. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  149. package/src/commands/task-migrate.js +3 -3
  150. package/src/commands/task-recover.js +10 -2
  151. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  152. package/src/commands/task-scope.js +18 -2
  153. package/src/commands/task-show.js +16 -2
  154. package/src/commands/test-inventory.js +5 -0
  155. package/src/commands/test-prune-plan.js +5 -0
  156. package/src/commands/test-prune-probe.js +5 -0
  157. package/src/commands/test-utility.js +5 -0
  158. package/src/commands/validate-protocol.js +39 -15
  159. package/src/commands/validate-receipt.js +8 -1
  160. package/src/config/guides.json +4 -0
  161. package/src/core/action-ledger-projection.js +10 -3
  162. package/src/core/actions.js +38 -75
  163. package/src/core/activation.js +11 -3
  164. package/src/core/approvals.js +17 -51
  165. package/src/core/artifact-registry.js +83 -2
  166. package/src/core/artifacts.js +81 -27
  167. package/src/core/attachment-paths.js +45 -0
  168. package/src/core/attestation-coverage.js +6 -1
  169. package/src/core/attestation-verifier.js +21 -2
  170. package/src/core/attestation.js +17 -6
  171. package/src/core/audit-ux.js +514 -0
  172. package/src/core/audit.js +6 -1
  173. package/src/core/browser-verification/constants.js +149 -0
  174. package/src/core/browser-verification/normalize.js +254 -0
  175. package/src/core/browser-verification/provider.js +519 -0
  176. package/src/core/browser-verification/service.js +115 -0
  177. package/src/core/bundles.js +160 -66
  178. package/src/core/checkpoint-revalidation.js +321 -0
  179. package/src/core/cli-command-definitions.js +350 -7
  180. package/src/core/code-manifest.js +9 -3
  181. package/src/core/command-executors.js +274 -108
  182. package/src/core/command-input.js +136 -44
  183. package/src/core/completion-artifacts.js +85 -34
  184. package/src/core/completion-ownership.js +19 -8
  185. package/src/core/completion-recovery-rebind.js +11 -1
  186. package/src/core/completion.js +64 -15
  187. package/src/core/config.js +3 -0
  188. package/src/core/conformance.js +2 -1
  189. package/src/core/context-compiler/budget.js +9 -0
  190. package/src/core/context-compiler/candidates.js +39 -0
  191. package/src/core/context-compiler/compiler.js +63 -0
  192. package/src/core/context-compiler/fingerprint.js +11 -0
  193. package/src/core/context-compiler/policy.js +13 -0
  194. package/src/core/context-compiler/result.js +23 -0
  195. package/src/core/continuity-lint.js +13 -2
  196. package/src/core/continuity-reconciliation.js +16 -3
  197. package/src/core/continuity.js +16 -8
  198. package/src/core/contract-bootstrap-recovery.js +656 -0
  199. package/src/core/contract-revision.js +209 -0
  200. package/src/core/decision/artifact.js +70 -0
  201. package/src/core/decision/benchmarks.js +103 -0
  202. package/src/core/decision/cache.js +27 -0
  203. package/src/core/decision/constants.js +58 -0
  204. package/src/core/decision/cutover.js +34 -0
  205. package/src/core/decision/engine.js +22 -0
  206. package/src/core/decision/errors.js +68 -0
  207. package/src/core/decision/events.js +104 -0
  208. package/src/core/decision/freshness.js +19 -0
  209. package/src/core/decision/normalizers/index.js +115 -0
  210. package/src/core/decision/policy.js +18 -0
  211. package/src/core/decision/projection.js +16 -0
  212. package/src/core/decision/question-registry.js +201 -0
  213. package/src/core/decision/request.js +26 -0
  214. package/src/core/decision/resolver.js +138 -0
  215. package/src/core/decision/result.js +58 -0
  216. package/src/core/decision/service.js +181 -0
  217. package/src/core/decision/state-builder.js +65 -0
  218. package/src/core/decision/task-bindings.js +35 -0
  219. package/src/core/decision/test-provider.js +32 -0
  220. package/src/core/decision/thresholds.js +15 -0
  221. package/src/core/diagnosis-model.js +11 -0
  222. package/src/core/diagnosis.js +10 -1
  223. package/src/core/diagnostic-projection.js +29 -12
  224. package/src/core/diagnostic-record.js +28 -3
  225. package/src/core/efficiency.js +6 -1
  226. package/src/core/error-codes.js +278 -0
  227. package/src/core/events.js +535 -147
  228. package/src/core/evidence-readiness.js +9 -0
  229. package/src/core/execution-prerequisites.js +36 -13
  230. package/src/core/execution-profile.js +63 -38
  231. package/src/core/execution.js +26 -40
  232. package/src/core/filesystem.js +11 -0
  233. package/src/core/gate-artifact.js +105 -19
  234. package/src/core/gate-provenance.js +125 -0
  235. package/src/core/handoff.js +26 -14
  236. package/src/core/history.js +39 -35
  237. package/src/core/hypothesis-projection.js +32 -0
  238. package/src/core/information-gain-projection.js +104 -100
  239. package/src/core/inspect.js +12 -3
  240. package/src/core/integration-invocation-policy.js +37 -4
  241. package/src/core/integration-resources.js +119 -87
  242. package/src/core/ledger-event-collection.js +189 -0
  243. package/src/core/ledger-relations.js +23 -0
  244. package/src/core/model-router/constants.js +10 -0
  245. package/src/core/model-router/policy.js +103 -0
  246. package/src/core/model-router/router.js +37 -0
  247. package/src/core/native-storage.js +12 -0
  248. package/src/core/next-action-continuity.js +31 -9
  249. package/src/core/next-action-model.js +58 -0
  250. package/src/core/next-action-phases.js +131 -43
  251. package/src/core/next-action-refresh.js +43 -9
  252. package/src/core/next-action-review-phase.js +9 -4
  253. package/src/core/next-action-verification-phase.js +2 -1
  254. package/src/core/next-action.js +56 -9
  255. package/src/core/phase.js +266 -103
  256. package/src/core/policy-engine.js +12 -13
  257. package/src/core/preflight-consistency.js +89 -62
  258. package/src/core/preflight-loaders.js +48 -9
  259. package/src/core/preflight-model.js +6 -1
  260. package/src/core/preflight.js +29 -12
  261. package/src/core/progress.js +21 -10
  262. package/src/core/protocol-info.js +68 -0
  263. package/src/core/protocol-migration.js +7 -6
  264. package/src/core/protocol.js +20 -0
  265. package/src/core/reconcile-closure.js +281 -67
  266. package/src/core/recovery-history.js +67 -47
  267. package/src/core/reflection.js +72 -77
  268. package/src/core/responsibility.js +25 -13
  269. package/src/core/resumability.js +154 -44
  270. package/src/core/route-artifact.js +28 -4
  271. package/src/core/router.js +67 -1
  272. package/src/core/runtime-context.js +123 -61
  273. package/src/core/schema-validation.js +11 -3
  274. package/src/core/security-review/constants.js +64 -0
  275. package/src/core/security-review/normalize.js +245 -0
  276. package/src/core/security-review/provider.js +204 -0
  277. package/src/core/security-review/service.js +134 -0
  278. package/src/core/semantic-planning/constants.js +19 -0
  279. package/src/core/semantic-planning/projection.js +94 -0
  280. package/src/core/semantic-planning/service.js +15 -0
  281. package/src/core/settlement.js +12 -1
  282. package/src/core/signing/bundle-file.js +53 -0
  283. package/src/core/signing/bundle-reference.js +15 -0
  284. package/src/core/signing/sigstore.js +60 -33
  285. package/src/core/signing/statement-file.js +27 -0
  286. package/src/core/sources.js +37 -0
  287. package/src/core/storage-runtime-registry.js +41 -0
  288. package/src/core/structural-quality/artifacts.js +20 -14
  289. package/src/core/structural-quality/service.js +81 -56
  290. package/src/core/target-layout.js +1 -1
  291. package/src/core/task-claim-state.js +249 -15
  292. package/src/core/task-command.js +21 -1
  293. package/src/core/task-conflict-inspection.js +133 -102
  294. package/src/core/task-descriptor.js +29 -22
  295. package/src/core/task-discovery.js +288 -90
  296. package/src/core/task-lock.js +66 -306
  297. package/src/core/task-migration-validation.js +5 -5
  298. package/src/core/task-migration.js +17 -287
  299. package/src/core/task-paths.js +13 -0
  300. package/src/core/task-recovery.js +1 -0
  301. package/src/core/task-snapshot.js +51 -8
  302. package/src/core/task-storage-migration.js +18 -0
  303. package/src/core/templates.js +23 -8
  304. package/src/core/test-intelligence/benchmarks.js +68 -0
  305. package/src/core/test-intelligence/inventory.js +73 -0
  306. package/src/core/test-intelligence/prune.js +94 -0
  307. package/src/core/test-intelligence/semantic-state.js +15 -0
  308. package/src/core/test-intelligence/service.js +40 -0
  309. package/src/core/test-intelligence/utility.js +50 -0
  310. package/src/core/trace.js +164 -105
  311. package/src/core/trajectory-evaluation.js +15 -3
  312. package/src/core/trajectory-metrics.js +47 -32
  313. package/src/core/transaction.js +16 -287
  314. package/src/core/verification-scope.js +20 -12
  315. package/src/core/work-state.js +100 -59
  316. package/src/integration.d.ts +492 -0
  317. package/src/integration.js +54 -0
  318. package/src/providers/README.md +47 -0
  319. package/src/providers/capabilities.js +46 -0
  320. package/src/providers/errors.js +15 -0
  321. package/src/providers/index.js +29 -0
  322. package/src/providers/json-snapshot.js +105 -0
  323. package/src/providers/registry.js +152 -0
  324. package/src/repository-index/binary-manager.js +7 -5
  325. package/src/storage/artifact-bytes.js +22 -0
  326. package/src/storage/attachment-binding.js +11 -0
  327. package/src/storage/attachment-files.js +82 -0
  328. package/src/storage/attachment-references.js +36 -0
  329. package/src/storage/backup.js +53 -0
  330. package/src/storage/capabilities.js +31 -0
  331. package/src/storage/connection-owner.js +119 -0
  332. package/src/storage/connection.js +376 -0
  333. package/src/storage/existing-project-scope.js +45 -0
  334. package/src/storage/export-destination.js +44 -0
  335. package/src/storage/exporter.js +284 -0
  336. package/src/storage/file-durability.js +46 -0
  337. package/src/storage/importer.js +461 -0
  338. package/src/storage/index.js +74 -0
  339. package/src/storage/leases.js +63 -0
  340. package/src/storage/ledger-event-snapshot.js +150 -0
  341. package/src/storage/ledger-relations.js +220 -0
  342. package/src/storage/legacy-maintenance-boundary.js +18 -0
  343. package/src/storage/legacy-signature.js +115 -0
  344. package/src/storage/maintenance-handoff.js +129 -0
  345. package/src/storage/maintenance-owner.js +42 -0
  346. package/src/storage/maintenance.js +310 -0
  347. package/src/storage/metadata-json.js +35 -0
  348. package/src/storage/migration-activate.js +59 -0
  349. package/src/storage/migration-archive.js +57 -0
  350. package/src/storage/migration-attachment-root.js +61 -0
  351. package/src/storage/migration-candidate.js +398 -0
  352. package/src/storage/migration-publication.js +143 -0
  353. package/src/storage/migration-rollback-admission.js +54 -0
  354. package/src/storage/migration-rollback-preparation.js +132 -0
  355. package/src/storage/migration-rollback-publication.js +183 -0
  356. package/src/storage/migration-rollback-release.js +66 -0
  357. package/src/storage/migration-rollback-source-stage.js +103 -0
  358. package/src/storage/migration-rollback.js +43 -0
  359. package/src/storage/migration-source-partition.js +56 -0
  360. package/src/storage/migration-source.js +255 -0
  361. package/src/storage/migration-terminal.js +39 -0
  362. package/src/storage/migration-validation.js +75 -0
  363. package/src/storage/migration.js +43 -0
  364. package/src/storage/operational-attachments.js +49 -0
  365. package/src/storage/operational-context.js +50 -0
  366. package/src/storage/project-backup.js +98 -0
  367. package/src/storage/project-bootstrap.js +24 -0
  368. package/src/storage/project-boundary.js +79 -0
  369. package/src/storage/project-read-snapshot.js +18 -0
  370. package/src/storage/project-replacement.js +137 -0
  371. package/src/storage/project-restore.js +203 -0
  372. package/src/storage/record-definitions.js +45 -0
  373. package/src/storage/repository.js +507 -0
  374. package/src/storage/restore-journal.js +62 -0
  375. package/src/storage/restore-layout.js +30 -0
  376. package/src/storage/restore-preparation.js +84 -0
  377. package/src/storage/restore-replacement-archive.js +162 -0
  378. package/src/storage/restore-replacement-owner.js +31 -0
  379. package/src/storage/restore-replacement-preparation.js +128 -0
  380. package/src/storage/restore-replacement.js +142 -0
  381. package/src/storage/restore-terminal.js +32 -0
  382. package/src/storage/runtime.js +31 -0
  383. package/src/storage/schema.js +214 -0
  384. package/src/storage/singleton-import.js +84 -0
  385. package/src/storage/snapshot-continuity.js +16 -0
  386. package/src/storage/snapshot.js +36 -0
  387. package/src/storage/storage-marker.js +79 -0
  388. package/src/storage/task-guards.js +107 -0
  389. package/src/storage/topology.js +42 -0
  390. package/src/storage/transaction.js +179 -0
  391. package/src/storage/unit-of-work.js +663 -0
  392. package/src/storage/windows-process-incarnation.js +206 -0
  393. package/.forgeloop/forgeloop.gitignore +0 -4
  394. package/LICENSE-DOCS.md +0 -13
  395. package/src/core/transaction-maintenance.js +0 -70
@@ -1,9 +1,12 @@
1
1
  import { readAction } from "../core/actions.js";
2
2
  import { resolveTaskContext } from "../core/task-context.js";
3
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
3
4
 
4
5
  export async function runActionShow({ target, packageRoot, taskId, actionId }) {
5
- const ctx = await resolveTaskContext(target, { packageRoot, taskId });
6
- return readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
6
+ return withProjectReadSnapshot(target, async () => {
7
+ const ctx = await resolveTaskContext(target, { packageRoot, taskId });
8
+ return readAction(target, { packageRoot, taskId: ctx.taskId, actionId });
9
+ });
7
10
  }
8
11
  export function formatActionShowResult(result) {
9
12
  return `FORGELOOP ACTION\naction: ${result.actionId}\nstate: ${result.state}\ncapability: ${result.capability}\n`;
@@ -1,10 +1,14 @@
1
+ import { assertCanonicalPersistence } from "../storage/operational-context.js";
1
2
  import { advanceWorkState } from "../core/phase.js";
2
3
  import { withTaskMutation } from "../core/task-command.js";
3
4
 
4
5
  export { advanceWorkState };
5
6
 
6
- export async function runAdvance({ target, packageRoot, to, taskId, task, authorityContext, runtimeContext }) {
7
+ export async function runAdvance({ target, packageRoot, to, taskId, task, authorityContext, runtimeContext, persistence = null }) {
7
8
  if (!to) throw new Error("--to is required for advance");
9
+
10
+ assertCanonicalPersistence(persistence);
11
+
8
12
  return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "advance", async (ctx) => {
9
13
  return advanceWorkState(target, to, {
10
14
  packageRoot,
@@ -1,3 +1,4 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import {
2
3
  computePersistedPolicyLockData,
3
4
  evaluateTargetPolicy,
@@ -18,6 +19,17 @@ function baselineError(code, message) {
18
19
  return err;
19
20
  }
20
21
 
22
+ async function hasActivePolicySnapshot(target, packageRoot) {
23
+ return withProjectReadSnapshot(target, async () => {
24
+ const tasks = await discoverTasks(target, packageRoot);
25
+ for (const task of tasks) {
26
+ if (task.healthy && task.phase && task.phase !== "COMPLETE" && task.phase !== "BLOCKED"
27
+ && await readTaskPolicySnapshot(target, task.taskId, packageRoot)) return true;
28
+ }
29
+ return false;
30
+ });
31
+ }
32
+
21
33
  export async function runBaseline({
22
34
  target = process.cwd(),
23
35
  packageRoot,
@@ -28,17 +40,7 @@ export async function runBaseline({
28
40
  const policyEval = await evaluateTargetPolicy({ target, packageRoot });
29
41
  let baseline = await readBaseline(target, packageRoot);
30
42
 
31
- const tasks = await discoverTasks(target, packageRoot);
32
- let hasActivePolicyTask = false;
33
- for (const t of tasks) {
34
- if (t.healthy && t.phase && t.phase !== "COMPLETE" && t.phase !== "BLOCKED") {
35
- const snap = await readTaskPolicySnapshot(target, t.taskId, packageRoot);
36
- if (snap) {
37
- hasActivePolicyTask = true;
38
- break;
39
- }
40
- }
41
- }
43
+ const hasActivePolicyTask = await hasActivePolicySnapshot(target, packageRoot);
42
44
 
43
45
  if (record) {
44
46
  if (hasActivePolicyTask && !policyResetAuthorized) {
@@ -0,0 +1,192 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
2
+ import { canonicalFingerprint } from "../core/artifacts.js";
3
+ import { readContract } from "../core/contract.js";
4
+ import {
5
+ appendProtocolEvent,
6
+ validateEventLedger,
7
+ validateStateLedgerCoherence,
8
+ } from "../core/events.js";
9
+ import { readPersistedRoute } from "../core/route-artifact.js";
10
+ import { currentRepositoryFingerprint } from "../core/repository.js";
11
+ import { resolveTaskContext } from "../core/task-context.js";
12
+ import { taskArtifactPath } from "../core/task-paths.js";
13
+ import { assertTaskMutationAllowed } from "../core/task-claim-state.js";
14
+ import { getTaskTransaction, withTaskTransaction } from "../core/transaction.js";
15
+ import {
16
+ CHECKPOINT_REVALIDATED_EVENT,
17
+ assertCheckpointRevalidatedDetails,
18
+ assertRepositoryFingerprint,
19
+ } from "../core/checkpoint-revalidation.js";
20
+ import { resolveTaskClaimState } from "../core/task-claim-state.js";
21
+ import { sameCanonicalGuideList, resolveEffectiveContractBootstrapRepairAnchor } from "../core/contract-bootstrap-recovery.js";
22
+ import {
23
+ classifyLoadedWorkState,
24
+ createWorkState,
25
+ mutateWorkState,
26
+ readWorkState,
27
+ } from "../core/work-state.js";
28
+
29
+ function revalidationError(message, details = {}) {
30
+ const error = new Error(message);
31
+ error.code = "E_CHECKPOINT_REVALIDATION_UNSAFE";
32
+ Object.assign(error, details);
33
+ return error;
34
+ }
35
+
36
+ function sameRepositoryFingerprint(left, right) {
37
+ return left?.branch === right?.branch && left?.head === right?.head;
38
+ }
39
+
40
+ function assertRouteIdentity(state, contract, route) {
41
+ if (!route || route.value?.contractFingerprint !== contract.fingerprint
42
+ || state.routeFingerprint !== route.fingerprint
43
+ || !sameCanonicalGuideList(state.selectedGuides, route.value.guides)) {
44
+ throw revalidationError("ROUTED checkpoint route identity cannot be proven safe", {
45
+ artifacts: [taskArtifactPath(state.taskId, "state"), taskArtifactPath(state.taskId, "route")],
46
+ });
47
+ }
48
+ }
49
+
50
+ async function inspectEligibility(target, packageRoot, taskId) {
51
+ return withProjectReadSnapshot(target, () => inspectSelectedEligibility(target, packageRoot, taskId));
52
+ }
53
+
54
+ async function inspectSelectedEligibility(target, packageRoot, taskId) {
55
+ const state = await readWorkState(target, { packageRoot, taskId });
56
+ if (!state) throw revalidationError("A persisted work-state checkpoint is required");
57
+ if (state.phase !== "ROUTED") throw revalidationError(`Checkpoint revalidation supports phase ROUTED, found ${state.phase}`);
58
+
59
+ const ledger = await validateEventLedger(target, packageRoot, { taskId });
60
+ if (!ledger.valid) throw revalidationError("Checkpoint revalidation requires a structurally valid event ledger", { errors: ledger.errors });
61
+ if (ledger.events.some((event) => event.event === "EXECUTION_STARTED")) {
62
+ throw revalidationError("Checkpoint revalidation is unavailable after execution has started");
63
+ }
64
+ const coherenceErrors = validateStateLedgerCoherence(state, ledger.events);
65
+ if (coherenceErrors.length > 0) throw revalidationError("Checkpoint revalidation requires coherent state and ledger", { errors: coherenceErrors });
66
+ const ownership = await resolveTaskClaimState(target, { taskId, packageRoot });
67
+ if (!ownership.ownershipValid || !ownership.mutationAllowed) {
68
+ throw revalidationError("Checkpoint revalidation cannot bypass invalid task ownership", { errors: ownership.ownershipErrors });
69
+ }
70
+ const contract = await readContract(target, packageRoot, { taskId });
71
+ if (state.contractFingerprint !== contract.fingerprint) {
72
+ throw revalidationError("Checkpoint contract identity changed; use the contract revision path");
73
+ }
74
+ const route = await readPersistedRoute(target, packageRoot, { taskId });
75
+ assertRouteIdentity(state, contract, route);
76
+
77
+ const freshness = await classifyLoadedWorkState({
78
+ target,
79
+ state,
80
+ contractFile: taskArtifactPath(taskId, "contract"),
81
+ });
82
+ const repository = freshness.repository;
83
+ assertRepositoryFingerprint(repository, "current repository fingerprint", { requireHead: true });
84
+ const reasons = [...new Set(freshness.reasons)].sort();
85
+ if (reasons.length === 0) {
86
+ return { state, contract, route, ledger, repository, freshness, alreadyFresh: true };
87
+ }
88
+ if (reasons.length !== 1 || reasons[0] !== "REPOSITORY_CHANGED") {
89
+ throw revalidationError(`Checkpoint freshness is not repository-only: ${reasons.join(", ")}`);
90
+ }
91
+ const repairAnchor = resolveEffectiveContractBootstrapRepairAnchor(ledger.events);
92
+ if (repairAnchor && (repairAnchor.details.contractFingerprint !== contract.fingerprint
93
+ || repairAnchor.details.routeFingerprint !== route.fingerprint)) {
94
+ throw revalidationError("Repaired checkpoint identity does not match the current contract and route");
95
+ }
96
+ return { state, contract, route, ledger, repository, freshness, repairAnchor, alreadyFresh: false };
97
+ }
98
+
99
+ function resultFor(taskId, state, repository, event = null, transaction = null, alreadyFresh = false) {
100
+ return {
101
+ taskId,
102
+ revalidated: !alreadyFresh,
103
+ alreadyFresh,
104
+ phase: state.phase,
105
+ previousRevision: event ? event.details.previousStateRevision : state.revision,
106
+ revalidatedRevision: event ? event.details.revalidatedStateRevision : state.revision,
107
+ previousRepositoryFingerprint: event?.details.previousRepositoryFingerprint ?? repository,
108
+ repositoryFingerprint: event?.details.repositoryFingerprint ?? repository,
109
+ eventSeq: event?.seq ?? null,
110
+ transactionCommitSeq: transaction?.seq ?? null,
111
+ };
112
+ }
113
+
114
+ async function revalidateLocked(target, packageRoot, taskId, transaction) {
115
+ const ownership = await resolveTaskClaimState(target, { taskId, packageRoot });
116
+ if (!ownership.ownershipValid || !ownership.mutationAllowed) {
117
+ throw revalidationError("Checkpoint revalidation cannot bypass invalid task ownership", { errors: ownership.ownershipErrors });
118
+ }
119
+ await assertTaskMutationAllowed(target, { taskId, packageRoot });
120
+ const inspected = await inspectEligibility(target, packageRoot, taskId);
121
+ if (inspected.alreadyFresh) return resultFor(taskId, inspected.state, inspected.repository, null, null, true);
122
+
123
+ const before = inspected.state;
124
+ const previousStateFingerprint = canonicalFingerprint(before);
125
+ const previousRepositoryFingerprint = structuredClone(before.repositoryFingerprint);
126
+ const repository = await currentRepositoryFingerprint(target);
127
+ assertRepositoryFingerprint(repository, "current repository fingerprint", { requireHead: true });
128
+ if (!sameRepositoryFingerprint(repository, inspected.repository)) {
129
+ throw revalidationError("Repository identity changed during checkpoint revalidation");
130
+ }
131
+ const next = await mutateWorkState(target, {
132
+ expectedRevision: before.revision,
133
+ packageRoot,
134
+ taskId,
135
+ }, (current) => createWorkState({
136
+ ...current,
137
+ repositoryFingerprint: repository,
138
+ lastUpdated: new Date().toISOString(),
139
+ }));
140
+ const details = {
141
+ phase: next.phase,
142
+ previousRepositoryFingerprint,
143
+ repositoryFingerprint: structuredClone(repository),
144
+ contractFingerprint: next.contractFingerprint,
145
+ routeFingerprint: next.routeFingerprint,
146
+ previousStateRevision: before.revision,
147
+ revalidatedStateRevision: next.revision,
148
+ previousStateFingerprint,
149
+ revalidatedStateFingerprint: canonicalFingerprint(next),
150
+ };
151
+ assertCheckpointRevalidatedDetails(details);
152
+ const event = await appendProtocolEvent(target, {
153
+ taskId,
154
+ event: CHECKPOINT_REVALIDATED_EVENT,
155
+ details,
156
+ }, packageRoot, { taskId });
157
+ const commitEvent = await appendProtocolEvent(target, {
158
+ taskId,
159
+ event: "TRANSACTION_COMMITTED",
160
+ details: { transactionId: transaction.transactionId, operation: "checkpoint-revalidate" },
161
+ }, packageRoot, { taskId });
162
+ return resultFor(taskId, next, repository, event, commitEvent, false);
163
+ }
164
+
165
+ export async function runCheckpointRevalidate({ target, packageRoot, taskId } = {}) {
166
+ const context = await resolveTaskContext(target, { taskId, packageRoot, explicitRequired: true });
167
+ const effectiveTaskId = context.taskId;
168
+ const initial = await inspectEligibility(target, packageRoot, effectiveTaskId);
169
+ if (initial.alreadyFresh) return resultFor(effectiveTaskId, initial.state, initial.repository, null, null, true);
170
+ const nested = Boolean(await getTaskTransaction(target));
171
+ try {
172
+ return await withTaskTransaction({
173
+ target,
174
+ taskId: effectiveTaskId,
175
+ operation: "checkpoint-revalidate",
176
+ packageRoot,
177
+ recordCommitEvent: false,
178
+ }, async (transaction) => revalidateLocked(target, packageRoot, effectiveTaskId, transaction));
179
+ } catch (error) {
180
+ if (nested || error?.code !== "E_STATE_REVISION_CONFLICT") throw error;
181
+ // A competing writer may have completed this exact revalidation. Recheck
182
+ // all eligibility and history after our preparation rolled back; never
183
+ // replay mutation or convert an unrelated conflict into success.
184
+ const current = await inspectEligibility(target, packageRoot, effectiveTaskId);
185
+ if (!current.alreadyFresh) throw error;
186
+ return resultFor(effectiveTaskId, current.state, current.repository, null, null, true);
187
+ }
188
+ }
189
+
190
+ export function formatCheckpointRevalidateResult(result) {
191
+ return `${JSON.stringify(result, null, 2)}\n`;
192
+ }
@@ -1,13 +1,29 @@
1
1
  import { runComplete as evaluateAndComplete } from "../core/completion.js";
2
2
  import { withTaskMutation } from "../core/task-command.js";
3
+ import { isRecoverableCompletionEvidenceCode } from "../core/completion-recovery.js";
3
4
 
4
5
  export async function runComplete(options = {}) {
5
6
  const target = options.target;
6
7
  const packageRoot = options.packageRoot;
7
8
  const taskId = options.taskId ?? options.task ?? null;
8
- return withTaskMutation(target, { taskId, packageRoot }, "complete", async (ctx) => {
9
- return evaluateAndComplete({ ...options, taskId: ctx?.taskId ?? null });
10
- });
9
+ const rejected = new Error("Completion validation rejected");
10
+ let rejectedResult;
11
+ try {
12
+ return await withTaskMutation(target, { taskId, packageRoot }, "complete", async (ctx) => {
13
+ const result = await evaluateAndComplete({ ...options, taskId: ctx?.taskId ?? null });
14
+ const recordsEvidenceRecovery = options.persist !== false && result.errors.length > 0
15
+ && result.errors.every(error => isRecoverableCompletionEvidenceCode(error.code));
16
+ if (result.status === "REJECTED" && !recordsEvidenceRecovery) {
17
+ rejectedResult = result;
18
+ throw rejected;
19
+ }
20
+ return result;
21
+ });
22
+ } catch (error) {
23
+ // Return the domain rejection only after its transaction has rolled back.
24
+ if (error === rejected) return rejectedResult;
25
+ throw error;
26
+ }
11
27
  }
12
28
 
13
29
  export function formatCompleteResult(result) {
@@ -0,0 +1,38 @@
1
+ import { compileContext } from "../core/context-compiler/compiler.js";
2
+ import { normalizeContextCandidates } from "../core/context-compiler/candidates.js";
3
+ import { canonicalFingerprint } from "../core/artifacts.js";
4
+ import { buildContextQuestionSet } from "../core/decision/question-registry.js";
5
+ import { ensureSemanticDecision } from "../core/decision/service.js";
6
+
7
+ export async function runContextPlan({ target, packageRoot, taskId, decisionId, profile = "balanced", candidates = [], mandatoryIds = [], provider = null, readOnly = false } = {}) {
8
+ if (readOnly) return compileContext({ profile, candidates, mandatoryIds });
9
+ if (target && taskId) {
10
+ const normalized = normalizeContextCandidates(candidates);
11
+ const candidateSetFingerprint = canonicalFingerprint(normalized.map((candidate) => ({
12
+ id: candidate.id, sourceRef: candidate.sourceRef, summary: candidate.summary,
13
+ required: candidate.required, mandatory: candidate.mandatory,
14
+ })));
15
+ const questionSet = buildContextQuestionSet(normalized);
16
+ const decision = await ensureSemanticDecision({
17
+ target, packageRoot, taskId, decisionId, provider, allowNetwork: !readOnly,
18
+ request: {
19
+ decisionKind: "CONTEXT_PLAN", questionSetId: questionSet.id, questionSet,
20
+ state: { candidates: normalized.map(({ id, sourceRef, kind, summary, required, mandatory, risk, trust }) => ({ id, sourceRef, kind, summary, required, mandatory, risk, trust })) },
21
+ candidateIds: normalized.map((candidate) => candidate.id), candidateSetFingerprint,
22
+ },
23
+ });
24
+ return compileContext({ profile, candidates, mandatoryIds, semanticRanker: () => ({ rankedIds: decision.artifact.decision?.rankedIds ?? [], excludedIds: decision.artifact.decision?.excludedIds ?? [] }) });
25
+ }
26
+ return compileContext({ profile, candidates, mandatoryIds });
27
+ }
28
+
29
+ export function formatContextPlanResult(result) {
30
+ return [
31
+ `Context plan: ${result.selectedItems}/${result.candidateItems} items`,
32
+ `Profile: ${result.profile}`,
33
+ `Selected chars: ${result.selectedChars}/${result.budget.maxChars}`,
34
+ `Prompt-injection candidates: ${result.promptInjectionIds.length}`,
35
+ `Fingerprint: ${result.decisionFingerprint}`,
36
+ "",
37
+ ].join("\n");
38
+ }
@@ -0,0 +1,266 @@
1
+ import { readFile } from "node:fs/promises";
2
+
3
+ import { createPresetContract } from "../core/contract-presets.js";
4
+ import { contractFingerprint, readContract, validateContract, writeContract } from "../core/contract.js";
5
+ import { appendProtocolEvent, readEvents, validateEventLedger } from "../core/events.js";
6
+ import { isContractBootstrapRepairCandidate } from "../core/contract-bootstrap-recovery.js";
7
+ import { currentRepositoryFingerprint } from "../core/repository.js";
8
+ import { createWorkState, initializeWorkState, readWorkState } from "../core/work-state.js";
9
+ import { ensureWithin, fileExists } from "../core/filesystem.js";
10
+ import { withTaskMutation } from "../core/task-command.js";
11
+ import { readPersistedRoute } from "../core/route-artifact.js";
12
+ import {
13
+ buildResumableWorkStateFields,
14
+ deriveResumePhaseFromLedger,
15
+ } from "../core/resumability.js";
16
+ import { taskArtifactPath } from "../core/task-paths.js";
17
+ import { ensureSemanticDecision } from "../core/decision/service.js";
18
+ import { operationalArtifactExists } from "../storage/operational-context.js";
19
+
20
+ function bootstrapInconsistent(message, candidate = false) {
21
+ const error = new Error(message);
22
+ error.code = candidate ? "E_CONTRACT_BOOTSTRAP_REPAIR_AVAILABLE" : "E_CONTRACT_BOOTSTRAP_INCONSISTENT";
23
+ return error;
24
+ }
25
+
26
+ async function hasValidatedContractHistory(target, packageRoot, taskId) {
27
+ if (!target || !packageRoot || !taskId) return false;
28
+ const events = await readEvents(target, packageRoot, { taskId });
29
+ return events.some((event) => event.event === "CONTRACT_VALIDATED");
30
+ }
31
+
32
+ /**
33
+ * Resolves the reconstruction projection for a task with a validated ledger
34
+ * whose work-state checkpoint is missing.
35
+ *
36
+ * Ownership rule: the existing canonical contract plus the validated
37
+ * CONTRACT_VALIDATED event are authoritative. Caller-supplied --preset or
38
+ * --contract-file material is only used to prove fingerprint equivalence; the
39
+ * preset generator never redefines historical truth. Reconstruction is
40
+ * recovery, not contract creation.
41
+ *
42
+ * Fail-closed rule: the ledger proves which lifecycle milestones happened.
43
+ * A ROUTE_VALIDATED event with a missing, malformed, or differently-bound
44
+ * route artifact cannot be downgraded to CONTRACT_READY; that would regress
45
+ * the lifecycle and allow a later route command to append a duplicate
46
+ * ROUTE_VALIDATED milestone.
47
+ */
48
+ async function loadReconstructionEvidence(target, packageRoot, taskId) {
49
+ const ledger = await validateEventLedger(target, packageRoot, { taskId });
50
+ if (!ledger.valid) {
51
+ const candidate = isContractBootstrapRepairCandidate(ledger.events, ledger.errors, taskId);
52
+ const error = bootstrapInconsistent(
53
+ candidate
54
+ ? "The exact contract bootstrap defect requires task-repair-contract-bootstrap"
55
+ : "Existing contract has an inconsistent ledger; cannot overwrite",
56
+ !!candidate,
57
+ );
58
+ error.next = candidate
59
+ ? `forgeloop task-repair-contract-bootstrap --task ${taskId} --acknowledge-repair --json`
60
+ : undefined;
61
+ throw error;
62
+ }
63
+ const contract = await readContract(target, packageRoot, { taskId });
64
+ if (!ledger.events.some((event) => event.event === "CONTRACT_VALIDATED"
65
+ && event.details?.contractFingerprint === contract.fingerprint)) {
66
+ throw bootstrapInconsistent("Existing contract is not bound to a valid CONTRACT_VALIDATED event");
67
+ }
68
+
69
+ const routeValidated = ledger.events.some((event) => event.event === "ROUTE_VALIDATED");
70
+ const route = await readPersistedRoute(target, packageRoot, { taskId }).catch((error) => ({
71
+ __reconstructionError: error,
72
+ }));
73
+ if (routeValidated) {
74
+ if (route?.__reconstructionError || !route?.fingerprint) {
75
+ throw bootstrapInconsistent(
76
+ "Ledger contains ROUTE_VALIDATED but the route artifact is missing or malformed; reconstruction must not downgrade the lifecycle",
77
+ );
78
+ }
79
+ if (route.value?.contractFingerprint !== undefined
80
+ && route.value.contractFingerprint !== contract.fingerprint) {
81
+ throw bootstrapInconsistent(
82
+ "Persisted route artifact is bound to a different contract than the validated contract",
83
+ );
84
+ }
85
+ }
86
+ const scopedEvents = ledger.events.filter((event) => event.taskId === taskId);
87
+ const resumedPhase = routeValidated
88
+ ? (await deriveResumePhaseFromLedger(target, packageRoot, taskId) ?? "ROUTED")
89
+ : "CONTRACT_READY";
90
+ const projection = buildResumableWorkStateFields({ events: scopedEvents, resumedPhase });
91
+ return { ledger, contract, route: routeValidated ? route : null, projection };
92
+ }
93
+
94
+ async function reconstructFromExistingContract(target, packageRoot, taskId, requestedFingerprint = null) {
95
+ const { contract, route, projection } = await loadReconstructionEvidence(target, packageRoot, taskId);
96
+ if (requestedFingerprint !== null && requestedFingerprint !== contract.fingerprint) {
97
+ throw bootstrapInconsistent("A different contract already exists for this task; contract-create is not contract-revise");
98
+ }
99
+ const repositoryFingerprint = await currentRepositoryFingerprint(target);
100
+ const reconstructedState = createWorkState({
101
+ taskId,
102
+ contractFingerprint: contract.fingerprint,
103
+ ...(route ? { routeFingerprint: route.fingerprint } : {}),
104
+ repositoryFingerprint,
105
+ phase: projection.phase,
106
+ selectedGuides: route ? [...route.value.guides] : [],
107
+ requiredGates: [],
108
+ satisfiedGates: [],
109
+ completedSteps: projection.completedSteps,
110
+ pendingSteps: projection.pendingSteps,
111
+ ...(projection.verificationCycle !== undefined ? { verificationCycle: projection.verificationCycle } : {}),
112
+ requiredArtifacts: [],
113
+ checks: [],
114
+ failures: [],
115
+ blockers: [],
116
+ verificationEvidence: [],
117
+ });
118
+ await initializeWorkState(target, reconstructedState, { packageRoot, taskId });
119
+ return {
120
+ taskId,
121
+ phase: projection.phase,
122
+ idempotent: true,
123
+ contractFingerprint: contract.fingerprint,
124
+ reconstructed: true,
125
+ };
126
+ }
127
+
128
+ export async function runContractCreate({ target, packageRoot, taskId, task, contractFile = null, preset = null, semanticProvider = null } = {}) {
129
+ if (!preset && !contractFile) throw new Error("contract-create requires --preset or --contract-file");
130
+ if (preset && contractFile) throw new Error("contract-create accepts either --preset or --contract-file, not both");
131
+
132
+ const effectiveTaskId = taskId ?? task;
133
+ const hasCanonicalContract = await hasValidatedContractHistory(target, packageRoot, effectiveTaskId);
134
+ if (target && packageRoot && effectiveTaskId && !hasCanonicalContract) {
135
+ await ensureSemanticDecision({
136
+ target, packageRoot, taskId: effectiveTaskId, provider: semanticProvider,
137
+ request: {
138
+ decisionKind: "CONTRACT_APPLICABILITY", questionSetId: "contract-v1",
139
+ state: { source: preset ? "preset" : "contract-file", preset: preset ?? null },
140
+ },
141
+ });
142
+ }
143
+ return withTaskMutation(target, { taskId: effectiveTaskId, packageRoot }, "contract-create", async (ctx) => {
144
+ const events = await readEvents(target, packageRoot, { taskId: ctx.taskId });
145
+ if (!events.some((event) => event.event === "DISCOVERY_STARTED")) {
146
+ const error = new Error("Contract creation requires completed discovery");
147
+ error.code = "E_DISCOVERY_REQUIRED";
148
+ throw error;
149
+ }
150
+ const existingState = await readWorkState(target, { packageRoot, taskId: ctx.taskId });
151
+ if (existingState) {
152
+ if (existingState.phase !== "CONTRACT_READY") throw new Error("Contract already exists beyond the contract-ready phase");
153
+ const existingContract = await readContract(target, packageRoot, { taskId: ctx.taskId });
154
+ const existingLedger = await validateEventLedger(target, packageRoot, { taskId: ctx.taskId });
155
+ if (!existingLedger.valid) {
156
+ const candidate = isContractBootstrapRepairCandidate(existingLedger.events, existingLedger.errors, ctx.taskId);
157
+ const error = bootstrapInconsistent(
158
+ candidate
159
+ ? "The exact contract bootstrap defect requires task-repair-contract-bootstrap"
160
+ : "Existing contract checkpoint has an invalid event ledger",
161
+ !!candidate,
162
+ );
163
+ error.next = candidate ? `forgeloop task-repair-contract-bootstrap --task ${ctx.taskId} --acknowledge-repair --json` : undefined;
164
+ throw error;
165
+ }
166
+ if (existingState.contractFingerprint !== existingContract.fingerprint
167
+ || !existingLedger.events.some((event) => event.event === "CONTRACT_VALIDATED"
168
+ && event.details?.contractFingerprint === existingContract.fingerprint)) {
169
+ throw bootstrapInconsistent("Existing contract checkpoint is not bound to its validated contract event");
170
+ }
171
+ return { taskId: ctx.taskId, phase: existingState.phase, idempotent: true, contractFingerprint: existingContract.fingerprint };
172
+ }
173
+
174
+ // Reconstruction precedes preset generation: when the task already has a
175
+ // canonical contract, recover from it without generating a replacement
176
+ // candidate first. The requested preset/file contract is only compared by
177
+ // fingerprint to prove idempotent equivalence; it never redefines the
178
+ // canonical historical contract.
179
+ const existingContractPath = taskArtifactPath(ctx.taskId, "contract");
180
+ if (operationalArtifactExists(target, existingContractPath)
181
+ ?? await fileExists(ensureWithin(target, existingContractPath)).catch(() => false)) {
182
+ const existingContract = await readContract(target, packageRoot, { taskId: ctx.taskId });
183
+ const existingLedger = await validateEventLedger(target, packageRoot, { taskId: ctx.taskId });
184
+ const validatedContractEvents = existingLedger.events.filter(
185
+ (event) => event.event === "CONTRACT_VALIDATED",
186
+ );
187
+
188
+ // task-create persists the proposed contract before contract-create has
189
+ // validated it. That artifact is not yet canonical history, so preserve
190
+ // ordinary first-time contract creation when no CONTRACT_VALIDATED event
191
+ // exists. Once validation history exists, reconstruction must be
192
+ // authoritative and fail closed on any binding mismatch.
193
+ if (validatedContractEvents.length > 0) {
194
+ if (!validatedContractEvents.some(
195
+ (event) => event.details?.contractFingerprint === existingContract.fingerprint,
196
+ )) {
197
+ throw bootstrapInconsistent("Existing contract is not bound to a valid CONTRACT_VALIDATED event");
198
+ }
199
+
200
+ let requestedFingerprint = null;
201
+ if (contractFile) {
202
+ const source = ensureWithin(target, contractFile);
203
+ if (await fileExists(source).catch(() => false)) {
204
+ try {
205
+ const candidate = { ...JSON.parse(await readFile(source, "utf8")), taskId: ctx.taskId };
206
+ await validateContract(candidate, packageRoot);
207
+ requestedFingerprint = contractFingerprint(candidate);
208
+ } catch {
209
+ requestedFingerprint = undefined;
210
+ }
211
+ }
212
+ }
213
+ // A preset selector is not historical contract authority. If the
214
+ // caller supplied a replacement file, compare it; otherwise recover
215
+ // the persisted contract independently of current preset generation.
216
+ return reconstructFromExistingContract(
217
+ target,
218
+ packageRoot,
219
+ ctx.taskId,
220
+ requestedFingerprint ?? null,
221
+ );
222
+ }
223
+ }
224
+
225
+ let contract;
226
+ if (preset) {
227
+ contract = createPresetContract({ taskId: ctx.taskId, preset, claims: ctx.descriptor.writeClaims });
228
+ } else {
229
+ const source = ensureWithin(target, contractFile);
230
+ if (!(await fileExists(source))) throw new Error(`Specified contract file not found: ${contractFile}`);
231
+ contract = JSON.parse(await readFile(source, "utf8"));
232
+ contract = { ...contract, taskId: ctx.taskId };
233
+ }
234
+ await validateContract(contract, packageRoot);
235
+ const fingerprint = contractFingerprint(contract);
236
+
237
+ await writeContract(target, contract, packageRoot, { taskId: ctx.taskId });
238
+ const repositoryFingerprint = await currentRepositoryFingerprint(target);
239
+ await initializeWorkState(target, createWorkState({
240
+ taskId: ctx.taskId,
241
+ contractFingerprint: fingerprint,
242
+ repositoryFingerprint,
243
+ phase: "CONTRACT_READY",
244
+ selectedGuides: [],
245
+ requiredGates: [],
246
+ satisfiedGates: [],
247
+ completedSteps: ["contract"],
248
+ pendingSteps: ["route", "planning", "implementation", "verification"],
249
+ requiredArtifacts: [],
250
+ checks: [],
251
+ failures: [],
252
+ blockers: [],
253
+ verificationEvidence: [],
254
+ }), { packageRoot, taskId: ctx.taskId });
255
+ await appendProtocolEvent(target, {
256
+ taskId: ctx.taskId,
257
+ event: "CONTRACT_VALIDATED",
258
+ details: { contractFingerprint: fingerprint },
259
+ }, packageRoot, { taskId: ctx.taskId });
260
+ return { taskId: ctx.taskId, phase: "CONTRACT_READY", idempotent: false, contractFingerprint: fingerprint };
261
+ });
262
+ }
263
+
264
+ export function formatContractCreateResult(result) {
265
+ return `phase: ${result.phase}\n${result.idempotent ? "idempotent: true\n" : ""}`;
266
+ }