@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,7 +1,9 @@
1
+ import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
2
+ import { ledgerRelationMap, ledgerRelationSet, ledgerGroupedSet } from "./ledger-relations.js";
1
3
  import { buildTaskTrace } from "./trace.js";
2
4
  import { readEvents } from "./events.js";
3
5
  import { findTaskById } from "./task-discovery.js";
4
- import { projectHypothesisStates } from "./hypothesis-projection.js";
6
+ import { projectHypothesisStates, summarizeOpenHypotheses } from "./hypothesis-projection.js";
5
7
  import {
6
8
  buildInformationGainProjection,
7
9
  evaluateStructuredDiagnosticStall,
@@ -64,11 +66,9 @@ export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
64
66
  }
65
67
 
66
68
  function failedRequirementSurfacesFromEvents(events = [], state = null) {
67
- const byCycle = new Map();
69
+ const byCycle = ledgerGroupedSet();
68
70
  const record = (cycle, requirement) => {
69
- if (!requirement) return;
70
- if (!byCycle.has(cycle)) byCycle.set(cycle, new Set());
71
- byCycle.get(cycle).add(requirement);
71
+ if (requirement) byCycle.add(cycle, requirement);
72
72
  };
73
73
  for (const event of events) {
74
74
  if (event.event !== "VERIFICATION_RECORDED") continue;
@@ -95,88 +95,83 @@ function signatureSetsByCycle(trace) {
95
95
  return byCycle;
96
96
  }
97
97
 
98
- export function deriveDiagnosticContext(events = [], state = null) {
99
- const cycle = state?.verificationCycle ?? null;
100
- const taskEvents = events.filter((event) => !state?.taskId || event.taskId === state.taskId);
101
-
102
- // Canonical failure-signature hashes for the active verification cycle.
103
- const activeFailureSignatures = [...new Set(
104
- taskEvents
105
- .filter((event) => event.event === "VERIFICATION_RECORDED"
106
- && ["failed", "blocked"].includes(event.details?.status)
107
- && (cycle === null || event.details?.verificationCycle === cycle))
108
- .map((event) => {
109
- const d = event.details;
110
- return computeFailureSignature({
111
- requirement: d.requirement ?? d.id ?? d.checkId,
112
- status: d.status,
113
- exitCode: Number.isInteger(d.exitCode) ? d.exitCode : null,
114
- failureToken: typeof d.failureToken === "string" && d.failureToken
115
- ? d.failureToken
116
- : (typeof d.details?.failureToken === "string" ? d.details.failureToken : null),
117
- });
118
- }),
119
- )].sort();
120
-
121
- const activeFailedRequirements = [...new Set(
122
- [
123
- ...taskEvents
124
- .filter((event) => event.event === "VERIFICATION_RECORDED"
125
- && ["failed", "blocked"].includes(event.details?.status)
126
- && (cycle === null || event.details?.verificationCycle === cycle))
127
- .map((event) => event.details?.requirement ?? event.details?.id ?? event.details?.checkId),
128
- ...((state?.checks ?? []))
129
- .filter((check) => ["failed", "blocked"].includes(check.status)
130
- && (cycle === null || check.details?.verificationCycle === cycle))
131
- .map((check) => check.requirement ?? check.id ?? check.checkId),
132
- ].filter(Boolean),
133
- )].sort();
98
+ function* contextEvents(events, types, taskId) {
99
+ for (const event of ledgerEventsOfTypes(events, types)) if (!taskId || event.taskId === taskId) yield event;
100
+ }
134
101
 
135
- const projection = projectHypothesisStates(taskEvents);
136
- const openHypotheses = [...projection.openHypotheses].sort();
102
+ function contextFailureSignature(details) {
103
+ return computeFailureSignature({ requirement: details.requirement ?? details.id ?? details.checkId,
104
+ status: details.status, exitCode: Number.isInteger(details.exitCode) ? details.exitCode : null,
105
+ failureToken: typeof details.failureToken === "string" && details.failureToken
106
+ ? details.failureToken : (typeof details.details?.failureToken === "string" ? details.details.failureToken : null) });
107
+ }
137
108
 
138
- const interventions = taskEvents.filter((event) => event.event === "INTERVENTION_RECORDED");
139
- const latestIntervention = interventions.at(-1)?.details?.intervention?.id ?? null;
109
+ function activeFailure(details, cycle, observedCycle) {
110
+ return ["failed", "blocked"].includes(details?.status) && (cycle === null || observedCycle === cycle);
111
+ }
140
112
 
141
- // doNotRepeat requires semantic repetition AND at least two completed
142
- // post-intervention verification cycles AND unchanged failure surface.
143
- const surfacesByCycle = failedRequirementSurfacesFromEvents(taskEvents, state);
144
- const completedCycles = [...new Set(
145
- taskEvents.filter((event) => event.event === "VERIFICATION_STARTED")
146
- .map((event) => event.details?.verificationCycle)
147
- .filter(Number.isInteger),
148
- )].sort((a, b) => a - b);
113
+ function activeContextFailures(events, state, cycle) {
114
+ const signatures = new Set();
115
+ const requirements = new Set();
116
+ for (const event of contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId)) {
117
+ const details = event.details;
118
+ if (!activeFailure(details, cycle, details?.verificationCycle)) continue;
119
+ signatures.add(contextFailureSignature(details));
120
+ const requirement = details.requirement ?? details.id ?? details.checkId;
121
+ if (requirement) requirements.add(requirement);
122
+ }
123
+ for (const check of state?.checks ?? []) {
124
+ if (!activeFailure(check, cycle, check.details?.verificationCycle)) continue;
125
+ const requirement = check.requirement ?? check.id ?? check.checkId;
126
+ if (requirement) requirements.add(requirement);
127
+ }
128
+ return { activeFailureSignatures: [...signatures].sort(), activeFailedRequirements: [...requirements].sort() };
129
+ }
149
130
 
150
- const fingerprintGroups = new Map();
151
- for (const event of interventions) {
131
+ function contextInterventions(events, taskId) {
132
+ const groups = ledgerRelationMap();
133
+ const lastCycles = ledgerRelationMap();
134
+ const invalidCycles = ledgerRelationSet();
135
+ let latestIntervention = null;
136
+ for (const event of contextEvents(events, ["INTERVENTION_RECORDED"], taskId)) {
137
+ latestIntervention = event.details?.intervention?.id ?? null;
152
138
  const fingerprint = event.details?.interventionSemanticFingerprint;
153
139
  if (!fingerprint) continue;
154
- if (!fingerprintGroups.has(fingerprint)) fingerprintGroups.set(fingerprint, []);
155
- fingerprintGroups.get(fingerprint).push(event);
140
+ groups.set(fingerprint, (groups.get(fingerprint) ?? 0) + 1);
141
+ const cycle = Number(event.details?.verificationCycle ?? 1);
142
+ if (!Number.isFinite(cycle)) invalidCycles.add(fingerprint);
143
+ else lastCycles.set(fingerprint, Math.max(lastCycles.get(fingerprint) ?? -Infinity, cycle));
156
144
  }
145
+ return { groups, lastCycles, invalidCycles, latestIntervention };
146
+ }
157
147
 
148
+ export function deriveDiagnosticContext(events = [], state = null) {
149
+ const cycle = state?.verificationCycle ?? null;
150
+ const failures = activeContextFailures(events, state, cycle);
151
+ const openHypotheses = summarizeOpenHypotheses(events, state?.taskId).sort();
152
+ const relevant = { *[Symbol.iterator]() {
153
+ yield* contextEvents(events, ["VERIFICATION_RECORDED"], state?.taskId);
154
+ } };
155
+ const surfacesByCycle = failedRequirementSurfacesFromEvents(relevant, state);
156
+ const cycles = new Set();
157
+ for (const event of contextEvents(events, ["VERIFICATION_STARTED"], state?.taskId)) {
158
+ if (Number.isInteger(event.details?.verificationCycle)) cycles.add(event.details.verificationCycle);
159
+ }
160
+ const completedCycles = [...cycles].sort((a, b) => a - b);
161
+ const { groups, lastCycles, invalidCycles, latestIntervention } = contextInterventions(events, state?.taskId);
158
162
  const doNotRepeat = [];
159
- for (const [fingerprint, group] of fingerprintGroups.entries()) {
160
- if (group.length < 2) continue;
161
- const lastInterventionCycle = Math.max(...group.map((event) => event.details?.verificationCycle ?? 1));
162
- const postCycles = completedCycles.filter((completed) => completed > lastInterventionCycle);
163
- if (postCycles.length < 2) continue;
164
- const firstSurface = [...(surfacesByCycle.get(lastInterventionCycle) ?? [])].sort();
165
- const latestSurface = [...(surfacesByCycle.get(postCycles.at(-1)) ?? [])].sort();
166
- if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) {
167
- doNotRepeat.push(fingerprint);
168
- }
163
+ for (const [fingerprint, countOfInterventions] of groups) {
164
+ if (countOfInterventions < 2 || invalidCycles.has(fingerprint)) continue;
165
+ const lastCycle = lastCycles.get(fingerprint);
166
+ let count = 0;
167
+ let latestCycle = null;
168
+ for (const completed of completedCycles) if (completed > lastCycle) { count++; latestCycle = completed; }
169
+ if (count < 2) continue;
170
+ const firstSurface = [...(surfacesByCycle.values(lastCycle))].sort();
171
+ const latestSurface = [...(surfacesByCycle.values(latestCycle))].sort();
172
+ if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) doNotRepeat.push(fingerprint);
169
173
  }
170
- doNotRepeat.sort();
171
-
172
- return {
173
- activeFailureSignatures,
174
- activeFailedRequirements,
175
- openHypotheses,
176
- latestIntervention,
177
- nextExperiment: null,
178
- doNotRepeat,
179
- };
174
+ return { ...failures, openHypotheses, latestIntervention, nextExperiment: null, doNotRepeat: doNotRepeat.sort() };
180
175
  }
181
176
 
182
177
  export async function buildTaskReflection({ target, packageRoot, taskId = null } = {}) {
@@ -10,7 +10,9 @@ import { resolveTaskClaimState } from "./task-claim-state.js";
10
10
  import { normalizeWriteClaims } from "./task-scope.js";
11
11
  import { taskResponsibilityPath } from "./task-paths.js";
12
12
  import { ensureWithin, fileExists } from "./filesystem.js";
13
- import { appendProtocolEvent } from "./events.js";
13
+ import { getOperationalStore, operationalArtifactExists } from "../storage/operational-context.js";
14
+ import { withNativeReadScope } from "./native-storage.js";
15
+ import { appendProtocolEvent, withEventLedgerAudit } from "./events.js";
14
16
  import { withTaskTransaction } from "./transaction.js";
15
17
  import { assertTaskMutationAllowed } from "./task-claim-state.js";
16
18
  import { assertWorkspaceBinding } from "./workspace-binding.js";
@@ -118,17 +120,19 @@ export async function validateResponsibilityContract(value, packageRoot = getPac
118
120
  }
119
121
 
120
122
  export async function readResponsibility(target, { taskId, packageRoot = getPackageRoot() } = {}) {
121
- const relativePath = taskResponsibilityPath(taskId);
122
- if (!(await fileExists(ensureWithin(target, relativePath)))) return null;
123
- try {
124
- const artifact = await readJsonArtifact(target, relativePath, "responsibility", packageRoot);
125
- const value = await validateResponsibilityContract(artifact.value, packageRoot);
126
- if (value.taskId !== taskId) throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility taskId does not match its task namespace", [relativePath]);
127
- return { ...artifact, value };
128
- } catch (error) {
129
- if (error.code === "E_RESPONSIBILITY_INVALID") throw error;
130
- throw responsibilityError("E_RESPONSIBILITY_INVALID", `Responsibility contract is invalid: ${error.message}`, [relativePath]);
131
- }
123
+ return withNativeReadScope(target, async () => {
124
+ const relativePath = taskResponsibilityPath(taskId);
125
+ if (!(operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath)))) return null;
126
+ try {
127
+ const artifact = await readJsonArtifact(target, relativePath, "responsibility", packageRoot);
128
+ const value = await validateResponsibilityContract(artifact.value, packageRoot);
129
+ if (value.taskId !== taskId) throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility taskId does not match its task namespace", [relativePath]);
130
+ return { ...artifact, value };
131
+ } catch (error) {
132
+ if (error.code === "E_RESPONSIBILITY_INVALID") throw error;
133
+ throw responsibilityError("E_RESPONSIBILITY_INVALID", `Responsibility contract is invalid: ${error.message}`, [relativePath]);
134
+ }
135
+ });
132
136
  }
133
137
 
134
138
  async function currentResponsibilityInputs(target, { taskId, packageRoot }) {
@@ -173,6 +177,14 @@ export function validateResponsibilityChecks(responsibility, state) {
173
177
  }
174
178
 
175
179
  export async function resolveResponsibilityStatus(target, { taskId, packageRoot = getPackageRoot() } = {}) {
180
+ const read = () => resolveLoadedResponsibilityStatus(target, { taskId, packageRoot });
181
+ return withNativeReadScope(target, () => {
182
+ if (getOperationalStore(target)) return withEventLedgerAudit(target, packageRoot, { taskId }, read);
183
+ return read();
184
+ });
185
+ }
186
+
187
+ async function resolveLoadedResponsibilityStatus(target, { taskId, packageRoot }) {
176
188
  const artifact = await readResponsibility(target, { taskId, packageRoot });
177
189
  if (!artifact) return { status: "NOT_APPLICABLE", taskId, path: taskResponsibilityPath(taskId), responsibility: null, errors: [] };
178
190
  let inputs;
@@ -245,7 +257,7 @@ export async function setResponsibilityContract(target, options = {}) {
245
257
  return withTaskTransaction({ target, taskId, packageRoot, operation: "responsibility-set", recordCommitEvent: true }, async () => {
246
258
  await assertTaskMutationAllowed(target, { taskId, packageRoot });
247
259
  await assertWorkspaceBinding(target, { taskId, packageRoot, operation: "responsibility-set" });
248
- if (await fileExists(ensureWithin(target, relativePath))) {
260
+ if (operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath))) {
249
261
  throw responsibilityError("E_RESPONSIBILITY_INVALID", "Responsibility contract is immutable during a pass", [relativePath]);
250
262
  }
251
263
  const state = await readWorkState(target, { packageRoot, taskId });
@@ -4,6 +4,12 @@ import { createWorkState, initializeWorkState, readWorkState, mutateWorkState }
4
4
 
5
5
  const DEFAULT_PENDING_STEPS = ["planning", "implementation", "verification"];
6
6
 
7
+ const CURRENT_ROUTE_CHECKPOINT_PHASES = new Set(["ROUTED"]);
8
+
9
+ export function routeCheckpointMustMatchCurrentRoute(phase) {
10
+ return CURRENT_ROUTE_CHECKPOINT_PHASES.has(phase);
11
+ }
12
+
7
13
  /**
8
14
  * Resume phase derived from the highest lifecycle milestone already recorded in
9
15
  * a validated ledger. Recreating a checkpoint at ROUTED for a task whose ledger
@@ -16,9 +22,88 @@ const RESUME_PHASE_BY_MILESTONE = Object.freeze({
16
22
  EXECUTION_STARTED: "EXECUTING",
17
23
  VERIFICATION_STARTED: "VERIFYING",
18
24
  VERIFICATION_RECORDED: "VERIFYING",
25
+ REVIEW_STARTED: "REVIEWING",
26
+ });
27
+
28
+ const RESUME_STEPS_BY_PHASE = Object.freeze({
29
+ CONTRACT_READY: {
30
+ completedSteps: ["contract"],
31
+ pendingSteps: ["route", ...DEFAULT_PENDING_STEPS],
32
+ },
33
+ ROUTED: {
34
+ completedSteps: ["contract", "route"],
35
+ pendingSteps: [...DEFAULT_PENDING_STEPS],
36
+ },
37
+ PLANNED: {
38
+ completedSteps: ["contract", "route", "planning"],
39
+ pendingSteps: [...DEFAULT_PENDING_STEPS.filter((step) => step !== "planning")],
40
+ },
41
+ EXECUTING: {
42
+ completedSteps: ["contract", "route", "planning", "implementation"],
43
+ pendingSteps: ["verification"],
44
+ },
45
+ VERIFYING: {
46
+ completedSteps: ["contract", "route", "planning", "implementation"],
47
+ pendingSteps: ["verification"],
48
+ },
49
+ REVIEWING: {
50
+ completedSteps: ["contract", "route", "planning", "implementation", "verification"],
51
+ pendingSteps: [],
52
+ },
19
53
  });
20
54
 
21
- async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
55
+ function cycleEvents(events) {
56
+ const CYCLE_EVENT_NAMES = new Set([
57
+ "VERIFICATION_STARTED",
58
+ "VERIFICATION_RECORDED",
59
+ "DIAGNOSIS_RECORDED",
60
+ "DIAGNOSTIC_CASE_RECORDED",
61
+ ]);
62
+ const cycles = events
63
+ .filter((event) => CYCLE_EVENT_NAMES.has(event.event))
64
+ .map((event) => event.details?.verificationCycle)
65
+ .filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
66
+ return cycles.at(-1);
67
+ }
68
+
69
+ /**
70
+ * Canonical reconstruction projection: for a resumed phase derived from a
71
+ * validated ledger, returns the single source of truth for completedSteps and
72
+ * pendingSteps. Unknown phases fall back to the ROUTED projection, matching the
73
+ * historical default resume checkpoint.
74
+ */
75
+ export function resumeStepsForPhase(phase) {
76
+ return RESUME_STEPS_BY_PHASE[phase] ?? RESUME_STEPS_BY_PHASE.ROUTED;
77
+ }
78
+
79
+ /**
80
+ * Canonical verification cycle derived from the ledger when history proves
81
+ * verification has started; undefined when the ledger has no cycle metadata.
82
+ */
83
+ export function resumeVerificationCycleForPhase(events) {
84
+ return cycleEvents(events);
85
+ }
86
+
87
+ /**
88
+ * Canonical work-state identity fields for reconstruction from a validated
89
+ * ledger: resumed phase, completedSteps, pendingSteps, and verificationCycle.
90
+ * This is the one projection shared by ensureResumableState and
91
+ * contract-create reconstruction so later phases cannot be reconstructed with
92
+ * contradictory steps.
93
+ */
94
+ export function buildResumableWorkStateFields({ events, resumedPhase }) {
95
+ const steps = resumeStepsForPhase(resumedPhase);
96
+ return {
97
+ phase: resumedPhase,
98
+ completedSteps: [...steps.completedSteps],
99
+ pendingSteps: [...steps.pendingSteps],
100
+ ...(events && events.length > 0 && cycleEvents(events) !== undefined
101
+ ? { verificationCycle: cycleEvents(events) }
102
+ : {}),
103
+ };
104
+ }
105
+
106
+ export async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
22
107
  let ledger;
23
108
  try {
24
109
  ledger = await validateEventLedger(target, packageRoot, { taskId });
@@ -32,71 +117,37 @@ async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
32
117
  let derived = null;
33
118
  for (const event of scoped) {
34
119
  const phase = positions[event.event];
35
- if (!phase) continue;
36
- if (!derived) {
37
- derived = phase;
38
- continue;
39
- }
40
- if (phase === "VERIFYING") derived = "VERIFYING";
120
+ if (phase) derived = phase;
41
121
  }
42
122
  return derived;
43
123
  }
44
124
 
45
- function deriveVerificationCycleFromLedger(events) {
46
- const cycleEvents = new Set([
47
- "VERIFICATION_STARTED",
48
- "VERIFICATION_RECORDED",
49
- "DIAGNOSIS_RECORDED",
50
- "DIAGNOSTIC_CASE_RECORDED",
51
- ]);
52
- const cycles = events
53
- .filter((event) => cycleEvents.has(event.event))
54
- .map((event) => event.details?.verificationCycle)
55
- .filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
56
- return cycles.at(-1);
57
- }
58
-
59
- function resumeSteps(phase) {
60
- if (phase === "EXECUTING" || phase === "VERIFYING") {
61
- return {
62
- completedSteps: ["contract", "route", "planning", "implementation"],
63
- pendingSteps: ["verification"],
64
- };
65
- }
66
- return {
67
- completedSteps: ["contract", "route"],
68
- pendingSteps: [...DEFAULT_PENDING_STEPS],
69
- };
70
- }
71
-
72
125
  export async function ensureResumableState({ target, packageRoot, contract, route, taskId, statePath }) {
73
126
  if (!contract || !route) return null;
74
127
  const existing = await readWorkState(target, { packageRoot, taskId, statePath });
75
128
  if (existing) return existing;
76
129
 
77
130
  const resumedPhase = await deriveResumePhaseFromLedger(target, packageRoot, taskId) ?? "ROUTED";
78
- let verificationCycle;
131
+ let events = [];
79
132
  try {
80
133
  const ledger = await validateEventLedger(target, packageRoot, { taskId });
81
134
  if (ledger.valid) {
82
- verificationCycle = deriveVerificationCycleFromLedger(
83
- (ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId),
84
- );
135
+ events = (ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId);
85
136
  }
86
137
  } catch {
87
- verificationCycle = undefined;
138
+ events = [];
88
139
  }
89
- const steps = resumeSteps(resumedPhase);
140
+ const projection = buildResumableWorkStateFields({ events, resumedPhase });
90
141
  const state = createWorkState({
91
142
  taskId: contract.value.taskId,
92
143
  contractFingerprint: contract.fingerprint,
93
144
  routeFingerprint: route.fingerprint,
94
145
  repositoryFingerprint: await currentRepositoryFingerprint(target),
95
- phase: resumedPhase,
146
+ phase: projection.phase,
96
147
  selectedGuides: route.value.guides,
97
- completedSteps: steps.completedSteps,
98
- pendingSteps: steps.pendingSteps,
99
- ...(verificationCycle !== undefined ? { verificationCycle } : {}),
148
+ completedSteps: projection.completedSteps,
149
+ pendingSteps: projection.pendingSteps,
150
+ ...(projection.verificationCycle !== undefined ? { verificationCycle: projection.verificationCycle } : {}),
100
151
  checks: [],
101
152
  failures: [],
102
153
  blockers: [],
@@ -105,6 +156,65 @@ export async function ensureResumableState({ target, packageRoot, contract, rout
105
156
  return initializeWorkState(target, state, { packageRoot, taskId, statePath });
106
157
  }
107
158
 
159
+ function routeSyncError(code, message) {
160
+ const error = new Error(message);
161
+ error.code = code;
162
+ return error;
163
+ }
164
+
165
+ function sameStringList(left, right) {
166
+ const a = [...(left ?? [])].sort();
167
+ const b = [...(right ?? [])].sort();
168
+ return a.length === b.length && a.every((value, index) => value === b[index]);
169
+ }
170
+
171
+ /**
172
+ * Rebinds an existing ROUTED checkpoint to an already-persisted route.
173
+ *
174
+ * Only the route-bound identity fields are updated; contract, repository,
175
+ * steps, checks, evidence, and phase are preserved. Same-identity reruns are
176
+ * a no-op. Every other situation fails closed.
177
+ */
178
+ export async function synchronizePersistedRouteState({ target, packageRoot, taskId, route, contract = null, statePath } = {}) {
179
+ if (!route || !route.value || typeof route.fingerprint !== "string") {
180
+ throw routeSyncError("E_ROUTE_STALE", "A persisted route artifact is required to synchronize checkpoint identity");
181
+ }
182
+ const state = await readWorkState(target, { packageRoot, taskId, statePath });
183
+ if (!state) return null;
184
+ if (state.phase !== "ROUTED") {
185
+ throw routeSyncError(
186
+ "E_ROUTE_PHASE_UNSUPPORTED",
187
+ `Route checkpoint synchronization supports phase ROUTED, found ${state.phase}`,
188
+ );
189
+ }
190
+ if (taskId && state.taskId !== taskId) {
191
+ throw routeSyncError("E_ROUTE_STALE", "Work state does not belong to the current route task");
192
+ }
193
+ if (contract && state.contractFingerprint !== contract.fingerprint) {
194
+ // Contract evolution with regenerated routing is a pre-existing flow whose
195
+ // stale checkpoint is recovered through the sanctioned clear-state and
196
+ // preflight recreation path. Never rebind route identity across a contract
197
+ // boundary here; leave the checkpoint untouched.
198
+ return state;
199
+ }
200
+ if (route.value.contractFingerprint !== undefined && state.contractFingerprint !== route.value.contractFingerprint) {
201
+ return state;
202
+ }
203
+ if (state.routeFingerprint === route.fingerprint && sameStringList(state.selectedGuides, route.value.guides)) {
204
+ return state;
205
+ }
206
+ return mutateWorkState(target, {
207
+ expectedRevision: state.revision ?? 0,
208
+ packageRoot,
209
+ taskId,
210
+ statePath,
211
+ }, () => ({
212
+ ...state,
213
+ routeFingerprint: route.fingerprint,
214
+ selectedGuides: [...route.value.guides],
215
+ }));
216
+ }
217
+
108
218
  export async function synchronizePreflightState({
109
219
  target,
110
220
  packageRoot,
@@ -1,17 +1,27 @@
1
1
  import { assertRouteInvariants } from "./router.js";
2
2
  import { ARTIFACT_PATHS, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
3
3
  import { readContract } from "./contract.js";
4
- import { ensureResumableState } from "./resumability.js";
4
+ import { ensureResumableState, synchronizePersistedRouteState } from "./resumability.js";
5
+ import { readWorkState } from "./work-state.js";
5
6
  import { taskArtifactPath } from "./task-paths.js";
7
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
8
+
9
+
6
10
 
7
11
  export async function persistRoute(target, route, packageRoot, options = {}) {
12
+ if (!options.taskId) return persistRouteInTransaction(target, route, packageRoot, options);
13
+ return withTaskTransaction({ target, taskId: options.taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, options));
14
+ }
15
+
16
+ async function persistRouteInTransaction(target, route, packageRoot, options) {
8
17
  assertRouteInvariants(route);
9
- const { contractFingerprint, ...writeOptions } = options;
18
+ const { contractFingerprint: suppliedFingerprint, ...writeOptions } = options;
19
+ let contractFingerprint = suppliedFingerprint;
10
20
  let contractArtifact = null;
11
- if (contractFingerprint === undefined) {
21
+ if (contractFingerprint === undefined || !options.taskId) {
12
22
  try {
13
23
  contractArtifact = await readContract(target, packageRoot, options);
14
- contractFingerprint = contractArtifact.fingerprint;
24
+ if (contractFingerprint === undefined) contractFingerprint = contractArtifact.fingerprint;
15
25
  } catch (error) {
16
26
  if (error.code !== "ARTIFACT_MISSING") throw error;
17
27
  }
@@ -21,6 +31,17 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
21
31
  : { ...route, contractFingerprint };
22
32
  assertRouteInvariants(value);
23
33
  const taskId = options.taskId ?? contractArtifact?.value?.taskId ?? null;
34
+ if (taskId && !(await getTaskTransaction(target))) {
35
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "route" }, () => persistRouteInTransaction(target, route, packageRoot, { ...options, taskId }));
36
+ }
37
+ let existingState = null;
38
+ if (taskId) {
39
+ try {
40
+ existingState = await readWorkState(target, { packageRoot, taskId });
41
+ } catch {
42
+ existingState = null;
43
+ }
44
+ }
24
45
  const relPath = options.routePath ?? options.routeFile ?? options.relativePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
25
46
  const artifact = await writeJsonArtifact(
26
47
  target,
@@ -33,6 +54,9 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
33
54
  if (contractArtifact && contractArtifact.fingerprint === artifact.value.contractFingerprint) {
34
55
  await ensureResumableState({ target, packageRoot, contract: contractArtifact, route: artifact, taskId });
35
56
  }
57
+ if (existingState?.phase === "ROUTED") {
58
+ await synchronizePersistedRouteState({ target, packageRoot, taskId, route: artifact, contract: contractArtifact });
59
+ }
36
60
  return artifact;
37
61
  }
38
62
 
@@ -313,6 +313,64 @@ function reasonForSignal(prefix, signal) {
313
313
  return `${prefix}_${signal.toUpperCase().replaceAll("-", "_")}`;
314
314
  }
315
315
 
316
+ const SECURITY_TRUST_BOUNDARY_RISKS = Object.freeze([
317
+ "untrusted-input", "personal-data", "secrets", "external-service", "publication",
318
+ ]);
319
+
320
+ // Canonical deterministic safety floor. These are the route reason codes the
321
+ // router assigns when a trust-boundary signal selects the security guide. The
322
+ // semantic exclusion policy reads protection from this same source, so a
323
+ // mandatory safety guide can never drift out of protection (for example the
324
+ // previously missing external-service boundary).
325
+ const MANDATORY_SAFETY_REASONS = Object.freeze(new Set([
326
+ "SURFACE_AUTH",
327
+ ...SECURITY_TRUST_BOUNDARY_RISKS.map((risk) => reasonForSignal("RISK", risk)),
328
+ ]));
329
+
330
+ export function isMandatorySafetyReason(reason) {
331
+ return MANDATORY_SAFETY_REASONS.has(reason);
332
+ }
333
+
334
+ export function isMandatorySafetyGuide(guide, reasons) {
335
+ return guide === "security"
336
+ && Array.isArray(reasons)
337
+ && reasons.some((reason) => isMandatorySafetyReason(reason));
338
+ }
339
+
340
+ function applySemanticGuideRecommendation(selected, excluded, recommendation) {
341
+ const guideDecision = recommendation?.guideRelevance
342
+ ?? (recommendation?.rankedIds || recommendation?.excludedIds ? recommendation : null);
343
+ if (!guideDecision) return;
344
+ const excludedIds = new Set(Array.isArray(guideDecision.excludedIds) ? guideDecision.excludedIds : []);
345
+ const confidenceById = guideDecision.confidenceById ?? {};
346
+ for (const guide of [...selected.keys()]) {
347
+ const reasons = selected.get(guide);
348
+ if (isMandatorySafetyGuide(guide, reasons)) {
349
+ if (!reasons.includes("MANDATORY_SAFETY_GUIDE")) reasons.push("MANDATORY_SAFETY_GUIDE");
350
+ continue;
351
+ }
352
+ if (!excludedIds.has(guide)) {
353
+ if (!reasons.includes("JEV_RELEVANT")) reasons.push("JEV_RELEVANT");
354
+ continue;
355
+ }
356
+ if ((confidenceById[guide] ?? 0) < 0.75) {
357
+ if (!reasons.includes("JEV_LOW_CONFIDENCE_RETAINED")) reasons.push("JEV_LOW_CONFIDENCE_RETAINED");
358
+ continue;
359
+ }
360
+ selected.delete(guide);
361
+ excluded[guide] = ["JEV_EXCLUDED"];
362
+ }
363
+ const ranked = Array.isArray(guideDecision.rankedIds) ? guideDecision.rankedIds : [];
364
+ const order = new Map(ranked.map((guide, index) => [guide, index]));
365
+ const entries = [...selected.entries()].sort((left, right) => {
366
+ const leftRank = order.has(left[0]) ? order.get(left[0]) : ranked.length + [...selected.keys()].indexOf(left[0]);
367
+ const rightRank = order.has(right[0]) ? order.get(right[0]) : ranked.length + [...selected.keys()].indexOf(right[0]);
368
+ return leftRank - rightRank;
369
+ });
370
+ selected.clear();
371
+ for (const [guide, reasons] of entries) selected.set(guide, reasons);
372
+ }
373
+
316
374
  function normalizeArray(value, name, allowed) {
317
375
  if (value === undefined) return [];
318
376
  if (!Array.isArray(value)) throw new RouteInputError(`${name} must be an array`);
@@ -440,7 +498,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
440
498
  }
441
499
 
442
500
  for (const risk of normalized.risks) {
443
- if (["untrusted-input", "personal-data", "secrets", "external-service", "publication"].includes(risk)) {
501
+ if (SECURITY_TRUST_BOUNDARY_RISKS.includes(risk)) {
444
502
  add("security", reasonForSignal("RISK", risk));
445
503
  }
446
504
  if (["critical-path", "performance"].includes(risk)) {
@@ -481,12 +539,19 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
481
539
  excluded[guide] = [exclusionReasonForGuide(guide, projectEvidence, normalized.workType)];
482
540
  }
483
541
 
542
+ applySemanticGuideRecommendation(
543
+ selected,
544
+ excluded,
545
+ profileOptions.semanticRecommendation ?? null,
546
+ );
547
+
484
548
  const guides = [...selected.keys()];
485
549
  const result = {
486
550
  schemaVersion: ROUTING_SCHEMA_VERSION,
487
551
  protocolVersion: PROTOCOL_VERSION,
488
552
  input: normalized,
489
553
  primary: Object.prototype.hasOwnProperty.call(PRIMARY_GUIDES, normalized.workType)
554
+ && guides.includes(PRIMARY_GUIDES[normalized.workType])
490
555
  ? PRIMARY_GUIDES[normalized.workType]
491
556
  : guides[0] ?? null,
492
557
  guides,
@@ -499,6 +564,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
499
564
  configuredProfile: profileOptions.configuredProfile ?? input.configuredProfile ?? "auto",
500
565
  requestedProfile: profileOptions.requestedProfile
501
566
  ?? (Object.prototype.hasOwnProperty.call(input, "executionProfile") ? input.executionProfile : null),
567
+ semanticRecommendation: profileOptions.semanticRecommendation ?? null,
502
568
  }),
503
569
  };
504
570
  return assertRouteInvariants(result);