@opengsd/gsd-path 1.6.2 → 1.7.1

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 (281) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/DOCS.md +27 -1
  3. package/FULL.md +4 -3
  4. package/HOOKS.md +38 -4
  5. package/QUICK.md +1 -1
  6. package/README.md +19 -11
  7. package/RUNTIME.md +5 -3
  8. package/WORKFLOW.md +11 -4
  9. package/package.json +1 -1
  10. package/scripts/_common.py +76 -2
  11. package/scripts/archive_milestone.py +129 -7
  12. package/scripts/build_recovery.py +25 -2
  13. package/scripts/build_state.py +333 -8
  14. package/scripts/check_handoffs.py +437 -30
  15. package/scripts/check_task_briefs.py +135 -11
  16. package/scripts/discussion_validate.py +46 -10
  17. package/scripts/dispatch_driver.py +33 -3
  18. package/scripts/git_guard.py +44 -4
  19. package/scripts/guard_hook.py +456 -9
  20. package/scripts/install.py +34 -4
  21. package/scripts/integration.py +196 -39
  22. package/scripts/isolation.py +1000 -38
  23. package/scripts/lean_verification.py +28 -13
  24. package/scripts/loop_run.py +9 -4
  25. package/scripts/members.py +17 -3
  26. package/scripts/pipeline_diagnose.py +104 -1
  27. package/scripts/pipeline_state.py +10 -2
  28. package/scripts/runtime_store.py +88 -3
  29. package/scripts/state_checkpoint.py +15 -0
  30. package/scripts/status_runtime.py +73 -2
  31. package/scripts/workflow_run.py +10 -3
  32. package/scripts/worktree_paths.py +45 -0
  33. package/skills/gsd-path/BUILD.md +4 -1
  34. package/skills/gsd-path/DEFINE.md +2 -1
  35. package/skills/gsd-path/PLAN.md +13 -3
  36. package/skills/gsd-path/SHIP.md +41 -8
  37. package/skills/gsd-path/SKILL.md +16 -1
  38. package/skills/gsd-path/references/build-native.md +28 -7
  39. package/skills/gsd-path/references/coder.md +6 -3
  40. package/skills/gsd-path/references/planner.md +4 -3
  41. package/skills/gsd-path/references/reviewer-final.md +10 -0
  42. package/skills/gsd-path/references/reviewer-wave.md +71 -27
  43. package/skills/gsd-path/references/reviewer.md +21 -8
  44. package/skills/gsd-path/references/runtime-selection.md +36 -0
  45. package/skills/gsd-path/references/spec-probes.md +19 -1
  46. package/skills/gsd-path/scripts/_common.py +76 -2
  47. package/skills/gsd-path/scripts/archive_milestone.py +129 -7
  48. package/skills/gsd-path/scripts/build_recovery.py +25 -2
  49. package/skills/gsd-path/scripts/build_state.py +333 -8
  50. package/skills/gsd-path/scripts/check_handoffs.py +437 -30
  51. package/skills/gsd-path/scripts/check_task_briefs.py +135 -11
  52. package/skills/gsd-path/scripts/discussion_validate.py +46 -10
  53. package/skills/gsd-path/scripts/dispatch_driver.py +33 -3
  54. package/skills/gsd-path/scripts/git_guard.py +44 -4
  55. package/skills/gsd-path/scripts/integration.py +196 -39
  56. package/skills/gsd-path/scripts/isolation.py +1000 -38
  57. package/skills/gsd-path/scripts/lean_verification.py +28 -13
  58. package/skills/gsd-path/scripts/loop_run.py +9 -4
  59. package/skills/gsd-path/scripts/members.py +17 -3
  60. package/skills/gsd-path/scripts/pipeline_state.py +10 -2
  61. package/skills/gsd-path/scripts/state_checkpoint.py +15 -0
  62. package/skills/gsd-path/scripts/status_runtime.py +73 -2
  63. package/skills/gsd-path/scripts/workflow_run.py +10 -3
  64. package/skills/gsd-path/scripts/worktree_paths.py +45 -0
  65. package/skills/gsd-path/templates/intent.md +5 -3
  66. package/skills/gsd-path/templates/plan.md +3 -2
  67. package/skills/gsd-path/templates/task.md +8 -1
  68. package/skills/gsd-path/templates/wave-review.md +24 -8
  69. package/skills/gsd-path-build/SKILL.md +4 -1
  70. package/skills/gsd-path-build/references/build-native.md +28 -7
  71. package/skills/gsd-path-build/references/coder.md +6 -3
  72. package/skills/gsd-path-build/references/reviewer-final.md +10 -0
  73. package/skills/gsd-path-build/references/reviewer-wave.md +71 -27
  74. package/skills/gsd-path-build/references/reviewer.md +21 -8
  75. package/skills/gsd-path-build/references/runtime-selection.md +36 -0
  76. package/skills/gsd-path-build/references/spec-probes.md +19 -1
  77. package/skills/gsd-path-build/scripts/_common.py +76 -2
  78. package/skills/gsd-path-build/scripts/archive_milestone.py +129 -7
  79. package/skills/gsd-path-build/scripts/build_recovery.py +25 -2
  80. package/skills/gsd-path-build/scripts/build_state.py +333 -8
  81. package/skills/gsd-path-build/scripts/check_handoffs.py +437 -30
  82. package/skills/gsd-path-build/scripts/check_task_briefs.py +135 -11
  83. package/skills/gsd-path-build/scripts/discussion_validate.py +46 -10
  84. package/skills/gsd-path-build/scripts/dispatch_driver.py +33 -3
  85. package/skills/gsd-path-build/scripts/integration.py +196 -39
  86. package/skills/gsd-path-build/scripts/isolation.py +1000 -38
  87. package/skills/gsd-path-build/scripts/loop_run.py +9 -4
  88. package/skills/gsd-path-build/scripts/members.py +17 -3
  89. package/skills/gsd-path-build/scripts/pipeline_state.py +10 -2
  90. package/skills/gsd-path-build/scripts/state_checkpoint.py +15 -0
  91. package/skills/gsd-path-build/scripts/workflow_run.py +10 -3
  92. package/skills/gsd-path-build/scripts/worktree_paths.py +45 -0
  93. package/skills/gsd-path-build/templates/task.md +8 -1
  94. package/skills/gsd-path-build/templates/wave-review.md +24 -8
  95. package/skills/gsd-path-decide/references/runtime-selection.md +36 -0
  96. package/skills/gsd-path-decide/scripts/_common.py +76 -2
  97. package/skills/gsd-path-decide/scripts/archive_milestone.py +129 -7
  98. package/skills/gsd-path-decide/scripts/build_recovery.py +25 -2
  99. package/skills/gsd-path-decide/scripts/check_handoffs.py +437 -30
  100. package/skills/gsd-path-decide/scripts/check_task_briefs.py +135 -11
  101. package/skills/gsd-path-decide/scripts/discussion_validate.py +46 -10
  102. package/skills/gsd-path-decide/scripts/integration.py +196 -39
  103. package/skills/gsd-path-decide/scripts/isolation.py +1000 -38
  104. package/skills/gsd-path-decide/scripts/loop_run.py +9 -4
  105. package/skills/gsd-path-decide/scripts/members.py +17 -3
  106. package/skills/gsd-path-decide/scripts/pipeline_state.py +10 -2
  107. package/skills/gsd-path-decide/scripts/state_checkpoint.py +15 -0
  108. package/skills/gsd-path-decide/scripts/worktree_paths.py +45 -0
  109. package/skills/gsd-path-define/SKILL.md +2 -1
  110. package/skills/gsd-path-define/references/runtime-selection.md +36 -0
  111. package/skills/gsd-path-define/references/spec-probes.md +19 -1
  112. package/skills/gsd-path-define/scripts/_common.py +76 -2
  113. package/skills/gsd-path-define/scripts/archive_milestone.py +129 -7
  114. package/skills/gsd-path-define/scripts/build_recovery.py +25 -2
  115. package/skills/gsd-path-define/scripts/check_handoffs.py +437 -30
  116. package/skills/gsd-path-define/scripts/check_task_briefs.py +135 -11
  117. package/skills/gsd-path-define/scripts/discussion_validate.py +46 -10
  118. package/skills/gsd-path-define/scripts/integration.py +196 -39
  119. package/skills/gsd-path-define/scripts/isolation.py +1000 -38
  120. package/skills/gsd-path-define/scripts/loop_run.py +9 -4
  121. package/skills/gsd-path-define/scripts/members.py +17 -3
  122. package/skills/gsd-path-define/scripts/pipeline_state.py +10 -2
  123. package/skills/gsd-path-define/scripts/state_checkpoint.py +15 -0
  124. package/skills/gsd-path-define/scripts/worktree_paths.py +45 -0
  125. package/skills/gsd-path-define/templates/intent.md +5 -3
  126. package/skills/gsd-path-discuss/references/runtime-selection.md +36 -0
  127. package/skills/gsd-path-discuss/scripts/_common.py +76 -2
  128. package/skills/gsd-path-discuss/scripts/loop_run.py +9 -4
  129. package/skills/gsd-path-discuss/scripts/worktree_paths.py +45 -0
  130. package/skills/gsd-path-docs-audit/references/runtime-selection.md +36 -0
  131. package/skills/gsd-path-docs-audit/scripts/_common.py +76 -2
  132. package/skills/gsd-path-docs-audit/scripts/archive_milestone.py +129 -7
  133. package/skills/gsd-path-docs-audit/scripts/build_recovery.py +25 -2
  134. package/skills/gsd-path-docs-audit/scripts/check_handoffs.py +437 -30
  135. package/skills/gsd-path-docs-audit/scripts/check_task_briefs.py +135 -11
  136. package/skills/gsd-path-docs-audit/scripts/discussion_validate.py +46 -10
  137. package/skills/gsd-path-docs-audit/scripts/integration.py +196 -39
  138. package/skills/gsd-path-docs-audit/scripts/isolation.py +1000 -38
  139. package/skills/gsd-path-docs-audit/scripts/loop_run.py +9 -4
  140. package/skills/gsd-path-docs-audit/scripts/members.py +17 -3
  141. package/skills/gsd-path-docs-audit/scripts/pipeline_state.py +10 -2
  142. package/skills/gsd-path-docs-audit/scripts/state_checkpoint.py +15 -0
  143. package/skills/gsd-path-docs-audit/scripts/worktree_paths.py +45 -0
  144. package/skills/gsd-path-forensics/references/runtime-selection.md +36 -0
  145. package/skills/gsd-path-forensics/scripts/_common.py +76 -2
  146. package/skills/gsd-path-forensics/scripts/loop_run.py +9 -4
  147. package/skills/gsd-path-forensics/scripts/pipeline_diagnose.py +104 -1
  148. package/skills/gsd-path-forensics/scripts/worktree_paths.py +45 -0
  149. package/skills/gsd-path-inspect/references/runtime-selection.md +36 -0
  150. package/skills/gsd-path-inspect/scripts/_common.py +76 -2
  151. package/skills/gsd-path-inspect/scripts/archive_milestone.py +129 -7
  152. package/skills/gsd-path-inspect/scripts/build_recovery.py +25 -2
  153. package/skills/gsd-path-inspect/scripts/check_handoffs.py +437 -30
  154. package/skills/gsd-path-inspect/scripts/check_task_briefs.py +135 -11
  155. package/skills/gsd-path-inspect/scripts/discussion_validate.py +46 -10
  156. package/skills/gsd-path-inspect/scripts/integration.py +196 -39
  157. package/skills/gsd-path-inspect/scripts/isolation.py +1000 -38
  158. package/skills/gsd-path-inspect/scripts/loop_run.py +9 -4
  159. package/skills/gsd-path-inspect/scripts/members.py +17 -3
  160. package/skills/gsd-path-inspect/scripts/pipeline_state.py +10 -2
  161. package/skills/gsd-path-inspect/scripts/state_checkpoint.py +15 -0
  162. package/skills/gsd-path-inspect/scripts/workflow_run.py +10 -3
  163. package/skills/gsd-path-inspect/scripts/worktree_paths.py +45 -0
  164. package/skills/gsd-path-loop/references/runtime-selection.md +36 -0
  165. package/skills/gsd-path-loop/scripts/_common.py +76 -2
  166. package/skills/gsd-path-loop/scripts/loop_run.py +9 -4
  167. package/skills/gsd-path-loop/scripts/worktree_paths.py +45 -0
  168. package/skills/gsd-path-migrate/references/runtime-selection.md +36 -0
  169. package/skills/gsd-path-migrate/scripts/_common.py +76 -2
  170. package/skills/gsd-path-migrate/scripts/loop_run.py +9 -4
  171. package/skills/gsd-path-plan/SKILL.md +13 -3
  172. package/skills/gsd-path-plan/references/planner.md +4 -3
  173. package/skills/gsd-path-plan/references/runtime-selection.md +36 -0
  174. package/skills/gsd-path-plan/references/spec-probes.md +19 -1
  175. package/skills/gsd-path-plan/scripts/_common.py +76 -2
  176. package/skills/gsd-path-plan/scripts/archive_milestone.py +129 -7
  177. package/skills/gsd-path-plan/scripts/build_recovery.py +25 -2
  178. package/skills/gsd-path-plan/scripts/check_handoffs.py +437 -30
  179. package/skills/gsd-path-plan/scripts/check_task_briefs.py +135 -11
  180. package/skills/gsd-path-plan/scripts/discussion_validate.py +46 -10
  181. package/skills/gsd-path-plan/scripts/integration.py +196 -39
  182. package/skills/gsd-path-plan/scripts/isolation.py +1000 -38
  183. package/skills/gsd-path-plan/scripts/loop_run.py +9 -4
  184. package/skills/gsd-path-plan/scripts/members.py +17 -3
  185. package/skills/gsd-path-plan/scripts/pipeline_state.py +10 -2
  186. package/skills/gsd-path-plan/scripts/state_checkpoint.py +15 -0
  187. package/skills/gsd-path-plan/scripts/workflow_run.py +10 -3
  188. package/skills/gsd-path-plan/scripts/worktree_paths.py +45 -0
  189. package/skills/gsd-path-plan/templates/plan.md +3 -2
  190. package/skills/gsd-path-plan/templates/task.md +8 -1
  191. package/skills/gsd-path-research/references/runtime-selection.md +36 -0
  192. package/skills/gsd-path-research/scripts/_common.py +76 -2
  193. package/skills/gsd-path-research/scripts/archive_milestone.py +129 -7
  194. package/skills/gsd-path-research/scripts/build_recovery.py +25 -2
  195. package/skills/gsd-path-research/scripts/check_handoffs.py +437 -30
  196. package/skills/gsd-path-research/scripts/check_task_briefs.py +135 -11
  197. package/skills/gsd-path-research/scripts/discussion_validate.py +46 -10
  198. package/skills/gsd-path-research/scripts/integration.py +196 -39
  199. package/skills/gsd-path-research/scripts/isolation.py +1000 -38
  200. package/skills/gsd-path-research/scripts/loop_run.py +9 -4
  201. package/skills/gsd-path-research/scripts/members.py +17 -3
  202. package/skills/gsd-path-research/scripts/pipeline_state.py +10 -2
  203. package/skills/gsd-path-research/scripts/state_checkpoint.py +15 -0
  204. package/skills/gsd-path-research/scripts/worktree_paths.py +45 -0
  205. package/skills/gsd-path-roadmap/references/runtime-selection.md +36 -0
  206. package/skills/gsd-path-roadmap/scripts/_common.py +76 -2
  207. package/skills/gsd-path-roadmap/scripts/archive_milestone.py +129 -7
  208. package/skills/gsd-path-roadmap/scripts/build_recovery.py +25 -2
  209. package/skills/gsd-path-roadmap/scripts/check_handoffs.py +437 -30
  210. package/skills/gsd-path-roadmap/scripts/check_task_briefs.py +135 -11
  211. package/skills/gsd-path-roadmap/scripts/discussion_validate.py +46 -10
  212. package/skills/gsd-path-roadmap/scripts/integration.py +196 -39
  213. package/skills/gsd-path-roadmap/scripts/isolation.py +1000 -38
  214. package/skills/gsd-path-roadmap/scripts/loop_run.py +9 -4
  215. package/skills/gsd-path-roadmap/scripts/members.py +17 -3
  216. package/skills/gsd-path-roadmap/scripts/pipeline_state.py +10 -2
  217. package/skills/gsd-path-roadmap/scripts/state_checkpoint.py +15 -0
  218. package/skills/gsd-path-roadmap/scripts/worktree_paths.py +45 -0
  219. package/skills/gsd-path-ship/SKILL.md +41 -8
  220. package/skills/gsd-path-ship/references/reviewer-final.md +10 -0
  221. package/skills/gsd-path-ship/references/reviewer-wave.md +71 -27
  222. package/skills/gsd-path-ship/references/reviewer.md +21 -8
  223. package/skills/gsd-path-ship/references/runtime-selection.md +36 -0
  224. package/skills/gsd-path-ship/references/spec-probes.md +19 -1
  225. package/skills/gsd-path-ship/scripts/_common.py +76 -2
  226. package/skills/gsd-path-ship/scripts/archive_milestone.py +129 -7
  227. package/skills/gsd-path-ship/scripts/build_recovery.py +25 -2
  228. package/skills/gsd-path-ship/scripts/build_state.py +333 -8
  229. package/skills/gsd-path-ship/scripts/check_handoffs.py +437 -30
  230. package/skills/gsd-path-ship/scripts/check_task_briefs.py +135 -11
  231. package/skills/gsd-path-ship/scripts/discussion_validate.py +46 -10
  232. package/skills/gsd-path-ship/scripts/integration.py +196 -39
  233. package/skills/gsd-path-ship/scripts/isolation.py +1000 -38
  234. package/skills/gsd-path-ship/scripts/lean_verification.py +28 -13
  235. package/skills/gsd-path-ship/scripts/loop_run.py +9 -4
  236. package/skills/gsd-path-ship/scripts/members.py +17 -3
  237. package/skills/gsd-path-ship/scripts/pipeline_state.py +10 -2
  238. package/skills/gsd-path-ship/scripts/state_checkpoint.py +15 -0
  239. package/skills/gsd-path-ship/scripts/workflow_run.py +10 -3
  240. package/skills/gsd-path-ship/scripts/worktree_paths.py +45 -0
  241. package/skills/gsd-path-ship/templates/wave-review.md +24 -8
  242. package/skills/gsd-path-undo/references/runtime-selection.md +36 -0
  243. package/skills/gsd-path-undo/scripts/_common.py +76 -2
  244. package/skills/gsd-path-undo/scripts/loop_run.py +9 -4
  245. package/skills/gsd-path-undo/scripts/worktree_paths.py +45 -0
  246. package/skills/path/BUILD.md +4 -1
  247. package/skills/path/DEFINE.md +2 -1
  248. package/skills/path/PLAN.md +13 -3
  249. package/skills/path/SHIP.md +41 -8
  250. package/skills/path/SKILL.md +16 -1
  251. package/skills/path/references/build-native.md +28 -7
  252. package/skills/path/references/coder.md +6 -3
  253. package/skills/path/references/planner.md +4 -3
  254. package/skills/path/references/reviewer-final.md +10 -0
  255. package/skills/path/references/reviewer-wave.md +71 -27
  256. package/skills/path/references/reviewer.md +21 -8
  257. package/skills/path/references/runtime-selection.md +36 -0
  258. package/skills/path/references/spec-probes.md +19 -1
  259. package/skills/path/scripts/_common.py +76 -2
  260. package/skills/path/scripts/archive_milestone.py +129 -7
  261. package/skills/path/scripts/build_recovery.py +25 -2
  262. package/skills/path/scripts/build_state.py +333 -8
  263. package/skills/path/scripts/check_handoffs.py +437 -30
  264. package/skills/path/scripts/check_task_briefs.py +135 -11
  265. package/skills/path/scripts/discussion_validate.py +46 -10
  266. package/skills/path/scripts/dispatch_driver.py +33 -3
  267. package/skills/path/scripts/git_guard.py +44 -4
  268. package/skills/path/scripts/integration.py +196 -39
  269. package/skills/path/scripts/isolation.py +1000 -38
  270. package/skills/path/scripts/lean_verification.py +28 -13
  271. package/skills/path/scripts/loop_run.py +9 -4
  272. package/skills/path/scripts/members.py +17 -3
  273. package/skills/path/scripts/pipeline_state.py +10 -2
  274. package/skills/path/scripts/state_checkpoint.py +15 -0
  275. package/skills/path/scripts/status_runtime.py +73 -2
  276. package/skills/path/scripts/workflow_run.py +10 -3
  277. package/skills/path/scripts/worktree_paths.py +45 -0
  278. package/skills/path/templates/intent.md +5 -3
  279. package/skills/path/templates/plan.md +3 -2
  280. package/skills/path/templates/task.md +8 -1
  281. package/skills/path/templates/wave-review.md +24 -8
