zob-harness 0.1.0

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 (356) hide show
  1. package/.pi/adapters/registry.json +103 -0
  2. package/.pi/agents/architecture-cartographer.md +53 -0
  3. package/.pi/agents/chief-vision.md +39 -0
  4. package/.pi/agents/clarifier.md +58 -0
  5. package/.pi/agents/context-steward.md +52 -0
  6. package/.pi/agents/doc-steward.md +34 -0
  7. package/.pi/agents/explore.md +49 -0
  8. package/.pi/agents/factory.md +41 -0
  9. package/.pi/agents/implementer.md +44 -0
  10. package/.pi/agents/librarian.md +32 -0
  11. package/.pi/agents/oracle-merge.md +50 -0
  12. package/.pi/agents/oracle.md +55 -0
  13. package/.pi/agents/pattern-miner.md +53 -0
  14. package/.pi/agents/planner.md +39 -0
  15. package/.pi/agents/project-dna-golden-evaluator.md +32 -0
  16. package/.pi/agents/project-dna-ontology-steward.md +30 -0
  17. package/.pi/agents/project-dna-oracle.md +56 -0
  18. package/.pi/agents/project-dna-orchestrator.md +60 -0
  19. package/.pi/agents/project-dna-query-steward.md +38 -0
  20. package/.pi/agents/project-dna-safety-preflight.md +54 -0
  21. package/.pi/agents/project-dna-test-linker.md +27 -0
  22. package/.pi/agents/qa.md +38 -0
  23. package/.pi/agents/refactor-cartographer.md +28 -0
  24. package/.pi/agents/refactor-mover.md +31 -0
  25. package/.pi/agents/refactor-oracle.md +49 -0
  26. package/.pi/agents/repo-scout.md +60 -0
  27. package/.pi/agents/sample-architect.md +48 -0
  28. package/.pi/agents/specifier.md +57 -0
  29. package/.pi/agents/symbol-range-curator.md +41 -0
  30. package/.pi/agents/synthesis.md +52 -0
  31. package/.pi/agents/temp-agent-creator.md +35 -0
  32. package/.pi/autonomy-policy.json +67 -0
  33. package/.pi/budget-policy.json +54 -0
  34. package/.pi/capabilities/zob-public-runtime-capabilities.json +1700 -0
  35. package/.pi/chains/explore-plan-oracle.json +78 -0
  36. package/.pi/chains/explore-spec-clarify-plan-oracle.json +64 -0
  37. package/.pi/chains/explore-spec-plan-oracle.json +53 -0
  38. package/.pi/chains/spec-clarify-plan-oracle.json +53 -0
  39. package/.pi/chains/spec-factory-oracle.json +42 -0
  40. package/.pi/chains/spec-plan-oracle.json +42 -0
  41. package/.pi/compute-profiles/defaults.json +19 -0
  42. package/.pi/compute-profiles/overrides.json +13 -0
  43. package/.pi/compute-profiles/risk-rules.json +16 -0
  44. package/.pi/daemon-policy.json +80 -0
  45. package/.pi/damage-control-rules.json +45 -0
  46. package/.pi/extensions/zob-child-safety/index.ts +212 -0
  47. package/.pi/extensions/zob-harness/AGENTS.md +28 -0
  48. package/.pi/extensions/zob-harness/index.ts +391 -0
  49. package/.pi/extensions/zob-harness/src/AGENTS.md +25 -0
  50. package/.pi/extensions/zob-harness/src/agents.ts +82 -0
  51. package/.pi/extensions/zob-harness/src/autonomous-runtime.ts +2912 -0
  52. package/.pi/extensions/zob-harness/src/autonomy-readiness.ts +778 -0
  53. package/.pi/extensions/zob-harness/src/budget-policy.ts +308 -0
  54. package/.pi/extensions/zob-harness/src/capabilities.ts +249 -0
  55. package/.pi/extensions/zob-harness/src/child-runner.ts +249 -0
  56. package/.pi/extensions/zob-harness/src/chronicle.ts +262 -0
  57. package/.pi/extensions/zob-harness/src/compute-profile.ts +602 -0
  58. package/.pi/extensions/zob-harness/src/compute-workflow-shape.ts +168 -0
  59. package/.pi/extensions/zob-harness/src/coms-v2/AGENTS.md +16 -0
  60. package/.pi/extensions/zob-harness/src/coms-v2/envelope.ts +121 -0
  61. package/.pi/extensions/zob-harness/src/coms-v2/identity.ts +53 -0
  62. package/.pi/extensions/zob-harness/src/coms-v2/ledger-bridge.ts +67 -0
  63. package/.pi/extensions/zob-harness/src/coms-v2/local-transport.ts +147 -0
  64. package/.pi/extensions/zob-harness/src/coms-v2/pending-replies.ts +80 -0
  65. package/.pi/extensions/zob-harness/src/coms-v2/policy.ts +125 -0
  66. package/.pi/extensions/zob-harness/src/coms-v2/presence.ts +55 -0
  67. package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +113 -0
  68. package/.pi/extensions/zob-harness/src/coms-v2/response-capture.ts +50 -0
  69. package/.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts +164 -0
  70. package/.pi/extensions/zob-harness/src/coms-v2/types.ts +149 -0
  71. package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +140 -0
  72. package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +452 -0
  73. package/.pi/extensions/zob-harness/src/constants.ts +108 -0
  74. package/.pi/extensions/zob-harness/src/context-gbrain.ts +465 -0
  75. package/.pi/extensions/zob-harness/src/daemon-policy.ts +223 -0
  76. package/.pi/extensions/zob-harness/src/daemon-readiness.ts +134 -0
  77. package/.pi/extensions/zob-harness/src/daemon-runtime.ts +393 -0
  78. package/.pi/extensions/zob-harness/src/factory/AGENTS.md +24 -0
  79. package/.pi/extensions/zob-harness/src/factory/agentic-plan.ts +65 -0
  80. package/.pi/extensions/zob-harness/src/factory/quarantine.ts +319 -0
  81. package/.pi/extensions/zob-harness/src/factory/run.ts +520 -0
  82. package/.pi/extensions/zob-harness/src/factory/validation.ts +454 -0
  83. package/.pi/extensions/zob-harness/src/factory-selector.ts +318 -0
  84. package/.pi/extensions/zob-harness/src/full-autonomy-test.ts +226 -0
  85. package/.pi/extensions/zob-harness/src/git-ops.ts +868 -0
  86. package/.pi/extensions/zob-harness/src/goal-room.ts +178 -0
  87. package/.pi/extensions/zob-harness/src/goal-runtime.ts +1569 -0
  88. package/.pi/extensions/zob-harness/src/goal-todo-imports.ts +111 -0
  89. package/.pi/extensions/zob-harness/src/goal-todo-types.ts +231 -0
  90. package/.pi/extensions/zob-harness/src/goal-todos.ts +1410 -0
  91. package/.pi/extensions/zob-harness/src/goal.ts +152 -0
  92. package/.pi/extensions/zob-harness/src/governed-requests.ts +436 -0
  93. package/.pi/extensions/zob-harness/src/interactive-autonomy.ts +595 -0
  94. package/.pi/extensions/zob-harness/src/launch-apply.ts +313 -0
  95. package/.pi/extensions/zob-harness/src/merge-queue.ts +290 -0
  96. package/.pi/extensions/zob-harness/src/mission-control.ts +573 -0
  97. package/.pi/extensions/zob-harness/src/model-availability.ts +52 -0
  98. package/.pi/extensions/zob-harness/src/model-routing.ts +429 -0
  99. package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +23 -0
  100. package/.pi/extensions/zob-harness/src/orchestration/adaptive-delegation.ts +547 -0
  101. package/.pi/extensions/zob-harness/src/orchestration/adaptive-workflow.ts +585 -0
  102. package/.pi/extensions/zob-harness/src/orchestration/lead-plan.ts +192 -0
  103. package/.pi/extensions/zob-harness/src/orchestration/plan.ts +168 -0
  104. package/.pi/extensions/zob-harness/src/orchestration/room.ts +346 -0
  105. package/.pi/extensions/zob-harness/src/orchestration/run.ts +134 -0
  106. package/.pi/extensions/zob-harness/src/orchestration/supervised-readonly.ts +1147 -0
  107. package/.pi/extensions/zob-harness/src/orchestration/widget-readers.ts +132 -0
  108. package/.pi/extensions/zob-harness/src/output-contracts.ts +656 -0
  109. package/.pi/extensions/zob-harness/src/project-dna.ts +533 -0
  110. package/.pi/extensions/zob-harness/src/promotion/AGENTS.md +24 -0
  111. package/.pi/extensions/zob-harness/src/promotion/candidate.ts +336 -0
  112. package/.pi/extensions/zob-harness/src/promotion/coms.ts +127 -0
  113. package/.pi/extensions/zob-harness/src/promotion/documentation.ts +142 -0
  114. package/.pi/extensions/zob-harness/src/promotion/factory.ts +107 -0
  115. package/.pi/extensions/zob-harness/src/promotion/ledger.ts +2 -0
  116. package/.pi/extensions/zob-harness/src/promotion/temp-agent.ts +151 -0
  117. package/.pi/extensions/zob-harness/src/promotion/types.ts +149 -0
  118. package/.pi/extensions/zob-harness/src/promotion/validate.ts +6 -0
  119. package/.pi/extensions/zob-harness/src/promotion/write-lane.ts +162 -0
  120. package/.pi/extensions/zob-harness/src/prompt-packs.ts +239 -0
  121. package/.pi/extensions/zob-harness/src/queue.ts +386 -0
  122. package/.pi/extensions/zob-harness/src/rules.ts +225 -0
  123. package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +26 -0
  124. package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +116 -0
  125. package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +715 -0
  126. package/.pi/extensions/zob-harness/src/runtime/commands.ts +1315 -0
  127. package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +516 -0
  128. package/.pi/extensions/zob-harness/src/runtime/delegation-click-markers.ts +141 -0
  129. package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +415 -0
  130. package/.pi/extensions/zob-harness/src/runtime/delegation-markdown.ts +97 -0
  131. package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +553 -0
  132. package/.pi/extensions/zob-harness/src/runtime/delegation-mouse.ts +205 -0
  133. package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +434 -0
  134. package/.pi/extensions/zob-harness/src/runtime/events.ts +736 -0
  135. package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +214 -0
  136. package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +144 -0
  137. package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +270 -0
  138. package/.pi/extensions/zob-harness/src/runtime/state.ts +403 -0
  139. package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +117 -0
  140. package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +136 -0
  141. package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +365 -0
  142. package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +70 -0
  143. package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1854 -0
  144. package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +810 -0
  145. package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +46 -0
  146. package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +38 -0
  147. package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +61 -0
  148. package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +77 -0
  149. package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +106 -0
  150. package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +123 -0
  151. package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +93 -0
  152. package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +62 -0
  153. package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +147 -0
  154. package/.pi/extensions/zob-harness/src/runtime/widget.ts +353 -0
  155. package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +60 -0
  156. package/.pi/extensions/zob-harness/src/safety.ts +338 -0
  157. package/.pi/extensions/zob-harness/src/sandbox.ts +1508 -0
  158. package/.pi/extensions/zob-harness/src/schemas-project-dna.ts +47 -0
  159. package/.pi/extensions/zob-harness/src/schemas.ts +695 -0
  160. package/.pi/extensions/zob-harness/src/telemetry.ts +373 -0
  161. package/.pi/extensions/zob-harness/src/topology/AGENTS.md +22 -0
  162. package/.pi/extensions/zob-harness/src/topology/chains.ts +236 -0
  163. package/.pi/extensions/zob-harness/src/topology/coms.ts +211 -0
  164. package/.pi/extensions/zob-harness/src/topology/orchestration-profiles.ts +204 -0
  165. package/.pi/extensions/zob-harness/src/topology/teams.ts +113 -0
  166. package/.pi/extensions/zob-harness/src/types/core.ts +47 -0
  167. package/.pi/extensions/zob-harness/src/types.ts +939 -0
  168. package/.pi/extensions/zob-harness/src/utils/AGENTS.md +22 -0
  169. package/.pi/extensions/zob-harness/src/utils/formatting.ts +34 -0
  170. package/.pi/extensions/zob-harness/src/utils/hashing.ts +11 -0
  171. package/.pi/extensions/zob-harness/src/utils/json.ts +28 -0
  172. package/.pi/extensions/zob-harness/src/utils/paths.ts +54 -0
  173. package/.pi/extensions/zob-harness/src/utils/records.ts +25 -0
  174. package/.pi/extensions/zob-harness/src/utils/resources.ts +38 -0
  175. package/.pi/extensions/zob-harness/src/worker-pool.ts +672 -0
  176. package/.pi/extensions/zob-harness/src/workspace-claims.ts +297 -0
  177. package/.pi/extensions/zob-switch/index.ts +180 -0
  178. package/.pi/factories/budget-preflight-dry-run/batch-manifest.json +59 -0
  179. package/.pi/factories/budget-preflight-dry-run/factory.json +94 -0
  180. package/.pi/factories/budget-preflight-dry-run/pilot-manifest.json +50 -0
  181. package/.pi/factories/budget-preflight-dry-run/smoke-manifest.json +43 -0
  182. package/.pi/factories/code-review-matrix/batch-manifest.json +61 -0
  183. package/.pi/factories/code-review-matrix/factory.json +163 -0
  184. package/.pi/factories/code-review-matrix/pilot-manifest.json +41 -0
  185. package/.pi/factories/code-review-matrix/smoke-manifest.json +35 -0
  186. package/.pi/factories/factory-forge/batch-manifest.json +56 -0
  187. package/.pi/factories/factory-forge/factory.json +84 -0
  188. package/.pi/factories/factory-forge/pilot-manifest.json +32 -0
  189. package/.pi/factories/factory-forge/smoke-manifest.json +19 -0
  190. package/.pi/factories/opencode-pattern-canonizer/batch-manifest.json +54 -0
  191. package/.pi/factories/opencode-pattern-canonizer/factory.json +86 -0
  192. package/.pi/factories/opencode-pattern-canonizer/pilot-manifest.json +39 -0
  193. package/.pi/factories/opencode-pattern-canonizer/smoke-manifest.json +26 -0
  194. package/.pi/factories/project-dna/README.md +182 -0
  195. package/.pi/factories/project-dna/batch-manifest.json +37 -0
  196. package/.pi/factories/project-dna/example-project-dna-manifest-v2.json +80 -0
  197. package/.pi/factories/project-dna/example-project-dna-manifest.json +58 -0
  198. package/.pi/factories/project-dna/factory.json +131 -0
  199. package/.pi/factories/project-dna/golden-cases-smoke.json +62 -0
  200. package/.pi/factories/project-dna/pi-agentic-ontology.json +88 -0
  201. package/.pi/factories/project-dna/pilot-manifest.json +32 -0
  202. package/.pi/factories/project-dna/schemas/benchmark-suite.schema.json +27 -0
  203. package/.pi/factories/project-dna/schemas/code-knowledge-graph.schema.json +97 -0
  204. package/.pi/factories/project-dna/schemas/context-pack.schema.json +43 -0
  205. package/.pi/factories/project-dna/schemas/golden-case.schema.json +36 -0
  206. package/.pi/factories/project-dna/schemas/manifest-v2.schema.json +128 -0
  207. package/.pi/factories/project-dna/schemas/manifest.schema.json +77 -0
  208. package/.pi/factories/project-dna/schemas/ontology.schema.json +45 -0
  209. package/.pi/factories/project-dna/schemas/project-fingerprint.schema.json +28 -0
  210. package/.pi/factories/project-dna/schemas/query-steward-report.schema.json +52 -0
  211. package/.pi/factories/project-dna/smoke-manifest.json +27 -0
  212. package/.pi/factories/roadmap-smoke-lots/batch-manifest.json +49 -0
  213. package/.pi/factories/roadmap-smoke-lots/factory.json +89 -0
  214. package/.pi/factories/roadmap-smoke-lots/pilot-manifest.json +50 -0
  215. package/.pi/factories/roadmap-smoke-lots/smoke-manifest.json +35 -0
  216. package/.pi/git-policy.json +120 -0
  217. package/.pi/mission-control/zob_coms_transport.json +64 -0
  218. package/.pi/model-catalog.example.json +345 -0
  219. package/.pi/model-economy.example.json +196 -0
  220. package/.pi/model-routing.json +86 -0
  221. package/.pi/orchestrations/adaptive-chief-vision.json +193 -0
  222. package/.pi/orchestrations/ceo-feature-build.json +182 -0
  223. package/.pi/orchestrations/readonly-dynamic-smoke.json +75 -0
  224. package/.pi/output-contracts/agent-event.v1.json +19 -0
  225. package/.pi/output-contracts/base.v1.json +24 -0
  226. package/.pi/output-contracts/brain-lookup.v1.json +21 -0
  227. package/.pi/output-contracts/clarification.v1.json +21 -0
  228. package/.pi/output-contracts/context-pack.v1.json +20 -0
  229. package/.pi/output-contracts/context-request.v1.json +21 -0
  230. package/.pi/output-contracts/context-steward.v1.json +19 -0
  231. package/.pi/output-contracts/context-writeback-proposal.v1.json +18 -0
  232. package/.pi/output-contracts/delegation-request.v1.json +21 -0
  233. package/.pi/output-contracts/explore.v1.json +52 -0
  234. package/.pi/output-contracts/factory.v1.json +48 -0
  235. package/.pi/output-contracts/guidance-steward.v1.json +18 -0
  236. package/.pi/output-contracts/implement.v1.json +40 -0
  237. package/.pi/output-contracts/launch-authorization.v1.json +21 -0
  238. package/.pi/output-contracts/lead-plan.v1.json +22 -0
  239. package/.pi/output-contracts/mission-readiness.v1.json +20 -0
  240. package/.pi/output-contracts/oracle-merge.v1.json +44 -0
  241. package/.pi/output-contracts/oracle-request.v1.json +20 -0
  242. package/.pi/output-contracts/oracle.v1.json +44 -0
  243. package/.pi/output-contracts/orchestration-profile.v1.json +22 -0
  244. package/.pi/output-contracts/plan.v1.json +48 -0
  245. package/.pi/output-contracts/prompt-pack.v1.json +20 -0
  246. package/.pi/output-contracts/qa.v1.json +40 -0
  247. package/.pi/output-contracts/research.v1.json +36 -0
  248. package/.pi/output-contracts/spec.v1.json +22 -0
  249. package/.pi/output-contracts/synthesis.v1.json +44 -0
  250. package/.pi/output-contracts/temp-agent-card.v1.json +23 -0
  251. package/.pi/output-contracts/todo-child-result.v1.json +20 -0
  252. package/.pi/output-contracts/todo-child-result.v2.json +22 -0
  253. package/.pi/output-contracts/todo-claim-validation.v1.json +22 -0
  254. package/.pi/output-contracts/todo-split-request.v1.json +20 -0
  255. package/.pi/prompts/adaptive-workflow.md +63 -0
  256. package/.pi/prompts/autonomous-runtime.md +15 -0
  257. package/.pi/prompts/benchmark-contender.md +15 -0
  258. package/.pi/prompts/benchmark-judge.md +19 -0
  259. package/.pi/prompts/clarify-spec.md +20 -0
  260. package/.pi/prompts/compute-plan.md +36 -0
  261. package/.pi/prompts/compute-preview.md +42 -0
  262. package/.pi/prompts/contract.md +29 -0
  263. package/.pi/prompts/explore.md +13 -0
  264. package/.pi/prompts/factory-run.md +36 -0
  265. package/.pi/prompts/factory.md +20 -0
  266. package/.pi/prompts/implement.md +27 -0
  267. package/.pi/prompts/model-catalog.md +68 -0
  268. package/.pi/prompts/model-economy.md +64 -0
  269. package/.pi/prompts/oracle-merge.md +18 -0
  270. package/.pi/prompts/oracle.md +13 -0
  271. package/.pi/prompts/orchestrator.md +48 -0
  272. package/.pi/prompts/parallel-review.md +21 -0
  273. package/.pi/prompts/plan.md +21 -0
  274. package/.pi/prompts/project-dna.md +90 -0
  275. package/.pi/prompts/refactor-oracle.md +23 -0
  276. package/.pi/prompts/refactor-slice.md +24 -0
  277. package/.pi/prompts/research.md +20 -0
  278. package/.pi/prompts/spec.md +19 -0
  279. package/.pi/prompts/synthesis.md +18 -0
  280. package/.pi/rules/always.md +38 -0
  281. package/.pi/rules/docs.md +32 -0
  282. package/.pi/rules/factory.md +44 -0
  283. package/.pi/rules/oracle.md +34 -0
  284. package/.pi/rules/orchestration.md +44 -0
  285. package/.pi/rules/project.md +34 -0
  286. package/.pi/rules/prompts.md +43 -0
  287. package/.pi/rules/runtime.md +43 -0
  288. package/.pi/rules/sandbox.md +43 -0
  289. package/.pi/settings.json +28 -0
  290. package/.pi/skills/zob-agentic-access/SKILL.md +20 -0
  291. package/.pi/skills/zob-autonomous-runtime/SKILL.md +41 -0
  292. package/.pi/skills/zob-commit/SKILL.md +79 -0
  293. package/.pi/skills/zob-compaction-policy/SKILL.md +92 -0
  294. package/.pi/skills/zob-compute-profile/SKILL.md +108 -0
  295. package/.pi/skills/zob-coms-safety/SKILL.md +54 -0
  296. package/.pi/skills/zob-coms-v2-live/SKILL.md +47 -0
  297. package/.pi/skills/zob-delegation-routing/SKILL.md +82 -0
  298. package/.pi/skills/zob-factory/SKILL.md +28 -0
  299. package/.pi/skills/zob-goal-todo-tree/SKILL.md +279 -0
  300. package/.pi/skills/zob-harness/SKILL.md +68 -0
  301. package/.pi/skills/zob-mission-control-coms/SKILL.md +39 -0
  302. package/.pi/skills/zob-oracle/SKILL.md +21 -0
  303. package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +244 -0
  304. package/.pi/skills/zob-owner-pool-launcher/SKILL.md +261 -0
  305. package/.pi/skills/zob-project-dna/SKILL.md +275 -0
  306. package/.pi/skills/zob-sandbox/SKILL.md +29 -0
  307. package/.pi/skills/zob-spec/SKILL.md +25 -0
  308. package/.pi/skills/zob-split-refactor/SKILL.md +39 -0
  309. package/.pi/skills/zob-tool-router/SKILL.md +104 -0
  310. package/.pi/teams/zob-core.json +122 -0
  311. package/AGENTS.md +89 -0
  312. package/CONTRIBUTING.md +56 -0
  313. package/LICENSE +21 -0
  314. package/README.md +360 -0
  315. package/SECURITY.md +35 -0
  316. package/SOURCE_INDEX.md +46 -0
  317. package/package.json +135 -0
  318. package/scripts/README.md +57 -0
  319. package/scripts/autonomy/mission-readiness-secret-smoke.mjs +90 -0
  320. package/scripts/compute-profile/plan-workflow.mjs +85 -0
  321. package/scripts/compute-profile/preview.mjs +242 -0
  322. package/scripts/compute-profile/regression-smoke.mjs +38 -0
  323. package/scripts/compute-profile/summarize.mjs +72 -0
  324. package/scripts/compute-profile/validate-policy.mjs +50 -0
  325. package/scripts/compute-profile/validate-preview.mjs +95 -0
  326. package/scripts/compute-profile/validate-workflow.mjs +58 -0
  327. package/scripts/git-ops/commit-policy-smoke.mjs +221 -0
  328. package/scripts/goal-todo/child-goal-ref-smoke.mjs +252 -0
  329. package/scripts/harness-switch/static-smoke.mjs +43 -0
  330. package/scripts/model-catalog/validate-economy.mjs +223 -0
  331. package/scripts/model-catalog/validate.mjs +199 -0
  332. package/scripts/package-surface/validate-script-refs.mjs +190 -0
  333. package/scripts/path-policy/validate-smoke.mjs +103 -0
  334. package/scripts/project-dna/bench-smoke.mjs +217 -0
  335. package/scripts/project-dna/build-capsules.mjs +207 -0
  336. package/scripts/project-dna/build-sample-spec.mjs +140 -0
  337. package/scripts/project-dna/emit-golden-cases.mjs +75 -0
  338. package/scripts/project-dna/emit-ontology.mjs +75 -0
  339. package/scripts/project-dna/generate-sample.mjs +302 -0
  340. package/scripts/project-dna/oracle-review-smoke.mjs +157 -0
  341. package/scripts/project-dna/plan-workflow.mjs +289 -0
  342. package/scripts/project-dna/query-context.mjs +276 -0
  343. package/scripts/project-dna/query-steward.mjs +149 -0
  344. package/scripts/project-dna/scan.mjs +553 -0
  345. package/scripts/project-dna/validate-5of5.mjs +159 -0
  346. package/scripts/project-dna/validate-golden-cases.mjs +78 -0
  347. package/scripts/project-dna/validate-ontology.mjs +97 -0
  348. package/scripts/project-dna/validate-sample-project.mjs +105 -0
  349. package/scripts/project-dna/validate-scaffold.mjs +383 -0
  350. package/scripts/project-dna/validate-scan-artifacts.mjs +187 -0
  351. package/scripts/project-dna/validate-workflow.mjs +166 -0
  352. package/scripts/start-pi.sh +4 -0
  353. package/scripts/worker-pool/static-smoke.mjs +54 -0
  354. package/scripts/zpeer-local-e2e-smoke.mjs +395 -0
  355. package/scripts/zpeer-static-smoke.mjs +129 -0
  356. package/tsconfig.json +12 -0
