@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
@@ -0,0 +1,138 @@
1
+ import { readdir } from "node:fs/promises";
2
+
3
+ import { canonicalFingerprint, readJsonArtifact } from "../artifacts.js";
4
+ import { fileExists, ensureWithin } from "../filesystem.js";
5
+ import { validateEventLedger } from "../events.js";
6
+ import { taskDecisionDirectory } from "../task-paths.js";
7
+ import { getQuestionSet } from "./question-registry.js";
8
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
9
+ import { DECISION_ENGINE_ID, DECISION_POLICY_VERSION, PINNED_JEV_MODEL, SEMANTIC_DECISION_RECORDED_EVENT, SEMANTIC_DECISION_SUPERSEDED_EVENT } from "./constants.js";
10
+ import { assertDecisionFresh } from "./freshness.js";
11
+ import { listOperationalArtifactNames } from "../../storage/operational-context.js";
12
+ import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
13
+
14
+ function invalid(message, code = DECISION_ERROR_CODES.BINDING_INVALID) {
15
+ return decisionError(code, message);
16
+ }
17
+
18
+ function assertArtifactIdentity(artifact, { expectedDecisionKind, expectedTaskId, expectedQuestionSetId } = {}) {
19
+ if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) throw invalid("Semantic decision artifact is not an object.");
20
+ if (artifact.taskId !== expectedTaskId) throw invalid("Semantic decision task binding is invalid.");
21
+ if (expectedDecisionKind && artifact.decisionKind !== expectedDecisionKind) throw invalid("Semantic decision kind binding is invalid.");
22
+ if (expectedQuestionSetId && artifact.questionSetId !== expectedQuestionSetId) throw invalid("Semantic decision question-set binding is invalid.");
23
+ if (artifact.engine !== DECISION_ENGINE_ID || artifact.model !== PINNED_JEV_MODEL) throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision is not bound to the pinned Jev engine and model.");
24
+ if (artifact.policyVersion !== DECISION_POLICY_VERSION) throw invalid("Semantic decision policy version is invalid.");
25
+ }
26
+
27
+ function assertArtifactShape(artifact) {
28
+ if (artifact.schemaVersion !== 1 || artifact.protocolVersion !== 1
29
+ || !artifact.answers || typeof artifact.answers !== "object" || Array.isArray(artifact.answers)
30
+ || !artifact.confidence || typeof artifact.confidence !== "object" || Array.isArray(artifact.confidence)
31
+ || !artifact.decision || typeof artifact.decision !== "object" || Array.isArray(artifact.decision)
32
+ || !artifact.usage || typeof artifact.usage !== "object" || Array.isArray(artifact.usage)) {
33
+ throw invalid("Semantic decision artifact schema is invalid.");
34
+ }
35
+ }
36
+
37
+ function assertArtifactAuthority(artifact) {
38
+ if (artifact.authority !== "SEMANTIC_DECISION" || artifact.evidenceAuthority !== "NONE"
39
+ || artifact.lifecycleAuthority !== false || artifact.completionAuthority !== false
40
+ || artifact.ownershipAuthority !== false || artifact.installationAuthority !== false) {
41
+ throw invalid("Semantic decision authority boundary is invalid.");
42
+ }
43
+ }
44
+
45
+ function assertArtifactQuestionSet(artifact) {
46
+ const questionSet = artifact.questionSet ?? getQuestionSet(artifact.questionSetId);
47
+ if (questionSet.id !== artifact.questionSetId || questionSet.decisionKind === undefined) throw invalid("Semantic decision question-set identity is invalid.");
48
+ if (artifact.questionSetVersion !== questionSet.version || artifact.questionSetFingerprint !== questionSet.fingerprint) {
49
+ throw invalid("Semantic decision question-set fingerprint is stale.");
50
+ }
51
+ if (typeof artifact.decisionId !== "string" || artifact.decisionId.length === 0) {
52
+ throw invalid("Semantic decision ID is invalid.");
53
+ }
54
+ }
55
+
56
+ function assertCanonicalArtifact(artifact, options = {}) {
57
+ assertArtifactIdentity(artifact, options);
58
+ assertArtifactShape(artifact);
59
+ assertArtifactAuthority(artifact);
60
+ assertArtifactQuestionSet(artifact);
61
+ return artifact;
62
+ }
63
+
64
+ function decisionRecordForArtifact(events, artifact) {
65
+ return events.find((event) => event.event === SEMANTIC_DECISION_RECORDED_EVENT
66
+ && event.taskId === artifact.taskId
67
+ && event.details?.decisionId === artifact.decisionId) ?? null;
68
+ }
69
+
70
+ function isSuperseded(events, artifact) {
71
+ return events.some((event) => event.event === SEMANTIC_DECISION_SUPERSEDED_EVENT
72
+ && event.taskId === artifact.taskId
73
+ && event.details?.decisionId === artifact.decisionId);
74
+ }
75
+
76
+ export function assertRequiredFreshDecision({ artifact, currentBindings = {}, expectedDecisionKind, expectedTaskId, expectedQuestionSetId, ledger = [] } = {}) {
77
+ assertCanonicalArtifact(artifact, { expectedDecisionKind, expectedTaskId, expectedQuestionSetId });
78
+ if (!Array.isArray(ledger)) throw invalid("Semantic decision ledger is unavailable.", DECISION_ERROR_CODES.LEDGER_INVALID);
79
+ const record = decisionRecordForArtifact(ledger, artifact);
80
+ if (!record || record.details.artifactFingerprint !== canonicalFingerprint(artifact)) {
81
+ throw invalid("Semantic decision has no matching canonical ledger record.", DECISION_ERROR_CODES.LEDGER_INVALID);
82
+ }
83
+ if (isSuperseded(ledger, artifact)) throw decisionError(DECISION_ERROR_CODES.STALE, "Semantic decision has been superseded.");
84
+ assertDecisionFresh(artifact, {
85
+ ...currentBindings,
86
+ model: PINNED_JEV_MODEL,
87
+ policyFingerprint: currentBindings.policyFingerprint ?? artifact.policyFingerprint,
88
+ });
89
+ return artifact;
90
+ }
91
+
92
+ async function decisionArtifacts(target, taskId, packageRoot) {
93
+ const directory = taskDecisionDirectory(taskId);
94
+ let names = listOperationalArtifactNames(target, taskId, "decisions");
95
+ if (names === null) {
96
+ const absolute = ensureWithin(target, directory);
97
+ if (!(await fileExists(absolute))) return [];
98
+ names = (await readdir(absolute, { withFileTypes: true }))
99
+ .filter(entry => entry.isFile()).map(entry => entry.name);
100
+ }
101
+ const ids = names.filter(name => name.endsWith(".json")).map(name => name.slice(0, -5)).sort();
102
+ const artifacts = [];
103
+ for (const decisionId of ids) {
104
+ try {
105
+ artifacts.push((await readJsonArtifact(target, `${directory}/${decisionId}.json`, "semantic-decision", packageRoot)).value);
106
+ } catch {
107
+ // The canonical resolver reports a missing/invalid candidate below.
108
+ }
109
+ }
110
+ return artifacts;
111
+ }
112
+
113
+ export async function resolveRequiredSemanticDecision(options = {}) {
114
+ if (typeof options.taskId !== "string" || !options.taskId) throw decisionError(DECISION_ERROR_CODES.REQUIRED, "A task-bound semantic decision is required.");
115
+ return withProjectReadSnapshot(options.target, () => resolveSelectedSemanticDecision(options));
116
+ }
117
+
118
+ async function resolveSelectedSemanticDecision({ target, packageRoot, taskId, decisionKind, decisionId = null, currentBindings = {} }) {
119
+ const ledgerResult = await validateEventLedger(target, packageRoot, { taskId });
120
+ if (!ledgerResult.valid) throw decisionError(DECISION_ERROR_CODES.LEDGER_INVALID, "The task ledger is invalid for semantic decision resolution.", ledgerResult.errors);
121
+ const events = ledgerResult.events;
122
+ const candidates = decisionId
123
+ ? [(await readJsonArtifact(target, `${taskDecisionDirectory(taskId)}/${decisionId}.json`, "semantic-decision", packageRoot)).value]
124
+ : await decisionArtifacts(target, taskId, packageRoot);
125
+ const records = events
126
+ .filter((event) => event.event === SEMANTIC_DECISION_RECORDED_EVENT && event.taskId === taskId && (!decisionKind || event.details?.decisionKind === decisionKind))
127
+ .sort((left, right) => right.seq - left.seq);
128
+ for (const record of records) {
129
+ const artifact = candidates.find((candidate) => candidate.decisionId === record.details?.decisionId);
130
+ if (!artifact || isSuperseded(events, artifact)) continue;
131
+ try {
132
+ return assertRequiredFreshDecision({ artifact, currentBindings, expectedDecisionKind: decisionKind, expectedTaskId: taskId, ledger: events });
133
+ } catch (error) {
134
+ if (decisionId) throw error;
135
+ }
136
+ }
137
+ throw decisionError(DECISION_ERROR_CODES.REQUIRED, `No fresh canonical Jev decision is available for ${decisionKind ?? "the requested operation"}.`);
138
+ }
@@ -0,0 +1,58 @@
1
+ import { PINNED_JEV_MODEL } from "./constants.js";
2
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
3
+ import { normalizeCanonicalDecision } from "./normalizers/index.js";
4
+
5
+ function finite(value) { return typeof value === "number" && Number.isFinite(value); }
6
+
7
+ function normalizeAnswer(value) {
8
+ if (value === null || typeof value === "string" || typeof value === "boolean" || finite(value)) return value;
9
+ if (value && typeof value === "object" && !Array.isArray(value)) {
10
+ const result = {};
11
+ for (const [key, child] of Object.entries(value)) result[key] = normalizeAnswer(child);
12
+ return result;
13
+ }
14
+ if (Array.isArray(value)) return value.slice(0, 64).map(normalizeAnswer);
15
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision answer has an unsupported shape.");
16
+ }
17
+
18
+ function normalizeAnswers(rawAnswers, questionSet) {
19
+ return Object.fromEntries(Object.keys(questionSet.questions).map((key) => {
20
+ if (!Object.prototype.hasOwnProperty.call(rawAnswers, key)) {
21
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, `Semantic decision result is missing answer ${key}.`);
22
+ }
23
+ return [key, normalizeAnswer(rawAnswers[key])];
24
+ }));
25
+ }
26
+
27
+ function normalizeConfidence(confidence, questionSet) {
28
+ if (!confidence || typeof confidence !== "object" || Array.isArray(confidence)) {
29
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result confidence is invalid.");
30
+ }
31
+ return Object.fromEntries(Object.keys(questionSet.questions).map((key) => [key, confidence[key] ?? null]));
32
+ }
33
+
34
+ function normalizeUsage(raw) {
35
+ const inputTokens = raw?.input_tokens ?? raw?.inputTokens;
36
+ const outputTokens = raw?.output_tokens ?? raw?.outputTokens;
37
+ if (!Number.isInteger(inputTokens) || inputTokens < 0 || !Number.isInteger(outputTokens) || outputTokens < 0) {
38
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result usage is invalid.");
39
+ }
40
+ return { inputTokens, outputTokens, reportedBy: "PROVIDER" };
41
+ }
42
+
43
+ export function normalizeDecisionResult(raw, { questionSet, input = {}, model = PINNED_JEV_MODEL } = {}) {
44
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result must be an object.");
45
+ if (raw.model !== model || raw.model !== PINNED_JEV_MODEL) throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision result used an unsupported model.");
46
+ if (!raw.answers || typeof raw.answers !== "object" || Array.isArray(raw.answers)) throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result is missing answers.");
47
+ const answers = normalizeAnswers(raw.answers, questionSet);
48
+ const confidence = normalizeConfidence(raw.confidence, questionSet);
49
+ const usage = normalizeUsage(raw.usage);
50
+ return {
51
+ model,
52
+ questionSetId: questionSet?.id ?? null,
53
+ answers,
54
+ confidence: Object.fromEntries(Object.keys(questionSet.questions).map((key) => [key, confidence[key] ?? null])),
55
+ decision: normalizeCanonicalDecision({ questionSet, answers, confidence, input }),
56
+ usage,
57
+ };
58
+ }
@@ -0,0 +1,181 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { readConfig } from "../config.js";
3
+ import { appendProtocolEvent, iterateEvents } from "../events.js";
4
+ import { withTaskMutation } from "../task-command.js";
5
+ import { DECISION_DEFAULT_POLICY, PINNED_JEV_MODEL, SEMANTIC_DECISION_RECORDED_EVENT } from "./constants.js";
6
+ import { buildDecisionArtifact, decisionArtifactFingerprint, readDecisionArtifact, writeDecisionArtifact } from "./artifact.js";
7
+ import { decisionEventDetails, decisionSupersededEventDetails } from "./events.js";
8
+ import { createTypesafeEngine } from "../../adapters/typesafe/engine.js";
9
+ import { normalizeDecisionPolicy, decisionPolicyFingerprint } from "./policy.js";
10
+ import { validateDecisionRequest } from "./request.js";
11
+ import { normalizeDecisionResult } from "./result.js";
12
+ import { randomUUID } from "node:crypto";
13
+ import { readCurrentDecisionBindings } from "./task-bindings.js";
14
+ import { resolveRequiredSemanticDecision } from "./resolver.js";
15
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
16
+ import { getTaskTransaction } from "../transaction.js";
17
+ import { getTestSemanticProvider } from "./test-provider.js";
18
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
19
+ import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
20
+
21
+ async function withDecisionScope(options, run) {
22
+ if (options.target && options.taskId && await needsExistingProjectScope(options.target)) {
23
+ return withExistingProjectScope(options.target, () => run(options));
24
+ }
25
+ return run(options);
26
+ }
27
+
28
+ function resolveProvider(provider) {
29
+ return provider ?? getTestSemanticProvider();
30
+ }
31
+
32
+ async function loadPolicy(target, packageRoot) {
33
+ try {
34
+ const config = await readConfig(target, packageRoot);
35
+ return normalizeDecisionPolicy(config.decisionEngine ?? DECISION_DEFAULT_POLICY);
36
+ } catch (error) {
37
+ if (error.code === "ARTIFACT_MISSING") return normalizeDecisionPolicy(DECISION_DEFAULT_POLICY);
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ async function readCachedDecision({ target, packageRoot, taskId, decisionKind, currentBindings }) {
43
+ try {
44
+ return await resolveRequiredSemanticDecision({ target, packageRoot, taskId, decisionKind, currentBindings });
45
+ } catch (error) {
46
+ if (["E_DECISION_REQUIRED", "E_DECISION_STALE", "E_DECISION_BINDING_INVALID"].includes(error.code)) return null;
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ function buildArtifact({ taskId, decisionId, policy, validated, result, request, taskBindings }) {
52
+ return buildDecisionArtifact({
53
+ taskId,
54
+ decisionId,
55
+ decisionKind: validated.decisionKind,
56
+ model: result.model ?? PINNED_JEV_MODEL,
57
+ questionSetId: validated.questionSet.id,
58
+ questionSetVersion: validated.questionSet.version,
59
+ questionSetFingerprint: validated.questionSet.fingerprint,
60
+ questionSet: validated.questionSet,
61
+ stateFingerprint: canonicalFingerprint(validated.state),
62
+ taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
63
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
64
+ policyVersion: policy.policyVersion,
65
+ policyFingerprint: decisionPolicyFingerprint(policy),
66
+ repositoryFingerprint: request.repositoryFingerprint ?? taskBindings.repositoryFingerprint,
67
+ contractFingerprint: request.contractFingerprint ?? taskBindings.contractFingerprint,
68
+ routeFingerprint: request.routeFingerprint ?? taskBindings.routeFingerprint,
69
+ verificationCycle: request.verificationCycle ?? taskBindings.verificationCycle,
70
+ candidateSetFingerprint: request.candidateSetFingerprint,
71
+ answers: result.answers,
72
+ confidence: result.confidence ?? {},
73
+ decision: result.decision,
74
+ usage: result.usage,
75
+ latencyMs: result.latencyMs ?? null,
76
+ });
77
+ }
78
+
79
+ async function readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request }) {
80
+ if (!policy.cache || decisionId || !target || !taskId || await getTaskTransaction(target)) return null;
81
+ return readCachedDecision({
82
+ target,
83
+ packageRoot,
84
+ taskId,
85
+ decisionKind: validated.decisionKind,
86
+ currentBindings: {
87
+ ...taskBindings,
88
+ stateFingerprint: canonicalFingerprint(validated.state),
89
+ taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
90
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
91
+ questionSetFingerprint: validated.questionSet.fingerprint,
92
+ policyFingerprint: decisionPolicyFingerprint(policy),
93
+ model: PINNED_JEV_MODEL,
94
+ ...(request?.candidateSetFingerprint !== undefined ? { candidateSetFingerprint: request.candidateSetFingerprint } : {}),
95
+ ...(request?.policyFingerprint !== undefined ? { policyFingerprint: request.policyFingerprint } : {}),
96
+ },
97
+ });
98
+ }
99
+
100
+ async function persistDecision(target, packageRoot, resolvedDecisionId, artifact, ctx) {
101
+ await writeDecisionArtifact(target, ctx.taskId, resolvedDecisionId, artifact, packageRoot, { taskId: ctx.taskId, operation: "semantic-decision" });
102
+ const priorRecords = [];
103
+ const supersededIds = new Set();
104
+ for await (const candidate of iterateEvents(target, packageRoot, { taskId: ctx.taskId })) {
105
+ if (candidate.event === SEMANTIC_DECISION_RECORDED_EVENT && candidate.taskId === ctx.taskId
106
+ && candidate.details?.decisionKind === artifact.decisionKind) priorRecords.push(candidate);
107
+ if (candidate.event === "SEMANTIC_DECISION_SUPERSEDED") supersededIds.add(candidate.details?.decisionId);
108
+ }
109
+ const event = await appendProtocolEvent(target, { taskId: ctx.taskId, event: SEMANTIC_DECISION_RECORDED_EVENT, details: decisionEventDetails(artifact) }, packageRoot, { taskId: ctx.taskId });
110
+ const previous = priorRecords.findLast(candidate => !supersededIds.has(candidate.details?.decisionId));
111
+ if (!previous) return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent: null };
112
+ const previousArtifact = await readDecisionArtifact(target, ctx.taskId, previous.details.decisionId, packageRoot);
113
+ const supersededEvent = await appendProtocolEvent(target, {
114
+ taskId: ctx.taskId,
115
+ event: "SEMANTIC_DECISION_SUPERSEDED",
116
+ details: decisionSupersededEventDetails(previousArtifact.value, resolvedDecisionId),
117
+ }, packageRoot, { taskId: ctx.taskId });
118
+ return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent };
119
+ }
120
+
121
+ export async function recordSemanticDecision(options = {}) {
122
+ return withDecisionScope(options, recordSelectedSemanticDecision);
123
+ }
124
+
125
+ async function recordSelectedSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null }) {
126
+ const resolvedDecisionId = decisionId ?? `${String(request?.decisionKind ?? "decision").toLowerCase()}-${Date.now()}-${randomUUID().slice(0, 8)}`;
127
+ const { policy, taskBindings, validated, cached } = await withProjectReadSnapshot(target, async () => {
128
+ const policy = await loadPolicy(target, packageRoot);
129
+ const taskBindings = target && taskId ? await readCurrentDecisionBindings(target, packageRoot, taskId) : {};
130
+ const validated = validateDecisionRequest({
131
+ ...request,
132
+ taskId,
133
+ state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
134
+ });
135
+ const cached = await readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request });
136
+ return { policy, taskBindings, validated, cached };
137
+ });
138
+ if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), event: null, supersededEvent: null, cached: true, policy };
139
+ const engine = resolveProvider(provider) ?? createTypesafeEngine({ policy });
140
+ if (provider && (provider.id !== "typesafe-jev" || provider.model !== PINNED_JEV_MODEL || typeof provider.evaluate !== "function")) {
141
+ throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision provider must be the pinned ForgeLoop Jev engine.");
142
+ }
143
+ const result = normalizeDecisionResult(await engine.evaluate(validated), { questionSet: validated.questionSet, input: validated });
144
+ const artifact = buildArtifact({ taskId, decisionId: resolvedDecisionId, policy, validated, result, request, taskBindings });
145
+ const persisted = await withTaskMutation(target, { taskId, packageRoot }, "semantic-decision", (ctx) => persistDecision(target, packageRoot, resolvedDecisionId, artifact, ctx), { explicitRequired: true });
146
+ return { ...persisted, policy };
147
+ }
148
+
149
+ export async function ensureSemanticDecision(options = {}) {
150
+ return withDecisionScope(options, ensureSelectedSemanticDecision);
151
+ }
152
+
153
+ async function ensureSelectedSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null, allowNetwork = true }) {
154
+ const { policy, validated, cached } = await withProjectReadSnapshot(target, async () => {
155
+ const policy = await loadPolicy(target, packageRoot);
156
+ const taskBindings = await readCurrentDecisionBindings(target, packageRoot, taskId);
157
+ const validated = validateDecisionRequest({
158
+ ...request,
159
+ taskId,
160
+ state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
161
+ });
162
+ const currentBindings = {
163
+ ...taskBindings,
164
+ stateFingerprint: canonicalFingerprint(validated.state),
165
+ taskStateFingerprint: taskBindings.taskStateFingerprint ?? canonicalFingerprint(validated.state.lifecycle),
166
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
167
+ questionSetFingerprint: validated.questionSet.fingerprint,
168
+ policyFingerprint: decisionPolicyFingerprint(policy),
169
+ model: PINNED_JEV_MODEL,
170
+ ...(request?.candidateSetFingerprint !== undefined ? { candidateSetFingerprint: request.candidateSetFingerprint } : {}),
171
+ };
172
+ const cached = await getTaskTransaction(target) ? null : await readCachedDecision({
173
+ target, packageRoot, taskId, decisionKind: validated.decisionKind,
174
+ currentBindings,
175
+ });
176
+ return { policy, validated, cached };
177
+ });
178
+ if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), cached: true, policy };
179
+ if (!allowNetwork && !provider) throw decisionError(DECISION_ERROR_CODES.REQUIRED, `A current ${validated.decisionKind} decision is required.`);
180
+ return recordSemanticDecision({ target, packageRoot, taskId, decisionId, request: { ...request, questionSet: validated.questionSet }, provider });
181
+ }
@@ -0,0 +1,65 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { DECISION_LIMITS } from "./constants.js";
3
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
4
+
5
+ const SECRET_KEY = /(^|[_-])(api[_-]?key|password|passwd|secret|token|refresh|cookie|authorization|private[_-]?key|credential|database[_-]?url|connection[_-]?string|access[_-]?key|client[_-]?secret)([_-]|$)/i;
6
+ const SECRET_VALUE = /^(?:bearer\s+)?(?:sk-|pk-|ghp_|github_pat_|xox[baprs]-|AIza|AKIA)[A-Za-z0-9._-]{12,}$/i;
7
+
8
+ function isHomePath(value) {
9
+ return typeof value === "string" && (/^\/Users\/[^/]+\//.test(value) || /^\/home\/[^/]+\//.test(value));
10
+ }
11
+
12
+ function sanitize(value, depth, counters) {
13
+ if (depth > DECISION_LIMITS.maxDepth) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state nesting exceeds the supported limit.");
14
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
15
+ if (typeof value === "string") {
16
+ if (/[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD]/u.test(value)) throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains unsafe control characters.");
17
+ if (SECRET_VALUE.test(value)) throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains a secret-like value.");
18
+ if (isHomePath(value)) return "<redacted-user-path>";
19
+ counters.bytes += Buffer.byteLength(value);
20
+ if (value.length > DECISION_LIMITS.maxStringChars) return `${value.slice(0, DECISION_LIMITS.maxStringChars)}…<truncated>`;
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ if (value.length > DECISION_LIMITS.maxItems) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state contains too many items.");
25
+ return value.map((item) => sanitize(item, depth + 1, counters));
26
+ }
27
+ if (typeof value === "object") {
28
+ const entries = Object.entries(value);
29
+ if (entries.length > DECISION_LIMITS.maxItems) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state contains too many fields.");
30
+ const result = {};
31
+ for (const [key, child] of entries) {
32
+ if (SECRET_KEY.test(key) || key.toLowerCase() === "env" || key.toLowerCase() === "environment") continue;
33
+ result[key] = sanitize(child, depth + 1, counters);
34
+ }
35
+ return result;
36
+ }
37
+ throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains an unsupported value.");
38
+ }
39
+
40
+ export function buildDecisionState(value) {
41
+ const counters = { bytes: 0 };
42
+ const sanitized = sanitize(value, 0, counters);
43
+ const bytes = Buffer.byteLength(JSON.stringify(sanitized));
44
+ if (bytes > DECISION_LIMITS.maxStateBytes) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state exceeds the supported byte limit.");
45
+ return sanitized;
46
+ }
47
+
48
+ export function buildLifecycleDecisionState(value = {}) {
49
+ return buildDecisionState(value ?? {});
50
+ }
51
+
52
+ export function buildSemanticDecisionState(value = {}) {
53
+ return buildDecisionState(value ?? {});
54
+ }
55
+
56
+ export function buildCombinedDecisionState({ lifecycle = {}, semantic = {} } = {}) {
57
+ return {
58
+ lifecycle: buildLifecycleDecisionState(lifecycle),
59
+ semantic: buildSemanticDecisionState(semantic),
60
+ };
61
+ }
62
+
63
+ export function decisionStateFingerprint(value, canonicalFingerprint) {
64
+ return canonicalFingerprint(buildDecisionState(value));
65
+ }
@@ -0,0 +1,35 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { readContract } from "../contract.js";
3
+ import { readPersistedRoute } from "../route-artifact.js";
4
+ import { readWorkState } from "../work-state.js";
5
+ import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
6
+
7
+ async function readOptionalArtifact(reader) {
8
+ try {
9
+ return await reader();
10
+ } catch (error) {
11
+ if (error.code === "ARTIFACT_MISSING") return null;
12
+ throw error;
13
+ }
14
+ }
15
+
16
+ export async function readCurrentDecisionBindings(target, packageRoot, taskId, options = {}) {
17
+ return withProjectReadSnapshot(target, () => readSnapshotBindings(target, packageRoot, taskId, options));
18
+ }
19
+
20
+ async function readSnapshotBindings(target, packageRoot, taskId, { candidateSetFingerprint, policyFingerprint }) {
21
+ const state = await readWorkState(target, { packageRoot, taskId });
22
+ const contract = await readOptionalArtifact(() => readContract(target, packageRoot, { taskId }));
23
+ const route = await readOptionalArtifact(() => readPersistedRoute(target, packageRoot, { taskId }));
24
+ return {
25
+ state,
26
+ ...(state ? { stateFingerprint: canonicalFingerprint(state) } : {}),
27
+ ...(state ? { taskStateFingerprint: canonicalFingerprint(state) } : {}),
28
+ ...(state?.repositoryFingerprint ? { repositoryFingerprint: state.repositoryFingerprint } : {}),
29
+ ...((contract?.fingerprint ?? state?.contractFingerprint) ? { contractFingerprint: contract?.fingerprint ?? state.contractFingerprint } : {}),
30
+ ...((route?.fingerprint ?? state?.routeFingerprint) ? { routeFingerprint: route?.fingerprint ?? state.routeFingerprint } : {}),
31
+ ...(state?.verificationCycle !== undefined ? { verificationCycle: state.verificationCycle } : {}),
32
+ ...(candidateSetFingerprint !== undefined ? { candidateSetFingerprint } : {}),
33
+ ...(policyFingerprint !== undefined ? { policyFingerprint } : {}),
34
+ };
35
+ }
@@ -0,0 +1,32 @@
1
+ // This provider is deliberately reachable only through repository test injection.
2
+ // Production commands always use the pinned TypeSafe Jev engine or fail closed.
3
+ export const testSemanticProvider = Object.freeze({
4
+ id: "typesafe-jev",
5
+ model: "jev-1.13.0",
6
+ async evaluate(request) {
7
+ const answers = Object.fromEntries(Object.entries(request.questionSet.questions).map(([name, question]) => [
8
+ name,
9
+ question.type === "choice" ? Object.keys(question.criteria)[0] : { noul: true },
10
+ ]));
11
+ return {
12
+ model: "jev-1.13.0",
13
+ answers,
14
+ confidence: Object.fromEntries(Object.keys(answers).map((name) => [name, 0.95])),
15
+ usage: { inputTokens: 0, outputTokens: 0 },
16
+ };
17
+ },
18
+ });
19
+
20
+ let activeTestSemanticProvider = null;
21
+
22
+ export function installTestSemanticProvider(provider = testSemanticProvider) {
23
+ activeTestSemanticProvider = provider;
24
+ }
25
+
26
+ export function clearTestSemanticProvider() {
27
+ activeTestSemanticProvider = null;
28
+ }
29
+
30
+ export function getTestSemanticProvider() {
31
+ return activeTestSemanticProvider;
32
+ }
@@ -0,0 +1,15 @@
1
+ export const DECISION_RISK_TIERS = Object.freeze(["LOW_RISK", "NORMAL", "HIGH_RISK", "CRITICAL"]);
2
+ export const DECISION_THRESHOLDS = Object.freeze({
3
+ LOW_RISK: 0.65,
4
+ NORMAL: 0.75,
5
+ HIGH_RISK: 0.9,
6
+ CRITICAL: 0.97,
7
+ });
8
+
9
+ export function decisionConfidenceThreshold(risk = "NORMAL") {
10
+ return DECISION_THRESHOLDS[risk] ?? DECISION_THRESHOLDS.NORMAL;
11
+ }
12
+
13
+ export function requiresEscalation(confidence, risk = "NORMAL") {
14
+ return typeof confidence !== "number" || confidence < decisionConfidenceThreshold(risk);
15
+ }
@@ -1,3 +1,5 @@
1
+ import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
2
+
1
3
  import { canonicalFingerprint } from "./artifacts.js";
2
4
  import { assertFailureClass } from "./protocol.js";
3
5
 
@@ -212,3 +214,12 @@ export function currentCycleDiagnosis(events, taskId, verificationCycle) {
212
214
  }
213
215
  return null;
214
216
  }
