@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,5 +1,7 @@
1
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import { readdir } from "node:fs/promises";
4
+ import { listOperationalArtifactNames, operationalArtifactExists } from "../storage/operational-context.js";
3
5
 
4
6
  import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
5
7
  import { assertPortableContextSafe, normalizePortableText } from "./portable-context.js";
@@ -165,14 +167,19 @@ export async function validateCanonicalHandoff(target, handoff, {
165
167
  return validateHandoffDigest(handoff);
166
168
  }
167
169
 
168
- export async function writeCanonicalHandoff(target, handoff, {
170
+ export async function writeCanonicalHandoff(target, handoff, options = {}) {
171
+ return withTaskTransaction({ target, taskId: options.taskId ?? handoff?.taskId, packageRoot: options.packageRoot, operation: "handoff-create" }, () => writeCanonicalHandoffInTransaction(target, handoff, options));
172
+ }
173
+
174
+ async function writeCanonicalHandoffInTransaction(target, handoff, {
169
175
  packageRoot = getPackageRoot(),
170
176
  taskId = handoff?.taskId,
171
177
  } = {}) {
172
178
  await validateCanonicalHandoff(target, handoff, { taskId, packageRoot });
173
179
  const relativePath = taskHandoffPath(taskId, handoff.handoffId);
174
- await assertSafePath(target, relativePath);
175
- if (await fileExists(ensureWithin(target, relativePath))) {
180
+ const operationalExists = operationalArtifactExists(target, relativePath);
181
+ if (operationalExists === null) await assertSafePath(target, relativePath);
182
+ if (operationalExists ?? await fileExists(ensureWithin(target, relativePath))) {
176
183
  throw handoffError("E_HANDOFF_INVALID", "An existing handoff cannot be overwritten", [relativePath]);
177
184
  }
178
185
  const artifact = await writeJsonArtifact(target, relativePath, handoff, "handoff-envelope", packageRoot, { taskId, operation: "handoff-create" });
@@ -219,17 +226,22 @@ export async function readCanonicalHandoff(target, {
219
226
  }
220
227
 
221
228
  export async function listCanonicalHandoffs(target, { taskId, packageRoot = getPackageRoot() } = {}) {
222
- const directory = taskHandoffDirectory(taskId);
223
- if (!(await fileExists(ensureWithin(target, directory)))) return [];
224
- const entries = await readdir(ensureWithin(target, directory), { withFileTypes: true });
225
- const handoffs = [];
226
- for (const entry of entries) {
227
- if (!entry.isFile() || !/^handoff-[A-Za-z0-9_-]+\.json$/u.test(entry.name)) continue;
228
- const handoffId = entry.name.slice(0, -5);
229
- const artifact = await readCanonicalHandoff(target, { taskId, handoffId, packageRoot });
230
- handoffs.push(artifact.value);
231
- }
232
- return handoffs.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.handoffId.localeCompare(right.handoffId));
229
+ return withProjectReadSnapshot(target, async () => {
230
+ const directory = taskHandoffDirectory(taskId);
231
+ let names = listOperationalArtifactNames(target, taskId, "handoffs");
232
+ if (names === null) {
233
+ if (!(await fileExists(ensureWithin(target, directory)))) return [];
234
+ names = (await readdir(ensureWithin(target, directory), { withFileTypes: true })).filter(entry => entry.isFile()).map(entry => entry.name);
235
+ }
236
+ const handoffs = [];
237
+ for (const name of names) {
238
+ if (!/^handoff-[A-Za-z0-9_-]+\.json$/u.test(name)) continue;
239
+ const handoffId = name.slice(0, -5);
240
+ const artifact = await readCanonicalHandoff(target, { taskId, handoffId, packageRoot });
241
+ handoffs.push(artifact.value);
242
+ }
243
+ return handoffs.sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.handoffId.localeCompare(right.handoffId));
244
+ });
233
245
  }
234
246
 
235
247
  export async function resolveLatestHandoff(target, { taskId, packageRoot = getPackageRoot() } = {}) {
@@ -10,46 +10,50 @@ export const HISTORY_FILTER_OPTIONS = Object.freeze([
10
10
  "limit",
11
11
  ]);
12
12
 
13
+ function historyEventProjection(filters) {
14
+ const predicates = [];
15
+ for (const key of ["type", "phase"]) {
16
+ if (!filters[key]) continue;
17
+ const values = String(filters[key]).split(",").map(value => value.trim()).filter(Boolean);
18
+ if (values.length) predicates.push(event => values.includes(event[key])
19
+ || (key === "type" && values.includes(event.category)));
20
+ }
21
+ if (filters.failures) predicates.push(event => event.category === "verification"
22
+ && ["failed", "blocked"].includes(String(event.data?.status ?? "")));
23
+ if (filters.checks) predicates.push(event => event.category === "verification");
24
+ for (const key of ["since", "until"]) {
25
+ if (!filters[key]) continue;
26
+ const bound = Date.parse(filters[key]);
27
+ if (!Number.isNaN(bound)) predicates.push(event => event.timestamp
28
+ && (key === "since" ? Date.parse(event.timestamp) >= bound : Date.parse(event.timestamp) <= bound));
29
+ }
30
+ const limit = Number.isInteger(filters.limit) && filters.limit >= 0 ? filters.limit : null;
31
+ const events = [];
32
+ let matched = 0;
33
+ let cursor = 0;
34
+ return {
35
+ add(event) {
36
+ if (!predicates.every(predicate => predicate(event))) return;
37
+ matched += 1;
38
+ if (limit === 0) return;
39
+ if (limit === null || events.length < limit) events.push(event);
40
+ else { events[cursor] = event; cursor = (cursor + 1) % limit; }
41
+ },
42
+ result: () => cursor === 0 ? events : events.slice(cursor).concat(events.slice(0, cursor)),
43
+ get omittedEvents() { return matched - events.length; },
44
+ };
45
+ }
46
+
13
47
  export async function buildTaskHistory({
14
48
  target,
15
49
  packageRoot,
16
50
  taskId = null,
17
51
  filters = {},
18
52
  } = {}) {
19
- const trace = await buildTaskTrace({ target, packageRoot, taskId });
20
- let events = [...trace.events];
21
-
22
- if (filters.type) {
23
- const types = String(filters.type).split(",").map((value) => value.trim()).filter(Boolean);
24
- if (types.length > 0) {
25
- events = events.filter((event) => types.includes(event.type) || types.includes(event.category));
26
- }
27
- }
28
- if (filters.phase) {
29
- const phases = String(filters.phase).split(",").map((value) => value.trim()).filter(Boolean);
30
- if (phases.length > 0) events = events.filter((event) => phases.includes(event.phase));
31
- }
32
- if (filters.failures) {
33
- events = events.filter((event) => event.category === "verification"
34
- && ["failed", "blocked"].includes(String(event.data?.status ?? "")));
35
- }
36
- if (filters.checks) {
37
- events = events.filter((event) => event.category === "verification");
38
- }
39
- if (filters.since) {
40
- const since = Date.parse(filters.since);
41
- if (!Number.isNaN(since)) events = events.filter((event) => event.timestamp && Date.parse(event.timestamp) >= since);
42
- }
43
- if (filters.until) {
44
- const until = Date.parse(filters.until);
45
- if (!Number.isNaN(until)) events = events.filter((event) => event.timestamp && Date.parse(event.timestamp) <= until);
46
- }
47
-
48
- let omittedEvents = 0;
49
- if (Number.isInteger(filters.limit) && filters.limit >= 0 && events.length > filters.limit) {
50
- omittedEvents = events.length - filters.limit;
51
- events = events.slice(-filters.limit);
52
- }
53
+ const projection = historyEventProjection(filters);
54
+ const trace = await buildTaskTrace({ target, packageRoot, taskId }, projection);
55
+ const events = trace.events;
56
+ const omittedEvents = projection.omittedEvents;
53
57
 
54
58
  const checkAttempts = trace.checks.reduce(
55
59
  (total, check) => total + check.attemptCount,
@@ -67,7 +71,7 @@ export async function buildTaskHistory({
67
71
  snapshot: trace.snapshot,
68
72
  summary: {
69
73
  eventCount: events.length,
70
- totalEventCount: trace.events.length,
74
+ totalEventCount: trace.totalEventCount,
71
75
  checkAttemptCount: checkAttempts,
72
76
  failedAttemptCount: failedAttempts,
73
77
  diagnosticCaseCount: trace.diagnostics.cases.length,
@@ -1,3 +1,5 @@
1
+ import { isLedgerEventCollection, ledgerEventsOfTypes } from "./ledger-event-collection.js";
2
+ import { ledgerRelationMap } from "./ledger-relations.js";
1
3
  import { DISPOSITION_TRANSITIONS } from "./diagnostic-model.js";
2
4
 
3
5
  const TERMINAL_STATUSES = Object.freeze(["FALSIFIED", "SUPERSEDED", "UNRESOLVED"]);
@@ -83,3 +85,33 @@ export function getOpenHypotheses(projection) {
83
85
  export function getHypothesisState(projection, id) {
84
86
  return projection?.hypotheses.find((hypothesis) => hypothesis.id === id) ?? null;
85
87
  }
88
+
89
+
90
+ /** Internal context needs current IDs, not every historical disposition payload. */
91
+ export function summarizeOpenHypotheses(events, taskId = null) {
92
+ if (Array.isArray(events)) return projectHypothesisStates(events.filter(event => !taskId || event.taskId === taskId)).openHypotheses;
93
+ if (!isLedgerEventCollection(events)) throw new TypeError("Hypothesis context requires a ledger collection");
94
+ let previous = -Infinity;
95
+ for (const event of events) {
96
+ if (!Number.isFinite(event.seq) || event.seq < previous) {
97
+ return projectHypothesisStates([...events].filter(candidate => !taskId || candidate.taskId === taskId)).openHypotheses;
98
+ }
99
+ previous = event.seq;
100
+ }
101
+ const statuses = ledgerRelationMap();
102
+ const ensure = id => { if (!statuses.has(id)) statuses.set(id, "OPEN"); };
103
+ for (const event of ledgerEventsOfTypes(events, ["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED", "HYPOTHESIS_DISPOSITION_RECORDED"])) {
104
+ if (taskId && event.taskId !== taskId) continue;
105
+ const details = event.details ?? {};
106
+ if (event.event === "DIAGNOSTIC_CASE_RECORDED") {
107
+ for (const hypothesis of details.hypotheses ?? []) if (hypothesis?.id) ensure(hypothesis.id);
108
+ } else if (event.event === "DIAGNOSIS_RECORDED") ensure(HYPOTHESIS_LEGACY_ID);
109
+ else if (details.hypothesisRef && details.status) {
110
+ ensure(details.hypothesisRef);
111
+ if (canTransition(statuses.get(details.hypothesisRef), details.status)) statuses.set(details.hypothesisRef, details.status);
112
+ }
113
+ }
114
+ const open = [];
115
+ for (const [id, status] of statuses) if (status === "OPEN") open.push(id);
116
+ return open.sort();
117
+ }
@@ -1,5 +1,6 @@
1
- import { diagnosticEventsForTask } from "./diagnostic-projection.js";
2
- import { normalizeDiagnosticSnapshot, computeInformationGain } from "./information-gain.js";
1
+ import { ledgerRelationSet, ledgerGroupedSet } from "./ledger-relations.js";
2
+ import { isLedgerEventCollection, ledgerEventsOfTypes } from "./ledger-event-collection.js";
3
+ import { normalizeDiagnosticSnapshot, compareDiagnosticCycles, classifyGain, isEffectiveGain } from "./information-gain.js";
3
4
  import { computeFailureSignature } from "./failure-signature.js";
4
5
 
5
6
  function snapshotFor(event) {
@@ -19,23 +20,43 @@ const sameSortedSet = (a, b) =>
19
20
  // current diagnostic sequence inclusive.
20
21
  // Events inside an interval belong to the *current* cycle's knowledge state;
21
22
  // they are never attributed retroactively to the earlier diagnosis.
22
- function intervalEvents(taskEvents, fromExclusive, toInclusive) {
23
- return taskEvents.filter((event) =>
24
- event.seq > fromExclusive && event.seq <= toInclusive);
23
+ function* intervalEvents(taskEvents, fromExclusive, toInclusive) {
24
+ for (const event of taskEvents) if (event.seq > fromExclusive && event.seq <= toInclusive) yield event;
25
+ }
26
+
27
+ function orderedTaskEvents(events, taskId) {
28
+ const belongs = event => !taskId || !event.taskId || event.taskId === taskId;
29
+ if (!Array.isArray(events) && isLedgerEventCollection(events)) {
30
+ let previous = -Infinity;
31
+ let ordered = true;
32
+ for (const event of events) {
33
+ if (!Number.isFinite(event.seq) || event.seq < previous) { ordered = false; break; }
34
+ previous = event.seq;
35
+ }
36
+ if (ordered) return { *[Symbol.iterator]() { for (const event of ledgerEventsOfTypes(events, ["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED", "VERIFICATION_STARTED", "VERIFICATION_RECORDED", "INTERVENTION_RECORDED", "HYPOTHESIS_DISPOSITION_RECORDED"])) if (belongs(event)) yield event; } };
37
+ }
38
+ return [...(events ?? [])].filter(belongs).sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
39
+ }
40
+
41
+ function eliminatedHypothesis(previousDiagnostic, diagnostic) {
42
+ if (previousDiagnostic?.event !== "DIAGNOSTIC_CASE_RECORDED" || diagnostic.event !== "DIAGNOSTIC_CASE_RECORDED") return false;
43
+ const previous = previousDiagnostic.details?.hypotheses ?? [];
44
+ const current = diagnostic.details?.hypotheses ?? [];
45
+ const statements = new Set(current.map(hypothesis => `${hypothesis.statement}`.trim().toLowerCase()));
46
+ return previous.some(hypothesis => !current.some(candidate => candidate.id === hypothesis.id)
47
+ && !statements.has(`${hypothesis.statement}`.trim().toLowerCase()));
25
48
  }
26
49
 
27
50
  function failureStateByCycle(taskEvents) {
28
- const surfaces = new Map();
29
- const signatures = new Map();
51
+ const surfaces = ledgerGroupedSet();
52
+ const signatures = ledgerGroupedSet();
30
53
  const record = (cycle, details) => {
31
54
  if (!Number.isInteger(cycle)) cycle = Number(cycle) || 1;
32
- if (!surfaces.has(cycle)) surfaces.set(cycle, new Set());
33
- if (!signatures.has(cycle)) signatures.set(cycle, new Set());
34
55
  const requirement = details.requirement ?? details.id ?? details.checkId;
35
56
  if (!requirement) return;
36
57
  if (details.status === "failed" || details.status === "blocked") {
37
- surfaces.get(cycle).add(requirement);
38
- signatures.get(cycle).add(computeFailureSignature({
58
+ surfaces.add(cycle, requirement);
59
+ signatures.add(cycle, computeFailureSignature({
39
60
  requirement,
40
61
  status: details.status,
41
62
  exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
@@ -44,13 +65,6 @@ function failureStateByCycle(taskEvents) {
44
65
  }
45
66
  };
46
67
  for (const event of taskEvents) {
47
- if (event.event === "VERIFICATION_STARTED") {
48
- const cycle = event.details?.verificationCycle;
49
- if (Number.isInteger(cycle)) {
50
- if (!surfaces.has(cycle)) surfaces.set(cycle, new Set());
51
- if (!signatures.has(cycle)) signatures.set(cycle, new Set());
52
- }
53
- }
54
68
  if (event.event === "VERIFICATION_RECORDED") {
55
69
  record(event.details?.verificationCycle ?? 1, event.details ?? {});
56
70
  }
@@ -102,46 +116,45 @@ function snapshotHasContentDelta(previousDetails, currentDetails) {
102
116
  || differs(prev.evidence, cur.evidence);
103
117
  }
104
118
 
105
- export function buildInformationGainProjection(events, taskId) {
106
- const taskEvents = (events ?? [])
107
- .filter((event) => !taskId || !event.taskId || event.taskId === taskId)
108
- .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
119
+ function intervalKnowledge(taskEvents, previousDiagnostic, diagnostic) {
120
+ const intervalStart = previousDiagnostic ? previousDiagnostic.seq : -Infinity;
121
+ let hypothesisDispositionChanged = false;
122
+ const knownUpToPrev = ledgerRelationSet();
123
+ for (const event of taskEvents) {
124
+ if (event.event === "INTERVENTION_RECORDED" && previousDiagnostic && (event.seq ?? 0) <= previousDiagnostic.seq) {
125
+ const fingerprint = event.details?.interventionSemanticFingerprint
126
+ ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase();
127
+ if (fingerprint) knownUpToPrev.add(fingerprint);
128
+ }
129
+ }
130
+ let novelIntervention = false;
131
+ for (const event of intervalEvents(taskEvents, intervalStart, diagnostic.seq)) {
132
+ if (event.event === "HYPOTHESIS_DISPOSITION_RECORDED") hypothesisDispositionChanged = true;
133
+ if (event.event === "INTERVENTION_RECORDED") {
134
+ const fingerprint = event.details?.interventionSemanticFingerprint
135
+ ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase();
136
+ if (fingerprint && !knownUpToPrev.has(fingerprint)) novelIntervention = true;
137
+ }
138
+ }
139
+ return { hypothesisDispositionChanged, novelIntervention };
140
+ }
109
141
 
110
- const diagnosticEvents = diagnosticEventsForTask(taskEvents, taskId)
111
- .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
112
- if (diagnosticEvents.length === 0) return [];
142
+ function* iterateInformationGainProjection(events, taskId) {
143
+ const taskEvents = orderedTaskEvents(events, taskId);
144
+ const diagnosticEvents = { *[Symbol.iterator]() {
145
+ for (const event of taskEvents) {
146
+ if (["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED"].includes(event.event)
147
+ && (!taskId || event.taskId === taskId)) yield event;
148
+ }
149
+ } };
113
150
 
114
151
  const failure = failureStateByCycle(taskEvents);
115
152
 
116
- // Build final per-cycle entries first; effectiveGain is computed once at the
117
- // end from fully final dimensions (no post-mutation anywhere).
118
- const built = [];
119
153
  let previousDiagnostic = null;
154
+ let previousSnapshot = null;
120
155
  for (const diagnostic of diagnosticEvents) {
121
- const intervalStart = previousDiagnostic ? previousDiagnostic.seq : -Infinity;
122
- const interval = intervalEvents(taskEvents, intervalStart, diagnostic.seq);
123
-
124
- const hypothesisDispositionChanged =
125
- interval.some((event) => event.event === "HYPOTHESIS_DISPOSITION_RECORDED");
156
+ const { hypothesisDispositionChanged, novelIntervention } = intervalKnowledge(taskEvents, previousDiagnostic, diagnostic);
126
157
 
127
- // Intervention deltas recognize only genuinely NEW semantic interventions:
128
- // recorded after the previous diagnosis, attributed to the current
129
- // correction cycle, and never attempted before. Repeating an already-known
130
- // intervention — or merely having executed the previous cycle's corrective
131
- // action — is not new information.
132
- const knownUpToPrev = new Set(
133
- taskEvents
134
- .filter((event) => event.event === "INTERVENTION_RECORDED"
135
- && (previousDiagnostic ? (event.seq ?? 0) <= previousDiagnostic.seq : false))
136
- .map((event) => event.details?.interventionSemanticFingerprint
137
- ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase())
138
- .filter(Boolean),
139
- );
140
- const novelInInterval = interval
141
- .filter((event) => event.event === "INTERVENTION_RECORDED")
142
- .map((event) => event.details?.interventionSemanticFingerprint
143
- ?? `${event.details?.intervention?.statement ?? ""}`.trim().toLowerCase())
144
- .filter((fingerprint) => fingerprint && !knownUpToPrev.has(fingerprint));
145
158
  // A genuinely new intervention counts as information only when the
146
159
  // diagnosis itself moves: an identical re-proposal of the previous
147
160
  // diagnosis after executing its already-known corrective action carries
@@ -149,18 +162,18 @@ export function buildInformationGainProjection(events, taskId) {
149
162
  const sameSemanticsAsPrevious = Boolean(previousDiagnostic)
150
163
  && !snapshotHasContentDelta(previousDiagnostic.details ?? {}, diagnostic.details ?? {});
151
164
  const interventionChanged = Boolean(previousDiagnostic)
152
- && novelInInterval.length > 0
165
+ && novelIntervention
153
166
  && !sameSemanticsAsPrevious;
154
167
 
155
168
  const cycle = diagnostic.details?.verificationCycle ?? 1;
156
169
  const previousCycle = previousDiagnostic?.details?.verificationCycle ?? null;
157
- const surface = [...(failure.surfaces.get(cycle) ?? new Set())].sort();
158
- const signatures = [...(failure.signatures.get(cycle) ?? new Set())].sort();
170
+ const surface = [...(failure.surfaces.values(cycle))].sort();
171
+ const signatures = [...(failure.signatures.values(cycle))].sort();
159
172
  const previousSurface = previousCycle !== null && previousCycle !== undefined
160
- ? [...(failure.surfaces.get(previousCycle) ?? new Set())].sort()
173
+ ? [...(failure.surfaces.values(previousCycle))].sort()
161
174
  : null;
162
175
  const previousSignatures = previousCycle !== null && previousCycle !== undefined
163
- ? [...(failure.signatures.get(previousCycle) ?? new Set())].sort()
176
+ ? [...(failure.signatures.values(previousCycle))].sort()
164
177
  : null;
165
178
  const hasPreviousFailureState = previousSurface !== null;
166
179
  const failureSurfaceChanged = hasPreviousFailureState
@@ -181,7 +194,7 @@ export function buildInformationGainProjection(events, taskId) {
181
194
 
182
195
  const snapshot = snapshotFor(diagnostic);
183
196
 
184
- built.push({
197
+ const entry = {
185
198
  verificationCycle: cycle,
186
199
  sequence: diagnostic.seq ?? null,
187
200
  diagnosticSequence: diagnostic.seq ?? null,
@@ -195,62 +208,53 @@ export function buildInformationGainProjection(events, taskId) {
195
208
  failureSurfaceChanged,
196
209
  interventionChanged,
197
210
  strategyChanged,
198
- hypothesisEliminated: false,
211
+ hypothesisEliminated: eliminatedHypothesis(previousDiagnostic, diagnostic),
199
212
  },
200
213
  evidence: {
201
214
  semanticRefs: [...(snapshot.evidenceRefs ?? [])].sort(),
202
215
  surface, signatures, strategyFingerprint,
203
216
  },
204
- });
205
-
217
+ };
218
+ const dimensions = compareDiagnosticCycles(previousSnapshot, snapshot, entry.dimensionsInput);
219
+ const classification = classifyGain(dimensions, { first: previousDiagnostic === null });
220
+ const effectiveGain = isEffectiveGain(dimensions, classification);
221
+ yield finalizeGainEntry(entry, { dimensions, classification, effectiveGain });
206
222
  previousDiagnostic = diagnostic;
223
+ previousSnapshot = snapshot;
207
224
  }
225
+ }
208
226
 
209
- // Hypothesis elimination: an id disappearing only counts as elimination when
210
- // no surviving hypothesis carries the same normalized statement — ID-only
211
- // churn is artificial novelty and must never create gain.
212
- for (let i = 1; i < built.length; i++) {
213
- if (built[i].sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1"
214
- || built[i - 1].sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1") continue;
215
- const previousHypotheses = diagnosticEvents[i - 1]?.details?.hypotheses ?? [];
216
- const currentHypotheses = diagnosticEvents[i]?.details?.hypotheses ?? [];
217
- const currentStatements = new Set(currentHypotheses.map(
218
- (hypothesis) => `${hypothesis.statement}`.trim().toLowerCase()));
219
- built[i].dimensionsInput.hypothesisEliminated = previousHypotheses.some((hypothesis) => {
220
- const survivedById = currentHypotheses.some((candidate) => candidate.id === hypothesis.id);
221
- return !survivedById
222
- && !currentStatements.has(`${hypothesis.statement}`.trim().toLowerCase());
223
- });
224
- }
225
-
226
- // Final classification + single-point effectiveGain computation.
227
- const entries = computeInformationGain(built.map((entry) => ({
227
+ function finalizeGainEntry(entry, { dimensions, classification, effectiveGain }) {
228
+ return Object.freeze({
228
229
  verificationCycle: entry.verificationCycle,
229
230
  sequence: entry.sequence,
230
- snapshot: entry.snapshot,
231
- context: entry.dimensionsInput,
232
- })));
233
-
234
- return built.map((entry, index) => {
235
- const { dimensions, classification, effectiveGain } = entries[index];
236
- return Object.freeze({
237
- verificationCycle: entry.verificationCycle,
238
- sequence: entry.sequence,
239
- diagnosticSequence: entry.diagnosticSequence,
240
- sourceModel: entry.sourceModel,
241
- evidence: Object.freeze({
242
- semanticRefs: Object.freeze(entry.evidence.semanticRefs),
243
- failureSurface: Object.freeze(entry.evidence.surface),
244
- failureSignatures: Object.freeze(entry.evidence.signatures),
245
- strategyFingerprint: entry.evidence.strategyFingerprint,
246
- }),
247
- dimensions: Object.freeze({ ...dimensions }),
248
- classification,
249
- effectiveGain,
250
- });
231
+ diagnosticSequence: entry.diagnosticSequence,
232
+ sourceModel: entry.sourceModel,
233
+ evidence: Object.freeze({
234
+ semanticRefs: Object.freeze(entry.evidence.semanticRefs),
235
+ failureSurface: Object.freeze(entry.evidence.surface),
236
+ failureSignatures: Object.freeze(entry.evidence.signatures),
237
+ strategyFingerprint: entry.evidence.strategyFingerprint,
238
+ }),
239
+ dimensions: Object.freeze({ ...dimensions }),
240
+ classification,
241
+ effectiveGain,
251
242
  });
252
243
  }
253
244
 
245
+ export function buildInformationGainProjection(events, taskId) {
246
+ return [...iterateInformationGainProjection(events, taskId)];
247
+ }
248
+
249
+ /** Internal latest-only use does not retain all diagnostic result objects. */
250
+ export function summarizeInformationGain(events, taskId, { verificationCycle = null } = {}) {
251
+ let latest = null;
252
+ for (const entry of iterateInformationGainProjection(events, taskId)) {
253
+ if (verificationCycle === null || verificationCycle === undefined || entry.verificationCycle === verificationCycle) latest = entry;
254
+ }
255
+ return evaluateStructuredDiagnosticStall(latest ? [latest] : []);
256
+ }
257
+
254
258
  // One canonical structured-stall policy (fail-fast):
255
259
  // The latest comparable diagnostic state that produces no effective
256
260
  // information gain is stalled and may not trigger another blind correction
@@ -17,6 +17,8 @@ import { evaluateProgress } from "./progress.js";
17
17
  import { readEvents } from "./events.js";
18
18
  import { taskStructuralQualityDirectory } from "./task-paths.js";
19
19
  import { projectStructuralQualityStatus } from "./structural-quality/status.js";
20
+ import { operationalArtifactExists } from "../storage/operational-context.js";
21
+ import { withProjectReadSnapshot } from "../storage/project-read-snapshot.js";
20
22
 
21
23
  function reasonCodesFor({ state, trace, progress }) {
22
24
  const codes = [];
@@ -169,7 +171,15 @@ function profileMetadata(bytes) {
169
171
 
170
172
  import { taskArtifactPath } from "./task-paths.js";
171
173
 
172
- export async function inspectTarget({ target, packageRoot, contractFile = null, authorityContext, runtimeContext, taskId = null, stateFile = null } = {}) {
174
+ async function inspectStatePresence(target, relativePath) {
175
+ return operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
176
+ }
177
+
178
+ export async function inspectTarget(options = {}) {
179
+ return withProjectReadSnapshot(options.target, () => inspectSelectedTarget(options));
180
+ }
181
+
182
+ async function inspectSelectedTarget({ target, packageRoot, contractFile = null, authorityContext, runtimeContext, taskId = null, stateFile = null } = {}) {
173
183
  let manifest = null;
174
184
  let manifestError = null;
175
185
  try {
@@ -184,8 +194,7 @@ export async function inspectTarget({ target, packageRoot, contractFile = null,
184
194
  ? { ...profileMetadata(await readBytes(profilePath)), path: profileRelativePath }
185
195
  : { mode: null, status: null };
186
196
  const effectiveStateRel = stateFile ?? (taskId ? taskArtifactPath(taskId, "state") : WORK_STATE_PATH);
187
- const statePath = ensureWithin(target, effectiveStateRel);
188
- const statePresent = await fileExists(statePath);
197
+ const statePresent = await inspectStatePresence(target, effectiveStateRel);
189
198
  const classifiedState = await readAndClassifyWorkState({ target, packageRoot, contractFile, taskId, stateFile: effectiveStateRel });
190
199
  const rawState = classifiedState?.state ?? null;
191
200
  const taskInfo = taskId ? await findTaskById(target, taskId, packageRoot) : null;
@@ -1,3 +1,4 @@
1
+ import { getStorageCapabilities } from "../storage/capabilities.js";
1
2
  import { CLI_COMMAND_DEFINITIONS } from "./cli-command-definitions.js";
2
3
  import { COMMAND_EXECUTORS } from "./command-executors.js";
3
4
  import { PROTOCOL_VERSION } from "./protocol.js";
@@ -24,7 +25,8 @@ export const INTEGRATION_RISK_CLASSES = Object.freeze({
24
25
  });
25
26
 
26
27
  const READ_ONLY_COMMANDS = Object.freeze(new Set([
27
- "protocol-info", "status", "next", "continuity", "reconcile-continuity",
28
+ "storage-migration-status",
29
+ "protocol-info", "decision-status", "decision-show", "context-plan", "model-route", "semantic-plan", "test-inventory", "status", "next", "continuity", "reconcile-continuity",
28
30
  "task-list", "task-show", "task-lock-status", "progress", "audit", "report",
29
31
  "inspect", "validate-state", "validate-protocol", "validate-receipt",
30
32
  "policy-status", "policy-diff", "rule-verify", "policy",
@@ -37,7 +39,7 @@ const READ_ONLY_COMMANDS = Object.freeze(new Set([
37
39
  ]));
38
40
 
39
41
  const LOOP_MUTATION_COMMANDS = Object.freeze(new Set([
40
- "route", "preflight", "advance", "task-create", "task-scope",
42
+ "discover", "contract-create", "route", "preflight", "advance", "task-create", "task-scope",
41
43
  "record-continuity", "clear-continuity", "prepare-completion",
42
44
  "record-check", "record-diagnosis", "record-decision-criterion",
43
45
  "record-terminal-result", "complete",
@@ -69,6 +71,13 @@ const STATIC_RISK_CLASSES = Object.freeze({
69
71
  "usage-record": INTEGRATION_RISK_CLASSES.LOOP_MUTATION,
70
72
  init: INTEGRATION_RISK_CLASSES.MAINTENANCE,
71
73
  update: INTEGRATION_RISK_CLASSES.MAINTENANCE,
74
+ "storage-migration-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
75
+ "storage-rollback-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
76
+ "storage-rollback": INTEGRATION_RISK_CLASSES.MAINTENANCE,
77
+ "storage-migrate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
78
+ "storage-backup": INTEGRATION_RISK_CLASSES.MAINTENANCE,
79
+ "storage-restore": INTEGRATION_RISK_CLASSES.MAINTENANCE,
80
+ "storage-restore-resume": INTEGRATION_RISK_CLASSES.MAINTENANCE,
72
81
  activate: INTEGRATION_RISK_CLASSES.MAINTENANCE,
73
82
  "task-migrate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
74
83
  "migrate-protocol": INTEGRATION_RISK_CLASSES.MAINTENANCE,
@@ -86,7 +95,17 @@ const STATIC_RISK_CLASSES = Object.freeze({
86
95
  bundle: INTEGRATION_RISK_CLASSES.MAINTENANCE,
87
96
  "profile-interview": INTEGRATION_RISK_CLASSES.MAINTENANCE,
88
97
  "task-recover": INTEGRATION_RISK_CLASSES.CLAIM_RELEASE_RECOVERY,
98
+ "task-abandon": INTEGRATION_RISK_CLASSES.CLAIM_RELEASE_RECOVERY,
89
99
  "task-repair-legacy-recovery": INTEGRATION_RISK_CLASSES.LEGACY_MIGRATION,
100
+ "gate-record": INTEGRATION_RISK_CLASSES.MAINTENANCE,
101
+ "gate-revalidate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
102
+ "test-utility": INTEGRATION_RISK_CLASSES.MAINTENANCE,
103
+ "test-prune-plan": INTEGRATION_RISK_CLASSES.READ_ONLY,
104
+ "test-prune-probe": INTEGRATION_RISK_CLASSES.EXTERNAL_EXECUTION,
105
+ "task-repair-contract-bootstrap": INTEGRATION_RISK_CLASSES.LEGACY_MIGRATION,
106
+ "task-migrate-contract-bootstrap-repair": INTEGRATION_RISK_CLASSES.LEGACY_MIGRATION,
107
+ "checkpoint-revalidate": INTEGRATION_RISK_CLASSES.MAINTENANCE,
108
+ "contract-revise": INTEGRATION_RISK_CLASSES.MAINTENANCE,
90
109
  });
91
110
 
92
111
  // Sparse input-dependent refinements over the static table.
@@ -133,6 +152,7 @@ export function getForgeLoopCapabilities({ packageVersion = null } = {}) {
133
152
  integrationApiVersion: FORGELOOP_INTEGRATION_RUNTIME_VERSION,
134
153
  executorParity: Object.keys(COMMAND_EXECUTORS).length === Object.keys(CLI_COMMAND_DEFINITIONS).length,
135
154
  features: {
155
+ operationalStorage: getStorageCapabilities(),
136
156
  taskClaimRecovery: {
137
157
  version: 1,
138
158
  durableRecoveryState: true,
@@ -169,6 +189,18 @@ export function getForgeLoopCapabilities({ packageVersion = null } = {}) {
169
189
  commands: ["next", "task-show"],
170
190
  preservesDefaultOutput: true,
171
191
  },
192
+ auditUx: {
193
+ version: 1,
194
+ supported: true,
195
+ readOnly: true,
196
+ resource: "task/audit-view",
197
+ timeline: true,
198
+ lifecycleAuthority: false,
199
+ evidenceAuthority: false,
200
+ completionAuthority: false,
201
+ mutationAuthority: false,
202
+ externalExecution: false,
203
+ },
172
204
  usageTelemetry: {
173
205
  version: 1,
174
206
  supported: true,
@@ -295,6 +327,7 @@ export function getForgeLoopCapabilities({ packageVersion = null } = {}) {
295
327
  { name: "task/approvals", scope: "TASK" },
296
328
  { name: "task/metrics", scope: "TASK" },
297
329
  { name: "task/context", scope: "TASK" },
330
+ { name: "task/audit-view", scope: "TASK" },
298
331
  { name: "task/evaluations", scope: "TASK" },
299
332
  { name: "project/capability-policy", scope: "PROJECT" },
300
333
  { name: "repository/index-status", scope: "PROJECT" },
@@ -352,8 +385,8 @@ export function classifyForgeLoopInvocation(command, input = {}) {
352
385
  removesArtifacts: definition.removes.length > 0,
353
386
  executesExternalProcess: definition.mayExecuteExternalProcess === true,
354
387
  affectsClaimAuthority: [
355
- "task-resume", "task-recover", "task-repair-legacy-recovery",
356
- "task-create", "task-scope", "complete",
388
+ "task-resume", "task-recover", "task-repair-legacy-recovery", "task-repair-contract-bootstrap",
389
+ "task-create", "task-scope", "complete", "task-migrate-contract-bootstrap-repair",
357
390
  ].includes(command),
358
391
  destructive: [
359
392
  INTEGRATION_RISK_CLASSES.AUTHORITY_MUTATION,