@cassiomc1/forgeloop 1.13.0 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/CONTRIBUTING.md +11 -7
  3. package/DOCS_INDEX.md +37 -4
  4. package/ENG/c-development-eng.md +2 -2
  5. package/ENG/cpp-development-eng.md +2 -2
  6. package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
  7. package/ENG/flutter-development-eng.md +4 -2
  8. package/ENG/go-development-eng.md +2 -2
  9. package/ENG/java-development-eng.md +3 -3
  10. package/ENG/nodejs-backend-development-eng.md +10 -6
  11. package/ENG/php-development-eng.md +3 -3
  12. package/ENG/python-development-eng.md +205 -0
  13. package/ENG/rust-development-eng.md +42 -1
  14. package/ENG/sec-code-eng.md +7 -7
  15. package/ENG/sql-development-eng.md +4 -4
  16. package/ENG/swift-development-eng.md +5 -4
  17. package/ENG/test-code-eng.md +1 -1
  18. package/ENG/typescript-development-eng.md +4 -4
  19. package/EXECUTION_STATE.md +12 -0
  20. package/GUIDE_ROUTER.md +6 -0
  21. package/LICENSE +133 -21
  22. package/LOOP_ENGINEERING.md +28 -2
  23. package/LOOP_SYSTEM_DESIGN.md +10 -10
  24. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  25. package/PROTOCOL_INTEGRATION.md +55 -2
  26. package/README.md +63 -59
  27. package/TERMINOLOGY.md +2 -0
  28. package/THIRD_PARTY_NOTICES.md +3 -4
  29. package/THREAT_MODEL.md +140 -1
  30. package/completions/_forgeloop +29 -3
  31. package/completions/forgeloop.bash +55 -3
  32. package/completions/forgeloop.fish +183 -1
  33. package/docs/ADVISORY_CONTEXT.md +25 -0
  34. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  35. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  36. package/docs/AGENT_PROTOCOL_SUMMARY.md +74 -39
  37. package/docs/AGENT_SKILL.md +66 -0
  38. package/docs/ARTIFACT_REFERENCE.md +171 -35
  39. package/docs/AUDIT_UX.md +46 -0
  40. package/docs/BROWSER_VERIFICATION.md +136 -0
  41. package/docs/CLI_REFERENCE.md +512 -8
  42. package/docs/CODE_ATTESTATION.md +2 -2
  43. package/docs/DOCUMENTATION_GUIDE.md +34 -11
  44. package/docs/GETTING_STARTED.md +3 -3
  45. package/docs/JEV_BENCHMARKS.md +31 -0
  46. package/docs/MODEL_ROUTING.md +37 -0
  47. package/docs/OPENSRC_ADAPTER.md +241 -0
  48. package/docs/PACKAGE_CONTENTS.md +46 -18
  49. package/docs/PROVIDERS.md +126 -0
  50. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  51. package/docs/RECIPES.md +9 -0
  52. package/docs/RELEASE_CHECKLIST.md +116 -12
  53. package/docs/SECURITY_REVIEW.md +71 -0
  54. package/docs/SELF_HOSTED_TESTS.md +121 -0
  55. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  56. package/docs/TEST_INTELLIGENCE.md +29 -0
  57. package/docs/TEST_PRUNING.md +14 -0
  58. package/docs/TROUBLESHOOTING.md +198 -1
  59. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  60. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  61. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  62. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  63. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  64. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  65. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  66. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  67. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  68. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  69. package/docs/diagrams/README.md +13 -9
  70. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  71. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  72. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  73. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  74. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  75. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  76. package/docs/documentation-manifest.json +774 -16
  77. package/docs/protocol-requirements.json +24 -0
  78. package/package.json +43 -11
  79. package/schemas/config.schema.json +14 -0
  80. package/schemas/context-plan.schema.json +18 -0
  81. package/schemas/semantic-decision.schema.json +46 -0
  82. package/schemas/task-bundle.schema.json +7 -2
  83. package/schemas/test-utility.schema.json +44 -0
  84. package/scripts/CI_VALIDATORS.md +6 -6
  85. package/scripts/benchmark-jev.mjs +5 -0
  86. package/scripts/benchmark-test-intelligence.mjs +4 -0
  87. package/scripts/generate-agent-protocol-summary.mjs +6 -3
  88. package/scripts/generate-forgeloop-skill.mjs +133 -0
  89. package/scripts/jev-smoke.mjs +19 -0
  90. package/scripts/run-mcp-clean-tests.mjs +30 -0
  91. package/skills/forgeloop/README.md +9 -0
  92. package/skills/forgeloop/SKILL.md +77 -0
  93. package/skills/forgeloop/references/lifecycle.md +9 -0
  94. package/skills/forgeloop/references/recovery.md +7 -0
  95. package/skills/forgeloop/references/verification.md +7 -0
  96. package/src/adapters/agent-browser/assertions.js +47 -0
  97. package/src/adapters/agent-browser/commands.js +54 -0
  98. package/src/adapters/agent-browser/index.js +3 -0
  99. package/src/adapters/agent-browser/locator.js +40 -0
  100. package/src/adapters/agent-browser/process.js +215 -0
  101. package/src/adapters/agent-browser/provider.js +316 -0
  102. package/src/adapters/emulated-services/constants.js +24 -0
  103. package/src/adapters/emulated-services/index.js +7 -0
  104. package/src/adapters/emulated-services/process.js +162 -0
  105. package/src/adapters/emulated-services/provider.js +289 -0
  106. package/src/adapters/opensrc/normalize.js +90 -0
  107. package/src/adapters/opensrc/process.js +248 -0
  108. package/src/adapters/opensrc/provider.js +338 -0
  109. package/src/adapters/opensrc/search.js +264 -0
  110. package/src/adapters/temporary-paths.js +18 -0
  111. package/src/adapters/typesafe/client.js +28 -0
  112. package/src/adapters/typesafe/engine.js +63 -0
  113. package/src/adapters/typesafe/normalize.js +41 -0
  114. package/src/cli.js +218 -174
  115. package/src/commands/action-show.js +5 -2
  116. package/src/commands/advance.js +5 -1
  117. package/src/commands/baseline.js +13 -11
  118. package/src/commands/checkpoint-revalidate.js +192 -0
  119. package/src/commands/complete.js +19 -3
  120. package/src/commands/context-plan.js +38 -0
  121. package/src/commands/contract-create.js +266 -0
  122. package/src/commands/contract-revise.js +236 -0
  123. package/src/commands/decision-show.js +14 -0
  124. package/src/commands/decision-status.js +22 -0
  125. package/src/commands/discover.js +41 -0
  126. package/src/commands/doctor.js +51 -13
  127. package/src/commands/gate-record.js +194 -0
  128. package/src/commands/gate-revalidate.js +135 -0
  129. package/src/commands/handoff-list.js +6 -1
  130. package/src/commands/handoff-show.js +6 -1
  131. package/src/commands/migrate-protocol.js +2 -2
  132. package/src/commands/model-route.js +40 -0
  133. package/src/commands/next.js +7 -1
  134. package/src/commands/policy-diff.js +25 -4
  135. package/src/commands/progress.js +17 -11
  136. package/src/commands/record-diagnosis.js +3 -0
  137. package/src/commands/route.js +146 -18
  138. package/src/commands/semantic-plan.js +41 -0
  139. package/src/commands/status.js +6 -1
  140. package/src/commands/storage-backup.js +15 -0
  141. package/src/commands/storage-migrate.js +5 -0
  142. package/src/commands/storage-migration-resume.js +5 -0
  143. package/src/commands/storage-migration-status.js +44 -0
  144. package/src/commands/storage-restore-resume.js +7 -0
  145. package/src/commands/storage-restore.js +8 -0
  146. package/src/commands/task-abandon.js +224 -0
  147. package/src/commands/task-list.js +8 -43
  148. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  149. package/src/commands/task-migrate.js +3 -3
  150. package/src/commands/task-recover.js +10 -2
  151. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  152. package/src/commands/task-scope.js +18 -2
  153. package/src/commands/task-show.js +16 -2
  154. package/src/commands/test-inventory.js +5 -0
  155. package/src/commands/test-prune-plan.js +5 -0
  156. package/src/commands/test-prune-probe.js +5 -0
  157. package/src/commands/test-utility.js +5 -0
  158. package/src/commands/validate-protocol.js +39 -15
  159. package/src/commands/validate-receipt.js +8 -1
  160. package/src/config/guides.json +4 -0
  161. package/src/core/action-ledger-projection.js +10 -3
  162. package/src/core/actions.js +38 -75
  163. package/src/core/activation.js +11 -3
  164. package/src/core/approvals.js +17 -51
  165. package/src/core/artifact-registry.js +83 -2
  166. package/src/core/artifacts.js +81 -27
  167. package/src/core/attachment-paths.js +45 -0
  168. package/src/core/attestation-coverage.js +6 -1
  169. package/src/core/attestation-verifier.js +21 -2
  170. package/src/core/attestation.js +17 -6
  171. package/src/core/audit-ux.js +514 -0
  172. package/src/core/audit.js +6 -1
  173. package/src/core/browser-verification/constants.js +149 -0
  174. package/src/core/browser-verification/normalize.js +254 -0
  175. package/src/core/browser-verification/provider.js +519 -0
  176. package/src/core/browser-verification/service.js +115 -0
  177. package/src/core/bundles.js +160 -66
  178. package/src/core/checkpoint-revalidation.js +321 -0
  179. package/src/core/cli-command-definitions.js +350 -7
  180. package/src/core/code-manifest.js +9 -3
  181. package/src/core/command-executors.js +274 -108
  182. package/src/core/command-input.js +136 -44
  183. package/src/core/completion-artifacts.js +85 -34
  184. package/src/core/completion-ownership.js +19 -8
  185. package/src/core/completion-recovery-rebind.js +11 -1
  186. package/src/core/completion.js +64 -15
  187. package/src/core/config.js +3 -0
  188. package/src/core/conformance.js +2 -1
  189. package/src/core/context-compiler/budget.js +9 -0
  190. package/src/core/context-compiler/candidates.js +39 -0
  191. package/src/core/context-compiler/compiler.js +63 -0
  192. package/src/core/context-compiler/fingerprint.js +11 -0
  193. package/src/core/context-compiler/policy.js +13 -0
  194. package/src/core/context-compiler/result.js +23 -0
  195. package/src/core/continuity-lint.js +13 -2
  196. package/src/core/continuity-reconciliation.js +16 -3
  197. package/src/core/continuity.js +16 -8
  198. package/src/core/contract-bootstrap-recovery.js +656 -0
  199. package/src/core/contract-revision.js +209 -0
  200. package/src/core/decision/artifact.js +70 -0
  201. package/src/core/decision/benchmarks.js +103 -0
  202. package/src/core/decision/cache.js +27 -0
  203. package/src/core/decision/constants.js +58 -0
  204. package/src/core/decision/cutover.js +34 -0
  205. package/src/core/decision/engine.js +22 -0
  206. package/src/core/decision/errors.js +68 -0
  207. package/src/core/decision/events.js +104 -0
  208. package/src/core/decision/freshness.js +19 -0
  209. package/src/core/decision/normalizers/index.js +115 -0
  210. package/src/core/decision/policy.js +18 -0
  211. package/src/core/decision/projection.js +16 -0
  212. package/src/core/decision/question-registry.js +201 -0
  213. package/src/core/decision/request.js +26 -0
  214. package/src/core/decision/resolver.js +138 -0
  215. package/src/core/decision/result.js +58 -0
  216. package/src/core/decision/service.js +181 -0
  217. package/src/core/decision/state-builder.js +65 -0
  218. package/src/core/decision/task-bindings.js +35 -0
  219. package/src/core/decision/test-provider.js +32 -0
  220. package/src/core/decision/thresholds.js +15 -0
  221. package/src/core/diagnosis-model.js +11 -0
  222. package/src/core/diagnosis.js +10 -1
  223. package/src/core/diagnostic-projection.js +29 -12
  224. package/src/core/diagnostic-record.js +28 -3
  225. package/src/core/efficiency.js +6 -1
  226. package/src/core/error-codes.js +278 -0
  227. package/src/core/events.js +535 -147
  228. package/src/core/evidence-readiness.js +9 -0
  229. package/src/core/execution-prerequisites.js +36 -13
  230. package/src/core/execution-profile.js +63 -38
  231. package/src/core/execution.js +26 -40
  232. package/src/core/filesystem.js +11 -0
  233. package/src/core/gate-artifact.js +105 -19
  234. package/src/core/gate-provenance.js +125 -0
  235. package/src/core/handoff.js +26 -14
  236. package/src/core/history.js +39 -35
  237. package/src/core/hypothesis-projection.js +32 -0
  238. package/src/core/information-gain-projection.js +104 -100
  239. package/src/core/inspect.js +12 -3
  240. package/src/core/integration-invocation-policy.js +37 -4
  241. package/src/core/integration-resources.js +119 -87
  242. package/src/core/ledger-event-collection.js +189 -0
  243. package/src/core/ledger-relations.js +23 -0
  244. package/src/core/model-router/constants.js +10 -0
  245. package/src/core/model-router/policy.js +103 -0
  246. package/src/core/model-router/router.js +37 -0
  247. package/src/core/native-storage.js +12 -0
  248. package/src/core/next-action-continuity.js +31 -9
  249. package/src/core/next-action-model.js +58 -0
  250. package/src/core/next-action-phases.js +131 -43
  251. package/src/core/next-action-refresh.js +43 -9
  252. package/src/core/next-action-review-phase.js +9 -4
  253. package/src/core/next-action-verification-phase.js +2 -1
  254. package/src/core/next-action.js +56 -9
  255. package/src/core/phase.js +266 -103
  256. package/src/core/policy-engine.js +12 -13
  257. package/src/core/preflight-consistency.js +89 -62
  258. package/src/core/preflight-loaders.js +48 -9
  259. package/src/core/preflight-model.js +6 -1
  260. package/src/core/preflight.js +29 -12
  261. package/src/core/progress.js +21 -10
  262. package/src/core/protocol-info.js +68 -0
  263. package/src/core/protocol-migration.js +7 -6
  264. package/src/core/protocol.js +20 -0
  265. package/src/core/reconcile-closure.js +281 -67
  266. package/src/core/recovery-history.js +67 -47
  267. package/src/core/reflection.js +72 -77
  268. package/src/core/responsibility.js +25 -13
  269. package/src/core/resumability.js +154 -44
  270. package/src/core/route-artifact.js +28 -4
  271. package/src/core/router.js +67 -1
  272. package/src/core/runtime-context.js +123 -61
  273. package/src/core/schema-validation.js +11 -3
  274. package/src/core/security-review/constants.js +64 -0
  275. package/src/core/security-review/normalize.js +245 -0
  276. package/src/core/security-review/provider.js +204 -0
  277. package/src/core/security-review/service.js +134 -0
  278. package/src/core/semantic-planning/constants.js +19 -0
  279. package/src/core/semantic-planning/projection.js +94 -0
  280. package/src/core/semantic-planning/service.js +15 -0
  281. package/src/core/settlement.js +12 -1
  282. package/src/core/signing/bundle-file.js +53 -0
  283. package/src/core/signing/bundle-reference.js +15 -0
  284. package/src/core/signing/sigstore.js +60 -33
  285. package/src/core/signing/statement-file.js +27 -0
  286. package/src/core/sources.js +37 -0
  287. package/src/core/storage-runtime-registry.js +41 -0
  288. package/src/core/structural-quality/artifacts.js +20 -14
  289. package/src/core/structural-quality/service.js +81 -56
  290. package/src/core/target-layout.js +1 -1
  291. package/src/core/task-claim-state.js +249 -15
  292. package/src/core/task-command.js +21 -1
  293. package/src/core/task-conflict-inspection.js +133 -102
  294. package/src/core/task-descriptor.js +29 -22
  295. package/src/core/task-discovery.js +288 -90
  296. package/src/core/task-lock.js +66 -306
  297. package/src/core/task-migration-validation.js +5 -5
  298. package/src/core/task-migration.js +17 -287
  299. package/src/core/task-paths.js +13 -0
  300. package/src/core/task-recovery.js +1 -0
  301. package/src/core/task-snapshot.js +51 -8
  302. package/src/core/task-storage-migration.js +18 -0
  303. package/src/core/templates.js +23 -8
  304. package/src/core/test-intelligence/benchmarks.js +68 -0
  305. package/src/core/test-intelligence/inventory.js +73 -0
  306. package/src/core/test-intelligence/prune.js +94 -0
  307. package/src/core/test-intelligence/semantic-state.js +15 -0
  308. package/src/core/test-intelligence/service.js +40 -0
  309. package/src/core/test-intelligence/utility.js +50 -0
  310. package/src/core/trace.js +164 -105
  311. package/src/core/trajectory-evaluation.js +15 -3
  312. package/src/core/trajectory-metrics.js +47 -32
  313. package/src/core/transaction.js +16 -287
  314. package/src/core/verification-scope.js +20 -12
  315. package/src/core/work-state.js +100 -59
  316. package/src/integration.d.ts +492 -0
  317. package/src/integration.js +54 -0
  318. package/src/providers/README.md +47 -0
  319. package/src/providers/capabilities.js +46 -0
  320. package/src/providers/errors.js +15 -0
  321. package/src/providers/index.js +29 -0
  322. package/src/providers/json-snapshot.js +105 -0
  323. package/src/providers/registry.js +152 -0
  324. package/src/repository-index/binary-manager.js +7 -5
  325. package/src/storage/artifact-bytes.js +22 -0
  326. package/src/storage/attachment-binding.js +11 -0
  327. package/src/storage/attachment-files.js +82 -0
  328. package/src/storage/attachment-references.js +36 -0
  329. package/src/storage/backup.js +53 -0
  330. package/src/storage/capabilities.js +31 -0
  331. package/src/storage/connection-owner.js +119 -0
  332. package/src/storage/connection.js +376 -0
  333. package/src/storage/existing-project-scope.js +45 -0
  334. package/src/storage/export-destination.js +44 -0
  335. package/src/storage/exporter.js +284 -0
  336. package/src/storage/file-durability.js +46 -0
  337. package/src/storage/importer.js +461 -0
  338. package/src/storage/index.js +74 -0
  339. package/src/storage/leases.js +63 -0
  340. package/src/storage/ledger-event-snapshot.js +150 -0
  341. package/src/storage/ledger-relations.js +220 -0
  342. package/src/storage/legacy-maintenance-boundary.js +18 -0
  343. package/src/storage/legacy-signature.js +115 -0
  344. package/src/storage/maintenance-handoff.js +129 -0
  345. package/src/storage/maintenance-owner.js +42 -0
  346. package/src/storage/maintenance.js +310 -0
  347. package/src/storage/metadata-json.js +35 -0
  348. package/src/storage/migration-activate.js +59 -0
  349. package/src/storage/migration-archive.js +57 -0
  350. package/src/storage/migration-attachment-root.js +61 -0
  351. package/src/storage/migration-candidate.js +398 -0
  352. package/src/storage/migration-publication.js +143 -0
  353. package/src/storage/migration-rollback-admission.js +54 -0
  354. package/src/storage/migration-rollback-preparation.js +132 -0
  355. package/src/storage/migration-rollback-publication.js +183 -0
  356. package/src/storage/migration-rollback-release.js +66 -0
  357. package/src/storage/migration-rollback-source-stage.js +103 -0
  358. package/src/storage/migration-rollback.js +43 -0
  359. package/src/storage/migration-source-partition.js +56 -0
  360. package/src/storage/migration-source.js +255 -0
  361. package/src/storage/migration-terminal.js +39 -0
  362. package/src/storage/migration-validation.js +75 -0
  363. package/src/storage/migration.js +43 -0
  364. package/src/storage/operational-attachments.js +49 -0
  365. package/src/storage/operational-context.js +50 -0
  366. package/src/storage/project-backup.js +98 -0
  367. package/src/storage/project-bootstrap.js +24 -0
  368. package/src/storage/project-boundary.js +79 -0
  369. package/src/storage/project-read-snapshot.js +18 -0
  370. package/src/storage/project-replacement.js +137 -0
  371. package/src/storage/project-restore.js +203 -0
  372. package/src/storage/record-definitions.js +45 -0
  373. package/src/storage/repository.js +507 -0
  374. package/src/storage/restore-journal.js +62 -0
  375. package/src/storage/restore-layout.js +30 -0
  376. package/src/storage/restore-preparation.js +84 -0
  377. package/src/storage/restore-replacement-archive.js +162 -0
  378. package/src/storage/restore-replacement-owner.js +31 -0
  379. package/src/storage/restore-replacement-preparation.js +128 -0
  380. package/src/storage/restore-replacement.js +142 -0
  381. package/src/storage/restore-terminal.js +32 -0
  382. package/src/storage/runtime.js +31 -0
  383. package/src/storage/schema.js +214 -0
  384. package/src/storage/singleton-import.js +84 -0
  385. package/src/storage/snapshot-continuity.js +16 -0
  386. package/src/storage/snapshot.js +36 -0
  387. package/src/storage/storage-marker.js +79 -0
  388. package/src/storage/task-guards.js +107 -0
  389. package/src/storage/topology.js +42 -0
  390. package/src/storage/transaction.js +179 -0
  391. package/src/storage/unit-of-work.js +663 -0
  392. package/src/storage/windows-process-incarnation.js +206 -0
  393. package/.forgeloop/forgeloop.gitignore +0 -4
  394. package/LICENSE-DOCS.md +0 -13
  395. package/src/core/transaction-maintenance.js +0 -70
