@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,11 +1,26 @@
1
+ import { attachStorageConnectionOwner } from "./storage-runtime-registry.js";
1
2
  import {
2
3
  E_VERIFICATION_ISOLATION_UNAVAILABLE,
3
4
  isVerificationExecutionAdapter,
4
5
  normalizeVerificationExecutionPolicy,
5
6
  } from "./verification-execution.js";
6
7
  import { STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN } from "./structural-quality/constants.js";
7
- import { E_ADVISORY_CONTEXT_PROVIDER_INVALID } from "./error-codes.js";
8
+ import {
9
+ E_ADVISORY_CONTEXT_PROVIDER_INVALID,
10
+ E_BROWSER_VERIFICATION_PROVIDER_INVALID,
11
+ E_SECURITY_REVIEW_PROVIDER_INVALID,
12
+ } from "./error-codes.js";
8
13
  import { assertAdvisoryContextProviderIdentity } from "./advisory-context/provider.js";
14
+ import {
15
+ BROWSER_VERIFICATION_PROVIDER_ID_PATTERN,
16
+ assertBrowserVerificationProvider,
17
+ assertBrowserVerificationProviderIdentity,
18
+ } from "./browser-verification/provider.js";
19
+ import {
20
+ SECURITY_REVIEW_PROVIDER_ID_PATTERN,
21
+ assertSecurityReviewProvider,
22
+ assertSecurityReviewProviderIdentity,
23
+ } from "./security-review/provider.js";
9
24
 
10
25
  export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
11
26
 
@@ -83,6 +98,100 @@ export function resolveAuthorityContext(options = {}) {
83
98
  return hasDirectAuthority ? createAuthorityContext({ ...source, trustMode: "NONE" }) : createAuthorityContext();
84
99
  }
85
100
 