@@ -0,0 +1,1508 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+
4
+ import { DEFAULT_RULES } from "./constants.js";
5
+ import { createDiffGateResult, createRollbackMetadata, createSandboxMetadata, validateRuntimeWritePolicy } from "./safety.js";
6
+ import { sha256 } from "./utils/hashing.js";
7
+ import { parseJsonFile } from "./utils/json.js";
8
+ import { pathMatches, resolveRepoPath, safeFileStem, safeRunId } from "./utils/paths.js";
9
+ import { isRecord } from "./utils/records.js";
10
+
11
+ export type SandboxWriteAction = "create" | "update";
12
+
13
+ export interface SandboxWritePlanChange {
14
+ path: string;
15
+ action: SandboxWriteAction;
16
+ contentHash: string;
17
+ reason?: string;
18
+ }
19
+
20
+ export interface SandboxWritePlanInput {
21
+ run_id?: string;
22
+ allowed_paths: string[];
23
+ forbidden_paths?: string[];
24
+ changes: SandboxWritePlanChange[];
25
+ base_ref?: string;
26
+ }
27
+
28
+ export interface SandboxWritePlanResult {
29
+ runId: string;
30
+ runDir: string;
31
+ sandboxRoot: string;
32
+ status: "planned_safe" | "blocked_preflight";
33
+ changedPaths: string[];
34
+ diffHash?: string;
35
+ artifacts: string[];
36
+ errors: string[];
37
+ }
38
+
39
+ export interface SandboxApplyReadinessInput {
40
+ run_id: string;
41
+ oracle_review_path?: string;
42
+ diff_review_gate_path?: string;
43
+ apply_id?: string;
44
+ approval?: {
45
+ approvedBy?: string;
46
+ approvedAt?: string;
47
+ approvalId?: string;
48
+ };
49
+ }
50
+
51
+ export interface SandboxApplyReadinessResult {
52
+ runId: string;
53
+ applyId: string;
54
+ reviewDir: string;
55
+ status: "ready_for_manual_apply" | "blocked_preflight";
56
+ applyReady: boolean;
57
+ applyPerformed: false;
58
+ artifacts: string[];
59
+ errors: string[];
60
+ }
61
+
62
+ export interface SandboxApplySimulationInput {
63
+ run_id: string;
64
+ apply_readiness_path?: string;
65
+ simulation_id?: string;
66
+ }
67
+
68
+ export interface SandboxApplySimulationResult {
69
+ runId: string;
70
+ simulationId: string;
71
+ simulationDir: string;
72
+ targetWorkspace: string;
73
+ status: "simulated_apply_in_temp_workspace" | "blocked_preflight";
74
+ simulatedApplyPerformed: boolean;
75
+ productionWritesPerformed: false;
76
+ autoApply: false;
77
+ artifacts: string[];
78
+ errors: string[];
79
+ }
80
+
81
+ export interface SandboxManualApplyPreflightInput {
82
+ run_id: string;
83
+ apply_readiness_path?: string;
84
+ apply_simulation_path?: string;
85
+ preflight_id?: string;
86
+ confirmation_phrase?: string;
87
+ }
88
+
89
+ export interface SandboxManualApplyPreflightResult {
90
+ runId: string;
91
+ preflightId: string;
92
+ preflightDir: string;
93
+ status: "manual_apply_preflight_passed" | "blocked_preflight";
94
+ manualApplyPreflightPassed: boolean;
95
+ applyPerformed: false;
96
+ productionWritesPerformed: false;
97
+ autoApply: false;
98
+ artifacts: string[];
99
+ errors: string[];
100
+ }
101
+
102
+ export interface SandboxIsolatedExecutionInput {
103
+ run_id: string;
104
+ execution_id?: string;
105
+ }
106
+
107
+ export interface SandboxIsolatedExecutionResult {
108
+ runId: string;
109
+ executionId: string;
110
+ executionDir: string;
111
+ status: "executed_in_sandbox" | "blocked_preflight";
112
+ isolatedExecutionPerformed: boolean;
113
+ productionWritesPerformed: false;
114
+ artifacts: string[];
115
+ errors: string[];
116
+ }
117
+
118
+ export interface SandboxDiffReviewGateInput {
119
+ run_id: string;
120
+ oracle_review_path?: string;
121
+ review_id?: string;
122
+ }
123
+
124
+ export interface SandboxDiffReviewGateResult {
125
+ runId: string;
126
+ reviewId: string;
127
+ reviewDir: string;
128
+ status: "diff_review_passed" | "blocked_preflight";
129
+ reviewPassed: boolean;
130
+ applyReadyUnlocked: boolean;
131
+ applyPerformed: false;
132
+ productionWritesPerformed: false;
133
+ artifacts: string[];
134
+ errors: string[];
135
+ }
136
+
137
+ const HEX_SHA256 = /^[a-f0-9]{64}$/i;
138
+ const FORBIDDEN_PLAINTEXT_KEYS = new Set(["task", "prompt", "output", "body", "content", "patch", "diff"]);
139
+
140
+ function containsForbiddenPlaintextKeys(value: unknown): boolean {
141
+ if (!value || typeof value !== "object") return false;
142
+ if (Array.isArray(value)) return value.some(containsForbiddenPlaintextKeys);
143
+ return Object.entries(value as Record<string, unknown>).some(([key, child]) => FORBIDDEN_PLAINTEXT_KEYS.has(key) || containsForbiddenPlaintextKeys(child));
144
+ }
145
+
146
+ function sandboxLedger(runDir: string, entry: Record<string, unknown>): void {
147
+ appendFileSync(join(runDir, "ledger.jsonl"), `${JSON.stringify({ ...entry, timestamp: new Date().toISOString() })}\n`, "utf8");
148
+ }
149
+
150
+ function readRecord(path: string, errors: string[], label: string): Record<string, unknown> | undefined {
151
+ if (!existsSync(path)) {
152
+ errors.push(`${label} is missing: ${path}`);
153
+ return undefined;
154
+ }
155
+ try {
156
+ const parsed = parseJsonFile(path);
157
+ if (!isRecord(parsed)) {
158
+ errors.push(`${label} is not a JSON object: ${path}`);
159
+ return undefined;
160
+ }
161
+ return parsed;
162
+ } catch (error) {
163
+ errors.push(`${label} could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
164
+ return undefined;
165
+ }
166
+ }
167
+
168
+ function artifactHash(path: string | undefined): string | undefined {
169
+ if (!path || !existsSync(path)) return undefined;
170
+ return sha256(readFileSync(path, "utf8"));
171
+ }
172
+
173
+ function expectedManualApplyPreflightConfirmation(runId: string, preflightId: string): string {
174
+ return `CONFIRM SANDBOX MANUAL APPLY PREFLIGHT ${runId} ${preflightId}`;
175
+ }
176
+
177
+ function normalizeChanges(changes: SandboxWritePlanChange[]): Array<Record<string, unknown>> {
178
+ return changes.map((change) => ({
179
+ path: change.path,
180
+ action: change.action,
181
+ contentHash: change.contentHash,
182
+ ...(change.reason ? { reasonHash: sha256(change.reason) } : {}),
183
+ bodyStored: false,
184
+ }));
185
+ }
186
+
187
+ function changesFromManifest(manifest: Record<string, unknown> | undefined): Array<Record<string, unknown>> {
188
+ if (!manifest || !Array.isArray(manifest.changes)) return [];
189
+ return manifest.changes.filter(isRecord).map((change) => ({
190
+ path: typeof change.path === "string" ? change.path : "unknown",
191
+ action: change.action === "create" || change.action === "update" ? change.action : "unknown",
192
+ contentHash: typeof change.contentHash === "string" ? change.contentHash : undefined,
193
+ reasonHash: typeof change.reasonHash === "string" ? change.reasonHash : undefined,
194
+ bodyStored: false,
195
+ }));
196
+ }
197
+
198
+ function sandboxRootFromManifest(repoRoot: string, runId: string, manifest: Record<string, unknown> | undefined, errors: string[]): string | undefined {
199
+ if (!manifest || typeof manifest.sandboxRoot !== "string") {
200
+ errors.push("sandbox manifest requires sandboxRoot");
201
+ return undefined;
202
+ }
203
+ const resolved = resolveRepoPath(repoRoot, manifest.sandboxRoot);
204
+ errors.push(...resolved.errors.map((error) => `sandboxRoot: ${error}`));
205
+ const requiredPrefix = resolve(repoRoot, ".pi", "tmp", "sandbox-runs", runId);
206
+ if (resolved.path !== requiredPrefix && !resolved.path.startsWith(`${requiredPrefix}/`)) errors.push("sandboxRoot must stay inside this run's .pi/tmp/sandbox-runs workspace");
207
+ return resolved.path;
208
+ }
209
+
210
+ function sameStringSet(left: unknown, right: string[]): boolean {
211
+ if (!Array.isArray(left) || !left.every((item) => typeof item === "string")) return false;
212
+ return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort());
213
+ }
214
+
215
+ function isInsidePath(parent: string, child: string): boolean {
216
+ const resolvedParent = resolve(parent);
217
+ const resolvedChild = resolve(child);
218
+ return resolvedChild === resolvedParent || resolvedChild.startsWith(`${resolvedParent}/`);
219
+ }
220
+
221
+ function findIsolatedExecutionValidations(sandboxRoot: string | undefined): Array<Record<string, unknown>> {
222
+ if (!sandboxRoot) return [];
223
+ const root = join(sandboxRoot, "isolated-executions");
224
+ if (!existsSync(root)) return [];
225
+ return readdirSync(root)
226
+ .sort()
227
+ .map((executionId): Record<string, unknown> | undefined => {
228
+ const validationPath = join(root, executionId, "validation.json");
229
+ const sentinelPath = join(root, executionId, "SANDBOX_ISOLATED_EXECUTION_COMPLETE.sentinel");
230
+ if (!existsSync(validationPath)) return undefined;
231
+ try {
232
+ const parsed = parseJsonFile(validationPath);
233
+ if (!isRecord(parsed)) return undefined;
234
+ return {
235
+ executionId,
236
+ validationPath,
237
+ sentinelPath,
238
+ sentinelPresent: existsSync(sentinelPath),
239
+ status: parsed.status,
240
+ isolatedExecutionPerformed: parsed.isolatedExecutionPerformed,
241
+ productionWritesPerformed: parsed.productionWritesPerformed,
242
+ autoApply: parsed.autoApply,
243
+ markerCount: parsed.markerCount,
244
+ errors: parsed.errors,
245
+ bodyStored: false,
246
+ };
247
+ } catch {
248
+ return undefined;
249
+ }
250
+ })
251
+ .filter((record): record is Record<string, unknown> => Boolean(record));
252
+ }
253
+
254
+ export function validateSandboxWritePlanInputs(repoRoot: string, input: SandboxWritePlanInput): string[] {
255
+ const errors: string[] = [];
256
+ if (input.run_id && safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
257
+ if (!Array.isArray(input.allowed_paths) || input.allowed_paths.length === 0) errors.push("sandbox write plan requires non-empty allowed_paths");
258
+ if (!Array.isArray(input.changes) || input.changes.length === 0) errors.push("sandbox write plan requires at least one planned change");
259
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox write plan metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
260
+
261
+ for (const allowedPath of input.allowed_paths ?? []) {
262
+ const resolved = resolveRepoPath(repoRoot, allowedPath);
263
+ errors.push(...resolved.errors.map((error) => `allowed_paths: ${error}`));
264
+ }
265
+ for (const forbiddenPath of input.forbidden_paths ?? []) {
266
+ const resolved = resolveRepoPath(repoRoot, forbiddenPath);
267
+ errors.push(...resolved.errors.map((error) => `forbidden_paths: ${error}`));
268
+ }
269
+
270
+ for (const [index, change] of (input.changes ?? []).entries()) {
271
+ const label = `changes[${index}]`;
272
+ if (!change || typeof change !== "object") {
273
+ errors.push(`${label} must be an object`);
274
+ continue;
275
+ }
276
+ if (change.action !== "create" && change.action !== "update") errors.push(`${label}.action must be create or update; delete/apply is not supported`);
277
+ if (typeof change.path !== "string" || change.path.trim().length === 0) errors.push(`${label}.path is required`);
278
+ if (typeof change.contentHash !== "string" || !HEX_SHA256.test(change.contentHash)) errors.push(`${label}.contentHash must be a sha256 hex hash; plaintext content is not stored`);
279
+ if (typeof change.path === "string") {
280
+ const resolved = resolveRepoPath(repoRoot, change.path);
281
+ errors.push(...resolved.errors.map((error) => `${label}: ${error}`));
282
+ const policy = validateRuntimeWritePolicy({
283
+ targetPath: change.path,
284
+ cwd: repoRoot,
285
+ allowedPaths: input.allowed_paths,
286
+ forbiddenPaths: input.forbidden_paths,
287
+ zeroAccessPaths: DEFAULT_RULES.zeroAccessPaths,
288
+ readOnlyPaths: DEFAULT_RULES.readOnlyPaths,
289
+ });
290
+ errors.push(...policy.violations.map((violation) => `${label}: ${violation}`));
291
+ if (change.action === "update" && !existsSync(resolved.path)) errors.push(`${label}.path does not exist for update: ${change.path}`);
292
+ }
293
+ }
294
+
295
+ return errors;
296
+ }
297
+
298
+ export function validateSandboxApplyReadinessInputs(repoRoot: string, input: SandboxApplyReadinessInput): string[] {
299
+ const errors: string[] = [];
300
+ if (!input.run_id || safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
301
+ const applyId = input.apply_id ?? "manual-apply-review";
302
+ if (safeFileStem(applyId) !== applyId) errors.push(`apply_id must be path-safe: ${applyId}`);
303
+ if (!input.oracle_review_path) {
304
+ errors.push("sandbox apply readiness requires oracle_review_path");
305
+ } else {
306
+ const resolvedOracle = resolveRepoPath(repoRoot, input.oracle_review_path);
307
+ errors.push(...resolvedOracle.errors.map((error) => `oracle_review_path: ${error}`));
308
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
309
+ if (pathMatches(input.oracle_review_path, protectedPattern, repoRoot, repoRoot)) errors.push(`oracle_review_path references zero-access path: ${protectedPattern}`);
310
+ }
311
+ }
312
+ if (!input.diff_review_gate_path) {
313
+ errors.push("sandbox apply readiness requires diff_review_gate_path");
314
+ } else {
315
+ const resolvedDiffReview = resolveRepoPath(repoRoot, input.diff_review_gate_path);
316
+ errors.push(...resolvedDiffReview.errors.map((error) => `diff_review_gate_path: ${error}`));
317
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
318
+ if (pathMatches(input.diff_review_gate_path, protectedPattern, repoRoot, repoRoot)) errors.push(`diff_review_gate_path references zero-access path: ${protectedPattern}`);
319
+ }
320
+ }
321
+ if (!input.approval || typeof input.approval.approvedBy !== "string" || input.approval.approvedBy.trim().length === 0) errors.push("sandbox apply readiness requires approval.approvedBy");
322
+ if (!input.approval || typeof input.approval.approvedAt !== "string" || input.approval.approvedAt.trim().length === 0) errors.push("sandbox apply readiness requires approval.approvedAt");
323
+ if (!input.approval || typeof input.approval.approvalId !== "string" || input.approval.approvalId.trim().length === 0) errors.push("sandbox apply readiness requires approval.approvalId");
324
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox apply readiness metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
325
+
326
+ const runDir = join(repoRoot, "reports", "sandbox-runs", input.run_id ?? "invalid-run");
327
+ const manifest = readRecord(join(runDir, "manifest.json"), errors, "sandbox manifest");
328
+ const validation = readRecord(join(runDir, "validation.json"), errors, "sandbox validation");
329
+ const diffGate = readRecord(join(runDir, "diff-gate.json"), errors, "diff gate");
330
+ const rollback = readRecord(join(runDir, "rollback-metadata.json"), errors, "rollback metadata");
331
+ const oracleReviewPath = input.oracle_review_path ? resolveRepoPath(repoRoot, input.oracle_review_path).path : undefined;
332
+ const oracleReview = oracleReviewPath ? readRecord(oracleReviewPath, errors, "oracle review") : undefined;
333
+ const diffReviewGatePath = input.diff_review_gate_path ? resolveRepoPath(repoRoot, input.diff_review_gate_path).path : undefined;
334
+ const diffReviewGate = diffReviewGatePath ? readRecord(diffReviewGatePath, errors, "sandbox diff review gate") : undefined;
335
+
336
+ if (manifest) {
337
+ if (manifest.schema !== "zob.sandbox-write-plan.v1") errors.push("sandbox manifest schema must be zob.sandbox-write-plan.v1");
338
+ if (manifest.runId !== input.run_id) errors.push("sandbox manifest runId must match input run_id");
339
+ if (manifest.status !== "planned_safe") errors.push("sandbox manifest must be planned_safe before apply readiness");
340
+ if (manifest.autoApply !== false || manifest.noExecution !== true || manifest.humanApprovalRequired !== true) errors.push("sandbox manifest must remain noExecution=true, autoApply=false, humanApprovalRequired=true");
341
+ if (typeof manifest.diffHash !== "string" || !HEX_SHA256.test(manifest.diffHash)) errors.push("sandbox manifest requires sha256 diffHash");
342
+ if (containsForbiddenPlaintextKeys(manifest)) errors.push("sandbox manifest must remain metadata/hash-only");
343
+ }
344
+ if (validation) {
345
+ if (validation.schema !== "zob.sandbox-write-validation.v1") errors.push("sandbox validation schema must be zob.sandbox-write-validation.v1");
346
+ if (validation.status !== "planned_safe") errors.push("sandbox validation must be planned_safe before apply readiness");
347
+ if (Array.isArray(validation.errors) && validation.errors.length > 0) errors.push("sandbox validation must have no errors before apply readiness");
348
+ if (validation.autoApply !== false || validation.noExecution !== true || validation.humanApprovalRequired !== true) errors.push("sandbox validation must remain noExecution=true, autoApply=false, humanApprovalRequired=true");
349
+ if (validation.rollbackPrepared !== true || validation.rollbackApplied !== false) errors.push("sandbox validation requires rollbackPrepared=true and rollbackApplied=false");
350
+ if (validation.sentinel !== "SANDBOX_PLAN_READY.sentinel" || !existsSync(join(runDir, "SANDBOX_PLAN_READY.sentinel"))) errors.push("sandbox plan ready sentinel is required before apply readiness");
351
+ if (containsForbiddenPlaintextKeys(validation)) errors.push("sandbox validation must remain metadata/hash-only");
352
+ }
353
+ if (diffGate) {
354
+ if (diffGate.schema !== "zob.diff-gate-result.v1") errors.push("diff gate schema must be zob.diff-gate-result.v1");
355
+ if (diffGate.allowed !== true || diffGate.applyRequired !== true || diffGate.autoApply !== false) errors.push("diff gate must be allowed=true, applyRequired=true, autoApply=false");
356
+ if (manifest && diffGate.diffHash !== manifest.diffHash) errors.push("diff gate diffHash must match sandbox manifest diffHash");
357
+ if (containsForbiddenPlaintextKeys(diffGate)) errors.push("diff gate must remain metadata/hash-only");
358
+ }
359
+ if (rollback) {
360
+ if (rollback.schema !== "zob.rollback-metadata.v1") errors.push("rollback metadata schema must be zob.rollback-metadata.v1");
361
+ if (rollback.rollbackPrepared !== true || rollback.rollbackApplied !== false || rollback.autoApply !== false) errors.push("rollback metadata must prepare rollback without applying it");
362
+ if (containsForbiddenPlaintextKeys(rollback)) errors.push("rollback metadata must remain metadata/hash-only");
363
+ }
364
+ if (oracleReview) {
365
+ if (oracleReview.schema !== "zob.oracle-review.v1") errors.push("oracle review schema must be zob.oracle-review.v1");
366
+ if (oracleReview.reviewedRunId !== input.run_id) errors.push("oracle review reviewedRunId must match sandbox run_id");
367
+ if (oracleReview.verdict !== "PASS") errors.push("sandbox apply oracle review verdict must be PASS");
368
+ if (oracleReview.no_ship !== false) errors.push("sandbox apply oracle review no_ship must be false");
369
+ if (typeof oracleReview.evidence !== "string" || oracleReview.evidence.trim().length === 0) errors.push("sandbox apply oracle review evidence is required");
370
+ }
371
+ if (diffReviewGate) {
372
+ if (diffReviewGate.schema !== "zob.sandbox-diff-review-gate.v1") errors.push("sandbox diff review gate schema must be zob.sandbox-diff-review-gate.v1");
373
+ if (diffReviewGate.runId !== input.run_id) errors.push("sandbox diff review gate runId must match sandbox run_id");
374
+ if (diffReviewGate.status !== "diff_review_passed" || diffReviewGate.reviewPassed !== true || diffReviewGate.applyReadyUnlocked !== true) errors.push("sandbox diff review gate must pass before apply readiness");
375
+ if (diffReviewGate.autoApply !== false || diffReviewGate.noExecution !== true || diffReviewGate.productionWritesPerformed !== false || diffReviewGate.applyPerformed !== false) errors.push("sandbox diff review gate must remain noExecution=true, autoApply=false, productionWritesPerformed=false, applyPerformed=false");
376
+ if (manifest && diffReviewGate.diffHash !== manifest.diffHash) errors.push("sandbox diff review gate diffHash must match sandbox manifest diffHash");
377
+ if (!sameStringSet(diffReviewGate.changedPaths, changesFromManifest(manifest).map((change) => String(change.path)))) errors.push("sandbox diff review gate changedPaths must match sandbox manifest changes");
378
+ const gates = isRecord(diffReviewGate.gates) ? diffReviewGate.gates : undefined;
379
+ if (!gates || gates.rollbackValidated !== true || gates.oracleReviewPassed !== true || gates.isolatedExecutionValidated !== true) errors.push("sandbox diff review gate requires rollbackValidated, oracleReviewPassed, and isolatedExecutionValidated");
380
+ if (!existsSync(join(dirname(diffReviewGatePath ?? ""), "SANDBOX_DIFF_REVIEW_PASSED.sentinel"))) errors.push("sandbox diff review passed sentinel is required before apply readiness");
381
+ if (containsForbiddenPlaintextKeys(diffReviewGate)) errors.push("sandbox diff review gate must remain metadata/hash-only");
382
+ }
383
+
384
+ return errors;
385
+ }
386
+
387
+ export function validateSandboxIsolatedExecutionInputs(repoRoot: string, input: SandboxIsolatedExecutionInput): string[] {
388
+ const errors: string[] = [];
389
+ if (!input.run_id || safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
390
+ const executionId = input.execution_id ?? "isolated-execution";
391
+ if (safeFileStem(executionId) !== executionId) errors.push(`execution_id must be path-safe: ${executionId}`);
392
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox isolated execution metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
393
+
394
+ const runDir = join(repoRoot, "reports", "sandbox-runs", input.run_id ?? "invalid-run");
395
+ const manifest = readRecord(join(runDir, "manifest.json"), errors, "sandbox manifest");
396
+ const validation = readRecord(join(runDir, "validation.json"), errors, "sandbox validation");
397
+ const diffGate = readRecord(join(runDir, "diff-gate.json"), errors, "diff gate");
398
+ const rollback = readRecord(join(runDir, "rollback-metadata.json"), errors, "rollback metadata");
399
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, input.run_id ?? "invalid-run", manifest, errors);
400
+
401
+ if (manifest) {
402
+ if (manifest.schema !== "zob.sandbox-write-plan.v1") errors.push("sandbox manifest schema must be zob.sandbox-write-plan.v1");
403
+ if (manifest.runId !== input.run_id) errors.push("sandbox manifest runId must match input run_id");
404
+ if (manifest.status !== "planned_safe") errors.push("sandbox manifest must be planned_safe before isolated execution");
405
+ if (manifest.autoApply !== false || manifest.noExecution !== true || manifest.humanApprovalRequired !== true) errors.push("sandbox manifest must remain noExecution=true, autoApply=false, humanApprovalRequired=true before isolated execution");
406
+ if (typeof manifest.diffHash !== "string" || !HEX_SHA256.test(manifest.diffHash)) errors.push("sandbox manifest requires sha256 diffHash before isolated execution");
407
+ if (changesFromManifest(manifest).length === 0) errors.push("sandbox manifest requires at least one change before isolated execution");
408
+ if (containsForbiddenPlaintextKeys(manifest)) errors.push("sandbox manifest must remain metadata/hash-only before isolated execution");
409
+ }
410
+ if (validation) {
411
+ if (validation.schema !== "zob.sandbox-write-validation.v1") errors.push("sandbox validation schema must be zob.sandbox-write-validation.v1");
412
+ if (validation.status !== "planned_safe") errors.push("sandbox validation must be planned_safe before isolated execution");
413
+ if (Array.isArray(validation.errors) && validation.errors.length > 0) errors.push("sandbox validation must have no errors before isolated execution");
414
+ if (validation.autoApply !== false || validation.noExecution !== true || validation.humanApprovalRequired !== true) errors.push("sandbox validation must remain noExecution=true, autoApply=false, humanApprovalRequired=true before isolated execution");
415
+ if (validation.rollbackPrepared !== true || validation.rollbackApplied !== false) errors.push("sandbox validation requires rollbackPrepared=true and rollbackApplied=false before isolated execution");
416
+ if (validation.sentinel !== "SANDBOX_PLAN_READY.sentinel" || !existsSync(join(runDir, "SANDBOX_PLAN_READY.sentinel"))) errors.push("sandbox plan ready sentinel is required before isolated execution");
417
+ if (containsForbiddenPlaintextKeys(validation)) errors.push("sandbox validation must remain metadata/hash-only before isolated execution");
418
+ }
419
+ if (diffGate) {
420
+ if (diffGate.schema !== "zob.diff-gate-result.v1") errors.push("diff gate schema must be zob.diff-gate-result.v1");
421
+ if (diffGate.allowed !== true || diffGate.applyRequired !== true || diffGate.autoApply !== false) errors.push("diff gate must be allowed=true, applyRequired=true, autoApply=false before isolated execution");
422
+ if (manifest && diffGate.diffHash !== manifest.diffHash) errors.push("diff gate diffHash must match sandbox manifest diffHash before isolated execution");
423
+ if (containsForbiddenPlaintextKeys(diffGate)) errors.push("diff gate must remain metadata/hash-only before isolated execution");
424
+ }
425
+ if (rollback) {
426
+ if (rollback.schema !== "zob.rollback-metadata.v1") errors.push("rollback metadata schema must be zob.rollback-metadata.v1");
427
+ if (rollback.rollbackPrepared !== true || rollback.rollbackApplied !== false || rollback.autoApply !== false) errors.push("rollback metadata must prepare rollback without applying it before isolated execution");
428
+ if (containsForbiddenPlaintextKeys(rollback)) errors.push("rollback metadata must remain metadata/hash-only before isolated execution");
429
+ }
430
+ if (sandboxRoot && !existsSync(sandboxRoot)) errors.push("sandboxRoot workspace is missing before isolated execution");
431
+
432
+ return errors;
433
+ }
434
+
435
+ export function validateSandboxDiffReviewGateInputs(repoRoot: string, input: SandboxDiffReviewGateInput): string[] {
436
+ const errors: string[] = [];
437
+ if (!input.run_id || safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
438
+ const reviewId = input.review_id ?? "oracle-diff-review";
439
+ if (safeFileStem(reviewId) !== reviewId) errors.push(`review_id must be path-safe: ${reviewId}`);
440
+ if (!input.oracle_review_path) {
441
+ errors.push("sandbox diff review gate requires oracle_review_path");
442
+ } else {
443
+ const resolvedOracle = resolveRepoPath(repoRoot, input.oracle_review_path);
444
+ errors.push(...resolvedOracle.errors.map((error) => `oracle_review_path: ${error}`));
445
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
446
+ if (pathMatches(input.oracle_review_path, protectedPattern, repoRoot, repoRoot)) errors.push(`oracle_review_path references zero-access path: ${protectedPattern}`);
447
+ }
448
+ }
449
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox diff review gate metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
450
+
451
+ const runDir = join(repoRoot, "reports", "sandbox-runs", input.run_id ?? "invalid-run");
452
+ const manifest = readRecord(join(runDir, "manifest.json"), errors, "sandbox manifest");
453
+ const validation = readRecord(join(runDir, "validation.json"), errors, "sandbox validation");
454
+ const diffGate = readRecord(join(runDir, "diff-gate.json"), errors, "diff gate");
455
+ const rollback = readRecord(join(runDir, "rollback-metadata.json"), errors, "rollback metadata");
456
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, input.run_id ?? "invalid-run", manifest, errors);
457
+ const oracleReviewPath = input.oracle_review_path ? resolveRepoPath(repoRoot, input.oracle_review_path).path : undefined;
458
+ const oracleReview = oracleReviewPath ? readRecord(oracleReviewPath, errors, "sandbox diff oracle review") : undefined;
459
+ const manifestChangedPaths = changesFromManifest(manifest).map((change) => String(change.path));
460
+ const isolatedExecutions = findIsolatedExecutionValidations(sandboxRoot);
461
+ const validIsolatedExecutions = isolatedExecutions.filter((execution) => execution.status === "executed_in_sandbox" && execution.isolatedExecutionPerformed === true && execution.productionWritesPerformed === false && execution.autoApply === false && execution.sentinelPresent === true && Array.isArray(execution.errors) && execution.errors.length === 0);
462
+
463
+ if (manifest) {
464
+ if (manifest.schema !== "zob.sandbox-write-plan.v1") errors.push("sandbox manifest schema must be zob.sandbox-write-plan.v1");
465
+ if (manifest.runId !== input.run_id) errors.push("sandbox manifest runId must match input run_id");
466
+ if (manifest.status !== "planned_safe") errors.push("sandbox manifest must be planned_safe before diff review");
467
+ if (manifest.autoApply !== false || manifest.noExecution !== true || manifest.humanApprovalRequired !== true) errors.push("sandbox manifest must remain noExecution=true, autoApply=false, humanApprovalRequired=true before diff review");
468
+ if (typeof manifest.diffHash !== "string" || !HEX_SHA256.test(manifest.diffHash)) errors.push("sandbox manifest requires sha256 diffHash before diff review");
469
+ if (manifestChangedPaths.length === 0) errors.push("sandbox manifest requires changed paths before diff review");
470
+ if (containsForbiddenPlaintextKeys(manifest)) errors.push("sandbox manifest must remain metadata/hash-only before diff review");
471
+ }
472
+ if (validation) {
473
+ if (validation.schema !== "zob.sandbox-write-validation.v1") errors.push("sandbox validation schema must be zob.sandbox-write-validation.v1");
474
+ if (validation.status !== "planned_safe") errors.push("sandbox validation must be planned_safe before diff review");
475
+ if (Array.isArray(validation.errors) && validation.errors.length > 0) errors.push("sandbox validation must have no errors before diff review");
476
+ if (validation.autoApply !== false || validation.noExecution !== true || validation.humanApprovalRequired !== true) errors.push("sandbox validation must remain noExecution=true, autoApply=false, humanApprovalRequired=true before diff review");
477
+ if (validation.rollbackPrepared !== true || validation.rollbackApplied !== false) errors.push("sandbox validation requires rollbackPrepared=true and rollbackApplied=false before diff review");
478
+ if (validation.sentinel !== "SANDBOX_PLAN_READY.sentinel" || !existsSync(join(runDir, "SANDBOX_PLAN_READY.sentinel"))) errors.push("sandbox plan ready sentinel is required before diff review");
479
+ if (!sameStringSet(validation.changedPaths, manifestChangedPaths)) errors.push("sandbox validation changedPaths must match sandbox manifest changes before diff review");
480
+ if (manifest && validation.diffHash !== manifest.diffHash) errors.push("sandbox validation diffHash must match sandbox manifest diffHash before diff review");
481
+ if (containsForbiddenPlaintextKeys(validation)) errors.push("sandbox validation must remain metadata/hash-only before diff review");
482
+ }
483
+ if (diffGate) {
484
+ if (diffGate.schema !== "zob.diff-gate-result.v1") errors.push("diff gate schema must be zob.diff-gate-result.v1");
485
+ if (diffGate.allowed !== true || diffGate.applyRequired !== true || diffGate.autoApply !== false) errors.push("diff gate must be allowed=true, applyRequired=true, autoApply=false before diff review");
486
+ if (!sameStringSet(diffGate.changedPaths, manifestChangedPaths)) errors.push("diff gate changedPaths must match sandbox manifest changes before diff review");
487
+ if (manifest && diffGate.diffHash !== manifest.diffHash) errors.push("diff gate diffHash must match sandbox manifest diffHash before diff review");
488
+ if (containsForbiddenPlaintextKeys(diffGate)) errors.push("diff gate must remain metadata/hash-only before diff review");
489
+ }
490
+ if (rollback) {
491
+ if (rollback.schema !== "zob.rollback-metadata.v1") errors.push("rollback metadata schema must be zob.rollback-metadata.v1");
492
+ if (rollback.runId !== input.run_id) errors.push("rollback metadata runId must match input run_id");
493
+ if (rollback.rollbackPrepared !== true || rollback.rollbackApplied !== false || rollback.autoApply !== false) errors.push("rollback metadata must prepare rollback without applying it before diff review");
494
+ if (typeof rollback.snapshotPath !== "string" || rollback.snapshotPath.trim().length === 0) errors.push("rollback metadata requires snapshotPath before diff review");
495
+ if (!sameStringSet(rollback.changedPaths, manifestChangedPaths)) errors.push("rollback metadata changedPaths must match sandbox manifest changes before diff review");
496
+ if (containsForbiddenPlaintextKeys(rollback)) errors.push("rollback metadata must remain metadata/hash-only before diff review");
497
+ }
498
+ if (validIsolatedExecutions.length === 0) errors.push("sandbox diff review requires completed isolated execution validation before manual apply review");
499
+ for (const execution of validIsolatedExecutions) {
500
+ if (typeof execution.markerCount === "number" && execution.markerCount !== manifestChangedPaths.length) errors.push("isolated execution markerCount must match sandbox manifest changes before diff review");
501
+ if (containsForbiddenPlaintextKeys(execution)) errors.push("isolated execution validation metadata must remain metadata/hash-only before diff review");
502
+ }
503
+ if (oracleReview) {
504
+ if (oracleReview.schema !== "zob.sandbox-diff-review.v1") errors.push("sandbox diff oracle review schema must be zob.sandbox-diff-review.v1");
505
+ if (oracleReview.reviewedRunId !== input.run_id) errors.push("sandbox diff oracle review reviewedRunId must match sandbox run_id");
506
+ if (oracleReview.verdict !== "PASS") errors.push("sandbox diff oracle review verdict must be PASS");
507
+ if (oracleReview.no_ship !== false) errors.push("sandbox diff oracle review no_ship must be false");
508
+ if (manifest && oracleReview.diffHash !== manifest.diffHash) errors.push("sandbox diff oracle review diffHash must match sandbox manifest diffHash");
509
+ if (oracleReview.rollbackReviewed !== true) errors.push("sandbox diff oracle review requires rollbackReviewed=true");
510
+ if (oracleReview.isolatedExecutionReviewed !== true) errors.push("sandbox diff oracle review requires isolatedExecutionReviewed=true");
511
+ if (typeof oracleReview.evidence !== "string" || oracleReview.evidence.trim().length === 0) errors.push("sandbox diff oracle review evidence is required");
512
+ if (containsForbiddenPlaintextKeys(oracleReview)) errors.push("sandbox diff oracle review must remain metadata/hash-only");
513
+ }
514
+
515
+ return errors;
516
+ }
517
+
518
+ export function runSandboxWritePlan(repoRoot: string, input: SandboxWritePlanInput): SandboxWritePlanResult {
519
+ const runId = safeRunId(input.run_id, "sandbox");
520
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
521
+ const sandboxRootRelative = join(".pi", "tmp", "sandbox-runs", runId);
522
+ const sandboxRoot = resolve(repoRoot, sandboxRootRelative);
523
+ const errors = validateSandboxWritePlanInputs(repoRoot, input);
524
+ const normalizedChanges = normalizeChanges(input.changes ?? []);
525
+ const changedPaths = normalizedChanges.map((change) => String(change.path));
526
+ const diffHash = errors.length === 0 ? sha256(JSON.stringify({ changedPaths, changes: normalizedChanges, allowedPaths: input.allowed_paths, forbiddenPaths: input.forbidden_paths ?? [] })) : undefined;
527
+ const status: SandboxWritePlanResult["status"] = errors.length === 0 ? "planned_safe" : "blocked_preflight";
528
+ const artifacts = ["manifest.json", "sandbox-metadata.json", "diff-gate.json", "rollback-metadata.json", "validation.json", "ledger.jsonl"];
529
+ const sentinel = status === "planned_safe" ? "SANDBOX_PLAN_READY.sentinel" : "SANDBOX_BLOCKED.sentinel";
530
+
531
+ mkdirSync(runDir, { recursive: true });
532
+ mkdirSync(sandboxRoot, { recursive: true });
533
+ writeFileSync(join(sandboxRoot, "README.md"), ["# ZOB sandbox workspace", "", "This workspace is reserved for isolated write planning.", "Auto-apply is disabled.", ""].join("\n"), "utf8");
534
+
535
+ const sandboxMetadata = createSandboxMetadata({
536
+ runId,
537
+ repoRoot,
538
+ sandboxRoot: sandboxRootRelative,
539
+ allowedPaths: input.allowed_paths,
540
+ forbiddenPaths: input.forbidden_paths,
541
+ });
542
+ const diffGate = createDiffGateResult({
543
+ runId,
544
+ diffHash,
545
+ changedPaths,
546
+ allowed: errors.length === 0,
547
+ violations: errors,
548
+ });
549
+ const rollbackMetadata = createRollbackMetadata({
550
+ runId,
551
+ baseRef: input.base_ref,
552
+ snapshotPath: join(sandboxRootRelative, "rollback-snapshot.metadata.json"),
553
+ changedPaths,
554
+ });
555
+
556
+ const manifest = {
557
+ schema: "zob.sandbox-write-plan.v1",
558
+ runId,
559
+ sandboxRoot,
560
+ status,
561
+ allowedPaths: input.allowed_paths,
562
+ forbiddenPaths: input.forbidden_paths ?? [],
563
+ changes: normalizedChanges,
564
+ diffHash,
565
+ applyRequired: true,
566
+ autoApply: false,
567
+ noExecution: true,
568
+ humanApprovalRequired: true,
569
+ bodyStored: false,
570
+ promptBodiesStored: false,
571
+ outputBodiesStored: false,
572
+ generatedAt: new Date().toISOString(),
573
+ };
574
+ const validation = {
575
+ schema: "zob.sandbox-write-validation.v1",
576
+ runId,
577
+ status,
578
+ errors,
579
+ changedPaths,
580
+ diffHash,
581
+ sandboxRoot,
582
+ policy: {
583
+ allowedPaths: input.allowed_paths,
584
+ forbiddenPaths: input.forbidden_paths ?? [],
585
+ zeroAccessPaths: DEFAULT_RULES.zeroAccessPaths,
586
+ readOnlyPaths: DEFAULT_RULES.readOnlyPaths,
587
+ },
588
+ diffGate: { allowed: errors.length === 0, applyRequired: true, autoApply: false },
589
+ rollbackPrepared: true,
590
+ rollbackApplied: false,
591
+ autoApply: false,
592
+ noExecution: true,
593
+ humanApprovalRequired: true,
594
+ sentinel,
595
+ bodyStored: false,
596
+ promptBodiesStored: false,
597
+ outputBodiesStored: false,
598
+ generatedAt: new Date().toISOString(),
599
+ };
600
+
601
+ writeFileSync(join(runDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
602
+ writeFileSync(join(runDir, "sandbox-metadata.json"), JSON.stringify(sandboxMetadata, null, 2), "utf8");
603
+ writeFileSync(join(runDir, "diff-gate.json"), JSON.stringify(diffGate, null, 2), "utf8");
604
+ writeFileSync(join(runDir, "rollback-metadata.json"), JSON.stringify(rollbackMetadata, null, 2), "utf8");
605
+ writeFileSync(join(runDir, "validation.json"), JSON.stringify(validation, null, 2), "utf8");
606
+ sandboxLedger(runDir, { event: "sandbox_write_plan", status, changedPaths, diffHash, autoApply: false, noExecution: true });
607
+ writeFileSync(join(runDir, sentinel), `${status} ${new Date().toISOString()}\n`, "utf8");
608
+ artifacts.push(sentinel);
609
+
610
+ return { runId, runDir, sandboxRoot, status, changedPaths, diffHash, artifacts, errors };
611
+ }
612
+
613
+ export function runSandboxIsolatedExecution(repoRoot: string, input: SandboxIsolatedExecutionInput): SandboxIsolatedExecutionResult {
614
+ const runId = safeRunId(input.run_id, "sandbox");
615
+ const executionId = safeRunId(input.execution_id, "isolated-execution");
616
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
617
+ const manifestPath = join(runDir, "manifest.json");
618
+ const manifest = existsSync(manifestPath) && isRecord(parseJsonFile(manifestPath)) ? parseJsonFile(manifestPath) as Record<string, unknown> : undefined;
619
+ const sandboxRootErrors: string[] = [];
620
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, runId, manifest, sandboxRootErrors);
621
+ const errors = validateSandboxIsolatedExecutionInputs(repoRoot, { run_id: input.run_id, execution_id: input.execution_id });
622
+ const canWriteSandboxArtifacts = sandboxRootErrors.length === 0 && typeof sandboxRoot === "string" && existsSync(sandboxRoot);
623
+ const executionDir = join(sandboxRoot ?? join(repoRoot, ".pi", "tmp", "sandbox-runs", runId), "isolated-executions", executionId);
624
+ const status: SandboxIsolatedExecutionResult["status"] = errors.length === 0 ? "executed_in_sandbox" : "blocked_preflight";
625
+ const isolatedExecutionPerformed = status === "executed_in_sandbox";
626
+ const changes = changesFromManifest(manifest);
627
+ const changedPaths = changes.map((change) => String(change.path));
628
+ const markerDir = join(executionDir, "change-markers");
629
+ const markerPaths: string[] = [];
630
+ const artifacts: string[] = [];
631
+ const sentinel = isolatedExecutionPerformed ? "SANDBOX_ISOLATED_EXECUTION_COMPLETE.sentinel" : "SANDBOX_ISOLATED_EXECUTION_BLOCKED.sentinel";
632
+
633
+ if (canWriteSandboxArtifacts) {
634
+ mkdirSync(executionDir, { recursive: true });
635
+ mkdirSync(markerDir, { recursive: true });
636
+ }
637
+ if (isolatedExecutionPerformed && canWriteSandboxArtifacts) {
638
+ for (const [index, change] of changes.entries()) {
639
+ const markerName = `${String(index + 1).padStart(3, "0")}-${safeFileStem(String(change.path).replace(/[\\/]+/g, "-"))}.metadata.json`;
640
+ const markerPath = join(markerDir, markerName);
641
+ const marker = {
642
+ schema: "zob.sandbox-isolated-change-marker.v1",
643
+ runId,
644
+ executionId,
645
+ path: change.path,
646
+ action: change.action,
647
+ contentHash: change.contentHash,
648
+ reasonHash: change.reasonHash,
649
+ isolatedWorkspaceWrite: true,
650
+ productionWritePerformed: false,
651
+ autoApply: false,
652
+ bodyStored: false,
653
+ promptBodiesStored: false,
654
+ outputBodiesStored: false,
655
+ generatedAt: new Date().toISOString(),
656
+ };
657
+ writeFileSync(markerPath, JSON.stringify(marker, null, 2), "utf8");
658
+ markerPaths.push(markerPath);
659
+ }
660
+ }
661
+
662
+ const executionReport = {
663
+ schema: "zob.sandbox-isolated-execution.v1",
664
+ runId,
665
+ executionId,
666
+ status,
667
+ isolatedExecutionPerformed,
668
+ isolatedWorkspace: executionDir,
669
+ changedPaths,
670
+ changeCount: changes.length,
671
+ markerPaths,
672
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
673
+ autoApply: false,
674
+ manualApplyRequired: true,
675
+ humanApprovalRequired: true,
676
+ productionWritesPerformed: false,
677
+ rollbackPrepared: true,
678
+ rollbackApplied: false,
679
+ childDispatchAllowed: false,
680
+ networkAccessed: false,
681
+ liveChildExecution: false,
682
+ bodyStored: false,
683
+ promptBodiesStored: false,
684
+ outputBodiesStored: false,
685
+ generatedAt: new Date().toISOString(),
686
+ };
687
+ const validation = {
688
+ schema: "zob.sandbox-isolated-execution-validation.v1",
689
+ runId,
690
+ executionId,
691
+ status,
692
+ isolatedExecutionPerformed,
693
+ productionWritesPerformed: false,
694
+ autoApply: false,
695
+ manualApplyRequired: true,
696
+ errors,
697
+ changedPaths,
698
+ markerCount: markerPaths.length,
699
+ sentinel,
700
+ bodyStored: false,
701
+ promptBodiesStored: false,
702
+ outputBodiesStored: false,
703
+ generatedAt: new Date().toISOString(),
704
+ };
705
+ if (containsForbiddenPlaintextKeys(executionReport) || containsForbiddenPlaintextKeys(validation)) errors.push("sandbox isolated execution artifacts must remain metadata/hash-only");
706
+
707
+ if (canWriteSandboxArtifacts) {
708
+ writeFileSync(join(executionDir, "execution-report.json"), JSON.stringify(executionReport, null, 2), "utf8");
709
+ writeFileSync(join(executionDir, "validation.json"), JSON.stringify(validation, null, 2), "utf8");
710
+ writeFileSync(join(executionDir, sentinel), `${status} ${new Date().toISOString()}\n`, "utf8");
711
+ artifacts.push("execution-report.json", "validation.json", sentinel);
712
+ }
713
+
714
+ return { runId, executionId, executionDir, status, isolatedExecutionPerformed, productionWritesPerformed: false, artifacts, errors };
715
+ }
716
+
717
+ export function runSandboxDiffReviewGate(repoRoot: string, input: SandboxDiffReviewGateInput): SandboxDiffReviewGateResult {
718
+ const runId = safeRunId(input.run_id, "sandbox");
719
+ const reviewId = safeRunId(input.review_id, "oracle-diff-review");
720
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
721
+ const reviewDir = join(runDir, "diff-review", reviewId);
722
+ const errors = validateSandboxDiffReviewGateInputs(repoRoot, input);
723
+ const status: SandboxDiffReviewGateResult["status"] = errors.length === 0 ? "diff_review_passed" : "blocked_preflight";
724
+ const reviewPassed = status === "diff_review_passed";
725
+ const manifestPath = join(runDir, "manifest.json");
726
+ const validationPath = join(runDir, "validation.json");
727
+ const diffGatePath = join(runDir, "diff-gate.json");
728
+ const rollbackPath = join(runDir, "rollback-metadata.json");
729
+ const manifest = existsSync(manifestPath) && isRecord(parseJsonFile(manifestPath)) ? parseJsonFile(manifestPath) as Record<string, unknown> : undefined;
730
+ const validation = existsSync(validationPath) && isRecord(parseJsonFile(validationPath)) ? parseJsonFile(validationPath) as Record<string, unknown> : undefined;
731
+ const diffGate = existsSync(diffGatePath) && isRecord(parseJsonFile(diffGatePath)) ? parseJsonFile(diffGatePath) as Record<string, unknown> : undefined;
732
+ const rollback = existsSync(rollbackPath) && isRecord(parseJsonFile(rollbackPath)) ? parseJsonFile(rollbackPath) as Record<string, unknown> : undefined;
733
+ const sandboxRootErrors: string[] = [];
734
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, runId, manifest, sandboxRootErrors);
735
+ const isolatedExecutions = findIsolatedExecutionValidations(sandboxRoot);
736
+ const oracleReviewPath = input.oracle_review_path ? resolveRepoPath(repoRoot, input.oracle_review_path).path : undefined;
737
+ let oracleReview: Record<string, unknown> | undefined;
738
+ if (oracleReviewPath && existsSync(oracleReviewPath)) {
739
+ try {
740
+ const parsedOracleReview = parseJsonFile(oracleReviewPath);
741
+ if (isRecord(parsedOracleReview)) oracleReview = parsedOracleReview;
742
+ } catch {
743
+ oracleReview = undefined;
744
+ }
745
+ }
746
+ const oracleReviewPassed = oracleReview?.schema === "zob.sandbox-diff-review.v1"
747
+ && oracleReview.reviewedRunId === runId
748
+ && oracleReview.verdict === "PASS"
749
+ && oracleReview.no_ship === false
750
+ && oracleReview.diffHash === manifest?.diffHash
751
+ && oracleReview.rollbackReviewed === true
752
+ && oracleReview.isolatedExecutionReviewed === true
753
+ && typeof oracleReview.evidence === "string"
754
+ && oracleReview.evidence.trim().length > 0;
755
+ const changedPaths = changesFromManifest(manifest).map((change) => String(change.path));
756
+ const reviewGate = {
757
+ schema: "zob.sandbox-diff-review-gate.v1",
758
+ runId,
759
+ reviewId,
760
+ status,
761
+ reviewPassed,
762
+ applyReadyUnlocked: reviewPassed,
763
+ applyPerformed: false,
764
+ autoApply: false,
765
+ manualApplyRequired: true,
766
+ humanApprovalRequired: true,
767
+ productionWritesPerformed: false,
768
+ noExecution: true,
769
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
770
+ changedPaths,
771
+ evidence: {
772
+ manifest: manifestPath,
773
+ validation: validationPath,
774
+ diffGate: diffGatePath,
775
+ rollback: rollbackPath,
776
+ oracleReview: oracleReviewPath,
777
+ isolatedExecutions: isolatedExecutions.map((execution) => execution.validationPath),
778
+ },
779
+ gates: {
780
+ sandboxPlanReady: manifest?.status === "planned_safe" && validation?.status === "planned_safe",
781
+ diffAllowed: diffGate?.allowed === true,
782
+ rollbackValidated: rollback?.rollbackPrepared === true && rollback?.rollbackApplied === false && rollback?.autoApply === false,
783
+ isolatedExecutionValidated: isolatedExecutions.some((execution) => execution.status === "executed_in_sandbox" && execution.isolatedExecutionPerformed === true && execution.productionWritesPerformed === false && execution.sentinelPresent === true),
784
+ oracleReviewPassed,
785
+ },
786
+ errors,
787
+ bodyStored: false,
788
+ promptBodiesStored: false,
789
+ outputBodiesStored: false,
790
+ generatedAt: new Date().toISOString(),
791
+ };
792
+ const reviewValidation = {
793
+ schema: "zob.sandbox-diff-review-validation.v1",
794
+ runId,
795
+ reviewId,
796
+ status,
797
+ reviewPassed,
798
+ applyReadyUnlocked: reviewPassed,
799
+ applyPerformed: false,
800
+ productionWritesPerformed: false,
801
+ autoApply: false,
802
+ manualApplyRequired: true,
803
+ errors,
804
+ sentinel: reviewPassed ? "SANDBOX_DIFF_REVIEW_PASSED.sentinel" : "SANDBOX_DIFF_REVIEW_BLOCKED.sentinel",
805
+ noExecution: true,
806
+ bodyStored: false,
807
+ promptBodiesStored: false,
808
+ outputBodiesStored: false,
809
+ generatedAt: new Date().toISOString(),
810
+ };
811
+ if (containsForbiddenPlaintextKeys(reviewGate) || containsForbiddenPlaintextKeys(reviewValidation)) errors.push("sandbox diff review artifacts must remain metadata/hash-only");
812
+
813
+ mkdirSync(reviewDir, { recursive: true });
814
+ writeFileSync(join(reviewDir, "diff-review-gate.json"), JSON.stringify(reviewGate, null, 2), "utf8");
815
+ writeFileSync(join(reviewDir, "validation.json"), JSON.stringify(reviewValidation, null, 2), "utf8");
816
+ const sentinel = reviewPassed ? "SANDBOX_DIFF_REVIEW_PASSED.sentinel" : "SANDBOX_DIFF_REVIEW_BLOCKED.sentinel";
817
+ writeFileSync(join(reviewDir, sentinel), `${status} ${new Date().toISOString()}\n`, "utf8");
818
+ sandboxLedger(runDir, { event: "sandbox_diff_review_gate", reviewId, status, reviewPassed, applyReadyUnlocked: reviewPassed, applyPerformed: false, autoApply: false, productionWritesPerformed: false });
819
+
820
+ return { runId, reviewId, reviewDir, status, reviewPassed, applyReadyUnlocked: reviewPassed, applyPerformed: false, productionWritesPerformed: false, artifacts: ["diff-review-gate.json", "validation.json", sentinel], errors };
821
+ }
822
+
823
+ export function runSandboxApplyReadiness(repoRoot: string, input: SandboxApplyReadinessInput): SandboxApplyReadinessResult {
824
+ const runId = safeRunId(input.run_id, "sandbox");
825
+ const applyId = safeRunId(input.apply_id, "manual-apply-review");
826
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
827
+ const reviewDir = join(runDir, "apply-readiness", applyId);
828
+ const errors = validateSandboxApplyReadinessInputs(repoRoot, input);
829
+ const status: SandboxApplyReadinessResult["status"] = errors.length === 0 ? "ready_for_manual_apply" : "blocked_preflight";
830
+ const applyReady = status === "ready_for_manual_apply";
831
+ const artifacts = ["apply-readiness.json", "validation.json", "APPLY_NOT_PERFORMED.sentinel"];
832
+ const manifestPath = join(runDir, "manifest.json");
833
+ const validationPath = join(runDir, "validation.json");
834
+ const diffGatePath = join(runDir, "diff-gate.json");
835
+ const rollbackPath = join(runDir, "rollback-metadata.json");
836
+ const manifest = existsSync(manifestPath) && isRecord(parseJsonFile(manifestPath)) ? parseJsonFile(manifestPath) as Record<string, unknown> : undefined;
837
+ const validation = existsSync(validationPath) && isRecord(parseJsonFile(validationPath)) ? parseJsonFile(validationPath) as Record<string, unknown> : undefined;
838
+ const diffGate = existsSync(diffGatePath) && isRecord(parseJsonFile(diffGatePath)) ? parseJsonFile(diffGatePath) as Record<string, unknown> : undefined;
839
+ const rollback = existsSync(rollbackPath) && isRecord(parseJsonFile(rollbackPath)) ? parseJsonFile(rollbackPath) as Record<string, unknown> : undefined;
840
+ const approval = input.approval ? {
841
+ approvedByHash: input.approval.approvedBy ? sha256(input.approval.approvedBy) : undefined,
842
+ approvedAt: input.approval.approvedAt,
843
+ approvalIdHash: input.approval.approvalId ? sha256(input.approval.approvalId) : undefined,
844
+ bodyStored: false,
845
+ } : undefined;
846
+ const oracleReviewPath = input.oracle_review_path ? resolveRepoPath(repoRoot, input.oracle_review_path).path : undefined;
847
+ let oracleReview: Record<string, unknown> | undefined;
848
+ if (oracleReviewPath && existsSync(oracleReviewPath)) {
849
+ try {
850
+ const parsedOracleReview = parseJsonFile(oracleReviewPath);
851
+ if (isRecord(parsedOracleReview)) oracleReview = parsedOracleReview;
852
+ } catch {
853
+ oracleReview = undefined;
854
+ }
855
+ }
856
+ const diffReviewGatePath = input.diff_review_gate_path ? resolveRepoPath(repoRoot, input.diff_review_gate_path).path : undefined;
857
+ let diffReviewGate: Record<string, unknown> | undefined;
858
+ if (diffReviewGatePath && existsSync(diffReviewGatePath)) {
859
+ try {
860
+ const parsedDiffReviewGate = parseJsonFile(diffReviewGatePath);
861
+ if (isRecord(parsedDiffReviewGate)) diffReviewGate = parsedDiffReviewGate;
862
+ } catch {
863
+ diffReviewGate = undefined;
864
+ }
865
+ }
866
+ const diffReviewGates = isRecord(diffReviewGate?.gates) ? diffReviewGate.gates : undefined;
867
+ const diffReviewPassed = diffReviewGate?.schema === "zob.sandbox-diff-review-gate.v1"
868
+ && diffReviewGate.runId === runId
869
+ && diffReviewGate.status === "diff_review_passed"
870
+ && diffReviewGate.reviewPassed === true
871
+ && diffReviewGate.applyReadyUnlocked === true
872
+ && diffReviewGate.applyPerformed === false
873
+ && diffReviewGate.productionWritesPerformed === false
874
+ && diffReviewGate.autoApply === false
875
+ && diffReviewGate.noExecution === true
876
+ && diffReviewGate.diffHash === manifest?.diffHash
877
+ && diffReviewGates?.rollbackValidated === true
878
+ && diffReviewGates?.oracleReviewPassed === true
879
+ && diffReviewGates?.isolatedExecutionValidated === true;
880
+ const oracleReviewPassed = oracleReview?.schema === "zob.oracle-review.v1"
881
+ && oracleReview.reviewedRunId === runId
882
+ && oracleReview.verdict === "PASS"
883
+ && oracleReview.no_ship === false
884
+ && typeof oracleReview.evidence === "string"
885
+ && oracleReview.evidence.trim().length > 0;
886
+ const readiness = {
887
+ schema: "zob.sandbox-apply-readiness.v1",
888
+ runId,
889
+ applyId,
890
+ status,
891
+ applyReady,
892
+ applyPerformed: false,
893
+ autoApply: false,
894
+ manualApplyRequired: true,
895
+ humanApprovalRequired: true,
896
+ productionWritesPerformed: false,
897
+ noExecution: true,
898
+ changedPaths: Array.isArray(manifest?.changedPaths) ? manifest?.changedPaths : Array.isArray(validation?.changedPaths) ? validation?.changedPaths : [],
899
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
900
+ evidence: {
901
+ manifest: manifestPath,
902
+ validation: validationPath,
903
+ diffGate: diffGatePath,
904
+ rollback: rollbackPath,
905
+ oracleReview: oracleReviewPath,
906
+ diffReviewGate: diffReviewGatePath,
907
+ },
908
+ gates: {
909
+ sandboxPlanReady: manifest?.status === "planned_safe" && validation?.status === "planned_safe",
910
+ diffAllowed: diffGate?.allowed === true,
911
+ rollbackPrepared: rollback?.rollbackPrepared === true && rollback?.rollbackApplied === false,
912
+ diffReviewPassed,
913
+ oracleReviewPassed,
914
+ approvalPresent: approval !== undefined,
915
+ },
916
+ approval,
917
+ errors,
918
+ bodyStored: false,
919
+ promptBodiesStored: false,
920
+ outputBodiesStored: false,
921
+ generatedAt: new Date().toISOString(),
922
+ };
923
+ const readinessValidation = {
924
+ schema: "zob.sandbox-apply-readiness-validation.v1",
925
+ runId,
926
+ applyId,
927
+ status,
928
+ applyReady,
929
+ applyPerformed: false,
930
+ autoApply: false,
931
+ manualApplyRequired: true,
932
+ productionWritesPerformed: false,
933
+ errors,
934
+ sentinel: "APPLY_NOT_PERFORMED.sentinel",
935
+ noExecution: true,
936
+ bodyStored: false,
937
+ promptBodiesStored: false,
938
+ outputBodiesStored: false,
939
+ generatedAt: new Date().toISOString(),
940
+ };
941
+
942
+ mkdirSync(reviewDir, { recursive: true });
943
+ writeFileSync(join(reviewDir, "apply-readiness.json"), JSON.stringify(readiness, null, 2), "utf8");
944
+ writeFileSync(join(reviewDir, "validation.json"), JSON.stringify(readinessValidation, null, 2), "utf8");
945
+ writeFileSync(join(reviewDir, "APPLY_NOT_PERFORMED.sentinel"), `apply_not_performed ${new Date().toISOString()}\n`, "utf8");
946
+ sandboxLedger(runDir, { event: "sandbox_apply_readiness", applyId, status, applyReady, applyPerformed: false, autoApply: false });
947
+
948
+ return { runId, applyId, reviewDir, status, applyReady, applyPerformed: false, artifacts, errors };
949
+ }
950
+
951
+ export function validateSandboxApplySimulationInputs(repoRoot: string, input: SandboxApplySimulationInput): string[] {
952
+ const errors: string[] = [];
953
+ if (!input.run_id || safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
954
+ const simulationId = input.simulation_id ?? "apply-simulation";
955
+ if (safeFileStem(simulationId) !== simulationId) errors.push(`simulation_id must be path-safe: ${simulationId}`);
956
+ if (!input.apply_readiness_path) {
957
+ errors.push("sandbox apply simulation requires apply_readiness_path");
958
+ }
959
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox apply simulation metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
960
+
961
+ const runDir = join(repoRoot, "reports", "sandbox-runs", input.run_id ?? "invalid-run");
962
+ const manifest = readRecord(join(runDir, "manifest.json"), errors, "sandbox manifest");
963
+ const validation = readRecord(join(runDir, "validation.json"), errors, "sandbox validation");
964
+ const diffGate = readRecord(join(runDir, "diff-gate.json"), errors, "diff gate");
965
+ const rollback = readRecord(join(runDir, "rollback-metadata.json"), errors, "rollback metadata");
966
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, input.run_id ?? "invalid-run", manifest, errors);
967
+ const manifestChangedPaths = changesFromManifest(manifest).map((change) => String(change.path));
968
+
969
+ let applyReadinessPath: string | undefined;
970
+ let applyReadiness: Record<string, unknown> | undefined;
971
+ let applyReadinessValidation: Record<string, unknown> | undefined;
972
+ if (input.apply_readiness_path) {
973
+ const resolvedApplyReadiness = resolveRepoPath(repoRoot, input.apply_readiness_path);
974
+ errors.push(...resolvedApplyReadiness.errors.map((error) => `apply_readiness_path: ${error}`));
975
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
976
+ if (pathMatches(input.apply_readiness_path, protectedPattern, repoRoot, repoRoot)) errors.push(`apply_readiness_path references zero-access path: ${protectedPattern}`);
977
+ }
978
+ const expectedPrefix = join(repoRoot, "reports", "sandbox-runs", input.run_id ?? "invalid-run", "apply-readiness");
979
+ if (!isInsidePath(expectedPrefix, resolvedApplyReadiness.path)) errors.push("apply_readiness_path must stay inside this run's reports/sandbox-runs apply-readiness directory");
980
+ if (resolvedApplyReadiness.errors.length === 0 && isInsidePath(expectedPrefix, resolvedApplyReadiness.path)) {
981
+ applyReadinessPath = resolvedApplyReadiness.path;
982
+ applyReadiness = readRecord(applyReadinessPath, errors, "sandbox apply readiness");
983
+ applyReadinessValidation = readRecord(join(dirname(applyReadinessPath), "validation.json"), errors, "sandbox apply readiness validation");
984
+ if (!existsSync(join(dirname(applyReadinessPath), "APPLY_NOT_PERFORMED.sentinel"))) errors.push("sandbox apply simulation requires APPLY_NOT_PERFORMED.sentinel from manual apply readiness");
985
+ }
986
+ }
987
+
988
+ const readinessEvidence = isRecord(applyReadiness?.evidence) ? applyReadiness.evidence : undefined;
989
+ let diffReviewGate: Record<string, unknown> | undefined;
990
+ if (typeof readinessEvidence?.diffReviewGate === "string") {
991
+ const resolvedDiffReviewGate = resolveRepoPath(repoRoot, readinessEvidence.diffReviewGate);
992
+ errors.push(...resolvedDiffReviewGate.errors.map((error) => `apply_readiness.evidence.diffReviewGate: ${error}`));
993
+ if (resolvedDiffReviewGate.errors.length === 0) diffReviewGate = readRecord(resolvedDiffReviewGate.path, errors, "sandbox diff review gate");
994
+ }
995
+ let oracleReview: Record<string, unknown> | undefined;
996
+ if (typeof readinessEvidence?.oracleReview === "string") {
997
+ const resolvedOracleReview = resolveRepoPath(repoRoot, readinessEvidence.oracleReview);
998
+ errors.push(...resolvedOracleReview.errors.map((error) => `apply_readiness.evidence.oracleReview: ${error}`));
999
+ if (resolvedOracleReview.errors.length === 0) oracleReview = readRecord(resolvedOracleReview.path, errors, "sandbox apply oracle review");
1000
+ }
1001
+
1002
+ if (manifest) {
1003
+ if (manifest.schema !== "zob.sandbox-write-plan.v1") errors.push("sandbox manifest schema must be zob.sandbox-write-plan.v1 before apply simulation");
1004
+ if (manifest.runId !== input.run_id) errors.push("sandbox manifest runId must match input run_id before apply simulation");
1005
+ if (manifest.status !== "planned_safe") errors.push("sandbox manifest must be planned_safe before apply simulation");
1006
+ if (manifest.autoApply !== false || manifest.noExecution !== true || manifest.humanApprovalRequired !== true) errors.push("sandbox manifest must remain noExecution=true, autoApply=false, humanApprovalRequired=true before apply simulation");
1007
+ if (typeof manifest.diffHash !== "string" || !HEX_SHA256.test(manifest.diffHash)) errors.push("sandbox manifest requires sha256 diffHash before apply simulation");
1008
+ if (manifestChangedPaths.length === 0) errors.push("sandbox manifest requires changed paths before apply simulation");
1009
+ if (containsForbiddenPlaintextKeys(manifest)) errors.push("sandbox manifest must remain metadata/hash-only before apply simulation");
1010
+ }
1011
+ if (validation) {
1012
+ if (validation.schema !== "zob.sandbox-write-validation.v1") errors.push("sandbox validation schema must be zob.sandbox-write-validation.v1 before apply simulation");
1013
+ if (validation.status !== "planned_safe") errors.push("sandbox validation must be planned_safe before apply simulation");
1014
+ if (Array.isArray(validation.errors) && validation.errors.length > 0) errors.push("sandbox validation must have no errors before apply simulation");
1015
+ if (validation.autoApply !== false || validation.noExecution !== true || validation.humanApprovalRequired !== true) errors.push("sandbox validation must remain noExecution=true, autoApply=false, humanApprovalRequired=true before apply simulation");
1016
+ if (validation.rollbackPrepared !== true || validation.rollbackApplied !== false) errors.push("sandbox validation requires rollbackPrepared=true and rollbackApplied=false before apply simulation");
1017
+ if (!sameStringSet(validation.changedPaths, manifestChangedPaths)) errors.push("sandbox validation changedPaths must match sandbox manifest changes before apply simulation");
1018
+ if (manifest && validation.diffHash !== manifest.diffHash) errors.push("sandbox validation diffHash must match sandbox manifest diffHash before apply simulation");
1019
+ if (containsForbiddenPlaintextKeys(validation)) errors.push("sandbox validation must remain metadata/hash-only before apply simulation");
1020
+ }
1021
+ if (diffGate) {
1022
+ if (diffGate.schema !== "zob.diff-gate-result.v1") errors.push("diff gate schema must be zob.diff-gate-result.v1 before apply simulation");
1023
+ if (diffGate.allowed !== true || diffGate.applyRequired !== true || diffGate.autoApply !== false) errors.push("diff gate must be allowed=true, applyRequired=true, autoApply=false before apply simulation");
1024
+ if (!sameStringSet(diffGate.changedPaths, manifestChangedPaths)) errors.push("diff gate changedPaths must match sandbox manifest changes before apply simulation");
1025
+ if (manifest && diffGate.diffHash !== manifest.diffHash) errors.push("diff gate diffHash must match sandbox manifest diffHash before apply simulation");
1026
+ if (containsForbiddenPlaintextKeys(diffGate)) errors.push("diff gate must remain metadata/hash-only before apply simulation");
1027
+ }
1028
+ if (rollback) {
1029
+ if (rollback.schema !== "zob.rollback-metadata.v1") errors.push("rollback metadata schema must be zob.rollback-metadata.v1 before apply simulation");
1030
+ if (rollback.runId !== input.run_id) errors.push("rollback metadata runId must match input run_id before apply simulation");
1031
+ if (rollback.rollbackPrepared !== true || rollback.rollbackApplied !== false || rollback.autoApply !== false) errors.push("rollback metadata must prepare rollback without applying it before apply simulation");
1032
+ if (typeof rollback.snapshotPath !== "string" || rollback.snapshotPath.trim().length === 0) errors.push("rollback metadata requires snapshotPath before apply simulation");
1033
+ if (!sameStringSet(rollback.changedPaths, manifestChangedPaths)) errors.push("rollback metadata changedPaths must match sandbox manifest changes before apply simulation");
1034
+ if (containsForbiddenPlaintextKeys(rollback)) errors.push("rollback metadata must remain metadata/hash-only before apply simulation");
1035
+ }
1036
+ if (!sandboxRoot || !existsSync(sandboxRoot)) errors.push("sandboxRoot workspace is required before apply simulation");
1037
+
1038
+ if (applyReadiness) {
1039
+ if (applyReadiness.schema !== "zob.sandbox-apply-readiness.v1") errors.push("sandbox apply readiness schema must be zob.sandbox-apply-readiness.v1 before apply simulation");
1040
+ if (applyReadiness.runId !== input.run_id) errors.push("sandbox apply readiness runId must match input run_id before apply simulation");
1041
+ if (applyReadiness.status !== "ready_for_manual_apply" || applyReadiness.applyReady !== true) errors.push("sandbox apply readiness must be ready_for_manual_apply before apply simulation");
1042
+ if (applyReadiness.applyPerformed !== false || applyReadiness.autoApply !== false || applyReadiness.productionWritesPerformed !== false || applyReadiness.noExecution !== true) errors.push("sandbox apply readiness must not have performed production apply before apply simulation");
1043
+ if (manifest && applyReadiness.diffHash !== manifest.diffHash) errors.push("sandbox apply readiness diffHash must match sandbox manifest diffHash before apply simulation");
1044
+ if (!sameStringSet(applyReadiness.changedPaths, manifestChangedPaths)) errors.push("sandbox apply readiness changedPaths must match sandbox manifest changes before apply simulation");
1045
+ const gates = isRecord(applyReadiness.gates) ? applyReadiness.gates : undefined;
1046
+ if (!gates || gates.sandboxPlanReady !== true || gates.diffAllowed !== true || gates.rollbackPrepared !== true || gates.diffReviewPassed !== true || gates.oracleReviewPassed !== true || gates.approvalPresent !== true) errors.push("sandbox apply readiness gates must all pass before apply simulation");
1047
+ const approval = isRecord(applyReadiness.approval) ? applyReadiness.approval : undefined;
1048
+ if (!approval || typeof approval.approvedByHash !== "string" || !HEX_SHA256.test(approval.approvedByHash) || typeof approval.approvalIdHash !== "string" || !HEX_SHA256.test(approval.approvalIdHash) || "approvedBy" in approval || "approvalId" in approval) errors.push("sandbox apply readiness approval must be hash-only before apply simulation");
1049
+ if (!readinessEvidence || typeof readinessEvidence.diffReviewGate !== "string" || typeof readinessEvidence.oracleReview !== "string") errors.push("sandbox apply readiness evidence must include diff review gate and oracle review paths before apply simulation");
1050
+ if (containsForbiddenPlaintextKeys(applyReadiness)) errors.push("sandbox apply readiness must remain metadata/hash-only before apply simulation");
1051
+ }
1052
+ if (applyReadinessValidation) {
1053
+ if (applyReadinessValidation.schema !== "zob.sandbox-apply-readiness-validation.v1") errors.push("sandbox apply readiness validation schema must be zob.sandbox-apply-readiness-validation.v1 before apply simulation");
1054
+ if (applyReadinessValidation.status !== "ready_for_manual_apply" || applyReadinessValidation.applyReady !== true) errors.push("sandbox apply readiness validation must be ready before apply simulation");
1055
+ if (applyReadinessValidation.applyPerformed !== false || applyReadinessValidation.autoApply !== false || applyReadinessValidation.productionWritesPerformed !== false || applyReadinessValidation.noExecution !== true) errors.push("sandbox apply readiness validation must not have performed production apply before apply simulation");
1056
+ if (containsForbiddenPlaintextKeys(applyReadinessValidation)) errors.push("sandbox apply readiness validation must remain metadata/hash-only before apply simulation");
1057
+ }
1058
+ if (diffReviewGate) {
1059
+ const gates = isRecord(diffReviewGate.gates) ? diffReviewGate.gates : undefined;
1060
+ if (diffReviewGate.schema !== "zob.sandbox-diff-review-gate.v1" || diffReviewGate.runId !== input.run_id || diffReviewGate.status !== "diff_review_passed" || diffReviewGate.reviewPassed !== true || diffReviewGate.applyReadyUnlocked !== true) errors.push("sandbox diff review gate must pass before apply simulation");
1061
+ if (diffReviewGate.applyPerformed !== false || diffReviewGate.productionWritesPerformed !== false || diffReviewGate.autoApply !== false || diffReviewGate.noExecution !== true) errors.push("sandbox diff review gate must not have applied production writes before apply simulation");
1062
+ if (manifest && diffReviewGate.diffHash !== manifest.diffHash) errors.push("sandbox diff review gate diffHash must match sandbox manifest diffHash before apply simulation");
1063
+ if (!gates || gates.rollbackValidated !== true || gates.oracleReviewPassed !== true || gates.isolatedExecutionValidated !== true) errors.push("sandbox diff review gate requires rollback/oracle/isolated execution gates before apply simulation");
1064
+ if (containsForbiddenPlaintextKeys(diffReviewGate)) errors.push("sandbox diff review gate must remain metadata/hash-only before apply simulation");
1065
+ }
1066
+ if (oracleReview) {
1067
+ if (oracleReview.schema !== "zob.oracle-review.v1" || oracleReview.reviewedRunId !== input.run_id || oracleReview.verdict !== "PASS" || oracleReview.no_ship !== false) errors.push("sandbox apply oracle review must PASS before apply simulation");
1068
+ if (typeof oracleReview.evidence !== "string" || oracleReview.evidence.trim().length === 0) errors.push("sandbox apply oracle review evidence is required before apply simulation");
1069
+ }
1070
+ if (sandboxRoot) {
1071
+ const targetWorkspace = join(sandboxRoot, "apply-simulations", simulationId, "target-workspace");
1072
+ if (!isInsidePath(sandboxRoot, targetWorkspace)) errors.push("sandbox apply simulation target workspace must stay inside sandboxRoot");
1073
+ }
1074
+
1075
+ return errors;
1076
+ }
1077
+
1078
+ export function runSandboxApplySimulation(repoRoot: string, input: SandboxApplySimulationInput): SandboxApplySimulationResult {
1079
+ const runId = safeRunId(input.run_id, "sandbox");
1080
+ const simulationId = safeRunId(input.simulation_id, "apply-simulation");
1081
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
1082
+ const simulationDir = join(runDir, "apply-simulations", simulationId);
1083
+ const manifestPath = join(runDir, "manifest.json");
1084
+ const manifest = existsSync(manifestPath) && isRecord(parseJsonFile(manifestPath)) ? parseJsonFile(manifestPath) as Record<string, unknown> : undefined;
1085
+ const sandboxRootErrors: string[] = [];
1086
+ const sandboxRoot = sandboxRootFromManifest(repoRoot, runId, manifest, sandboxRootErrors);
1087
+ const fallbackSandboxRoot = join(repoRoot, ".pi", "tmp", "sandbox-runs", runId);
1088
+ const targetWorkspace = join(sandboxRoot ?? fallbackSandboxRoot, "apply-simulations", simulationId, "target-workspace");
1089
+ const errors = validateSandboxApplySimulationInputs(repoRoot, input);
1090
+ const status: SandboxApplySimulationResult["status"] = errors.length === 0 ? "simulated_apply_in_temp_workspace" : "blocked_preflight";
1091
+ const simulatedApplyPerformed = status === "simulated_apply_in_temp_workspace";
1092
+ const changes = changesFromManifest(manifest);
1093
+ const changedPaths = changes.map((change) => String(change.path));
1094
+ const markerDir = join(targetWorkspace, "simulated-change-markers");
1095
+ const rollbackSnapshotPath = join(targetWorkspace, "rollback-snapshot-simulated.metadata.json");
1096
+ const markerPaths: string[] = [];
1097
+ const artifacts = ["apply-simulation.json", "validation.json", simulatedApplyPerformed ? "SANDBOX_APPLY_SIMULATED.sentinel" : "SANDBOX_APPLY_SIMULATION_BLOCKED.sentinel"];
1098
+ const canWriteTargetWorkspace = simulatedApplyPerformed && sandboxRootErrors.length === 0 && typeof sandboxRoot === "string" && existsSync(sandboxRoot) && isInsidePath(sandboxRoot, targetWorkspace);
1099
+
1100
+ mkdirSync(simulationDir, { recursive: true });
1101
+ if (canWriteTargetWorkspace) {
1102
+ mkdirSync(markerDir, { recursive: true });
1103
+ writeFileSync(join(targetWorkspace, "README.md"), ["# ZOB sandbox apply simulation target", "", "This temp workspace contains metadata-only simulated apply markers.", "Production writes and auto-apply are disabled.", ""].join("\n"), "utf8");
1104
+ for (const [index, change] of changes.entries()) {
1105
+ const markerName = `${String(index + 1).padStart(3, "0")}-${safeFileStem(String(change.path).replace(/[\\/]+/g, "-"))}.metadata.json`;
1106
+ const markerPath = join(markerDir, markerName);
1107
+ const marker = {
1108
+ schema: "zob.sandbox-apply-simulation-marker.v1",
1109
+ runId,
1110
+ simulationId,
1111
+ targetPath: change.path,
1112
+ action: change.action,
1113
+ contentHash: change.contentHash,
1114
+ reasonHash: change.reasonHash,
1115
+ simulatedTempWorkspaceWrite: true,
1116
+ productionWritePerformed: false,
1117
+ autoApply: false,
1118
+ bodyStored: false,
1119
+ promptBodiesStored: false,
1120
+ outputBodiesStored: false,
1121
+ generatedAt: new Date().toISOString(),
1122
+ };
1123
+ writeFileSync(markerPath, JSON.stringify(marker, null, 2), "utf8");
1124
+ markerPaths.push(markerPath);
1125
+ }
1126
+ writeFileSync(rollbackSnapshotPath, JSON.stringify({
1127
+ schema: "zob.sandbox-apply-simulation-rollback-snapshot.v1",
1128
+ runId,
1129
+ simulationId,
1130
+ changedPaths,
1131
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
1132
+ rollbackPrepared: true,
1133
+ rollbackApplied: false,
1134
+ productionRollbackRequired: false,
1135
+ bodyStored: false,
1136
+ promptBodiesStored: false,
1137
+ outputBodiesStored: false,
1138
+ generatedAt: new Date().toISOString(),
1139
+ }, null, 2), "utf8");
1140
+ }
1141
+
1142
+ const simulation = {
1143
+ schema: "zob.sandbox-apply-simulation.v1",
1144
+ runId,
1145
+ simulationId,
1146
+ status,
1147
+ simulatedApplyPerformed,
1148
+ applyPerformed: false,
1149
+ autoApply: false,
1150
+ manualApplyRequired: true,
1151
+ humanApprovalRequired: true,
1152
+ productionWritesPerformed: false,
1153
+ tempTargetWorkspace: targetWorkspace,
1154
+ tempTargetWorkspaceWritten: canWriteTargetWorkspace,
1155
+ tempTargetWorkspaceScoped: typeof sandboxRoot === "string" && isInsidePath(sandboxRoot, targetWorkspace),
1156
+ changedPaths,
1157
+ changeCount: changes.length,
1158
+ markerPaths,
1159
+ markerCount: markerPaths.length,
1160
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
1161
+ evidence: {
1162
+ manifest: manifestPath,
1163
+ applyReadiness: input.apply_readiness_path ? resolveRepoPath(repoRoot, input.apply_readiness_path).path : undefined,
1164
+ rollbackSnapshot: canWriteTargetWorkspace ? rollbackSnapshotPath : undefined,
1165
+ },
1166
+ gates: {
1167
+ applyReadinessReady: simulatedApplyPerformed,
1168
+ rollbackPrepared: simulatedApplyPerformed,
1169
+ tempTargetWorkspaceScoped: typeof sandboxRoot === "string" && isInsidePath(sandboxRoot, targetWorkspace),
1170
+ productionWritesBlocked: true,
1171
+ autoApplyBlocked: true,
1172
+ },
1173
+ childDispatchAllowed: false,
1174
+ networkAccessed: false,
1175
+ liveChildExecution: false,
1176
+ rollbackPrepared: simulatedApplyPerformed,
1177
+ rollbackApplied: false,
1178
+ bodyStored: false,
1179
+ promptBodiesStored: false,
1180
+ outputBodiesStored: false,
1181
+ errors,
1182
+ generatedAt: new Date().toISOString(),
1183
+ };
1184
+ const validation = {
1185
+ schema: "zob.sandbox-apply-simulation-validation.v1",
1186
+ runId,
1187
+ simulationId,
1188
+ status,
1189
+ simulatedApplyPerformed,
1190
+ productionWritesPerformed: false,
1191
+ autoApply: false,
1192
+ tempTargetWorkspace: targetWorkspace,
1193
+ tempTargetWorkspaceWritten: canWriteTargetWorkspace,
1194
+ markerCount: markerPaths.length,
1195
+ changedPaths,
1196
+ errors,
1197
+ sentinel: simulatedApplyPerformed ? "SANDBOX_APPLY_SIMULATED.sentinel" : "SANDBOX_APPLY_SIMULATION_BLOCKED.sentinel",
1198
+ bodyStored: false,
1199
+ promptBodiesStored: false,
1200
+ outputBodiesStored: false,
1201
+ generatedAt: new Date().toISOString(),
1202
+ };
1203
+ if (containsForbiddenPlaintextKeys(simulation) || containsForbiddenPlaintextKeys(validation)) errors.push("sandbox apply simulation artifacts must remain metadata/hash-only");
1204
+
1205
+ writeFileSync(join(simulationDir, "apply-simulation.json"), JSON.stringify(simulation, null, 2), "utf8");
1206
+ writeFileSync(join(simulationDir, "validation.json"), JSON.stringify(validation, null, 2), "utf8");
1207
+ const sentinel = simulatedApplyPerformed ? "SANDBOX_APPLY_SIMULATED.sentinel" : "SANDBOX_APPLY_SIMULATION_BLOCKED.sentinel";
1208
+ writeFileSync(join(simulationDir, sentinel), `${status} ${new Date().toISOString()}\n`, "utf8");
1209
+ sandboxLedger(runDir, { event: "sandbox_apply_simulation", simulationId, status, simulatedApplyPerformed, applyPerformed: false, autoApply: false, productionWritesPerformed: false });
1210
+
1211
+ return { runId, simulationId, simulationDir, targetWorkspace, status, simulatedApplyPerformed, productionWritesPerformed: false, autoApply: false, artifacts, errors };
1212
+ }
1213
+
1214
+ export function validateSandboxManualApplyPreflightInputs(repoRoot: string, input: SandboxManualApplyPreflightInput): string[] {
1215
+ const errors: string[] = [];
1216
+ if (!input.run_id || safeFileStem(input.run_id) !== input.run_id) errors.push(`run_id must be path-safe: ${input.run_id}`);
1217
+ const runId = input.run_id ?? "invalid-run";
1218
+ const preflightId = input.preflight_id ?? "manual-apply-preflight";
1219
+ if (safeFileStem(preflightId) !== preflightId) errors.push(`preflight_id must be path-safe: ${preflightId}`);
1220
+ const expectedConfirmation = expectedManualApplyPreflightConfirmation(runId, preflightId);
1221
+ if (input.confirmation_phrase !== expectedConfirmation) errors.push(`confirmation_phrase must exactly match: ${expectedConfirmation}`);
1222
+ if (containsForbiddenPlaintextKeys(input)) errors.push("sandbox manual apply preflight metadata must not include plaintext task/prompt/output/body/content/patch/diff keys");
1223
+
1224
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
1225
+ const manifest = readRecord(join(runDir, "manifest.json"), errors, "sandbox manifest");
1226
+ const validation = readRecord(join(runDir, "validation.json"), errors, "sandbox validation");
1227
+ const diffGate = readRecord(join(runDir, "diff-gate.json"), errors, "diff gate");
1228
+ const rollback = readRecord(join(runDir, "rollback-metadata.json"), errors, "rollback metadata");
1229
+ const manifestChanges = changesFromManifest(manifest);
1230
+ const manifestChangedPaths = manifestChanges.map((change) => String(change.path));
1231
+ const allowedPaths = Array.isArray(manifest?.allowedPaths) ? manifest.allowedPaths.filter((item): item is string => typeof item === "string") : [];
1232
+ const forbiddenPaths = Array.isArray(manifest?.forbiddenPaths) ? manifest.forbiddenPaths.filter((item): item is string => typeof item === "string") : [];
1233
+
1234
+ let applyReadinessPath: string | undefined;
1235
+ let applyReadiness: Record<string, unknown> | undefined;
1236
+ let applyReadinessValidation: Record<string, unknown> | undefined;
1237
+ if (!input.apply_readiness_path) {
1238
+ errors.push("sandbox manual apply preflight requires apply_readiness_path");
1239
+ } else {
1240
+ const resolvedApplyReadiness = resolveRepoPath(repoRoot, input.apply_readiness_path);
1241
+ errors.push(...resolvedApplyReadiness.errors.map((error) => `apply_readiness_path: ${error}`));
1242
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
1243
+ if (pathMatches(input.apply_readiness_path, protectedPattern, repoRoot, repoRoot)) errors.push(`apply_readiness_path references zero-access path: ${protectedPattern}`);
1244
+ }
1245
+ const expectedPrefix = join(repoRoot, "reports", "sandbox-runs", runId, "apply-readiness");
1246
+ if (!isInsidePath(expectedPrefix, resolvedApplyReadiness.path)) errors.push("apply_readiness_path must stay inside this run's reports/sandbox-runs apply-readiness directory");
1247
+ if (resolvedApplyReadiness.errors.length === 0 && isInsidePath(expectedPrefix, resolvedApplyReadiness.path)) {
1248
+ applyReadinessPath = resolvedApplyReadiness.path;
1249
+ applyReadiness = readRecord(applyReadinessPath, errors, "sandbox apply readiness");
1250
+ applyReadinessValidation = readRecord(join(dirname(applyReadinessPath), "validation.json"), errors, "sandbox apply readiness validation");
1251
+ if (!existsSync(join(dirname(applyReadinessPath), "APPLY_NOT_PERFORMED.sentinel"))) errors.push("sandbox manual apply preflight requires APPLY_NOT_PERFORMED.sentinel from apply readiness");
1252
+ }
1253
+ }
1254
+
1255
+ let applySimulationPath: string | undefined;
1256
+ let applySimulation: Record<string, unknown> | undefined;
1257
+ let applySimulationValidation: Record<string, unknown> | undefined;
1258
+ if (!input.apply_simulation_path) {
1259
+ errors.push("sandbox manual apply preflight requires apply_simulation_path");
1260
+ } else {
1261
+ const resolvedApplySimulation = resolveRepoPath(repoRoot, input.apply_simulation_path);
1262
+ errors.push(...resolvedApplySimulation.errors.map((error) => `apply_simulation_path: ${error}`));
1263
+ for (const protectedPattern of DEFAULT_RULES.zeroAccessPaths) {
1264
+ if (pathMatches(input.apply_simulation_path, protectedPattern, repoRoot, repoRoot)) errors.push(`apply_simulation_path references zero-access path: ${protectedPattern}`);
1265
+ }
1266
+ const expectedPrefix = join(repoRoot, "reports", "sandbox-runs", runId, "apply-simulations");
1267
+ if (!isInsidePath(expectedPrefix, resolvedApplySimulation.path)) errors.push("apply_simulation_path must stay inside this run's reports/sandbox-runs apply-simulations directory");
1268
+ if (resolvedApplySimulation.errors.length === 0 && isInsidePath(expectedPrefix, resolvedApplySimulation.path)) {
1269
+ applySimulationPath = resolvedApplySimulation.path;
1270
+ applySimulation = readRecord(applySimulationPath, errors, "sandbox apply simulation");
1271
+ applySimulationValidation = readRecord(join(dirname(applySimulationPath), "validation.json"), errors, "sandbox apply simulation validation");
1272
+ if (!existsSync(join(dirname(applySimulationPath), "SANDBOX_APPLY_SIMULATED.sentinel"))) errors.push("sandbox manual apply preflight requires SANDBOX_APPLY_SIMULATED.sentinel from apply simulation");
1273
+ }
1274
+ }
1275
+
1276
+ if (manifest) {
1277
+ if (manifest.schema !== "zob.sandbox-write-plan.v1") errors.push("sandbox manifest schema must be zob.sandbox-write-plan.v1 before manual apply preflight");
1278
+ if (manifest.runId !== runId) errors.push("sandbox manifest runId must match input run_id before manual apply preflight");
1279
+ if (manifest.status !== "planned_safe") errors.push("sandbox manifest must be planned_safe before manual apply preflight");
1280
+ if (manifest.autoApply !== false || manifest.noExecution !== true || manifest.humanApprovalRequired !== true) errors.push("sandbox manifest must remain noExecution=true, autoApply=false, humanApprovalRequired=true before manual apply preflight");
1281
+ if (typeof manifest.diffHash !== "string" || !HEX_SHA256.test(manifest.diffHash)) errors.push("sandbox manifest requires sha256 diffHash before manual apply preflight");
1282
+ if (manifestChanges.length === 0) errors.push("sandbox manifest requires changed paths before manual apply preflight");
1283
+ if (allowedPaths.length === 0) errors.push("sandbox manifest requires non-empty allowedPaths before manual apply preflight");
1284
+ if (containsForbiddenPlaintextKeys(manifest)) errors.push("sandbox manifest must remain metadata/hash-only before manual apply preflight");
1285
+ for (const [index, change] of manifestChanges.entries()) {
1286
+ const targetPath = typeof change.path === "string" ? change.path : String(change.path);
1287
+ const policy = validateRuntimeWritePolicy({
1288
+ targetPath,
1289
+ cwd: repoRoot,
1290
+ allowedPaths,
1291
+ forbiddenPaths,
1292
+ zeroAccessPaths: DEFAULT_RULES.zeroAccessPaths,
1293
+ readOnlyPaths: DEFAULT_RULES.readOnlyPaths,
1294
+ });
1295
+ errors.push(...policy.violations.map((violation) => `manifest.changes[${index}]: ${violation}`));
1296
+ }
1297
+ }
1298
+ if (validation) {
1299
+ if (validation.schema !== "zob.sandbox-write-validation.v1") errors.push("sandbox validation schema must be zob.sandbox-write-validation.v1 before manual apply preflight");
1300
+ if (validation.status !== "planned_safe") errors.push("sandbox validation must be planned_safe before manual apply preflight");
1301
+ if (Array.isArray(validation.errors) && validation.errors.length > 0) errors.push("sandbox validation must have no errors before manual apply preflight");
1302
+ if (validation.autoApply !== false || validation.noExecution !== true || validation.humanApprovalRequired !== true) errors.push("sandbox validation must remain noExecution=true, autoApply=false, humanApprovalRequired=true before manual apply preflight");
1303
+ if (validation.rollbackPrepared !== true || validation.rollbackApplied !== false) errors.push("sandbox validation requires rollbackPrepared=true and rollbackApplied=false before manual apply preflight");
1304
+ if (!sameStringSet(validation.changedPaths, manifestChangedPaths)) errors.push("sandbox validation changedPaths must match sandbox manifest changes before manual apply preflight");
1305
+ if (manifest && validation.diffHash !== manifest.diffHash) errors.push("sandbox validation diffHash must match sandbox manifest diffHash before manual apply preflight");
1306
+ if (validation.sentinel !== "SANDBOX_PLAN_READY.sentinel" || !existsSync(join(runDir, "SANDBOX_PLAN_READY.sentinel"))) errors.push("sandbox plan ready sentinel is required before manual apply preflight");
1307
+ if (containsForbiddenPlaintextKeys(validation)) errors.push("sandbox validation must remain metadata/hash-only before manual apply preflight");
1308
+ }
1309
+ if (diffGate) {
1310
+ if (diffGate.schema !== "zob.diff-gate-result.v1") errors.push("diff gate schema must be zob.diff-gate-result.v1 before manual apply preflight");
1311
+ if (diffGate.allowed !== true || diffGate.applyRequired !== true || diffGate.autoApply !== false) errors.push("diff gate must be allowed=true, applyRequired=true, autoApply=false before manual apply preflight");
1312
+ if (!sameStringSet(diffGate.changedPaths, manifestChangedPaths)) errors.push("diff gate changedPaths must match sandbox manifest changes before manual apply preflight");
1313
+ if (manifest && diffGate.diffHash !== manifest.diffHash) errors.push("diff gate diffHash must match sandbox manifest diffHash before manual apply preflight");
1314
+ if (containsForbiddenPlaintextKeys(diffGate)) errors.push("diff gate must remain metadata/hash-only before manual apply preflight");
1315
+ }
1316
+ if (rollback) {
1317
+ if (rollback.schema !== "zob.rollback-metadata.v1") errors.push("rollback metadata schema must be zob.rollback-metadata.v1 before manual apply preflight");
1318
+ if (rollback.runId !== runId) errors.push("rollback metadata runId must match input run_id before manual apply preflight");
1319
+ if (rollback.rollbackPrepared !== true || rollback.rollbackApplied !== false || rollback.autoApply !== false) errors.push("rollback metadata must prepare rollback without applying it before manual apply preflight");
1320
+ if (typeof rollback.snapshotPath !== "string" || rollback.snapshotPath.trim().length === 0) errors.push("rollback metadata requires snapshotPath before manual apply preflight");
1321
+ if (!sameStringSet(rollback.changedPaths, manifestChangedPaths)) errors.push("rollback metadata changedPaths must match sandbox manifest changes before manual apply preflight");
1322
+ if (containsForbiddenPlaintextKeys(rollback)) errors.push("rollback metadata must remain metadata/hash-only before manual apply preflight");
1323
+ }
1324
+ if (applyReadiness) {
1325
+ const gates = isRecord(applyReadiness.gates) ? applyReadiness.gates : undefined;
1326
+ const approval = isRecord(applyReadiness.approval) ? applyReadiness.approval : undefined;
1327
+ if (applyReadiness.schema !== "zob.sandbox-apply-readiness.v1") errors.push("sandbox apply readiness schema must be zob.sandbox-apply-readiness.v1 before manual apply preflight");
1328
+ if (applyReadiness.runId !== runId) errors.push("sandbox apply readiness runId must match input run_id before manual apply preflight");
1329
+ if (applyReadiness.status !== "ready_for_manual_apply" || applyReadiness.applyReady !== true) errors.push("sandbox apply readiness must be ready_for_manual_apply before manual apply preflight");
1330
+ if (applyReadiness.applyPerformed !== false || applyReadiness.autoApply !== false || applyReadiness.productionWritesPerformed !== false || applyReadiness.noExecution !== true) errors.push("sandbox apply readiness must not have performed production apply before manual apply preflight");
1331
+ if (manifest && applyReadiness.diffHash !== manifest.diffHash) errors.push("sandbox apply readiness diffHash must match sandbox manifest diffHash before manual apply preflight");
1332
+ if (!sameStringSet(applyReadiness.changedPaths, manifestChangedPaths)) errors.push("sandbox apply readiness changedPaths must match sandbox manifest changes before manual apply preflight");
1333
+ if (!gates || gates.sandboxPlanReady !== true || gates.diffAllowed !== true || gates.rollbackPrepared !== true || gates.diffReviewPassed !== true || gates.oracleReviewPassed !== true || gates.approvalPresent !== true) errors.push("sandbox apply readiness gates must all pass before manual apply preflight");
1334
+ if (!approval || typeof approval.approvedByHash !== "string" || !HEX_SHA256.test(approval.approvedByHash) || typeof approval.approvalIdHash !== "string" || !HEX_SHA256.test(approval.approvalIdHash) || "approvedBy" in approval || "approvalId" in approval) errors.push("sandbox apply readiness approval must be hash-only before manual apply preflight");
1335
+ if (containsForbiddenPlaintextKeys(applyReadiness)) errors.push("sandbox apply readiness must remain metadata/hash-only before manual apply preflight");
1336
+ }
1337
+ if (applyReadinessValidation) {
1338
+ if (applyReadinessValidation.schema !== "zob.sandbox-apply-readiness-validation.v1") errors.push("sandbox apply readiness validation schema must be zob.sandbox-apply-readiness-validation.v1 before manual apply preflight");
1339
+ if (applyReadinessValidation.status !== "ready_for_manual_apply" || applyReadinessValidation.applyReady !== true) errors.push("sandbox apply readiness validation must be ready before manual apply preflight");
1340
+ if (applyReadinessValidation.applyPerformed !== false || applyReadinessValidation.autoApply !== false || applyReadinessValidation.productionWritesPerformed !== false || applyReadinessValidation.noExecution !== true) errors.push("sandbox apply readiness validation must not have performed production apply before manual apply preflight");
1341
+ if (containsForbiddenPlaintextKeys(applyReadinessValidation)) errors.push("sandbox apply readiness validation must remain metadata/hash-only before manual apply preflight");
1342
+ }
1343
+ if (applySimulation) {
1344
+ const simulationGates = isRecord(applySimulation.gates) ? applySimulation.gates : undefined;
1345
+ const simulationEvidence = isRecord(applySimulation.evidence) ? applySimulation.evidence : undefined;
1346
+ if (applySimulation.schema !== "zob.sandbox-apply-simulation.v1") errors.push("sandbox apply simulation schema must be zob.sandbox-apply-simulation.v1 before manual apply preflight");
1347
+ if (applySimulation.runId !== runId) errors.push("sandbox apply simulation runId must match input run_id before manual apply preflight");
1348
+ if (applySimulation.status !== "simulated_apply_in_temp_workspace" || applySimulation.simulatedApplyPerformed !== true) errors.push("sandbox apply simulation must pass before manual apply preflight");
1349
+ if (applySimulation.applyPerformed !== false || applySimulation.autoApply !== false || applySimulation.productionWritesPerformed !== false) errors.push("sandbox apply simulation must not have performed production apply before manual apply preflight");
1350
+ if (applySimulation.tempTargetWorkspaceScoped !== true || applySimulation.tempTargetWorkspaceWritten !== true) errors.push("sandbox apply simulation must write only a scoped temp target workspace before manual apply preflight");
1351
+ if (manifest && applySimulation.diffHash !== manifest.diffHash) errors.push("sandbox apply simulation diffHash must match sandbox manifest diffHash before manual apply preflight");
1352
+ if (!sameStringSet(applySimulation.changedPaths, manifestChangedPaths)) errors.push("sandbox apply simulation changedPaths must match sandbox manifest changes before manual apply preflight");
1353
+ if (!simulationGates || simulationGates.applyReadinessReady !== true || simulationGates.rollbackPrepared !== true || simulationGates.productionWritesBlocked !== true || simulationGates.autoApplyBlocked !== true) errors.push("sandbox apply simulation gates must pass before manual apply preflight");
1354
+ if (applyReadinessPath && simulationEvidence && typeof simulationEvidence.applyReadiness === "string" && resolve(simulationEvidence.applyReadiness) !== resolve(applyReadinessPath)) errors.push("sandbox apply simulation evidence must reference the same apply readiness artifact before manual apply preflight");
1355
+ if (containsForbiddenPlaintextKeys(applySimulation)) errors.push("sandbox apply simulation must remain metadata/hash-only before manual apply preflight");
1356
+ }
1357
+ if (applySimulationValidation) {
1358
+ if (applySimulationValidation.schema !== "zob.sandbox-apply-simulation-validation.v1") errors.push("sandbox apply simulation validation schema must be zob.sandbox-apply-simulation-validation.v1 before manual apply preflight");
1359
+ if (applySimulationValidation.status !== "simulated_apply_in_temp_workspace" || applySimulationValidation.simulatedApplyPerformed !== true) errors.push("sandbox apply simulation validation must pass before manual apply preflight");
1360
+ if (applySimulationValidation.productionWritesPerformed !== false || applySimulationValidation.autoApply !== false) errors.push("sandbox apply simulation validation must not have performed production apply before manual apply preflight");
1361
+ if (containsForbiddenPlaintextKeys(applySimulationValidation)) errors.push("sandbox apply simulation validation must remain metadata/hash-only before manual apply preflight");
1362
+ }
1363
+ return errors;
1364
+ }
1365
+
1366
+ export function runSandboxManualApplyPreflight(repoRoot: string, input: SandboxManualApplyPreflightInput): SandboxManualApplyPreflightResult {
1367
+ const runId = safeRunId(input.run_id, "sandbox");
1368
+ const preflightId = safeRunId(input.preflight_id, "manual-apply-preflight");
1369
+ const runDir = join(repoRoot, "reports", "sandbox-runs", runId);
1370
+ const preflightDir = join(runDir, "manual-apply-preflight", preflightId);
1371
+ const errors = validateSandboxManualApplyPreflightInputs(repoRoot, input);
1372
+ const status: SandboxManualApplyPreflightResult["status"] = errors.length === 0 ? "manual_apply_preflight_passed" : "blocked_preflight";
1373
+ const manualApplyPreflightPassed = status === "manual_apply_preflight_passed";
1374
+ const canUseValidatedArtifactPaths = errors.length === 0;
1375
+ const manifestPath = join(runDir, "manifest.json");
1376
+ const validationPath = join(runDir, "validation.json");
1377
+ const diffGatePath = join(runDir, "diff-gate.json");
1378
+ const rollbackPath = join(runDir, "rollback-metadata.json");
1379
+ const manifest = existsSync(manifestPath) && isRecord(parseJsonFile(manifestPath)) ? parseJsonFile(manifestPath) as Record<string, unknown> : undefined;
1380
+ const validation = existsSync(validationPath) && isRecord(parseJsonFile(validationPath)) ? parseJsonFile(validationPath) as Record<string, unknown> : undefined;
1381
+ const rollback = existsSync(rollbackPath) && isRecord(parseJsonFile(rollbackPath)) ? parseJsonFile(rollbackPath) as Record<string, unknown> : undefined;
1382
+ const applyReadinessPath = canUseValidatedArtifactPaths && input.apply_readiness_path ? resolveRepoPath(repoRoot, input.apply_readiness_path).path : undefined;
1383
+ const applySimulationPath = canUseValidatedArtifactPaths && input.apply_simulation_path ? resolveRepoPath(repoRoot, input.apply_simulation_path).path : undefined;
1384
+ const applyReadiness = canUseValidatedArtifactPaths && applyReadinessPath && existsSync(applyReadinessPath) && isRecord(parseJsonFile(applyReadinessPath)) ? parseJsonFile(applyReadinessPath) as Record<string, unknown> : undefined;
1385
+ const applySimulation = canUseValidatedArtifactPaths && applySimulationPath && existsSync(applySimulationPath) && isRecord(parseJsonFile(applySimulationPath)) ? parseJsonFile(applySimulationPath) as Record<string, unknown> : undefined;
1386
+ const manifestChanges = changesFromManifest(manifest);
1387
+ const changedPaths = manifestChanges.map((change) => String(change.path));
1388
+ const confirmationPhraseHash = input.confirmation_phrase ? sha256(input.confirmation_phrase) : undefined;
1389
+ const expectedConfirmationHash = sha256(expectedManualApplyPreflightConfirmation(runId, preflightId));
1390
+ const approval = isRecord(applyReadiness?.approval) ? applyReadiness.approval : undefined;
1391
+ const preflight = {
1392
+ schema: "zob.sandbox-manual-apply-preflight.v1",
1393
+ runId,
1394
+ preflightId,
1395
+ status,
1396
+ manualApplyPreflightPassed,
1397
+ applyPerformed: false,
1398
+ realApplyExecuted: false,
1399
+ autoApply: false,
1400
+ manualApplyRequired: true,
1401
+ humanApprovalRequired: true,
1402
+ productionWritesPerformed: false,
1403
+ noExecution: true,
1404
+ executionAllowedByThisTool: false,
1405
+ changedPaths,
1406
+ changeCount: changedPaths.length,
1407
+ diffHash: typeof manifest?.diffHash === "string" ? manifest.diffHash : undefined,
1408
+ evidence: {
1409
+ manifest: manifestPath,
1410
+ validation: validationPath,
1411
+ diffGate: diffGatePath,
1412
+ rollback: rollbackPath,
1413
+ applyReadiness: applyReadinessPath,
1414
+ applyReadinessValidation: applyReadinessPath ? join(dirname(applyReadinessPath), "validation.json") : undefined,
1415
+ applySimulation: applySimulationPath,
1416
+ applySimulationValidation: applySimulationPath ? join(dirname(applySimulationPath), "validation.json") : undefined,
1417
+ },
1418
+ evidenceHashes: {
1419
+ manifest: artifactHash(manifestPath),
1420
+ validation: artifactHash(validationPath),
1421
+ diffGate: artifactHash(diffGatePath),
1422
+ rollback: artifactHash(rollbackPath),
1423
+ applyReadiness: artifactHash(applyReadinessPath),
1424
+ applyReadinessValidation: applyReadinessPath ? artifactHash(join(dirname(applyReadinessPath), "validation.json")) : undefined,
1425
+ applySimulation: artifactHash(applySimulationPath),
1426
+ applySimulationValidation: applySimulationPath ? artifactHash(join(dirname(applySimulationPath), "validation.json")) : undefined,
1427
+ },
1428
+ gates: {
1429
+ sandboxPlanReady: manifest?.status === "planned_safe" && validation?.status === "planned_safe",
1430
+ diffAllowed: existsSync(diffGatePath),
1431
+ rollbackPrepared: rollback?.rollbackPrepared === true && rollback?.rollbackApplied === false,
1432
+ applyReadinessReady: canUseValidatedArtifactPaths && applyReadiness?.status === "ready_for_manual_apply" && applyReadiness.applyReady === true,
1433
+ applySimulationPassed: canUseValidatedArtifactPaths && applySimulation?.status === "simulated_apply_in_temp_workspace" && applySimulation.simulatedApplyPerformed === true,
1434
+ confirmationPhraseMatched: confirmationPhraseHash === expectedConfirmationHash,
1435
+ approvalHashOnly: approval !== undefined && typeof approval.approvedByHash === "string" && typeof approval.approvalIdHash === "string" && !("approvedBy" in approval) && !("approvalId" in approval),
1436
+ changedPathsStillScoped: errors.every((error) => !error.includes("manifest.changes")),
1437
+ productionWritesBlocked: true,
1438
+ autoApplyBlocked: true,
1439
+ },
1440
+ approval: approval ? {
1441
+ approvedByHash: approval.approvedByHash,
1442
+ approvedAt: approval.approvedAt,
1443
+ approvalIdHash: approval.approvalIdHash,
1444
+ bodyStored: false,
1445
+ } : undefined,
1446
+ confirmation: {
1447
+ expectedPhraseHash: expectedConfirmationHash,
1448
+ suppliedPhraseHash: confirmationPhraseHash,
1449
+ matched: confirmationPhraseHash === expectedConfirmationHash,
1450
+ rawConfirmationStored: false,
1451
+ },
1452
+ rollbackPolicy: {
1453
+ rollbackPrepared: rollback?.rollbackPrepared === true,
1454
+ rollbackApplied: false,
1455
+ snapshotPathHash: typeof rollback?.snapshotPath === "string" ? sha256(rollback.snapshotPath) : undefined,
1456
+ mainWorkspaceRollbackSnapshotRequiredBeforeRealApply: true,
1457
+ },
1458
+ manualApplyPolicy: {
1459
+ realApplyAllowedByThisPreflight: false,
1460
+ futureManualApplyRequiresSeparateExecutor: true,
1461
+ futureManualApplyRequiresFreshMainWorkspaceSnapshot: true,
1462
+ futureManualApplyRequiresOperatorAtKeyboard: true,
1463
+ futureManualApplyRequiresExactDiffHashMatch: true,
1464
+ futureManualApplyRequiresRollbackDryRun: true,
1465
+ futureManualApplyRequiresPostApplyValidationAndOracle: true,
1466
+ autoApplyAllowed: false,
1467
+ },
1468
+ childDispatchAllowed: false,
1469
+ daemonStarted: false,
1470
+ networkAccessed: false,
1471
+ liveChildExecution: false,
1472
+ globalAutonomyReady: false,
1473
+ globalAutonomyNoShip: true,
1474
+ bodyStored: false,
1475
+ promptBodiesStored: false,
1476
+ outputBodiesStored: false,
1477
+ errors,
1478
+ generatedAt: new Date().toISOString(),
1479
+ };
1480
+ const preflightValidation = {
1481
+ schema: "zob.sandbox-manual-apply-preflight-validation.v1",
1482
+ runId,
1483
+ preflightId,
1484
+ status,
1485
+ manualApplyPreflightPassed,
1486
+ applyPerformed: false,
1487
+ realApplyExecuted: false,
1488
+ productionWritesPerformed: false,
1489
+ autoApply: false,
1490
+ noExecution: true,
1491
+ executionAllowedByThisTool: false,
1492
+ errors,
1493
+ sentinel: "MANUAL_APPLY_NOT_PERFORMED.sentinel",
1494
+ bodyStored: false,
1495
+ promptBodiesStored: false,
1496
+ outputBodiesStored: false,
1497
+ generatedAt: new Date().toISOString(),
1498
+ };
1499
+ if (containsForbiddenPlaintextKeys(preflight) || containsForbiddenPlaintextKeys(preflightValidation)) errors.push("sandbox manual apply preflight artifacts must remain metadata/hash-only");
1500
+
1501
+ mkdirSync(preflightDir, { recursive: true });
1502
+ writeFileSync(join(preflightDir, "manual-apply-preflight.json"), JSON.stringify(preflight, null, 2), "utf8");
1503
+ writeFileSync(join(preflightDir, "validation.json"), JSON.stringify(preflightValidation, null, 2), "utf8");
1504
+ writeFileSync(join(preflightDir, "MANUAL_APPLY_NOT_PERFORMED.sentinel"), `manual_apply_not_performed ${new Date().toISOString()}\n`, "utf8");
1505
+ sandboxLedger(runDir, { event: "sandbox_manual_apply_preflight", preflightId, status, manualApplyPreflightPassed, applyPerformed: false, autoApply: false, productionWritesPerformed: false });
1506
+
1507
+ return { runId, preflightId, preflightDir, status, manualApplyPreflightPassed, applyPerformed: false, productionWritesPerformed: false, autoApply: false, artifacts: ["manual-apply-preflight.json", "validation.json", "MANUAL_APPLY_NOT_PERFORMED.sentinel"], errors };
1508
+ }