@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
@@ -164,6 +164,15 @@ export function terminalRequirements(requirements = []) {
164
164
  return normalizeRequirements(requirements).filter((requirement) => requirement.terminalOwned);
165
165
  }
166
166
 
167
+ export function terminalRequirementsForContract(contractValue = {}, { additionalEvidence = [] } = {}) {
168
+ const pool = [
169
+ ...(contractValue.verification ?? []),
170
+ ...(contractValue.successCriteria ?? []),
171
+ ...additionalEvidence,
172
+ ];
173
+ return terminalRequirements(pool);
174
+ }
175
+
167
176
  export function lifecycleRequirements(requirements = []) {
168
177
  return normalizeRequirements(requirements).filter((requirement) => requirement.type === "LIFECYCLE");
169
178
  }
@@ -6,6 +6,7 @@ import { readPersistedRoute } from "./route-artifact.js";
6
6
  import { stateIdentityErrors } from "./completion-relationships.js";
7
7
  import { classifyLoadedWorkState } from "./work-state.js";
8
8
  import { taskArtifactPath } from "./task-paths.js";
9
+ import { hasCurrentGateEvidence } from "./gate-provenance.js";
9
10
 
10
11
  const START_EXECUTION_EVENTS = Object.freeze([
11
12
  "CONTRACT_VALIDATED",
@@ -59,6 +60,7 @@ async function load(loadArtifact, code, message, artifacts, errors) {
59
60
  try {
60
61
  return await loadArtifact();
61
62
  } catch (error) {
63
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
62
64
  errors.push(issue(code, `${message}: ${error.message}`, artifacts));
63
65
  return null;
64
66
  }
@@ -129,9 +131,26 @@ function prerequisiteLedgerErrors(ledger, taskId, preflight, route) {
129
131
  [ARTIFACT_PATHS.events, ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.route],
130
132
  ));
131
133
  }
134
+ const latestContractRevisionSeq = currentEvents.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
135
+ const currentEpochSatisfiedGates = new Set(preflight.requiredGates.filter((gate) => (
136
+ hasCurrentGateEvidence(currentEvents, taskId, gate, latestContractRevisionSeq)
137
+ )));
138
+ for (const gate of preflight.requiredGates) {
139
+ if (!currentEpochSatisfiedGates.has(gate)) {
140
+ errors.push(issue(
141
+ "E_PHASE_CHRONOLOGY_INVALID",
142
+ `execution started before current-contract gate satisfaction: ${gate}`,
143
+ [ARTIFACT_PATHS.events, ARTIFACT_PATHS.preflight],
144
+ ));
145
+ }
146
+ }
132
147
  return errors;
133
148
  }
134
149
 
150
+ function prerequisiteArtifactPath(explicitPath, taskId, kind) {
151
+ return explicitPath ?? (taskId ? taskArtifactPath(taskId, kind) : ARTIFACT_PATHS[kind]);
152
+ }
153
+
135
154
  export async function evaluateStartExecutionPrerequisites({
136
155
  target,
137
156
  state,
@@ -142,13 +161,14 @@ export async function evaluateStartExecutionPrerequisites({
142
161
  statePath = null,
143
162
  preflightPath = null,
144
163
  eventsPath = null,
164
+ readers = null,
145
165
  } = {}) {
146
166
  const effectiveTaskId = taskId ?? null;
147
- const contractRel = contractPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "contract") : ARTIFACT_PATHS.contract);
148
- const routeRel = routePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "route") : ARTIFACT_PATHS.route);
149
- const stateRel = statePath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "state") : ARTIFACT_PATHS.state);
150
- const preflightRel = preflightPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "preflight") : ARTIFACT_PATHS.preflight);
151
- const eventsRel = eventsPath ?? (effectiveTaskId ? taskArtifactPath(effectiveTaskId, "events") : ARTIFACT_PATHS.events);
167
+ const contractRel = prerequisiteArtifactPath(contractPath, effectiveTaskId, "contract");
168
+ const routeRel = prerequisiteArtifactPath(routePath, effectiveTaskId, "route");
169
+ const stateRel = prerequisiteArtifactPath(statePath, effectiveTaskId, "state");
170
+ const preflightRel = prerequisiteArtifactPath(preflightPath, effectiveTaskId, "preflight");
171
+ const eventsRel = prerequisiteArtifactPath(eventsPath, effectiveTaskId, "events");
152
172
 
153
173
  const errors = [];