@@ -35,7 +35,10 @@ reality:
35
35
  item in INTENT.md `## Success criteria`.
36
36
  - Confirm your Interface contract text appears verbatim in every sibling
37
37
  task file it exchanges with. Sibling task files under `.project/tasks/`
38
- are readable in your worktree — read-only; never edit them.
38
+ are readable in your worktree — read-only; never edit them. On a member
39
+ task the orchestrator pins them under
40
+ `.gsd-path-coordinator/.project/tasks/` beside your live task copy —
41
+ read them there.
39
42
  - Confirm every path the Verify command references exists or is declared.
40
43
 
41
44
  Route any mismatch before writing a line of product code: a clear defect — a
@@ -86,8 +89,8 @@ them.
86
89
 
87
90
  - You are not alone in the repository. Other agents may use sibling worktrees;
88
91
  do not inspect, revert, or overwrite their changes.
89
- - Never edit `base`, `worktree`, `task_branch`, `status`, or `agent`.
90
- The orchestrator owns them.
92
+ - Never edit `base`, `worktree`, `task_branch`, `status`, `agent`, or
93
+ `member_base` (member tasks). The orchestrator owns them.
91
94
  - Never stage, commit, amend, rebase, reset, or otherwise mutate Git.
92
95
  - Never add an unnamed dependency, weaken verification, or improve adjacent
