forgeos 0.1.0-alpha.7 → 0.1.0-alpha.71

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 (598) hide show
  1. package/.agents/skills/forge-agent-fabric/SKILL.md +178 -0
  2. package/.agents/skills/forge-agent-fabric/references/managed-workflows.md +95 -0
  3. package/.agents/skills/forge-agent-fabric/references/repository-analysis.md +102 -0
  4. package/.agents/skills/forge-agent-fabric/scripts/fabric.mjs +46 -0
  5. package/.agents/skills/forge-agent-fabric/scripts/repository.mjs +44 -0
  6. package/.npmignore +10 -0
  7. package/AGENTS.md +142 -90
  8. package/CHANGELOG.md +870 -9
  9. package/LICENSE +21 -0
  10. package/README.md +121 -6
  11. package/adapters/go/README.md +23 -0
  12. package/adapters/go/go.mod +3 -0
  13. package/adapters/go/http.go +149 -0
  14. package/adapters/go/registry.go +234 -0
  15. package/adapters/go/types.go +136 -0
  16. package/adapters/java/README.md +68 -0
  17. package/adapters/java/pom.xml +47 -0
  18. package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
  19. package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
  20. package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
  21. package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
  22. package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
  23. package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
  24. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
  25. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
  26. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
  27. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
  28. package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
  29. package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
  30. package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
  31. package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
  32. package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
  33. package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
  34. package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
  35. package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
  36. package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
  37. package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
  38. package/adapters/java-spring-boot-starter/README.md +32 -0
  39. package/adapters/java-spring-boot-starter/pom.xml +49 -0
  40. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
  41. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
  42. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
  43. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
  44. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
  45. package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
  46. package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
  47. package/bin/forge.mjs +18 -0
  48. package/docs/agent-fabric-programs.md +151 -0
  49. package/docs/agent-fabric.md +582 -0
  50. package/docs/architecture/agent-fabric/PORTABLE_PROJECTS_PLAN.md +136 -0
  51. package/docs/architecture/agent-fabric/README.md +180 -0
  52. package/docs/architecture/agent-fabric/REPOSITORY_MANIFEST_PLAN.md +284 -0
  53. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_ADR.md +81 -0
  54. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS.md +39 -0
  55. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_CORRECTIONS_EVIDENCE.md +53 -0
  56. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_EVIDENCE.md +49 -0
  57. package/docs/architecture/agent-fabric/WORKFLOW_PROGRAM_V2_PLAN.md +590 -0
  58. package/docs/cair-protocol.md +103 -0
  59. package/docs/changelog.md +1070 -0
  60. package/docs/forge-protocol.md +189 -0
  61. package/docs/repository-agent-benchmark.md +179 -0
  62. package/docs/repository-analysis.md +139 -0
  63. package/docs/repository-context-quality.md +179 -0
  64. package/docs/repository-runtime-observation.md +82 -0
  65. package/examples/agent-fabric-v2/README.md +70 -0
  66. package/examples/agent-fabric-v2/item-child.workflow.ts +11 -0
  67. package/examples/agent-fabric-v2/make-request.mjs +4 -0
  68. package/examples/agent-fabric-v2/map-child.workflow.ts +10 -0
  69. package/examples/agent-fabric-v2/migrate.workflow.ts +35 -0
  70. package/examples/agent-fabric-v2/registry.example.json +256 -0
  71. package/examples/agent-fabric-v2/run-ui-fixture.ts +48 -0
  72. package/examples/agent-fabric-v2/ui-audit.workflow.ts +28 -0
  73. package/examples/agent-fabric-v2/ui-fixture-worker.cjs +36 -0
  74. package/examples/agent-fabric-v2/visual-population.json +13 -0
  75. package/examples/agent-fabric-v2/workers/check.mjs +2 -0
  76. package/examples/agent-fabric-v2/workers/common.mjs +7 -0
  77. package/examples/agent-fabric-v2/workers/discover.mjs +2 -0
  78. package/examples/agent-fabric-v2/workers/migrate.mjs +4 -0
  79. package/examples/agent-fabric-v2/workers/review.mjs +3 -0
  80. package/examples/go-billing/go.mod +7 -0
  81. package/examples/go-billing/main.go +120 -0
  82. package/examples/java-billing/pom.xml +60 -0
  83. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
  84. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
  85. package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
  86. package/package.json +60 -5
  87. package/schemas/agent-fabric/v0.1/control-event.schema.json +281 -0
  88. package/schemas/forge-manifest.schema.json +389 -0
  89. package/scripts/field-test-forgeos.mjs +1021 -0
  90. package/scripts/install-agent-fabric-skill.mjs +105 -0
  91. package/src/forge/_generated/releaseManifest.json +1 -2
  92. package/src/forge/_generated/releaseManifest.ts +3 -3
  93. package/src/forge/agent-adapters/index.ts +1592 -125
  94. package/src/forge/agent-adapters/types.ts +225 -1
  95. package/src/forge/agent-fabric/adapter.ts +131 -0
  96. package/src/forge/agent-fabric/attached-snapshot.ts +55 -0
  97. package/src/forge/agent-fabric/attached-task-contract.ts +105 -0
  98. package/src/forge/agent-fabric/attached-task-service.ts +127 -0
  99. package/src/forge/agent-fabric/attached-task-store.ts +109 -0
  100. package/src/forge/agent-fabric/authority.ts +298 -0
  101. package/src/forge/agent-fabric/canonical.ts +135 -0
  102. package/src/forge/agent-fabric/codex-adversarial-review.ts +229 -0
  103. package/src/forge/agent-fabric/codex-sdk-worker.ts +203 -0
  104. package/src/forge/agent-fabric/conductor.ts +754 -0
  105. package/src/forge/agent-fabric/dictionary.ts +12 -0
  106. package/src/forge/agent-fabric/errors.ts +32 -0
  107. package/src/forge/agent-fabric/hardened-conductor.ts +485 -0
  108. package/src/forge/agent-fabric/hardened-reducer.ts +211 -0
  109. package/src/forge/agent-fabric/index.ts +48 -0
  110. package/src/forge/agent-fabric/journal.ts +116 -0
  111. package/src/forge/agent-fabric/local-adaptive-digest-worker.mjs +26 -0
  112. package/src/forge/agent-fabric/local-adaptive-harness.ts +343 -0
  113. package/src/forge/agent-fabric/local-adaptive-service.ts +398 -0
  114. package/src/forge/agent-fabric/local-adaptive-worker.ts +146 -0
  115. package/src/forge/agent-fabric/local-approval-window.ts +211 -0
  116. package/src/forge/agent-fabric/local-change-review-service.ts +374 -0
  117. package/src/forge/agent-fabric/local-coding-worker.ts +285 -0
  118. package/src/forge/agent-fabric/local-control-store.ts +239 -0
  119. package/src/forge/agent-fabric/local-effect-broker.ts +348 -0
  120. package/src/forge/agent-fabric/local-evolution-approval.ts +81 -0
  121. package/src/forge/agent-fabric/local-evolution-profile.ts +87 -0
  122. package/src/forge/agent-fabric/local-evolution-registry.ts +427 -0
  123. package/src/forge/agent-fabric/local-evolution-service.ts +200 -0
  124. package/src/forge/agent-fabric/local-intelligence.ts +281 -0
  125. package/src/forge/agent-fabric/local-paths.ts +18 -0
  126. package/src/forge/agent-fabric/local-task-contract.ts +203 -0
  127. package/src/forge/agent-fabric/local-task-inbox.ts +543 -0
  128. package/src/forge/agent-fabric/local-task-server.ts +374 -0
  129. package/src/forge/agent-fabric/local-task-service.ts +982 -0
  130. package/src/forge/agent-fabric/local-verification.ts +551 -0
  131. package/src/forge/agent-fabric/managed-environment.ts +205 -0
  132. package/src/forge/agent-fabric/managed-run-contract.ts +103 -0
  133. package/src/forge/agent-fabric/managed-run-service.ts +401 -0
  134. package/src/forge/agent-fabric/managed-run-store.ts +165 -0
  135. package/src/forge/agent-fabric/managed-workspace.ts +253 -0
  136. package/src/forge/agent-fabric/p0a.ts +104 -0
  137. package/src/forge/agent-fabric/p0b-model-adapter.ts +434 -0
  138. package/src/forge/agent-fabric/planning.ts +155 -0
  139. package/src/forge/agent-fabric/program-api.ts +37 -0
  140. package/src/forge/agent-fabric/program-contract.ts +283 -0
  141. package/src/forge/agent-fabric/program-dsl.ts +126 -0
  142. package/src/forge/agent-fabric/program-evidence.ts +22 -0
  143. package/src/forge/agent-fabric/program-image.ts +15 -0
  144. package/src/forge/agent-fabric/program-lock.ts +50 -0
  145. package/src/forge/agent-fabric/program-owner.ts +16 -0
  146. package/src/forge/agent-fabric/program-scheduler.ts +38 -0
  147. package/src/forge/agent-fabric/program-service.ts +793 -0
  148. package/src/forge/agent-fabric/program-store.ts +142 -0
  149. package/src/forge/agent-fabric/program-structure.ts +65 -0
  150. package/src/forge/agent-fabric/program-types.ts +110 -0
  151. package/src/forge/agent-fabric/program-view.ts +12 -0
  152. package/src/forge/agent-fabric/program-worker.ts +108 -0
  153. package/src/forge/agent-fabric/project-profile.ts +50 -0
  154. package/src/forge/agent-fabric/project-registry.ts +120 -0
  155. package/src/forge/agent-fabric/project-runtime.ts +85 -0
  156. package/src/forge/agent-fabric/readiness.ts +26 -0
  157. package/src/forge/agent-fabric/reducer.ts +863 -0
  158. package/src/forge/agent-fabric/repository-context.ts +83 -0
  159. package/src/forge/agent-fabric/resource-ledger.ts +266 -0
  160. package/src/forge/agent-fabric/serialized-local-adapter.ts +60 -0
  161. package/src/forge/agent-fabric/types.ts +424 -0
  162. package/src/forge/agent-fabric/validation.ts +500 -0
  163. package/src/forge/agent-fabric/workflow-engine.ts +218 -0
  164. package/src/forge/agent-fabric/workflow-task-actions.ts +104 -0
  165. package/src/forge/agent-fabric/workflow-templates.ts +11 -0
  166. package/src/forge/agent-memory/bridge.ts +1728 -0
  167. package/src/forge/agent-memory/context-pack.ts +284 -0
  168. package/src/forge/agent-memory/hook-runner.ts +331 -0
  169. package/src/forge/agent-memory/mcp.ts +597 -0
  170. package/src/forge/agent-memory/normalize.ts +498 -0
  171. package/src/forge/agent-memory/redaction.ts +103 -0
  172. package/src/forge/agent-memory/sources/claude-code.ts +51 -0
  173. package/src/forge/agent-memory/sources/codex-hook-runner.mjs +343 -0
  174. package/src/forge/agent-memory/sources/codex.ts +161 -0
  175. package/src/forge/agent-memory/sources/cursor.ts +35 -0
  176. package/src/forge/agent-memory/types.ts +215 -0
  177. package/src/forge/bench.ts +248 -0
  178. package/src/forge/brownfield-import/index.ts +896 -0
  179. package/src/forge/brownfield-import/types.ts +127 -0
  180. package/src/forge/cair/action-journal.ts +61 -0
  181. package/src/forge/cair/action-parser.ts +314 -0
  182. package/src/forge/cair/action-validator.ts +40 -0
  183. package/src/forge/cair/actions.ts +1818 -0
  184. package/src/forge/cair/format.ts +77 -0
  185. package/src/forge/cair/index.ts +118 -0
  186. package/src/forge/cair/query.ts +478 -0
  187. package/src/forge/cair/snapshot.ts +315 -0
  188. package/src/forge/cair/types.ts +255 -0
  189. package/src/forge/cli/adaptive.ts +51 -0
  190. package/src/forge/cli/ai.ts +16 -1
  191. package/src/forge/cli/auth.ts +459 -15
  192. package/src/forge/cli/authmd.ts +441 -0
  193. package/src/forge/cli/baseline.ts +112 -0
  194. package/src/forge/cli/build.ts +20 -4
  195. package/src/forge/cli/changed.ts +418 -0
  196. package/src/forge/cli/codex-app-server.ts +877 -0
  197. package/src/forge/cli/commands.ts +1859 -20
  198. package/src/forge/cli/db.ts +209 -2
  199. package/src/forge/cli/deploy.ts +1339 -0
  200. package/src/forge/cli/deps.ts +150 -23
  201. package/src/forge/cli/dev.ts +1137 -53
  202. package/src/forge/cli/docs.ts +265 -0
  203. package/src/forge/cli/doctor.ts +297 -0
  204. package/src/forge/cli/env-placeholders.ts +62 -0
  205. package/src/forge/cli/evolution.ts +40 -0
  206. package/src/forge/cli/fabric.ts +253 -0
  207. package/src/forge/cli/field-test.ts +568 -0
  208. package/src/forge/cli/golden-path.ts +487 -0
  209. package/src/forge/cli/handoff.ts +336 -0
  210. package/src/forge/cli/index.ts +1 -0
  211. package/src/forge/cli/last-run.ts +84 -0
  212. package/src/forge/cli/main.ts +196 -3
  213. package/src/forge/cli/new.ts +169 -17
  214. package/src/forge/cli/next-actions.ts +23 -0
  215. package/src/forge/cli/output.ts +398 -6
  216. package/src/forge/cli/parse.ts +1377 -38
  217. package/src/forge/cli/progress.ts +51 -0
  218. package/src/forge/cli/query.ts +32 -0
  219. package/src/forge/cli/release.ts +35 -11
  220. package/src/forge/cli/repository.ts +151 -0
  221. package/src/forge/cli/run.ts +42 -0
  222. package/src/forge/cli/secrets.ts +137 -1
  223. package/src/forge/cli/seed.ts +581 -0
  224. package/src/forge/cli/self-host.ts +56 -14
  225. package/src/forge/cli/studio.ts +2282 -0
  226. package/src/forge/cli/verify.ts +1458 -32
  227. package/src/forge/cli/windows.ts +23 -0
  228. package/src/forge/cli/workos.ts +3509 -0
  229. package/src/forge/compiler/agent-contract/build.ts +728 -64
  230. package/src/forge/compiler/agent-contract/types.ts +45 -2
  231. package/src/forge/compiler/ai-registry/parse.ts +71 -2
  232. package/src/forge/compiler/api-surface/build.ts +47 -0
  233. package/src/forge/compiler/app-graph/build.ts +112 -9
  234. package/src/forge/compiler/app-graph/extract.ts +107 -0
  235. package/src/forge/compiler/app-graph/module-graph.ts +73 -78
  236. package/src/forge/compiler/app-graph/parser.ts +24 -24
  237. package/src/forge/compiler/app-graph/profile.ts +26 -0
  238. package/src/forge/compiler/app-graph/tsconfig-hash.ts +41 -4
  239. package/src/forge/compiler/app-graph/versions.ts +1 -1
  240. package/src/forge/compiler/classifier/capabilities.ts +3 -2
  241. package/src/forge/compiler/classifier/classify.ts +32 -8
  242. package/src/forge/compiler/classifier/secrets.ts +3 -2
  243. package/src/forge/compiler/classifier/signals.ts +91 -1
  244. package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
  245. package/src/forge/compiler/client-sdk/render-client.ts +295 -13
  246. package/src/forge/compiler/data-graph/build.ts +29 -1
  247. package/src/forge/compiler/data-graph/parse.ts +20 -4
  248. package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
  249. package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
  250. package/src/forge/compiler/data-graph/sql/types.ts +1 -0
  251. package/src/forge/compiler/diagnostics/codes.ts +38 -0
  252. package/src/forge/compiler/diagnostics/create.ts +28 -2
  253. package/src/forge/compiler/diagnostics/index.ts +2 -0
  254. package/src/forge/compiler/emitter/barrel.ts +3 -0
  255. package/src/forge/compiler/emitter/render.ts +9 -0
  256. package/src/forge/compiler/external-manifest/registry.ts +217 -0
  257. package/src/forge/compiler/external-manifest/types.ts +91 -0
  258. package/src/forge/compiler/external-manifest/validate.ts +373 -0
  259. package/src/forge/compiler/frontend-graph/build.ts +142 -14
  260. package/src/forge/compiler/integration/add.ts +991 -33
  261. package/src/forge/compiler/integration/plan.ts +43 -12
  262. package/src/forge/compiler/integration/render.ts +29 -0
  263. package/src/forge/compiler/integration/snapshot.ts +2 -0
  264. package/src/forge/compiler/integration/templates/convex.ts +70 -0
  265. package/src/forge/compiler/integration/templates/index.ts +1 -0
  266. package/src/forge/compiler/integration/templates/render.ts +38 -0
  267. package/src/forge/compiler/integration/templates/types.ts +3 -0
  268. package/src/forge/compiler/integration/templates/workos.ts +1439 -0
  269. package/src/forge/compiler/make-registry/build.ts +8 -9
  270. package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
  271. package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
  272. package/src/forge/compiler/orchestrator/manifest.ts +1 -1
  273. package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
  274. package/src/forge/compiler/orchestrator/plan.ts +249 -10
  275. package/src/forge/compiler/orchestrator/profile.ts +65 -0
  276. package/src/forge/compiler/orchestrator/run.ts +101 -33
  277. package/src/forge/compiler/orchestrator/serialize.ts +33 -8
  278. package/src/forge/compiler/orchestrator/types.ts +1 -1
  279. package/src/forge/compiler/package-graph/compiler.ts +3 -1
  280. package/src/forge/compiler/package-graph/constants.ts +1 -1
  281. package/src/forge/compiler/package-graph/dts-extractor.ts +14 -1
  282. package/src/forge/compiler/package-manager/adapter.ts +4 -1
  283. package/src/forge/compiler/package-manager/commands.ts +4 -0
  284. package/src/forge/compiler/package-manager/executor.ts +30 -1
  285. package/src/forge/compiler/package-manager/version.ts +36 -12
  286. package/src/forge/compiler/package-upgrades/planner.ts +155 -18
  287. package/src/forge/compiler/package-upgrades/types.ts +2 -0
  288. package/src/forge/compiler/policy-registry/build.ts +47 -2
  289. package/src/forge/compiler/policy-registry/parse.ts +32 -2
  290. package/src/forge/compiler/recipes/definitions.ts +63 -0
  291. package/src/forge/compiler/recipes/index.ts +2 -0
  292. package/src/forge/compiler/recipes/profiles.ts +81 -0
  293. package/src/forge/compiler/recipes/registry.ts +6 -0
  294. package/src/forge/compiler/test-graph/build.ts +11 -3
  295. package/src/forge/compiler/types/app-graph.ts +8 -2
  296. package/src/forge/compiler/types/cli.ts +76 -1
  297. package/src/forge/compiler/types/dev-manifest.ts +4 -0
  298. package/src/forge/compiler/types/emit.ts +2 -0
  299. package/src/forge/compiler/types/frontend-graph.ts +2 -2
  300. package/src/forge/compiler/types/integration.ts +1 -0
  301. package/src/forge/compiler/types/package-graph.ts +6 -0
  302. package/src/forge/compiler/types/policy-registry.ts +3 -1
  303. package/src/forge/delta/broker-runner.mjs +5 -0
  304. package/src/forge/delta/broker.ts +429 -0
  305. package/src/forge/delta/classifier.ts +52 -0
  306. package/src/forge/delta/explain.ts +238 -0
  307. package/src/forge/delta/git-observer.ts +67 -0
  308. package/src/forge/delta/ids.ts +44 -0
  309. package/src/forge/delta/index.ts +25 -0
  310. package/src/forge/delta/process-identity.ts +37 -0
  311. package/src/forge/delta/queue-status.ts +190 -0
  312. package/src/forge/delta/recorder.ts +540 -0
  313. package/src/forge/delta/redaction.ts +50 -0
  314. package/src/forge/delta/schema.ts +240 -0
  315. package/src/forge/delta/session.ts +142 -0
  316. package/src/forge/delta/status.ts +1139 -0
  317. package/src/forge/delta/store.ts +3329 -0
  318. package/src/forge/delta/timeline.ts +178 -0
  319. package/src/forge/dev/server.ts +1061 -22
  320. package/src/forge/dev/types.ts +15 -1
  321. package/src/forge/dev/watch.ts +17 -7
  322. package/src/forge/dev-console/cycle.ts +285 -33
  323. package/src/forge/dev-console/types.ts +47 -1
  324. package/src/forge/impact/index.ts +222 -30
  325. package/src/forge/impact/types.ts +33 -1
  326. package/src/forge/intent/index.ts +35 -16
  327. package/src/forge/make/fields.ts +26 -0
  328. package/src/forge/make/index.ts +42 -6
  329. package/src/forge/make/templates.ts +211 -4
  330. package/src/forge/make/types.ts +2 -1
  331. package/src/forge/policy.ts +1 -1
  332. package/src/forge/react/index.ts +160 -2
  333. package/src/forge/refactor/index.ts +1 -0
  334. package/src/forge/repair/rules/index.ts +2 -2
  335. package/src/forge/repository-analysis/adapters/common.ts +72 -0
  336. package/src/forge/repository-analysis/adapters/docker.ts +185 -0
  337. package/src/forge/repository-analysis/adapters/java.ts +133 -0
  338. package/src/forge/repository-analysis/adapters/typescript.ts +350 -0
  339. package/src/forge/repository-analysis/adapters/vue.ts +71 -0
  340. package/src/forge/repository-analysis/analyze.ts +434 -0
  341. package/src/forge/repository-analysis/benchmark.ts +336 -0
  342. package/src/forge/repository-analysis/cache-gc.ts +154 -0
  343. package/src/forge/repository-analysis/check-selection.ts +123 -0
  344. package/src/forge/repository-analysis/context-node.ts +17 -0
  345. package/src/forge/repository-analysis/context.ts +456 -0
  346. package/src/forge/repository-analysis/http-resolution.ts +135 -0
  347. package/src/forge/repository-analysis/identity.ts +45 -0
  348. package/src/forge/repository-analysis/index.ts +18 -0
  349. package/src/forge/repository-analysis/nuxt-resolution.ts +188 -0
  350. package/src/forge/repository-analysis/quality.ts +60 -0
  351. package/src/forge/repository-analysis/retrieval.ts +309 -0
  352. package/src/forge/repository-analysis/runtime-artifacts.ts +195 -0
  353. package/src/forge/repository-analysis/runtime-observation.ts +325 -0
  354. package/src/forge/repository-analysis/scanner.ts +174 -0
  355. package/src/forge/repository-analysis/storage.ts +193 -0
  356. package/src/forge/repository-analysis/test-associations.ts +59 -0
  357. package/src/forge/repository-analysis/types.ts +99 -0
  358. package/src/forge/repository-manifest/index.ts +178 -0
  359. package/src/forge/repository-manifest/types.ts +63 -0
  360. package/src/forge/review/index.ts +162 -12
  361. package/src/forge/review/types.ts +15 -0
  362. package/src/forge/runtime/ai/providers.ts +19 -4
  363. package/src/forge/runtime/auth/config.ts +17 -0
  364. package/src/forge/runtime/auth/evaluate.ts +15 -2
  365. package/src/forge/runtime/auth/resolve.ts +61 -6
  366. package/src/forge/runtime/auth/types.ts +1 -0
  367. package/src/forge/runtime/db/factory.ts +1 -3
  368. package/src/forge/runtime/db/generated-client.ts +13 -2
  369. package/src/forge/runtime/db/memory-adapter.ts +139 -32
  370. package/src/forge/runtime/db/pglite-adapter.ts +285 -2
  371. package/src/forge/runtime/executor.ts +129 -16
  372. package/src/forge/runtime/external/bridge.ts +663 -0
  373. package/src/forge/runtime/runner/run-entry.ts +16 -7
  374. package/src/forge/runtime/telemetry/buffer.ts +4 -1
  375. package/src/forge/runtime/telemetry/scrubber.ts +35 -5
  376. package/src/forge/runtime/webhooks/security.ts +12 -7
  377. package/src/forge/server.ts +71 -7
  378. package/src/forge/ui/index.ts +831 -19
  379. package/src/forge/ui/types.ts +3 -1
  380. package/src/forge/version.ts +1 -1
  381. package/src/forge/vue/index.ts +422 -0
  382. package/src/forge/workspace/baseline.ts +112 -0
  383. package/src/forge/workspace/change-summary.ts +292 -0
  384. package/src/forge/workspace/forge-cli.ts +42 -0
  385. package/src/forge/workspace/git-summary.ts +393 -0
  386. package/templates/agent-workroom/AGENTS.md +29 -0
  387. package/templates/agent-workroom/README.md +34 -0
  388. package/templates/agent-workroom/forge.config.ts +3 -0
  389. package/templates/agent-workroom/package.json +34 -0
  390. package/templates/agent-workroom/pnpm-workspace.yaml +2 -0
  391. package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
  392. package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
  393. package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
  394. package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
  395. package/templates/agent-workroom/src/forge/schema.ts +54 -0
  396. package/templates/agent-workroom/src/policies.ts +6 -0
  397. package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
  398. package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
  399. package/templates/agent-workroom/tsconfig.json +16 -0
  400. package/templates/agent-workroom/web/index.html +13 -0
  401. package/templates/agent-workroom/web/package.json +21 -0
  402. package/templates/agent-workroom/web/src/App.tsx +345 -0
  403. package/templates/agent-workroom/web/src/lib/forge.ts +20 -0
  404. package/templates/agent-workroom/web/src/main.tsx +13 -0
  405. package/templates/agent-workroom/web/src/styles.css +545 -0
  406. package/templates/agent-workroom/web/tsconfig.json +27 -0
  407. package/templates/agent-workroom/web/vite.config.ts +36 -0
  408. package/templates/b2b-support-web/package.json +2 -0
  409. package/templates/b2b-support-web/pnpm-workspace.yaml +2 -0
  410. package/templates/b2b-support-web/web/app/page.tsx +2 -2
  411. package/templates/b2b-support-web/web/lib/forge.ts +9 -2
  412. package/templates/b2b-support-web/web/next.config.ts +24 -0
  413. package/templates/b2b-support-web/web/package.json +1 -1
  414. package/templates/minimal-web/package.json +2 -1
  415. package/templates/minimal-web/pnpm-workspace.yaml +2 -0
  416. package/templates/minimal-web/web/index.html +1 -0
  417. package/templates/minimal-web/web/src/App.tsx +12 -6
  418. package/templates/minimal-web/web/src/lib/forge.ts +9 -2
  419. package/templates/minimal-web/web/src/styles.css +34 -0
  420. package/templates/minimal-web/web/vite.config.ts +36 -0
  421. package/templates/nuxt-web/.vscode/settings.json +14 -0
  422. package/templates/nuxt-web/.yarnrc.yml +1 -0
  423. package/templates/nuxt-web/README.md +30 -0
  424. package/templates/nuxt-web/forge.config.ts +3 -0
  425. package/templates/nuxt-web/package.json +37 -0
  426. package/templates/nuxt-web/pnpm-workspace.yaml +2 -0
  427. package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
  428. package/templates/nuxt-web/src/commands/createNote.ts +26 -0
  429. package/templates/nuxt-web/src/forge/schema.ts +12 -0
  430. package/templates/nuxt-web/src/policies.ts +6 -0
  431. package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
  432. package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
  433. package/templates/nuxt-web/tsconfig.json +17 -0
  434. package/templates/nuxt-web/web/app.vue +67 -0
  435. package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
  436. package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
  437. package/templates/nuxt-web/web/composables/forge.ts +13 -0
  438. package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
  439. package/templates/nuxt-web/web/nuxt.config.ts +11 -0
  440. package/templates/nuxt-web/web/package.json +22 -0
  441. package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
  442. package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
  443. package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
  444. package/templates/nuxt-web/web/tsconfig.json +6 -0
  445. package/templates/vendor-access/.vscode/settings.json +14 -0
  446. package/templates/vendor-access/README.md +30 -0
  447. package/templates/vendor-access/forge.config.ts +3 -0
  448. package/templates/vendor-access/package.json +35 -0
  449. package/templates/vendor-access/pnpm-workspace.yaml +2 -0
  450. package/templates/vendor-access/src/commands/addEvidence.ts +42 -0
  451. package/templates/vendor-access/src/commands/approveAccessRequest.ts +43 -0
  452. package/templates/vendor-access/src/commands/createAccessRequest.ts +49 -0
  453. package/templates/vendor-access/src/commands/seedVendorAccessDemo.ts +200 -0
  454. package/templates/vendor-access/src/forge/schema.ts +74 -0
  455. package/templates/vendor-access/src/policies.ts +11 -0
  456. package/templates/vendor-access/src/queries/listVendorAccessDashboard.ts +24 -0
  457. package/templates/vendor-access/src/queries/liveVendorAccessDashboard.ts +24 -0
  458. package/templates/vendor-access/tsconfig.json +17 -0
  459. package/templates/vendor-access/web/index.html +13 -0
  460. package/templates/vendor-access/web/package.json +21 -0
  461. package/templates/vendor-access/web/src/App.tsx +573 -0
  462. package/templates/vendor-access/web/src/lib/forge.ts +20 -0
  463. package/templates/vendor-access/web/src/main.tsx +205 -0
  464. package/templates/vendor-access/web/src/styles.css +682 -0
  465. package/templates/vendor-access/web/tsconfig.json +18 -0
  466. package/templates/vendor-access/web/vite.config.ts +36 -0
  467. package/src/forge/_generated/actionSubscriptions.json +0 -2
  468. package/src/forge/_generated/actionSubscriptions.ts +0 -10
  469. package/src/forge/_generated/agentAdapterManifest.json +0 -2
  470. package/src/forge/_generated/agentAdapterManifest.ts +0 -73
  471. package/src/forge/_generated/agentContract.json +0 -2
  472. package/src/forge/_generated/agentContract.ts +0 -7829
  473. package/src/forge/_generated/agentQuickstart.md +0 -34
  474. package/src/forge/_generated/agentTools.json +0 -2
  475. package/src/forge/_generated/agentTools.md +0 -16
  476. package/src/forge/_generated/agentTools.ts +0 -12
  477. package/src/forge/_generated/aiContext.ts +0 -125
  478. package/src/forge/_generated/aiModels.json +0 -2
  479. package/src/forge/_generated/aiModels.ts +0 -51
  480. package/src/forge/_generated/aiProviders.json +0 -2
  481. package/src/forge/_generated/aiProviders.ts +0 -23
  482. package/src/forge/_generated/aiRegistry.json +0 -2
  483. package/src/forge/_generated/aiRegistry.ts +0 -31
  484. package/src/forge/_generated/api.json +0 -2
  485. package/src/forge/_generated/api.ts +0 -8
  486. package/src/forge/_generated/appGraph.json +0 -2
  487. package/src/forge/_generated/appGraph.ts +0 -14937
  488. package/src/forge/_generated/appMap.md +0 -55
  489. package/src/forge/_generated/artifactManifest.json +0 -2
  490. package/src/forge/_generated/artifactManifest.ts +0 -7
  491. package/src/forge/_generated/authClaims.json +0 -2
  492. package/src/forge/_generated/authClaims.ts +0 -13
  493. package/src/forge/_generated/authConfig.json +0 -2
  494. package/src/forge/_generated/authConfig.ts +0 -17
  495. package/src/forge/_generated/authContext.ts +0 -23
  496. package/src/forge/_generated/authRegistry.json +0 -2
  497. package/src/forge/_generated/authRegistry.ts +0 -25
  498. package/src/forge/_generated/buildInfo.json +0 -2
  499. package/src/forge/_generated/buildInfo.ts +0 -9
  500. package/src/forge/_generated/capabilityMap.json +0 -2
  501. package/src/forge/_generated/capabilityMap.md +0 -15
  502. package/src/forge/_generated/capabilityMap.ts +0 -17
  503. package/src/forge/_generated/client.ts +0 -282
  504. package/src/forge/_generated/clientApi.ts +0 -9
  505. package/src/forge/_generated/clientManifest.json +0 -2
  506. package/src/forge/_generated/clientManifest.ts +0 -39
  507. package/src/forge/_generated/clientTypes.ts +0 -78
  508. package/src/forge/_generated/configRegistry.json +0 -2
  509. package/src/forge/_generated/configRegistry.ts +0 -4
  510. package/src/forge/_generated/dataGraph.json +0 -2
  511. package/src/forge/_generated/dataGraph.ts +0 -8
  512. package/src/forge/_generated/db.json +0 -2
  513. package/src/forge/_generated/db.ts +0 -2
  514. package/src/forge/_generated/dbSecurityManifest.json +0 -2
  515. package/src/forge/_generated/dbSecurityManifest.ts +0 -15
  516. package/src/forge/_generated/dbSessionContext.json +0 -2
  517. package/src/forge/_generated/dbSessionContext.ts +0 -39
  518. package/src/forge/_generated/deployManifest.json +0 -2
  519. package/src/forge/_generated/deployManifest.ts +0 -14
  520. package/src/forge/_generated/devManifest.json +0 -2
  521. package/src/forge/_generated/devManifest.ts +0 -62
  522. package/src/forge/_generated/envSchema.json +0 -2
  523. package/src/forge/_generated/envSchema.ts +0 -59
  524. package/src/forge/_generated/frontendGraph.json +0 -2
  525. package/src/forge/_generated/frontendGraph.ts +0 -27
  526. package/src/forge/_generated/importGuards.json +0 -2
  527. package/src/forge/_generated/importGuards.ts +0 -686
  528. package/src/forge/_generated/index.ts +0 -68
  529. package/src/forge/_generated/liveProductionManifest.json +0 -2
  530. package/src/forge/_generated/liveProductionManifest.ts +0 -23
  531. package/src/forge/_generated/liveProtocol.json +0 -2
  532. package/src/forge/_generated/liveProtocol.ts +0 -21
  533. package/src/forge/_generated/liveQueryRegistry.json +0 -2
  534. package/src/forge/_generated/liveQueryRegistry.ts +0 -9
  535. package/src/forge/_generated/liveTransportConfig.json +0 -2
  536. package/src/forge/_generated/liveTransportConfig.ts +0 -19
  537. package/src/forge/_generated/makeRegistry.json +0 -2
  538. package/src/forge/_generated/makeRegistry.ts +0 -177
  539. package/src/forge/_generated/makeTemplates.json +0 -2
  540. package/src/forge/_generated/makeTemplates.ts +0 -66
  541. package/src/forge/_generated/mockMap.json +0 -2
  542. package/src/forge/_generated/mockMap.ts +0 -7
  543. package/src/forge/_generated/operationPlaybooks.md +0 -167
  544. package/src/forge/_generated/packageGraph.json +0 -2
  545. package/src/forge/_generated/packageGraph.ts +0 -249334
  546. package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
  547. package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
  548. package/src/forge/_generated/permissionMatrix.json +0 -2
  549. package/src/forge/_generated/permissionMatrix.ts +0 -7
  550. package/src/forge/_generated/policyRegistry.json +0 -2
  551. package/src/forge/_generated/policyRegistry.ts +0 -11
  552. package/src/forge/_generated/queryRegistry.json +0 -2
  553. package/src/forge/_generated/queryRegistry.ts +0 -9
  554. package/src/forge/_generated/react.d.ts +0 -22
  555. package/src/forge/_generated/react.ts +0 -29
  556. package/src/forge/_generated/reactManifest.json +0 -2
  557. package/src/forge/_generated/reactManifest.ts +0 -19
  558. package/src/forge/_generated/rlsPolicies.json +0 -2
  559. package/src/forge/_generated/rlsPolicies.sql +0 -34
  560. package/src/forge/_generated/rlsPolicies.ts +0 -6
  561. package/src/forge/_generated/runtimeGraph.json +0 -2
  562. package/src/forge/_generated/runtimeGraph.ts +0 -8
  563. package/src/forge/_generated/runtimeMatrix.json +0 -2
  564. package/src/forge/_generated/runtimeMatrix.ts +0 -334130
  565. package/src/forge/_generated/runtimeRegistry.ts +0 -2
  566. package/src/forge/_generated/runtimeRules.md +0 -91
  567. package/src/forge/_generated/secretRegistry.json +0 -2
  568. package/src/forge/_generated/secretRegistry.ts +0 -50
  569. package/src/forge/_generated/secretsContext.ts +0 -11
  570. package/src/forge/_generated/serverApi.ts +0 -10
  571. package/src/forge/_generated/sourceMapManifest.json +0 -2
  572. package/src/forge/_generated/sourceMapManifest.ts +0 -7
  573. package/src/forge/_generated/sqlPlan.json +0 -2
  574. package/src/forge/_generated/sqlPlan.ts +0 -88
  575. package/src/forge/_generated/subscriptionManifest.json +0 -2
  576. package/src/forge/_generated/subscriptionManifest.ts +0 -7
  577. package/src/forge/_generated/symbolicationManifest.json +0 -2
  578. package/src/forge/_generated/symbolicationManifest.ts +0 -17
  579. package/src/forge/_generated/telemetryRegistry.json +0 -2
  580. package/src/forge/_generated/telemetryRegistry.ts +0 -9
  581. package/src/forge/_generated/telemetrySinks.json +0 -2
  582. package/src/forge/_generated/telemetrySinks.ts +0 -11
  583. package/src/forge/_generated/tenantScope.json +0 -2
  584. package/src/forge/_generated/tenantScope.ts +0 -8
  585. package/src/forge/_generated/testGraph.json +0 -2
  586. package/src/forge/_generated/testGraph.ts +0 -3452
  587. package/src/forge/_generated/testPlanRegistry.json +0 -2
  588. package/src/forge/_generated/testPlanRegistry.ts +0 -33
  589. package/src/forge/_generated/uiRoutes.json +0 -2
  590. package/src/forge/_generated/uiRoutes.ts +0 -16
  591. package/src/forge/_generated/uiScenarios.json +0 -2
  592. package/src/forge/_generated/uiScenarios.ts +0 -30
  593. package/src/forge/_generated/uiTestManifest.json +0 -2
  594. package/src/forge/_generated/uiTestManifest.ts +0 -27
  595. package/src/forge/_generated/workflowRegistry.json +0 -2
  596. package/src/forge/_generated/workflowRegistry.ts +0 -9
  597. package/src/forge/_generated/workflowSubscriptions.json +0 -2
  598. package/src/forge/_generated/workflowSubscriptions.ts +0 -10