101
+ function isProviderObject(value) {
102
+ return value && typeof value === "object" && !Array.isArray(value);
103
+ }
104
+
105
+ function providerEntries(configured, label, code) {
106
+ if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
107
+ const error = new Error(`${label} must be an object or Map`);
108
+ error.code = code;
109
+ throw error;
110
+ }
111
+ return configured instanceof Map ? [...configured.entries()] : Object.entries(configured);
112
+ }
113
+
114
+ function registerProviders(configured, { label, code, idError, validateId, providerError, validateProvider }) {
115
+ const providers = {};
116
+ for (const [id, provider] of providerEntries(configured, label, code)) {
117
+ if (!validateId(id)) {
118
+ const error = new Error(idError(id));
119
+ error.code = code;
120
+ throw error;
121
+ }
122
+ if (typeof provider !== "function" && !isProviderObject(provider)) {
123
+ const error = new Error(providerError(id));
124
+ error.code = code;
125
+ throw error;
126
+ }
127
+ if (typeof provider !== "function") validateProvider(provider, id);
128
+ providers[id] = provider;
129
+ }
130
+ return Object.freeze(providers);
131
+ }
132
+
133
+ function configureUsageProvider(context, provider) {
134
+ if (!provider
135
+ || typeof provider !== "object"
136
+ || Array.isArray(provider)
137
+ || typeof provider.getTaskUsage !== "function") {
138
+ const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
139
+ error.code = "E_USAGE_INVALID";
140
+ throw error;
141
+ }
142
+ context.usageProvider = provider;
143
+ }
144
+
145
+ function configureStructuralQualityProviders(context, configured) {
146
+ context.structuralQualityProviders = registerProviders(configured, {
147
+ label: "structuralQualityProviders",
148
+ code: "E_STRUCTURAL_QUALITY_PROVIDER_INVALID",
149
+ idError: id => `Invalid or reserved structural-quality provider ID: ${id}`,
150
+ validateId: id => STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) && id !== "sentrux",
151
+ providerError: id => `Structural-quality provider ${id} must be an object or factory`,
152
+ validateProvider: (provider, id) => {},
153
+ });
154
+ }
155
+
156
+ function configureAdvisoryContextProviders(context, configured) {
157
+ context.advisoryContextProviders = registerProviders(configured, {
158
+ label: "advisoryContextProviders",
159
+ code: E_ADVISORY_CONTEXT_PROVIDER_INVALID,
160
+ idError: id => `Invalid advisory-context provider ID: ${id}`,
161
+ validateId: id => /^[a-z0-9][a-z0-9_-]*$/.test(id),
162
+ providerError: id => `Advisory-context provider ${id} must be an object or factory`,
163
+ validateProvider: assertAdvisoryContextProviderIdentity,
164
+ });
165
+ }
166
+
167
+ function configureBrowserVerificationProviders(context, configured) {
168
+ context.browserVerificationProviders = registerProviders(configured, {
169
+ label: "browserVerificationProviders",
170
+ code: E_BROWSER_VERIFICATION_PROVIDER_INVALID,
171
+ idError: id => `Invalid browser-verification provider ID: ${id}`,
172
+ validateId: id => BROWSER_VERIFICATION_PROVIDER_ID_PATTERN.test(id),
173
+ providerError: id => `Browser-verification provider ${id} must be an object with verify() or a factory`,
174
+ validateProvider: (provider, id) => {
175
+ assertBrowserVerificationProviderIdentity(provider, { expectedId: id });
176
+ assertBrowserVerificationProvider(provider, { label: `browser-verification provider "${id}"` });
177
+ },
178
+ });
179
+ }
180
+
181
+ function configureSecurityReviewProviders(context, configured) {
182
+ context.securityReviewProviders = registerProviders(configured, {
183
+ label: "securityReviewProviders",
184
+ code: E_SECURITY_REVIEW_PROVIDER_INVALID,
185
+ idError: id => `Invalid security-review provider ID: ${id}`,
186
+ validateId: id => SECURITY_REVIEW_PROVIDER_ID_PATTERN.test(id),
187
+ providerError: id => `Security-review provider ${id} must be an object with review() or a factory`,
188
+ validateProvider: (provider, id) => {
189
+ assertSecurityReviewProviderIdentity(provider, { expectedId: id });
190
+ assertSecurityReviewProvider(provider, { label: `security-review provider "${id}"` });
191
+ },
192
+ });
193
+ }
194
+
86
195
  export function createForgeLoopContext(options = {}) {
87
196
  const authorityContext = createAuthorityContext(options);
88
197
  const context = { authorityContext };
@@ -98,70 +207,23 @@ export function createForgeLoopContext(options = {}) {
98
207
  context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
99
208
  }
100
209
  if (options?.usageProvider !== undefined) {
101
- if (!options.usageProvider
102
- || typeof options.usageProvider !== "object"
103
- || Array.isArray(options.usageProvider)
104
- || typeof options.usageProvider.getTaskUsage !== "function") {
105
- const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
106
- error.code = "E_USAGE_INVALID";
107
- throw error;
108
- }
109
- context.usageProvider = options.usageProvider;
210
+ configureUsageProvider(context, options.usageProvider);
110
211
  }
111
212
  if (options?.structuralQualityProviders !== undefined) {
112
- const configured = options.structuralQualityProviders instanceof Map
113
- ? Object.fromEntries(options.structuralQualityProviders.entries())
114
- : options.structuralQualityProviders;
115
- if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
116
- const error = new Error("structuralQualityProviders must be an object or Map");
117
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
118
- throw error;
119
- }
120
- const providers = {};
121
- for (const [id, provider] of Object.entries(configured)) {
122
- if (!STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) || id === "sentrux") {
123
- const error = new Error(`Invalid or reserved structural-quality provider ID: ${id}`);
124
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
125
- throw error;
126
- }
127
- if (typeof provider !== "function"
128
- && (!provider || typeof provider !== "object" || Array.isArray(provider))) {
129
- const error = new Error(`Structural-quality provider ${id} must be an object or factory`);
130
- error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
131
- throw error;
132
- }
133
- providers[id] = provider;
134
- }
135
- context.structuralQualityProviders = Object.freeze(providers);
213
+ configureStructuralQualityProviders(context, options.structuralQualityProviders);
136
214
  }
