devrites 7.2.7 → 7.3.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 (340) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +6 -1
  3. package/docs/candidate-integrity.md +10 -0
  4. package/docs/cli.md +7 -1
  5. package/docs/command-map.md +9 -2
  6. package/docs/engine/commands.md +50 -4
  7. package/docs/orchestration.md +35 -13
  8. package/docs/skills.md +5 -0
  9. package/engine/internal/acceptance/cli.go +35 -30
  10. package/engine/internal/acceptance/preconditions.go +1 -1
  11. package/engine/internal/acceptance/run.go +138 -53
  12. package/engine/internal/acceptance/transcript.go +236 -45
  13. package/engine/internal/devritespaths/paths.go +23 -5
  14. package/engine/internal/gate/gate.go +6 -0
  15. package/engine/internal/install/apply.go +5 -0
  16. package/engine/internal/install/merge.go +48 -6
  17. package/engine/internal/install/preflight.go +21 -3
  18. package/engine/internal/install/uninstall.go +9 -10
  19. package/engine/internal/lib/apply_bundle.go +7 -1
  20. package/engine/internal/lib/candidate.go +20 -7
  21. package/engine/internal/lib/candidate_coverage.go +2 -19
  22. package/engine/internal/lib/closeout.go +22 -10
  23. package/engine/internal/lib/context.go +67 -47
  24. package/engine/internal/lib/context_io.go +171 -0
  25. package/engine/internal/lib/detect.go +8 -8
  26. package/engine/internal/lib/dispatch.go +6 -0
  27. package/engine/internal/lib/packet.go +37 -7
  28. package/engine/internal/lib/packet_freeze.go +60 -12
  29. package/engine/internal/lib/packet_integrity.go +179 -0
  30. package/engine/internal/lib/perturb.go +3 -3
  31. package/engine/internal/lib/resolve.go +10 -5
  32. package/engine/internal/lib/slicecheck.go +7 -1
  33. package/engine/internal/lib/state_edit.go +9 -2
  34. package/engine/internal/lib/taskgraph.go +20 -36
  35. package/engine/internal/lib/triggers.go +30 -31
  36. package/engine/internal/lib/workspace_pool.go +330 -0
  37. package/engine/internal/parallel/cli.go +190 -49
  38. package/engine/internal/parallel/git.go +47 -14
  39. package/engine/internal/parallel/landing.go +232 -0
  40. package/engine/internal/parallel/lease.go +97 -18
  41. package/engine/internal/parallel/ops.go +312 -155
  42. package/engine/internal/parallel/pathdisjoint.go +48 -4
  43. package/engine/internal/parallel/prune.go +133 -0
  44. package/engine/internal/parallel/repository.go +239 -0
  45. package/engine/internal/parallel/select.go +64 -42
  46. package/engine/internal/state/cursor.go +5 -5
  47. package/engine/main.go +2 -1
  48. package/pack/.claude/agents/devrites-proof-runner.md +16 -9
  49. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  50. package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  51. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  52. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  53. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  54. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +7 -3
  55. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  56. package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  57. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  58. package/pack/.claude/skills/rite-autocomplete/SKILL.md +13 -10
  59. package/pack/.claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  60. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +13 -9
  61. package/pack/.claude/skills/rite-build/SKILL.md +13 -10
  62. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +1 -1
  63. package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
  64. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +60 -39
  65. package/pack/.claude/skills/rite-build/reference/phase-contract.md +8 -5
  66. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  67. package/pack/.claude/skills/rite-clarify/SKILL.md +2 -2
  68. package/pack/.claude/skills/rite-converge/SKILL.md +4 -4
  69. package/pack/.claude/skills/rite-define/SKILL.md +7 -3
  70. package/pack/.claude/skills/rite-define/reference/plan-template.md +4 -2
  71. package/pack/.claude/skills/rite-plan/SKILL.md +11 -8
  72. package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  73. package/pack/.claude/skills/rite-plan/reference/slicing.md +2 -0
  74. package/pack/.claude/skills/rite-polish/SKILL.md +3 -3
  75. package/pack/.claude/skills/rite-prove/SKILL.md +9 -5
  76. package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
  77. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  78. package/pack/.claude/skills/rite-review/SKILL.md +4 -3
  79. package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  80. package/pack/.claude/skills/rite-seal/SKILL.md +1 -1
  81. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +1 -1
  82. package/pack/.claude/skills/rite-ship/SKILL.md +2 -2
  83. package/pack/.claude/skills/rite-ship/reference/git-ship.md +2 -2
  84. package/pack/.claude/skills/rite-spec/SKILL.md +8 -5
  85. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +5 -0
  86. package/pack/.claude/skills/rite-temper/SKILL.md +3 -3
  87. package/pack/.claude/skills/rite-vet/SKILL.md +9 -5
  88. package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -5
  89. package/pack/generated/claude/agents/devrites-proof-runner.md +16 -9
  90. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  91. package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  92. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  93. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  94. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  95. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +7 -3
  96. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  97. package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  98. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  99. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +13 -10
  100. package/pack/generated/claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  101. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +13 -9
  102. package/pack/generated/claude/skills/rite-build/SKILL.md +13 -10
  103. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +1 -1
  104. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
  105. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +60 -39
  106. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +8 -5
  107. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  108. package/pack/generated/claude/skills/rite-clarify/SKILL.md +2 -2
  109. package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -4
  110. package/pack/generated/claude/skills/rite-define/SKILL.md +7 -3
  111. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +4 -2
  112. package/pack/generated/claude/skills/rite-plan/SKILL.md +11 -8
  113. package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  114. package/pack/generated/claude/skills/rite-plan/reference/slicing.md +2 -0
  115. package/pack/generated/claude/skills/rite-polish/SKILL.md +3 -3
  116. package/pack/generated/claude/skills/rite-prove/SKILL.md +9 -5
  117. package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
  118. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  119. package/pack/generated/claude/skills/rite-review/SKILL.md +4 -3
  120. package/pack/generated/claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  121. package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -1
  122. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +1 -1
  123. package/pack/generated/claude/skills/rite-ship/SKILL.md +2 -2
  124. package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +2 -2
  125. package/pack/generated/claude/skills/rite-spec/SKILL.md +8 -5
  126. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +5 -0
  127. package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -3
  128. package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -5
  129. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -5
  130. package/pack/generated/codex/agents/devrites-proof-runner.toml +16 -9
  131. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  132. package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  133. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  134. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  135. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  136. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +7 -3
  137. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  138. package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +18 -9
  139. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  140. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +13 -10
  141. package/pack/generated/codex/skills/rite-autocomplete/reference/lookahead.md +4 -4
  142. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +13 -9
  143. package/pack/generated/codex/skills/rite-build/SKILL.md +13 -10
  144. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +1 -1
  145. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
  146. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +60 -39
  147. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +8 -5
  148. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -7
  149. package/pack/generated/codex/skills/rite-clarify/SKILL.md +2 -2
  150. package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
  151. package/pack/generated/codex/skills/rite-define/SKILL.md +7 -3
  152. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +4 -2
  153. package/pack/generated/codex/skills/rite-plan/SKILL.md +11 -8
  154. package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +9 -2
  155. package/pack/generated/codex/skills/rite-plan/reference/slicing.md +2 -0
  156. package/pack/generated/codex/skills/rite-polish/SKILL.md +3 -3
  157. package/pack/generated/codex/skills/rite-prove/SKILL.md +9 -5
  158. package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
  159. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -0
  160. package/pack/generated/codex/skills/rite-review/SKILL.md +4 -3
  161. package/pack/generated/codex/skills/rite-review/reference/feature-scoped-review.md +2 -2
  162. package/pack/generated/codex/skills/rite-seal/SKILL.md +1 -1
  163. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +1 -1
  164. package/pack/generated/codex/skills/rite-ship/SKILL.md +2 -2
  165. package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +2 -2
  166. package/pack/generated/codex/skills/rite-spec/SKILL.md +8 -5
  167. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +5 -0
  168. package/pack/generated/codex/skills/rite-temper/SKILL.md +3 -3
  169. package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -5
  170. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -5
  171. package/pack/generated/devin/agents/devrites-proof-runner.md +16 -9
  172. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  173. package/pack/generated/devin/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  174. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  175. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  176. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  177. package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +7 -3
  178. package/pack/generated/devin/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  179. package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +18 -9
  180. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  181. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +13 -10
  182. package/pack/generated/devin/skills/rite-autocomplete/reference/lookahead.md +4 -4
  183. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +13 -9
  184. package/pack/generated/devin/skills/rite-build/SKILL.md +13 -10
  185. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +1 -1
  186. package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
  187. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +60 -39
  188. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +8 -5
  189. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +7 -7
  190. package/pack/generated/devin/skills/rite-clarify/SKILL.md +2 -2
  191. package/pack/generated/devin/skills/rite-converge/SKILL.md +4 -4
  192. package/pack/generated/devin/skills/rite-define/SKILL.md +7 -3
  193. package/pack/generated/devin/skills/rite-define/reference/plan-template.md +4 -2
  194. package/pack/generated/devin/skills/rite-plan/SKILL.md +11 -8
  195. package/pack/generated/devin/skills/rite-plan/reference/dependency-graph.md +9 -2
  196. package/pack/generated/devin/skills/rite-plan/reference/slicing.md +2 -0
  197. package/pack/generated/devin/skills/rite-polish/SKILL.md +3 -3
  198. package/pack/generated/devin/skills/rite-prove/SKILL.md +9 -5
  199. package/pack/generated/devin/skills/rite-resolve/SKILL.md +3 -3
  200. package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +2 -0
  201. package/pack/generated/devin/skills/rite-review/SKILL.md +4 -3
  202. package/pack/generated/devin/skills/rite-review/reference/feature-scoped-review.md +2 -2
  203. package/pack/generated/devin/skills/rite-seal/SKILL.md +1 -1
  204. package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +1 -1
  205. package/pack/generated/devin/skills/rite-ship/SKILL.md +2 -2
  206. package/pack/generated/devin/skills/rite-ship/reference/git-ship.md +2 -2
  207. package/pack/generated/devin/skills/rite-spec/SKILL.md +8 -5
  208. package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +5 -0
  209. package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
  210. package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -5
  211. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +12 -5
  212. package/pack/generated/dsh/agents/devrites-proof-runner.md +16 -9
  213. package/pack/generated/dsh/runtime/cordis.patch.yml +1 -1
  214. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  215. package/pack/generated/dsh/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  216. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  217. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  218. package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  219. package/pack/generated/dsh/skills/devrites-lib/reference/standards/agents.md +7 -3
  220. package/pack/generated/dsh/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  221. package/pack/generated/dsh/skills/devrites-lib/reference/standards/gates.md +18 -9
  222. package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  223. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +13 -10
  224. package/pack/generated/dsh/skills/rite-autocomplete/reference/lookahead.md +4 -4
  225. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +13 -9
  226. package/pack/generated/dsh/skills/rite-build/SKILL.md +13 -10
  227. package/pack/generated/dsh/skills/rite-build/reference/afk-discipline.md +1 -1
  228. package/pack/generated/dsh/skills/rite-build/reference/checkpoint.md +1 -1
  229. package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +60 -39
  230. package/pack/generated/dsh/skills/rite-build/reference/phase-contract.md +8 -5
  231. package/pack/generated/dsh/skills/rite-build/reference/wright-dispatch.md +7 -7
  232. package/pack/generated/dsh/skills/rite-clarify/SKILL.md +2 -2
  233. package/pack/generated/dsh/skills/rite-converge/SKILL.md +4 -4
  234. package/pack/generated/dsh/skills/rite-define/SKILL.md +7 -3
  235. package/pack/generated/dsh/skills/rite-define/reference/plan-template.md +4 -2
  236. package/pack/generated/dsh/skills/rite-plan/SKILL.md +11 -8
  237. package/pack/generated/dsh/skills/rite-plan/reference/dependency-graph.md +9 -2
  238. package/pack/generated/dsh/skills/rite-plan/reference/slicing.md +2 -0
  239. package/pack/generated/dsh/skills/rite-polish/SKILL.md +3 -3
  240. package/pack/generated/dsh/skills/rite-prove/SKILL.md +9 -5
  241. package/pack/generated/dsh/skills/rite-resolve/SKILL.md +3 -3
  242. package/pack/generated/dsh/skills/rite-resolve/reference/answer-protocol.md +2 -0
  243. package/pack/generated/dsh/skills/rite-review/SKILL.md +4 -3
  244. package/pack/generated/dsh/skills/rite-review/reference/feature-scoped-review.md +2 -2
  245. package/pack/generated/dsh/skills/rite-seal/SKILL.md +1 -1
  246. package/pack/generated/dsh/skills/rite-seal/reference/phase-contract.md +1 -1
  247. package/pack/generated/dsh/skills/rite-ship/SKILL.md +2 -2
  248. package/pack/generated/dsh/skills/rite-ship/reference/git-ship.md +2 -2
  249. package/pack/generated/dsh/skills/rite-spec/SKILL.md +8 -5
  250. package/pack/generated/dsh/skills/rite-spec/reference/state-workspace.md +5 -0
  251. package/pack/generated/dsh/skills/rite-temper/SKILL.md +3 -3
  252. package/pack/generated/dsh/skills/rite-vet/SKILL.md +9 -5
  253. package/pack/generated/dsh/skills/rite-vet/reference/review-axes.md +12 -5
  254. package/pack/generated/omp/agents/devrites-proof-runner.md +16 -9
  255. package/pack/generated/omp/commands/rite-build.md +1 -1
  256. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  257. package/pack/generated/omp/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  258. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  259. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  260. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  261. package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +11 -3
  262. package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  263. package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +18 -9
  264. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  265. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +13 -10
  266. package/pack/generated/omp/skills/rite-autocomplete/reference/lookahead.md +4 -4
  267. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +13 -9
  268. package/pack/generated/omp/skills/rite-build/SKILL.md +13 -10
  269. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +1 -1
  270. package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
  271. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +60 -39
  272. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +8 -5
  273. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +7 -7
  274. package/pack/generated/omp/skills/rite-clarify/SKILL.md +2 -2
  275. package/pack/generated/omp/skills/rite-converge/SKILL.md +4 -4
  276. package/pack/generated/omp/skills/rite-define/SKILL.md +7 -3
  277. package/pack/generated/omp/skills/rite-define/reference/plan-template.md +4 -2
  278. package/pack/generated/omp/skills/rite-plan/SKILL.md +11 -8
  279. package/pack/generated/omp/skills/rite-plan/reference/dependency-graph.md +9 -2
  280. package/pack/generated/omp/skills/rite-plan/reference/slicing.md +2 -0
  281. package/pack/generated/omp/skills/rite-polish/SKILL.md +3 -3
  282. package/pack/generated/omp/skills/rite-prove/SKILL.md +9 -5
  283. package/pack/generated/omp/skills/rite-resolve/SKILL.md +3 -3
  284. package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +2 -0
  285. package/pack/generated/omp/skills/rite-review/SKILL.md +4 -3
  286. package/pack/generated/omp/skills/rite-review/reference/feature-scoped-review.md +2 -2
  287. package/pack/generated/omp/skills/rite-seal/SKILL.md +1 -1
  288. package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +1 -1
  289. package/pack/generated/omp/skills/rite-ship/SKILL.md +2 -2
  290. package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
  291. package/pack/generated/omp/skills/rite-spec/SKILL.md +8 -5
  292. package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +5 -0
  293. package/pack/generated/omp/skills/rite-temper/SKILL.md +3 -3
  294. package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -5
  295. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +12 -5
  296. package/pack/generated/pi/agents/devrites-proof-runner.md +16 -9
  297. package/pack/generated/pi/prompts/rite-build.md +1 -1
  298. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  299. package/pack/generated/pi/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  300. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  301. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  302. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  303. package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +7 -3
  304. package/pack/generated/pi/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  305. package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +18 -9
  306. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  307. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +13 -10
  308. package/pack/generated/pi/skills/rite-autocomplete/reference/lookahead.md +4 -4
  309. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +13 -9
  310. package/pack/generated/pi/skills/rite-build/SKILL.md +13 -10
  311. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +1 -1
  312. package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
  313. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +60 -39
  314. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +8 -5
  315. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +7 -7
  316. package/pack/generated/pi/skills/rite-clarify/SKILL.md +2 -2
  317. package/pack/generated/pi/skills/rite-converge/SKILL.md +4 -4
  318. package/pack/generated/pi/skills/rite-define/SKILL.md +7 -3
  319. package/pack/generated/pi/skills/rite-define/reference/plan-template.md +4 -2
  320. package/pack/generated/pi/skills/rite-plan/SKILL.md +11 -8
  321. package/pack/generated/pi/skills/rite-plan/reference/dependency-graph.md +9 -2
  322. package/pack/generated/pi/skills/rite-plan/reference/slicing.md +2 -0
  323. package/pack/generated/pi/skills/rite-polish/SKILL.md +3 -3
  324. package/pack/generated/pi/skills/rite-prove/SKILL.md +9 -5
  325. package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
  326. package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +2 -0
  327. package/pack/generated/pi/skills/rite-review/SKILL.md +4 -3
  328. package/pack/generated/pi/skills/rite-review/reference/feature-scoped-review.md +2 -2
  329. package/pack/generated/pi/skills/rite-seal/SKILL.md +1 -1
  330. package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +1 -1
  331. package/pack/generated/pi/skills/rite-ship/SKILL.md +2 -2
  332. package/pack/generated/pi/skills/rite-ship/reference/git-ship.md +2 -2
  333. package/pack/generated/pi/skills/rite-spec/SKILL.md +8 -5
  334. package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +5 -0
  335. package/pack/generated/pi/skills/rite-temper/SKILL.md +3 -3
  336. package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -5
  337. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +12 -5
  338. package/package.json +1 -1
  339. package/scripts/build-host-artifacts.sh +5 -6
  340. package/scripts/omp-generate.sh +24 -14