93
96
  code.
@@ -1,6 +1,16 @@
1
1
  ## Final integration mode
2
2
 
3
3
  Use only final-review.md. Treat INTENT.md success criteria as the rubric.
4
+ To locate each task's authoritative accepted evidence instead of joining
5
+ task Logs, ledger rows, and gap artifacts by hand, run
6
+ `python3 <absolute build_state.py> evidence-manifest --repo
7
+ <absolute primary> --project-dir .project --head <reviewed HEAD>`:
8
+ it classifies every task as `ledger`, `log-only`, `rejected`,
9
+ `superseded`, or `missing` and names the receipt, and reports the member
10
+ project Verify as `gap-embedded` only from a passing `final-gap-1.md` at
11
+ the reviewed HEAD — a
12
+ `log-only` task is accepted through its retained isolated-Verify Log, and
13
+ an absent ledger row is explained, never treated as data loss.
4
14
  Each `### SCn —` heading must have every word of the criterion in INTENT.md
5
15
  `## Success criteria`: join indented continuation lines and sub-bullets
6
16
  with a single space. You can keep or remove the `- ` markers of the
@@ -8,19 +8,45 @@ tasks in this wave as the rubric. For each task:
8
8
  Never infer either SHA from task ids, branch position, or nearby history.
9
9
  2. Fail a missing, malformed, or invalid SHA. Inspect the commit with