154
174
  const requiredArtifacts = [
@@ -166,14 +186,14 @@ export async function evaluateStartExecutionPrerequisites({
166
186
  }
167
187
 
168
188
  const contract = await load(
169
- () => readContract(target, packageRoot, { taskId: effectiveTaskId, contractPath }),
189
+ () => (readers?.readContract ?? readContract)(target, packageRoot, { taskId: effectiveTaskId, contractPath }),
170
190
  "E_PHASE_PREREQUISITE_MISSING",
171
191
  `EXECUTING requires ${contractRel}`,
172
192
  [contractRel],
173
193
  errors,
174
194
  );
175
195
  const route = await load(
176
- () => readPersistedRoute(target, packageRoot, { taskId: effectiveTaskId, routePath }),
196
+ () => (readers?.readRoute ?? readPersistedRoute)(target, packageRoot, { taskId: effectiveTaskId, routePath }),
177
197
  "E_PHASE_PREREQUISITE_MISSING",
178
198
  `EXECUTING requires ${routeRel}`,
179
199
  [routeRel],
@@ -190,21 +210,24 @@ export async function evaluateStartExecutionPrerequisites({
190
210
  }
191
211
  errors.push(...stateIdentityErrors({ contract, route, state }));
192
212
 
193
- const freshness = await classifyLoadedWorkState({
213
+ const freshness = await (readers?.classifyState ?? classifyLoadedWorkState)({
194
214
  target,
195
215
  state,
196
216
  contractFile: contractRel,
197
217
  });
198
218
  errors.push(...freshnessErrors(state, freshness));
199
219
 
200
- const preflight = await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId, contractPath, routePath, statePath });
220
+ const preflight = await evaluatePreflight({ target, packageRoot, taskId: effectiveTaskId, contractPath, routePath, statePath, readers });
201
221
  let persistedPreflight = null;
222
+ let persistedPreflightReadError = null;
202
223
  try {
203
- persistedPreflight = await readJsonArtifact(target, preflightRel, "preflight", packageRoot);
204
- } catch {
224
+ persistedPreflight = await (readers?.readPreflight ?? readJsonArtifact)(target, preflightRel, "preflight", packageRoot);
225
+ } catch (error) {
226
+ if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
227
+ persistedPreflightReadError = error;
205
228
  // validatePersistedPreflight reports the stable, actionable preflight reason.
206
229
  }
207
- const persistedPreflightErrors = validatePersistedPreflight(persistedPreflight?.value, preflight);
230
+ const persistedPreflightErrors = validatePersistedPreflight(persistedPreflight?.value, preflight, persistedPreflightReadError);
208
231
  if (!sameStringSet(state.requiredGates, preflight.requiredGates)
209
232
  || !sameStringSet(state.satisfiedGates, preflight.satisfiedGates)) {
210
233
  errors.push(issue(
@@ -214,7 +237,7 @@ export async function evaluateStartExecutionPrerequisites({
214
237
  ));
215
238
  }
216
239
 
217
- const ledger = await validateEventLedger(target, packageRoot, { taskId: effectiveTaskId, eventsPath });
240
+ const ledger = await (readers?.validateLedger ?? validateEventLedger)(target, packageRoot, { taskId: effectiveTaskId, eventsPath });
218
241
  errors.push(...prerequisiteLedgerErrors(ledger, contract.value.taskId, preflight, route));
219
242
  errors.push(...validateStateLedgerCoherence(state, ledger.events).map((error) => issue(
220
243
  error.code,
@@ -162,12 +162,63 @@ export function projectExecutionProfile(route) {
162
162
  return route.executionProfile?.resolved ?? LEGACY_EXECUTION_PROFILE;
163
163
  }
164
164
 
165
+ function routeProfileSignals(normalizedRoute) {
166
+ const fullReasons = [];
167
+ const balancedReasons = [];
168
+ if (FULL_WORK_TYPES.has(normalizedRoute.workType)) fullReasons.push(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
169
+ if (BALANCED_WORK_TYPES.has(normalizedRoute.workType)) balancedReasons.push(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
170
+ for (const surface of normalizedRoute.surfaces) {
171
+ if (FULL_SURFACES.has(surface)) fullReasons.push(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
172
+ else if (BALANCED_SURFACES.has(surface)) balancedReasons.push(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
173
+ }
174
+ for (const risk of normalizedRoute.risks) {
175
+ if (FULL_RISKS.has(risk)) fullReasons.push(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
176
+ else if (BALANCED_RISKS.has(risk)) balancedReasons.push(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
177
+ }
178
+ if (normalizedRoute.behaviorChange) balancedReasons.push("WORK_BEHAVIOR_CHANGE");
179
+ if (normalizedRoute.executableChange) balancedReasons.push("WORK_EXECUTABLE_CHANGE");
180
+ return { fullReasons, balancedReasons };
181
+ }
182
+
183
+ function contractProfileSignals(textSignals, normalizedRoute) {
184
+ const fullReasons = [];
185
+ if (textSignals.secrets) fullReasons.push("RISK_SECRETS");
186
+ if (textSignals.personalData) fullReasons.push("RISK_PERSONAL_DATA");
187
+ if (textSignals.publication) fullReasons.push("RISK_PUBLICATION");
188
+ if (textSignals.destructive) fullReasons.push("RISK_DESTRUCTIVE");
189
+ if (textSignals.migration && normalizedRoute.surfaces.includes("database")) fullReasons.push("RISK_MIGRATION");
190
+ if (textSignals.payment) fullReasons.push("RISK_PAYMENT");
191
+ if (textSignals.externalMutation) fullReasons.push("RISK_EXTERNAL_MUTATION");
192
+ if (textSignals.authoritySensitiveExternalMutation) fullReasons.push("RISK_AUTHORITY_SENSITIVE_EXTERNAL_MUTATION");
193
+ if (textSignals.broadProductionValidation) fullReasons.push("RISK_PRODUCTION_VALIDATION");
194
+ return fullReasons;
195
+ }
196
+
197
+ function scopeProfileSignals(scope) {
198
+ const balancedReasons = [];
199
+ if (scope.multiDeliverable) balancedReasons.push("SCOPE_MULTI_DELIVERABLE");
200
+ if (scope.multiClaim) balancedReasons.push("SCOPE_MULTI_CLAIM");
201
+ if (scope.broad || scope.multiSurfaceExecutableChange) balancedReasons.push("SCOPE_BROAD");
202
+ return balancedReasons;
203
+ }
204
+
205
+ function profileSignals({ normalizedRoute, contract, taskDescriptor }) {
206
+ const route = routeProfileSignals(normalizedRoute);
207
+ const scope = scopeSignals({ routeInput: normalizedRoute, contract, taskDescriptor });
208
+ return {
209
+ fullReasons: [...route.fullReasons, ...contractProfileSignals(contractSignals(contract), normalizedRoute)],
210
+ balancedReasons: [...route.balancedReasons, ...scopeProfileSignals(scope)],
211
+ scope,
212
+ };
213
+ }
214
+
165
215
  export function resolveExecutionProfile({
166
216
  routeInput = {},
167
217
  contract = null,
168
218
  taskDescriptor = null,
169
219
  configuredProfile = "auto",
170
220
  requestedProfile = null,
221
+ semanticRecommendation = null,
171
222
  } = {}) {
172
223
  const normalizedRoute = normalizedRouteInput(routeInput);
173
224
  const configured = normalizeRequestedProfile(configuredProfile, "configuredProfile");
@@ -181,43 +232,7 @@ export function resolveExecutionProfile({
181
232
  else reasons.push("PROFILE_DEFAULT_AUTO");
182
233
 
183
234
  let floor = "light";
184
- const fullReasons = [];
185
- const balancedReasons = [];
186
- const addFull = (reason) => fullReasons.push(reason);
187
- const addBalanced = (reason) => balancedReasons.push(reason);
188
-
189
- if (FULL_WORK_TYPES.has(normalizedRoute.workType)) {
190
- addFull(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
191
- }
192
- if (BALANCED_WORK_TYPES.has(normalizedRoute.workType)) {
193
- addBalanced(`WORK_${normalizedRoute.workType.toUpperCase().replaceAll("-", "_")}`);
194
- }
195
- for (const surface of normalizedRoute.surfaces) {
196
- if (FULL_SURFACES.has(surface)) addFull(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
197
- else if (BALANCED_SURFACES.has(surface)) addBalanced(`SURFACE_${surface.toUpperCase().replaceAll("-", "_")}`);
198
- }
199
- for (const risk of normalizedRoute.risks) {
200
- if (FULL_RISKS.has(risk)) addFull(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
201
- else if (BALANCED_RISKS.has(risk)) addBalanced(`RISK_${risk.toUpperCase().replaceAll("-", "_")}`);
202
- }
203
- if (normalizedRoute.behaviorChange) addBalanced("WORK_BEHAVIOR_CHANGE");
204
- if (normalizedRoute.executableChange) addBalanced("WORK_EXECUTABLE_CHANGE");
205
-
206
- const textSignals = contractSignals(contract);
207
- if (textSignals.secrets) addFull("RISK_SECRETS");
208
- if (textSignals.personalData) addFull("RISK_PERSONAL_DATA");
209
- if (textSignals.publication) addFull("RISK_PUBLICATION");
210
- if (textSignals.destructive) addFull("RISK_DESTRUCTIVE");
211
- if (textSignals.migration && normalizedRoute.surfaces.includes("database")) addFull("RISK_MIGRATION");
212
- if (textSignals.payment) addFull("RISK_PAYMENT");
213
- if (textSignals.externalMutation) addFull("RISK_EXTERNAL_MUTATION");
214
- if (textSignals.authoritySensitiveExternalMutation) addFull("RISK_AUTHORITY_SENSITIVE_EXTERNAL_MUTATION");
215
- if (textSignals.broadProductionValidation) addFull("RISK_PRODUCTION_VALIDATION");
216
-
217
- const scope = scopeSignals({ routeInput: normalizedRoute, contract, taskDescriptor });
218
- if (scope.multiDeliverable) addBalanced("SCOPE_MULTI_DELIVERABLE");
219
- if (scope.multiClaim) addBalanced("SCOPE_MULTI_CLAIM");
220
- if (scope.broad || scope.multiSurfaceExecutableChange) addBalanced("SCOPE_BROAD");
235
+ const { fullReasons, balancedReasons, scope } = profileSignals({ normalizedRoute, contract, taskDescriptor });
221
236
 
222
237
  if (fullReasons.length > 0) {
223
238
  floor = "full";
@@ -247,7 +262,17 @@ export function resolveExecutionProfile({
247
262
  }
248
263
 
249
264
  const requestedRank = requested === "auto" ? PROFILE_RANK[floor] : PROFILE_RANK[requested];
250
- const resolved = EXECUTION_PROFILES.find((profile) => PROFILE_RANK[profile] === Math.max(requestedRank, PROFILE_RANK[floor]));
265
+ let resolved = EXECUTION_PROFILES.find((profile) => PROFILE_RANK[profile] === Math.max(requestedRank, PROFILE_RANK[floor]));
266
+ const semanticProfile = typeof semanticRecommendation === "string"
267
+ ? semanticRecommendation
268
+ : semanticRecommendation?.executionProfile?.recommendedProfile
269
+ ?? semanticRecommendation?.recommendedProfile
270
+ ?? null;
271
+ if (["light", "balanced", "full"].includes(semanticProfile)
272
+ && PROFILE_RANK[semanticProfile] > PROFILE_RANK[resolved]) {
273
+ resolved = semanticProfile;
274
+ reasons.push("PROFILE_ESCALATED_BY_JEV");
275
+ }
251
276
  const escalated = requested !== "auto" && PROFILE_RANK[requested] < PROFILE_RANK[floor];
252
277
  if (escalated) reasons.push("PROFILE_ESCALATED_BY_SAFETY");
253
278
 
@@ -1,12 +1,13 @@
1
- import { readdir } from "node:fs/promises";
2
- import path from "node:path";
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../storage/existing-project-scope.js";
3
2
  import { ensureWithin, fileExists } from "./filesystem.js";
4
3
  import {
5
4
  ARTIFACT_PATHS,
6
5
  executionArtifactPath,
6
+ parseCapturedJsonArtifact,
7
7
  readJsonArtifact,
8
8
  } from "./artifacts.js";
9
9
  import { taskArtifactPath, taskExecutionPath } from "./task-paths.js";
10
+ import { getOperationalStore, operationalArtifactExists } from "../storage/operational-context.js";
10
11
 
11
12
  export { E_COMMAND_RESOLUTION_AMBIGUOUS } from "./verification-capability.js";
12
13
  import {
@@ -26,18 +27,14 @@ function executionError(code, message, artifacts = []) {
26
27
  return error;
27
28
  }
28
29
 
29
- /**
30
- * Resolves where a new execution artifact should be written. Task-scoped
31
- * execution artifacts require a real modern task namespace (a task.json
32
- * descriptor). A descriptor-less task is legacy: writing task-scoped here
33
- * would create a phantom `.forgeloop/task-state/<key>/executions/` namespace
34
- * that corrupts task discovery. Reads already fall back across both
35
- * locations, so a legacy execution stays resolvable.
36
- */
30
+ /** Resolve an execution identity; persistence separately requires canonical SQLite authority. */
37
31
  export async function resolveExecutionArtifactPath(target, taskId, executionId) {
32
+ if (await needsExistingProjectScope(target)) {
33
+ return withExistingProjectScope(target, () => resolveExecutionArtifactPath(target, taskId, executionId), { readOnly: true });
34
+ }
38
35
  if (!taskId) return executionArtifactPath(executionId);
39
36
  const descriptorRel = taskArtifactPath(taskId, "descriptor");
40
- if (await fileExists(ensureWithin(target, descriptorRel))) {
37
+ if (operationalArtifactExists(target, descriptorRel) ?? await fileExists(ensureWithin(target, descriptorRel))) {
41
38
  return taskExecutionPath(taskId, executionId);
42
39
  }
43
40
  return executionArtifactPath(executionId);
@@ -87,42 +84,31 @@ export async function runCommandExecution({
87
84
  }
88
85
 
89
86
  export async function readExecutionArtifact({ target, executionRef, packageRoot, taskId } = {}) {
87
+ if (await needsExistingProjectScope(target)) {
88
+ return withExistingProjectScope(target, () => readExecutionArtifact({ target, executionRef, packageRoot, taskId }), { readOnly: true });
89
+ }
90
90
  let relativePath;
91
91
  try {
92
+ const store = getOperationalStore(target);
93
+ if (store && !taskId) {
94
+ // Validate the public reference before using it as a canonical identity.
95
+ executionArtifactPath(executionRef);
96
+ taskId = store.executionTaskId(executionRef);
97
+ }
92
98
  relativePath = taskId ? taskExecutionPath(taskId, executionRef) : executionArtifactPath(executionRef);
99
+ if (store?.recognizes(relativePath)) {
100
+ const execution = store.readExecution(taskId, executionRef);
101
+ return parseCapturedJsonArtifact(
102
+ execution === null ? null : `${JSON.stringify(execution, null, 2)}\n`,
103
+ relativePath,
104
+ "execution",
105
+ packageRoot,
106
+ );
107
+ }
93
108
  const artifact = await readJsonArtifact(target, relativePath, "execution", packageRoot);
94
109
  return artifact;
95
110
  } catch (error) {
96
111
  if (error.code === "E_EXECUTION_REF_INVALID") throw error;
97
- if (error.code === "ARTIFACT_MISSING") {
98
- if (taskId) {
99
- try {
100
- const fallbackPath = executionArtifactPath(executionRef);
101
- const artifact = await readJsonArtifact(target, fallbackPath, "execution", packageRoot);
102
- return artifact;
103
- } catch {
104
- // ignore fallback failure
105
- }
106
- } else {
107
- try {
108
- const taskStateDir = path.join(target, ".forgeloop", "task-state");
109
- if (await fileExists(taskStateDir)) {
110
- const entries = await readdir(taskStateDir, { withFileTypes: true });
111
- for (const entry of entries) {
112
- if (entry.isDirectory()) {
113
- const execFile = path.join(taskStateDir, entry.name, "executions", `${executionRef}.json`);
114
- if (await fileExists(execFile)) {
115
- const rel = path.relative(target, execFile).replaceAll("\\", "/");
116
- return await readJsonArtifact(target, rel, "execution", packageRoot);
117
- }
118
- }
119
- }
120
- }
121
- } catch {
122
- // ignore scan failure
123
- }
124
- }
125
- }
126
112
  throw executionError("E_EXECUTION_REF_INVALID", "Execution reference does not resolve to a valid ForgeLoop artifact", [relativePath ?? ARTIFACT_PATHS.executionDirectory]);
127
113
  }
128
114
  }
@@ -105,6 +105,17 @@ export function isPathWithin(root, candidate, { platform = process.platform } =
105
105
  && !pathApi.isAbsolute(relative));
106
106
  }
107
107
 
108
+ /** Recheck an opened regular file against its admitted project path before reading. */
109
+ export async function assertRegularProjectFileIdentity(file, filename, observed, root, invalid) {
110
+ const actual = await file.stat({ bigint: true });
111
+ const current = await lstat(filename, { bigint: true });
112
+ if (!actual.isFile() || !current.isFile() || current.isSymbolicLink()
113
+ || actual.dev !== observed.dev || actual.ino !== observed.ino
114
+ || current.dev !== observed.dev || current.ino !== observed.ino
115
+ || !isPathWithin(root, await realpathWithTransientWindowsRetry(filename))) throw invalid();
116
+ return actual;
117
+ }
118
+
108
119
  export async function assertSafePath(root, relativePath) {
109
120
  const destination = ensureWithin(root, relativePath);
110
121
  const absoluteRoot = path.resolve(root);
@@ -1,8 +1,13 @@
1
- import { assertSafePath, ensureWithin, fileExists, readBytes } from "./filesystem.js";
1
+ import { assertSafePath, ensureWithin, fileExists, isPathWithin, realpathWithTransientWindowsRetry } from "./filesystem.js";
2
+ import { constants } from "node:fs";
3
+ import { lstat, open } from "node:fs/promises";
2
4
  import { ARTIFACT_PATHS, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
3
5
  import { assertSchema, readSchema } from "./schema-validation.js";
4
6
  import { sha256 } from "./manifest.js";
5
7
  import { taskGatePath } from "./task-paths.js";
8
+ import { getOperationalStore, operationalArtifactExists, readOperationalText } from "../storage/operational-context.js";
9
+ import { createHash } from "node:crypto";
10
+ import { withNativeReadScope } from "./native-storage.js";
6
11
 
7
12
  function gateName(value) {
8
13
  if (typeof value !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(value)) {
@@ -32,26 +37,107 @@ export async function readGate(target, gate, packageRoot, options = {}) {
32
37
  }
33
38
 
34
39
  export async function readGateIfPresent(target, gate, packageRoot, options = {}) {
35
- const relativePath = options?.gatePath ?? (options?.taskId ? taskGatePath(options.taskId, gate) : gatePath(gate, options));
36
- await assertSafePath(target, relativePath);
37
- const filePath = ensureWithin(target, relativePath);
38
- if (!(await fileExists(filePath))) return null;
39
- return readJsonArtifact(target, relativePath, "gate", packageRoot);
40
+ return withNativeReadScope(target, async () => {
41
+ const relativePath = options?.gatePath ?? (options?.taskId ? taskGatePath(options.taskId, gate) : gatePath(gate, options));
42
+ await assertSafePath(target, relativePath);
43
+ const selected = operationalArtifactExists(target, relativePath);
44
+ if (selected !== null) return selected ? readJsonArtifact(target, relativePath, "gate", packageRoot) : null;
45
+ const filePath = ensureWithin(target, relativePath);
46
+ if (!(await fileExists(filePath))) return null;
47
+ return readJsonArtifact(target, relativePath, "gate", packageRoot);
48
+ });
40
49
  }
41
50
 
42
- export async function validateGateArtifacts(target, gateValue, packageRoot) {
43
- const schema = await readSchema("gate", packageRoot);
44
- assertSchema(gateValue, schema, "gate");
45
- const stale = [];
46
- for (const artifact of gateValue.artifacts ?? []) {
47
- await assertSafePath(target, artifact.path);
48
- const artifactPath = ensureWithin(target, artifact.path);
49
- if (!(await fileExists(artifactPath))) {
50
- stale.push({ path: artifact.path, status: "missing" });
51
- continue;
51
+ /** Hash selected bytes through the operational read set, including missing rows. */
52
+ export function readCanonicalGateArtifactBinding(target, relativePath) {
53
+ const store = getOperationalStore(target);
54
+ if (store?.recognizes(relativePath) && relativePath.replaceAll("\\", "/").endsWith("/events.ndjson")) {
55
+ if (!operationalArtifactExists(target, relativePath)) return { path: relativePath, sha256: null, byteLength: 0 };
56
+ const digest = createHash("sha256");
57
+ let byteLength = 0;
58
+ for (const event of store.iterateEvents(relativePath)) {
59
+ const bytes = `${JSON.stringify(event)}\n`;
60
+ byteLength += Buffer.byteLength(bytes);
61
+ digest.update(bytes);
52
62
  }
53
- const digest = sha256(await readBytes(artifactPath));
54
- if (digest !== artifact.sha256) stale.push({ path: artifact.path, status: "changed" });
63
+ return { path: relativePath, sha256: digest.digest("hex"), byteLength };
55
64
  }
56
- return stale;
65
+ const source = readOperationalText(target, relativePath);
66
+ if (!source.selected) return null;
67
+ return { path: relativePath, sha256: source.text === null ? null : sha256(source.text), byteLength: source.text === null ? 0 : Buffer.byteLength(source.text) };
68
+ }
69
+
70
+ /** Admit one regular external evidence handle before reading any bytes. */
71
+ async function withGateFile(target, relativePath, maxBytes, callback) {
72
+ const filename = await assertSafePath(target, relativePath);
73
+ const root = await realpathWithTransientWindowsRetry(target);
74
+ const resolved = await realpathWithTransientWindowsRetry(filename);
75
+ if (!isPathWithin(root, resolved)) throw new Error("Gate artifact resolves outside the project");
76
+ const file = await open(resolved, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
77
+ try {
78
+ const info = await file.stat();
79
+ const current = await lstat(resolved);
80
+ if (!info.isFile() || !current.isFile() || current.isSymbolicLink()
81
+ || info.dev !== current.dev || info.ino !== current.ino
82
+ || !isPathWithin(root, await realpathWithTransientWindowsRetry(resolved))) throw new Error("Gate artifact changed during file admission");
83
+ if (info.size > maxBytes) throw new Error(`Gate artifact exceeds the maximum size of ${maxBytes} bytes`);
84
+ return await callback(file);
85
+ } finally { await file.close(); }
86
+ }
87
+
88
+ async function* gateFileChunks(file, maxBytes) {
89
+ let byteLength = 0;
90
+ for await (const bytes of file.createReadStream({ autoClose: false, highWaterMark: 64 * 1024 })) {
91
+ byteLength += bytes.length;
92
+ if (byteLength > maxBytes) throw new Error(`Gate artifact exceeds the maximum size of ${maxBytes} bytes`);
93
+ yield bytes;
94
+ }
95
+ }
96
+
97
+ /** Hash external evidence from one verified handle, with bounded streaming memory. */
98
+ export async function hashGateFile(target, relativePath, { maxBytes = Infinity } = {}) {
99
+ return withGateFile(target, relativePath, maxBytes, async file => {
100
+ const digest = createHash("sha256");
101
+ let byteLength = 0;
102
+ for await (const bytes of gateFileChunks(file, maxBytes)) {
103
+ byteLength += bytes.length;
104
+ digest.update(bytes);
105
+ }
106
+ return { path: relativePath, sha256: digest.digest("hex"), byteLength };
107
+ });
108
+ }
109
+
110
+ /** Explicit JSON gate input remains file-owned and allocation-bounded. */
111
+ export async function readGateFileJson(target, relativePath, { maxBytes } = {}) {
112
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) throw new Error("Gate evidence requires a finite byte limit");
113
+ return withGateFile(target, relativePath, maxBytes, async file => {
114
+ const chunks = [];
115
+ for await (const bytes of gateFileChunks(file, maxBytes)) chunks.push(bytes);
116
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
117
+ });
118
+ }
119
+
120
+ export async function validateGateArtifacts(target, gateValue, packageRoot) {
121
+ return withNativeReadScope(target, async () => {
122
+ const schema = await readSchema("gate", packageRoot);
123
+ assertSchema(gateValue, schema, "gate");
124
+ const stale = [];
125
+ for (const artifact of gateValue.artifacts ?? []) {
126
+ await assertSafePath(target, artifact.path);
127
+ const canonical = readCanonicalGateArtifactBinding(target, artifact.path);
128
+ if (canonical) {
129
+ if (canonical.sha256 === null) stale.push({ path: artifact.path, status: "missing" });
130
+ else if (canonical.sha256 !== artifact.sha256) stale.push({ path: artifact.path, status: "changed" });
131
+ continue;
132
+ }
133
+ const artifactPath = ensureWithin(target, artifact.path);
134
+ if (!(await fileExists(artifactPath))) {
135
+ stale.push({ path: artifact.path, status: "missing" });
136
+ continue;
137
+ }
138
+ const binding = await hashGateFile(target, artifact.path);
139
+ if (binding.sha256 !== artifact.sha256) stale.push({ path: artifact.path, status: "changed" });
140
+ }
141
+ return stale;
142
+ });
57
143
  }
@@ -0,0 +1,125 @@
1
+ import { isLedgerEventCollection, ledgerEventAt, ledgerEntriesOfTypes } from "./ledger-event-collection.js";
2
+ import { canonicalFingerprint } from "./artifacts.js";
3
+
4
+ export const GATE_SATISFIED_EVENT = "GATE_SATISFIED";
5
+ export const GATE_REVALIDATED_EVENT = "GATE_REVALIDATED";
6
+ const CONTRACT_REVISED_EVENT = "CONTRACT_REVISED";
7
+ const TRANSACTION_COMMITTED_EVENT = "TRANSACTION_COMMITTED";
8
+
9
+ function eventHash(event) {
10
+ const { hash, ...body } = event ?? {};
11
+ return canonicalFingerprint(body);
12
+ }
13
+
14
+ export function assertGateSatisfiedDetails(details) {
15
+ if (!details || typeof details !== "object" || Array.isArray(details)
16
+ || Object.keys(details).length !== 1
17
+ || typeof details.gate !== "string"
18
+ || details.gate.trim().length === 0) {
19
+ const error = new Error("GATE_SATISFIED requires exactly one non-empty gate detail");
20
+ error.code = "E_EVENT_INVALID";
21
+ throw error;
22
+ }
23
+ return details;
24
+ }
25
+
26
+ export function assertGateRevalidatedDetails(details) {
27
+ const keys = ["gate", "previousGateFingerprint", "gateFingerprint", "stalePaths"];
28
+ if (!details || typeof details !== "object" || Array.isArray(details)
29
+ || Object.keys(details).length !== keys.length
30
+ || keys.some((key) => !Object.prototype.hasOwnProperty.call(details, key))
31
+ || typeof details.gate !== "string"
32
+ || details.gate.trim().length === 0
33
+ || !/^[a-z0-9][a-z0-9-]*$/.test(details.gate)
34
+ || !/^[a-f0-9]{64}$/.test(details.previousGateFingerprint)
35
+ || !/^[a-f0-9]{64}$/.test(details.gateFingerprint)
36
+ || !Array.isArray(details.stalePaths)
37
+ || details.stalePaths.length === 0
38
+ || details.stalePaths.some((value) => typeof value !== "string" || value.trim().length === 0)
39
+ || [...details.stalePaths].sort().join("\0") !== details.stalePaths.join("\0")
40
+ || new Set(details.stalePaths).size !== details.stalePaths.length) {
41
+ const error = new Error("GATE_REVALIDATED requires a gate, two fingerprints, and sorted stale paths");
42
+ error.code = "E_GATE_REVALIDATION_INVALID";
43
+ throw error;
44
+ }
45
+ return details;
46
+ }
47
+
48
+ function gateIndex(events, gateEvent, eventName) {
49
+ return events.findIndex((event) => event === gateEvent
50
+ || (event?.seq === gateEvent?.seq
51
+ && event?.taskId === gateEvent?.taskId
52
+ && event?.event === eventName));
53
+ }
54
+
55
+ function isValidGateCommit(gateEvent, commitEvent, operation) {
56
+ return commitEvent?.event === TRANSACTION_COMMITTED_EVENT
57
+ && commitEvent.taskId === gateEvent.taskId
58
+ && commitEvent.seq === gateEvent.seq + 1
59
+ && commitEvent.previousHash === gateEvent.hash
60
+ && gateEvent.hash === eventHash(gateEvent)
61
+ && commitEvent.hash === eventHash(commitEvent)
62
+ && commitEvent.details
63
+ && typeof commitEvent.details === "object"
64
+ && !Array.isArray(commitEvent.details)
65
+ && Object.keys(commitEvent.details).length === 2
66
+ && typeof commitEvent.details.transactionId === "string"
67
+ && commitEvent.details.transactionId.length > 0
68
+ && commitEvent.details.operation === operation;
69
+ }
70
+
71
+ export function resolveGateRecordBoundary(events, gateEvent) {
72
+ if (!isLedgerEventCollection(events) || !gateEvent || gateEvent.event !== GATE_SATISFIED_EVENT) return null;
73
+ const index = gateIndex(events, gateEvent, GATE_SATISFIED_EVENT);
74
+ if (index < 0) return null;
75
+ const commitEvent = ledgerEventAt(events, index + 1);
76
+ return isValidGateCommit(gateEvent, commitEvent, "gate-record")
77
+ ? { gateEvent, transactionCommitEvent: commitEvent }
78
+ : null;
79
+ }
80
+
81
+ export function resolveGateRevalidationBoundary(events, gateEvent) {
82
+ if (!isLedgerEventCollection(events) || !gateEvent || gateEvent.event !== GATE_REVALIDATED_EVENT) return null;
83
+ const index = gateIndex(events, gateEvent, GATE_REVALIDATED_EVENT);
84
+ if (index < 0) return null;
85
+ const commitEvent = ledgerEventAt(events, index + 1);
86
+ return isValidGateCommit(gateEvent, commitEvent, "gate-revalidate")
87
+ ? { gateEvent, transactionCommitEvent: commitEvent }
88
+ : null;
89
+ }
90
+
91
+ export function hasCurrentGateEvidence(events, taskId, gate, latestContractRevisionSeq = 0) {
92
+ return events.some((event) => [GATE_SATISFIED_EVENT, GATE_REVALIDATED_EVENT].includes(event.event)
93
+ && event.taskId === taskId
94
+ && event.details?.gate === gate
95
+ && event.seq > latestContractRevisionSeq);
96
+ }
97
+
98
+ export function validateGateSatisfactionBindings(events = []) {
99
+ const errors = [];
100
+ for (const [index, event] of ledgerEntriesOfTypes(events, [GATE_SATISFIED_EVENT, GATE_REVALIDATED_EVENT])) {
101
+ if (![GATE_SATISFIED_EVENT, GATE_REVALIDATED_EVENT].includes(event.event)) continue;
102
+ const revalidated = event.event === GATE_REVALIDATED_EVENT;
103
+ try {
104
+ if (revalidated) assertGateRevalidatedDetails(event.details);
105
+ else assertGateSatisfiedDetails(event.details);
106
+ } catch (error) {
107
+ errors.push({ code: error.code ?? "E_EVENT_INVALID", message: `event ${event.seq} (${event.event}): ${error.message}` });
108
+ continue;
109
+ }
110
+ const revisedBeforeGate = events.slice(0, index).findLast((candidate) => candidate.taskId === event.taskId
111
+ && candidate.event === CONTRACT_REVISED_EVENT);
112
+ const boundary = revalidated
113
+ ? resolveGateRevalidationBoundary(events, event)
114
+ : resolveGateRecordBoundary(events, event);
115
+ if ((revalidated || revisedBeforeGate) && !boundary) {
116
+ errors.push({
117
+ code: revalidated ? "E_GATE_REVALIDATION_INVALID" : "E_EVENT_INVALID",
118
+ message: revalidated
119
+ ? `event ${event.seq} must be immediately followed by its gate-revalidate transaction commit`
120
+ : `event ${event.seq} must be immediately followed by its gate-record transaction commit after contract revision`,
121
+ });
122
+ }
123
+ }
124
+ return errors;
125
+ }