@@ -28,10 +28,11 @@ type GateResult struct {
28
28
  Ran bool
29
29
  Passed bool
30
30
  Detail string // exit/match/timeout/approval diagnostic
31
- Discarded bool // result dropped: ledger changed under the run
31
+ Discarded bool // result dropped: bound oracle or inputs changed during the run
32
32
  WriteErr error // evidence writeback failed for a reason other than drift
33
- Reused bool // met gate kept because its INPUTS are unchanged
33
+ Reused bool // met gate kept under the selected reuse policy
34
34
  Inputs string // INPUTS content digest at run time, when declared
35
+ Origin string // complete executed transcript retained when proof is reused
35
36
  // Outcome, Started and Ended describe the execution this result judged;
36
37
  // they are zero when the gate did not execute.
37
38
  Outcome RunOutcome
@@ -63,27 +64,52 @@ func ReadLedgerFile(ledgerPath string) (string, error) {
63
64
  return string(raw), nil
64
65
  }
65
66
 
66
- // RunGates executes the runnable gates of ledgerPath. reverify re-runs every
67
- // runnable gate including already-met ones; the normal mode runs only unmet
68
- // runnable gates. approved is the vetted command set from test-plan.md; a
67
+ // RunOptions controls evidence reuse and short ledger writeback locking.
68
+ // TranscriptWork requires a usable executed origin before a met gate can skip.
69
+ // The caller holds the gates execution lock through transcript publication.
70
+ type RunOptions struct {
71
+ WriteLock func(func() error) error
72
+ TranscriptWork string
73
+ }
74
+
75
+ // RunGates executes the runnable gates of ledgerPath. reverify also runs met
76
+ // gates without INPUTS; met gates with unchanged INPUTS reuse their proof in
77
+ // either mode, subject to options.TranscriptWork's executed-origin requirement.
78
+ // approved is the vetted command set from test-plan.md; a
69
79
  // CHECK outside it never executes, and neither does one outside a surface that
70
80
  // could not be determined — an absent preflight is a refusal, not permission.
71
81
  // Each pass writes definition-bound evidence; each failure clears the box and
72
82
  // restores EVIDENCE: pending.
73
83
  //
74
84
  // Gates sharing one CHECK and CWD execute once per call; each matches its own
75
- // EXPECT against that shared output. writeLock, when non-nil, wraps every
85
+ // EXPECT against that shared output. options.WriteLock, when non-nil, wraps every
76
86
  // ledger writeback so the caller need not hold the feature lock while checks
77
87
  // execute; writeResult re-verifies the gate's oracle before it writes.
78
- func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, approved []ApprovedCommand, reverify bool, timeout time.Duration, writeLock func(func() error) error) []GateResult {
88
+ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, approved []ApprovedCommand, reverify bool, timeout time.Duration, options *RunOptions) []GateResult {
89
+ var writeLock func(func() error) error
90
+ transcriptWork := ""
91
+ if options != nil {
92
+ writeLock, transcriptWork = options.WriteLock, options.TranscriptWork
93
+ }
79
94
  var results []GateResult
80
- outcomes := map[string]RunOutcome{}
81
- spans := map[string][2]time.Time{}
82
- write := func(gate *Gate, evidence string, checked bool) error {
83
- if writeLock == nil {
84
- return writeResult(ledgerPath, gate, evidence, checked)
95
+ executions := map[string]struct {
96
+ ran bool
97
+ outcome RunOutcome
98
+ span [2]time.Time
99
+ inputs map[string]string
100
+ }{}
101
+ // Collect every dependent INPUTS spec before any shared command can run.
102
+ for _, gate := range doc.Gates {
103
+ if gate.Check == "" || gate.Inputs == "" {
104
+ continue
85
105
  }
86
- return writeLock(func() error { return writeResult(ledgerPath, gate, evidence, checked) })
106
+ key := gate.Check + "\x00" + filepath.Join(root, filepath.FromSlash(gate.Cwd))
107
+ execution := executions[key]
108
+ if execution.inputs == nil {
109
+ execution.inputs = map[string]string{}
110
+ }
111
+ execution.inputs[gate.Inputs] = ""
112
+ executions[key] = execution
87
113
  }
88
114
  for _, gate := range doc.Gates {
89
115
  if _, abandoned := doc.Abandoned[gate.ID]; abandoned {
@@ -94,24 +120,35 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
94
120
  results = append(results, GateResult{ID: gate.ID, Detail: "manual gate; evidence is judged, not executed"})
95
121
  continue
96
122
  }
97
- inputs := ""
98
- if gate.Inputs != "" {
99
- // An unreadable or unmatched spec yields "", which never matches, so the gate re-executes.
100
- inputs, _ = InputsDigest(root, gate.Inputs)
123
+ isApproved := Approved(gate.Check, gate.Cwd, approved)
124
+ if transcriptWork != "" && !isApproved {
125
+ results = append(results, GateResult{ID: gate.ID, Detail: "CHECK/CWD is not an approved test-plan.md preflight row; not executed"})
126
+ continue
101
127
  }
128
+ inputs := ""
102
129
  if GateState(gate, doc) == StateMet {
103
- if gate.Inputs == "" && !reverify {
104
- results = append(results, GateResult{ID: gate.ID, Passed: true, Detail: "already met"})
105
- continue
130
+ canReuse := gate.Inputs == "" && !reverify
131
+ if gate.Inputs != "" {
132
+ // An unreadable or unmatched spec never matches recorded evidence.
133
+ inputs, _ = InputsDigest(root, gate.Inputs)
134
+ canReuse = inputs != "" && inputs == recordedInputs(gate)
106
135
  }
107
- // A gate that declares INPUTS is reused, under run and reverify alike,
108
- // exactly while the files it depends on are byte-identical; any change re-executes it.
109
- if inputs != "" && inputs == recordedInputs(gate) {
110
- results = append(results, GateResult{ID: gate.ID, Passed: true, Reused: true, Inputs: inputs, Detail: "reused: inputs unchanged"})
111
- continue
136
+ if canReuse {
137
+ origin := ""
138
+ if transcriptWork != "" {
139
+ origin = passingOrigin(transcriptWork, gate)
140
+ }
141
+ if transcriptWork == "" || origin != "" {
142
+ detail := "already met"
143
+ if gate.Inputs != "" {
144
+ detail = "reused: inputs unchanged"
145
+ }
146
+ results = append(results, GateResult{ID: gate.ID, Passed: true, Reused: gate.Inputs != "" || origin != "", Inputs: inputs, Origin: origin, Detail: detail})
147
+ continue
148
+ }
112
149
  }
113
150
  }
114
- if !Approved(gate.Check, gate.Cwd, approved) {
151
+ if !isApproved {
115
152
  results = append(results, GateResult{ID: gate.ID, Detail: "CHECK/CWD is not an approved test-plan.md preflight row; not executed"})
116
153
  continue
117
154
  }
@@ -120,36 +157,84 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
120
157
  results = append(results, GateResult{ID: gate.ID, Detail: err.Error()})
121
158
  continue
122
159
  }
123
- cwd := root
124
- if gate.Cwd != "" {
125
- cwd = filepath.Join(root, filepath.FromSlash(gate.Cwd))
126
- }
160
+ cwd := filepath.Join(root, filepath.FromSlash(gate.Cwd))
127
161
  key := gate.Check + "\x00" + cwd
128
- outcome, ran := outcomes[key]
129
- if !ran {
162
+ execution := executions[key]
163
+ if !execution.ran {
164
+ for spec := range execution.inputs {
165
+ execution.inputs[spec], _ = InputsDigest(root, spec)
166
+ }
130
167
  started := time.Now().UTC()
131
- outcome = Execute(ctx, gate.Check, cwd, timeout)
132
- outcomes[key] = outcome
133
- spans[key] = [2]time.Time{started, time.Now().UTC()}
134
- }
135
- span := spans[key]
136
- if outcome.Err == nil && outcome.ExitCode == 0 && expectation.Matches(outcome.Output) {
137
- evidence := withInputs(FormatEvidence(gate, OutputDigest(outcome.Output), outcome.OutputBytes, time.Now().UTC().Format(time.RFC3339)), inputs)
138
- writeErr := write(gate, evidence, true)
139
- results = append(results, GateResult{
140
- ID: gate.ID, Ran: true, Passed: true,
141
- Discarded: errors.Is(writeErr, ErrOracleMoved), WriteErr: writeErr,
142
- Detail: resultDetail(writeErr, "pass"),
143
- Inputs: inputs, Outcome: outcome, Started: span[0], Ended: span[1],
144
- })
168
+ execution.outcome = Execute(ctx, gate.Check, cwd, timeout)
169
+ execution.span = [2]time.Time{started, time.Now().UTC()}
170
+ for spec, before := range execution.inputs {
171
+ after, _ := InputsDigest(root, spec)
172
+ if after != before {
173
+ execution.inputs[spec] = ""
174
+ }
175
+ }
176
+ execution.ran = true
177
+ executions[key] = execution
178
+ }
179
+ outcome := execution.outcome
180
+ inputs = execution.inputs[gate.Inputs]
181
+ passed := outcome.Err == nil && outcome.ExitCode == 0 && expectation.Matches(outcome.Output)
182
+ detail := "pass"
183
+ if !passed {
184
+ detail = failDetail(outcome, expectation)
185
+ }
186
+ write := func() error {
187
+ if passed && gate.Inputs != "" {
188
+ // Earlier writebacks may have moved files since execution.
189
+ current, _ := InputsDigest(root, gate.Inputs)
190
+ if inputs == "" || current != inputs {
191
+ passed = false
192
+ detail = "INPUTS changed or could not be read during the run"
193
+ }
194
+ }
195
+ evidence := "pending"
196
+ if passed {
197
+ evidence = withInputs(FormatEvidence(gate, OutputDigest(outcome.Output), outcome.OutputBytes, time.Now().UTC().Format(time.RFC3339)), inputs)
198
+ }
199
+ return writeResult(ledgerPath, gate, evidence, passed)
200
+ }
201
+ var writeErr error
202
+ if writeLock == nil {
203
+ writeErr = write()
145
204
  } else {
146
- writeErr := write(gate, "pending", false)
147
- results = append(results, GateResult{
148
- ID: gate.ID, Ran: true,
149
- Discarded: errors.Is(writeErr, ErrOracleMoved), WriteErr: writeErr,
150
- Detail: resultDetail(writeErr, failDetail(outcome, expectation)),
151
- Inputs: inputs, Outcome: outcome, Started: span[0], Ended: span[1],
152
- })
205
+ writeErr = writeLock(write)
206
+ }
207
+ results = append(results, GateResult{
208
+ ID: gate.ID, Ran: true, Passed: passed,
209
+ Discarded: errors.Is(writeErr, ErrOracleMoved), WriteErr: writeErr,
210
+ Detail: resultDetail(writeErr, detail),
211
+ Inputs: inputs, Outcome: outcome, Started: execution.span[0], Ended: execution.span[1],
212
+ })
213
+ }
214
+ // Later checks may invalidate an earlier execution or reused pass.
215
+ for i, gate := range doc.Gates {
216
+ result := &results[i]
217
+ if !result.Passed || result.Discarded || result.WriteErr != nil || gate.Inputs == "" {
218
+ continue
219
+ }
220
+ validate := func() error {
221
+ current, _ := InputsDigest(root, gate.Inputs)
222
+ if current != "" && current == result.Inputs {
223
+ return nil
224
+ }
225
+ result.Passed, result.Reused, result.Discarded = false, false, true
226
+ result.Detail = "INPUTS changed or could not be read after a later check"
227
+ return writeResult(ledgerPath, gate, "pending", false)
228
+ }
229
+ var err error
230
+ if writeLock == nil {
231
+ err = validate()
232
+ } else {
233
+ err = writeLock(validate)
234
+ }
235
+ if err != nil {
236
+ result.WriteErr = err
237
+ result.Detail = resultDetail(err, result.Detail)
153
238
  }
154
239
  }
155
240
  return results
@@ -1,13 +1,17 @@
1
1
  package acceptance
2
2
 
3
3
  import (
4
+ "errors"
4
5
  "fmt"
6
+ "io"
5
7
  "os"
6
8
  "path/filepath"
7
9
  "regexp"
10
+ "strconv"
8
11
  "strings"
9
12
  "time"
10
13
 
14
+ "github.com/devrites/devrites/internal/fsutil"
11
15
  "github.com/devrites/devrites/internal/secretpattern"
12
16
  )
13
17
 
@@ -19,7 +23,11 @@ var CandidateDigest func(root, slug string) (string, error)
19
23
  // maxTranscriptOutput caps the redacted output a transcript keeps.
20
24
  const maxTranscriptOutput = 1 << 20
21
25
 
26
+ // Redaction can expand short credentials in metadata as well as output.
27
+ const maxTranscriptBytes = 2*MaxLedgerBytes + maxTranscriptOutput + (64 << 10)
28
+
22
29
  var unsafeNameRE = regexp.MustCompile(`[^A-Za-z0-9._-]`)
30
+ var transcriptDigestRE = regexp.MustCompile(`^[0-9a-f]{64}$`)
23
31
 
24
32
  func candidateLabel(root, slug string) string {
25
33
  if CandidateDigest == nil {
@@ -27,77 +35,260 @@ func candidateLabel(root, slug string) string {
27
35
  }
28
36
  digest, err := CandidateDigest(root, slug)
29
37
  if err != nil {
30
- return "unavailable: " + oneLine(err.Error())
38
+ return "unavailable: " + transcriptMetadata(err.Error())
31
39
  }
32
40
  return digest
33
41
  }
34
42
 
35
- // writeTranscripts records one immutable file per executed or reused gate under
36
- // <work>/references/gates/<gate-id>/, binding the exact command, cwd, attempt,
37
- // exit, EXPECT verdict and redacted output to the candidate identity read
38
- // before and after the run. OUTPUT_SHA256 equals the ledger evidence digest.
43
+ func transcriptMetadata(value string) string {
44
+ redacted, _ := secretpattern.Redact(oneLine(value))
45
+ return redacted
46
+ }
47
+
48
+ func transcriptGateName(id string) string {
49
+ name := unsafeNameRE.ReplaceAllString(transcriptMetadata(id), "_")
50
+ if name == "." || name == ".." || name == "" {
51
+ return "_"
52
+ }
53
+ return name
54
+ }
55
+
56
+ // transcriptDirectory opens each directory without accepting symlink parents.
57
+ // Holding the resulting root also confines publication if a parent is renamed.
58
+ func transcriptDirectory(work, id string, create bool) (*os.Root, error) {
59
+ root, err := os.OpenRoot(work)
60
+ if err != nil {
61
+ return nil, err
62
+ }
63
+ for _, name := range []string{"references", "gates", transcriptGateName(id)} {
64
+ info, err := root.Lstat(name)
65
+ if errors.Is(err, os.ErrNotExist) && create {
66
+ if err = root.Mkdir(name, 0o700); err == nil || errors.Is(err, os.ErrExist) {
67
+ info, err = root.Lstat(name)
68
+ }
69
+ }
70
+ if err != nil || !info.IsDir() {
71
+ _ = root.Close()
72
+ if err == nil {
73
+ err = fmt.Errorf("unsafe transcript directory %s", name)
74
+ }
75
+ return nil, err
76
+ }
77
+ next, err := root.OpenRoot(name)
78
+ if err == nil {
79
+ var opened os.FileInfo
80
+ opened, err = next.Stat(".")
81
+ if err == nil && !os.SameFile(info, opened) {
82
+ err = fmt.Errorf("transcript directory changed: %s", name)
83
+ }
84
+ }
85
+ _ = root.Close()
86
+ if err != nil {
87
+ if next != nil {
88
+ _ = next.Close()
89
+ }
90
+ return nil, err
91
+ }
92
+ root = next
93
+ }
94
+ return root, nil
95
+ }
96
+
97
+ // writeTranscripts atomically publishes private, complete executed records.
98
+ // Reuse points to the original file, preserving its historical candidate labels.
99
+ // OUTPUT_SHA256 and OUTPUT_BYTES always describe original, not redacted output.
39
100
  func writeTranscripts(work string, doc *Document, results []GateResult, before, after string) ([]string, error) {
40
101
  gates := map[string]*Gate{}
41
102
  for _, gate := range doc.Gates {
42
103
  gates[gate.ID] = gate
43
104
  }
44
105
  prefix := "nodigest"
45
- if len(before) >= 12 && !strings.HasPrefix(before, "unavailable") {
106
+ if transcriptDigestRE.MatchString(before) {
46
107
  prefix = before[:12]
47
108
  }
48
109
  var paths []string
49
110
  for _, result := range results {
50
111
  gate := gates[result.ID]
51
- if gate == nil || (!result.Ran && !result.Reused) {
112
+ if gate == nil {
52
113
  continue
53
114
  }
54
- name := unsafeNameRE.ReplaceAllString(gate.ID, "_")
55
- if name == "." || name == ".." {
56
- name = "_"
115
+ if result.Reused && result.Origin != "" {
116
+ paths = append(paths, result.Origin)
117
+ continue
57
118
  }
58
- dir := filepath.Join(work, "references", "gates", name)
59
- if err := os.MkdirAll(dir, 0o755); err != nil {
60
- return paths, err
119
+ if !result.Ran {
120
+ continue
61
121
  }
62
- var body strings.Builder
63
- fmt.Fprintf(&body, "DEVRITES-GATE-TRANSCRIPT v1\nGATE: %s\nTITLE: %s\nCOMMAND: %s\nCWD: %s\n", gate.ID, gate.Title, gate.Check, valueOr(gate.Cwd, "."))
64
- fmt.Fprintf(&body, "INPUTS: %s\nINPUTS_SHA256: %s\nCANDIDATE_BEFORE: %s\nCANDIDATE_AFTER: %s\n", valueOr(gate.Inputs, "none"), valueOr(result.Inputs, "none"), before, after)
65
- var file string
66
- if result.Reused {
67
- file = filepath.Join(dir, prefix+"-reused.log")
68
- fmt.Fprintf(&body, "RESULT: reused (inputs unchanged; not executed)\nPRIOR_EVIDENCE: %s\n", gate.Evidence)
69
- } else {
70
- attempt := 1
71
- if entries, err := os.ReadDir(dir); err == nil {
72
- for _, entry := range entries {
73
- if strings.HasPrefix(entry.Name(), prefix+"-a") {
74
- attempt++
75
- }
76
- }
77
- }
78
- file = filepath.Join(dir, fmt.Sprintf("%s-a%d.log", prefix, attempt))
79
- output, masked := secretpattern.Redact(result.Outcome.Output)
80
- truncated := len(output) > maxTranscriptOutput
81
- if truncated {
82
- output = output[:maxTranscriptOutput]
83
- }
84
- verdict := "fail"
85
- if result.Passed {
86
- verdict = "pass"
87
- }
88
- fmt.Fprintf(&body, "ATTEMPT: %d\nSTARTED: %s\nENDED: %s\nEXIT: %s\nEXPECT: %s\nRESULT: %s\n",
89
- attempt, result.Started.Format(time.RFC3339Nano), result.Ended.Format(time.RFC3339Nano), exitLabel(result.Outcome), gate.Expect, verdict)
90
- fmt.Fprintf(&body, "OUTPUT_SHA256: %s\nOUTPUT_BYTES: %d\nREDACTIONS: %d\nTRUNCATED: %t\n--- OUTPUT ---\n%s\n--- END OUTPUT ---\n",
91
- OutputDigest(result.Outcome.Output), result.Outcome.OutputBytes, masked, truncated, output)
122
+ dir, err := transcriptDirectory(work, gate.ID, true)
123
+ if err != nil {
124
+ return paths, err
92
125
  }
93
- if err := os.WriteFile(file, []byte(body.String()), 0o644); err != nil {
126
+ file, err := publishTranscript(dir, gate, result, prefix, before, after)
127
+ _ = dir.Close()
128
+ if err != nil {
94
129
  return paths, err
95
130
  }
96
- paths = append(paths, file)
131
+ paths = append(paths, filepath.Join(work, "references", "gates", transcriptGateName(gate.ID), file))
97
132
  }
98
133
  return paths, nil
99
134
  }
100
135
 
136
+ func publishTranscript(dir *os.Root, gate *Gate, result GateResult, prefix, before, after string) (string, error) {
137
+ attempt := 1
138
+ for {
139
+ file := fmt.Sprintf("%s-a%d.log", prefix, attempt)
140
+ if _, err := dir.Lstat(file); errors.Is(err, os.ErrNotExist) {
141
+ break
142
+ } else if err != nil {
143
+ return "", err
144
+ }
145
+ attempt++
146
+ }
147
+ var body strings.Builder
148
+ fmt.Fprintf(&body, "DEVRITES-GATE-TRANSCRIPT v2\nGATE: %s\nTITLE: %s\nCOMMAND: %s\nCWD: %s\nDEFINITION_SHA256: %s\n",
149
+ transcriptMetadata(gate.ID), transcriptMetadata(gate.Title), transcriptMetadata(gate.Check), transcriptMetadata(valueOr(gate.Cwd, ".")), DefinitionDigest(gate))
150
+ fmt.Fprintf(&body, "INPUTS: %s\nINPUTS_SHA256: %s\nCANDIDATE_BEFORE: %s\nCANDIDATE_AFTER: %s\n",
151
+ transcriptMetadata(valueOr(gate.Inputs, "none")), valueOr(result.Inputs, "none"), transcriptMetadata(before), transcriptMetadata(after))
152
+ output, masked := secretpattern.Redact(result.Outcome.Output)
153
+ truncated := len(output) > maxTranscriptOutput
154
+ if truncated {
155
+ output = output[:maxTranscriptOutput]
156
+ }
157
+ verdict := "fail"
158
+ if result.Passed && !result.Discarded && result.WriteErr == nil {
159
+ verdict = "pass"
160
+ }
161
+ fmt.Fprintf(&body, "ATTEMPT: %d\nSTARTED: %s\nENDED: %s\nEXIT: %s\nEXPECT: %s\nRESULT: %s\n",
162
+ attempt, result.Started.Format(time.RFC3339Nano), result.Ended.Format(time.RFC3339Nano), transcriptMetadata(exitLabel(result.Outcome)), transcriptMetadata(gate.Expect), verdict)
163
+ fmt.Fprintf(&body, "OUTPUT_SHA256: %s\nOUTPUT_BYTES: %d\nREDACTIONS: %d\nTRUNCATED: %t\n--- OUTPUT ---\n%s\n--- END OUTPUT ---\n",
164
+ OutputDigest(result.Outcome.Output), result.Outcome.OutputBytes, masked, truncated, output)
165
+ checksum := OutputDigest(body.String())
166
+ fmt.Fprintf(&body, "TRANSCRIPT_SHA256: %s\n", checksum)
167
+ file := fmt.Sprintf("%s-a%d.log", prefix, attempt)
168
+ return file, fsutil.WriteFileAtomicIn(dir, file, []byte(body.String()), 0o600)
169
+ }
170
+
171
+ // passingOrigin accepts only an executed, complete pass bound to ledger proof.
172
+ // Candidate labels are historical context, not a dependency of scoped INPUTS.
173
+ func passingOrigin(work string, gate *Gate) string {
174
+ evidence := automaticEvidenceRE.FindStringSubmatch(strings.TrimSpace(gate.Evidence))
175
+ if evidence == nil || evidence[1] != DefinitionDigest(gate) {
176
+ return ""
177
+ }
178
+ dir, err := transcriptDirectory(work, gate.ID, false)
179
+ if err != nil {
180
+ return ""
181
+ }
182
+ defer func() { _ = dir.Close() }()
183
+ listing, err := dir.Open(".")
184
+ if err != nil {
185
+ return ""
186
+ }
187
+ entries, err := listing.ReadDir(-1)
188
+ _ = listing.Close()
189
+ if err != nil {
190
+ return ""
191
+ }
192
+ for _, entry := range entries {
193
+ if !strings.HasSuffix(entry.Name(), ".log") {
194
+ continue
195
+ }
196
+ raw, err := readTranscript(dir, entry.Name())
197
+ if err == nil && validOrigin(string(raw), gate, evidence) {
198
+ return filepath.Join(work, "references", "gates", transcriptGateName(gate.ID), entry.Name())
199
+ }
200
+ }
201
+ return ""
202
+ }
203
+
204
+ func readTranscript(dir *os.Root, name string) ([]byte, error) {
205
+ info, err := dir.Lstat(name)
206
+ if err != nil {
207
+ return nil, err
208
+ }
209
+ if !info.Mode().IsRegular() || info.Size() > maxTranscriptBytes {
210
+ return nil, fmt.Errorf("unsafe or oversized transcript")
211
+ }
212
+ file, err := dir.Open(name)
213
+ if err != nil {
214
+ return nil, err
215
+ }
216
+ defer func() { _ = file.Close() }()
217
+ opened, err := file.Stat()
218
+ if err != nil {
219
+ return nil, err
220
+ }
221
+ if !os.SameFile(info, opened) {
222
+ return nil, fmt.Errorf("transcript changed while opening")
223
+ }
224
+ raw, err := io.ReadAll(io.LimitReader(file, maxTranscriptBytes+1))
225
+ if err != nil {
226
+ return nil, err
227
+ }
228
+ current, err := dir.Lstat(name)
229
+ if err != nil {
230
+ return nil, err
231
+ }
232
+ if len(raw) > maxTranscriptBytes || !current.Mode().IsRegular() || !os.SameFile(opened, current) || current.Size() != int64(len(raw)) {
233
+ return nil, fmt.Errorf("transcript changed while reading")
234
+ }
235
+ return raw, nil
236
+ }
237
+
238
+ func validOrigin(raw string, gate *Gate, evidence []string) bool {
239
+ const footer = "TRANSCRIPT_SHA256: "
240
+ end := strings.LastIndex(raw, footer)
241
+ if end < 0 || raw[end:] != footer+OutputDigest(raw[:end])+"\n" {
242
+ return false
243
+ }
244
+ header, output, ok := strings.Cut(raw[:end], "\n--- OUTPUT ---\n")
245
+ if !ok || !strings.HasSuffix(output, "\n--- END OUTPUT ---\n") {
246
+ return false
247
+ }
248
+ output = strings.TrimSuffix(output, "\n--- END OUTPUT ---\n")
249
+ lines := strings.Split(header, "\n")
250
+ if lines[0] != "DEVRITES-GATE-TRANSCRIPT v2" {
251
+ return false
252
+ }
253
+ fields := map[string]string{}
254
+ for _, line := range lines[1:] {
255
+ key, value, ok := strings.Cut(line, ": ")
256
+ if _, duplicate := fields[key]; !ok || duplicate {
257
+ return false
258
+ }
259
+ fields[key] = value
260
+ }
261
+ for key, want := range map[string]string{
262
+ "GATE": transcriptMetadata(gate.ID), "TITLE": transcriptMetadata(gate.Title),
263
+ "COMMAND": transcriptMetadata(gate.Check), "CWD": transcriptMetadata(valueOr(gate.Cwd, ".")),
264
+ "EXPECT": transcriptMetadata(gate.Expect), "DEFINITION_SHA256": evidence[1],
265
+ "INPUTS": transcriptMetadata(valueOr(gate.Inputs, "none")), "INPUTS_SHA256": valueOr(evidence[4], "none"),
266
+ "RESULT": "pass", "EXIT": "0", "OUTPUT_SHA256": evidence[2], "OUTPUT_BYTES": evidence[3],
267
+ } {
268
+ if got, exists := fields[key]; !exists || got != want {
269
+ return false
270
+ }
271
+ }
272
+ started, err := time.Parse(time.RFC3339Nano, fields["STARTED"])
273
+ if err != nil || started.IsZero() {
274
+ return false
275
+ }
276
+ ended, err := time.Parse(time.RFC3339Nano, fields["ENDED"])
277
+ if err != nil || ended.Before(started) {
278
+ return false
279
+ }
280
+ attempt, err := strconv.Atoi(fields["ATTEMPT"])
281
+ if err != nil || attempt < 1 || fields["CANDIDATE_BEFORE"] == "" || fields["CANDIDATE_AFTER"] == "" {
282
+ return false
283
+ }
284
+ redactions, err := strconv.Atoi(fields["REDACTIONS"])
285
+ if err != nil || redactions < 0 {
286
+ return false
287
+ }
288
+ truncated, err := strconv.ParseBool(fields["TRUNCATED"])
289
+ return err == nil && len(output) <= maxTranscriptOutput && (!truncated || len(output) == maxTranscriptOutput)
290
+ }
291
+
101
292
  func exitLabel(outcome RunOutcome) string {
102
293
  switch {
103
294
  case outcome.TimedOut:
@@ -47,9 +47,13 @@ func WorkspaceOverrideChecked(root, slug string) (string, error) {
47
47
  if raw == "" {
48
48
  return "", nil
49
49
  }
50
+ absoluteRoot, err := filepath.Abs(root)
51
+ if err != nil {
52
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DevRites root %q: %w", root, err)
53
+ }
50
54
  path := raw
51
55
  if !filepath.IsAbs(path) {
52
- path = filepath.Join(filepath.Dir(root), path)
56
+ path = filepath.Join(filepath.Dir(absoluteRoot), path)
53
57
  }
54
58
  abs, err := filepath.Abs(path)
55
59
  if err != nil {
@@ -59,10 +63,10 @@ func WorkspaceOverrideChecked(root, slug string) (string, error) {
59
63
  if err != nil {
60
64
  return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DEVRITES_WORKSPACE %q: %w", raw, err)
61
65
  }
62
- if !safepath.WithinResolved(path, root) {
66
+ if !safepath.WithinResolved(path, absoluteRoot) {
63
67
  return "", fmt.Errorf("DRV-WORKSPACE-OUTSIDE-ROOT: DEVRITES_WORKSPACE %q is outside %q; run `unset DEVRITES_WORKSPACE`", raw, root)
64
68
  }
65
- resolvedRoot, err := safepath.ResolveExisting(root)
69
+ resolvedRoot, err := safepath.ResolveExisting(absoluteRoot)
66
70
  if err != nil {
67
71
  return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DevRites root %q: %w", root, err)
68
72
  }
@@ -114,6 +118,15 @@ func ExistingFeatureDirChecked(root, slug string) (string, error) {
114
118
  }
115
119
  return ws, nil
116
120
  }
121
+ return ExistingCanonicalFeatureDirChecked(root, slug)
122
+ }
123
+
124
+ // ExistingCanonicalFeatureDirChecked resolves an explicit producer workspace
125
+ // inside root without redirecting it through a consumer's workspace override.
126
+ func ExistingCanonicalFeatureDirChecked(root, slug string) (string, error) {
127
+ if !ValidSlug(slug) {
128
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: %q is not a feature slug", slug)
129
+ }
117
130
  candidate := filepath.Join(root, "work", slug)
118
131
  resolved, err := checkedCanonicalDir(root, candidate, "work", slug)
119
132
  if err == nil {
@@ -194,14 +207,19 @@ func checkedCanonicalDir(root, candidate, parent, slug string) (string, error) {
194
207
  return filepath.Clean(candidate), nil
195
208
  }
196
209
 
197
- // ActiveSlug reads and trims the active-feature pointer. A missing pointer file
198
- // is not an error; it yields the empty slug.
210
+ // ActiveSlug resolves the workspace override, or reads the active-feature pointer.
211
+ // A missing pointer file is not an error; it yields the empty slug.
199
212
  func ActiveSlug(root string) (string, error) {
200
213
  if ws, err := WorkspaceOverrideChecked(root, ""); err != nil {
201
214
  return "", err
202
215
  } else if ws != "" {
203
216
  return filepath.Base(ws), nil
204
217
  }
218
+ return PhysicalActiveSlug(root)
219
+ }
220
+
221
+ // PhysicalActiveSlug reads the validated ACTIVE pointer without workspace overrides.
222
+ func PhysicalActiveSlug(root string) (string, error) {
205
223
  activePath := filepath.Join(root, "ACTIVE")
206
224
  if info, err := os.Lstat(activePath); err == nil && info.Mode()&os.ModeSymlink != 0 {
207
225
  return "", fmt.Errorf("DRV-ACTIVE-SYMLINK: ACTIVE is a symlink; run `rm -f %q`", activePath)
@@ -51,6 +51,12 @@ func Check(kind Kind, root, slug string) (*Result, error) {
51
51
  if err != nil {
52
52
  return nil, fmt.Errorf("gate %s: %w", kind, err)
53
53
  }
54
+ return CheckObserved(kind, root, observation)
55
+ }
56
+
57
+ // CheckObserved checks retained facts acquired from root, so a caller can use
58
+ // those same vetted bytes for subsequent admission without rereading the workspace.
59
+ func CheckObserved(kind Kind, root string, observation *state.WorkspaceObservation) (*Result, error) {
54
60
  result, err := checkObservation(kind, observation)
55
61
  if err != nil {
56
62
  return nil, fmt.Errorf("gate %s: %w", kind, err)
@@ -231,6 +231,11 @@ func (r *runner) writeConfined(dest string, data []byte, perm fs.FileMode) error
231
231
  return err
232
232
  }
233
233
  defer func() { _ = root.Close() }()
234
+ if info, err := root.Stat(rel); err == nil {
235
+ perm = info.Mode().Perm()
236
+ } else if !os.IsNotExist(err) {
237
+ return err
238
+ }
234
239
  return fsutil.WriteFileAtomicIn(root, rel, data, perm)
235
240
  }
236
241