10
10
  `git show --format=fuller --stat --patch <commit> --`.
11
- 3. In the supplied verify sidecar at `base`, apply only the complete
12
- binary patch from `commit^..commit` for the task's declared product files
13
- and inspect the diff. Use the orchestrator's recorded isolated Verify
14
- output in that task's Log as the Verify evidence. Do not re-run the
15
- task Verify command or PLAN.md's project Verify. Re-run a command only
16
- when the recorded output plus the isolated diff cannot check the
17
- criterion. Never use the primary worktree or branch tip as task
18
- evidence. Do not create or remove Git worktrees yourself.
19
- 4. Mark `pass` or `fail`. Before the evidence separator (` — `), copy the
20
- complete task acceptance or interface criterion (see Review file format
21
- below). Do not substitute an AC id or summary: finding grouping binds
22
- this text to the task. Give the observed result, `file:line`, and
11
+ 3. Select the inspection mode your brief supplies and record it — never
12
+ improvise a third:
13
+ - **Reconstructed product** (default): the brief supplies a verify
14
+ sidecar at `base`. In it, apply only the complete binary patch from
15
+ `commit^..commit` for the task's declared product files and inspect
16
+ the diff. Use the orchestrator's recorded isolated Verify output in
17
+ that task's Log as the Verify evidence.
18
+ - **Immutable patch**: the brief supplies one later combined checkout
19
+ with report-write authority only. Inspect the immutable objects
20
+ directly — `git show`/`git diff` on the recorded commits — and prove
21
+ the identity before judging: (a) the landing commit's parent
22
+ `commit^` equals the task file's recorded `base` (or the patch's
23
+ resulting blobs at `base` equal the landing commit's blobs for the
24
+ declared files), and (b) any file judged from the combined checkout
25
+ has its blob there equal to the landing commit's blob at that path.
26
+ Without both identities the review blocks: a mismatch means the
27
+ supplied checkout is not the reviewed product.
28
+ In both modes: do not re-run the task Verify command or PLAN.md's
29
+ project Verify; re-run a command only when the recorded output plus
30
+ the diff cannot check the criterion. Never use the primary worktree
31
+ or branch tip as task evidence. Do not create or remove Git worktrees
32
+ yourself.
33
+ 4. Mark `pass` or `fail`. Before the evidence separator (` — `), name the
34
+ criterion (see Review file format below): copy the complete task
35
+ acceptance or interface criterion verbatim, or — on a `pass` line only —
36
+ cite the criterion by its stable id in the task's numbered acceptance or
37
+ interface list (`T012/AC3`, `T012/IF2`) together with the evidence. A
38
+ `fail` line always copies the criterion verbatim: finding grouping binds
39
+ that text to the task. Give the observed result, `file:line`, and
23
40
  concrete fix direction for each failure.
41
+ 5. Account for every claim. Each acceptance criterion (`T012/AC3`) and
42
+ interface bullet (`T012/IF2`) of the task must appear in its
43
+ section as an evidence reference, a finding, or an explicitly
44
+ authorized disposition — a bare line naming the id with
45
+ `disposition: <authority and date> (<Cn>): <reason>`, where `Cn` is a
46
+ numbered INTENT.md correction. No citation, or a `Cn` that
47
+ `## Corrections` does not define, fails the gate. One observation may
48
+ support several claims when each is identified by its id. The wave gate
49
+ rejects a review that leaves a claim unaccounted for.
24
50
 
25
51
  A Log entry `orchestrator regression Verify of <task id> retired: superseded
26
52
  by <task id>` is not evidence: judge the superseding task's acceptance
@@ -30,8 +56,12 @@ criteria. The same applies to the entry `orchestrator regression Verify of
30
56
  Then check each INTENT.md success criterion owned by a task in this wave
31
57
  against that same isolated product. In each `### SCn —` heading, copy the
32
58
  complete criterion text from INTENT.md `## Success criteria` (see Review
33
- file format below). Mark `pass` or `fail` with evidence; a failed owned SC
34
- blocks the wave. Omit the Intent coverage section when no task in the wave owns
59
+ file format below). Write the headings in ascending SC id order (SC2 before
60
+ SC4), and separate the id from the text with an em dash (—). Mark `pass`
61
+ or `fail` with evidence; a failed owned SC blocks the wave. Under a `pass`
62
+ SC, record each subclaim a numbered INTENT.md correction excludes as
63
+ `- Excluded by <Cn>: <subclaim>`; the gate rejects an exclusion under a
64
+ failing SC or one that cites an undefined correction. Omit the Intent coverage section when no task in the wave owns
35
65
  an SC. When INTENT.md `## Edge coverage` or `## Prohibitions` has rows for