@@ -1,13 +1,30 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
1
2
  import { execFile } from "node:child_process";
2
3
  import { promisify } from "node:util";
3
4
 
4
5
  import path from "node:path";
5
6
  import { ensureWithin, assertSafePath } from "../filesystem.js";
7
+ import { withSigningStatementFile } from "./statement-file.js";
8
+ import { withSigningBundleFile } from "./bundle-file.js";
9
+ import { getOperationalStore } from "../../storage/operational-context.js";
10
+ import { withOperationalAttachmentFile } from "../../storage/operational-attachments.js";
6
11
 
7
12
  const execFileAsync = promisify(execFile);
8
13
  const DEFAULT_TIMEOUT_MS = 30_000;
9
14
  const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
10
15
 
16
+ async function withExistingSigningReadScope(target, read) {
17
+ if (await needsExistingProjectScope(target)) return withExistingProjectScope(target, read, { readOnly: true });
18
+ return read();
19
+ }
20
+
21
+ function signingTaskId(target, statementPath) {
22
+ const store = getOperationalStore(target);
23
+ const taskKey = /^\.forgeloop\/task-state\/([a-f0-9]{64})\/attestations\/statement\.json$/u.exec(statementPath)?.[1];
24
+ const taskId = store && taskKey ? store.taskRow(taskKey)?.task_id : null;
25
+ return taskId;
26
+ }
27
+
11
28
  export function createSigstoreSigningProvider({
12
29
  command = "cosign",
13
30
  execFileImpl = execFileAsync,
@@ -21,7 +38,7 @@ export function createSigstoreSigningProvider({
21
38
  killSignal: "SIGTERM",
22
39
  maxBuffer: MAX_OUTPUT_BYTES,
23
40
  });
24
- return {
41
+ const provider = {
25
42
  name: "sigstore",
26
43
  async detect() {
27
44
  try {
@@ -43,13 +60,13 @@ export function createSigstoreSigningProvider({
43
60
  try {
44
61
  await assertSafePath(target, statementPath);
45
62
  await assertSafePath(target, destinationPath);
46
- await run([
63
+ const signed = await withSigningBundleFile(target, destinationPath, statementPath, bundleAbsolute => withSigningStatementFile(target, statementPath, statementAbsolute => run([
47
64
  "attest-blob",
48
- "--statement", ensureWithin(target, statementPath),
49
- "--bundle", ensureWithin(target, destinationPath),
65
+ "--statement", statementAbsolute,
66
+ "--bundle", bundleAbsolute,
50
67
  "--yes",
51
- ], { cwd: path.resolve(target) });
52
- return { status: "VALID", path: destinationPath };
68
+ ], { cwd: path.resolve(target) })));
69
+ return { status: "VALID", ...signed };
53
70
  } catch (error) {
54
71
  const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
55
72
  const timedOut = error.code === "ETIMEDOUT" || error.killed === true || error.signal === "SIGTERM";
@@ -67,33 +84,42 @@ export function createSigstoreSigningProvider({
67
84
  async verify({ target, statementPath, bundlePath, policy = {} } = {}) {
68
85
  if (!bundlePath) return { status: "UNSIGNED", signer: null };
69
86
  try {
70
- await assertSafePath(target, statementPath);
71
- await assertSafePath(target, bundlePath);
72
- const statementAbsolute = ensureWithin(target, statementPath);
73
- const bundleAbsolute = ensureWithin(target, bundlePath);
74
- const args = [
75
- "verify-blob-attestation",
76
- "--statement", statementAbsolute,
77
- "--bundle", bundleAbsolute,
78
- ];
79
- const identities = Array.isArray(policy.identities)
80
- ? [...new Set(policy.identities.filter((value) => typeof value === "string" && value.length > 0))]
81
- : [];
82
- if (policy.identity) args.push("--certificate-identity", policy.identity);
83
- else if (identities.length === 1) args.push("--certificate-identity", identities[0]);
84
- else if (identities.length > 1) {
85
- const exactAlternatives = identities
86
- .map((value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
87
- .join("|");
88
- args.push("--certificate-identity-regexp", `^(?:${exactAlternatives})$`);
89
- }
90
- if (policy.issuer) args.push("--certificate-oidc-issuer", policy.issuer);
91
- if (policy.trustedRoot) {
92
- await assertSafePath(target, policy.trustedRoot);
93
- args.push("--trusted-root", ensureWithin(target, policy.trustedRoot));
94
- }
95
- await run(args, { cwd: path.resolve(target) });
96
- return { status: "VALID", signer: policy.identity ?? (identities.length === 1 ? identities[0] : null) };
87
+ return await withExistingSigningReadScope(target, async () => {
88
+ await assertSafePath(target, statementPath);
89
+ await assertSafePath(target, bundlePath);
90
+ const taskId = signingTaskId(target, statementPath);
91
+ const statementAbsolute = ensureWithin(target, statementPath);
92
+ const bundleAbsolute = ensureWithin(target, bundlePath);
93
+ const args = [
94
+ "verify-blob-attestation",
95
+ "--statement", statementAbsolute,
96
+ "--bundle", bundleAbsolute,
97
+ ];
98
+ const identities = Array.isArray(policy.identities)
99
+ ? [...new Set(policy.identities.filter((value) => typeof value === "string" && value.length > 0))]
100
+ : [];
101
+ if (policy.identity) args.push("--certificate-identity", policy.identity);
102
+ else if (identities.length === 1) args.push("--certificate-identity", identities[0]);
103
+ else if (identities.length > 1) {
104
+ const exactAlternatives = identities
105
+ .map((value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
106
+ .join("|");
107
+ args.push("--certificate-identity-regexp", `^(?:${exactAlternatives})$`);
108
+ }
109
+ if (policy.issuer) args.push("--certificate-oidc-issuer", policy.issuer);
110
+ if (policy.trustedRoot) {
111
+ await assertSafePath(target, policy.trustedRoot);
112
+ args.push("--trusted-root", ensureWithin(target, policy.trustedRoot));
113
+ }
114
+ await withSigningStatementFile(target, statementPath, statementFilename => {
115
+ args[args.indexOf("--statement") + 1] = statementFilename;
116
+ return withOperationalAttachmentFile(target, bundlePath, taskId, bundleFilename => {
117
+ args[args.indexOf("--bundle") + 1] = bundleFilename;
118
+ return run(args, { cwd: path.resolve(target) });
119
+ });
120
+ });
121
+ return { status: "VALID", signer: policy.identity ?? (identities.length === 1 ? identities[0] : null) };
122
+ });
97
123
  } catch (error) {
98
124
  if (error.code === "E_ATTESTATION_SIGNER_UNAVAILABLE") throw error;
99
125
  const unavailable = error.code === "ENOENT" || error.cause?.code === "ENOENT";
@@ -126,6 +152,7 @@ export function createSigstoreSigningProvider({
126
152
  }
127
153
  },
128
154
  };
155
+ return provider;
129
156
  }
130
157
 
131
158
  /**
@@ -0,0 +1,27 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { assertSafePath, ensureWithin, writeFileAtomic } from "../filesystem.js";
6
+ import { getOperationalStore, readOperationalText } from "../../storage/operational-context.js";
7
+
8
+ /** Present selected canonical bytes to external signers without a legacy mirror. */
9
+ export async function withSigningStatementFile(target, statementPath, callback) {
10
+ if (await needsExistingProjectScope(target)) {
11
+ return withExistingProjectScope(target, () => withSigningStatementFile(target, statementPath, callback), { readOnly: true });
12
+ }
13
+ await assertSafePath(target, statementPath);
14
+ const source = readOperationalText(target, statementPath);
15
+ if (!source.selected) return callback(ensureWithin(target, statementPath));
16
+ const invalid = message => Object.assign(new Error(message), { code: "E_ATTESTATION_STATEMENT_INVALID" });
17
+ if (source.text === null) throw invalid("Selected attestation statement is missing");
18
+ if (getOperationalStore(target).db.isTransaction) throw invalid("External signing cannot run inside a SQLite write transaction");
19
+ const directory = await mkdtemp(path.join(os.tmpdir(), "forgeloop-signing-statement-"));
20
+ try {
21
+ const filename = path.join(directory, "statement.json");
22
+ await writeFileAtomic(filename, source.text);
23
+ const result = await callback(filename);
24
+ if (readOperationalText(target, statementPath).text !== source.text) throw invalid("Attestation statement changed during external signing");
25
+ return result;
26
+ } finally { await rm(directory, { recursive: true, force: true }); }
27
+ }
@@ -1,5 +1,19 @@
1
+ import { CONTRACT_PRESET_IDS } from "./contract-presets.js";
1
2
  import { PROTOCOL_VERSION } from "./protocol.js";
2
3
 
4
+ const CONTRACT_PRESET_PREFIX = "contract-preset:";
5
+ const BUILTIN_CONTRACT_PRESET_REFS = new Set(
6
+ CONTRACT_PRESET_IDS.map((presetId) => `${CONTRACT_PRESET_PREFIX}${presetId}`),
7
+ );
8
+
9
+ export function isBuiltinContractPresetRef(ref) {
10
+ return typeof ref === "string" && BUILTIN_CONTRACT_PRESET_REFS.has(ref);
11
+ }
12
+
13
+ export function externalContractSourceRefs(refs = []) {
14
+ return refs.filter((ref) => !isBuiltinContractPresetRef(ref));
15
+ }
16
+
3
17
  export const SOURCE_REGISTRY_SCHEMA_VERSION = 1;
4
18
  export const SOURCE_KINDS = Object.freeze([
5
19
  "user-request",
@@ -84,3 +98,26 @@ export function assertSourceProvenance(registry, refs = [], { expectedKind } = {
84
98
  }
85
99
  return true;
86
100
  }
101
+
102
+ export function assertContractPresetRefs(refs = []) {
103
+ for (const ref of refs) {
104
+ if (typeof ref === "string" && ref.startsWith(CONTRACT_PRESET_PREFIX) && !isBuiltinContractPresetRef(ref)) {
105
+ const error = new Error(`Unknown built-in contract preset source: ${ref}`);
106
+ error.code = "E_PROFILE_SOURCE_UNKNOWN";
107
+ throw error;
108
+ }
109
+ }
110
+ return true;
111
+ }
112
+
113
+ export function assertContractSourceProvenance(registry, refs = []) {
114
+ assertContractPresetRefs(refs);
115
+ const externalRefs = externalContractSourceRefs(refs);
116
+ if (externalRefs.length === 0) return true;
117
+ if (!registry) {
118
+ const error = new Error("External contract source references require a source registry");
119
+ error.code = "E_PROFILE_SOURCE_MISSING";
120
+ throw error;
121
+ }
122
+ return assertSourceProvenance(registry, externalRefs);
123
+ }
@@ -0,0 +1,41 @@
1
+ const owners = new WeakMap();
2
+ function closed(message) { return Object.assign(new Error(message), { code: "E_STORAGE_RUNTIME_CLOSED" }); }
3
+
4
+ /** Register lazy ownership without importing a SQLite implementation. */
5
+ export function attachStorageConnectionOwner(context) {
6
+ let owner;
7
+ let loading;
8
+ let closing = false;
9
+ let closePromise;
10
+ const pending = new Set();
11
+ const getOwner = () => loading ??= import("../storage/connection-owner.js").then(module => {
12
+ owner = module.createStorageConnectionOwner();
13
+ return owner;
14
+ });
15
+ const proxy = {
16
+ run(target, callback, options) {
17
+ if (closing) return Promise.reject(closed("Persistent storage runtime is closing or closed"));
18
+ const request = getOwner().then(value => value.run(target, callback, options));
19
+ pending.add(request);
20
+ const finished = () => { pending.delete(request); };
21
+ request.then(finished, finished);
22
+ return request;
23
+ },
24
+ close() {
25
+ if (owner?.isActive()) return Promise.reject(closed("Close the runtime outside an active command"));
26
+ if (closePromise) return closePromise;
27
+ closing = true;
28
+ closePromise = (async () => {
29
+ await Promise.all([...pending].map(request => request.catch(() => undefined)));
30
+ if (loading) await (await loading).close();
31
+ })();
32
+ return closePromise;
33
+ },
34
+ };
35
+ owners.set(context, proxy);
36
+ return proxy.close;
37
+ }
38
+
39
+ export function getStorageConnectionOwner(context) {
40
+ return context && typeof context === "object" ? owners.get(context) ?? null : null;
41
+ }
@@ -1,4 +1,6 @@
1
+ import { needsExistingProjectScope, withExistingProjectScope } from "../../storage/existing-project-scope.js";
1
2
  import { readdir } from "node:fs/promises";
3
+ import { listOperationalArtifactNames, operationalArtifactExists } from "../../storage/operational-context.js";
2
4
 
3
5
  import {
4
6
  canonicalFingerprint,
@@ -205,21 +207,25 @@ export async function writeStructuralQualityBaseline(
205
207
  }
206
208
 
207
209
  export async function listStructuralQualityEvaluations(target, taskId, packageRoot = getPackageRoot()) {
210
+ if (await needsExistingProjectScope(target)) {
211
+ return withExistingProjectScope(target, () => listStructuralQualityEvaluations(target, taskId, packageRoot), { readOnly: true });
212
+ }
208
213
  const relativeDirectory = taskStructuralQualityEvaluationsDirectory(taskId);
209
- await assertSafePath(target, relativeDirectory);
210
- const absoluteDirectory = ensureWithin(target, relativeDirectory);
211
- if (!(await fileExists(absoluteDirectory))) return [];
212
- let entries;
213
- try {
214
- entries = await readdir(absoluteDirectory, { withFileTypes: true });
215
- } catch (error) {
216
- throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Unable to list structural-quality evaluations: ${error.message}`, [relativeDirectory]);
214
+ let names = listOperationalArtifactNames(target, taskId, "structural-quality/evaluations");
215
+ if (names === null) {
216
+ await assertSafePath(target, relativeDirectory);
217
+ const absoluteDirectory = ensureWithin(target, relativeDirectory);
218
+ if (!(await fileExists(absoluteDirectory))) return [];
219
+ try {
220
+ names = (await readdir(absoluteDirectory, { withFileTypes: true })).filter(entry => entry.isFile()).map(entry => entry.name);
221
+ } catch (error) {
222
+ throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Unable to list structural-quality evaluations: ${error.message}`, [relativeDirectory]);
223
+ }
217
224
  }
218
- const candidates = entries
219
- .filter((entry) => entry.isFile())
220
- .map((entry) => {
221
- const match = /^cycle-(\d+)-attempt-(\d+)\.json$/u.exec(entry.name);
222
- return match ? { name: entry.name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
225
+ const candidates = names
226
+ .map((name) => {
227
+ const match = /^cycle-(\d+)-attempt-(\d+)\.json$/u.exec(name);
228
+ return match ? { name, verificationCycle: Number(match[1]), attempt: Number(match[2]) } : null;
223
229
  })
224
230
  .filter(Boolean)
225
231
  .filter((item) => item.verificationCycle >= 1 && item.attempt >= 1)
@@ -267,7 +273,7 @@ export async function writeStructuralQualityEvaluation(target, taskId, verificat
267
273
  throw qualityArtifactError(E_STRUCTURAL_QUALITY_EVIDENCE_STALE, `Evaluation identity does not match ${relativePath}`, [relativePath]);
268
274
  }
269
275
  validateStructuralQualityArtifact(value, relativePath);
270
- const existing = await fileExists(ensureWithin(target, relativePath));
276
+ const existing = operationalArtifactExists(target, relativePath) ?? await fileExists(ensureWithin(target, relativePath));
271
277
  if (existing) {
272
278
  const current = await readStructuralQualityEvaluation(target, taskId, cycle, normalizedAttempt, packageRoot);
273
279
  if (current.fingerprint === canonicalFingerprint(value)) return { ...current, existing: true, identical: true };
@@ -1,3 +1,5 @@
1
+ import { needsExistingProjectScope } from "../../storage/existing-project-scope.js";
2
+ import { withProjectReadSnapshot } from "../../storage/project-read-snapshot.js";
1
3
  import { canonicalFingerprint, readJsonArtifact } from "../artifacts.js";
2
4
  import { readConfig } from "../config.js";
3
5
  import { readContract } from "../contract.js";
@@ -36,6 +38,22 @@ import {
36
38
  import { appendProtocolEvent } from "../events.js";
37
39
  import { getPackageRoot } from "../templates.js";
38
40
  import { withTaskMutation } from "../task-command.js";
41
+ import { withTaskTransaction } from "../transaction.js";
42
+ import { getOperationalStore } from "../../storage/operational-context.js";
43
+
44
+ async function withQualityMutation(target, options, operation, callback) {
45
+ const native = Boolean(getOperationalStore(target)) || await needsExistingProjectScope(target);
46
+ for (let retry = 0; ; retry += 1) {
47
+ try {
48
+ return await withTaskMutation(target, options, operation, callback);
49
+ } catch (error) {
50
+ // These callbacks persist an already captured observation. A native
51
+ // conflict discards their staged writes; rerun admission and bindings
52
+ // against current records without invoking the provider again.
53
+ if (!native || error.code !== "E_STATE_REVISION_CONFLICT" || retry >= 2) throw error;
54
+ }
55
+ }
56
+ }
39
57
  import { findTaskById } from "../task-discovery.js";
40
58
  import { currentChangedPaths } from "../repository.js";
41
59
  import { assertClaimsCoverChangedPaths } from "../task-scope.js";
@@ -142,7 +160,11 @@ function normalizeScanResult(result, provider, detection, projectPath) {
142
160
  };
143
161
  }
144
162
 
145
- export async function resolveStructuralQualityContext({
163
+ export async function resolveStructuralQualityContext(options = {}) {
164
+ return withProjectReadSnapshot(options.target, () => resolveStructuralQualityContextObservation(options));
165
+ }
166
+
167
+ async function resolveStructuralQualityContextObservation({
146
168
  target,
147
169
  packageRoot = getPackageRoot(),
148
170
  taskId,
@@ -378,31 +400,43 @@ export async function captureStructuralQualityBaseline({ target, packageRoot = g
378
400
  scope: updatedInputs.scope,
379
401
  snapshot: scan.snapshot,
380
402
  };
381
- const written = await writeStructuralQualityBaseline(target, taskId, value, packageRoot, { phase: inputs.state.phase, replace, taskId });
382
- if (!written.identical) {
383
- await appendProtocolEvent(target, {
384
- taskId,
385
- event: "STRUCTURAL_QUALITY_BASELINE_RECORDED",
386
- details: {
387
- artifactRef: written.path,
388
- artifactFingerprint: written.fingerprint,
389
- providerId: scan.provider.id,
390
- providerVersion: scan.provider.version,
391
- replaced: Boolean(existing),
392
- supersededFingerprint: existing?.fingerprint ?? null,
393
- },
394
- }, packageRoot, { taskId });
395
- }
396
- return {
397
- status: written.existing ? "REPLACED" : "CAPTURED",
398
- mode: policy.mode,
399
- provider: scan.provider,
400
- artifactRef: written.path,
401
- artifactFingerprint: written.fingerprint,
402
- baseline: written.value,
403
- existing: written.existing,
404
- identical: written.identical,
405
- };
403
+ return withTaskTransaction({ target, taskId, packageRoot, operation: "quality-baseline" }, async () => {
404
+ const currentInputs = await taskInputs(target, packageRoot, taskId, { runtimeContext, timeoutMs, maxOutputBytes });
405
+ if (currentInputs.state.phase !== "PLANNED"
406
+ || currentInputs.contract.fingerprint !== inputs.contract.fingerprint
407
+ || currentInputs.route.fingerprint !== inputs.route.fingerprint
408
+ || structuralQualityPolicyFingerprint(currentInputs.policy) !== policyFingerprint
409
+ || currentInputs.scopeFingerprint !== inputs.scopeFingerprint) {
410
+ throw qualityError(E_STRUCTURAL_QUALITY_OBSERVATION_EPOCH_STALE, "Task baseline configuration changed during observation");
411
+ }
412
+ await assertReadyPreflight(target, packageRoot, taskId);
413
+ const currentBaseline = await readStructuralQualityBaseline(target, taskId, packageRoot);
414
+ const written = await writeStructuralQualityBaseline(target, taskId, value, packageRoot, { phase: currentInputs.state.phase, replace, taskId });
415
+ if (!written.identical) {
416
+ await appendProtocolEvent(target, {
417
+ taskId,
418
+ event: "STRUCTURAL_QUALITY_BASELINE_RECORDED",
419
+ details: {
420
+ artifactRef: written.path,
421
+ artifactFingerprint: written.fingerprint,
422
+ providerId: scan.provider.id,
423
+ providerVersion: scan.provider.version,
424
+ replaced: Boolean(currentBaseline),
425
+ supersededFingerprint: currentBaseline?.fingerprint ?? null,
426
+ },
427
+ }, packageRoot, { taskId });
428
+ }
429
+ return {
430
+ status: written.existing ? "REPLACED" : "CAPTURED",
431
+ mode: policy.mode,
432
+ provider: scan.provider,
433
+ artifactRef: written.path,
434
+ artifactFingerprint: written.fingerprint,
435
+ baseline: written.value,
436
+ existing: written.existing,
437
+ identical: written.identical,
438
+ };
439
+ });
406
440
  }
407
441
 
408
442
  async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs, persisted, authorityContext, runtimeContext }) {
@@ -443,7 +477,7 @@ async function persistEvaluationAndCheck({ target, packageRoot, taskId, inputs,
443
477
  runtimeContext,
444
478
  taskId: context?.taskId ?? taskId,
445
479
  });
446
- const recorded = await withTaskMutation(target, { taskId, packageRoot }, "quality-verify-check", async (context) => {
480
+ const recorded = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-check", async (context) => {
447
481
  const effectiveTaskId = context?.taskId ?? taskId;
448
482
  const evaluations = await listStructuralQualityEvaluations(target, effectiveTaskId, packageRoot);
449
483
  const latest = evaluations
@@ -599,34 +633,33 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
599
633
 
600
634
  // Execute provider observation OUTSIDE task mutation lock
601
635
  const sourceFingerprintBefore = await computeMaterialSourceFingerprint(target);
636
+ // Every non-pass branch binds the same reserved task/cycle and source epoch.
637
+ const evaluationInputs = {
638
+ taskId,
639
+ inputs: initialInputs,
640
+ cycle,
641
+ attempt,
642
+ policy,
643
+ baseline,
644
+ sourceMaterialFingerprint: sourceFingerprintBefore,
645
+ };
602
646
  let evaluation;
603
647
 
604
648
  if (!baseline) {
605
649
  evaluation = nonPassEvaluation({
606
- taskId,
607
- inputs: initialInputs,
608
- cycle,
609
- attempt,
610
- policy,
650
+ ...evaluationInputs,
611
651
  reasonCode: E_STRUCTURAL_QUALITY_BASELINE_MISSING,
612
652
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
613
- sourceMaterialFingerprint: sourceFingerprintBefore,
614
653
  });
615
654
  } else {
616
655
  try {
617
656
  const scopeChanged = baseline.value.bindings?.scopeFingerprint !== initialInputs.scopeFingerprint;
618
657
  if (scopeChanged) {
619
658
  evaluation = nonPassEvaluation({
620
- taskId,
621
- inputs: initialInputs,
622
- cycle,
623
- attempt,
624
- policy,
659
+ ...evaluationInputs,
625
660
  reasonCode: E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE,
626
661
  reasonCodes: ["PROVIDER_CONFIG_CHANGED"],
627
- baseline,
628
662
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintBefore, stable: true },
629
- sourceMaterialFingerprint: sourceFingerprintBefore,
630
663
  });
631
664
  evaluation.comparison.reasonCodes = [E_STRUCTURAL_QUALITY_EVALUATION_INCOMPARABLE, "PROVIDER_CONFIG_CHANGED"];
632
665
  } else {
@@ -643,18 +676,12 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
643
676
 
644
677
  if (!isStable) {
645
678
  evaluation = nonPassEvaluation({
646
- taskId,
647
- inputs: initialInputs,
648
- cycle,
649
- attempt,
650
- policy,
679
+ ...evaluationInputs,
651
680
  reasonCode: E_STRUCTURAL_QUALITY_SOURCE_DRIFT,
652
681
  providerId: scan.provider.id,
653
682
  providerVersion: scan.provider.version,
654
683
  detection: scan.detection,
655
- baseline,
656
684
  sourceObservation: { beforeFingerprint: sourceFingerprintBefore, afterFingerprint: sourceFingerprintAfter, stable: false },
657
- sourceMaterialFingerprint: sourceFingerprintBefore,
658
685
  });
659
686
  } else {
660
687
  const updatedInputs = initialInputs;
@@ -699,26 +726,20 @@ export async function evaluateStructuralQuality({ target, packageRoot = getPacka
699
726
  const sourceFingerprintAfter = await computeMaterialSourceFingerprint(target).catch(() => sourceFingerprintBefore);
700
727
  const code = error.code ?? E_STRUCTURAL_QUALITY_PROVIDER_UNAVAILABLE;
701
728
  evaluation = nonPassEvaluation({
702
- taskId,
703
- inputs: initialInputs,
704
- cycle,
705
- attempt,
706
- policy,
729
+ ...evaluationInputs,
707
730
  reasonCode: code,
708
731
  providerId: policy.provider,
709
- baseline,
710
732
  sourceObservation: {
711
733
  beforeFingerprint: sourceFingerprintBefore,
712
734
  afterFingerprint: sourceFingerprintAfter,
713
735
  stable: sourceFingerprintBefore === sourceFingerprintAfter,
714
736
  },
715
- sourceMaterialFingerprint: sourceFingerprintBefore,
716
737
  });
717
738
  }
718
739
  }
719
740
 
720
741
  // Commit under task mutation lock
721
- const captured = await withTaskMutation(target, { taskId, packageRoot }, "quality-verify-evaluation", async (context) => {
742
+ const captured = await withQualityMutation(target, { taskId, packageRoot }, "quality-verify-evaluation", async (context) => {
722
743
  const lockedTaskId = context?.taskId ?? taskId;
723
744
  const inputs = await taskInputs(target, packageRoot, lockedTaskId, { runtimeContext, timeoutMs, maxOutputBytes });
724
745
  if (inputs.state.phase !== "VERIFYING") {
@@ -908,7 +929,11 @@ function projectionFromArtifacts(policy, baseline, current, state, evaluations =
908
929
  return projection;
909
930
  }
910
931
 
911
- export async function projectStructuralQualityStatus({ target, packageRoot = getPackageRoot(), taskId, runtimeContext } = {}) {
932
+ export async function projectStructuralQualityStatus(options = {}) {
933
+ return withProjectReadSnapshot(options.target, () => projectStructuralQualityStatusObservation(options));
934
+ }
935
+
936
+ async function projectStructuralQualityStatusObservation({ target, packageRoot = getPackageRoot(), taskId, runtimeContext } = {}) {
912
937
  const config = await loadOptionalConfig(target, packageRoot);
913
938
  const policy = configuredPolicy(config);
914
939
  if (!policy || policy.mode === "off") return emptyProjection(null);
@@ -26,7 +26,7 @@ export function targetPathForSource(sourcePath) {
26
26
  }
27
27
 
28
28
  export function legacyPathForSource(sourcePath) {
29
- return sourcePath === ".forgeloop/forgeloop.gitignore" ? ".forgeloop/.gitignore" : sourcePath;
29
+ return sourcePath;
30
30
  }
31
31
 
32
32
  export function isKitPath(relativePath) {