137
215
  if (options?.advisoryContextProviders !== undefined) {
138
- const configured = options.advisoryContextProviders instanceof Map
139
- ? Object.fromEntries(options.advisoryContextProviders.entries())
140
- : options.advisoryContextProviders;
141
- if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
142
- const error = new Error("advisoryContextProviders must be an object or Map");
143
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
144
- throw error;
145
- }
146
- const providers = {};
147
- for (const [id, provider] of Object.entries(configured)) {
148
- if (!/^[a-z0-9][a-z0-9_-]*$/.test(id)) {
149
- const error = new Error(`Invalid advisory-context provider ID: ${id}`);
150
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
151
- throw error;
152
- }
153
- if (typeof provider !== "function"
154
- && (!provider || typeof provider !== "object" || Array.isArray(provider))) {
155
- const error = new Error(`Advisory-context provider ${id} must be an object or factory`);
156
- error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
157
- throw error;
158
- }
159
- if (typeof provider !== "function") {
160
- assertAdvisoryContextProviderIdentity(provider, id);
161
- }
162
- providers[id] = provider;
163
- }
164
- context.advisoryContextProviders = Object.freeze(providers);
216
+ configureAdvisoryContextProviders(context, options.advisoryContextProviders);
217
+ }
218
+ if (options?.browserVerificationProviders !== undefined) {
219
+ configureBrowserVerificationProviders(context, options.browserVerificationProviders);
220
+ }
221
+ if (options?.securityReviewProviders !== undefined) {
222
+ configureSecurityReviewProviders(context, options.securityReviewProviders);
223
+ }
224
+ if (options?.persistentStorage !== undefined && typeof options.persistentStorage !== "boolean") {
225
+ throw Object.assign(new Error("persistentStorage must be a boolean"), { code: "E_STORAGE_RUNTIME_CONFIGURATION_INVALID" });
165
226
  }
227
+ if (options?.persistentStorage === true) context.close = attachStorageConnectionOwner(context);
166
228
  return Object.freeze(context);
167
229
  }
@@ -55,6 +55,9 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
55
55
  "execution-profile-benchmark-run",
56
56
  "execution-profile-benchmark-aggregate",
57
57
  "structural-quality",
58
+ "semantic-decision",
59
+ "context-plan",
60
+ "test-utility",
58
61
  ]);
59
62
 
60
63
  export class SchemaValidationError extends Error {
@@ -120,7 +123,6 @@ function validate(value, schema, location, errors, rootSchema = schema) {
120
123
  return candidateErrors.length === 0;
121
124
  });
122
125
  if (matches.length !== 1) errors.push(`${location}: expected exactly one matching schema`);
123
- return;
124
126
  }
125
127
 