36
66
  an owned SC, judge it against those rulings too: cite each `held-out` edge
37
67
  id with its held-out test's recorded output and each `judgment` prohibition
@@ -40,14 +70,19 @@ fails the SC.
40
70
 
41
71
  Allow changed paths only in the task's `files` plus its assigned task file.
42
72
  Within the task file, allow orchestrator-owned `base`, `worktree`,
43
- `task_branch`, `status`, and `agent` fields plus append-only Log
44
- entries; block contract-body changes in the task commit. Warn on disabled tests
73
+ `task_branch`, `status`, and `agent` fields — and, on a member task,
74
+ `member_base` — plus append-only Log entries; block contract-body changes
75
+ in the task commit. `base` names the task's starting commit in the
76
+ coordinator repository; `member_base` names the starting commit in the
77
+ member repository from which the member task branch and isolate were
78
+ created. Warn on disabled tests
45
79
  or Verify commands that cannot fail unless either defeats a criterion. Set the
46
80
  wave verdict to `pass` only when every task passes and every owned INTENT
47
81
  success criterion for the wave passes.
48
82
 
49
- For cycle greater than 1, read the previous review, re-check every failure,
50
- and regression-check affected passes. State when the same criterion fails for
83
+ For cycle greater than 1, read the previous review, re-check repaired failures,
84
+ and regression-check affected passes only. Reuse settled evidence for unaffected
85
+ criteria; do not start a fresh search for improvements. State when the same criterion fails for
51
86
  a different reason. Use only the wave-review template.
52
87
 
53
88
  ### Review file format
@@ -65,10 +100,14 @@ criterion text does not match. Apply the rules before you write the file.
65
100
  `- ❌ ` for fail. Each task section and each `### SCn —` block needs one
66
101
  or more lines with the marker of its own verdict. A failed task needs a
67
102
  `- ❌ ` criterion line, also when other text describes the failure.
68
- - **Criterion text.** In a task evidence line and in a `### SCn —` heading,
69
- copy every word of the criterion. Join wrapped lines and sub-bullets with
70
- one space. You can keep or remove the `- ` markers of the sub-bullets.
71
- A changed, added, removed or moved word does not match.
103
+ - **Criterion text.** In a `### SCn —` heading and on every `- ❌ ` fail
104
+ line, copy every word of the criterion. Join wrapped lines and
105
+ sub-bullets with one space. You can keep or remove the `- ` markers of
106
+ the sub-bullets. A changed, added, removed or moved word does not
107
+ match. On a `- ✅ ` pass line, name the criterion either by copying it
108
+ verbatim or by citing its stable id in the task's numbered acceptance
109
+ or interface list (`T001/AC1`, `T001/IF2`) together with the evidence;
110
+ the wave gate does not compare pass-line criterion text.
72
111
  - **Angle brackets.** Text such as `<b>` or `<path>` outside backticks reads
73
112
  as an unfilled template placeholder and is rejected. Write literal markup
74
113
  and code in backticks inside a sentence, for example: the output has
@@ -115,9 +154,12 @@ runtime checks freshness and derives the final view from these records.
115
154
 
116
155
  ## Deep review lenses
117
156
 
157
+ Use `deep` only when the user explicitly requested adversarial review and that
158
+ request is recorded in INTENT.md. Risk alone does not authorize it.
118
159
  A `deep` wave spawns two independent reviewers in parallel, each with a fresh
119
- context and its own verify sidecar at the recorded review base. Every
120
- Wave mode rule above applies to both lenses.
160
+ context and its own isolated workspace at the recorded review base (a verify
161
+ sidecar in reconstructed mode, or the supplied combined checkout in immutable
162
+ mode). Every Wave mode rule above applies to both lenses.
121
163
 
122
164
  - **Contract** — logical task name
123
165
  `review_wave_<wave>_cycle_<cycle>_contract`; stages