@@ -0,0 +1,793 @@
1
+ import { programVisualization } from "./program-view.ts";
2
+ import { programBlock, programGraph, canonicalItemKey, compareProgramKeys, programSegment, type ProgramBlock } from "./program-structure.ts";
3
+ import { ProgramActivityScheduler, type ActivityScope } from "./program-scheduler.ts";
4
+ import { ProgramOwnerLease } from "./program-owner.ts";
5
+ import { captureKeys, validateVisualCapture } from "./program-evidence.ts";
6
+ import { programImage } from "./program-image.ts";
7
+ import { randomUUID } from "node:crypto";
8
+ import { readFile, readdir, lstat } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { ProgramRunStore } from "./program-store.ts";
11
+ import { lowerWorkflowSource } from "./program-dsl.ts";
12
+ import { prepareProgramWorker, validateProgramCapabilities, type ProgramWorkerAdapter } from "./program-worker.ts";
13
+ import { captureManagedBase, previewManagedArtifacts, publishManagedArtifacts, confirmManagedPublication, confirmManagedBaseline, type ManagedBase, type ManagedArtifact } from "./managed-workspace.ts";
14
+ import { programAssert, programDigest, programValueBytes, programWithin, programPath, registryEntry, validateProgramData, validateWorkflowProgram, ProgramError,
15
+ type WorkflowProgramV2, type ProgramRegistry, type ProgramOperation, type ProgramExpr, type ProgramRef,
16
+ type ProgramValue, type ProgramRunV2, type ProgramCandidate, type ProgramAssessment, type ProgramAssessmentContext, type ProgramRepairResult } from "./program-contract.ts";
17
+
18
+ export const PROGRAM_RUN_ACTIONS = ["program-validate", "program-start", "program-status", "program-wait", "program-pause", "program-resume", "program-signal", "program-replan", "program-cancel", "program-reconcile", "program-apply", "program-history", "program-artifact-get", "program-explain", "program-diff"] as const;
19
+ export type ProgramRunAction = typeof PROGRAM_RUN_ACTIONS[number];
20
+ interface Context { prefix: string; steps: ProgramOperation[]; program: WorkflowProgramV2; inputRef?: string; parent?: Context; item?: unknown; itemKey?: string; state?: unknown; depth: number; chain: Set<string>; dependencies?: Set<string>; activityScopes?: ActivityScope[]; signal?: AbortSignal }
21
+ interface ActivityOutput { data: unknown; candidate: ProgramCandidate; attemptId: string; evidenceRefs?: string[] }
22
+ const asRecord = (value: unknown): Record<string, unknown> => { programAssert(value && typeof value === "object" && !Array.isArray(value), "Object required"); return value as Record<string, unknown>; };
23
+ const idText = (value: unknown): string => { programAssert(typeof value === "string" && value.length > 0 && value.length <= 256, "ID required"); return value; };
24
+ const candidateOf = (value: unknown): ProgramCandidate => { const record = asRecord(value); const candidate = (record.acceptedCandidate ?? record.candidate ?? value) as ProgramCandidate; programAssert(candidate && typeof candidate.digest === "string" && typeof candidate.candidateId === "string", "Candidate reference required"); return candidate; };
25
+
26
+ /** Templates and decisions are persisted as data; the existing owner hosts this service. */
27
+ export class ProgramRunService {
28
+ private active = new Map<string, Promise<void>>();
29
+ private applications = new Map<string, Promise<unknown>>();
30
+ private controllers = new Map<string, AbortController>();
31
+ private attemptControllers = new Map<string, AbortController>();
32
+ private jobs = new Map<string, Promise<unknown>>();
33
+ private scheduler = new ProgramActivityScheduler(4);
34
+ private reservations = new Map<string, () => void>();
35
+ private timers = new Map<string, ReturnType<typeof setTimeout>>();
36
+ private wakeRequests = new Set<string>();
37
+ private closed = false;
38
+ private constructor(readonly store: ProgramRunStore, private registry: ProgramRegistry, private adapter: ProgramWorkerAdapter, private owner: ProgramOwnerLease, private registryFile: boolean) {}
39
+ static async open(root: string, registry?: ProgramRegistry, adapter: ProgramWorkerAdapter = prepareProgramWorker): Promise<ProgramRunService> {
40
+ const store = await ProgramRunStore.open(root);
41
+ const registryFile = !registry;
42
+ if (!registry) {
43
+ try { registry = JSON.parse(await readFile(join(store.root, ".forge/fabric-programs.json"), "utf8")) as ProgramRegistry; }
44
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; registry = { schemas: {}, executors: {}, policies: {}, acceptance: {}, populations: {} }; }
45
+ }
46
+ const owner = await ProgramOwnerLease.acquire(store.directory);
47
+ const service = new ProgramRunService(store, registry, adapter, owner, registryFile);
48
+ try {
49
+ for (const id of await store.list()) {
50
+ const run = (await store.read(id))!;
51
+ if (run.status === "applying" || Object.values(run.attempts).some(attempt => attempt.outcome === "running") || Object.values(run.operations).some(operation => operation.status === "running")) {
52
+ await store.transact(id, "owner-recovery", state => {
53
+ const current = state!; if (current.status === "applying") current.status = "apply-uncertain"; else current.status = "needs-attention";
54
+ for (const attempt of Object.values(current.attempts)) if (attempt.outcome === "running") { attempt.outcome = "uncertain"; attempt.reason = "Owner stopped before observed completion"; }
55
+ for (const operation of Object.values(current.operations)) if (operation.status === "running" || operation.status === "ready") {
56
+ operation.status = Object.values(current.attempts).some(attempt => attempt.operationId === operation.id && attempt.outcome === "uncertain") ? "uncertain" : "ready";
57
+ operation.reason = "Recovered operation; completed child facts remain reusable";
58
+ }
59
+ current.reason = "Recovered incomplete dispatch/application"; return current;
60
+ });
61
+ }
62
+ }
63
+ for (const id of await store.list()) for (const attempt of Object.values((await store.read(id))!.attempts)) if (attempt.outcome === "uncertain" && attempt.reservation?.status === "held") service.reservations.set(attempt.attemptId, service.scheduler.restore(id, attempt.reservation.scopes));
64
+ return service;
65
+ } catch (error) { await owner.close(); throw error; }
66
+ }
67
+ async close(): Promise<void> { if (this.closed) return; this.closed = true; for (const timer of this.timers.values()) clearTimeout(timer); for (const controller of this.controllers.values()) controller.abort(); await Promise.allSettled([...this.active.values(), ...this.applications.values()]); await this.owner.close(); }
68
+ private async authorize(run: ProgramRunV2, executorRef?: ProgramRef): Promise<void> {
69
+ await this.owner.assert(); programAssert(!this.closed, "Owner is closed");
70
+ const live = this.registryFile ? JSON.parse(await readFile(join(this.store.root, ".forge/fabric-programs.json"), "utf8")) as ProgramRegistry : this.registry;
71
+ for (const [table, ref] of [["policies", run.program.policy], ["acceptance", run.program.acceptance], ...(executorRef ? [["executors", executorRef]] : [])] as ["policies" | "acceptance" | "executors", ProgramRef][]) {
72
+ programAssert(programDigest(registryEntry<unknown>(live[table], ref)) === programDigest(registryEntry<unknown>(run.registry[table], ref)), `Owner authorization revoked/changed: ${table}/${ref.id}@${ref.version}`);
73
+ }
74
+ }
75
+ private async cancellable<T>(task: Promise<T>, signal: AbortSignal, fallback: () => T | never, graceMs = 0): Promise<T> {
76
+ let timer: ReturnType<typeof setTimeout> | undefined;
77
+ let abort = () => {};
78
+ try { return await Promise.race([task, new Promise<T>((resolve, reject) => {
79
+ abort = () => { timer = setTimeout(() => { try { resolve(fallback()); } catch (error) { reject(error); } }, graceMs); };
80
+ signal.addEventListener("abort", abort, { once: true }); if (signal.aborted) abort();
81
+ })]); } finally { signal.removeEventListener("abort", abort); if (timer) clearTimeout(timer); }
82
+ }
83
+ private async state(id: string): Promise<ProgramRunV2> { const state = await this.store.read(id); programAssert(state, "Run not found"); return state; }
84
+ private schedule(id: string): void {
85
+ if (this.closed) return;
86
+ if (this.active.has(id)) { this.wakeRequests.add(id); return; }
87
+ if (this.timers.has(id)) { clearTimeout(this.timers.get(id)!); this.timers.delete(id); }
88
+ const task = this.run(id).finally(async () => {
89
+ let wake = false;
90
+ try {
91
+ const state = await this.state(id);
92
+ if (!this.closed && this.wakeRequests.delete(id) && ["waiting", "executing"].includes(state.status)) {
93
+ if (state.status === "waiting") await this.store.transact(id, "event-wakeup", current => { if (current!.status === "waiting") current!.status = "executing"; return current!; }); wake = true;
94
+ } else if (!this.closed && state.status === "waiting") {
95
+ const next = Math.min(Date.parse(state.deadlineAt), ...Object.values(state.waits).filter(wait => !wait.outputRef && wait.deadlineAt).map(wait => Date.parse(wait.deadlineAt!)));
96
+ this.timers.set(id, setTimeout(() => { void this.store.transact(id, "wait-deadline-wakeup", current => { if (current!.status === "waiting") current!.status = "executing"; return current!; }).then(current => { if (current.status === "executing") this.schedule(id); }).catch(() => {}); }, Math.max(1, Math.min(2147483647, next - Date.now()))));
97
+ }
98
+ } finally {
99
+ // close must retain and await the cycle until its asynchronous finalizer
100
+ // finishes reading/writing durable state, before releasing the owner.
101
+ this.active.delete(id); for (const key of this.jobs.keys()) if (key.startsWith(`${id}:`)) this.jobs.delete(key);
102
+ }
103
+ if (wake && !this.closed) this.schedule(id);
104
+ }); this.active.set(id, task);
105
+ }
106
+ private async block(runId: string, block: ProgramBlock, context: Context, scopeId: string, sequential = false, cancelSiblings = false): Promise<unknown> {
107
+ const controller = new AbortController();
108
+ const abort = () => controller.abort(); context.signal?.addEventListener("abort", abort, { once: true });
109
+ if (context.signal?.aborted) controller.abort();
110
+ const child = { ...context, steps: block.steps, signal: controller.signal };
111
+ const outcomes: { id: string; status: string; reason?: string; code?: string }[] = [];
112
+ const execute = async (step: ProgramOperation): Promise<void> => {
113
+ try { await this.ensure(runId, step, child); outcomes.push({ id: `${child.prefix}${step.id}`, status: "completed" }); }
114
+ catch (error) { const code = (error as ProgramError).code, waiting = code === "AF_PROGRAM_WAITING"; outcomes.push({ id: `${child.prefix}${step.id}`, status: waiting ? "waiting" : "failed", reason: (error as Error).message, ...(code ? { code } : {}) }); if (!waiting && code !== "AF_PROGRAM_REVISED" && cancelSiblings) controller.abort(); }
115
+ };
116
+ try {
117
+ if (sequential) { for (const step of block.steps) { await execute(step); if (outcomes.at(-1)?.status !== "completed") break; } }
118
+ else await Promise.all(block.steps.map(execute));
119
+ const failed = outcomes.some(outcome => outcome.status === "failed"), waiting = outcomes.some(outcome => outcome.status === "waiting");
120
+ await this.store.transact(runId, "scope-join", current => { (current!.scopeResults ??= {})[scopeId] = { outcomes, status: failed ? "failed" : waiting ? "waiting" : "completed" }; return current!; });
121
+ if (failed) { const failure = outcomes.find(outcome => outcome.status === "failed" && outcome.code !== "AF_PROGRAM_REVISED") ?? outcomes.find(outcome => outcome.status === "failed")!; throw new ProgramError(failure.code === "AF_PROGRAM_REVISED" ? failure.code : "AF_PROGRAM_SCOPE_FAILED", failure.reason!); }
122
+ if (waiting) throw new ProgramError("AF_PROGRAM_WAITING", `Scope ${scopeId} is waiting`);
123
+ return this.evaluate(runId, block.result, child);
124
+ } finally { context.signal?.removeEventListener("abort", abort); }
125
+ }
126
+ async execute(action: ProgramRunAction, request: Record<string, unknown>): Promise<unknown> {
127
+ await this.owner.assert();
128
+ programAssert((PROGRAM_RUN_ACTIONS as readonly string[]).includes(action), "Unsupported program action");
129
+ programAssert(!Object.hasOwn(request, "registry"), "Registry is owner-controlled project configuration, never request data");
130
+ if (action === "program-validate" || action === "program-start") {
131
+ const program = request.source ? lowerWorkflowSource(String(request.source)) : request.program as WorkflowProgramV2;
132
+ validateWorkflowProgram(program, this.registry);
133
+ if (action === "program-validate") return { valid: true, program, programDigest: programDigest(program), registryDigest: programDigest(this.registry) };
134
+ const requestId = idText(request.requestId), fingerprint = programDigest(request), runId = `program-${programDigest(requestId).slice(7, 39)}`;
135
+ const existing = await this.store.read(runId); if (existing) { programAssert(existing.requestDigest === fingerprint, "Start requestId changed", "AF_PROGRAM_CONFLICT"); return existing; }
136
+ validateProgramData(request.input, registryEntry(this.registry.schemas, program.inputSchema));
137
+ const registry = structuredClone(this.registry), policy = registryEntry(registry.policies, program.policy), acceptance = registryEntry(registry.acceptance, program.acceptance);
138
+ const scope = [...new Set([...(policy.captureScope ?? []), ...acceptance.writeScope.filter(path => programWithin(path, policy.writeScope)), ...policy.writeScope.filter(path => programWithin(path, acceptance.writeScope))])];
139
+ let base: ManagedBase | undefined;
140
+ if (this.adapter === prepareProgramWorker) { programAssert(scope.length > 0, "Process program requires an owner-authorized capture scope"); base = await captureManagedBase(this.store.root, runId, scope); }
141
+ const baselineDigest = base?.digest ?? programDigest({ root: this.store.root });
142
+ if (program.population) {
143
+ const population = registryEntry(registry.populations, program.population);
144
+ if (population.inventoryRoots) {
145
+ programAssert(base && population.inventoryRoots.length > 0, "File inventory requires captured source"); const members: string[] = [];
146
+ const walk = async (path: string, depth: number): Promise<void> => {
147
+ programPath(path); programAssert(depth <= policy.maxDepth && programWithin(path, scope), "Inventory exceeds captured scope/depth"); const stat = await lstat(join(base!.baselineDirectory, path));
148
+ programAssert(!stat.isSymbolicLink(), "Inventory symlink rejected");
149
+ if (stat.isDirectory()) for (const name of (await readdir(join(base!.baselineDirectory, path))).sort(compareProgramKeys)) await walk(`${path}/${name}`, depth + 1);
150
+ else if (stat.isFile() && (!population.extensions?.length || population.extensions.some(extension => path.endsWith(extension)))) { members.push(path); programAssert(members.length <= policy.maxItems, "Inventory item limit exceeded"); }
151
+ };
152
+ for (const root of population.inventoryRoots) await walk(root, 0);
153
+ population.members = [...new Set(members)].sort(compareProgramKeys); population.baselineDigest = baselineDigest; population.evidence = "owner-observed/files-v1: captured baseline and configured inventory roots/extensions";
154
+ }
155
+ programAssert(population.baselineDigest === baselineDigest, "Population inventory does not match captured baseline");
156
+ }
157
+ const inputRef = await this.store.put(request.input, policy.maxOutputBytes), baseRef = base ? await this.store.put(base) : undefined;
158
+ const now = new Date();
159
+ const run = await this.store.transact(runId, "start", () => ({ schemaVersion: 2, runId, requestId, requestDigest: fingerprint,
160
+ program, registry, programDigest: programDigest(program), registryDigest: programDigest(registry),
161
+ inputRef, baselineDigest, ...(baseRef ? { baseRef } : {}), createdAt: now.toISOString(), deadlineAt: new Date(now.getTime() + policy.deadlineMs).toISOString(),
162
+ version: 0, semanticVersion: 1, revision: 1, planRevision: 1, status: "executing", operations: {}, attempts: {}, seals: {}, signals: [], candidates: {}, assessments: {}, coverageReceipts: {}, deltas: {}, waits: {}, repairs: {}, collections: {}, totalAttempts: 0 }), { requestId, fingerprint, expectedVersion: 0 });
163
+ this.schedule(runId); return run;
164
+ }
165
+ const runId = idText(request.runId);
166
+ if (action === "program-status") return this.state(runId);
167
+ if (action === "program-history") return this.store.history(runId);
168
+ if (action === "program-explain") { const state = await this.state(runId); return { runId, graph: programVisualization(state.program, state.operations), status: state.status, reason: state.reason ?? null, operations: state.operations, scopes: state.scopeResults ?? {}, queue: state.queue ?? [], waits: state.waits, assessments: state.assessments, scheduler: this.scheduler.snapshot(), storage: this.store.metrics, guarantees: { attemptsUnit: "owner-visible-dispatch", providerCalls: "adapter-dependent", crossRunCache: false } }; }
169
+ if (action === "program-artifact-get") {
170
+ const state = await this.state(runId), ref = String(request.ref); programAssert(/^sha256:[a-f0-9]{64}$/.test(ref) && JSON.stringify(state).includes(ref), "Artifact reference is not reachable from this run");
171
+ if (request.binary === true) { programAssert(Object.values(state.artifactRecords ?? {}).some(record => record.artifactRef === ref), "Binary artifact not registered for this run"); return { ref, base64: (await this.store.getBinary(ref, Math.min(8 * 1024 * 1024, Number(request.maxBytes ?? 8 * 1024 * 1024)))).toString("base64") }; } return this.store.get(ref);
172
+ }
173
+ if (action === "program-diff") { const state = await this.state(runId), program = request.source ? lowerWorkflowSource(String(request.source)) : request.program as WorkflowProgramV2; validateWorkflowProgram(program, state.registry); return { from: state.programDigest, to: programDigest(program), graph: programGraph(program), changed: state.program.steps.filter(old => !program.steps.some(step => step.id === old.id && programDigest(step) === programDigest(old))).map(step => step.id), added: program.steps.filter(step => !state.program.steps.some(old => old.id === step.id)).map(step => step.id) }; }
174
+ if (action === "program-wait") {
175
+ const waitMs = Math.min(30000, Math.max(0, Number(request.waitMs ?? 1000))); programAssert(Number.isFinite(waitMs), "Invalid wait duration");
176
+ const until = Date.now() + waitMs; let state = await this.state(runId);
177
+ while (state.version === request.cursor && Date.now() < until && !this.closed) { await new Promise(resolve => setTimeout(resolve, 25)); state = await this.state(runId); }
178
+ return state;
179
+ }
180
+ if (action === "program-apply") {
181
+ const running = this.applications.get(runId); if (running) { await running; return this.apply(runId, request); }
182
+ const task = this.apply(runId, request).finally(() => this.applications.delete(runId)); this.applications.set(runId, task); return task;
183
+ }
184
+ if (action === "program-signal") {
185
+ programAssert(typeof request.authorization === "string" && request.authorization.length > 0, "Signal requires host authorization provenance");
186
+ request = { ...request, payloadRef: await this.store.put(request.payload) }; delete request.payload;
187
+ }
188
+ if (action === "program-reconcile" && (request.publication === true || request.publication === "retry")) return this.reconcileApply(runId, request);
189
+ const state = await this.store.transact(runId, action, current => {
190
+ programAssert(current, "Run not found");
191
+ programAssert(current.status !== "applied" && (current.status !== "canceled" || action === "program-reconcile"), "Terminal runs require an explicit successor");
192
+ if (current.status === "applying" || current.status === "apply-uncertain") {
193
+ programAssert(action === "program-signal" || action === "program-cancel", "Apply intent frozen; reconcile before changing execution");
194
+ }
195
+ if (action === "program-pause") current.status = "paused";
196
+ else if (action === "program-resume") {
197
+ programAssert(!this.active.has(runId), "Wait for the current scheduler cycle to stop before resume");
198
+ programAssert(!Object.values(current.attempts).some(attempt => attempt.outcome === "uncertain" || attempt.outcome === "running") && !Object.values(current.operations).some(operation => operation.status === "uncertain"), "Active/uncertain work requires observation or reconciliation");
199
+ current.status = "executing"; delete current.reason;
200
+ // Keep fenced generations and previous diagnostic outputs; materialize decides compatible reuse.
201
+ } else if (action === "program-cancel") {
202
+ if (current.intent && ["applying", "apply-uncertain"].includes(current.status)) { current.status = "apply-uncertain"; current.reason = "Cancellation requested; verify actual files"; }
203
+ else { current.status = Object.values(current.attempts).some(attempt => attempt.outcome === "running" || attempt.outcome === "uncertain") ? "paused" : "canceled"; current.reason = "Cancellation requested; active effects still require observed completion"; }
204
+ } else if (action === "program-signal") {
205
+ programAssert(current.status !== "acceptance-ready", "Result already gated; explicitly pause/replan before steering");
206
+ programAssert(current.signals.length < 1000 && request.payloadRef && /^sha256:[a-f0-9]{64}$/.test(String(request.payloadRef)), "Persisted signal payload reference required");
207
+ programAssert(Number.isSafeInteger(request.generation) && Number(request.generation) > 0 && (!request.expiresAt || Number.isFinite(Date.parse(String(request.expiresAt)))), "Invalid signal generation/expiry");
208
+ const duplicate = current.signals.find(signal => signal.id === request.signalId);
209
+ if (duplicate) { programAssert(duplicate.payloadRef === request.payloadRef && duplicate.target === request.target && duplicate.generation === request.generation && duplicate.type === request.type && duplicate.correlation === request.correlation && duplicate.authorization === request.authorization && duplicate.subjectDigest === (request.subject === undefined ? undefined : programDigest(request.subject)) && duplicate.expiresAt === request.expiresAt, "Signal ID body changed"); return current; }
210
+ current.signals.push({ id: idText(request.signalId), target: idText(request.target), generation: Number(request.generation), type: idText(request.type), correlation: idText(request.correlation), payloadRef: String(request.payloadRef), authorization: String(request.authorization), ...(request.subject === undefined ? {} : { subjectDigest: programDigest(request.subject) }), ...(request.expiresAt ? { expiresAt: String(request.expiresAt) } : {}), status: current.status === "applying" || current.status === "apply-uncertain" ? "pending-after-apply" : "pending" });
211
+ programAssert(new Set(current.signals.map(signal => signal.id)).size === current.signals.length, "Duplicate signal ID");
212
+ } else if (action === "program-replan") {
213
+ const program = request.source ? lowerWorkflowSource(String(request.source)) : request.program as WorkflowProgramV2;
214
+ validateWorkflowProgram(program, current.registry);
215
+ programAssert(current.planRevision < 20 && program.id === current.program.id && programDigest(program.inputSchema) === programDigest(current.program.inputSchema) && programDigest(program.outputSchema) === programDigest(current.program.outputSchema) && programDigest(program.acceptance) === programDigest(current.program.acceptance) && programDigest(program.policy) === programDigest(current.program.policy) && programDigest(program.population ?? null) === programDigest(current.program.population ?? null), "Replan cannot weaken external contracts or exceed revision limits");
216
+ if (request.mode === "additive") {
217
+ programAssert(current.program.steps.every(old => program.steps.some(step => step.id === old.id && programDigest(step) === programDigest(old))), "Additive replan cannot replace existing templates");
218
+ } else if (request.mode === "fenced") {
219
+ programAssert(registryEntry(current.registry.policies, current.program.policy).allowFencedReplan, "Online replacement is disabled by owner policy");
220
+ const changed = new Set(current.program.steps.filter(old => !program.steps.some(step => step.id === old.id && programDigest(step) === programDigest(old))).map(step => step.id));
221
+ programAssert(changed.size > 0, "Fenced replan requires changed templates");
222
+ const affected = new Set(Object.keys(current.operations).filter(id => [...changed].some(root => id === root || id.startsWith(`${root}/`))));
223
+ let grew = true; while (grew) { grew = false; for (const operation of Object.values(current.operations)) if (!affected.has(operation.id) && (operation.dependencies?.some(dependency => affected.has(dependency)) || [...affected].some(parent => operation.id.startsWith(`${parent}/`)))) { affected.add(operation.id); grew = true; } }
224
+ for (const id of affected) { const operation = current.operations[id]; operation.generation++; operation.retired = true; operation.status = operation.status === "running" || operation.status === "uncertain" ? "uncertain" : "skipped"; delete current.seals[id]; delete current.collections[id]; }
225
+ for (const attempt of Object.values(current.attempts)) if (affected.has(attempt.operationId) && attempt.outcome === "running") { attempt.outcome = "uncertain"; attempt.reason = "Fenced attempt: observe its process/effects before retry"; }
226
+ current.status = "paused";
227
+ } else {
228
+ programAssert(request.mode === undefined || request.mode === "barrier", "Unknown replan mode");
229
+ programAssert(!Object.values(current.attempts).some(attempt => attempt.outcome === "running" || attempt.outcome === "uncertain") && !Object.values(current.operations).some(operation => operation.status === "running" || operation.status === "uncertain"), "Replan barrier: observe/reconcile affected workers and controls first");
230
+ const changed = new Set(current.program.steps.filter(old => !program.steps.some(step => step.id === old.id && programDigest(step) === programDigest(old))).map(step => step.id));
231
+ const affected = new Set(Object.keys(current.operations).filter(id => [...changed].some(root => id === root || id.startsWith(`${root}/`))));
232
+ let expanded = true; while (expanded) { expanded = false; for (const operation of Object.values(current.operations)) if (!affected.has(operation.id) && operation.dependencies?.some(dependency => affected.has(dependency))) { affected.add(operation.id); expanded = true; } }
233
+ for (const id of affected) { current.operations[id].generation++; current.operations[id].status = "ready"; current.operations[id].retired = true; delete current.seals[id]; delete current.collections[id]; }
234
+ current.status = "paused";
235
+ }
236
+ current.program = program; current.programDigest = programDigest(program); current.planRevision++; current.semanticVersion++;
237
+ delete current.gateRef; delete current.acceptedCandidate; delete current.resultRef;
238
+ } else if (action === "program-reconcile") {
239
+ programAssert(request.resolution === "failed" && typeof request.reason === "string" && request.reason.length > 0, "Reconciliation requires observed failure/effect evidence");
240
+ if (request.operationId) {
241
+ const operation = current.operations[idText(request.operationId)]; programAssert(operation?.status === "uncertain" && !Object.values(current.attempts).some(attempt => (attempt.operationId === operation.id || attempt.operationId.startsWith(`${operation.id}/`)) && (attempt.outcome === "running" || attempt.outcome === "uncertain")), "Observe/reconcile worker attempts first");
242
+ operation.status = operation.retired ? "skipped" : "needs-attention"; operation.reason = String(request.reason); return current;
243
+ }
244
+ const attempt = current.attempts[idText(request.attemptId)]; programAssert(attempt?.outcome === "uncertain", "Only uncertain attempts can be reconciled");
245
+ attempt.outcome = "infrastructure_failed"; attempt.reason = String(request.reason); if (attempt.reservation) attempt.reservation.status = "released";
246
+ const operation = current.operations[attempt.operationId]; if (operation) { operation.status = operation.retired ? "skipped" : "needs-attention"; operation.reason = String(request.reason); }
247
+ }
248
+ return current;
249
+ }, { requestId: idText(request.requestId), fingerprint: programDigest(request), expectedVersion: Number(request.expectedVersion) });
250
+ if (action === "program-cancel") this.controllers.get(runId)?.abort();
251
+ if (action === "program-replan" && request.mode === "fenced") for (const attempt of Object.values(state.attempts)) if (attempt.outcome === "uncertain") this.attemptControllers.get(attempt.attemptId)?.abort();
252
+ if (action === "program-reconcile") for (const attempt of Object.values(state.attempts)) if (attempt.reservation?.status === "released") { this.reservations.get(attempt.attemptId)?.(); this.reservations.delete(attempt.attemptId); }
253
+ if (action === "program-resume") this.schedule(runId);
254
+ if (action === "program-signal" && ["waiting", "executing"].includes(state.status)) { await this.store.transact(runId, "signal-wakeup", current => { if (current!.status === "waiting") current!.status = "executing"; return current!; }); this.schedule(runId); }
255
+ return state;
256
+ }
257
+ private async run(runId: string): Promise<void> {
258
+ const controller = new AbortController(); this.controllers.set(runId, controller);
259
+ try {
260
+ let state: ProgramRunV2, context: Context;
261
+ for (;;) {
262
+ state = await this.state(runId); context = { prefix: "", steps: state.program.steps, program: state.program, inputRef: state.inputRef, depth: 0, chain: new Set() };
263
+ try { await this.block(runId, { steps: context.steps, result: state.program.result }, { ...context, signal: controller.signal }, "$root"); }
264
+ catch (error) {
265
+ const revised = await this.state(runId);
266
+ // A concurrent additive replan may invalidate a gate while its receipt is
267
+ // being persisted. All roots have settled before block propagates this
268
+ // control-only invalidation; genuine sibling failures still stop the run.
269
+ if ((error as ProgramError).code !== "AF_PROGRAM_REVISED" || revised.status !== "executing" || revised.programDigest === state.programDigest) throw error;
270
+ }
271
+ if ((await this.state(runId)).programDigest === state.programDigest) break;
272
+ // All roots of the previous cycle have settled. Recompute controls/gates for the new
273
+ // semantic version; activity preparation may then issue explicit compatible reuse receipts.
274
+ for (const key of this.jobs.keys()) if (key.startsWith(`${runId}:`)) this.jobs.delete(key);
275
+ }
276
+ const result = await this.evaluate(runId, state.program.result, context);
277
+ validateProgramData(result, registryEntry(state.registry.schemas, state.program.outputSchema));
278
+ const final = await this.state(runId), data = result && typeof result === "object" && !Array.isArray(result) ? result as Record<string, unknown> : undefined;
279
+ programAssert(!Object.values(final.attempts).some(attempt => attempt.outcome === "running" || attempt.outcome === "uncertain") && !Object.values(final.operations).some(operation => operation.status === "running" || operation.status === "uncertain"), "Unsettled work cannot complete the program");
280
+ if (data?.status === "accepted") programAssert(final.gateRef && final.acceptedCandidate, "Accepted result requires current owner gate");
281
+ if (final.gateRef && (data?.candidate || data?.acceptedCandidate)) programAssert(this.resolveCandidate(final, data.candidate ?? data.acceptedCandidate).digest === final.acceptedCandidate?.digest, "Result candidate differs from the owner acceptance gate");
282
+ const resultRef = await this.store.put(result, registryEntry(state.registry.policies, state.program.policy).maxOutputBytes);
283
+ await this.store.transact(runId, "program-result", current => { programAssert(current?.status === "executing" && current.programDigest === state.programDigest && current.semanticVersion === state.semanticVersion, "Execution paused, canceled or revised"); current.resultRef = resultRef; current.status = current.gateRef ? "acceptance-ready" : "completed"; return current; });
284
+ } catch (error) {
285
+ await this.store.transact(runId, "execution-stopped", current => {
286
+ const state = current!;
287
+ if (state.status === "executing") { state.status = (error as ProgramError).code === "AF_PROGRAM_WAITING" ? "waiting" : "needs-attention"; state.reason = (error as Error).message.slice(0, 1000); }
288
+ return state;
289
+ }).catch(() => {});
290
+ } finally { this.controllers.delete(runId); }
291
+ }
292
+ private async checkRunning(runId: string): Promise<ProgramRunV2> {
293
+ const state = await this.state(runId);
294
+ programAssert(!this.closed && state.status === "executing" && Date.now() < Date.parse(state.deadlineAt), "Run paused, canceled, or deadline exceeded", "AF_PROGRAM_STOPPED"); return state;
295
+ }
296
+ private async reference(runId: string, id: string, context: Context): Promise<unknown> {
297
+ const step = context.steps.find(operation => operation.id === id);
298
+ if (step) { context.dependencies?.add(`${context.prefix}${id}`); return this.ensure(runId, step, context); }
299
+ if (context.parent) return this.reference(runId, id, { ...context.parent, dependencies: context.dependencies });
300
+ programAssert(false, `Unknown output ${id}`);
301
+ }
302
+ private async evaluate(runId: string, value: unknown, context: Context): Promise<unknown> {
303
+ const runState = await this.state(runId), limits = registryEntry(runState.registry.policies, context.program.policy);
304
+ const evaluateMany = async (entries: unknown[]): Promise<unknown[]> => {
305
+ programAssert(entries.length <= 10000, "Expression operand budget exceeded"); const values: unknown[] = []; let bytes = 0;
306
+ for (const entry of entries) { const result = await this.evaluate(runId, entry, context); bytes += programValueBytes(result, limits.maxOutputBytes, limits.maxItems); programAssert(bytes <= limits.maxOutputBytes, "Expression expansion byte budget exceeded"); values.push(result); }
307
+ return values;
308
+ };
309
+ if (Array.isArray(value)) return evaluateMany(value);
310
+ if (!value || typeof value !== "object") return value;
311
+ const object = value as Record<string, unknown>;
312
+ if (!Object.hasOwn(object, "$expr")) { const entries = Object.entries(object), values = await evaluateMany(entries.map(([, entry]) => entry)); const result = Object.fromEntries(entries.map(([key], index) => [key, values[index]])); programValueBytes(result, limits.maxOutputBytes, limits.maxItems); return result; }
313
+ const expression = object as unknown as ProgramExpr; programAssert(Array.isArray(expression.args), "Invalid expression");
314
+ const name = expression.$expr, args = expression.args;
315
+ if (name === "literal") return args[0];
316
+ if (name === "workflowInput") return this.store.get(context.inputRef ?? (await this.state(runId)).inputRef);
317
+ if (name === "item") { programAssert(context.item !== undefined, "item requires map context"); return context.item; }
318
+ if (name === "loopState") return context.state;
319
+ if (["output", "acceptedCandidate", "acceptedCandidates", "outputCandidate", "coverageReceipt"].includes(name)) {
320
+ const output = await this.reference(runId, idText(args[0]), context);
321
+ if (name === "output") { const record = output && typeof output === "object" ? output as Record<string, unknown> : undefined; return record && Object.hasOwn(record, "data") && Object.hasOwn(record, "attemptId") ? record.data : output; }
322
+ if (name === "coverageReceipt") return asRecord(output).coverage;
323
+ if (name === "acceptedCandidate") { programAssert(asRecord(output).status === "accepted", "Repair candidate not accepted"); return candidateOf(output); }
324
+ if (name === "outputCandidate") return candidateOf(output);
325
+ const results = asRecord(output).results; programAssert(Array.isArray(results), "Map results required");
326
+ return results.map(result => { programAssert(asRecord(result).status === "accepted", "Map contains unaccepted candidate"); return candidateOf(result); });
327
+ }
328
+ const run = await this.state(runId);
329
+ if (name === "population") { const population = context.program.population ?? run.program.population; programAssert(population, "Population contract required"); return registryEntry(run.registry.populations, population); }
330
+ if (name === "acceptance") return registryEntry(run.registry.acceptance, context.program.acceptance);
331
+ if (name === "acceptanceWriteScope") return registryEntry(run.registry.acceptance, context.program.acceptance).writeScope;
332
+ if (name === "acceptanceChecks" || name === "approvedChecksFor") { const acceptance = registryEntry(run.registry.acceptance, context.program.acceptance); return acceptance.requiredChecksByScope?.[name === "approvedChecksFor" ? "item" : "final"] ?? acceptance.requiredChecks; }
333
+ if (name === "candidateFromBaseline") return this.initialCandidate(runId);
334
+ const values = await evaluateMany(args);
335
+ if (name === "object") return values[0]; if (name === "array") return values;
336
+ if (name === "field") { const data = asRecord(values[0]), key = idText(values[1]); programAssert(Object.hasOwn(data, key), `Field absent ${key}`); return data[key]; }
337
+ if (name === "eq") return programDigest(values[0]) === programDigest(values[1]);
338
+ if (["and", "or", "not"].includes(name)) { programAssert(values.every(value => typeof value === "boolean"), "Boolean expression requires booleans"); if (name === "and") return values.every(Boolean); if (name === "or") return values.some(Boolean); return !values[0]; }
339
+ if (name === "length") { programAssert(Array.isArray(values[0]) || typeof values[0] === "string", "Length requires collection"); return values[0].length; }
340
+ if (name === "concat") { programAssert(values.every(Array.isArray) && (values as unknown[][]).reduce((total, entries) => total + entries.length, 0) <= limits.maxItems, "Concat item budget exceeded"); return (values as unknown[][]).flat(); }
341
+ if (name === "coverageFor") return this.coverage(run, asRecord(values[0]), values[1]);
342
+ programAssert(Array.isArray(values[0]), "Transform requires array"); const data = values[0] as unknown[], key = String(values[1]);
343
+ const policy = registryEntry(run.registry.policies, context.program.policy); programAssert(data.length <= policy.maxItems, "Transform item limit exceeded");
344
+ if (name === "filter") return data.filter(entry => programDigest(asRecord(entry)[key]) === programDigest(values[2]));
345
+ if (name === "unique") { const seen = new Set<string>(); return data.filter(entry => { const id = programDigest(asRecord(entry)[key]); if (seen.has(id)) return false; seen.add(id); return true; }); }
346
+ if (name === "sort") return [...data].sort((a, b) => compareProgramKeys(String(asRecord(a)[key]), String(asRecord(b)[key])));
347
+ if (name === "take") { programAssert(Number.isSafeInteger(values[1]) && Number(values[1]) >= 0, "Take count invalid"); return data.slice(0, Number(values[1])); }
348
+ programAssert(false, `Unknown expression ${name}`);
349
+ }
350
+ private async ensure(runId: string, step: ProgramOperation, context: Context): Promise<unknown> {
351
+ const id = `${context.prefix}${step.id}`;
352
+ programAssert(!context.chain.has(id), `Dependency cycle at ${id}`);
353
+ const jobKey = `${runId}:${id}`;
354
+ if (this.jobs.has(jobKey)) return this.jobs.get(jobKey)!;
355
+ const promise = this.materialize(runId, step, { ...context, chain: new Set([...context.chain, id]) }, id).catch(async error => {
356
+ if ((error as ProgramError).code === "AF_PROGRAM_WAITING") await this.store.transact(runId, "dependency-wait", current => { if (!current!.operations[id]) current!.operations[id] = { id, kind: step.kind, semanticDigest: programDigest(step), inputDigest: programDigest(null), generation: 1, status: "waiting", attempts: [], reason: "Waiting for dependency" }; return current!; });
357
+ throw error;
358
+ });
359
+ this.jobs.set(jobKey, promise); return promise;
360
+ }
361
+ private async materialize(runId: string, step: ProgramOperation, context: Context, id: string): Promise<unknown> {
362
+ const run = await this.checkRunning(runId), policy = registryEntry(run.registry.policies, context.program.policy);
363
+ programAssert(context.depth <= policy.maxDepth, "Invocation depth exceeded");
364
+ const excluded = new Set(["body", "steps", "then", "else", "result", "until", "next", "key", "after", "label"]);
365
+ const dependencies = new Set<string>(); context = { ...context, dependencies };
366
+ for (const predecessor of (step.options.after ?? []) as string[]) await this.reference(runId, predecessor, context);
367
+ const input = await this.evaluate(runId, Object.fromEntries(Object.entries(step.options).filter(([key]) => !excluded.has(key))), context) as Record<string, unknown>;
368
+ const semanticDigest = programDigest(step), inputDigest = programDigest({ input, baselineDigest: run.baselineDigest, programInput: context.inputRef ?? run.inputRef, ...(step.kind === "gate" ? { gateVersion: run.semanticVersion } : {}), ...(context.item === undefined ? {} : { item: context.item }), ...(context.state === undefined ? {} : { state: context.state }) });
369
+ const existing = (await this.state(runId)).operations[id];
370
+ const targetGeneration = existing?.generation ?? 1;
371
+ if (existing?.status === "completed" && !existing.retired && existing.semanticDigest === semanticDigest && existing.inputDigest === inputDigest && existing.outputRef) {
372
+ const output = await this.store.get(existing.outputRef);
373
+ if (step.kind === "repair") programAssert(asRecord(output).status === "accepted", "Repair is not accepted");
374
+ return output;
375
+ }
376
+ programAssert(existing?.status !== "uncertain", "Operation requires reconciliation");
377
+ await this.store.transact(runId, "materialize", current => {
378
+ programAssert(current?.status === "executing", "Run stopped"); programAssert(Object.keys(current.operations).length < policy.maxOperations || current.operations[id], "Operation limit exceeded");
379
+ if (["agent", "command"].includes(step.kind)) return current;
380
+ current.operations[id] = { id, kind: step.kind, semanticDigest, inputDigest, dependencies: [...dependencies], generation: targetGeneration, status: "running", attempts: existing?.attempts ?? [] }; return current;
381
+ });
382
+ try {
383
+ const output = await this.operation(runId, step, input, context, id), outputRef = await this.store.put(output, policy.maxOutputBytes);
384
+ await this.store.transact(runId, "operation-result", current => {
385
+ programAssert(current?.operations[id]?.generation === targetGeneration, "Fenced operation result"); current.operations[id].status = "completed"; current.operations[id].outputRef = outputRef; return current;
386
+ });
387
+ if (step.kind === "repair") programAssert(asRecord(output).status === "accepted", `Repair ${id} ended ${asRecord(output).status}`, "AF_PROGRAM_REPAIR_STOPPED");
388
+ return output;
389
+ } catch (error) {
390
+ await this.store.transact(runId, "operation-stopped", current => {
391
+ const operation = current!.operations[id]; if (operation && operation.generation === targetGeneration && operation.status !== "uncertain") { operation.status = Object.values(current!.attempts).some(attempt => (attempt.operationId === id || attempt.operationId.startsWith(`${id}/`)) && attempt.outcome === "uncertain") ? "uncertain" : (error as ProgramError).code === "AF_PROGRAM_WAITING" ? "waiting" : "needs-attention"; operation.reason = (error as Error).message.slice(0, 1000); } return current!;
392
+ }); throw error;
393
+ }
394
+ }
395
+ private async coverage(run: ProgramRunV2, population: Record<string, unknown>, discovered: unknown): Promise<unknown> {
396
+ const contract = run.program.population ? registryEntry(run.registry.populations, run.program.population) : undefined;
397
+ programAssert(contract && programDigest(population) === programDigest(contract) && contract.baselineDigest === run.baselineDigest, "Coverage contract mismatch");
398
+ const items = Array.isArray(discovered) ? discovered : asRecord(discovered).items; programAssert(Array.isArray(items), "Discovered items required");
399
+ const ids = items.map(item => canonicalItemKey(asRecord(item).id)); programAssert(new Set(ids).size === ids.length, "Duplicate discovered item");
400
+ const exclusions = new Set(contract.exclusions.map(entry => { programAssert(entry.reason.length > 0 && contract.members.includes(entry.id), "Invalid exclusion"); return entry.id; }));
401
+ const expected = contract.members.filter(id => !exclusions.has(id));
402
+ programAssert(programDigest([...ids].sort(compareProgramKeys)) === programDigest([...expected].sort(compareProgramKeys)), "Discovery does not cover owner population");
403
+ if (!ids.length) programAssert(contract.allowNoWork && registryEntry(run.registry.acceptance, run.program.acceptance).allowNoWork, "Empty population cannot approve by vacuity");
404
+ const receipt = { status: ids.length ? "covered" : "no-work", populationDigest: programDigest(contract), baselineDigest: run.baselineDigest, ids: [...ids].sort(compareProgramKeys), exclusions: contract.exclusions, evidence: contract.evidence };
405
+ const receiptId = await this.store.put(receipt);
406
+ await this.store.transact(run.runId, "coverage-receipt", current => { current!.coverageReceipts[receiptId] = receipt; return current!; });
407
+ return { ...receipt, receiptId };
408
+ }
409
+ private async initialCandidate(runId: string): Promise<ProgramCandidate> {
410
+ const run = await this.state(runId), candidate: ProgramCandidate = { candidateId: `candidate-${run.baselineDigest.slice(7, 39)}`, digest: run.baselineDigest, baselineDigest: run.baselineDigest, parents: [], deltas: [], producerAttempts: [] };
411
+ await this.store.transact(runId, "baseline-candidate", current => { current!.candidates[candidate.candidateId] = candidate; return current!; }); return candidate;
412
+ }
413
+ private resolveCandidate(run: ProgramRunV2, reference: unknown): ProgramCandidate {
414
+ const candidate = candidateOf(reference), registered = run.candidates[candidate.candidateId];
415
+ programAssert(registered && programDigest(registered) === programDigest(candidate), "Candidate must match its immutable owner record"); return registered;
416
+ }
417
+ private async artifacts(runId: string, candidate: ProgramCandidate): Promise<ManagedArtifact[]> {
418
+ const run = await this.state(runId); this.resolveCandidate(run, candidate);
419
+ const artifacts: ManagedArtifact[] = []; for (const ref of candidate.deltas) { const delta = run.deltas[ref]; programAssert(delta, "Delta does not belong to this run"); artifacts.push(await this.store.get<ManagedArtifact>(delta.artifactRef)); } return artifacts;
420
+ }
421
+ private async activity(runId: string, id: string, ref: ProgramRef, data: unknown, candidate: ProgramCandidate, requestedScope: string[], context?: Context): Promise<ActivityOutput> {
422
+ const run = await this.checkRunning(runId), executor = registryEntry(run.registry.executors, ref), policy = registryEntry(run.registry.policies, run.program.policy), acceptance = registryEntry(run.registry.acceptance, run.program.acceptance);
423
+ if (executor.inputSchema) validateProgramData(data, registryEntry(run.registry.schemas, executor.inputSchema));
424
+ candidate = this.resolveCandidate(run, candidate);
425
+ const scope = validateProgramCapabilities(executor, policy, acceptance, requestedScope), attemptId = `attempt-${randomUUID()}`;
426
+ const inputDigest = programDigest({ executor, data, candidate, scope, acceptance, policy });
427
+ const completed = run.operations[id];
428
+ if (completed?.status === "completed" && !completed.retired && completed.inputDigest === inputDigest && completed.outputRef) {
429
+ const output = await this.store.get<ActivityOutput>(completed.outputRef); this.resolveCandidate(run, output.candidate); await this.artifacts(runId, output.candidate);
430
+ for (const ref of output.evidenceRefs ?? []) { const record = run.artifactRecords?.[ref]; programAssert(record, "Evidence receipt missing"); await this.store.getBinary(record.artifactRef); } return output;
431
+ }
432
+ await this.authorize(run, ref);
433
+ const generation = completed?.generation ?? 1;
434
+ await this.store.transact(runId, "activity-ready", current => {
435
+ current!.operations[id] = { id, kind: completed?.kind ?? "activity", semanticDigest: programDigest(executor), inputDigest, generation, status: "ready", attempts: completed?.attempts ?? [], dependencies: [...(context?.dependencies ?? completed?.dependencies ?? [])] };
436
+ current!.queue = [...(current!.queue ?? []).filter(entry => entry.id !== id), { id, generation, at: new Date().toISOString() }]; return current!;
437
+ });
438
+ const parentSignal = context?.signal ?? this.controllers.get(runId)?.signal ?? new AbortController().signal;
439
+ const admission = new AbortController(), abortAdmission = () => admission.abort();
440
+ parentSignal.addEventListener("abort", abortAdmission, { once: true }); if (parentSignal.aborted) abortAdmission();
441
+ const admissionDeadline = setTimeout(abortAdmission, Math.max(1, Math.min(2147483647, Date.parse(run.deadlineAt) - Date.now())));
442
+ const scopes = context?.activityScopes ?? [];
443
+ let release: () => void;
444
+ try { release = await this.scheduler.acquire(runId, policy.concurrency, scopes, admission.signal); }
445
+ finally { clearTimeout(admissionDeadline); parentSignal.removeEventListener("abort", abortAdmission); }
446
+ this.reservations.set(attemptId, release);
447
+ let completionObserved = false;
448
+ let timeout: ReturnType<typeof setTimeout> | undefined, removeAbort: (() => void) | undefined;
449
+ try {
450
+ await this.checkRunning(runId);
451
+ const controller = new AbortController(); const abort = () => controller.abort(); parentSignal.addEventListener("abort", abort, { once: true });
452
+ removeAbort = () => parentSignal.removeEventListener("abort", abort); if (parentSignal.aborted) abort();
453
+ this.attemptControllers.set(attemptId, controller);
454
+ timeout = setTimeout(abort, Math.min(executor.timeoutMs, Math.max(1, Date.parse(run.deadlineAt) - Date.now())));
455
+ const base = run.baseRef ? await this.store.get<ManagedBase>(run.baseRef) : undefined;
456
+ const evidence = [];
457
+ for (const ref of (data as { assessmentContext?: ProgramAssessmentContext })?.assessmentContext?.evidenceRefs ?? []) { const capture = run.artifactRecords?.[ref]; programAssert(capture && capture.candidateDigest === candidate.digest, "Evidence ref outside current candidate"); evidence.push({ receiptId: ref, bytes: await this.store.getBinary(capture.artifactRef), mime: capture.mime, itemKey: capture.itemKey }); }
458
+ const preparation = await this.cancellable(this.adapter({ executor, data, candidate, artifacts: await this.artifacts(runId, candidate), base, attemptId, scope, signal: controller.signal, registry: run.registry, evidence,
459
+ onThread: async threadId => { await this.owner.assert(); await this.store.transact(runId, "worker-thread", current => { const attempt = current!.attempts[attemptId]; programAssert(attempt?.outcome === "running" && attempt.reservation?.ownerEpoch === this.owner.epoch, "Fenced worker callback"); attempt.threadId = threadId; return current!; }); } }), controller.signal, () => { throw new ProgramError("AF_PROGRAM_STOPPED", "Preparation canceled before dispatch"); });
460
+
461
+ await this.store.transact(runId, "activity-materialize", current => {
462
+ programAssert(current?.status === "executing" && !controller.signal.aborted, "Preparation stopped before dispatch");
463
+ const parentGeneration = Object.values(current!.operations).filter(operation => id.startsWith(`${operation.id}/`)).reduce((generation, operation) => Math.max(generation, operation.generation), current!.revision);
464
+ const old = current!.operations[id];
465
+ current!.operations[id] = { id, kind: old?.kind ?? "activity", semanticDigest: programDigest(executor), inputDigest, generation: Math.max(old?.generation ?? 0, parentGeneration), status: "running", attempts: old?.attempts ?? [], ...(old?.dependencies ? { dependencies: old.dependencies } : {}) }; return current!;
466
+ });
467
+ await this.authorize(run, ref);
468
+ await this.store.transact(runId, "attempt-dispatch-intent", current => {
469
+ programAssert(current?.status === "executing" && current.totalAttempts < policy.maxAttempts, "Attempt budget exhausted or run stopped");
470
+ current.totalAttempts++; current.attempts[attemptId] = { attemptId, operationId: id, generation: current.operations[id].generation, inputDigest, executorDigest: programDigest(executor), outcome: "running", startedAt: new Date().toISOString(), reservation: { scopes, status: "held", ownerEpoch: this.owner.epoch } };
471
+ current.operations[id].attempts.push(attemptId); current.queue = current.queue?.filter(entry => entry.id !== id); return current;
472
+ });
473
+ const result = await this.cancellable(preparation.execute(), controller.signal, () => ({ outcome: "uncertain" as const, reason: "Worker did not confirm cancellation; reservation retained" }), 1000); await this.owner.assert(); completionObserved = result.outcome === "completed"; clearTimeout(timeout); parentSignal.removeEventListener("abort", abort);
474
+ if (result.outcome !== "completed") {
475
+ await this.store.transact(runId, "attempt-failed", current => { const attempt = current!.attempts[attemptId]; if (attempt.outcome === "running" && attempt.generation === current!.operations[id].generation) { attempt.outcome = result.outcome; attempt.completedAt = new Date().toISOString(); attempt.reason = result.reason ?? result.outcome; if (attempt.reservation && result.outcome !== "uncertain") attempt.reservation.status = "released"; } return current!; });
476
+ throw new ProgramError("AF_PROGRAM_ACTIVITY", result.reason ?? result.outcome);
477
+ }
478
+ validateProgramData(result.data, registryEntry(run.registry.schemas, executor.schema));
479
+ let next = candidate;
480
+ let deltaRecord: ProgramRunV2["deltas"][string] | undefined, deltaId: string | undefined;
481
+ if (result.artifact?.files.length) {
482
+ programAssert(executor.effect === "isolated-write", "Readonly activity changed candidate");
483
+ const deltaRef = await this.store.put(result.artifact, 32 * 1024 * 1024); deltaId = `delta-${attemptId}`; const deltas = [...candidate.deltas, deltaId];
484
+ const digest = base ? (await previewManagedArtifacts(base, [...await this.artifacts(runId, candidate), result.artifact])).digest : programDigest({ parent: candidate.digest, artifact: result.artifact });
485
+ deltaRecord = { artifactRef: deltaRef, inputCandidateDigest: candidate.digest, outputCandidateDigest: digest, producerAttemptId: attemptId };
486
+ next = { candidateId: `candidate-${programDigest({ digest, attemptId }).slice(7, 39)}`, digest, baselineDigest: run.baselineDigest, parents: [candidate.candidateId], deltas, producerAttempts: [...candidate.producerAttempts, attemptId] };
487
+ }
488
+ const evidenceRefs: string[] = [], captureRecords: NonNullable<ProgramRunV2["artifactRecords"]> = {};
489
+ programAssert((result.evidence?.length ?? 0) <= 100 && (result.evidence ?? []).reduce((bytes, artifact) => bytes + artifact.bytes.byteLength, 0) <= 32 * 1024 * 1024, "Evidence aggregate budget exceeded");
490
+ for (const artifact of result.evidence ?? []) {
491
+ const image = programImage(artifact.bytes); programAssert(image.mime === artifact.mime && image.width === artifact.width && image.height === artifact.height, "Evidence image bytes do not match metadata");
492
+ programAssert(executor.effect === "read" && artifact.buildDigest === candidate.digest && artifact.width > 0 && artifact.height > 0 && ["image/png", "image/jpeg", "image/webp"].includes(artifact.mime), "Invalid evidence origin/image metadata");
493
+ const assessment = (data as { assessmentContext?: ProgramAssessmentContext })?.assessmentContext;
494
+ programAssert(assessment && artifact.environmentRef === assessment.environmentRef, "Evidence environment mismatch");
495
+ const ownerPopulation = run.program.population ? registryEntry(run.registry.populations, run.program.population) : undefined;
496
+ const activeCases = ownerPopulation?.visualCases?.filter(entry => !ownerPopulation.exclusions.some(exclusion => canonicalItemKey(exclusion.id) === canonicalItemKey(entry.itemKey)));
497
+ validateVisualCapture(artifact, activeCases);
498
+ programAssert(assessment.phase !== "item" || canonicalItemKey(artifact.itemKey) === assessment.itemKey, "Capture outside current item assessment");
499
+ const artifactRef = await this.store.putBinary(artifact.bytes), receiptId = `capture-${randomUUID()}`;
500
+ const record = { receiptId, artifactRef, candidateDigest: candidate.digest, environmentRef: artifact.environmentRef, itemKey: canonicalItemKey(artifact.itemKey), buildDigest: artifact.buildDigest, mime: artifact.mime, width: artifact.width, height: artifact.height, route: artifact.route, viewport: artifact.viewport, state: artifact.state, producerAttemptId: attemptId };
501
+ captureRecords[receiptId] = record; evidenceRefs.push(receiptId);
502
+ }
503
+ const output: ActivityOutput = { data: result.data, candidate: next, attemptId, ...(evidenceRefs.length ? { evidenceRefs } : {}) }, outputRef = await this.store.put(output, policy.maxOutputBytes);
504
+ await this.store.transact(runId, "attempt-result", current => {
505
+ const attempt = current!.attempts[attemptId]; programAssert(attempt.outcome === "running" && attempt.generation === current!.operations[id].generation, "Fenced attempt result"); attempt.outcome = "completed"; attempt.completedAt = new Date().toISOString(); attempt.outputRef = outputRef; if (attempt.reservation) attempt.reservation.status = "released";
506
+ Object.assign(current!.artifactRecords ??= {}, captureRecords);
507
+ if (deltaId && deltaRecord) current!.deltas[deltaId] = deltaRecord;
508
+ if (result.usage !== undefined) attempt.usage = result.usage;
509
+ if (current!.candidates[next.candidateId]) programAssert(programDigest(current!.candidates[next.candidateId]) === programDigest(next), "Candidate record cannot be overwritten"); else current!.candidates[next.candidateId] = next;
510
+ const operation = current!.operations[id]; operation.status = "completed"; operation.outputRef = outputRef; return current!;
511
+ }); return output;
512
+ } catch (error) {
513
+ await this.store.transact(runId, "dispatch-uncertainty", current => { const attempt = current!.attempts[attemptId]; if (attempt?.outcome === "running") { attempt.outcome = completionObserved ? "invalid_output" : "uncertain"; attempt.reason = (error as Error).message.slice(0, 1000); current!.operations[id].status = completionObserved ? "needs-attention" : "uncertain"; if (completionObserved && attempt.reservation) attempt.reservation.status = "released"; } return current!; }); throw error;
514
+ } finally { if (timeout) clearTimeout(timeout); removeAbort?.(); this.attemptControllers.delete(attemptId); const observed = (await this.state(runId)).attempts[attemptId];
515
+ if (!observed || observed.outcome !== "uncertain") { release(); this.reservations.delete(attemptId); } }
516
+ }
517
+ private async operation(runId: string, step: ProgramOperation, input: Record<string, unknown>, context: Context, id: string): Promise<unknown> {
518
+ const run = await this.state(runId), policy = registryEntry(run.registry.policies, context.program.policy);
519
+ if (step.kind === "value") return input.value;
520
+ if (step.kind === "agent" || step.kind === "command") return this.activity(runId, id, input.executor as ProgramRef, input.input ?? {}, input.candidate ? candidateOf(input.candidate) : await this.initialCandidate(runId), (input.writeScope ?? []) as string[], context);
521
+ if (step.kind === "map") {
522
+ programAssert(Array.isArray(input.items) && input.items.length <= policy.maxItems, "Map items exceed policy");
523
+ const rawItems = input.items as unknown[], rawKeys: string[] = [];
524
+ for (const item of rawItems) rawKeys.push(canonicalItemKey(await this.evaluate(runId, step.options.key, { ...context, item })));
525
+ programAssert(new Set(rawKeys).size === rawKeys.length, "Map keys must be unique after NFC normalization");
526
+ const order = rawKeys.map((key, index) => ({ key, index })); if (input.order !== "input") order.sort((a,b) => compareProgramKeys(a.key,b.key));
527
+ const keys = order.map(entry => entry.key), items = order.map(entry => rawItems[entry.index]);
528
+ if (input.completion === "all-required" && context.program.mode !== "data") {
529
+ const coverage = asRecord(input.coverage), registered = (await this.state(runId)).coverageReceipts[String(coverage.receiptId)];
530
+ programAssert(registered && programDigest(Object.fromEntries(Object.entries(coverage).filter(([key]) => key !== "receiptId"))) === programDigest(registered) && programDigest([...keys].sort(compareProgramKeys)) === programDigest(registered.ids), "All-required map needs owner coverage matching its items");
531
+ }
532
+ const seal = await this.store.put({ ids: keys, collectionDigest: programDigest(input.items), coverage: input.coverage ?? null });
533
+ await this.store.transact(runId, "collection-seal", current => { programAssert(!current!.seals[id] || current!.seals[id] === seal, "Collection identity changed without replan"); current!.seals[id] = seal; return current!; });
534
+ const block = programBlock(step.options.body), body = block.steps, results: unknown[] = new Array(items.length), itemOutcomes: ("completed" | "waiting" | "failed")[] = new Array(items.length); let cursor = 0;
535
+ const concurrency = Math.min(policy.concurrency, Number(input.concurrency ?? policy.concurrency)); programAssert(Number.isSafeInteger(concurrency) && concurrency > 0, "Invalid map concurrency");
536
+ const failures: { id: string; reason: string }[] = [], waiting: string[] = [];
537
+ await Promise.all(Array.from({ length: Math.min(32, items.length) }, async () => {
538
+ for (;;) { const index = cursor++; if (index >= items.length) return;
539
+ const child: Context = { ...context, prefix: `${id}/${programSegment(keys[index])}/`, steps: body, parent: context, item: items[index], itemKey: keys[index], depth: context.depth + 1, chain: new Set(context.chain), activityScopes: [...(context.activityScopes ?? []), { id: `${runId}:${id}`, limit: concurrency }] };
540
+ try { results[index] = await this.block(runId, block, child, child.prefix); itemOutcomes[index] = "completed"; }
541
+ catch (error) { if ((error as ProgramError).code === "AF_PROGRAM_WAITING") { waiting.push(keys[index]); itemOutcomes[index] = "waiting"; results[index] = { status: "waiting" }; } else { failures.push({ id: keys[index], reason: (error as Error).message }); itemOutcomes[index] = "failed"; results[index] = { status: "failed", reason: (error as Error).message }; } }
542
+ }
543
+ }));
544
+ if (waiting.length) throw new ProgramError("AF_PROGRAM_WAITING", `Map waiting: ${waiting.join(", ")}`);
545
+ programAssert(!Object.values((await this.state(runId)).attempts).some(attempt => attempt.operationId.startsWith(`${id}/`) && attempt.outcome === "uncertain"), "Map has uncertain effects");
546
+ if (input.completion === "all-required") programAssert(!failures.length, `Required map item failed: ${failures.slice(0,3).map(failure => failure.reason).join("; ")}`);
547
+ if (input.completion === "quorum") { const min = Number(asRecord(input.quorum).minAccepted); programAssert(min > 0 && min <= keys.length && results.filter((result,index) => itemOutcomes[index] === "completed" && (context.program.mode === "data" || result && typeof result === "object" && asRecord(result).status === "accepted")).length >= min, "Quorum not reached/invalid threshold"); }
548
+ const completed = await this.state(runId), acceptedAssessmentIds: string[] = [], acceptedCandidateIds: string[] = []; let obligationsSatisfied = !failures.length;
549
+ for (const key of keys) {
550
+ const prefix = `${id}/${programSegment(key)}/`, repairs = Object.values(completed.operations).filter(operation => operation.id.startsWith(prefix) && operation.kind === "repair" && !operation.retired && operation.status !== "skipped");
551
+ if (!repairs.length) obligationsSatisfied = false;
552
+ for (const operation of repairs) {
553
+ const result = operation.outputRef ? asRecord(await this.store.get(operation.outputRef)) : {};
554
+ const assessment = result.assessment as ProgramAssessment | undefined;
555
+ if (operation.status !== "completed" || result.status !== "accepted" || !assessment?.receiptId || !completed.assessments[assessment.receiptId]) obligationsSatisfied = false;
556
+ else { acceptedAssessmentIds.push(assessment.receiptId); acceptedCandidateIds.push(this.resolveCandidate(completed, result.acceptedCandidate).candidateId); }
557
+ }
558
+ }
559
+ await this.store.transact(runId, "collection-completion", current => { programAssert(current!.seals[id] === seal, "Collection seal changed"); current!.collections[id] = { seal, coverageReceiptId: input.coverage ? String(asRecord(input.coverage).receiptId) : null, generation: current!.operations[id].generation, ids: [...keys].sort(compareProgramKeys), acceptedAssessmentIds, acceptedCandidateIds, obligationsSatisfied }; return current!; });
560
+ return { items: keys.map((key,index) => itemOutcomes[index] === "completed" ? { key, outcome: "completed", value: results[index] } : { key, outcome: itemOutcomes[index], reason: failures.find(failure => failure.id === key)?.reason }), results, failures, seal, coverage: input.coverage ?? null, status: failures.length ? "partial" : items.length ? "completed" : "no-work" };
561
+ }
562
+ if (step.kind === "branch") {
563
+ const selected = input.condition ? "then" : "else", skipped = input.condition ? "else" : "then";
564
+ programAssert(typeof input.condition === "boolean", "Branch condition must be boolean");
565
+ const block = programBlock(step.options[selected]), body = block.steps, child: Context = { ...context, prefix: `${id}/${selected}/`, steps: body, parent: context, depth: context.depth + 1, chain: new Set(context.chain) };
566
+ await this.store.transact(runId, "branch-selection", current => { for (const record of Object.values(current!.operations)) if (record.id.startsWith(`${id}/${skipped}/`)) { programAssert(!Object.values(current!.attempts).some(attempt => attempt.operationId === record.id && ["running", "uncertain"].includes(attempt.outcome)), "Skipped branch still has unobserved effects"); record.status = "skipped"; record.retired = true; }
567
+ for (const operation of programBlock(step.options[skipped]).steps) { const path = `${id}/${skipped}/${operation.id}`, old = current!.operations[path]; current!.operations[path] = { ...old, id: path, semanticDigest: programDigest(operation), inputDigest: programDigest(null), generation: Math.max(old?.generation ?? 0, current!.revision), status: "skipped", attempts: old?.attempts ?? [] }; } return current!; });
568
+ return this.block(runId, block, child, id);
569
+ }
570
+ if (step.kind === "loop") {
571
+ const maxRounds = Number(input.maxRounds); programAssert(Number.isSafeInteger(maxRounds) && maxRounds > 0 && maxRounds <= policy.maxAttempts, "Bounded loop required");
572
+ let state: unknown = input.initialState; const block = programBlock(step.options.body), body = block.steps;
573
+ for (let round = 0; round < maxRounds; round++) {
574
+ const child: Context = { ...context, prefix: `${id}/round-${round}/`, steps: body, parent: context, state, depth: context.depth + 1, chain: new Set(context.chain) };
575
+ await this.block(runId, block, child, child.prefix);
576
+ const next = await this.evaluate(runId, step.options.next, child), done = await this.evaluate(runId, step.options.until, { ...child, state: next });
577
+ programAssert(typeof done === "boolean", "Loop until requires boolean"); if (done) return next;
578
+ programAssert(programDigest(next) !== programDigest(state), "Loop stalled"); state = next;
579
+ }
580
+ throw new ProgramError("AF_PROGRAM_EXHAUSTED", "Loop exhausted without acceptance");
581
+ }
582
+ if (step.kind === "parallel" || step.kind === "sequence") {
583
+ const block = programBlock(step.options), child = { ...context, prefix: `${id}/`, steps: block.steps, parent: context, depth: context.depth + 1, chain: new Set(context.chain) };
584
+ return this.block(runId, block, child, id, step.kind === "sequence", input.onFailure === "cancel-siblings");
585
+ }
586
+ if (step.kind === "repair") return this.repair(runId, id, input, context);
587
+ if (step.kind === "compose") {
588
+ programAssert(Array.isArray(input.candidates) && input.candidates.length > 0, "Candidates required for compose");
589
+ const candidates = input.candidates.map(candidate => this.resolveCandidate(run, candidate)), refs: string[] = [], seen = new Set<string>();
590
+ for (const candidate of candidates) {
591
+ programAssert(candidate.baselineDigest === run.baselineDigest && run.candidates[candidate.candidateId]?.digest === candidate.digest, "Candidate ancestry/baseline mismatch");
592
+ for (const ref of candidate.deltas) if (!seen.has(ref)) { seen.add(ref); refs.push(ref); }
593
+ }
594
+ const artifacts = await Promise.all(refs.map(ref => { programAssert(run.deltas[ref], "Composition delta missing"); return this.store.get<ManagedArtifact>(run.deltas[ref].artifactRef); })), writers = new Map<string, string>();
595
+ for (let index = 0; index < artifacts.length; index++) for (const file of artifacts[index].files) {
596
+ const prior = writers.get(file.path);
597
+ if (prior) programAssert(candidates.some(candidate => candidate.deltas.includes(prior) && candidate.deltas.includes(refs[index]) && candidate.deltas.indexOf(prior) < candidate.deltas.indexOf(refs[index])), `Independent writers conflict at ${file.path}`);
598
+ writers.set(file.path, refs[index]);
599
+ }
600
+ const base = run.baseRef ? await this.store.get<ManagedBase>(run.baseRef) : undefined, digest = base ? (await previewManagedArtifacts(base, artifacts)).digest : programDigest({ candidates: candidates.map(candidate => candidate.digest), refs });
601
+ const parents = candidates.map(candidate => candidate.candidateId), producerAttempts = [...new Set(candidates.flatMap(candidate => candidate.producerAttempts))];
602
+ const candidate: ProgramCandidate = { candidateId: `candidate-${programDigest({ digest, id, parents, refs, producerAttempts }).slice(7, 39)}`, digest, baselineDigest: run.baselineDigest, parents, deltas: refs, producerAttempts };
603
+ await this.store.transact(runId, "compose-candidate", current => { const prior = current!.candidates[candidate.candidateId]; programAssert(!prior || programDigest(prior) === programDigest(candidate), "Compose candidate record cannot be overwritten"); current!.candidates[candidate.candidateId] = candidate; return current!; }); return { candidate };
604
+ }
605
+ if (step.kind === "gate") return this.gate(runId, id, input);
606
+ if (step.kind === "waitEvent") {
607
+ const generation = run.operations[id].generation, correlation = idText(input.correlation), type = idText(input.type);
608
+ const oldWait = run.waits[id];
609
+ if (oldWait?.generation === generation && oldWait.outputRef) return this.store.get(oldWait.outputRef);
610
+ if (!oldWait || oldWait.generation !== generation) {
611
+ const timeoutMs = input.timeoutMs === undefined ? undefined : Number(input.timeoutMs); programAssert(timeoutMs === undefined || Number.isSafeInteger(timeoutMs) && timeoutMs > 0 && timeoutMs <= policy.deadlineMs, "Wait deadline exceeds policy");
612
+ await this.store.transact(runId, "wait-checkpoint", current => { current!.waits[id] = { generation, ...(timeoutMs ? { deadlineAt: new Date(Math.min(Date.parse(run.deadlineAt), Date.now() + timeoutMs)).toISOString() } : {}) }; return current!; });
613
+ }
614
+ const wait = (await this.state(runId)).waits[id]; programAssert(!wait.deadlineAt || Date.parse(wait.deadlineAt) > Date.now(), "Human wait timed out without approval", "AF_PROGRAM_WAIT_TIMEOUT");
615
+ const signal = (await this.state(runId)).signals.find(signal => signal.target === id && signal.generation === generation && signal.type === type && signal.correlation === correlation && signal.status === "pending" && (input.subject === undefined || signal.subjectDigest === programDigest(input.subject)) && (!signal.expiresAt || Date.parse(signal.expiresAt) > Date.now()));
616
+ programAssert(signal, "Waiting for a matching authorized event", "AF_PROGRAM_WAITING"); const payload = await this.store.get(signal.payloadRef);
617
+ validateProgramData(payload, registryEntry(run.registry.schemas, input.schema as ProgramRef));
618
+ await this.store.transact(runId, "signal-consumed-and-decision", current => {
619
+ const event = current!.signals.find(event => event.id === signal.id); programAssert(event?.status === "pending" && current!.operations[id].generation === generation && (!event.expiresAt || Date.parse(event.expiresAt) > Date.now()), "Signal already consumed, expired or wait generation changed"); event.status = "consumed";
620
+ current!.waits[id] = { ...current!.waits[id], generation, signalId: signal.id, outputRef: signal.payloadRef };
621
+ current!.operations[id].status = "completed"; current!.operations[id].outputRef = signal.payloadRef; return current!;
622
+ }); return payload;
623
+ }
624
+ if (step.kind === "subworkflow") {
625
+ const childProgram = registryEntry(run.registry.programs ?? {}, input.program as ProgramRef); validateWorkflowProgram(childProgram, run.registry, new Set(), { ...(context.item === undefined ? {} : { item: { const: context.item as ProgramValue } }), ...(context.state === undefined ? {} : { state: { const: context.state as ProgramValue } }) });
626
+ programAssert(programDigest(childProgram.policy) === programDigest(context.program.policy) && programDigest(childProgram.acceptance) === programDigest(context.program.acceptance), "Child cannot replace inherited policy/acceptance");
627
+ validateProgramData(input.input, registryEntry(run.registry.schemas, childProgram.inputSchema));
628
+ const inputRef = await this.store.put(input.input, policy.maxOutputBytes);
629
+ const child: Context = { ...context, prefix: `${id}/`, steps: childProgram.steps, program: childProgram, inputRef, parent: context, depth: context.depth + 1, chain: new Set(context.chain) };
630
+ const result = await this.block(runId, { steps: child.steps, result: childProgram.result }, child, id);
631
+ validateProgramData(result, registryEntry(run.registry.schemas, childProgram.outputSchema)); return result;
632
+ }
633
+ programAssert(false, `Unsupported operation ${step.kind}`);
634
+ }
635
+ private async repair(runId: string, id: string, input: Record<string, unknown>, context: Context): Promise<ProgramRepairResult> {
636
+ const run = await this.state(runId), acceptance = registryEntry(run.registry.acceptance, run.program.acceptance);
637
+ const reviewer = registryEntry(run.registry.executors, input.review as ProgramRef), implementer = registryEntry(run.registry.executors, input.implement as ProgramRef);
638
+ programAssert(reviewer.role === "reviewer" && reviewer.effect === "read" && programDigest(reviewer) !== programDigest(implementer), "Repair needs a distinct readonly reviewer executor");
639
+ const maxRounds = Number(input.maxRepairRounds), maxAssessments = Number(input.maxAssessmentAttempts), maxInfrastructure = Number(input.maxInfrastructureAttempts);
640
+ programAssert([maxRounds, maxAssessments, maxInfrastructure].every(limit => Number.isSafeInteger(limit) && limit > 0), "Repair counters must be bounded");
641
+ const origin = run.operations[id], previous = run.repairs[id];
642
+ const checkpoint: ProgramRunV2["repairs"][string] = previous && previous.semanticDigest === origin.semanticDigest && previous.inputDigest === origin.inputDigest ? structuredClone(previous) : {
643
+ semanticDigest: origin.semanticDigest, inputDigest: origin.inputDigest, candidate: this.resolveCandidate(run, input.initialCandidate), rounds: 0, assessments: 0, infrastructure: 0, unchanged: 0, repeated: 0, priorFindings: "", feedback: null, phase: input.entryMode === "implement-first" ? "implement" : "assess",
644
+ };
645
+ // A compatible accepted recipe revalidates its observed assessment in this generation.
646
+ if (checkpoint.phase === "accepted" && checkpoint.assessment) return { status: "accepted", acceptedCandidate: checkpoint.candidate, assessment: checkpoint.assessment };
647
+ checkpoint.infrastructure = Math.max(checkpoint.infrastructure, Object.values(run.attempts).filter(attempt => attempt.operationId.startsWith(`${id}/`) && ["infrastructure_failed", "invalid_output"].includes(attempt.outcome)).length);
648
+ if (checkpoint.infrastructure >= maxInfrastructure) checkpoint.phase = "exhausted";
649
+ const save = async (type: string) => { await this.store.transact(runId, type, current => { programAssert(current!.operations[id].generation === origin.generation && current!.operations[id].semanticDigest === origin.semanticDigest, "Fenced recipe checkpoint"); current!.repairs[id] = structuredClone(checkpoint); return current!; }); };
650
+ const progress = asRecord(input.progressPolicy); programAssert(Number(progress.unchangedCandidateRounds) > 0 && Number(progress.repeatedFindingsRounds) > 0, "Progress policy required");
651
+ const scope = input.writeScope as string[], checks = input.checks as string[];
652
+ const activityInputs = { workflowInput: await this.store.get(context.inputRef ?? run.inputRef), ...(context.item === undefined ? {} : { workItem: context.item }), ...(input.input === undefined ? {} : { inputs: input.input }), writeScope: scope, criteria: acceptance.criteria };
653
+ programAssert(Array.isArray(checks) && (acceptance.requiredChecksByScope?.[input.assessmentScope as "item" | "final"] ?? acceptance.requiredChecks).every(check => checks.includes(check)), "Repair cannot omit required checks");
654
+ const phase = input.assessmentScope === "item" ? "item" : "final";
655
+ const population = run.program.population ? registryEntry(run.registry.populations, run.program.population) : undefined;
656
+ const expectedMembers = (population?.members ?? []).filter(key => !population!.exclusions.some(exclusion => exclusion.id === key)).map(canonicalItemKey);
657
+ const itemKey = context.itemKey;
658
+ if (input.assessmentScope === "item") programAssert(itemKey && expectedMembers.includes(itemKey), "Item assessment must belong to owner population");
659
+ const obligationIds = (acceptance.obligations ?? []).flatMap(obligation => obligation.scope === "final" ? phase === "final" ? [obligation.id] : [] : (phase === "item" ? [itemKey!] : expectedMembers).map(key => `${obligation.id}/${programSegment(key)}`));
660
+ const environmentRef = programDigest({ baseline: run.baselineDigest, inputRef: run.inputRef, policy: run.program.policy });
661
+ const assess = async (): Promise<ProgramAssessment> => {
662
+ const candidate = checkpoint.candidate;
663
+ const assessmentContext: ProgramAssessmentContext = { runId, invocationId: id, assessmentId: `${id}/assessment-${checkpoint.assessments}`, phase, candidateRef: candidate, obligationIds, ...(itemKey ? { itemKey } : {}), environmentRef, evidenceRefs: [], input: input.input ?? null, ...(population?.visualCases ? { visualCases: population.visualCases.filter(entry => expectedMembers.includes(canonicalItemKey(entry.itemKey)) && (phase === "final" || canonicalItemKey(entry.itemKey) === itemKey)) } : {}) };
664
+ const evidenceAttempts: string[] = [];
665
+ for (const [index, executor] of ((input.evidence ?? []) as ProgramRef[]).entries()) {
666
+ const evidence = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/evidence-${index}`, executor, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context);
667
+ assessmentContext.evidenceRefs.push(...(evidence.evidenceRefs ?? [])); evidenceAttempts.push(evidence.attemptId);
668
+ }
669
+ const observed = await this.state(runId);
670
+ const requiredCaptureKeys = captureKeys(acceptance, phase, expectedMembers, itemKey);
671
+ for (const key of requiredCaptureKeys) programAssert(assessmentContext.evidenceRefs.some(ref => { const capture = observed.artifactRecords?.[ref]; return capture?.itemKey === key && capture.candidateDigest === candidate.digest && capture.environmentRef === environmentRef && observed.attempts[capture.producerAttemptId]?.outcome === "completed"; }), `Missing current capture for ${key}`);
672
+ const review = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/review`, input.review as ProgramRef, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest, feedback: checkpoint.feedback }, candidate, [], context), report = asRecord(review.data);
673
+ programAssert(["approved", "changes_requested", "inconclusive"].includes(String(report.verdict)) && Array.isArray(report.findings), "Review verdict/findings required");
674
+ const outcomes: { id: string; passed: boolean }[] = [], attemptIds = [...evidenceAttempts, review.attemptId];
675
+ for (const check of checks) {
676
+ programAssert(check.includes("@"), "Check must be versioned executor reference"); const split = check.lastIndexOf("@"), reference = { id: check.slice(0, split), version: check.slice(split + 1) }, checker = registryEntry(run.registry.executors, reference);
677
+ programAssert(checker.kind === "command" && checker.effect === "read" && programDigest(checker) !== programDigest(reviewer), "Checks require distinct observed command executors");
678
+ const output = await this.activity(runId, `${id}/assessment-${checkpoint.assessments}/check-${encodeURIComponent(check)}`, reference, { ...activityInputs, assessmentContext, candidateDigest: candidate.digest }, candidate, [], context);
679
+ outcomes.push({ id: check, passed: asRecord(output.data).passed === true }); attemptIds.push(output.attemptId);
680
+ }
681
+ const covered = Array.isArray(report.coveredObligationIds) ? report.coveredObligationIds as string[] : [];
682
+ if (obligationIds.some(obligation => !covered.includes(obligation))) { report.verdict = "inconclusive"; report.findings = []; }
683
+ const satisfied = report.verdict === "approved" && !(report.findings as unknown[]).length && outcomes.every(outcome => outcome.passed) ? obligationIds.filter(obligation => covered.includes(obligation)) : [];
684
+ const assessment: ProgramAssessment = { phase, obligationIds, satisfiedObligationIds: satisfied, unsatisfiedObligationIds: obligationIds.filter(obligation => !satisfied.includes(obligation)), environmentRef, evidenceRefs: assessmentContext.evidenceRefs, contractDigest: programDigest(acceptance), receiptId: `assessment-${randomUUID()}`, operationId: id, generation: origin.generation, semanticDigest: origin.semanticDigest, inputDigest: origin.inputDigest, candidateDigest: candidate.digest, verdict: report.verdict as ProgramAssessment["verdict"], findings: report.findings as unknown[], checks: outcomes, attemptIds };
685
+ await this.store.put(assessment);
686
+ checkpoint.assessment = assessment; checkpoint.phase = "decide";
687
+ await this.store.transact(runId, "assessment-receipt-and-recipe", current => { programAssert(current!.operations[id].generation === origin.generation && current!.operations[id].semanticDigest === origin.semanticDigest && attemptIds.every(attemptId => current!.attempts[attemptId]?.outcome === "completed") && !candidate.producerAttempts.includes(review.attemptId), "Assessment lacks current distinct observed attempts"); current!.assessments[assessment.receiptId!] = assessment; current!.repairs[id] = structuredClone(checkpoint); return current!; });
688
+ return assessment;
689
+ };
690
+ await save("repair-checkpoint");
691
+ for (;;) {
692
+ try {
693
+ await this.checkRunning(runId);
694
+ if (checkpoint.phase === "exhausted" || checkpoint.phase === "stalled") return { status: checkpoint.phase, lastCandidate: checkpoint.candidate, reason: "Persisted repair budget/progress limit reached" };
695
+ if (checkpoint.phase === "implement") {
696
+ if (checkpoint.rounds >= maxRounds) { checkpoint.phase = "exhausted"; await save("repair-exhausted"); continue; }
697
+ checkpoint.rounds++; checkpoint.phase = "implementation-active"; await save("repair-implementation-intent");
698
+ }
699
+ if (checkpoint.phase === "implementation-active") {
700
+ const before = checkpoint.candidate.digest, output = await this.activity(runId, `${id}/round-${checkpoint.rounds - 1}/implement`, input.implement as ProgramRef, { ...activityInputs, candidateDigest: before, feedback: checkpoint.feedback }, checkpoint.candidate, scope, context);
701
+ checkpoint.candidate = output.candidate; checkpoint.unchanged = before === output.candidate.digest ? checkpoint.unchanged + 1 : 0;
702
+ checkpoint.phase = "assess"; await save("repair-implementation-checkpoint");
703
+ }
704
+ if (checkpoint.phase === "assess") {
705
+ if (checkpoint.assessments >= maxAssessments) { checkpoint.phase = "exhausted"; await save("repair-exhausted"); continue; }
706
+ checkpoint.assessments++; checkpoint.phase = "assessment-active"; await save("repair-assessment-intent");
707
+ }
708
+ if (checkpoint.phase === "assessment-active") await assess();
709
+ const assessment = checkpoint.assessment!; programAssert(assessment && checkpoint.phase === "decide", "Persisted assessment decision required");
710
+ if (assessment.verdict === "approved" && !assessment.findings.length && assessment.checks.every(check => check.passed) && !assessment.unsatisfiedObligationIds?.length) { checkpoint.phase = "accepted"; await save("repair-accepted"); return { status: "accepted", acceptedCandidate: checkpoint.candidate, assessment }; }
711
+ const findings = programDigest({ verdict: assessment.verdict, findings: assessment.findings, checks: assessment.checks }); checkpoint.repeated = findings === checkpoint.priorFindings ? checkpoint.repeated + 1 : 0; checkpoint.priorFindings = findings;
712
+ checkpoint.feedback = { candidateDigest: assessment.candidateDigest, verdict: assessment.verdict, findings: assessment.findings, checks: assessment.checks };
713
+ checkpoint.phase = checkpoint.unchanged >= Number(progress.unchangedCandidateRounds) || checkpoint.repeated >= Number(progress.repeatedFindingsRounds) ? "stalled" : assessment.verdict === "inconclusive" ? "assess" : "implement";
714
+ await save("repair-decision");
715
+ } catch (error) {
716
+ const current = await this.state(runId);
717
+ if (current.status !== "executing" || current.operations[id].generation !== origin.generation) throw error;
718
+ if (Object.values(current.attempts).some(attempt => attempt.operationId.startsWith(`${id}/`) && attempt.outcome === "uncertain")) return { status: "uncertain", lastCandidate: checkpoint.candidate, reason: (error as Error).message };
719
+ checkpoint.infrastructure++; if (checkpoint.infrastructure >= maxInfrastructure) checkpoint.phase = "exhausted";
720
+ await save("repair-infrastructure-checkpoint");
721
+ }
722
+ }
723
+ }
724
+ private async gate(runId: string, id: string, input: Record<string, unknown>): Promise<unknown> {
725
+ const run = await this.state(runId), candidate = this.resolveCandidate(run, input.candidate), acceptance = registryEntry(run.registry.acceptance, run.program.acceptance), policy = registryEntry(run.registry.policies, run.program.policy);
726
+ programAssert(run.candidates[candidate.candidateId]?.digest === candidate.digest, "Unregistered candidate");
727
+ programAssert(input.obligations === undefined || programDigest(input.obligations) === programDigest(acceptance), "Gate cannot replace owner acceptance obligations");
728
+ programAssert(!Object.values(run.attempts).some(attempt => attempt.outcome === "uncertain"), "Uncertain effects block gate");
729
+ programAssert(run.program.mode !== "data", "Data profile cannot grant candidate publication");
730
+ const requiredChecks = acceptance.requiredChecksByScope?.final ?? acceptance.requiredChecks;
731
+ const expectedMembers = run.program.population ? registryEntry(run.registry.populations, run.program.population).members.filter(key => !registryEntry(run.registry.populations, run.program.population!).exclusions.some(exclusion => exclusion.id === key)).map(canonicalItemKey) : [];
732
+ const requiredObligations = (acceptance.obligations ?? []).flatMap(obligation => obligation.scope === "final" ? [obligation.id] : expectedMembers.map(key => `${obligation.id}/${programSegment(key)}`));
733
+ const assessment = Object.values(run.assessments).find(assessment => {
734
+ const origin = assessment.operationId ? run.operations[assessment.operationId] : undefined;
735
+ return origin && origin.status === "completed" && origin.generation === assessment.generation && origin.semanticDigest === assessment.semanticDigest && origin.inputDigest === assessment.inputDigest && assessment.candidateDigest === candidate.digest && assessment.verdict === "approved" && !assessment.findings.length && assessment.attemptIds.every(attemptId => run.attempts[attemptId]?.outcome === "completed") && (!acceptance.requiredChecksByScope || assessment.phase === "final") && requiredObligations.every(obligation => assessment.satisfiedObligationIds?.includes(obligation)) && requiredChecks.every(check => assessment.checks.some(outcome => outcome.id === check && outcome.passed));
736
+ });
737
+ programAssert(!acceptance.requireReview || assessment, "Applicable distinct review/checks missing");
738
+ programAssert(assessment || requiredChecks.length === 0, "Required checks missing");
739
+ if (run.program.population) {
740
+ const coverage = asRecord(input.coverage), registered = run.coverageReceipts[String(coverage.receiptId)];
741
+ programAssert(registered && programDigest(Object.fromEntries(Object.entries(coverage).filter(([key]) => key !== "receiptId"))) === programDigest(registered) && registered.populationDigest === programDigest(registryEntry(run.registry.populations, run.program.population)), "Owner coverage receipt missing/mismatched");
742
+ programAssert(Object.entries(run.collections).some(([operationId, collection]) => collection.coverageReceiptId === coverage.receiptId && collection.obligationsSatisfied && collection.seal === run.seals[operationId] && run.operations[operationId]?.status === "completed" && run.operations[operationId]?.generation === collection.generation && programDigest(collection.ids) === programDigest(registered.ids) && collection.acceptedCandidateIds.every(candidateId => { const accepted = run.candidates[candidateId]; return accepted && accepted.deltas.every(delta => candidate.deltas.includes(delta)) && accepted.producerAttempts.every(attempt => candidate.producerAttempts.includes(attempt)); }) && collection.acceptedAssessmentIds.every(receiptId => {
743
+ const receipt = run.assessments[receiptId], origin = receipt?.operationId ? run.operations[receipt.operationId] : undefined;
744
+ return origin?.status === "completed" && origin.generation === receipt.generation && receipt.verdict === "approved" && !receipt.findings.length && receipt.attemptIds.every(attemptId => run.attempts[attemptId]?.outcome === "completed") && (acceptance.requiredChecksByScope?.item ?? acceptance.requiredChecks).every(check => receipt.checks.some(outcome => outcome.id === check && outcome.passed));
745
+ })), "Population gate requires closed item acceptance, not discovery coverage alone");
746
+ }
747
+ let authorizationSignalId: string | null = null;
748
+ if (acceptance.requireHumanApproval) {
749
+ const payload = asRecord(input.authorization), signal = run.signals.find(event => event.status === "consumed" && event.payloadRef === programDigest(payload) && event.subjectDigest === programDigest({ candidate, contract: acceptance }) && event.authorization && (!event.expiresAt || Date.parse(event.expiresAt) > Date.now()));
750
+ programAssert(signal && payload.approved === true, "Matching owner-consumed human approval required"); authorizationSignalId = signal.id;
751
+ }
752
+ const receipt = { authorizationSignalId, requiredObligations, gateId: id, candidateDigest: candidate.digest, semanticVersion: run.semanticVersion, programDigest: run.programDigest,
753
+ acceptanceDigest: programDigest(acceptance), policyDigest: programDigest(policy), populationDigest: run.program.population ? programDigest(registryEntry(run.registry.populations, run.program.population)) : programDigest(null), seals: run.seals, criteria: acceptance.criteria, assessment: assessment ?? null, coverage: input.coverage ?? null };
754
+ const gateRef = await this.store.put(receipt);
755
+ await this.store.transact(runId, "gate-receipt", current => { programAssert(current!.semanticVersion === run.semanticVersion && current!.programDigest === run.programDigest, "Gate inputs changed", "AF_PROGRAM_REVISED"); current!.gateRef = gateRef; current!.acceptedCandidate = candidate; return current!; }); return { passed: true, gateRef, candidate };
756
+ }
757
+ private async apply(runId: string, request: Record<string, unknown>): Promise<unknown> {
758
+ const run = await this.state(runId);
759
+ if (run.intent && run.intent.requestId === request.requestId) { programAssert(run.intent.requestDigest === programDigest(request), "Apply requestId body changed", "AF_PROGRAM_CONFLICT"); return run; }
760
+ programAssert(!this.closed && run.status === "acceptance-ready" && run.baseRef && run.gateRef && request.authorization, "Applicable gate and existing authorization required");
761
+ await this.authorize(run);
762
+ const gate = await this.store.get<Record<string, unknown>>(run.gateRef), candidate = Object.values(run.candidates).find(candidate => candidate.digest === gate.candidateDigest); programAssert(candidate, "Gate candidate absent");
763
+ if (gate.authorizationSignalId) { const signal = run.signals.find(event => event.id === gate.authorizationSignalId); programAssert(signal?.status === "consumed" && (!signal.expiresAt || Date.parse(signal.expiresAt) > Date.now()), "Human authorization expired before apply"); }
764
+ const base = await this.store.get<ManagedBase>(run.baseRef), artifacts = await this.artifacts(runId, candidate), expected = await previewManagedArtifacts(base, artifacts);
765
+ let won = false;
766
+ const state = await this.store.transact(runId, "apply-intent", current => {
767
+ programAssert(!Object.values(current!.attempts).some(attempt => attempt.outcome === "running" || attempt.outcome === "uncertain") && !Object.values(current!.operations).some(operation => operation.status !== "completed" && operation.status !== "skipped"), "Unsettled or rejected work blocks application");
768
+ programAssert(current?.status === "acceptance-ready" && current.gateRef === run.gateRef && current.semanticVersion === gate.semanticVersion && current.programDigest === gate.programDigest && programDigest(current.seals) === programDigest(gate.seals), "Apply gate/tuple changed");
769
+ current.intent = { id: `apply-${randomUUID()}`, requestId: idText(request.requestId), requestDigest: programDigest(request), candidateDigest: candidate.digest, gateRef: run.gateRef!, semanticVersion: current.semanticVersion, programDigest: current.programDigest,
770
+ acceptanceDigest: String(gate.acceptanceDigest), policyDigest: String(gate.policyDigest), populationDigest: String(gate.populationDigest), sealRefs: Object.values(current.seals), authorization: String(request.authorization), expectedDigest: expected.digest, changedFiles: expected.changedFiles };
771
+ current.status = "applying"; won = true; return current;
772
+ }, { requestId: idText(request.requestId), fingerprint: programDigest(request), expectedVersion: Number(request.expectedVersion) });
773
+ if (!won) return state;
774
+ try {
775
+ const receipt = await publishManagedArtifacts(base, artifacts, { beforeWrite: async () => { const current = await this.state(runId); programAssert(current.status === "applying" && current.intent?.id === state.intent?.id, "Application canceled/revoked; reconcile before continuing"); } }), receiptRef = await this.store.put({ ...receipt, intent: state.intent });
776
+ return await this.store.transact(runId, "applied", current => { programAssert(current && current.status === "applying" && current.intent && current.intent.id === state.intent?.id, "Apply interrupted; reconcile actual files"); programAssert(receipt.digest === current.intent.expectedDigest, "Applied digest mismatch"); current.status = "applied"; current.receiptRef = receiptRef; return current; });
777
+ } catch (error) {
778
+ return this.store.transact(runId, "apply-uncertain", current => { if (current!.status !== "applied" && current!.intent?.id === state.intent?.id) { current!.status = "apply-uncertain"; current!.reason = (error as Error).message.slice(0, 1000); } return current!; });
779
+ }
780
+ }
781
+ private async reconcileApply(runId: string, request: Record<string, unknown>): Promise<unknown> {
782
+ const run = await this.state(runId); programAssert(run.status === "apply-uncertain" && run.intent && run.baseRef, "Uncertain apply required");
783
+ const base = await this.store.get<ManagedBase>(run.baseRef), candidate = Object.values(run.candidates).find(candidate => candidate.digest === run.intent!.candidateDigest); programAssert(candidate, "Intent candidate absent");
784
+ if (request.publication === "retry") {
785
+ programAssert(request.authorization && await confirmManagedBaseline(base), "Retry requires authorization and observed complete original baseline");
786
+ return this.store.transact(runId, "apply-retry-authorized", current => { programAssert(current!.status === "apply-uncertain" && current!.intent?.id === run.intent?.id, "Intent changed"); delete current!.intent; delete current!.gateRef; delete current!.acceptedCandidate; delete current!.resultRef; current!.semanticVersion++; current!.status = "paused"; current!.reason = "Original baseline observed; explicit resume/revalidation required"; return current!; }, { requestId: idText(request.requestId), fingerprint: programDigest(request), expectedVersion: Number(request.expectedVersion) });
787
+ }
788
+ const observed = await confirmManagedPublication(base, await this.artifacts(runId, candidate));
789
+ programAssert(observed, "Publication does not match frozen intent; preserve partial files");
790
+ const receiptRef = await this.store.put({ observed, intent: run.intent });
791
+ return this.store.transact(runId, "apply-reconciled", current => { programAssert(current && current.intent?.id === run.intent?.id, "Intent changed"); current.status = "applied"; current.receiptRef = receiptRef; return current; }, { requestId: idText(request.requestId), fingerprint: programDigest(request), expectedVersion: Number(request.expectedVersion) });
792
+ }
793
+ }