126
128
  if (schema.type && !typeMatches(value, schema.type)) {
@@ -210,6 +212,12 @@ export async function readSchema(name, packageRoot = getPackageRoot()) {
210
212
  return schema;
211
213
  }
212
214
 
215
+ function schemaHealthVersion(schema, name) {
216
+ const versions = schema?.properties?.schemaVersion?.enum ?? null;
217
+ const version = schema?.properties?.schemaVersion?.const ?? (name === "task-bundle" && JSON.stringify(versions) === "[1,2]" ? 2 : null);
218
+ return version;
219
+ }
220
+
213
221
  export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
214
222
  const schemas = [];
215
223
  for (const name of SHIPPED_SCHEMA_NAMES) {
@@ -220,13 +228,13 @@ export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
220
228
  assertJsonBytes(bytes, filename);
221
229
  const schema = JSON.parse(bytes.toString("utf8"));
222
230
  assertJsonLimits(schema, filename);
223
- const version = schema?.properties?.schemaVersion?.const ?? null;
231
+ const version = schemaHealthVersion(schema, name);
224
232
  let status = "valid";
225
233
  let error = null;
226
234
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
227
235
  status = "invalid";
228
236
  error = "schema root must be an object";
229
- } else if (version !== 1) {
237
+ } else if (version !== 1 && !(name === "task-bundle" && version === 2)) {
230
238
  status = "unsupported-version";
231
239
  error = `schemaVersion ${version ?? "missing"} is not supported`;
232
240
  }
@@ -0,0 +1,64 @@
1
+ import { E_SECURITY_REVIEW_REQUEST_INVALID } from "../error-codes.js";
2
+
3
+ export const SECURITY_REVIEW_LIMITS = Object.freeze({
4
+ maxProjectPathChars: 4096,
5
+ maxTaskIdChars: 128,
6
+ maxReviewIdChars: 128,
7
+ maxRequirementChars: 2000,
8
+ maxRequirements: 64,
9
+ maxPaths: 128,
10
+ maxPathChars: 512,
11
+ maxCategories: 32,
12
+ maxCategoryChars: 64,
13
+ maxRevisionChars: 256,
14
+ maxFindings: 256,
15
+ maxFindingIdChars: 128,
16
+ maxSeverityChars: 16,
17
+ maxTitleChars: 512,
18
+ maxSummaryChars: 4096,
19
+ maxRuleIdChars: 128,
20
+ maxConfidenceChars: 16,
21
+ maxDiagnosticChars: 4000,
22
+ maxDiagnostics: 64,
23
+ maxResultChars: 524288,
24
+ maxSnapshotDepth: 32,
25
+ maxSnapshotNodes: 4096,
26
+ maxSnapshotChars: 524288,
27
+ maxDurationMs: 86_400_000,
28
+ defaultTimeoutMs: 30_000,
29
+ maxTimeoutMs: 120_000,
30
+ });
31
+
32
+ export const SECURITY_REVIEW_SCOPES = Object.freeze(["FULL", "CHANGED", "SELECTED"]);
33
+ export const SECURITY_REVIEW_SEVERITIES = Object.freeze(["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"]);
34
+ export const SECURITY_REVIEW_CONFIDENCE = Object.freeze(["LOW", "MEDIUM", "HIGH"]);
35
+ export const SECURITY_REVIEW_CATEGORIES = Object.freeze([
36
+ "DEPENDENCY", "SECRETS", "INJECTION", "AUTHENTICATION", "AUTHORIZATION",
37
+ "CRYPTOGRAPHY", "CONFIGURATION", "NETWORK", "FILESYSTEM", "SUPPLY_CHAIN",
38
+ "UNSAFE_EXECUTION", "OTHER",
39
+ ]);
40
+
41
+ export const SECURITY_REVIEW_TRUST = Object.freeze({
42
+ authority: "OBSERVATION",
43
+ evidenceAuthority: "NONE",
44
+ actionability: "NON_EXECUTABLE",
45
+ trustRole: "NON_EVIDENCE_SECURITY_REVIEW",
46
+ persisted: false,
47
+ lifecycleAuthority: false,
48
+ completionAuthority: false,
49
+ evidenceRequiresForgeLoopValidation: true,
50
+ });
51
+
52
+ export const SECURITY_REVIEW_BLOCKED_RESULT_FIELDS = Object.freeze([
53
+ "complete", "completed", "nextAction", "releaseClaims", "mutationAllowed", "taskPhase",
54
+ "lifecycleState", "receipt", "evidence", "evidenceStatus", "check", "gate", "satisfied",
55
+ "ownership", "writeClaims", "event", "events", "transaction", "installation", "command",
56
+ "commandArgv", "executable", "shell", "environment", "credentials", "secrets",
57
+ ]);
58
+
59
+ export function securityReviewRequestError(message, details = null) {
60
+ const error = new Error(message);
61
+ error.code = E_SECURITY_REVIEW_REQUEST_INVALID;
62
+ error.details = details;
63
+ return error;
64
+ }
@@ -0,0 +1,245 @@
1
+ import {
2
+ E_SECURITY_REVIEW_OUTPUT_LIMIT,
3
+ E_SECURITY_REVIEW_RESULT_INVALID,
4
+ } from "../error-codes.js";
5
+ import { deepFreeze, normalizePortableText } from "../portable-context.js";
6
+ import {
7
+ SECURITY_REVIEW_BLOCKED_RESULT_FIELDS,
8
+ SECURITY_REVIEW_CATEGORIES,
9
+ SECURITY_REVIEW_CONFIDENCE,
10
+ SECURITY_REVIEW_LIMITS,
11
+ SECURITY_REVIEW_SCOPES,
12
+ SECURITY_REVIEW_SEVERITIES,
13
+ SECURITY_REVIEW_TRUST,
14
+ } from "./constants.js";
15
+
16
+ function resultError(message, details = null, code = E_SECURITY_REVIEW_RESULT_INVALID) {
17
+ const error = new Error(message);
18
+ error.code = code;
19
+ error.details = details;
20
+ return error;
21
+ }
22
+
23
+ function limitError(message, details = null) {
24
+ return resultError(message, details, E_SECURITY_REVIEW_OUTPUT_LIMIT);
25
+ }
26
+
27
+ function text(label, value, maxLength, { optional = false } = {}) {
28
+ try {
29
+ return normalizePortableText(value, { label, maxLength, optional });
30
+ } catch {
31
+ throw resultError(`${label} is invalid.`);
32
+ }
33
+ }
34
+
35
+ function consumeSnapshotBudget(value, label, state, depth) {
36
+ if (depth > SECURITY_REVIEW_LIMITS.maxSnapshotDepth) {
37
+ throw limitError(`${label} exceeds the maximum result nesting depth.`);
38
+ }
39
+ state.nodes += 1;
40
+ if (state.nodes > SECURITY_REVIEW_LIMITS.maxSnapshotNodes) {
41
+ throw limitError("Security review result exceeds its maximum snapshot node count.");
42
+ }
43
+ if (typeof value === "string") {
44
+ state.chars += value.length;
45
+ if (state.chars > SECURITY_REVIEW_LIMITS.maxSnapshotChars) {
46
+ throw limitError("Security review result exceeds its maximum snapshot character count.");
47
+ }
48
+ }
49
+ }
50
+
51
+ function strictSnapshotArray(value, label, seen, state, depth) {
52
+ seen.add(value);
53
+ try {
54
+ const output = [];
55
+ for (let index = 0; index < value.length; index += 1) {
56
+ if (!Object.prototype.hasOwnProperty.call(value, index)) throw resultError(`${label} contains a sparse array.`);
57
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
58
+ if (!descriptor || descriptor.get || descriptor.set) throw resultError(`${label}[${index}] is an accessor.`);
59
+ output.push(strictSnapshot(value[index], `${label}[${index}]`, seen, state, depth + 1));
60
+ }
61
+ return output;
62
+ } finally {
63
+ seen.delete(value);
64
+ }
65
+ }
66
+
67
+ function strictSnapshotObject(value, label, seen, state, depth) {
68
+ const prototype = Object.getPrototypeOf(value);
69
+ if (value instanceof Date || value instanceof Map || value instanceof Set || value instanceof Promise
70
+ || (prototype !== Object.prototype && prototype !== null)) {
71
+ throw resultError(`${label} contains a non-plain object.`);
72
+ }
73
+ seen.add(value);
74
+ try {
75
+ const output = {};
76
+ for (const key of Reflect.ownKeys(value)) {
77
+ if (typeof key !== "string") throw resultError(`${label} contains a symbol key.`);
78
+ if (key === "toJSON") throw resultError(`${label} contains a custom toJSON method.`);
79
+ state.chars += key.length;
80
+ if (state.chars > SECURITY_REVIEW_LIMITS.maxSnapshotChars) {
81
+ throw limitError("Security review result exceeds its maximum snapshot character count.");
82
+ }
83
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
84
+ if (!descriptor || descriptor.get || descriptor.set) throw resultError(`${label}.${key} is an accessor.`);
85
+ output[key] = strictSnapshot(value[key], `${label}.${key}`, seen, state, depth + 1);
86
+ }
87
+ return output;
88
+ } finally {
89
+ seen.delete(value);
90
+ }
91
+ }
92
+
93
+ function strictSnapshot(value, label = "security review result", seen = new Set(), state = { nodes: 0, chars: 0 }, depth = 0) {
94
+ consumeSnapshotBudget(value, label, state, depth);
95
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
96
+ if (typeof value === "number") {
97
+ if (!Number.isFinite(value)) throw resultError(`${label} contains a non-finite number.`);
98
+ return value;
99
+ }
100
+ if (typeof value !== "object") throw resultError(`${label} contains an unsupported value.`);
101
+ if (seen.has(value)) throw resultError(`${label} contains a circular reference.`);
102
+ return Array.isArray(value)
103
+ ? strictSnapshotArray(value, label, seen, state, depth)
104
+ : strictSnapshotObject(value, label, seen, state, depth);
105
+ }
106
+
107
+ function rejectUnknown(source, allowed, label) {
108
+ for (const key of Reflect.ownKeys(source)) {
109
+ if (typeof key !== "string" || !allowed.includes(key)) {
110
+ throw resultError(`${label} contains an unsupported field.`, { field: typeof key === "string" ? key : String(key) });
111
+ }
112
+ }
113
+ }
114
+
115
+ function assertSafeText(label, value, maxLength) {
116
+ const normalized = text(label, value, maxLength);
117
+ if (/(?:authorization\s*:\s*bearer|cookie\s*:|(?:token|secret|password)=|-----begin|file:\/\/(?:\/|%2f)|(?:^|\/)Users\/|(?:^|\/)home\/)/i.test(normalized)) {
118
+ throw resultError(`${label} contains sensitive or non-portable content.`);
119
+ }
120
+ return normalized;
121
+ }
122
+
123
+ function normalizePath(value, label) {
124
+ const path = assertSafeText(label, value, SECURITY_REVIEW_LIMITS.maxPathChars).replaceAll("\\", "/");
125
+ if (path.startsWith("/") || /^[a-z]:\//i.test(path) || path.startsWith("//") || /^file:/i.test(path)
126
+ || path.split("/").includes("..") || path === "." || path.endsWith("/") || path.includes("//")) {
127
+ throw resultError(`${label} must be a project-relative non-escaping path.`);
128
+ }
129
+ return path;
130
+ }
131
+
132
+ function assertFindingScope(path, scope, requestedPaths, index) {
133
+ if (scope === "FULL" || requestedPaths.length === 0) return;
134
+ const inScope = requestedPaths.some((root) => path === root || path.startsWith(`${root}/`));
135
+ if (!inScope) {
136
+ throw resultError(`findings[${index}].path is outside the requested ${scope.toLowerCase()} scope.`, {
137
+ path,
138
+ requestedPaths,
139
+ });
140
+ }
141
+ }
142
+
143
+ function normalizeFinding(raw, index, scope, requestedPaths) {
144
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError(`findings[${index}] is invalid.`);
145
+ rejectUnknown(raw, ["id", "category", "severity", "title", "summary", "path", "line", "ruleId", "confidence"], `findings[${index}]`);
146
+ const finding = {
147
+ id: assertSafeText(`findings[${index}].id`, raw.id, SECURITY_REVIEW_LIMITS.maxFindingIdChars),
148
+ category: assertSafeText(`findings[${index}].category`, raw.category, SECURITY_REVIEW_LIMITS.maxCategoryChars),
149
+ severity: assertSafeText(`findings[${index}].severity`, raw.severity, SECURITY_REVIEW_LIMITS.maxSeverityChars),
150
+ title: assertSafeText(`findings[${index}].title`, raw.title, SECURITY_REVIEW_LIMITS.maxTitleChars),
151
+ summary: assertSafeText(`findings[${index}].summary`, raw.summary, SECURITY_REVIEW_LIMITS.maxSummaryChars),
152
+ };
153
+ if (!SECURITY_REVIEW_CATEGORIES.includes(finding.category)) throw resultError(`findings[${index}].category is unsupported.`);
154
+ if (!SECURITY_REVIEW_SEVERITIES.includes(finding.severity)) throw resultError(`findings[${index}].severity is unsupported.`);
155
+ if (raw.path !== undefined) {
156
+ finding.path = normalizePath(raw.path, `findings[${index}].path`);
157
+ assertFindingScope(finding.path, scope, requestedPaths, index);
158
+ }
159
+ if (raw.line !== undefined) {
160
+ if (!Number.isInteger(raw.line) || raw.line < 1 || raw.line > 10_000_000) throw resultError(`findings[${index}].line is invalid.`);
161
+ finding.line = raw.line;
162
+ }
163
+ if (raw.ruleId !== undefined) finding.ruleId = assertSafeText(`findings[${index}].ruleId`, raw.ruleId, SECURITY_REVIEW_LIMITS.maxRuleIdChars);
164
+ if (raw.confidence !== undefined) {
165
+ finding.confidence = assertSafeText(`findings[${index}].confidence`, raw.confidence, SECURITY_REVIEW_LIMITS.maxConfidenceChars);
166
+ if (!SECURITY_REVIEW_CONFIDENCE.includes(finding.confidence)) throw resultError(`findings[${index}].confidence is unsupported.`);
167
+ }
168
+ return deepFreeze(finding);
169
+ }
170
+
171
+ function normalizeDiagnostics(raw) {
172
+ if (raw === undefined) return [];
173
+ if (!Array.isArray(raw) || raw.length > SECURITY_REVIEW_LIMITS.maxDiagnostics) throw limitError("diagnostics exceeds its limit.");
174
+ return raw.map((entry, index) => assertSafeText(`diagnostics[${index}]`, entry, SECURITY_REVIEW_LIMITS.maxDiagnosticChars));
175
+ }
176
+
177
+ function deriveSummary(findings) {
178
+ const bySeverity = Object.fromEntries(SECURITY_REVIEW_SEVERITIES.map((severity) => [severity, 0]));
179
+ const byCategory = Object.fromEntries(SECURITY_REVIEW_CATEGORIES.map((category) => [category, 0]));
180
+ for (const finding of findings) {
181
+ bySeverity[finding.severity] += 1;
182
+ byCategory[finding.category] += 1;
183
+ }
184
+ return deepFreeze({ total: findings.length, bySeverity: deepFreeze(bySeverity), byCategory: deepFreeze(byCategory) });
185
+ }
186
+
187
+ function normalizeSummary(raw, expected) {
188
+ if (raw === undefined) return expected;
189
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError("summary is invalid.");
190
+ rejectUnknown(raw, ["total", "bySeverity", "byCategory"], "summary");
191
+ if (JSON.stringify(raw) !== JSON.stringify(expected)) throw resultError("summary does not match normalized findings.");
192
+ return expected;
193
+ }
194
+
195
+ function normalizeRequestedPaths(requestedPaths) {
196
+ if (!Array.isArray(requestedPaths)) throw resultError("requestedPaths must be an array.");
197
+ const normalized = requestedPaths.map((path, index) => normalizePath(path, `requestedPaths[${index}]`));
198
+ if (new Set(normalized).size !== normalized.length) throw resultError("requestedPaths must not contain duplicates.");
199
+ return normalized;
200
+ }
201
+
202
+ export function normalizeSecurityReviewResult(raw, {
203
+ provider, taskId, reviewId, scope, requestedPaths = [],
204
+ } = {}) {
205
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw resultError("security review result must be an object.");
206
+ if (!SECURITY_REVIEW_SCOPES.includes(scope)) throw resultError("scope is unsupported.");
207
+ const normalizedRequestedPaths = normalizeRequestedPaths(requestedPaths);
208
+ if (scope === "SELECTED" && normalizedRequestedPaths.length === 0) {
209
+ throw resultError("SELECTED scope requires requestedPaths.");
210
+ }
211
+ const snapshot = strictSnapshot(raw);
212
+ for (const field of SECURITY_REVIEW_BLOCKED_RESULT_FIELDS) {
213
+ if (Object.prototype.hasOwnProperty.call(snapshot, field)) throw resultError("security review result contains a reserved authority field.", { field });
214
+ }
215
+ rejectUnknown(snapshot, ["findings", "diagnostics", "durationMs", "summary"], "security review result");
216
+ if (!Array.isArray(snapshot.findings)) throw resultError("findings must be an array.");
217
+ if (snapshot.findings.length > SECURITY_REVIEW_LIMITS.maxFindings) throw limitError("Too many findings.", { count: snapshot.findings.length });
218
+ const findings = snapshot.findings.map((finding, index) => normalizeFinding(finding, index, scope, normalizedRequestedPaths));
219
+ const ids = findings.map((finding) => finding.id);
220
+ if (new Set(ids).size !== ids.length) throw resultError("findings must not contain duplicate IDs.");
221
+ const summary = deriveSummary(findings);
222
+ normalizeSummary(snapshot.summary, summary);
223
+ let durationMs;
224
+ if (snapshot.durationMs !== undefined) {
225
+ if (!Number.isInteger(snapshot.durationMs) || snapshot.durationMs < 0 || snapshot.durationMs > SECURITY_REVIEW_LIMITS.maxDurationMs) {
226
+ throw resultError("durationMs is invalid.");
227
+ }
228
+ durationMs = snapshot.durationMs;
229
+ }
230
+ const result = {
231
+ taskId: assertSafeText("taskId", taskId, SECURITY_REVIEW_LIMITS.maxTaskIdChars),
232
+ reviewId: assertSafeText("reviewId", reviewId, SECURITY_REVIEW_LIMITS.maxReviewIdChars),
233
+ scope,
234
+ requestedPaths: Object.freeze(normalizedRequestedPaths),
235
+ provider: deepFreeze({ id: provider.id, ...(provider.version === undefined ? {} : { version: provider.version }) }),
236
+ findings: Object.freeze(findings),
237
+ summary,
238
+ diagnostics: Object.freeze(normalizeDiagnostics(snapshot.diagnostics)),
239
+ ...(durationMs === undefined ? {} : { durationMs }),
240
+ ...SECURITY_REVIEW_TRUST,
241
+ };
242
+ const serialized = JSON.stringify(result);
243
+ if (serialized.length > SECURITY_REVIEW_LIMITS.maxResultChars) throw limitError("Security review result exceeds its size limit.", { chars: serialized.length });
244
+ return deepFreeze(result);
245
+ }