@@ -127,9 +169,11 @@ Wave mode rule above applies to both lenses.
127
169
  contract`.
128
170
  - **Adversarial** — logical task name
129
171
  `review_wave_<wave>_cycle_<cycle>_adversarial`; stages
130
- `.project/review/wave-N.cycleC.adversarial.md`. Try to kill the work:
131
- security holes, unhandled edge cases, failure modes, data-loss and
132
- concurrency risks, missing error handling. Record `Lens: adversarial`.
172
+ `.project/review/wave-N.cycleC.adversarial.md`. Exercise failure paths within
173
+ the agreed contract, including its required security, data-loss, concurrency,
174
+ and error-handling behavior. Every blocking finding still needs a named
175
+ requirement and checked failure or missing required proof. Do not invent
176
+ additional requirements. Record `Lens: adversarial`.
133
177
 
134
178
  The wave passes only when both lenses return `pass`; any `blocked` lens
135
179
  blocks the wave.
@@ -1,20 +1,23 @@
1
1
  # Reviewer role
2
2
 
3
- Verify written GSD Path expectations and block failures. Never fix code.
3
+ Validate that the agreed work functions. Block contract failures or missing
4
+ required evidence. Never search for improvements or fix code.
4
5
 
5
6
  ## Brief contract
6
7
 
7
8
  - Require the mode, all inputs, exact canonical output path, applicable
8
- template, repository root, and orchestrator-created verify sidecar
9
- root by absolute path. Wave mode also requires the absolute INTENT.md
10
- path. The brief must also name the staged output path under
11
- that sidecar.
9
+ template, and repository root by absolute path. Wave mode also requires
10
+ the absolute INTENT.md path and names the inspection mode: a verify
11
+ sidecar root for reconstructed-product review, or one combined checkout
12
+ with report-write authority for immutable-patch review. The brief must
13
+ also name the staged output path under that workspace.
12
14
  - Read AGENTS.md and the template before reviewing.
13
- - Write only the exact assigned review output under the supplied verify sidecar
15
+ - Write only the exact assigned review output under the supplied workspace
14
16
  at its staged `.project/review/` path. The orchestrator validates and
15
17
  atomically transfers that file to the canonical primary `.project/review/`
16
18
  path after collection. Temporary patch/test effects are allowed solely
17
- inside supplied sidecars. Never edit a product path or any other
19
+ inside supplied sidecars; an immutable-mode combined checkout carries
20
+ report-write authority only. Never edit a product path or any other
18
21
  primary-worktree path. Stop on a missing input or template.
19
22
 
20
23
  ## Load the assigned mode
@@ -34,7 +37,17 @@ Resolve sibling links against this role file's directory.
34
37
 
35
38
  - When `GSD_PATH_JEV=1`, wave and final reviewers follow the optional
36
39
  [Jev evidence screening procedure](jev-review.md) for uncovered criteria.
37
- - Review written expectations, not preferences or alternate designs.
40
+ - Use the written acceptance, interface, success, edge, prohibition, and
41
+ surface contracts as the complete review scope. Block only on a named
42
+ unmet requirement with checked evidence, or the specific missing evidence
43
+ needed to prove it. Passing tests alone do not prove a required walkthrough.
44
+ - Do not search for improvements, alternate designs, speculative hardening,
45
+ or unrelated edge cases. Do not turn these into findings or fix tasks.
46
+ A clean pass is a complete result; no finding quota applies.
47
+ - On re-review, check repaired failures and passes affected by the repair.
48
+ Reuse settled evidence for unaffected criteria; do not reopen them or
49
+ introduce new requirements. An observed regression against the unchanged
50
+ contract still blocks. Stop when every applicable claim is proven.
38
51
  - Treat an Interface contract violation as a failed criterion, citing the
39
52
  contract line and the offending diff hunk.
40
53
  - Require checked evidence for every verdict.
@@ -20,6 +20,42 @@ references to bundled copies of those helpers. Other skill helpers, templates,
20
20
  and role briefs remain in the installed skill bundle. Pass `-B` to Python helper
21
21
  invocations so execution does not create bytecode in the runtime store.
22
22
 
23
+ When the runtime enforcing an active task cannot finish it, one narrowly
24
+ authorized recovery exists:
25
+ `python3 -B <absolute isolation.py> runtime-hotfix --repo <absolute primary>
26
+ --task-id <id> --runtime <64-hex runtime digest printed by runtime_store.py publish>
27
+ --authorized-by <the owner ruling>` validates the replacement runtime,
28
+ re-runs that task's Verify in a fresh sidecar at the recorded base under the
29
+ replacement pin, and records the receipt twice: a Log line in the task file
30
+ naming the task, base, and both runtime digests, and an identical blob behind
31
+ the local ref `refs/gsd-path/runtime-hotfix/<task>-<base>`. Only a Log line
32
+ whose exact twin that ref holds later admits `.gsd-path/runtime.json` into that
33
+ task's landing, and only when the pin names exactly the receipt's runtime; any
34
+ other task, base, a Log line alone, or a bare mention authorizes nothing, and a
35
+ failing re-run refuses the hotfix. Limits: the command serves only a serial
36
+ task working in the primary and refuses parallel or member tasks; the ref is
37
+ local repository state, so a clone without it cannot land the pin and the
38
+ proof fails closed. The guard applies two rules to agent shell commands. It
39
+ denies any command where `runtime-hotfix` directly follows `isolation.py`, stdin
40
+ `-`, or an `isolation` module operand (`-m isolation`, `-Bm isolation`,
41
+ `-misolation`). It also denies every Git command with a word, including global
42
+ `-c` options and inline variable assignments, that contains `refs/gsd-path/` or
43
+ `runtime-hotfix`. This deliberately blocks read-only uses such as
44
+ `git log refs/gsd-path/<ref>` or `git log --grep runtime-hotfix`, and commit
45
+ messages or branch names that contain `runtime-hotfix`. Limits: it cannot see a copied or renamed
46
+ `isolation.py`, a refspec stored in a Git config file, a ref write made from
47
+ inside another program, a direct edit of files under `.git/`, or shell
48
+ expansion that splits both protected words, such as
49
+ `refs/gs{d,}-path/task-authorizations/<task>`.
50
+
51
+ Publishing a runtime to the shared store is project-free:
52
+ `python3 -B <absolute runtime_store.py> publish --source <absolute package root>`
53
+ verifies and publishes the content-addressed runtime, printing its version,
54
+ digest, and store path as JSON; `--provenance` records a note in that output
55
+ only. Publication alone neither authorizes execution nor changes a project's
56
+ trusted runtime — a project runs the bytes only when its own pin names that
57
+ digest.
58
+
23
59
  The selected runtime is authoritative for each helper in its `manifest.json`;
24
60
  this includes `review_findings.py` and `task_context.py`. A selected runtime
25
61
  that was published before a helper became a runtime file does not list that
@@ -96,6 +96,23 @@ Present the kept items with a recommended ruling and record each as a row:
96
96
  Work that affects no person directly records a single row instead, and no
97
97
  other: `| N1 | all | none | dismissed | <why no person is affected> |`.
98
98
 
99
+ ## Absence criteria
100
+
101
+ A criterion that claims absence ("X does not exist", "no X anywhere",
102
+ "never Y"), including one written from a prohibition, has no end until it
103
+ says where to look. Each review can look somewhere new, so the build loops
104
+ to the cycle cap. Every absence criterion states:
105
+
106
+ - the search space: the files, surfaces, or artifacts to check;
107
+ - the check: the command or observation that covers that space;
108
+ - the absent result: the output that counts as absent.
109
+
110
+ When one is missing, propose a bounded form in the same question round as
111
+ the edges, recommended form first, and amend the criterion after the user
112
+ rules. Do not choose the search space yourself: a narrower space is a scope
113
+ ruling. Under pre-approval, an unbounded absence criterion is a required
114
+ answer: stop and ask.
115
+
99
116
  ## INTENT.md tables
100
117
 
101
118
  ```markdown
@@ -131,7 +148,8 @@ rechecked; a near-miss heading fails the gate rather than switching it off.
131
148
  the owning task's Approach so the coder knows the line.
132
149
  - **Review.** A row pins the reading of its criterion. A reviewer does not
133
150
  pass a reading that contradicts a ruling in either table, and cites each
134
- held-out edge and judgment prohibition id with its evidence.
151
+ held-out edge and judgment prohibition id with its evidence. An absence
152
+ criterion is judged over its stated search space only.
135
153
  - **Final review.** A criterion that carries a held-out edge or a judgment
136
154
  prohibition is `met` only when its Check, Observed, or Reference cites
137
155
  every such id with the evidence: the held-out test's recorded passing
@@ -14,6 +14,7 @@ import sys
14
14
  # Runtime helpers must not modify their immutable installation.
15
15
  sys.dont_write_bytecode = True
16
16
 
17
+ import atexit
17
18
  import contextlib
18
19
  import errno
19
20
  import json
@@ -23,9 +24,10 @@ import shlex
23
24
  import shutil
24
25
  import signal
25
26
  import subprocess
27
+ import tempfile
26
28
  import time
27
29
  from pathlib import Path
28
- from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Tuple
30
+ from typing import BinaryIO, Iterable, Iterator, List, Optional, Sequence, Set, Tuple
29
31
 
30
32
 
31
33
  # Chosen by what imports, not os.name, so tests can emulate Windows on POSIX.
@@ -65,6 +67,23 @@ VERIFY_LEDGER_PATH = ".project/build/verify-ledger.jsonl"
65
67
  VERIFY_RESULTS = ("pass", "fail")
66
68
  VERIFY_LEDGER_SCHEMA = "gsd-path/verify-ledger/v2"
67
69
  VERIFY_BLOCK_PATTERN = re.compile(r"```bash[ \t]*\n(?P<block>.*?)```", re.DOTALL)