217
+
218
+
219
+ export function latestDiagnosisForTask(events, taskId) {
220
+ let latest = null;
221
+ for (const event of ledgerEventsOfTypes(events ?? [], ["DIAGNOSIS_RECORDED"])) {
222
+ if (!taskId || event.taskId === taskId) latest = event;
223
+ }
224
+ return latest;
225
+ }
@@ -1,5 +1,6 @@
1
1
  import { appendProtocolEvent, validateEventLedger } from "./events.js";
2
2
  import { readWorkState, mutateWorkState } from "./work-state.js";
3
+ import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
3
4
  import {
4
5
  DIAGNOSIS_INFORMATION_GAIN,
5
6
  assertDiagnosisDetails,
@@ -22,7 +23,15 @@ export {
22
23
  normalizeDiagnosisText,
23
24
  };
24
25
 
25
- export async function recordDiagnosis({
26
+ export async function recordDiagnosis(options) {
27
+ if (await getTaskTransaction(options.target)) return recordDiagnosisInTransaction(options);
28
+ const state = await readWorkState(options.target, { packageRoot: options.packageRoot, taskId: options.taskId, statePath: options.statePath });
29
+ if (!state) return recordDiagnosisInTransaction(options);
30
+ return withTaskTransaction({ target: options.target, taskId: options.taskId ?? state.taskId,
31
+ packageRoot: options.packageRoot, operation: "record-diagnosis" }, () => recordDiagnosisInTransaction(options));
32
+ }
33
+
34
+ async function recordDiagnosisInTransaction({
26
35
  target,
27
36
  packageRoot,
28
37
  hypothesis,
@@ -1,9 +1,17 @@
1
+ import { ledgerEventsOfTypes } from "./ledger-event-collection.js";
2
+
1
3
  export function diagnosticEventsForTask(events, taskId) {
2
4
  return (events ?? []).filter((event) =>
3
5
  (event.event === "DIAGNOSTIC_CASE_RECORDED" || event.event === "DIAGNOSIS_RECORDED")
4
6
  && (!taskId || event.taskId === taskId));
5
7
  }
6
8
 
9
+ export function* iterateDiagnosticEvents(events, taskId) {
10
+ for (const event of ledgerEventsOfTypes(events ?? [], ["DIAGNOSTIC_CASE_RECORDED", "DIAGNOSIS_RECORDED"])) {
11
+ if (!taskId || event.taskId === taskId) yield event;
12
+ }
13
+ }
14
+
7
15
  function structuredProjection(event) {
8
16
  const details = event.details ?? {};
9
17
  return {
@@ -26,20 +34,29 @@ function legacyProjection(event) {
26
34
  };
27
35
  }
28
36
 
37
+ function resolveArrayCurrentCycleDiagnostic(events, taskId, verificationCycle) {
38
+ const candidates = diagnosticEventsForTask(events, taskId).filter(event => {
39
+ const cycle = event.details?.verificationCycle;
40
+ return verificationCycle === null || verificationCycle === undefined || cycle === null || cycle === undefined || cycle === verificationCycle;
41
+ }).sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
42
+ const structured = [...candidates].reverse().find(event => event.event === "DIAGNOSTIC_CASE_RECORDED");
43
+ const legacy = [...candidates].reverse().find(event => event.event === "DIAGNOSIS_RECORDED");
44
+ return structured ? structuredProjection(structured) : (legacy ? legacyProjection(legacy) : null);
45
+ }
46
+
29
47
  export function resolveCurrentCycleDiagnostic(events, taskId, verificationCycle) {
30
- const candidates = diagnosticEventsForTask(events, taskId)
31
- .filter((event) => {
32
- const cycle = event.details?.verificationCycle;
33
- return (verificationCycle === null || verificationCycle === undefined)
34
- || (cycle === null || cycle === undefined)
35
- || cycle === verificationCycle;
36
- })
37
- .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));
38
-
39
- const structured = [...candidates].reverse().find((event) => event.event === "DIAGNOSTIC_CASE_RECORDED");
48
+ if (Array.isArray(events)) return resolveArrayCurrentCycleDiagnostic(events, taskId, verificationCycle);
49
+ let structured = null;
50
+ let legacy = null;
51
+ for (const event of iterateDiagnosticEvents(events, taskId)) {
52
+ const cycle = event.details?.verificationCycle;
53
+ if (verificationCycle !== null && verificationCycle !== undefined
54
+ && cycle !== null && cycle !== undefined && cycle !== verificationCycle) continue;
55
+ if (event.event === "DIAGNOSTIC_CASE_RECORDED") {
56
+ if (!structured || (event.seq ?? 0) >= (structured.seq ?? 0)) structured = event;
57
+ } else if (!legacy || (event.seq ?? 0) >= (legacy.seq ?? 0)) legacy = event;
58
+ }
40
59
  if (structured) return structuredProjection(structured);
41
-
42
- const legacy = [...candidates].reverse().find((event) => event.event === "DIAGNOSIS_RECORDED");
43
60
  if (legacy) return legacyProjection(legacy);
44
61
 
45
62
  return null;