@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,284 @@
1
+ import { PORTABLE_ARTIFACT_MAPPINGS } from "../core/artifact-registry.js";
2
+ import { artifactByteDigest } from "./artifact-bytes.js";
3
+ import { canonicalFingerprint } from "../core/artifacts.js";
4
+ import { withStorageSnapshot } from "./snapshot.js";
5
+ import { assertPortableExportDestination } from "./export-destination.js";
6
+ import { mkdir } from "node:fs/promises";
7
+ import { createHash } from "node:crypto";
8
+ import { createReadStream } from "node:fs";
9
+ import { iterateAttachmentReferences } from "./attachment-references.js";
10
+ import { publishAttachmentFile, verifyAttachmentFile, withVerifiedAttachmentFile } from "./attachment-files.js";
11
+ import { validateAttachmentReference } from "./attachment-binding.js";
12
+ import { STORAGE_CATALOG_LIMITS } from "./metadata-json.js";
13
+ import { assertJsonBytes, assertJsonLimits } from "../core/json-safety.js";
14
+ import { legacyAttachmentReferenceId, signatureReferencePrefix } from "../core/signing/bundle-reference.js";
15
+
16
+ import { assertSafePath, writeFileAtomic } from "../core/filesystem.js";
17
+ import { taskStorageKey } from "../core/task-identity.js";
18
+ import { TASK_STATE_ROOT } from "../core/task-paths.js";
19
+ import {
20
+ CURRENT_ARTIFACT_ID,
21
+ findTaskById,
22
+ iterateActions,
23
+ iterateApprovals,
24
+ iterateArtifacts,
25
+ iterateClaims,
26
+ iterateCanonicalEvents,
27
+ iterateExecutions,
28
+ } from "./repository.js";
29
+
30
+ /**
31
+ * Deterministic store -> filesystem export.
32
+ *
33
+ * Export is an interchange operation, not a mirror of live state. It runs from
34
+ * a consistent read snapshot, emits events in sequence order, and writes a
35
+ * manifest that binds the included artifacts so a consumer can tell exactly
36
+ * what a portable bundle contains. Export failure is reported independently of
37
+ * any state mutation.
38
+ */
39
+
40
+ const ARTIFACT_FILENAMES = Object.freeze(Object.fromEntries(
41
+ PORTABLE_ARTIFACT_MAPPINGS.singletons.map(({ kind, filename }) => [kind, filename]),
42
+ ));
43
+ const ARTIFACT_DIRECTORIES = Object.freeze(Object.fromEntries(
44
+ PORTABLE_ARTIFACT_MAPPINGS.collections.map(({ kind, directory }) => [kind, directory]),
45
+ ));
46
+
47
+ function serialize(value) {
48
+ return `${JSON.stringify(value, null, 2)}\n`;
49
+ }
50
+
51
+ function portableId(id) {
52
+ if (typeof id !== "string" || !id || id === "." || id === ".." || /[\\/\u0000]/u.test(id)) {
53
+ const error = new Error("Stored artifact identity cannot be exported as a portable filename");
54
+ error.code = "E_STORAGE_EXPORT_IDENTITY";
55
+ throw error;
56
+ }
57
+ return id;
58
+ }
59
+
60
+ function portableArtifactId(kind, id) {
61
+ if (kind !== "attestation" && kind !== "structuralQuality") return portableId(id);
62
+ if (typeof id !== "string" || id.split("/").length > 5) return portableId(id);
63
+ return id.split("/").map(portableId).join("/");
64
+ }
65
+
66
+ /**
67
+ * Export one task into `destination`, which is a task-state root. The layout
68
+ * matches `TASK_STATE_ROOT` so an exported bundle can be re-imported by the
69
+ * same importer without a translation step.
70
+ */
71
+ export async function exportTask(db, taskId, destination) {
72
+ await assertPortableExportDestination(destination);
73
+ return withStorageSnapshot(db, snapshot => exportTaskSnapshot(snapshot, taskId, destination));
74
+ }
75
+
76
+ async function exportTaskSnapshot(db, taskId, destination) {
77
+ const task = findTaskById(db, taskId);
78
+ if (!task) {
79
+ const error = new Error(`Unknown task: ${taskId}`);
80
+ error.code = "E_TASK_NOT_FOUND";
81
+ throw error;
82
+ }
83
+ if (task.taskKey !== taskStorageKey(taskId)) {
84
+ const error = new Error("Stored task key disagrees with its canonical identity");
85
+ error.code = "E_STORAGE_EXPORT_IDENTITY";
86
+ throw error;
87
+ }
88
+
89
+ await mkdir(destination, { recursive: true });
90
+ const directory = await assertSafePath(destination, task.taskKey);
91
+ await mkdir(directory, { recursive: true });
92
+ const files = [];
93
+ let manifestBytes = 4096;
94
+ function reserveFile(name) {
95
+ const entry = { path: name, size: Number.MAX_SAFE_INTEGER, sha256: "0".repeat(64) };
96
+ assertJsonLimits(entry, "export manifest entry", STORAGE_CATALOG_LIMITS);
97
+ const projectedBytes = manifestBytes + Buffer.byteLength(serialize(entry)) + Buffer.byteLength(serialize(name)) + 128;
98
+ assertJsonBytes({ byteLength: projectedBytes }, "export manifest", STORAGE_CATALOG_LIMITS);
99
+ if (files.length >= STORAGE_CATALOG_LIMITS.maxArrayLength) {
100
+ throw Object.assign(new Error("Export manifest exceeds its array entry limit"), { code: "JSON_LIMIT_EXCEEDED" });
101
+ }
102
+ manifestBytes = projectedBytes;
103
+ }
104
+ async function publish(name, bytes, { manifest = false } = {}) {
105
+ if (!manifest) reserveFile(name);
106
+ const filename = await assertSafePath(directory, name);
107
+ const digest = createHash("sha256");
108
+ let size = 0;
109
+ async function* countedBytes() {
110
+ const chunks = typeof bytes === "string" ? [bytes] : bytes;
111
+ for await (const chunk of chunks) {
112
+ digest.update(chunk);
113
+ size += Buffer.byteLength(chunk);
114
+ yield chunk;
115
+ }
116
+ }
117
+ await writeFileAtomic(filename, countedBytes());
118
+ if (!manifest) files.push({ path: name, size, sha256: digest.digest("hex") });
119
+ }
120
+
121
+ await publish("task.json", serialize(task.descriptor));
122
+ if (task.state) {
123
+ await publish("work-state.json", serialize(task.state));
124
+ }
125
+
126
+ // Events are emitted in sequence order with one object per line so a
127
+ // re-import reproduces byte-identical ledger semantics.
128
+ let eventCount = 0;
129
+ async function* ledgerBytes() {
130
+ for (const event of iterateCanonicalEvents(db, taskId)) {
131
+ eventCount += 1;
132
+ yield `${JSON.stringify(event)}\n`;
133
+ }
134
+ }
135
+ await publish("events.ndjson", ledgerBytes());
136
+
137
+ for (const artifact of iterateArtifacts(db, taskId)) {
138
+ // Runtime reservations are coordination state, not portable task evidence.
139
+ if (artifact.kind === "operationLease") continue;
140
+ if (artifact.fingerprint !== canonicalFingerprint(artifact.payload)) throw Object.assign(new Error("Stored artifact fingerprint disagrees with its payload"), { code: "E_STORAGE_ARTIFACT_INVALID" });
141
+ if (artifact.sourceText !== null || artifact.byteDigest !== null) artifactByteDigest(artifact);
142
+ const filename = ARTIFACT_FILENAMES[artifact.kind];
143
+ if (filename) {
144
+ // Single-file kinds hold a replaceable current snapshot.
145
+ if (artifact.artifactId !== CURRENT_ARTIFACT_ID) continue;
146
+ await publish(filename, artifact.sourceText ?? serialize(artifact.payload));
147
+ continue;
148
+ }
149
+ const subdirectory = ARTIFACT_DIRECTORIES[artifact.kind];
150
+ if (!subdirectory) {
151
+ const error = new Error(`No portable mapping exists for stored artifact kind: ${artifact.kind}`);
152
+ error.code = "E_STORAGE_EXPORT_UNSUPPORTED";
153
+ throw error;
154
+ }
155
+ const name = `${portableArtifactId(artifact.kind, artifact.artifactId)}.json`;
156
+ await publish(`${subdirectory}/${name}`, artifact.sourceText ?? serialize(artifact.payload));
157
+ }
158
+
159
+ for (const [records, subdirectory, identityField] of [
160
+ [iterateActions(db, taskId), "actions", "actionId"],
161
+ [iterateApprovals(db, taskId), "approvals", "approvalId"],
162
+ [iterateExecutions(db, taskId), "executions", "executionId"],
163
+ ]) {
164
+ for (const record of records) {
165
+ const id = record[identityField];
166
+ const name = `${portableId(id)}.json`;
167
+ await publish(`${subdirectory}/${name}`, serialize(record));
168
+ }
169
+ }
170
+
171
+ const claims = [];
172
+ for (const claim of iterateClaims(db, taskId)) {
173
+ assertJsonLimits(claim.claim_norm, "export claim", STORAGE_CATALOG_LIMITS);
174
+ const projectedBytes = manifestBytes + Buffer.byteLength(serialize(claim.claim_norm)) + 128;
175
+ assertJsonBytes({ byteLength: projectedBytes }, "export manifest", STORAGE_CATALOG_LIMITS);
176
+ if (claims.length >= STORAGE_CATALOG_LIMITS.maxArrayLength) {
177
+ throw Object.assign(new Error("Export claims exceed the array entry limit"), { code: "JSON_LIMIT_EXCEEDED" });
178
+ }
179
+ manifestBytes = projectedBytes;
180
+ claims.push(claim.claim_norm);
181
+ }
182
+ const manifest = {
183
+ schemaVersion: 1,
184
+ taskId,
185
+ taskKey: task.taskKey,
186
+ events: eventCount,
187
+ claims,
188
+ included: files.map(file => file.path).sort(),
189
+ files: files.sort((left, right) => left.path.localeCompare(right.path, "en")),
190
+ };
191
+ assertJsonLimits(manifest, "export manifest", STORAGE_CATALOG_LIMITS);
192
+ const manifestText = serialize(manifest);
193
+ assertJsonBytes(manifestText, "export manifest", STORAGE_CATALOG_LIMITS);
194
+ await publish("export-manifest.json", manifestText, { manifest: true });
195
+
196
+ return { taskId, taskKey: task.taskKey, directory, events: eventCount, manifest };
197
+ }
198
+
199
+ /**
200
+ * Export every task in the store. Each task is written from the same read
201
+ * snapshot so a bundle is internally consistent. The bundle index sits beside
202
+ * the task-state root rather than inside it, so the exported directory is a
203
+ * valid re-importable project state.
204
+ */
205
+ export async function exportDatabase(db, destination, { attachmentRoot } = {}) {
206
+ await assertPortableExportDestination(destination);
207
+ return withStorageSnapshot(db, snapshot => exportDatabaseSnapshot(snapshot, destination, attachmentRoot));
208
+ }
209
+
210
+ async function exportDatabaseSnapshot(db, destination, attachmentRoot) {
211
+ await mkdir(destination, { recursive: true });
212
+ const taskStateRoot = await assertSafePath(destination, TASK_STATE_ROOT);
213
+ await mkdir(taskStateRoot, { recursive: true });
214
+ const index = {
215
+ schemaVersion: 1,
216
+ tasks: [],
217
+ sessions: [],
218
+ activeSessionId: null,
219
+ attachments: [],
220
+ };
221
+ let catalogBytes = 4096;
222
+ function appendCatalogEntry(entries, value) {
223
+ assertJsonLimits(value, "export catalog entry", STORAGE_CATALOG_LIMITS);
224
+ const size = Buffer.byteLength(serialize(value)) + 128;
225
+ assertJsonBytes({ byteLength: catalogBytes + size }, "export catalog", STORAGE_CATALOG_LIMITS);
226
+ if (entries.length >= STORAGE_CATALOG_LIMITS.maxArrayLength) {
227
+ throw Object.assign(new Error("Export catalog exceeds its array entry limit"), { code: "JSON_LIMIT_EXCEEDED" });
228
+ }
229
+ catalogBytes += size;
230
+ entries.push(value);
231
+ }
232
+ for (const row of db.prepare("SELECT task_id FROM tasks ORDER BY task_id").iterate()) {
233
+ const result = await exportTaskSnapshot(db, row.task_id, taskStateRoot);
234
+ appendCatalogEntry(index.tasks, { taskId: result.taskId, taskKey: result.taskKey, events: result.events });
235
+ }
236
+ for (const reference of iterateAttachmentReferences(db)) {
237
+ if (!attachmentRoot) throw Object.assign(new Error("Attachment export requires its source project root"), { code: "E_STORAGE_ATTACHMENT_INVALID" });
238
+ await verifyAttachmentFile(attachmentRoot, reference);
239
+ const copied = await publishAttachmentFile(destination, createReadStream(await assertSafePath(attachmentRoot, reference.path)));
240
+ if (copied.size !== reference.size || copied.sha256 !== reference.sha256) throw Object.assign(new Error("Attachment changed during export"), { code: "E_STORAGE_ATTACHMENT_INVALID" });
241
+ appendCatalogEntry(index.attachments, reference);
242
+ }
243
+ await exportLegacySignatureMirrors(db, destination);
244
+ for (const row of db.prepare("SELECT * FROM sessions ORDER BY session_id").iterate()) {
245
+ const activation = JSON.parse(row.activation_json);
246
+ if (activation.sessionId !== row.session_id) throw Object.assign(new Error("Session identity disagrees with activation payload"), { code: "E_STORAGE_PAYLOAD_MISMATCH" });
247
+ const relativePath = `.forgeloop/sessions/${portableId(row.session_id)}.json`;
248
+ const bytes = serialize(activation);
249
+ await writeFileAtomic(await assertSafePath(destination, relativePath), bytes);
250
+ appendCatalogEntry(index.sessions, { sessionId: row.session_id, taskId: row.task_id, path: relativePath, size: Buffer.byteLength(bytes), sha256: createHash("sha256").update(bytes).digest("hex") });
251
+ }
252
+ const active = db.prepare("SELECT s.* FROM sessions s JOIN storage_meta m ON m.active_session_id = s.session_id WHERE m.id = 1").get();
253
+ if (active) {
254
+ index.activeSessionId = active.session_id;
255
+ await writeFileAtomic(await assertSafePath(destination, ".forgeloop/session.json"), serialize(JSON.parse(active.activation_json)));
256
+ }
257
+ assertJsonLimits(index, "export catalog", STORAGE_CATALOG_LIMITS);
258
+ const catalog = serialize(index);
259
+ assertJsonBytes(catalog, "export catalog", STORAGE_CATALOG_LIMITS);
260
+ await writeFileAtomic(await assertSafePath(destination, "export-index.json"), catalog);
261
+ return { destination, tasks: index.tasks.length, index };
262
+ }
263
+
264
+ /** Legacy mirrors belong only to explicit portable exports, never live storage. */
265
+ async function exportLegacySignatureMirrors(db, destination) {
266
+ const binding = db.prepare("SELECT * FROM attachment_references WHERE task_id = ? AND reference_id = ?");
267
+ for (const row of db.prepare("SELECT task_id, artifact_id, source_json, payload_json FROM task_artifacts WHERE kind = 'attestation' ORDER BY task_id, artifact_id").iterate()) {
268
+ if (!/^(?:history\/cycle-[1-9][0-9]*\/)?statement$/u.test(row.artifact_id)) continue;
269
+ const relativePath = `.forgeloop/task-state/${taskStorageKey(row.task_id)}/attestations/${row.artifact_id}.sigstore.json`;
270
+ const referenceRow = binding.get(row.task_id, legacyAttachmentReferenceId(relativePath));
271
+ if (!referenceRow) continue;
272
+ const text = row.source_json ?? serialize(JSON.parse(row.payload_json));
273
+ const statementBinding = binding.get(row.task_id, `${signatureReferencePrefix(text)}${legacyAttachmentReferenceId(relativePath)}`);
274
+ // Keep old aliases in the canonical index, but do not attach an old bundle
275
+ // to a replacement statement in the legacy filesystem representation.
276
+ if (!statementBinding) continue;
277
+ if (statementBinding.path !== referenceRow.path || statementBinding.size !== referenceRow.size || statementBinding.sha256 !== referenceRow.sha256) {
278
+ throw Object.assign(new Error("Legacy signature alias disagrees with its statement binding"), { code: "E_STORAGE_ATTACHMENT_INVALID" });
279
+ }
280
+ const reference = validateAttachmentReference({ taskId: referenceRow.task_id, referenceId: referenceRow.reference_id, path: referenceRow.path, size: referenceRow.size, sha256: referenceRow.sha256 });
281
+ const filename = await assertSafePath(destination, relativePath);
282
+ await withVerifiedAttachmentFile(destination, reference, source => writeFileAtomic(filename, createReadStream(source)));
283
+ }
284
+ }
@@ -0,0 +1,46 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { open } from "node:fs/promises";
4
+
5
+ const IGNORABLE_DIRECTORY_SYNC_ERRORS = new Set(["EINVAL", "EPERM", "EISDIR", "ENOTSUP", "UNKNOWN"]);
6
+
7
+ export async function syncDirectory(directory, { catchOpenErrors = true, catchCloseErrors = true } = {}) {
8
+ if (catchCloseErrors) {
9
+ try {
10
+ const handle = await open(directory, "r");
11
+ try {
12
+ await handle.sync();
13
+ } finally {
14
+ await handle.close();
15
+ }
16
+ } catch (error) {
17
+ if (!catchOpenErrors || !IGNORABLE_DIRECTORY_SYNC_ERRORS.has(error.code)) throw error;
18
+ }
19
+ return;
20
+ }
21
+ let handle;
22
+ try {
23
+ handle = await open(directory, "r");
24
+ } catch (error) {
25
+ if (!catchOpenErrors || !IGNORABLE_DIRECTORY_SYNC_ERRORS.has(error.code)) throw error;
26
+ return;
27
+ }
28
+ try {
29
+ await handle.sync();
30
+ } catch (error) {
31
+ if (!IGNORABLE_DIRECTORY_SYNC_ERRORS.has(error.code)) throw error;
32
+ } finally {
33
+ await handle.close();
34
+ }
35
+ }
36
+
37
+ export async function digestFile(filename, { includeSize = false } = {}) {
38
+ const hash = createHash("sha256");
39
+ let size = 0;
40
+ for await (const bytes of createReadStream(filename)) {
41
+ hash.update(bytes);
42
+ size += bytes.length;
43
+ }
44
+ const sha256 = hash.digest("hex");
45
+ return includeSize ? { size, sha256 } : sha256;
46
+ }