70
+ # The owner citation a withdrawn task tombstone must carry in its Log (#483).
71
+ # One definition shared by every gate that reads the representation.
72
+ WITHDRAWN_CITATION = re.compile(
73
+ r"^- Withdrawn — (?P<date>\d{4}-\d{2}-\d{2}): (?P<ruling>.+)$", re.MULTILINE)
74
+
75
+
76
+ def withdrawn_citations(task_text: str) -> list[str]:
77
+ """The owner citations in a withdrawn task's Log, as `date: ruling`.
78
+
79
+ The Log is the append-only record of rulings; a Withdrawn line in any
80
+ other section is not the citation the gates accept (#483).
81
+ """
82
+ body = section_body(task_text, "Log")
83
+ if body is None:
84
+ return []
85
+ return [f"{m.group('date')}: {m.group('ruling')}"
86
+ for m in WITHDRAWN_CITATION.finditer(body)]
68
87
 
69
88
  REVIEW_BULLET_HEADING_PATTERN = re.compile(r"^#{2,3} ")
70
89
  REVIEW_BULLET_FIELD_LABEL_PATTERN = re.compile(r"^- \*\*[^*]+\*\*:")
@@ -197,6 +216,19 @@ def section_body(text: str, heading: str) -> Optional[str]:
197
216
  return match.group("body") if match else None
198
217
 
199
218
 
219
+ def contract_shapes(body: str) -> Set[str]:
220
+ """The whitespace-normalized `- ` entries of an Interface contract body.
221
+
222
+ HTML comments are dropped and continuation lines join their entry. A
223
+ body that says only `None` has no shapes.
224
+ """
225
+ cleaned = re.sub(r"(?s)<!--.*?-->", "", body).strip()
226
+ if " ".join(cleaned.split()) in {"", "None", "- None"}:
227
+ return set()
228
+ entries = re.split(r"(?m)^-[ \t]+", cleaned)
229
+ return {shape for shape in (" ".join(entry.split()) for entry in entries) if shape}
230
+
231
+
200
232
  def overlapping_paths(left: Sequence[str], right: Sequence[str]) -> List[str]:
201
233
  """Return the sorted paths that two task `files` lists both cover.
202
234
 
@@ -875,8 +907,50 @@ def find_bash() -> str:
875
907
  )
876
908
 
877
909
 
910
+ def _remove_quietly(path: str) -> None:
911
+ try:
912
+ os.remove(path)
913
+ except OSError:
914
+ pass
915
+
916
+
917
+ def _command_script_dir() -> str:
918
+ """A temp directory for command scripts that cannot dirty the running tree.
919
+
920
+ TMPDIR/TEMP/TMP may point into the tree being verified, and a script file
921
+ there shows up in git status and blocks landings (#448), so fall back to a
922
+ system directory when the environment temp sits inside the current tree.
923
+ """
924
+ candidate = os.path.realpath(tempfile.gettempdir())
925
+ tree = os.path.realpath(os.getcwd())
926
+ if candidate != tree and not candidate.startswith(tree + os.sep):
927
+ return candidate
928
+ for fallback in (
929
+ os.path.join(os.environ.get("SystemRoot", r"C:\Windows"), "Temp"),
930
+ "/var/tmp",
931
+ "/tmp",
932
+ ):
933
+ resolved = os.path.realpath(fallback)
934
+ if os.path.isdir(resolved) and resolved != tree and not resolved.startswith(tree + os.sep):
935
+ return resolved
936
+ raise OSError("no temp directory outside the current worktree for a command script")
937
+
938
+
878
939
  def bash_argv(command: str) -> List[str]:
879
- return [find_bash(), "-c", command]
940
+ if os.name != "nt":
941
+ return [find_bash(), "-c", command]
942
+ # Git for Windows' bash (MSYS2) cuts an argument that carries a double
943
+ # quote near 8 KB before bash sees it, so a long Verify dies mid-script
944
+ # or silently passes when the cut lands in comments (#448). Run the exact
945
+ # bytes from a temp file outside every worktree instead.
946
+ bash = find_bash()
947
+ descriptor, path = tempfile.mkstemp(
948
+ prefix="gsd-path-command-", suffix=".sh", dir=_command_script_dir()
949
+ )
950
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as handle:
951
+ handle.write(command)
952
+ atexit.register(_remove_quietly, path)
953
+ return [bash, path]
880
954
 
881
955
 
882
956
  # cmd.exe re-parses the arguments of a .cmd or .bat target, so these would
@@ -781,7 +781,14 @@ def validate_panel_skip_receipt(path: Path, expected_mode: str) -> None:
781
781
  not isinstance(item, dict)
782
782
  or set(item) != {"family", "reason"}
783
783
  or item["family"] not in KNOWN_FAMILIES
784
- or item["reason"] not in {"parent family", "not advertised", "cap"}
784
+ # review_panel resolve writes every one of these reasons (#460);
785
+ # the receipt must validate whatever the helper can produce.
786
+ or item["reason"] not in {
787
+ "parent family",
788
+ "not advertised",
789
+ "cap",
790
+ "excluded reviewer family",
791
+ }
785
792
  for item in skipped
786
793
  ):
787
794
  raise ArchiveError(f"{path.name} has invalid skipped families")
@@ -1998,6 +2005,62 @@ def member_ship_rows(project: Path, archive: Path, ship_commit: str) -> list:
1998
2005
  return rows
1999
2006
 
2000
2007
 
2008
+ def tag_metadata_fields(project: Path, tag_ref: str) -> dict[str, str]:
2009
+ """The Key: value fields of an annotated milestone tag's message."""
2010
+ message = require_git_success(
2011
+ run_git(project, "for-each-ref", "--format=%(contents)", tag_ref),
2012
+ "inspect milestone tag",
2013
+ )
2014
+ fields: dict[str, str] = {}
2015
+ for line in message.splitlines():
2016
+ match = re.fullmatch(r"(Mode|Pull-Request|Ship|Landing): (.+)", line)
2017
+ if match:
2018
+ if match.group(1) in fields:
2019
+ raise ArchiveError("milestone tag repeats metadata")
2020
+ fields[match.group(1)] = match.group(2)
2021
+ return fields
2022
+
2023
+
2024
+ def accept_rebased_ship(
2025
+ project: Path, archive: Path, ship_commit: str, reviewed_head: str
2026
+ ) -> Optional[str]:
2027
+ """Accept the rebased ship copy a rebase merge landed, keyed by the milestone tag.
2028
+
2029
+ Historical validation only (issue #450): after a rebase merge the landed
2030
+ ship copy's parent is never the reviewed HEAD, so the strict check cannot
2031
+ pass over landed history. Acceptance requires the milestone tag to name
2032
+ exactly this commit as its Landing, the original ship commit the tag
2033
+ names to still resolve, the strict invariant re-proven against the
2034
+ original ship's parent, and the rebase replay to hold. Returns the tag's
2035
+ original Ship sha, or None when the landing is not a proven rebase: a
2036
+ landing whose originals no longer resolve anywhere is refused, because
2037
+ the per-task landing proofs elsewhere in validation pin the original
2038
+ commits and a tag receipt alone cannot carry a full validation.
2039
+ """
2040
+ tag_ref = f"refs/tags/milestone/{archive.name}"
2041
+ if run_git(project, "rev-parse", "--verify", "--quiet", tag_ref).returncode != 0:
2042
+ return None
2043
+ try:
2044
+ fields = tag_metadata_fields(project, tag_ref)
2045
+ except ArchiveError:
2046
+ return None
2047
+ if fields.get("Mode") != "pull-request":
2048
+ return None
2049
+ original = fields.get("Ship")
2050
+ if not original or fields.get("Landing") != ship_commit:
2051
+ return None
2052
+ if run_git(project, "cat-file", "-e", f"{original}^{{commit}}").returncode != 0:
2053
+ return None
2054
+ original_parent = require_git_success(
2055
+ run_git(project, "rev-parse", f"{original}^"), "resolve original ship parent"
2056
+ )
2057
+ if not reviewed_head_covers(project, reviewed_head, original_parent):
2058
+ return None
2059
+ if _common.rebase_landing_problem(project, original, ship_commit) is not None:
2060
+ return None
2061
+ return original
2062
+
2063
+
2001
2064
  def validate(repo: Path, *, historical: bool = False) -> dict:
2002
2065
  project, _, state, configured, archive, archived_files, reviewed_head = prepared_transaction(
2003
2066
  repo,
@@ -2093,11 +2156,17 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
2093
2156
 
2094
2157
  members = member_ship_rows(project, archive, ship_commit)
2095
2158
  ship_parent = require_git_success(run_git(project, "rev-parse", f"{ship_commit}^"), "resolve ship parent")
2159
+ rebased_ship: Optional[str] = None
2096
2160
  if not reviewed_head_covers(project, reviewed_head, ship_parent):
2097
- raise ArchiveError(
2098
- f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}; "
2099
- f"{_common.RUNTIME_PIN_ALLOWANCE}"
2161
+ rebased_ship = (
2162
+ accept_rebased_ship(project, archive, ship_commit, reviewed_head)
2163
+ if historical else None
2100
2164
  )
2165
+ if rebased_ship is None:
2166
+ raise ArchiveError(
2167
+ f"FINAL.md Reviewed HEAD {reviewed_head} does not match ship parent {ship_parent}; "
2168
+ f"{_common.RUNTIME_PIN_ALLOWANCE}"
2169
+ )
2101
2170
  require_canonical_commit_body(
2102
2171
  project,
2103
2172
  ship_commit,
@@ -2141,6 +2210,8 @@ def validate(repo: Path, *, historical: bool = False) -> dict:
2141
2210
  )
2142
2211
 
2143
2212
  result = {"archive": configured, "commit": ship_commit}
2213
+ if rebased_ship is not None:
2214
+ result["rebased_ship"] = rebased_ship
2144
2215
  if members:
2145
2216
  result["members"] = members
2146
2217
  return result
@@ -2178,6 +2249,15 @@ def parser() -> argparse.ArgumentParser:
2178
2249
  )
2179
2250
  validate_integrated_parser.add_argument("--repo", required=True, type=Path)
2180
2251
  validate_integrated_parser.add_argument("--slug", required=True)
2252
+ validate_integrated_parser.add_argument(
2253
+ "--historical",
2254
+ action="store_true",
2255
+ help=(
2256
+ "validate from the landed default branch after the milestone closed "
2257
+ "(the bound branch may be gone; a rebase-merged landing is proven "
2258
+ "through the milestone tag)"
2259
+ ),
2260
+ )
2181
2261
 
2182
2262
  refresh_parser = subparsers.add_parser(
2183
2263
  "refresh-origin",
@@ -2190,6 +2270,17 @@ def parser() -> argparse.ArgumentParser:
2190
2270
  help="after preflight, integrate each locked member in lock order before the ship commit",
2191
2271
  )
2192
2272
  close_members_parser.add_argument("--repo", required=True, type=Path)
2273
+ close_members_parser.add_argument(
2274
+ "--adopt-landed-member",
2275
+ action="append",
2276
+ default=[],
2277
+ metavar="MEMBER=SHA",
2278
+ help=(
2279
+ "user-approved recovery for a member whose reviewed HEAD already reached its "
2280
+ "default branch outside Path; MEMBER=SHA names the live default-branch tip "
2281
+ "to adopt as the landing"
2282
+ ),
2283
+ )
2193
2284
 
2194
2285
  integrate_parser = subparsers.add_parser(
2195
2286
  "integrate",
@@ -2197,6 +2288,16 @@ def parser() -> argparse.ArgumentParser:
2197
2288
  )
2198
2289
  integrate_parser.add_argument("--repo", required=True, type=Path)
2199
2290
  integrate_parser.add_argument("--slug", required=True)
2291
+ integrate_parser.add_argument(
2292
+ "--adopt-published-bound",
2293
+ metavar="SHA",
2294
+ help=(
2295
+ "user-approved recovery when origin's bound branch is a strict ancestor "
2296
+ "of the ship commit (a build task pushed it as a deploy pin, #464); "
2297
+ "the 40-hex SHA must equal the live remote tip exactly; the publish "
2298
+ "fast-forwards under the existing exact lease. Direct integration only"
2299
+ ),
2300
+ )
2200
2301
 
2201
2302
  abandon_parser = subparsers.add_parser(
2202
2303
  "abandon",
@@ -2225,13 +2326,34 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
2225
2326
  elif arguments.command == "abandon":
2226
2327
  result = abandon(arguments.repo, arguments.slug, arguments.reason)
2227
2328
  elif arguments.command == "validate-integrated":
2228
- result = integration.validate_integrated(arguments.repo, arguments.slug)
2329
+ result = integration.validate_integrated(
2330
+ arguments.repo, arguments.slug, historical=arguments.historical
2331
+ )
2229
2332
  elif arguments.command == "refresh-origin":
2230
2333
  result = integration.refresh_origin(arguments.repo)
2231
2334
  elif arguments.command == "close-members":
2232
- result = integration.close_members(arguments.repo)
2335
+ adopt: dict[str, str] = {}
2336
+ for item in arguments.adopt_landed_member:
2337
+ name, separator, sha = item.partition("=")
2338
+ if not name or not separator or not re.fullmatch(r"[0-9a-f]{40}", sha):
2339
+ parser_error = (
2340
+ f"--adopt-landed-member expects MEMBER=SHA (40-hex), got {item!r}"
2341
+ )
2342
+ print(parser_error, file=sys.stderr)
2343
+ return 2
2344
+ adopt[name] = sha
2345
+ result = integration.close_members(arguments.repo, adopt=adopt or None)
2233
2346
  elif arguments.command == "integrate":
2234
- result = integration.integrate(arguments.repo, arguments.slug)
2347
+ adopt_bound = arguments.adopt_published_bound
2348
+ if adopt_bound is not None:
2349
+ adopt_bound = adopt_bound.lower()
2350
+ if adopt_bound is not None and not re.fullmatch(r"[0-9a-f]{40}", adopt_bound):
2351
+ print(
2352
+ f"--adopt-published-bound expects a 40-hex SHA, got {adopt_bound!r}",
2353
+ file=sys.stderr,
2354
+ )
2355
+ return 2
2356
+ result = integration.integrate(arguments.repo, arguments.slug, adopt_bound)
2235
2357
  else:
2236
2358
  result = validate(arguments.repo)
2237
2359
  except (ArchiveError, OSError) as error: