devrites 3.2.27 → 4.0.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 (897) hide show
  1. package/CHANGELOG.md +22 -144
  2. package/NOTICE.md +6 -5
  3. package/README.md +187 -95
  4. package/SECURITY.md +103 -79
  5. package/bin/devrites.mjs +140 -24
  6. package/docs/adr/0003-gate-model-hitl-pause.md +1 -1
  7. package/docs/adr/0013-native-codex-agent-orchestration.md +74 -0
  8. package/docs/adr/0014-native-host-hook-boundary.md +64 -0
  9. package/docs/adr/0015-read-only-root-native-orchestration.md +96 -0
  10. package/docs/adr/0016-codex-writer-fail-closed.md +53 -0
  11. package/docs/adr/0017-native-codex-writer-agent.md +63 -0
  12. package/docs/adr/0018-native-sandbox-instruction-writer-boundary.md +66 -0
  13. package/docs/adr/0019-native-boundary-with-deterministic-gates.md +48 -0
  14. package/docs/adr/0020-thin-engine-native-orchestration-boundary.md +54 -0
  15. package/docs/adr/0021-observable-workspace-compatibility.md +49 -0
  16. package/docs/adr/0022-native-orchestration-thin-engine.md +90 -0
  17. package/docs/adr/0023-native-workspace-reads-line-output.md +71 -0
  18. package/docs/adr/0024-native-policy-offline-installer-boundary.md +111 -0
  19. package/docs/adr/0025-evidence-gated-workspace-upgrades.md +64 -0
  20. package/docs/adr/0026-content-bound-proof-and-bounded-inputs.md +169 -0
  21. package/docs/adr/0027-content-bound-build-readiness.md +77 -0
  22. package/docs/adr/README.md +26 -11
  23. package/docs/agents/domain.md +2 -1
  24. package/docs/agents/issue-driven-rites.md +1 -1
  25. package/docs/architecture.md +191 -103
  26. package/docs/candidate-integrity.md +138 -0
  27. package/docs/capability-surface-selection.md +15 -5
  28. package/docs/cli.md +139 -92
  29. package/docs/command-map.md +88 -63
  30. package/docs/engine/commands.md +115 -507
  31. package/docs/engine/state-schema.md +94 -113
  32. package/docs/engine/workspace-schema.md +169 -229
  33. package/docs/flow.md +113 -68
  34. package/docs/harness-compliance.md +29 -56
  35. package/docs/markdown-instruction-upgrade-2026-08-02.md +302 -0
  36. package/docs/orchestration.md +136 -149
  37. package/docs/porting-to-a-new-harness.md +22 -7
  38. package/docs/quick-reference.md +21 -8
  39. package/docs/release.md +47 -4
  40. package/docs/skills.md +111 -72
  41. package/docs/templates/CRITIC_REVIEW_PACKET_TEMPLATE.md +2 -0
  42. package/docs/upstream-workflow-benchmark-2026-08-01.md +655 -0
  43. package/docs/usage.md +99 -65
  44. package/engine/Makefile +1 -2
  45. package/engine/cmd/releasepack/main.go +45 -25
  46. package/engine/cmd/releasepack/main_test.go +69 -0
  47. package/engine/commands.go +8 -258
  48. package/engine/harnessmatrix_test.go +28 -41
  49. package/engine/internal/devritespaths/paths.go +12 -28
  50. package/engine/internal/fsutil/copy.go +8 -0
  51. package/engine/internal/gate/gate.go +104 -105
  52. package/engine/internal/gate/gate_test.go +94 -84
  53. package/engine/internal/gate/readiness_binding.go +203 -0
  54. package/engine/internal/gate/readiness_binding_test.go +271 -0
  55. package/engine/internal/gitenv/env.go +46 -0
  56. package/engine/internal/gitenv/env_test.go +48 -0
  57. package/engine/internal/hostpack/hostpack.go +16 -20
  58. package/engine/internal/hostpack/hostpack_test.go +6 -6
  59. package/engine/internal/install/install.go +289 -511
  60. package/engine/internal/install/install_test.go +218 -306
  61. package/engine/internal/lib/candidate.go +475 -0
  62. package/engine/internal/lib/candidate_test.go +503 -0
  63. package/engine/internal/lib/closeout.go +17 -21
  64. package/engine/internal/lib/closeout_test.go +23 -0
  65. package/engine/internal/lib/cursor_compat_test.go +12 -75
  66. package/engine/internal/lib/evidencefresh.go +58 -47
  67. package/engine/internal/lib/evidencefresh_test.go +120 -0
  68. package/engine/internal/lib/resolve.go +13 -35
  69. package/engine/internal/lib/resolve_remediation_test.go +5 -17
  70. package/engine/internal/lib/secretscan.go +432 -0
  71. package/engine/internal/lib/secretscan_test.go +555 -0
  72. package/engine/internal/lib/util.go +24 -65
  73. package/engine/internal/lib/util_test.go +44 -0
  74. package/engine/internal/markdowntext/markdowntext.go +108 -0
  75. package/engine/internal/markdowntext/markdowntext_test.go +58 -0
  76. package/engine/internal/markdowntext/testdata/structural.json +27 -0
  77. package/engine/internal/reason/reason.go +2 -123
  78. package/engine/internal/reason/reason_test.go +4 -2
  79. package/engine/internal/rootfacts/facts.go +6 -8
  80. package/engine/internal/rootfacts/facts_test.go +20 -0
  81. package/engine/internal/state/cmd/workflowmanifest/main.go +10 -12
  82. package/engine/internal/state/cursor.go +53 -14
  83. package/engine/internal/state/cursor_test.go +57 -0
  84. package/engine/internal/state/feature.go +64 -154
  85. package/engine/internal/state/schema.go +24 -50
  86. package/engine/internal/state/state_test.go +151 -140
  87. package/engine/internal/state/status.go +4 -17
  88. package/engine/internal/state/workflow_manifest.json +9 -89
  89. package/engine/main.go +73 -502
  90. package/engine/root_routing_test.go +183 -212
  91. package/engine/testdata/fixtures/basic/devrites-root/{features/auth-tokens/feature.md → work/auth-tokens/README.md} +0 -1
  92. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/architecture.md +3 -0
  93. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/assumptions.md +3 -0
  94. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/brief.md +3 -0
  95. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/decision-coverage.md +3 -0
  96. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/eng-review.md +3 -0
  97. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/questions.md +3 -0
  98. package/engine/testdata/fixtures/basic/devrites-root/{features/auth-tokens/status.md → work/auth-tokens/state.md} +3 -0
  99. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/test-plan.md +3 -0
  100. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/traceability.md +3 -0
  101. package/engine/testdata/fixtures/basic/devrites-root/{features/search-ranking/feature.md → work/search-ranking/README.md} +0 -1
  102. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/assumptions.md +3 -0
  103. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/brief.md +3 -0
  104. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/decisions.md +3 -0
  105. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/questions.md +3 -0
  106. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/state.md +2 -0
  107. package/engine/testdata/golden/TestParityCloseOut/active_pointed_elsewhere.golden +1 -1
  108. package/engine/testdata/golden/TestParityCloseOut/active_slug_cleared.golden +1 -1
  109. package/engine/tests/adr_0003_gate_exit_code_test.go +2 -2
  110. package/engine/tests/adr_0011_define_plan_test.go +2 -2
  111. package/engine/tests/adr_0027_readiness_binding_test.go +106 -0
  112. package/engine/tests/cli_test.go +24 -107
  113. package/engine/tests/gate_test.go +177 -200
  114. package/engine/tests/meta_test.go +5 -35
  115. package/engine/tests/parity_closeout_test.go +12 -12
  116. package/engine/tests/parity_resolve_test.go +6 -16
  117. package/engine/tests/parity_test.go +8 -302
  118. package/install.sh +201 -34
  119. package/pack/.claude/agents/devrites-code-reviewer.md +9 -18
  120. package/pack/.claude/agents/devrites-devex-reviewer.md +10 -18
  121. package/pack/.claude/agents/devrites-doubt-reviewer.md +10 -20
  122. package/pack/.claude/agents/devrites-evidence-scout.md +14 -26
  123. package/pack/.claude/agents/devrites-frontend-reviewer.md +9 -15
  124. package/pack/.claude/agents/devrites-performance-reviewer.md +13 -21
  125. package/pack/.claude/agents/devrites-plan-drafter.md +24 -28
  126. package/pack/.claude/agents/devrites-plan-reviewer.md +30 -47
  127. package/pack/.claude/agents/devrites-proof-runner.md +41 -47
  128. package/pack/.claude/agents/devrites-retrospector.md +34 -79
  129. package/pack/.claude/agents/devrites-security-auditor.md +9 -17
  130. package/pack/.claude/agents/devrites-simplifier-reviewer.md +9 -18
  131. package/pack/.claude/agents/devrites-slice-wright.md +44 -62
  132. package/pack/.claude/agents/devrites-spec-reviewer.md +38 -50
  133. package/pack/.claude/agents/devrites-strategy-reviewer.md +10 -19
  134. package/pack/.claude/agents/devrites-test-analyst.md +22 -21
  135. package/pack/.claude/agents/devrites-upgrade-planner.md +52 -71
  136. package/pack/.claude/settings.json +12 -100
  137. package/pack/.claude/skills/devrites-api-interface/SKILL.md +1 -2
  138. package/pack/.claude/skills/devrites-audit/SKILL.md +6 -9
  139. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +0 -1
  140. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +21 -27
  141. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  142. package/pack/.claude/skills/devrites-doubt/SKILL.md +4 -6
  143. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +1 -2
  144. package/pack/.claude/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  145. package/pack/.claude/skills/devrites-interview/SKILL.md +31 -36
  146. package/pack/.claude/skills/devrites-lib/SKILL.md +37 -66
  147. package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  148. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +20 -12
  149. package/pack/.claude/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  150. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  151. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +26 -93
  152. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +6 -3
  153. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  154. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +90 -216
  155. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  156. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +9 -28
  157. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  158. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +59 -54
  159. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -5
  160. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +29 -143
  161. package/pack/.claude/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  162. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +13 -31
  163. package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  164. package/pack/.claude/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  165. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +22 -11
  166. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +15 -24
  167. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  168. package/pack/.claude/skills/devrites-prose-craft/SKILL.md +3 -4
  169. package/pack/.claude/skills/devrites-source-driven/SKILL.md +3 -4
  170. package/pack/.claude/skills/devrites-ux-shape/SKILL.md +3 -3
  171. package/pack/.claude/skills/rite/SKILL.md +12 -13
  172. package/pack/.claude/skills/rite/reference/menu.md +11 -11
  173. package/pack/.claude/skills/rite-adopt/SKILL.md +29 -84
  174. package/pack/.claude/skills/rite-adopt/reference/adoption.md +16 -50
  175. package/pack/.claude/skills/rite-autocomplete/SKILL.md +65 -54
  176. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +24 -17
  177. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  178. package/pack/.claude/skills/rite-build/SKILL.md +28 -34
  179. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +36 -34
  180. package/pack/.claude/skills/rite-build/reference/anti-patterns.md +5 -19
  181. package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  182. package/pack/.claude/skills/rite-build/reference/checkpoint.md +17 -17
  183. package/pack/.claude/skills/rite-build/reference/frontend-trigger.md +3 -3
  184. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +11 -14
  185. package/pack/.claude/skills/rite-build/reference/output.md +14 -24
  186. package/pack/.claude/skills/rite-build/reference/phase-contract.md +49 -188
  187. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +1 -1
  188. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +43 -133
  189. package/pack/.claude/skills/rite-clarify/SKILL.md +46 -40
  190. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +4 -10
  191. package/pack/.claude/skills/rite-converge/SKILL.md +9 -33
  192. package/pack/.claude/skills/rite-converge/reference/convergence-assessment.md +2 -2
  193. package/pack/.claude/skills/rite-customize/SKILL.md +34 -50
  194. package/pack/.claude/skills/rite-define/SKILL.md +34 -58
  195. package/pack/.claude/skills/rite-define/reference/plan-template.md +15 -0
  196. package/pack/.claude/skills/rite-doctor/SKILL.md +52 -64
  197. package/pack/.claude/skills/rite-dogfood/SKILL.md +1 -2
  198. package/pack/.claude/skills/rite-explain/SKILL.md +1 -20
  199. package/pack/.claude/skills/rite-frame/SKILL.md +1 -7
  200. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +1 -1
  201. package/pack/.claude/skills/rite-handoff/SKILL.md +1 -2
  202. package/pack/.claude/skills/rite-handoff/reference/handoff-template.md +1 -1
  203. package/pack/.claude/skills/rite-learn/SKILL.md +44 -104
  204. package/pack/.claude/skills/rite-plan/SKILL.md +14 -26
  205. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -1
  206. package/pack/.claude/skills/rite-polish/SKILL.md +20 -28
  207. package/pack/.claude/skills/rite-polish/reference/adr-promotion.md +11 -0
  208. package/pack/{generated/claude/skills/rite-ship → .claude/skills/rite-polish}/reference/design-memory.md +23 -21
  209. package/pack/.claude/skills/rite-polish/reference/ledger.md +62 -0
  210. package/pack/.claude/skills/rite-pov/SKILL.md +28 -32
  211. package/pack/.claude/skills/rite-pr-feedback/SKILL.md +0 -1
  212. package/pack/.claude/skills/rite-pressure-test/SKILL.md +6 -13
  213. package/pack/.claude/skills/rite-prototype/SKILL.md +0 -6
  214. package/pack/.claude/skills/rite-prove/SKILL.md +73 -68
  215. package/pack/.claude/skills/rite-prove/reference/acceptance-proof.md +34 -30
  216. package/pack/.claude/skills/rite-prove/reference/test-command-discovery.md +15 -14
  217. package/pack/.claude/skills/rite-quick/SKILL.md +5 -19
  218. package/pack/.claude/skills/rite-resolve/SKILL.md +4 -21
  219. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -2
  220. package/pack/.claude/skills/rite-review/SKILL.md +34 -57
  221. package/pack/.claude/skills/rite-seal/SKILL.md +54 -87
  222. package/pack/.claude/skills/rite-seal/reference/anti-patterns.md +2 -2
  223. package/pack/.claude/skills/rite-seal/reference/final-evidence.md +31 -56
  224. package/pack/.claude/skills/rite-seal/reference/go-no-go.md +27 -51
  225. package/pack/.claude/skills/rite-seal/reference/output.md +2 -4
  226. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +42 -140
  227. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  228. package/pack/.claude/skills/rite-seal/reference/seal-template.md +17 -28
  229. package/pack/.claude/skills/rite-ship/SKILL.md +65 -93
  230. package/pack/.claude/skills/rite-ship/reference/anti-patterns.md +4 -1
  231. package/pack/.claude/skills/rite-ship/reference/close-out.md +4 -12
  232. package/pack/.claude/skills/rite-ship/reference/git-ship.md +86 -48
  233. package/pack/.claude/skills/rite-ship/reference/ship-template.md +22 -16
  234. package/pack/.claude/skills/rite-spec/SKILL.md +62 -95
  235. package/pack/.claude/skills/rite-spec/reference/investigation.md +1 -4
  236. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +21 -20
  237. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +16 -17
  238. package/pack/.claude/skills/rite-spec/reference/spec-template.md +47 -49
  239. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +28 -13
  240. package/pack/.claude/skills/rite-status/SKILL.md +30 -88
  241. package/pack/.claude/skills/rite-temper/SKILL.md +12 -31
  242. package/pack/.claude/skills/rite-upgrade/SKILL.md +86 -101
  243. package/pack/.claude/skills/rite-vet/SKILL.md +88 -117
  244. package/pack/.claude/skills/rite-vet/reference/artifacts.md +56 -50
  245. package/pack/.claude/skills/rite-vet/reference/cross-model.md +15 -39
  246. package/pack/.claude/skills/rite-vet/reference/depth.md +2 -2
  247. package/pack/.claude/skills/rite-vet/reference/review-axes.md +3 -6
  248. package/pack/.claude/skills/rite-zoom-out/SKILL.md +0 -6
  249. package/pack/generated/README.md +2 -2
  250. package/pack/generated/claude/agents/devrites-code-reviewer.md +9 -18
  251. package/pack/generated/claude/agents/devrites-devex-reviewer.md +10 -18
  252. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +10 -20
  253. package/pack/generated/claude/agents/devrites-evidence-scout.md +14 -26
  254. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +9 -15
  255. package/pack/generated/claude/agents/devrites-performance-reviewer.md +13 -21
  256. package/pack/generated/claude/agents/devrites-plan-drafter.md +24 -28
  257. package/pack/generated/claude/agents/devrites-plan-reviewer.md +30 -47
  258. package/pack/generated/claude/agents/devrites-proof-runner.md +41 -47
  259. package/pack/generated/claude/agents/devrites-retrospector.md +34 -79
  260. package/pack/generated/claude/agents/devrites-security-auditor.md +9 -17
  261. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +9 -18
  262. package/pack/generated/claude/agents/devrites-slice-wright.md +44 -62
  263. package/pack/generated/claude/agents/devrites-spec-reviewer.md +38 -50
  264. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +10 -19
  265. package/pack/generated/claude/agents/devrites-test-analyst.md +22 -21
  266. package/pack/generated/claude/agents/devrites-upgrade-planner.md +52 -71
  267. package/pack/generated/claude/settings.json +12 -100
  268. package/pack/generated/claude/skills/devrites-api-interface/SKILL.md +1 -2
  269. package/pack/generated/claude/skills/devrites-audit/SKILL.md +6 -9
  270. package/pack/generated/claude/skills/devrites-browser-proof/SKILL.md +0 -1
  271. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +21 -27
  272. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  273. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +4 -6
  274. package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +1 -2
  275. package/pack/generated/claude/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  276. package/pack/generated/claude/skills/devrites-interview/SKILL.md +31 -36
  277. package/pack/generated/claude/skills/devrites-lib/SKILL.md +37 -66
  278. package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  279. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +20 -12
  280. package/pack/generated/claude/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  281. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  282. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +26 -93
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +6 -3
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +90 -216
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +9 -28
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  289. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +59 -54
  290. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -5
  291. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +29 -143
  292. package/pack/generated/claude/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  293. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +13 -31
  294. package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  295. package/pack/generated/claude/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  296. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +22 -11
  297. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +15 -24
  298. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  299. package/pack/generated/claude/skills/devrites-prose-craft/SKILL.md +3 -4
  300. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +3 -4
  301. package/pack/generated/claude/skills/devrites-ux-shape/SKILL.md +3 -3
  302. package/pack/generated/claude/skills/rite/SKILL.md +12 -13
  303. package/pack/generated/claude/skills/rite/reference/menu.md +11 -11
  304. package/pack/generated/claude/skills/rite-adopt/SKILL.md +29 -84
  305. package/pack/generated/claude/skills/rite-adopt/reference/adoption.md +16 -50
  306. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +65 -54
  307. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +24 -17
  308. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  309. package/pack/generated/claude/skills/rite-build/SKILL.md +28 -34
  310. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +36 -34
  311. package/pack/generated/claude/skills/rite-build/reference/anti-patterns.md +5 -19
  312. package/pack/generated/claude/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  313. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +17 -17
  314. package/pack/generated/claude/skills/rite-build/reference/frontend-trigger.md +3 -3
  315. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +11 -14
  316. package/pack/generated/claude/skills/rite-build/reference/output.md +14 -24
  317. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +49 -188
  318. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +1 -1
  319. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +43 -133
  320. package/pack/generated/claude/skills/rite-clarify/SKILL.md +46 -40
  321. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +4 -10
  322. package/pack/generated/claude/skills/rite-converge/SKILL.md +9 -33
  323. package/pack/generated/claude/skills/rite-converge/reference/convergence-assessment.md +2 -2
  324. package/pack/generated/claude/skills/rite-customize/SKILL.md +34 -50
  325. package/pack/generated/claude/skills/rite-define/SKILL.md +34 -58
  326. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +15 -0
  327. package/pack/generated/claude/skills/rite-doctor/SKILL.md +52 -64
  328. package/pack/generated/claude/skills/rite-dogfood/SKILL.md +1 -2
  329. package/pack/generated/claude/skills/rite-explain/SKILL.md +1 -20
  330. package/pack/generated/claude/skills/rite-frame/SKILL.md +1 -7
  331. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +1 -1
  332. package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -2
  333. package/pack/generated/claude/skills/rite-handoff/reference/handoff-template.md +1 -1
  334. package/pack/generated/claude/skills/rite-learn/SKILL.md +44 -104
  335. package/pack/generated/claude/skills/rite-plan/SKILL.md +14 -26
  336. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -1
  337. package/pack/generated/claude/skills/rite-polish/SKILL.md +20 -28
  338. package/pack/generated/claude/skills/rite-polish/reference/adr-promotion.md +11 -0
  339. package/pack/generated/{codex/skills/rite-ship → claude/skills/rite-polish}/reference/design-memory.md +23 -21
  340. package/pack/generated/claude/skills/rite-polish/reference/ledger.md +62 -0
  341. package/pack/generated/claude/skills/rite-pov/SKILL.md +28 -32
  342. package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +0 -1
  343. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +6 -13
  344. package/pack/generated/claude/skills/rite-prototype/SKILL.md +0 -6
  345. package/pack/generated/claude/skills/rite-prove/SKILL.md +73 -68
  346. package/pack/generated/claude/skills/rite-prove/reference/acceptance-proof.md +34 -30
  347. package/pack/generated/claude/skills/rite-prove/reference/test-command-discovery.md +15 -14
  348. package/pack/generated/claude/skills/rite-quick/SKILL.md +5 -19
  349. package/pack/generated/claude/skills/rite-resolve/SKILL.md +4 -21
  350. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -2
  351. package/pack/generated/claude/skills/rite-review/SKILL.md +34 -57
  352. package/pack/generated/claude/skills/rite-seal/SKILL.md +54 -87
  353. package/pack/generated/claude/skills/rite-seal/reference/anti-patterns.md +2 -2
  354. package/pack/generated/claude/skills/rite-seal/reference/final-evidence.md +31 -56
  355. package/pack/generated/claude/skills/rite-seal/reference/go-no-go.md +27 -51
  356. package/pack/generated/claude/skills/rite-seal/reference/output.md +2 -4
  357. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +42 -140
  358. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  359. package/pack/generated/claude/skills/rite-seal/reference/seal-template.md +17 -28
  360. package/pack/generated/claude/skills/rite-ship/SKILL.md +65 -93
  361. package/pack/generated/claude/skills/rite-ship/reference/anti-patterns.md +4 -1
  362. package/pack/generated/claude/skills/rite-ship/reference/close-out.md +4 -12
  363. package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +86 -48
  364. package/pack/generated/claude/skills/rite-ship/reference/ship-template.md +22 -16
  365. package/pack/generated/claude/skills/rite-spec/SKILL.md +62 -95
  366. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +1 -4
  367. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +21 -20
  368. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +16 -17
  369. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +47 -49
  370. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +28 -13
  371. package/pack/generated/claude/skills/rite-status/SKILL.md +30 -88
  372. package/pack/generated/claude/skills/rite-temper/SKILL.md +12 -31
  373. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +86 -101
  374. package/pack/generated/claude/skills/rite-vet/SKILL.md +88 -117
  375. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +56 -50
  376. package/pack/generated/claude/skills/rite-vet/reference/cross-model.md +15 -39
  377. package/pack/generated/claude/skills/rite-vet/reference/depth.md +2 -2
  378. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +3 -6
  379. package/pack/generated/claude/skills/rite-zoom-out/SKILL.md +0 -6
  380. package/pack/generated/codex/AGENTS.md +5 -4
  381. package/pack/generated/codex/agents/devrites-code-reviewer.toml +8 -27
  382. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +9 -27
  383. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +9 -29
  384. package/pack/generated/codex/agents/devrites-evidence-scout.toml +14 -36
  385. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +8 -24
  386. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +12 -30
  387. package/pack/generated/codex/agents/devrites-plan-drafter.toml +24 -38
  388. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +30 -57
  389. package/pack/generated/codex/agents/devrites-proof-runner.toml +41 -57
  390. package/pack/generated/codex/agents/devrites-retrospector.toml +34 -89
  391. package/pack/generated/codex/agents/devrites-security-auditor.toml +8 -26
  392. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +8 -27
  393. package/pack/generated/codex/agents/devrites-slice-wright.toml +44 -71
  394. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +38 -60
  395. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +9 -28
  396. package/pack/generated/codex/agents/devrites-test-analyst.toml +21 -30
  397. package/pack/generated/codex/agents/devrites-upgrade-planner.toml +52 -81
  398. package/pack/generated/codex/config.toml +7 -0
  399. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +1 -21
  400. package/pack/generated/codex/skills/devrites-audit/SKILL.md +6 -28
  401. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +1 -21
  402. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +21 -46
  403. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  404. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +4 -25
  405. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +1 -21
  406. package/pack/generated/codex/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  407. package/pack/generated/codex/skills/devrites-interview/SKILL.md +32 -56
  408. package/pack/generated/codex/skills/devrites-lib/SKILL.md +38 -86
  409. package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  410. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +20 -12
  411. package/pack/generated/codex/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  412. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  413. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +26 -93
  414. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +6 -3
  415. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  416. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +90 -216
  417. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  418. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +9 -28
  419. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  420. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +59 -54
  421. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -5
  422. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +29 -143
  423. package/pack/generated/codex/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  424. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +13 -31
  425. package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  426. package/pack/generated/codex/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  427. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +22 -11
  428. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +15 -24
  429. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  430. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +3 -23
  431. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +3 -23
  432. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +3 -22
  433. package/pack/generated/codex/skills/rite/SKILL.md +12 -32
  434. package/pack/generated/codex/skills/rite/reference/menu.md +11 -11
  435. package/pack/generated/codex/skills/rite-adopt/SKILL.md +29 -103
  436. package/pack/generated/codex/skills/rite-adopt/reference/adoption.md +16 -50
  437. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +65 -73
  438. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +24 -17
  439. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  440. package/pack/generated/codex/skills/rite-build/SKILL.md +28 -53
  441. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +36 -34
  442. package/pack/generated/codex/skills/rite-build/reference/anti-patterns.md +5 -19
  443. package/pack/generated/codex/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  444. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +17 -17
  445. package/pack/generated/codex/skills/rite-build/reference/frontend-trigger.md +3 -3
  446. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +11 -14
  447. package/pack/generated/codex/skills/rite-build/reference/output.md +14 -24
  448. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +49 -188
  449. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +1 -1
  450. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +43 -133
  451. package/pack/generated/codex/skills/rite-clarify/SKILL.md +46 -59
  452. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +4 -10
  453. package/pack/generated/codex/skills/rite-converge/SKILL.md +9 -52
  454. package/pack/generated/codex/skills/rite-converge/reference/convergence-assessment.md +2 -2
  455. package/pack/generated/codex/skills/rite-customize/SKILL.md +32 -67
  456. package/pack/generated/codex/skills/rite-define/SKILL.md +34 -77
  457. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +15 -0
  458. package/pack/generated/codex/skills/rite-doctor/SKILL.md +52 -83
  459. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +1 -21
  460. package/pack/generated/codex/skills/rite-explain/SKILL.md +1 -39
  461. package/pack/generated/codex/skills/rite-frame/SKILL.md +1 -26
  462. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +1 -1
  463. package/pack/generated/codex/skills/rite-handoff/SKILL.md +1 -21
  464. package/pack/generated/codex/skills/rite-handoff/reference/handoff-template.md +1 -1
  465. package/pack/generated/codex/skills/rite-learn/SKILL.md +44 -123
  466. package/pack/generated/codex/skills/rite-plan/SKILL.md +14 -45
  467. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +4 -1
  468. package/pack/generated/codex/skills/rite-polish/SKILL.md +20 -47
  469. package/pack/generated/codex/skills/rite-polish/reference/adr-promotion.md +11 -0
  470. package/pack/{.claude/skills/rite-ship → generated/codex/skills/rite-polish}/reference/design-memory.md +23 -21
  471. package/pack/generated/codex/skills/rite-polish/reference/ledger.md +62 -0
  472. package/pack/generated/codex/skills/rite-pov/SKILL.md +28 -51
  473. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +0 -20
  474. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -32
  475. package/pack/generated/codex/skills/rite-prototype/SKILL.md +0 -25
  476. package/pack/generated/codex/skills/rite-prove/SKILL.md +73 -87
  477. package/pack/generated/codex/skills/rite-prove/reference/acceptance-proof.md +34 -30
  478. package/pack/generated/codex/skills/rite-prove/reference/test-command-discovery.md +15 -14
  479. package/pack/generated/codex/skills/rite-quick/SKILL.md +5 -38
  480. package/pack/generated/codex/skills/rite-resolve/SKILL.md +4 -40
  481. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -2
  482. package/pack/generated/codex/skills/rite-review/SKILL.md +34 -76
  483. package/pack/generated/codex/skills/rite-seal/SKILL.md +51 -103
  484. package/pack/generated/codex/skills/rite-seal/reference/anti-patterns.md +2 -2
  485. package/pack/generated/codex/skills/rite-seal/reference/final-evidence.md +31 -56
  486. package/pack/generated/codex/skills/rite-seal/reference/go-no-go.md +27 -51
  487. package/pack/generated/codex/skills/rite-seal/reference/output.md +2 -4
  488. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +42 -140
  489. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  490. package/pack/generated/codex/skills/rite-seal/reference/seal-template.md +17 -28
  491. package/pack/generated/codex/skills/rite-ship/SKILL.md +65 -112
  492. package/pack/generated/codex/skills/rite-ship/reference/anti-patterns.md +4 -1
  493. package/pack/generated/codex/skills/rite-ship/reference/close-out.md +4 -12
  494. package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +86 -48
  495. package/pack/generated/codex/skills/rite-ship/reference/ship-template.md +22 -16
  496. package/pack/generated/codex/skills/rite-spec/SKILL.md +62 -114
  497. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +1 -4
  498. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +21 -20
  499. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +16 -17
  500. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +47 -49
  501. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +28 -13
  502. package/pack/generated/codex/skills/rite-status/SKILL.md +30 -107
  503. package/pack/generated/codex/skills/rite-temper/SKILL.md +12 -50
  504. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +86 -120
  505. package/pack/generated/codex/skills/rite-vet/SKILL.md +88 -136
  506. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +56 -50
  507. package/pack/generated/codex/skills/rite-vet/reference/cross-model.md +15 -39
  508. package/pack/generated/codex/skills/rite-vet/reference/depth.md +2 -2
  509. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +3 -6
  510. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +0 -25
  511. package/package.json +6 -5
  512. package/scripts/build-host-artifacts.sh +5 -9
  513. package/scripts/codex-generate.sh +18 -278
  514. package/scripts/install-lib.sh +121 -34
  515. package/uninstall.sh +19 -17
  516. package/update.sh +26 -18
  517. package/docs/engine/agent-contract.md +0 -172
  518. package/docs/extensions.md +0 -178
  519. package/engine/git_guard.go +0 -187
  520. package/engine/git_guard_test.go +0 -283
  521. package/engine/harnessmatrix.go +0 -60
  522. package/engine/hookpolicy.go +0 -113
  523. package/engine/hookpolicy_test.go +0 -160
  524. package/engine/hooks.go +0 -677
  525. package/engine/hooks_agent_dispatch.go +0 -1637
  526. package/engine/hooks_events_test.go +0 -431
  527. package/engine/hooks_workspace.go +0 -1196
  528. package/engine/internal/doctor/doctor.go +0 -306
  529. package/engine/internal/doctor/doctor_test.go +0 -252
  530. package/engine/internal/forge/forge.go +0 -938
  531. package/engine/internal/forge/forge_test.go +0 -576
  532. package/engine/internal/forge/git.go +0 -245
  533. package/engine/internal/forge/liveness_unix.go +0 -48
  534. package/engine/internal/forge/liveness_windows.go +0 -67
  535. package/engine/internal/forge/manifest.go +0 -402
  536. package/engine/internal/harness/compliance.go +0 -96
  537. package/engine/internal/harness/harness.go +0 -301
  538. package/engine/internal/harness/harness_test.go +0 -124
  539. package/engine/internal/iohooks/detach_other.go +0 -11
  540. package/engine/internal/iohooks/detach_unix.go +0 -11
  541. package/engine/internal/iohooks/iohooks.go +0 -839
  542. package/engine/internal/iohooks/iohooks_test.go +0 -561
  543. package/engine/internal/lib/adoption.go +0 -659
  544. package/engine/internal/lib/adoption_test.go +0 -121
  545. package/engine/internal/lib/analyze.go +0 -232
  546. package/engine/internal/lib/archivesearch.go +0 -125
  547. package/engine/internal/lib/archivesearch_test.go +0 -138
  548. package/engine/internal/lib/budget.go +0 -172
  549. package/engine/internal/lib/buildreadiness.go +0 -111
  550. package/engine/internal/lib/checkacceptance.go +0 -176
  551. package/engine/internal/lib/clarifyreturn.go +0 -91
  552. package/engine/internal/lib/clarifyreturn_test.go +0 -123
  553. package/engine/internal/lib/command_other.go +0 -7
  554. package/engine/internal/lib/command_unix.go +0 -24
  555. package/engine/internal/lib/context.go +0 -280
  556. package/engine/internal/lib/conventions.go +0 -449
  557. package/engine/internal/lib/coverage.go +0 -145
  558. package/engine/internal/lib/dispatchwaive.go +0 -31
  559. package/engine/internal/lib/dispatchwaive_test.go +0 -23
  560. package/engine/internal/lib/doubtcoverage.go +0 -78
  561. package/engine/internal/lib/extensions.go +0 -622
  562. package/engine/internal/lib/extensions_test.go +0 -305
  563. package/engine/internal/lib/footprint.go +0 -239
  564. package/engine/internal/lib/gitauthority.go +0 -601
  565. package/engine/internal/lib/gitauthority_test.go +0 -346
  566. package/engine/internal/lib/jsonout.go +0 -129
  567. package/engine/internal/lib/jsonout_test.go +0 -73
  568. package/engine/internal/lib/lanes.go +0 -112
  569. package/engine/internal/lib/learnings.go +0 -397
  570. package/engine/internal/lib/learnings_inject_test.go +0 -77
  571. package/engine/internal/lib/learnings_remediation_test.go +0 -24
  572. package/engine/internal/lib/ledger.go +0 -311
  573. package/engine/internal/lib/ledger_test.go +0 -186
  574. package/engine/internal/lib/mutationgate.go +0 -89
  575. package/engine/internal/lib/observability_test.go +0 -358
  576. package/engine/internal/lib/overrides.go +0 -164
  577. package/engine/internal/lib/overrides_test.go +0 -112
  578. package/engine/internal/lib/preamble.go +0 -159
  579. package/engine/internal/lib/preamble_questions_test.go +0 -10
  580. package/engine/internal/lib/progress.go +0 -210
  581. package/engine/internal/lib/provenance.go +0 -462
  582. package/engine/internal/lib/provenance_test.go +0 -154
  583. package/engine/internal/lib/readiness_contract.json +0 -118
  584. package/engine/internal/lib/readinessartifact.go +0 -533
  585. package/engine/internal/lib/readinessartifact_test.go +0 -422
  586. package/engine/internal/lib/reconcile.go +0 -1496
  587. package/engine/internal/lib/reconcile_test.go +0 -1071
  588. package/engine/internal/lib/recoveryattempts.go +0 -298
  589. package/engine/internal/lib/recoveryattempts_test.go +0 -215
  590. package/engine/internal/lib/reviewers.go +0 -143
  591. package/engine/internal/lib/reviewers_test.go +0 -45
  592. package/engine/internal/lib/reviewerstats.go +0 -199
  593. package/engine/internal/lib/reviewerstats_test.go +0 -104
  594. package/engine/internal/lib/reviewintegrity.go +0 -143
  595. package/engine/internal/lib/reviewintegrity_test.go +0 -103
  596. package/engine/internal/lib/runbook.go +0 -295
  597. package/engine/internal/lib/runbook_context_test.go +0 -182
  598. package/engine/internal/lib/session.go +0 -1020
  599. package/engine/internal/lib/session_test.go +0 -177
  600. package/engine/internal/lib/specparse.go +0 -125
  601. package/engine/internal/lib/specskeleton.go +0 -79
  602. package/engine/internal/lib/specvalidate.go +0 -391
  603. package/engine/internal/lib/specvalidate_test.go +0 -185
  604. package/engine/internal/lib/stuck.go +0 -206
  605. package/engine/internal/lib/templates/conventions_drift.tmpl +0 -6
  606. package/engine/internal/lib/templates/conventions_ledger_header.md +0 -6
  607. package/engine/internal/lib/templates/conventions_orient_header.txt +0 -1
  608. package/engine/internal/lib/testintegrity.go +0 -199
  609. package/engine/internal/lib/testintegrity_test.go +0 -149
  610. package/engine/internal/lib/tickafk.go +0 -86
  611. package/engine/internal/migrate/migrate.go +0 -279
  612. package/engine/internal/migrate/migrate_test.go +0 -157
  613. package/engine/internal/orient/firstrun.go +0 -50
  614. package/engine/internal/orient/firstrun_test.go +0 -72
  615. package/engine/internal/orient/orient.go +0 -69
  616. package/engine/internal/orient/orient_test.go +0 -25
  617. package/engine/internal/profile/profile.go +0 -284
  618. package/engine/internal/profile/profile_test.go +0 -13
  619. package/engine/internal/state/clarify_transition.go +0 -165
  620. package/engine/internal/state/clarify_transition_test.go +0 -130
  621. package/engine/internal/state/snapshot.go +0 -457
  622. package/engine/internal/toolpolicy/classifier.go +0 -533
  623. package/engine/internal/toolpolicy/classifier_test.go +0 -424
  624. package/engine/internal/toolpolicy/git.go +0 -616
  625. package/engine/internal/toolpolicy/scanner.go +0 -382
  626. package/engine/observability_cli_test.go +0 -52
  627. package/engine/profile_test.go +0 -76
  628. package/engine/subagent_orient_context.txt +0 -8
  629. package/engine/testdata/golden/TestParityA1Guard/devrites-edit-allowed.golden +0 -1
  630. package/engine/testdata/golden/TestParityA1Guard/no-window-silent.golden +0 -1
  631. package/engine/testdata/golden/TestParityA1Guard/non-edit-tool.golden +0 -1
  632. package/engine/testdata/golden/TestParityA1Guard/observe-mode-silent.golden +0 -1
  633. package/engine/testdata/golden/TestParityA1Guard/source-edit-enforce-denies.golden +0 -2
  634. package/engine/testdata/golden/TestParityA1Guard/subagent-allowed.golden +0 -1
  635. package/engine/testdata/golden/TestParityAnalyze/noactive.golden +0 -1
  636. package/engine/testdata/golden/TestParityAnalyze/slug=covered.golden +0 -15
  637. package/engine/testdata/golden/TestParityAnalyze/slug=dangling.golden +0 -16
  638. package/engine/testdata/golden/TestParityAnalyze/slug=ghost.golden +0 -1
  639. package/engine/testdata/golden/TestParityAnalyze/slug=noac.golden +0 -15
  640. package/engine/testdata/golden/TestParityAnalyze/slug=order.golden +0 -17
  641. package/engine/testdata/golden/TestParityAnalyze/slug=orphan.golden +0 -16
  642. package/engine/testdata/golden/TestParityAnalyze/slug=uncovered.golden +0 -16
  643. package/engine/testdata/golden/TestParityBudget/default-active.golden +0 -4
  644. package/engine/testdata/golden/TestParityBudget/extra-md.golden +0 -5
  645. package/engine/testdata/golden/TestParityBudget/no-active.golden +0 -1
  646. package/engine/testdata/golden/TestParityBudget/no-feature.golden +0 -1
  647. package/engine/testdata/golden/TestParityBudget/over-advisory.golden +0 -6
  648. package/engine/testdata/golden/TestParityBudget/over-strict.golden +0 -6
  649. package/engine/testdata/golden/TestParityBudget/within.golden +0 -6
  650. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +0 -2
  651. package/engine/testdata/golden/TestParityBuildReadiness/arg=approvenone.golden +0 -1
  652. package/engine/testdata/golden/TestParityBuildReadiness/arg=await.golden +0 -1
  653. package/engine/testdata/golden/TestParityBuildReadiness/arg=blocked.golden +0 -1
  654. package/engine/testdata/golden/TestParityBuildReadiness/arg=clarifyopen.golden +0 -1
  655. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +0 -2
  656. package/engine/testdata/golden/TestParityBuildReadiness/arg=ghost.golden +0 -1
  657. package/engine/testdata/golden/TestParityBuildReadiness/arg=legacycontract.golden +0 -1
  658. package/engine/testdata/golden/TestParityBuildReadiness/arg=noapprove.golden +0 -1
  659. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +0 -1
  660. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +0 -1
  661. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +0 -1
  662. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +0 -2
  663. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +0 -2
  664. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +0 -1
  665. package/engine/testdata/golden/TestParityCheckAcceptance/arg=blank-section.golden +0 -1
  666. package/engine/testdata/golden/TestParityCheckAcceptance/arg=empty.golden +0 -1
  667. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-dropped.golden +0 -1
  668. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-ok.golden +0 -2
  669. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-unchecked.golden +0 -1
  670. package/engine/testdata/golden/TestParityCheckAcceptance/arg=id-gap.golden +0 -1
  671. package/engine/testdata/golden/TestParityCheckAcceptance/arg=id-ok.golden +0 -2
  672. package/engine/testdata/golden/TestParityCheckAcceptance/arg=no-seal.golden +0 -1
  673. package/engine/testdata/golden/TestParityCheckAcceptance/arg=no-section.golden +0 -1
  674. package/engine/testdata/golden/TestParityCheckAcceptance/arg=nonexistent.golden +0 -1
  675. package/engine/testdata/golden/TestParityCheckAcceptance.golden +0 -1
  676. package/engine/testdata/golden/TestParityCoverage/arg=flat.golden +0 -7
  677. package/engine/testdata/golden/TestParityCoverage/arg=full.golden +0 -10
  678. package/engine/testdata/golden/TestParityCoverage/arg=nonexistent.golden +0 -1
  679. package/engine/testdata/golden/TestParityCoverage/arg=nospec.golden +0 -1
  680. package/engine/testdata/golden/TestParityCoverage/arg=speconly.golden +0 -9
  681. package/engine/testdata/golden/TestParityCoverage.golden +0 -1
  682. package/engine/testdata/golden/TestParityCursor/bare.golden +0 -3
  683. package/engine/testdata/golden/TestParityCursor/full-afk.golden +0 -6
  684. package/engine/testdata/golden/TestParityCursor/full-hitl.golden +0 -5
  685. package/engine/testdata/golden/TestParityCursor/no-active.golden +0 -1
  686. package/engine/testdata/golden/TestParityCursor/red.golden +0 -5
  687. package/engine/testdata/golden/TestParityCursor/zerogates.golden +0 -5
  688. package/engine/testdata/golden/TestParityDoubtCoverage/arg=doubted.golden +0 -2
  689. package/engine/testdata/golden/TestParityDoubtCoverage/arg=ghost.golden +0 -2
  690. package/engine/testdata/golden/TestParityDoubtCoverage/arg=missing.golden +0 -3
  691. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nodoubt.golden +0 -4
  692. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nolog.golden +0 -2
  693. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nowright.golden +0 -2
  694. package/engine/testdata/golden/TestParityDoubtCoverage.golden +0 -1
  695. package/engine/testdata/golden/TestParityMutationGate/active-slug.golden +0 -3
  696. package/engine/testdata/golden/TestParityMutationGate/explicit-slug.golden +0 -3
  697. package/engine/testdata/golden/TestParityMutationGate/ghost.golden +0 -1
  698. package/engine/testdata/golden/TestParityOrientSilentPath.golden +0 -1
  699. package/engine/testdata/golden/TestParityReconcile/bad-mode.golden +0 -1
  700. package/engine/testdata/golden/TestParityReconcile/check-clean/snapshot.golden +0 -2
  701. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +0 -2
  702. package/engine/testdata/golden/TestParityReconcile/check-no-base.golden +0 -1
  703. package/engine/testdata/golden/TestParityReconcile/check-no-claimed/snapshot.golden +0 -2
  704. package/engine/testdata/golden/TestParityReconcile/check-violation/snapshot.golden +0 -2
  705. package/engine/testdata/golden/TestParityReconcile/check-violation.golden +0 -1
  706. package/engine/testdata/golden/TestParityReconcile/close-window/snapshot.golden +0 -2
  707. package/engine/testdata/golden/TestParityReconcile/close-window.golden +0 -3
  708. package/engine/testdata/golden/TestParityReconcile/not-git.golden +0 -1
  709. package/engine/testdata/golden/TestParityReconcile/snapshot/snapshot.golden +0 -2
  710. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +0 -1
  711. package/engine/testdata/golden/TestParityReconcile/snapshot.golden +0 -2
  712. package/engine/testdata/golden/TestParityRedwatch/fail-writes-red.golden +0 -2
  713. package/engine/testdata/golden/TestParityRedwatch/non-bash-tool.golden +0 -1
  714. package/engine/testdata/golden/TestParityRedwatch/non-test-command.golden +0 -1
  715. package/engine/testdata/golden/TestParityRedwatch/pass-clears-red.golden +0 -1
  716. package/engine/testdata/golden/TestParityResolve/next-qid.golden +0 -2
  717. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-devrites-agent-denies.golden +0 -2
  718. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-non-devrites-allows.golden +0 -1
  719. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-subagent-type-only-allows.golden +0 -1
  720. package/engine/testdata/golden/TestParityReviewerReadonly/enforce-deny-git-commit.golden +0 -2
  721. package/engine/testdata/golden/TestParityReviewerReadonly/enforce-deny-mutating.golden +0 -2
  722. package/engine/testdata/golden/TestParityReviewerReadonly/non-bash-tool.golden +0 -1
  723. package/engine/testdata/golden/TestParityReviewerReadonly/non-tool-input.golden +0 -1
  724. package/engine/testdata/golden/TestParityReviewerReadonly/observe-mutating-silent.golden +0 -1
  725. package/engine/testdata/golden/TestParityReviewerReadonly/safe-bash-command.golden +0 -1
  726. package/engine/testdata/golden/TestParitySpecValidate/arg=dup.golden +0 -1
  727. package/engine/testdata/golden/TestParitySpecValidate/arg=empty.golden +0 -1
  728. package/engine/testdata/golden/TestParitySpecValidate/arg=flat.golden +0 -2
  729. package/engine/testdata/golden/TestParitySpecValidate/arg=nonexistent.golden +0 -1
  730. package/engine/testdata/golden/TestParitySpecValidate/arg=noscenario.golden +0 -1
  731. package/engine/testdata/golden/TestParitySpecValidate/arg=noshall.golden +0 -1
  732. package/engine/testdata/golden/TestParitySpecValidate/arg=nothen.golden +0 -1
  733. package/engine/testdata/golden/TestParitySpecValidate/arg=valid/spec.md.golden +0 -2
  734. package/engine/testdata/golden/TestParitySpecValidate/arg=valid.golden +0 -2
  735. package/engine/testdata/golden/TestParitySpecValidate.golden +0 -1
  736. package/engine/testdata/golden/TestParityStatusline/full-afk.golden +0 -2
  737. package/engine/testdata/golden/TestParityStatusline/full-hitl.golden +0 -2
  738. package/engine/testdata/golden/TestParityStatusline/no-active.golden +0 -1
  739. package/engine/testdata/golden/TestParityStatusline/nophase.golden +0 -2
  740. package/engine/testdata/golden/TestParityStatusline/red.golden +0 -2
  741. package/engine/testdata/golden/TestParityStuckCheck/bad-cmd.golden +0 -1
  742. package/engine/testdata/golden/TestParityStuckCheck/custom-window-2.golden +0 -2
  743. package/engine/testdata/golden/TestParityStuckCheck/dominant.golden +0 -2
  744. package/engine/testdata/golden/TestParityStuckCheck/few.golden +0 -2
  745. package/engine/testdata/golden/TestParityStuckCheck/ghost.golden +0 -2
  746. package/engine/testdata/golden/TestParityStuckCheck/identical.golden +0 -2
  747. package/engine/testdata/golden/TestParityStuckCheck/no-cmd.golden +0 -1
  748. package/engine/testdata/golden/TestParityStuckCheck/no-slug.golden +0 -1
  749. package/engine/testdata/golden/TestParityStuckCheck/nolog.golden +0 -2
  750. package/engine/testdata/golden/TestParityStuckCheck/pingpong.golden +0 -2
  751. package/engine/testdata/golden/TestParityStuckCheck/varied.golden +0 -2
  752. package/engine/testdata/golden/TestParityStuckCheck/window-04-identical.golden +0 -2
  753. package/engine/testdata/golden/TestParityStuckCheck/window-2.5-varied4.golden +0 -2
  754. package/engine/testdata/golden/TestParityStuckCheck/window-4x-varied4.golden +0 -2
  755. package/engine/testdata/golden/TestParityStuckCheck/window-leadspace-varied4.golden +0 -2
  756. package/engine/testdata/golden/TestParityStuckCheck/window-plus4-identical.golden +0 -2
  757. package/engine/testdata/golden/TestParitySubagentOrient/devrites-agent-subagent-type-key.golden +0 -2
  758. package/engine/testdata/golden/TestParitySubagentOrient/devrites-agent.golden +0 -2
  759. package/engine/testdata/golden/TestParitySubagentOrient/garbage-input.golden +0 -1
  760. package/engine/testdata/golden/TestParitySubagentOrient/non-devrites-agent.golden +0 -1
  761. package/engine/testdata/golden/TestParityTestIntegrity/assertion-dropped.golden +0 -1
  762. package/engine/testdata/golden/TestParityTestIntegrity/clean.golden +0 -2
  763. package/engine/testdata/golden/TestParityTestIntegrity/deleted.golden +0 -1
  764. package/engine/testdata/golden/TestParityTestIntegrity/ghost-workspace.golden +0 -1
  765. package/engine/testdata/golden/TestParityTestIntegrity/not-git.golden +0 -1
  766. package/engine/testdata/golden/TestParityTestIntegrity/skip-added.golden +0 -1
  767. package/engine/testdata/golden/TestParityTickAfk/budget1/state.md.golden +0 -1
  768. package/engine/testdata/golden/TestParityTickAfk/budget1.golden +0 -2
  769. package/engine/testdata/golden/TestParityTickAfk/budget3/state.md.golden +0 -5
  770. package/engine/testdata/golden/TestParityTickAfk/budget3.golden +0 -2
  771. package/engine/testdata/golden/TestParityTickAfk/bulletPipe/state.md.golden +0 -1
  772. package/engine/testdata/golden/TestParityTickAfk/bulletPipe.golden +0 -2
  773. package/engine/testdata/golden/TestParityTickAfk/missingField/state.md.golden +0 -3
  774. package/engine/testdata/golden/TestParityTickAfk/missingField.golden +0 -2
  775. package/engine/testdata/golden/TestParityTickAfk/missingFile.golden +0 -1
  776. package/engine/testdata/golden/TestParityTickAfk/nonNumeric/state.md.golden +0 -1
  777. package/engine/testdata/golden/TestParityTickAfk/nonNumeric.golden +0 -1
  778. package/engine/testdata/golden/TestParityTickAfk/none/state.md.golden +0 -1
  779. package/engine/testdata/golden/TestParityTickAfk/none.golden +0 -2
  780. package/engine/testdata/golden/TestParityWrightScope/agent-required-non-wright.golden +0 -1
  781. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +0 -2
  782. package/engine/testdata/golden/TestParityWrightScope/in-scope-allowed.golden +0 -1
  783. package/engine/testdata/golden/TestParityWrightScope/non-edit-tool.golden +0 -1
  784. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +0 -2
  785. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-observe-silent.golden +0 -1
  786. package/engine/testdata/golden/TestSpecSkeleton/missing-some.golden +0 -2
  787. package/engine/testdata/golden/TestSpecSkeleton/present-all.golden +0 -2
  788. package/engine/testdata/golden/TestStuckLogHashParity.golden +0 -1
  789. package/engine/tests/adr_0006_clock_seam_test.go +0 -36
  790. package/engine/tests/budget_test.go +0 -88
  791. package/engine/tests/canonical_ac_ids_test.go +0 -44
  792. package/engine/tests/concurrency_cli_test.go +0 -131
  793. package/engine/tests/conventions_test.go +0 -106
  794. package/engine/tests/doctor_cli_test.go +0 -223
  795. package/engine/tests/forge_cli_test.go +0 -463
  796. package/engine/tests/hook_test.go +0 -2732
  797. package/engine/tests/hooks_io_test.go +0 -309
  798. package/engine/tests/json_contract_test.go +0 -224
  799. package/engine/tests/lib_parity_test.go +0 -930
  800. package/engine/tests/migrate_cli_test.go +0 -195
  801. package/engine/tests/parity_analyze_test.go +0 -55
  802. package/engine/tests/parity_buildreadiness_test.go +0 -199
  803. package/engine/tests/parity_learnings_test.go +0 -89
  804. package/engine/tests/parity_mutationgate_test.go +0 -36
  805. package/engine/tests/parity_reconcile_test.go +0 -94
  806. package/engine/tests/parity_testintegrity_test.go +0 -65
  807. package/engine/tests/parity_tickafk_test.go +0 -83
  808. package/engine/validatepack.go +0 -192
  809. package/engine/validatepack_test.go +0 -79
  810. package/engine/validatepackskills.go +0 -181
  811. package/engine/validatepackskills_test.go +0 -171
  812. package/pack/.claude/agents/devrites-forge-judge.md +0 -118
  813. package/pack/.claude/skills/devrites-lib/reference/model-tiers.md +0 -44
  814. package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +0 -52
  815. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +0 -45
  816. package/pack/.claude/skills/rite-build/reference/forge.md +0 -210
  817. package/pack/.claude/skills/rite-seal/reference/conventions-ledger.md +0 -60
  818. package/pack/.claude/skills/rite-ship/reference/adr-promotion.md +0 -9
  819. package/pack/.claude/skills/rite-ship/reference/ledger.md +0 -56
  820. package/pack/generated/claude/agents/devrites-forge-judge.md +0 -118
  821. package/pack/generated/claude/skills/devrites-lib/reference/model-tiers.md +0 -44
  822. package/pack/generated/claude/skills/devrites-refresh-indexes/SKILL.md +0 -52
  823. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +0 -45
  824. package/pack/generated/claude/skills/rite-build/reference/forge.md +0 -210
  825. package/pack/generated/claude/skills/rite-seal/reference/conventions-ledger.md +0 -60
  826. package/pack/generated/claude/skills/rite-ship/reference/adr-promotion.md +0 -9
  827. package/pack/generated/claude/skills/rite-ship/reference/ledger.md +0 -56
  828. package/pack/generated/codex/agents/devrites-forge-judge.toml +0 -128
  829. package/pack/generated/codex/hooks.json +0 -185
  830. package/pack/generated/codex/skills/devrites-api-interface/agents/openai.yaml +0 -2
  831. package/pack/generated/codex/skills/devrites-audit/agents/openai.yaml +0 -2
  832. package/pack/generated/codex/skills/devrites-browser-proof/agents/openai.yaml +0 -2
  833. package/pack/generated/codex/skills/devrites-debug-recovery/agents/openai.yaml +0 -2
  834. package/pack/generated/codex/skills/devrites-doubt/agents/openai.yaml +0 -2
  835. package/pack/generated/codex/skills/devrites-frontend-craft/agents/openai.yaml +0 -2
  836. package/pack/generated/codex/skills/devrites-interview/agents/openai.yaml +0 -2
  837. package/pack/generated/codex/skills/devrites-lib/reference/model-tiers.md +0 -44
  838. package/pack/generated/codex/skills/devrites-prose-craft/agents/openai.yaml +0 -2
  839. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +0 -71
  840. package/pack/generated/codex/skills/devrites-refresh-indexes/agents/openai.yaml +0 -2
  841. package/pack/generated/codex/skills/devrites-source-driven/agents/openai.yaml +0 -2
  842. package/pack/generated/codex/skills/devrites-ux-shape/agents/openai.yaml +0 -2
  843. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +0 -45
  844. package/pack/generated/codex/skills/rite-build/reference/forge.md +0 -210
  845. package/pack/generated/codex/skills/rite-seal/reference/conventions-ledger.md +0 -60
  846. package/pack/generated/codex/skills/rite-ship/reference/adr-promotion.md +0 -9
  847. package/pack/generated/codex/skills/rite-ship/reference/ledger.md +0 -56
  848. package/scripts/build-binaries.sh +0 -75
  849. package/scripts/build-release-tarball.sh +0 -105
  850. package/scripts/check-authority-drift.py +0 -125
  851. package/scripts/check-cross-refs.py +0 -139
  852. package/scripts/check-generated-skill-budget.mjs +0 -42
  853. package/scripts/check-instruction-size-baseline.mjs +0 -88
  854. package/scripts/check-invocation-integrity.py +0 -115
  855. package/scripts/check-no-global-writes.sh +0 -66
  856. package/scripts/check-no-personal-paths.py +0 -50
  857. package/scripts/check-npm-audit.mjs +0 -110
  858. package/scripts/check-reference-governance.mjs +0 -83
  859. package/scripts/check-reply-contract.sh +0 -64
  860. package/scripts/check-rule-uniqueness.sh +0 -45
  861. package/scripts/devrites-detect.sh +0 -176
  862. package/scripts/grade-feature.sh +0 -204
  863. package/scripts/live-hosts/agent-result.schema.json +0 -231
  864. package/scripts/live-hosts/claude.sh +0 -87
  865. package/scripts/live-hosts/codex.sh +0 -85
  866. package/scripts/live-hosts/common.sh +0 -113
  867. package/scripts/live-hosts/fake-host.py +0 -257
  868. package/scripts/live-hosts/host-transport.py +0 -286
  869. package/scripts/npm-audit-exceptions.json +0 -26
  870. package/scripts/pin.sh +0 -249
  871. package/scripts/release-check.sh +0 -75
  872. package/scripts/run-agent-contract-evals.py +0 -1380
  873. package/scripts/run-behavioral-evals.sh +0 -196
  874. package/scripts/run-evals.sh +0 -132
  875. package/scripts/run-live-behavioral-evals.py +0 -1310
  876. package/scripts/run-outcome-evals.sh +0 -454
  877. package/scripts/run-routing-evals.py +0 -342
  878. package/scripts/run-tests.mjs +0 -232
  879. package/scripts/scan-pack-security.py +0 -209
  880. package/scripts/scan-supply-chain-iocs.py +0 -127
  881. package/scripts/skill-pruning-audit.mjs +0 -121
  882. package/scripts/skills-inventory.mjs +0 -125
  883. package/scripts/supply-chain-iocs.json +0 -11
  884. package/scripts/sync-version.sh +0 -54
  885. package/scripts/validate-agent-composition.py +0 -57
  886. package/scripts/validate-command-parity.py +0 -70
  887. package/scripts/validate-frontmatter.py +0 -218
  888. package/scripts/validate-skill-anatomy.py +0 -139
  889. package/scripts/validate-workflow-security.py +0 -184
  890. package/scripts/validate-workspace-schema.py +0 -800
  891. package/scripts/validate.sh +0 -354
  892. package/scripts/workflow_schema.py +0 -69
  893. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/decisions.md +0 -0
  894. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/plan.md +0 -0
  895. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/spec.md +0 -0
  896. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/tasks.md +0 -0
  897. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/search-ranking/spec.md +0 -0
@@ -1,1380 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Run the hermetic Claude/Codex agent-contract matrix.
3
-
4
- Fake mode is deterministic and network-free. Live mode is manual, paid, and
5
- fails closed unless host pins, distinct scoped credentials, artifact input,
6
- and both cost ceilings pass preflight.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import argparse
12
- import hashlib
13
- import json
14
- import os
15
- import re
16
- import shutil
17
- import signal
18
- import stat
19
- import subprocess
20
- import sys
21
- import tempfile
22
- import time
23
- from collections import Counter
24
- from datetime import datetime, timezone
25
- from decimal import Decimal, InvalidOperation
26
- from pathlib import Path, PurePosixPath
27
- from typing import Any
28
-
29
-
30
- ROOT = Path(__file__).resolve().parent.parent
31
- DEFAULT_MATRIX = ROOT / "evals" / "agent-contract" / "contract-matrix.json"
32
- FAKE_HOST = ROOT / "scripts" / "live-hosts" / "fake-host.py"
33
- LIVE_CONFIRMATION = "RUN-PAID-HOST-CONTRACTS"
34
- SUMMARY_SCHEMA = "devrites-agent-contract-summary/v1"
35
- PREFLIGHT_SCHEMA = "devrites-agent-contract-preflight/v1"
36
- BUDGET_SCHEMA = "devrites-agent-contract-budget/v1"
37
- LIVE_RUNTIME_COST_CAPS = {"claude": "cli-max-budget-usd"}
38
- RUN_ID_RE = re.compile(r"^drv-run-v1:[0-9a-f]{32}$")
39
- REASON_RE = re.compile(r'\bID\s*=\s*"([A-Z0-9-]+)"')
40
- SAFE_TOKEN_RE = re.compile(r"^[a-z][a-z0-9-]{0,63}$")
41
- SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
42
- SENSITIVE_ASSIGNMENT_RE = re.compile(
43
- r"(?i)(?:api[_-]?key|authorization|bearer|credential|password|secret|token)\s*[:=]\s*\S+"
44
- )
45
- SECRET_TOKEN_RE = re.compile(
46
- r"(?i)\b(?:sk|rk|ghp|github_pat|xox[baprs])[-_][a-z0-9_-]{8,}\b"
47
- )
48
- TEXT_PAYLOAD_RE = re.compile(r"(?i)(?:^|[\s,{])(prompt|raw|source|trace)\s*[:=]")
49
- MANDATORY_SIGNALS = {
50
- "auth_preflight",
51
- "budget_bound",
52
- "cleanup_ready",
53
- "environment_allowlisted",
54
- "mcp_absent",
55
- "plugins_absent",
56
- "roots_isolated",
57
- "shell_startup_ignored",
58
- "version_pinned",
59
- }
60
- RESULT_KEYS = {
61
- "result_version",
62
- "run_id",
63
- "cell_id",
64
- "host",
65
- "role",
66
- "status",
67
- "baseline_id",
68
- "scope_id",
69
- "budget",
70
- "side_effects",
71
- "payload_type",
72
- "execution",
73
- "signals",
74
- "usage",
75
- "dispatch_reason_id",
76
- "result_reason_id",
77
- "outcome",
78
- "terminal_sentinel",
79
- "host_version",
80
- "model",
81
- }
82
- FORBIDDEN_RESULT_KEYS = {
83
- "absolute_path",
84
- "auth",
85
- "config",
86
- "home",
87
- "prompt",
88
- "raw",
89
- "scratch_root",
90
- "source",
91
- "trace",
92
- }
93
-
94
-
95
- class ContractError(Exception):
96
- def __init__(self, message: str, reason_id: str = "DRV-AGENT-RESULT-MALFORMED"):
97
- super().__init__(message)
98
- self.reason_id = reason_id
99
-
100
-
101
- def canonical_json(value: Any) -> bytes:
102
- return json.dumps(
103
- value, sort_keys=True, separators=(",", ":"), ensure_ascii=True
104
- ).encode()
105
-
106
-
107
- def sha256_bytes(value: bytes) -> str:
108
- return hashlib.sha256(value).hexdigest()
109
-
110
-
111
- def load_json(path: Path) -> Any:
112
- try:
113
- return json.loads(path.read_text(encoding="utf-8"))
114
- except (OSError, UnicodeError, json.JSONDecodeError) as exc:
115
- raise ContractError("JSON-compatible YAML is required") from exc
116
-
117
-
118
- def exact_keys(value: dict[str, Any], required: set[str], where: str) -> None:
119
- keys = set(value)
120
- if keys != required:
121
- raise ContractError(f"{where} keys do not match the frozen schema")
122
-
123
-
124
- def safe_relative(value: Any, where: str) -> str:
125
- if not isinstance(value, str) or not value:
126
- raise ContractError(f"{where} must be a non-empty relative path")
127
- path = PurePosixPath(value)
128
- if path.is_absolute() or ".." in path.parts or str(path) != value or "\\" in value:
129
- raise ContractError(f"{where} is not a confined portable path")
130
- return value
131
-
132
-
133
- def safe_id(value: Any, where: str) -> str:
134
- if (
135
- not isinstance(value, str)
136
- or not 1 <= len(value) <= 128
137
- or value.startswith("/")
138
- or any(ord(character) < 32 or ord(character) == 127 for character in value)
139
- ):
140
- raise ContractError(
141
- f"{where} is not a safe identifier", "DRV-AGENT-UNAVAILABLE"
142
- )
143
- return value
144
-
145
-
146
- def positive_int(value: Any, where: str) -> int:
147
- if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
148
- raise ContractError(f"{where} must be a positive integer")
149
- return value
150
-
151
-
152
- def money(value: Any, where: str, *, allow_zero: bool = False) -> Decimal:
153
- try:
154
- parsed = Decimal(str(value))
155
- except (InvalidOperation, ValueError) as exc:
156
- raise ContractError(f"{where} must be a decimal amount") from exc
157
- if not parsed.is_finite() or parsed < 0 or (not allow_zero and parsed == 0):
158
- raise ContractError(
159
- f"{where} must be {'non-negative' if allow_zero else 'positive'}"
160
- )
161
- return parsed
162
-
163
-
164
- def private_string_fault(value: str) -> str | None:
165
- if (
166
- value.startswith(("/", "~", "file://"))
167
- or re.match(r"^[A-Za-z]:[\\/]", value)
168
- or "\x00" in value
169
- ):
170
- return "path"
171
- if len(value) > 256 or any(character in value for character in ("\n", "\r", "```")):
172
- return "text payload"
173
- if (
174
- SENSITIVE_ASSIGNMENT_RE.search(value)
175
- or SECRET_TOKEN_RE.search(value)
176
- or TEXT_PAYLOAD_RE.search(value)
177
- ):
178
- return "sensitive payload"
179
- return None
180
-
181
-
182
- def frozen_reasons() -> set[str]:
183
- path = ROOT / "engine" / "internal" / "reason" / "reason.go"
184
- try:
185
- reasons = set(REASON_RE.findall(path.read_text(encoding="utf-8")))
186
- except OSError as exc:
187
- raise ContractError("frozen reason catalog is unavailable") from exc
188
- if not reasons:
189
- raise ContractError("frozen reason catalog is empty")
190
- return reasons
191
-
192
-
193
- def validate_matrix(data: Any) -> dict[str, Any]:
194
- if not isinstance(data, dict):
195
- raise ContractError("matrix must be an object")
196
- exact_keys(
197
- data,
198
- {"schema", "packet_schema", "result_schema", "hosts", "budget", "scenarios"},
199
- "matrix",
200
- )
201
- if data["schema"] != "devrites-agent-contract-matrix/v1":
202
- raise ContractError("matrix schema is unsupported")
203
- if (
204
- data["packet_schema"] != "agent-packet/v1"
205
- or data["result_schema"] != "agent-result/v1"
206
- ):
207
- raise ContractError("transport schema is unsupported")
208
-
209
- reasons = frozen_reasons()
210
- hosts = data["hosts"]
211
- if not isinstance(hosts, list) or len(hosts) != 2:
212
- raise ContractError("matrix must define exactly two hosts")
213
- host_ids: set[str] = set()
214
- for host in hosts:
215
- if not isinstance(host, dict):
216
- raise ContractError("host row must be an object")
217
- exact_keys(host, {"id", "launcher", "fake_version"}, "host")
218
- host_id = host["id"]
219
- if host_id not in {"claude", "codex"} or host_id in host_ids:
220
- raise ContractError("host IDs must be unique claude and codex")
221
- host_ids.add(host_id)
222
- safe_relative(host["launcher"], "host.launcher")
223
- if not isinstance(host["fake_version"], str) or not host["fake_version"]:
224
- raise ContractError("host.fake_version must be non-empty")
225
- launcher = (ROOT / host["launcher"]).resolve()
226
- if ROOT.resolve() not in launcher.parents:
227
- raise ContractError("host launcher escapes the repository")
228
-
229
- budget = data["budget"]
230
- if not isinstance(budget, dict):
231
- raise ContractError("budget must be an object")
232
- exact_keys(
233
- budget,
234
- {
235
- "timeout_seconds",
236
- "estimated_max_cost_usd_per_cell",
237
- "max_files",
238
- "max_loaded_lines",
239
- "max_result_lines",
240
- },
241
- "budget",
242
- )
243
- for key in ("timeout_seconds", "max_files", "max_loaded_lines", "max_result_lines"):
244
- positive_int(budget[key], f"budget.{key}")
245
- money(
246
- budget["estimated_max_cost_usd_per_cell"],
247
- "budget.estimated_max_cost_usd_per_cell",
248
- )
249
-
250
- scenarios = data["scenarios"]
251
- if not isinstance(scenarios, list) or not scenarios:
252
- raise ContractError("matrix scenarios must be non-empty")
253
- scenario_ids: set[str] = set()
254
- for scenario in scenarios:
255
- if not isinstance(scenario, dict):
256
- raise ContractError("scenario row must be an object")
257
- exact_keys(
258
- scenario,
259
- {"id", "role", "role_class", "simulate", "allowed_repo_writes", "expect"},
260
- "scenario",
261
- )
262
- scenario_id = scenario["id"]
263
- if (
264
- not isinstance(scenario_id, str)
265
- or not SAFE_TOKEN_RE.fullmatch(scenario_id)
266
- or scenario_id in scenario_ids
267
- ):
268
- raise ContractError("scenario IDs must be unique safe tokens")
269
- scenario_ids.add(scenario_id)
270
- if not isinstance(scenario["role"], str) or not scenario["role"]:
271
- raise ContractError("scenario.role must be non-empty")
272
- if scenario["role_class"] not in {"reviewer", "wright"}:
273
- raise ContractError("scenario.role_class is unsupported")
274
- if scenario["simulate"] not in {
275
- "accepted",
276
- "dispatch-unavailable",
277
- "generic-fallback",
278
- "interrupted",
279
- "malformed",
280
- "missing",
281
- "post-compaction",
282
- "reviewer-denied",
283
- "session-start",
284
- "stale",
285
- "wright-denied",
286
- "write-allowed",
287
- }:
288
- raise ContractError("scenario.simulate is unsupported")
289
- writes = scenario["allowed_repo_writes"]
290
- if not isinstance(writes, list) or len(writes) != len(set(writes)):
291
- raise ContractError("allowed writes must be a unique list")
292
- for index, path in enumerate(writes):
293
- safe_relative(path, f"allowed_repo_writes[{index}]")
294
- if scenario["role_class"] == "reviewer" and writes:
295
- raise ContractError("reviewers cannot receive repo writes")
296
-
297
- expect = scenario["expect"]
298
- if not isinstance(expect, dict):
299
- raise ContractError("scenario.expect must be an object")
300
- exact_keys(
301
- expect,
302
- {
303
- "execution_mode",
304
- "guard_strength",
305
- "independence",
306
- "dispatch_reason_id",
307
- "result_reason_id",
308
- "status",
309
- "outcome",
310
- "terminal_sentinel",
311
- "host_exit",
312
- "required_signals",
313
- },
314
- "scenario.expect",
315
- )
316
- if expect["execution_mode"] not in {"named", "generic", "none"}:
317
- raise ContractError("execution mode is unsupported")
318
- if expect["guard_strength"] not in {
319
- "enforced",
320
- "observed",
321
- "unavailable",
322
- "bypassed",
323
- }:
324
- raise ContractError("guard strength is unsupported")
325
- if not isinstance(expect["independence"], bool):
326
- raise ContractError("independence must be boolean")
327
- for key in ("dispatch_reason_id", "result_reason_id"):
328
- if expect[key] not in reasons:
329
- raise ContractError(f"{key} is not in the frozen reason catalog")
330
- if expect["status"] not in {"complete", "partial", "blocked", "failed"}:
331
- raise ContractError("expected status is unsupported")
332
- if expect["outcome"] not in {"passed", "denied", "unavailable"}:
333
- raise ContractError("expected outcome is unsupported")
334
- if expect["terminal_sentinel"] not in {
335
- "complete",
336
- "interrupted",
337
- "malformed",
338
- "missing",
339
- "stale",
340
- }:
341
- raise ContractError("terminal sentinel is unsupported")
342
- if isinstance(expect["host_exit"], bool) or expect["host_exit"] not in {0, 130}:
343
- raise ContractError("expected host exit is unsupported")
344
- required_signals = expect["required_signals"]
345
- if (
346
- not isinstance(required_signals, list)
347
- or len(required_signals) != len(set(required_signals))
348
- or not all(
349
- isinstance(item, str)
350
- and SAFE_TOKEN_RE.fullmatch(item.replace("_", "-"))
351
- for item in required_signals
352
- )
353
- ):
354
- raise ContractError("required signals must be unique safe tokens")
355
- return data
356
-
357
-
358
- def tree_snapshot(root: Path) -> dict[str, dict[str, Any]]:
359
- out: dict[str, dict[str, Any]] = {}
360
- if not root.exists():
361
- return out
362
- for path in sorted(root.rglob("*")):
363
- rel = path.relative_to(root).as_posix()
364
- mode = stat.S_IMODE(path.lstat().st_mode)
365
- if path.is_symlink():
366
- out[rel] = {
367
- "kind": "symlink",
368
- "mode": mode,
369
- "sha256": sha256_bytes(os.readlink(path).encode()),
370
- }
371
- elif path.is_file():
372
- out[rel] = {
373
- "kind": "file",
374
- "mode": mode,
375
- "sha256": sha256_bytes(path.read_bytes()),
376
- }
377
- return out
378
-
379
-
380
- def tree_delta(
381
- before: dict[str, dict[str, Any]], after: dict[str, dict[str, Any]]
382
- ) -> list[dict[str, Any]]:
383
- delta: list[dict[str, Any]] = []
384
- for path in sorted(set(before) | set(after)):
385
- if before.get(path) == after.get(path):
386
- continue
387
- if path not in after:
388
- delta.append(
389
- {"path": path, "kind": "deleted", "sha256": sha256_bytes(b"deleted")}
390
- )
391
- else:
392
- delta.append(
393
- {
394
- "path": path,
395
- "kind": after[path]["kind"],
396
- "sha256": after[path]["sha256"],
397
- }
398
- )
399
- return delta
400
-
401
-
402
- def parse_assignments(values: list[str], hosts: set[str], label: str) -> dict[str, str]:
403
- out: dict[str, str] = {}
404
- for value in values:
405
- if "=" not in value:
406
- raise ContractError(f"{label} must use HOST=VALUE", "DRV-AGENT-UNAVAILABLE")
407
- host, item = value.split("=", 1)
408
- if host not in hosts or not item or host in out:
409
- raise ContractError(
410
- f"{label} has an invalid or duplicate host", "DRV-AGENT-UNAVAILABLE"
411
- )
412
- out[host] = item
413
- return out
414
-
415
-
416
- def secure_auth_file(path_value: str) -> Path:
417
- path = Path(path_value)
418
- try:
419
- info = path.lstat()
420
- except OSError as exc:
421
- raise ContractError(
422
- "scoped auth file is unavailable", "DRV-AGENT-UNAVAILABLE"
423
- ) from exc
424
- if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
425
- raise ContractError(
426
- "scoped auth file must be a regular non-symlink", "DRV-AGENT-UNAVAILABLE"
427
- )
428
- if info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) != 0o600:
429
- raise ContractError(
430
- "scoped auth file must be owner-only mode 0600", "DRV-AGENT-UNAVAILABLE"
431
- )
432
- if info.st_size <= 0 or info.st_size > 64 * 1024:
433
- raise ContractError("scoped auth file size is invalid", "DRV-AGENT-UNAVAILABLE")
434
- return path.resolve()
435
-
436
-
437
- def launcher_version(
438
- host: dict[str, Any], fake: bool, fake_fault: str | None = None
439
- ) -> str:
440
- launcher = ROOT / host["launcher"]
441
- env = {
442
- "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
443
- "LANG": "C",
444
- "LC_ALL": "C",
445
- }
446
- if fake:
447
- env["DEVRITES_CONTRACT_FAKE_HOST"] = str(FAKE_HOST)
448
- env["DEVRITES_CONTRACT_FAKE_VERSION"] = host["fake_version"]
449
- env["DEVRITES_CONTRACT_HOST"] = host["id"]
450
- if fake_fault == "version-mismatch":
451
- env["DEVRITES_CONTRACT_FAKE_VERSION"] += "-mismatch"
452
- try:
453
- proc = subprocess.run(
454
- [str(launcher), "version"],
455
- env=env,
456
- text=True,
457
- stdout=subprocess.PIPE,
458
- stderr=subprocess.DEVNULL,
459
- timeout=15,
460
- check=False,
461
- )
462
- except (OSError, subprocess.TimeoutExpired) as exc:
463
- raise ContractError(
464
- "host version preflight failed", "DRV-AGENT-UNAVAILABLE"
465
- ) from exc
466
- version = proc.stdout.strip()
467
- if proc.returncode != 0 or not version or "\n" in version:
468
- raise ContractError("host version preflight failed", "DRV-AGENT-UNAVAILABLE")
469
- return version
470
-
471
-
472
- def load_budget_ledger(path: Path) -> dict[str, Any]:
473
- data = load_json(path)
474
- if not isinstance(data, dict):
475
- raise ContractError("budget ledger must be an object", "DRV-AGENT-UNAVAILABLE")
476
- exact_keys(data, {"schema", "month", "spent_usd"}, "budget ledger")
477
- if data["schema"] != BUDGET_SCHEMA:
478
- raise ContractError(
479
- "budget ledger schema is unsupported", "DRV-AGENT-UNAVAILABLE"
480
- )
481
- if data["month"] != datetime.now(timezone.utc).strftime("%Y-%m"):
482
- raise ContractError(
483
- "budget ledger month is not current", "DRV-AGENT-UNAVAILABLE"
484
- )
485
- money(data["spent_usd"], "budget ledger spent_usd", allow_zero=True)
486
- return data
487
-
488
-
489
- def live_preflight(
490
- args: argparse.Namespace, matrix: dict[str, Any]
491
- ) -> tuple[dict[str, dict[str, str]], Decimal, Decimal, Path]:
492
- hosts = {row["id"]: row for row in matrix["hosts"]}
493
- host_ids = set(hosts)
494
- if args.confirm_live != LIVE_CONFIRMATION:
495
- raise ContractError("live confirmation is missing", "DRV-AGENT-UNAVAILABLE")
496
- versions = parse_assignments(args.host_version, host_ids, "--host-version")
497
- models = parse_assignments(args.host_model, host_ids, "--host-model")
498
- versions = {
499
- host: safe_id(value, f"--host-version {host}")
500
- for host, value in versions.items()
501
- }
502
- models = {
503
- host: safe_id(value, f"--host-model {host}")
504
- for host, value in models.items()
505
- }
506
- auth_values = parse_assignments(args.auth_file, host_ids, "--auth-file")
507
- if (
508
- set(versions) != host_ids
509
- or set(models) != host_ids
510
- or set(auth_values) != host_ids
511
- ):
512
- raise ContractError(
513
- "every host needs a version, model, and auth file", "DRV-AGENT-UNAVAILABLE"
514
- )
515
- auth = {host: secure_auth_file(auth_values[host]) for host in host_ids}
516
- auth_identities = {
517
- (path.stat().st_dev, path.stat().st_ino) for path in auth.values()
518
- }
519
- if len(set(auth.values())) != len(auth) or len(auth_identities) != len(auth):
520
- raise ContractError(
521
- "host credentials must use separate scoped files", "DRV-AGENT-UNAVAILABLE"
522
- )
523
- actual_versions: dict[str, dict[str, str]] = {}
524
- for host_id, host in hosts.items():
525
- actual = launcher_version(host, False)
526
- if actual != versions[host_id]:
527
- raise ContractError(
528
- "host version does not match its exact pin", "DRV-AGENT-UNAVAILABLE"
529
- )
530
- actual_versions[host_id] = {
531
- "version": actual,
532
- "model": models[host_id],
533
- "auth_file": str(auth[host_id]),
534
- }
535
-
536
- if args.host_artifacts is None or not args.host_artifacts.is_dir():
537
- raise ContractError(
538
- "live mode requires a prepared host artifact directory",
539
- "DRV-AGENT-UNAVAILABLE",
540
- )
541
- per_run = money(args.max_cost_per_run_usd, "--max-cost-per-run-usd")
542
- monthly = money(args.max_monthly_cost_usd, "--max-monthly-cost-usd")
543
- if args.budget_ledger is None:
544
- raise ContractError(
545
- "live mode requires a monthly budget ledger", "DRV-AGENT-UNAVAILABLE"
546
- )
547
- ledger = load_budget_ledger(args.budget_ledger)
548
- spent = money(ledger["spent_usd"], "budget ledger spent_usd", allow_zero=True)
549
- projected = per_run * Decimal(len(matrix["hosts"]) * len(matrix["scenarios"]))
550
- if spent + projected > monthly:
551
- raise ContractError(
552
- "the full matrix exceeds the monthly ceiling", "DRV-AGENT-UNAVAILABLE"
553
- )
554
- uncapped = sorted(host_ids - LIVE_RUNTIME_COST_CAPS.keys())
555
- if uncapped:
556
- raise ContractError(
557
- "paid live host lacks an enforced runtime/provider cost cap: "
558
- + ", ".join(uncapped),
559
- "DRV-AGENT-UNAVAILABLE",
560
- )
561
- return actual_versions, per_run, monthly, args.budget_ledger
562
-
563
-
564
- def seed_project(
565
- project: Path, live: bool, host_artifacts: Path | None, env: dict[str, str]
566
- ) -> None:
567
- project.mkdir(parents=True)
568
- (project / "README.md").write_text(
569
- "# Synthetic agent contract fixture\n", encoding="utf-8"
570
- )
571
- (project / "src").mkdir()
572
- if not live:
573
- return
574
- install_env = {
575
- "PATH": env["PATH"],
576
- "HOME": env["HOME"],
577
- "TMPDIR": env["TMPDIR"],
578
- "LANG": "C",
579
- "LC_ALL": "C",
580
- "DEVRITES_HOST_ARTIFACT_DIR": str(host_artifacts),
581
- }
582
- proc = subprocess.run(
583
- ["bash", str(ROOT / "install.sh"), "--target", str(project)],
584
- env=install_env,
585
- stdout=subprocess.DEVNULL,
586
- stderr=subprocess.DEVNULL,
587
- timeout=180,
588
- check=False,
589
- )
590
- if proc.returncode != 0:
591
- raise ContractError(
592
- "isolated host-pack install failed", "DRV-AGENT-UNAVAILABLE"
593
- )
594
- fixture = ROOT / "evals" / "golden" / "shippable-feature"
595
- workspace = project / ".devrites" / "work" / "shippable-feature"
596
- workspace.parent.mkdir(parents=True, exist_ok=True)
597
- shutil.copytree(fixture, workspace)
598
- (project / ".devrites" / "ACTIVE").write_text(
599
- "shippable-feature\n", encoding="utf-8"
600
- )
601
-
602
-
603
- def make_packet(
604
- matrix: dict[str, Any],
605
- host: dict[str, Any],
606
- scenario: dict[str, Any],
607
- project: Path,
608
- scratch: Path,
609
- ) -> dict[str, Any]:
610
- baseline_tree = tree_snapshot(project)
611
- diff_hash = sha256_bytes(canonical_json(baseline_tree))
612
- touched_hash = sha256_bytes(canonical_json(sorted(baseline_tree)))
613
- run_id = "drv-run-v1:" + os.urandom(16).hex()
614
- baseline_id = f"no-git:{diff_hash}:{touched_hash}"
615
- budget = matrix["budget"]
616
- return {
617
- "packet_version": matrix["packet_schema"],
618
- "run_id": run_id,
619
- "cell_id": f"{host['id']}:{scenario['id']}",
620
- "host": host["id"],
621
- "role": scenario["role"],
622
- "role_class": scenario["role_class"],
623
- "phase": "eval",
624
- "attempt": 1,
625
- "baseline": {
626
- "id": baseline_id,
627
- "head": "no-git",
628
- "diff_sha256": diff_hash,
629
- "touched_sha256": touched_hash,
630
- },
631
- "objective_id": scenario["id"],
632
- "inputs": [{"path": "README.md", "purpose": "synthetic-contract-fixture"}],
633
- "scope": {
634
- "id": sha256_bytes(canonical_json(scenario["allowed_repo_writes"])),
635
- "in": ["README.md"],
636
- "out": ["repository-history", "external-systems"],
637
- "allowed_repo_writes": scenario["allowed_repo_writes"],
638
- },
639
- "budgets": {
640
- "max_files": budget["max_files"],
641
- "max_loaded_lines": budget["max_loaded_lines"],
642
- "max_result_lines": budget["max_result_lines"],
643
- },
644
- "scratch_root": str(scratch),
645
- "simulate": scenario["simulate"],
646
- "expected_execution": scenario["expect"]["execution_mode"],
647
- "expected_guard": scenario["expect"]["guard_strength"],
648
- "allowed_signals": sorted(
649
- MANDATORY_SIGNALS | set(scenario["expect"]["required_signals"])
650
- ),
651
- "result_schema": matrix["result_schema"],
652
- }
653
-
654
-
655
- def write_transport(path: Path, value: Any) -> None:
656
- path.write_bytes(canonical_json(value) + b"\n")
657
- path.chmod(0o600)
658
-
659
-
660
- def synthetic_reconcile(
661
- host: dict[str, Any],
662
- scenario: dict[str, Any],
663
- exit_code: int,
664
- reason_id: str,
665
- sentinel: str,
666
- host_version: str,
667
- model: str,
668
- *,
669
- live: bool,
670
- identity_matched: bool = True,
671
- baseline_matched: bool = True,
672
- ) -> dict[str, Any]:
673
- expect = scenario["expect"]
674
- return {
675
- "cell_id": f"{host['id']}:{scenario['id']}",
676
- "host": host["id"],
677
- "scenario": scenario["id"],
678
- "role": scenario["role"],
679
- "passed": False,
680
- "status": "partial" if sentinel == "interrupted" else "failed",
681
- "outcome": "unavailable",
682
- "execution_mode": expect["execution_mode"],
683
- "guard_strength": expect["guard_strength"],
684
- "independence": expect["independence"],
685
- "dispatch_reason_id": expect["dispatch_reason_id"],
686
- "result_reason_id": reason_id,
687
- "terminal_sentinel": sentinel,
688
- "host_exit": exit_code,
689
- "host_version": host_version,
690
- "model": model,
691
- "identity_matched": identity_matched,
692
- "baseline_matched": baseline_matched,
693
- "repo_writes": [],
694
- "signals": sorted(MANDATORY_SIGNALS if sentinel == "interrupted" else []),
695
- "usage": {
696
- "input_tokens": 0,
697
- "output_tokens": 0,
698
- "cost_usd": None if live else "0",
699
- "cost_provenance": "unavailable" if live else "synthetic",
700
- },
701
- }
702
-
703
-
704
- def declared_writes(value: Any) -> list[dict[str, Any]]:
705
- if not isinstance(value, list):
706
- raise ContractError("repo_writes must be a list")
707
- out: list[dict[str, Any]] = []
708
- for item in value:
709
- if not isinstance(item, dict):
710
- raise ContractError("repo write must be an object")
711
- exact_keys(item, {"path", "kind", "sha256"}, "repo write")
712
- safe_relative(item["path"], "repo write path")
713
- if item["kind"] not in {
714
- "file",
715
- "symlink",
716
- "deleted",
717
- } or not SHA256_RE.fullmatch(str(item["sha256"])):
718
- raise ContractError("repo write metadata is invalid")
719
- out.append(item)
720
- if len({item["path"] for item in out}) != len(out):
721
- raise ContractError("repo writes contain duplicates")
722
- return sorted(out, key=lambda item: item["path"])
723
-
724
-
725
- def validate_result(
726
- raw: Any,
727
- packet: dict[str, Any],
728
- host: dict[str, Any],
729
- scenario: dict[str, Any],
730
- exit_code: int,
731
- actual_delta: list[dict[str, Any]],
732
- host_version: str,
733
- model: str,
734
- reasons: set[str],
735
- live: bool,
736
- ) -> dict[str, Any]:
737
- if not isinstance(raw, dict) or set(raw) != RESULT_KEYS:
738
- raise ContractError("result object does not match the frozen schema")
739
- if any(key in FORBIDDEN_RESULT_KEYS for key in raw):
740
- raise ContractError("result contains a forbidden retention key")
741
- if raw["result_version"] != "agent-result/v1":
742
- raise ContractError("result version is unsupported")
743
- identity_fields = {
744
- "run_id": packet["run_id"],
745
- "cell_id": packet["cell_id"],
746
- "host": packet["host"],
747
- "role": packet["role"],
748
- }
749
- if any(raw.get(key) != value for key, value in identity_fields.items()):
750
- raise ContractError(
751
- "result identity mismatch", "DRV-AGENT-RESULT-IDENTITY-MISMATCH"
752
- )
753
- if not RUN_ID_RE.fullmatch(raw["run_id"]):
754
- raise ContractError("result run ID is malformed")
755
- if raw["baseline_id"] != packet["baseline"]["id"]:
756
- raise ContractError("result baseline is stale", "DRV-AGENT-RESULT-STALE")
757
- if raw["scope_id"] != packet["scope"]["id"]:
758
- raise ContractError(
759
- "result scope identity mismatch", "DRV-AGENT-RESULT-IDENTITY-MISMATCH"
760
- )
761
- if raw["host_version"] != host_version or raw["model"] != model:
762
- raise ContractError(
763
- "result host pin identity mismatch", "DRV-AGENT-RESULT-IDENTITY-MISMATCH"
764
- )
765
-
766
- if raw["status"] not in {"complete", "partial", "blocked", "failed"}:
767
- raise ContractError("result status is unsupported")
768
- if raw["outcome"] not in {"passed", "denied", "unavailable"}:
769
- raise ContractError("result outcome is unsupported")
770
- if raw["terminal_sentinel"] not in {"complete", "interrupted"}:
771
- raise ContractError("result terminal sentinel is unsupported")
772
- if raw["payload_type"] not in {"review-findings", "wright-report"}:
773
- raise ContractError("result payload type is unsupported")
774
-
775
- execution = raw["execution"]
776
- if not isinstance(execution, dict):
777
- raise ContractError("result execution must be an object")
778
- exact_keys(
779
- execution, {"mode", "guard_strength", "independence"}, "result execution"
780
- )
781
- if execution["mode"] not in {"named", "generic", "none"}:
782
- raise ContractError("result execution mode is unsupported")
783
- if execution["guard_strength"] not in {
784
- "enforced",
785
- "observed",
786
- "unavailable",
787
- "bypassed",
788
- }:
789
- raise ContractError("result guard strength is unsupported")
790
- if not isinstance(execution["independence"], bool):
791
- raise ContractError("result independence must be boolean")
792
-
793
- for key in ("dispatch_reason_id", "result_reason_id"):
794
- if raw[key] not in reasons:
795
- raise ContractError("result reason is not frozen")
796
- budget = raw["budget"]
797
- if not isinstance(budget, dict):
798
- raise ContractError("result budget must be an object")
799
- exact_keys(
800
- budget,
801
- {"files_used", "loaded_lines_used", "result_lines_used"},
802
- "result budget",
803
- )
804
- packet_limits = packet["budgets"]
805
- for used_key, limit_key in (
806
- ("files_used", "max_files"),
807
- ("loaded_lines_used", "max_loaded_lines"),
808
- ("result_lines_used", "max_result_lines"),
809
- ):
810
- used = budget[used_key]
811
- if (
812
- isinstance(used, bool)
813
- or not isinstance(used, int)
814
- or used < 0
815
- or used > packet_limits[limit_key]
816
- ):
817
- raise ContractError("result exceeded its packet budget")
818
-
819
- side_effects = raw["side_effects"]
820
- if not isinstance(side_effects, dict):
821
- raise ContractError("result side_effects must be an object")
822
- exact_keys(
823
- side_effects, {"repo_writes", "scratch_write_count"}, "result side_effects"
824
- )
825
- writes = declared_writes(side_effects["repo_writes"])
826
- if (
827
- isinstance(side_effects["scratch_write_count"], bool)
828
- or not isinstance(side_effects["scratch_write_count"], int)
829
- or side_effects["scratch_write_count"] < 0
830
- ):
831
- raise ContractError("scratch write count is invalid")
832
- if writes != actual_delta:
833
- raise ContractError(
834
- "declared and observed repo effects differ", "DRV-AGENT-RESULT-OUT-OF-SCOPE"
835
- )
836
- allowed = set(packet["scope"]["allowed_repo_writes"])
837
- if any(item["path"] not in allowed for item in actual_delta):
838
- raise ContractError(
839
- "repo effect escaped the exact allowlist", "DRV-AGENT-RESULT-OUT-OF-SCOPE"
840
- )
841
-
842
- signals = raw["signals"]
843
- if (
844
- not isinstance(signals, list)
845
- or len(signals) != len(set(signals))
846
- or not all(isinstance(item, str) and item for item in signals)
847
- ):
848
- raise ContractError("result signals are invalid")
849
- if not MANDATORY_SIGNALS.issubset(signals):
850
- raise ContractError("result is missing hermetic preflight signals")
851
- allowed_signals = MANDATORY_SIGNALS | set(scenario["expect"]["required_signals"])
852
- if not set(signals).issubset(allowed_signals):
853
- raise ContractError("result contains a signal outside the scenario vocabulary")
854
- usage = raw["usage"]
855
- if not isinstance(usage, dict):
856
- raise ContractError("result usage must be an object")
857
- usage = dict(usage)
858
- if not live and set(usage) == {"input_tokens", "output_tokens", "cost_usd"}:
859
- usage["cost_provenance"] = "synthetic"
860
- exact_keys(
861
- usage,
862
- {"input_tokens", "output_tokens", "cost_usd", "cost_provenance"},
863
- "result usage",
864
- )
865
- for key in ("input_tokens", "output_tokens"):
866
- if (
867
- isinstance(usage[key], bool)
868
- or not isinstance(usage[key], int)
869
- or usage[key] < 0
870
- ):
871
- raise ContractError("result token count is invalid")
872
- provenance = usage["cost_provenance"]
873
- if provenance == "unavailable":
874
- if not live or usage["cost_usd"] is not None:
875
- raise ContractError("unavailable cost provenance is inconsistent")
876
- elif provenance == "provider-reported":
877
- if not live or usage["cost_usd"] is None:
878
- raise ContractError("provider cost provenance is inconsistent")
879
- usage["cost_usd"] = str(
880
- money(usage["cost_usd"], "result cost", allow_zero=True)
881
- )
882
- elif provenance == "synthetic":
883
- if live or usage["cost_usd"] is None:
884
- raise ContractError("synthetic cost provenance is inconsistent")
885
- usage["cost_usd"] = str(
886
- money(usage["cost_usd"], "result cost", allow_zero=True)
887
- )
888
- else:
889
- raise ContractError("result cost provenance is unsupported")
890
-
891
- return {
892
- "cell_id": packet["cell_id"],
893
- "host": host["id"],
894
- "scenario": scenario["id"],
895
- "role": scenario["role"],
896
- "passed": False,
897
- "status": raw["status"],
898
- "outcome": raw["outcome"],
899
- "execution_mode": execution["mode"],
900
- "guard_strength": execution["guard_strength"],
901
- "independence": execution["independence"],
902
- "dispatch_reason_id": raw["dispatch_reason_id"],
903
- "result_reason_id": raw["result_reason_id"],
904
- "terminal_sentinel": raw["terminal_sentinel"],
905
- "host_exit": exit_code,
906
- "host_version": raw["host_version"],
907
- "model": raw["model"],
908
- "identity_matched": True,
909
- "baseline_matched": True,
910
- "repo_writes": writes,
911
- "signals": sorted(signals),
912
- "usage": usage,
913
- }
914
-
915
-
916
- def usage_cost(usage: dict[str, Any]) -> Decimal | None:
917
- if usage["cost_provenance"] == "unavailable":
918
- return None
919
- return money(usage["cost_usd"], "result cost", allow_zero=True)
920
-
921
-
922
- def result_matches_expected(result: dict[str, Any], scenario: dict[str, Any]) -> bool:
923
- expect = scenario["expect"]
924
- pairs = {
925
- "status": expect["status"],
926
- "outcome": expect["outcome"],
927
- "execution_mode": expect["execution_mode"],
928
- "guard_strength": expect["guard_strength"],
929
- "independence": expect["independence"],
930
- "dispatch_reason_id": expect["dispatch_reason_id"],
931
- "result_reason_id": expect["result_reason_id"],
932
- "terminal_sentinel": expect["terminal_sentinel"],
933
- "host_exit": expect["host_exit"],
934
- }
935
- if any(result.get(key) != value for key, value in pairs.items()):
936
- return False
937
- if not set(expect["required_signals"]).issubset(result.get("signals", [])):
938
- return False
939
- if scenario["simulate"] == "write-allowed":
940
- if [item["path"] for item in result["repo_writes"]] != scenario[
941
- "allowed_repo_writes"
942
- ]:
943
- return False
944
- elif result["repo_writes"]:
945
- return False
946
- return True
947
-
948
-
949
- def run_cell(
950
- matrix: dict[str, Any],
951
- host: dict[str, Any],
952
- scenario: dict[str, Any],
953
- *,
954
- live: bool,
955
- host_version: str,
956
- model: str,
957
- auth_file: Path | None,
958
- host_artifacts: Path | None,
959
- per_run_cost: Decimal,
960
- fake_fault: str | None,
961
- ) -> dict[str, Any]:
962
- reasons = frozen_reasons()
963
- run_root = Path(tempfile.mkdtemp(prefix="devrites-agent-contract-")).resolve()
964
- project = run_root / "project"
965
- config = run_root / "config"
966
- state = run_root / "state"
967
- home = run_root / "home"
968
- scratch = run_root / "scratch"
969
- tmp = run_root / "tmp"
970
- guard = run_root / "guard"
971
- for path in (config, state, home, scratch, tmp, guard):
972
- path.mkdir(mode=0o700)
973
- (guard / "sentinel").write_text("unchanged\n", encoding="utf-8")
974
-
975
- if auth_file is None:
976
- auth_file = state / f"{host['id']}.auth"
977
- auth_file.write_text("fake-scoped-credential\n", encoding="utf-8")
978
- auth_file.chmod(0o600)
979
- base_env = {
980
- "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
981
- "LANG": "C",
982
- "LC_ALL": "C",
983
- "HOME": str(home),
984
- "TMPDIR": str(tmp),
985
- "XDG_CONFIG_HOME": str(config),
986
- "XDG_STATE_HOME": str(state),
987
- }
988
- try:
989
- seed_project(project, live, host_artifacts, base_env)
990
- packet = make_packet(matrix, host, scenario, project, scratch)
991
- packet_path = scratch / "agent-packet.yaml"
992
- result_path = scratch / "agent-result.yaml"
993
- write_transport(packet_path, packet)
994
-
995
- before_project = tree_snapshot(project)
996
- before_guard = tree_snapshot(guard)
997
- env = {
998
- **base_env,
999
- "DEVRITES_ROOT": str(project),
1000
- "DEVRITES_AGENT_SCRATCH": str(scratch),
1001
- "DEVRITES_RUN_ID": packet["run_id"],
1002
- "DEVRITES_CONTRACT_RUN_ROOT": str(run_root),
1003
- "DEVRITES_CONTRACT_PACKET": str(packet_path),
1004
- "DEVRITES_CONTRACT_RESULT": str(result_path),
1005
- "DEVRITES_CONTRACT_HOST": host["id"],
1006
- "DEVRITES_CONTRACT_HOST_VERSION_PIN": host_version,
1007
- "DEVRITES_CONTRACT_MODEL": model,
1008
- "DEVRITES_CONTRACT_AUTH_FILE": str(auth_file),
1009
- "DEVRITES_CONTRACT_MAX_COST_USD": str(per_run_cost),
1010
- }
1011
- if not live:
1012
- env["DEVRITES_CONTRACT_FAKE_HOST"] = str(FAKE_HOST)
1013
- env["DEVRITES_CONTRACT_FAKE_VERSION"] = host["fake_version"]
1014
- if fake_fault and fake_fault.startswith(packet["cell_id"] + ":"):
1015
- env["DEVRITES_CONTRACT_FAKE_FAULT"] = fake_fault.rsplit(":", 1)[1]
1016
-
1017
- launcher = ROOT / host["launcher"]
1018
- if live and scenario["simulate"] == "interrupted":
1019
- proc_live = subprocess.Popen(
1020
- [str(launcher), "run"],
1021
- env=env,
1022
- stdout=subprocess.DEVNULL,
1023
- stderr=subprocess.DEVNULL,
1024
- start_new_session=True,
1025
- )
1026
- time.sleep(2)
1027
- if proc_live.poll() is None:
1028
- os.killpg(proc_live.pid, signal.SIGINT)
1029
- try:
1030
- exit_code = proc_live.wait(timeout=15)
1031
- except subprocess.TimeoutExpired:
1032
- os.killpg(proc_live.pid, signal.SIGKILL)
1033
- proc_live.wait()
1034
- exit_code = 130
1035
- else:
1036
- try:
1037
- proc = subprocess.run(
1038
- [str(launcher), "run"],
1039
- env=env,
1040
- stdout=subprocess.DEVNULL,
1041
- stderr=subprocess.DEVNULL,
1042
- timeout=matrix["budget"]["timeout_seconds"],
1043
- check=False,
1044
- start_new_session=True,
1045
- )
1046
- exit_code = proc.returncode
1047
- except subprocess.TimeoutExpired:
1048
- exit_code = 130
1049
-
1050
- after_project = tree_snapshot(project)
1051
- actual_delta = tree_delta(before_project, after_project)
1052
- if before_guard != tree_snapshot(guard):
1053
- result = synthetic_reconcile(
1054
- host,
1055
- scenario,
1056
- exit_code,
1057
- "DRV-AGENT-RESULT-OUT-OF-SCOPE",
1058
- "complete",
1059
- host_version,
1060
- model,
1061
- live=live,
1062
- )
1063
- elif not result_path.exists():
1064
- result = synthetic_reconcile(
1065
- host,
1066
- scenario,
1067
- exit_code,
1068
- "DRV-AGENT-UNAVAILABLE",
1069
- "missing",
1070
- host_version,
1071
- model,
1072
- live=live,
1073
- )
1074
- else:
1075
- try:
1076
- raw = load_json(result_path)
1077
- result = validate_result(
1078
- raw,
1079
- packet,
1080
- host,
1081
- scenario,
1082
- exit_code,
1083
- actual_delta,
1084
- host_version,
1085
- model,
1086
- reasons,
1087
- live,
1088
- )
1089
- except ContractError as exc:
1090
- sentinel = {
1091
- "DRV-AGENT-RESULT-STALE": "stale",
1092
- "DRV-AGENT-RESULT-MALFORMED": "malformed",
1093
- "DRV-AGENT-RESULT-IDENTITY-MISMATCH": "malformed",
1094
- "DRV-AGENT-RESULT-OUT-OF-SCOPE": "malformed",
1095
- }.get(exc.reason_id, "malformed")
1096
- result = synthetic_reconcile(
1097
- host,
1098
- scenario,
1099
- exit_code,
1100
- exc.reason_id,
1101
- sentinel,
1102
- host_version,
1103
- model,
1104
- live=live,
1105
- identity_matched=exc.reason_id
1106
- != "DRV-AGENT-RESULT-IDENTITY-MISMATCH",
1107
- baseline_matched=exc.reason_id != "DRV-AGENT-RESULT-STALE",
1108
- )
1109
- if (
1110
- scenario["simulate"] == "interrupted"
1111
- and result["result_reason_id"] == "DRV-AGENT-UNAVAILABLE"
1112
- ):
1113
- result["status"] = "partial"
1114
- result["terminal_sentinel"] = "interrupted"
1115
- result["signals"] = sorted(
1116
- MANDATORY_SIGNALS | {"progress_recorded", "partial_metadata"}
1117
- )
1118
- cost = usage_cost(result["usage"])
1119
- if cost is not None and cost > per_run_cost:
1120
- result["passed"] = False
1121
- result["result_reason_id"] = "DRV-AGENT-UNAVAILABLE"
1122
- return result
1123
- result["passed"] = result_matches_expected(result, scenario)
1124
- return result
1125
- finally:
1126
- shutil.rmtree(run_root, ignore_errors=True)
1127
-
1128
-
1129
- def assert_private_metadata(value: Any) -> None:
1130
- def walk(item: Any) -> None:
1131
- if isinstance(item, dict):
1132
- if FORBIDDEN_RESULT_KEYS.intersection(item):
1133
- raise ContractError("summary contains forbidden retention fields")
1134
- for key, child in item.items():
1135
- if isinstance(key, str):
1136
- if key.lower() in FORBIDDEN_RESULT_KEYS:
1137
- raise ContractError(
1138
- "summary contains a forbidden retention key"
1139
- )
1140
- if private_string_fault(key):
1141
- raise ContractError("summary contains a private metadata key")
1142
- walk(child)
1143
- elif isinstance(item, list):
1144
- for child in item:
1145
- walk(child)
1146
- elif isinstance(item, str):
1147
- fault = private_string_fault(item)
1148
- if fault:
1149
- raise ContractError(f"summary contains a private {fault}")
1150
-
1151
- walk(value)
1152
-
1153
-
1154
- def write_summary(results_dir: Path | None, summary: dict[str, Any]) -> None:
1155
- assert_private_metadata(summary)
1156
- encoded = json.dumps(summary, sort_keys=True, indent=2) + "\n"
1157
- if results_dir is None:
1158
- sys.stdout.write(encoded)
1159
- return
1160
- results_dir.mkdir(parents=True, exist_ok=True)
1161
- target = results_dir / "summary.json"
1162
- temp = results_dir / ".summary.json.tmp"
1163
- temp.write_text(encoded, encoding="utf-8")
1164
- temp.chmod(0o600)
1165
- os.replace(temp, target)
1166
- sys.stdout.write(encoded)
1167
-
1168
-
1169
- def write_ledger(path: Path, month: str, spent: Decimal) -> None:
1170
- updated = {"schema": BUDGET_SCHEMA, "month": month, "spent_usd": str(spent)}
1171
- temp = path.with_name(path.name + ".tmp")
1172
- temp.write_bytes(canonical_json(updated) + b"\n")
1173
- temp.chmod(0o600)
1174
- os.replace(temp, path)
1175
-
1176
-
1177
- def locked_ledger(path: Path) -> tuple[int, Path, dict[str, Any]]:
1178
- lock = path.with_name(path.name + ".lock")
1179
- try:
1180
- descriptor = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
1181
- except FileExistsError as exc:
1182
- raise ContractError(
1183
- "budget ledger is locked by another run", "DRV-AGENT-UNAVAILABLE"
1184
- ) from exc
1185
- try:
1186
- return descriptor, lock, load_budget_ledger(path)
1187
- except Exception:
1188
- os.close(descriptor)
1189
- lock.unlink(missing_ok=True)
1190
- raise
1191
-
1192
-
1193
- def reserve_budget(path: Path, monthly: Decimal, amount: Decimal) -> None:
1194
- descriptor, lock, ledger = locked_ledger(path)
1195
- try:
1196
- spent = money(ledger["spent_usd"], "ledger spent", allow_zero=True)
1197
- if spent + amount > monthly:
1198
- raise ContractError(
1199
- "the full matrix exceeds the monthly ceiling", "DRV-AGENT-UNAVAILABLE"
1200
- )
1201
- write_ledger(path, ledger["month"], spent + amount)
1202
- finally:
1203
- os.close(descriptor)
1204
- lock.unlink(missing_ok=True)
1205
-
1206
-
1207
- def settle_budget(path: Path, reserved: Decimal, actual: Decimal | None) -> None:
1208
- descriptor, lock, ledger = locked_ledger(path)
1209
- try:
1210
- spent = money(ledger["spent_usd"], "ledger spent", allow_zero=True)
1211
- settled = reserved if actual is None else actual
1212
- write_ledger(
1213
- path, ledger["month"], max(Decimal("0"), spent - reserved + settled)
1214
- )
1215
- finally:
1216
- os.close(descriptor)
1217
- lock.unlink(missing_ok=True)
1218
-
1219
-
1220
- def parse_args() -> argparse.Namespace:
1221
- parser = argparse.ArgumentParser(description=__doc__)
1222
- mode = parser.add_mutually_exclusive_group()
1223
- mode.add_argument(
1224
- "--fake",
1225
- action="store_true",
1226
- help="run the deterministic fake-host matrix (default)",
1227
- )
1228
- mode.add_argument(
1229
- "--live",
1230
- action="store_true",
1231
- help="run paid host CLIs after strict manual preflight",
1232
- )
1233
- parser.add_argument("--matrix", type=Path, default=DEFAULT_MATRIX)
1234
- parser.add_argument("--results-dir", type=Path)
1235
- parser.add_argument("--preflight-only", action="store_true")
1236
- parser.add_argument("--confirm-live")
1237
- parser.add_argument(
1238
- "--host-version", action="append", default=[], metavar="HOST=VERSION"
1239
- )
1240
- parser.add_argument(
1241
- "--host-model", action="append", default=[], metavar="HOST=MODEL"
1242
- )
1243
- parser.add_argument("--auth-file", action="append", default=[], metavar="HOST=PATH")
1244
- parser.add_argument("--host-artifacts", type=Path)
1245
- parser.add_argument("--max-cost-per-run-usd")
1246
- parser.add_argument("--max-monthly-cost-usd")
1247
- parser.add_argument("--budget-ledger", type=Path)
1248
- parser.add_argument(
1249
- "--fake-fault",
1250
- help="fake-only fault injection as HOST:SCENARIO:identity-mismatch|escape-write|privacy-leak|cost-overrun",
1251
- )
1252
- return parser.parse_args()
1253
-
1254
-
1255
- def main() -> int:
1256
- args = parse_args()
1257
- try:
1258
- matrix = validate_matrix(load_json(args.matrix))
1259
- matrix_digest = sha256_bytes(canonical_json(matrix))
1260
- host_rows = {row["id"]: row for row in matrix["hosts"]}
1261
- cells = len(matrix["hosts"]) * len(matrix["scenarios"])
1262
- live = bool(args.live)
1263
- live_info: dict[str, dict[str, str]] = {}
1264
- ledger_path: Path | None = None
1265
- monthly = Decimal("0")
1266
- if live:
1267
- if args.results_dir is None:
1268
- raise ContractError(
1269
- "live mode requires --results-dir", "DRV-AGENT-UNAVAILABLE"
1270
- )
1271
- live_info, per_run, monthly, ledger_path = live_preflight(args, matrix)
1272
- else:
1273
- if any(
1274
- [
1275
- args.confirm_live,
1276
- args.host_version,
1277
- args.host_model,
1278
- args.auth_file,
1279
- args.host_artifacts,
1280
- args.max_cost_per_run_usd,
1281
- args.max_monthly_cost_usd,
1282
- args.budget_ledger,
1283
- ]
1284
- ):
1285
- raise ContractError("live-only options cannot be used in fake mode")
1286
- per_run = money(
1287
- matrix["budget"]["estimated_max_cost_usd_per_cell"],
1288
- "matrix estimated cost",
1289
- )
1290
- for host_id, row in host_rows.items():
1291
- actual = launcher_version(
1292
- row,
1293
- True,
1294
- "version-mismatch"
1295
- if args.fake_fault == f"{host_id}:version:version-mismatch"
1296
- else None,
1297
- )
1298
- if actual != row["fake_version"]:
1299
- raise ContractError(
1300
- "fake host version pin mismatch", "DRV-AGENT-UNAVAILABLE"
1301
- )
1302
- live_info[host_id] = {
1303
- "version": actual,
1304
- "model": f"{host_id}-contract-fake",
1305
- "auth_file": "",
1306
- }
1307
-
1308
- if args.preflight_only:
1309
- summary = {
1310
- "schema": PREFLIGHT_SCHEMA,
1311
- "mode": "live" if live else "fake",
1312
- "matrix_digest": matrix_digest,
1313
- "cells": cells,
1314
- "hosts": [
1315
- {
1316
- "host": host_id,
1317
- "version": live_info[host_id]["version"],
1318
- "model": live_info[host_id]["model"],
1319
- }
1320
- for host_id in sorted(host_rows)
1321
- ],
1322
- "max_live_cost_usd": str(per_run * Decimal(cells)),
1323
- "monthly_ceiling_usd": str(monthly) if live else "0",
1324
- "ready": True,
1325
- }
1326
- write_summary(args.results_dir, summary)
1327
- return 0
1328
-
1329
- results: list[dict[str, Any]] = []
1330
- reservation = per_run * Decimal(cells)
1331
- if live and ledger_path is not None:
1332
- reserve_budget(ledger_path, monthly, reservation)
1333
- for host in matrix["hosts"]:
1334
- info = live_info[host["id"]]
1335
- auth = Path(info["auth_file"]) if live else None
1336
- for scenario in matrix["scenarios"]:
1337
- result = run_cell(
1338
- matrix,
1339
- host,
1340
- scenario,
1341
- live=live,
1342
- host_version=info["version"],
1343
- model=info["model"],
1344
- auth_file=auth,
1345
- host_artifacts=args.host_artifacts,
1346
- per_run_cost=per_run,
1347
- fake_fault=args.fake_fault,
1348
- )
1349
- results.append(result)
1350
-
1351
- observed_costs = [usage_cost(row["usage"]) for row in results]
1352
- total_cost = (
1353
- sum((cost for cost in observed_costs if cost is not None), Decimal("0"))
1354
- if all(cost is not None for cost in observed_costs)
1355
- else None
1356
- )
1357
- summary = {
1358
- "schema": SUMMARY_SCHEMA,
1359
- "mode": "live" if live else "fake",
1360
- "matrix_digest": matrix_digest,
1361
- "cells_total": len(results),
1362
- "cells_passed": sum(1 for row in results if row["passed"]),
1363
- "cells_failed": sum(1 for row in results if not row["passed"]),
1364
- "cost_usd": str(total_cost) if total_cost is not None else None,
1365
- "reason_counts": dict(
1366
- sorted(Counter(row["result_reason_id"] for row in results).items())
1367
- ),
1368
- "results": results,
1369
- }
1370
- write_summary(args.results_dir, summary)
1371
- if live and ledger_path is not None:
1372
- settle_budget(ledger_path, reservation, total_cost)
1373
- return 0 if summary["cells_failed"] == 0 else 1
1374
- except ContractError as exc:
1375
- sys.stderr.write(f"agent-contract preflight failed: {exc.reason_id}: {exc}\n")
1376
- return 2
1377
-
1378
-
1379
- if __name__ == "__main__":
1380
- sys.exit(main())