@openrig/cli 0.4.3 → 0.4.4

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 (287) hide show
  1. package/daemon/assets/guidance/openrig-start.md +6 -0
  2. package/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md +49 -21
  3. package/daemon/assets/plugins/openrig-core/skills/openrig-user/SKILL.md +12 -12
  4. package/daemon/dist/build-info.d.ts +12 -0
  5. package/daemon/dist/build-info.d.ts.map +1 -0
  6. package/daemon/dist/build-info.js +11 -0
  7. package/daemon/dist/build-info.js.map +1 -0
  8. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.d.ts +18 -0
  9. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.d.ts.map +1 -0
  10. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.js +22 -0
  11. package/daemon/dist/db/migrations/048_queue_item_evidence_ref.js.map +1 -0
  12. package/daemon/dist/domain/feed/attention-aggregator.d.ts +31 -0
  13. package/daemon/dist/domain/feed/attention-aggregator.d.ts.map +1 -0
  14. package/daemon/dist/domain/feed/attention-aggregator.js +119 -0
  15. package/daemon/dist/domain/feed/attention-aggregator.js.map +1 -0
  16. package/daemon/dist/domain/files/file-write-service.d.ts +11 -2
  17. package/daemon/dist/domain/files/file-write-service.d.ts.map +1 -1
  18. package/daemon/dist/domain/files/file-write-service.js +77 -0
  19. package/daemon/dist/domain/files/file-write-service.js.map +1 -1
  20. package/daemon/dist/domain/hosts/fanout-contract.d.ts +33 -0
  21. package/daemon/dist/domain/hosts/fanout-contract.d.ts.map +1 -0
  22. package/daemon/dist/domain/hosts/fanout-contract.js +23 -0
  23. package/daemon/dist/domain/hosts/fanout-contract.js.map +1 -0
  24. package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts +49 -0
  25. package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts.map +1 -0
  26. package/daemon/dist/domain/hosts/hosts-registry-reader.js +165 -0
  27. package/daemon/dist/domain/hosts/hosts-registry-reader.js.map +1 -0
  28. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts +35 -0
  29. package/daemon/dist/domain/hosts/remote-daemon-http.d.ts.map +1 -0
  30. package/daemon/dist/domain/hosts/remote-daemon-http.js +105 -0
  31. package/daemon/dist/domain/hosts/remote-daemon-http.js.map +1 -0
  32. package/daemon/dist/domain/human-route-enforcer.d.ts +71 -0
  33. package/daemon/dist/domain/human-route-enforcer.d.ts.map +1 -0
  34. package/daemon/dist/domain/human-route-enforcer.js +80 -0
  35. package/daemon/dist/domain/human-route-enforcer.js.map +1 -0
  36. package/daemon/dist/domain/mission-control/audit-browse.d.ts +8 -0
  37. package/daemon/dist/domain/mission-control/audit-browse.d.ts.map +1 -1
  38. package/daemon/dist/domain/mission-control/audit-browse.js +19 -0
  39. package/daemon/dist/domain/mission-control/audit-browse.js.map +1 -1
  40. package/daemon/dist/domain/mission-control/mission-control-action-log.d.ts +1 -1
  41. package/daemon/dist/domain/mission-control/mission-control-action-log.d.ts.map +1 -1
  42. package/daemon/dist/domain/mission-control/mission-control-action-log.js +4 -0
  43. package/daemon/dist/domain/mission-control/mission-control-action-log.js.map +1 -1
  44. package/daemon/dist/domain/mission-control/mission-control-fleet-cli-capability.js +1 -1
  45. package/daemon/dist/domain/mission-control/mission-control-fleet-cli-capability.js.map +1 -1
  46. package/daemon/dist/domain/mission-control/mission-control-read-layer.js +1 -1
  47. package/daemon/dist/domain/mission-control/mission-control-read-layer.js.map +1 -1
  48. package/daemon/dist/domain/mission-control/mission-control-write-contract.d.ts +28 -0
  49. package/daemon/dist/domain/mission-control/mission-control-write-contract.d.ts.map +1 -1
  50. package/daemon/dist/domain/mission-control/mission-control-write-contract.js +125 -0
  51. package/daemon/dist/domain/mission-control/mission-control-write-contract.js.map +1 -1
  52. package/daemon/dist/domain/ps-projection.d.ts +28 -1
  53. package/daemon/dist/domain/ps-projection.d.ts.map +1 -1
  54. package/daemon/dist/domain/ps-projection.js +39 -0
  55. package/daemon/dist/domain/ps-projection.js.map +1 -1
  56. package/daemon/dist/domain/queue-repository.d.ts +25 -1
  57. package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
  58. package/daemon/dist/domain/queue-repository.js +111 -7
  59. package/daemon/dist/domain/queue-repository.js.map +1 -1
  60. package/daemon/dist/domain/review/brief-spine.d.ts +19 -0
  61. package/daemon/dist/domain/review/brief-spine.d.ts.map +1 -0
  62. package/daemon/dist/domain/review/brief-spine.js +86 -0
  63. package/daemon/dist/domain/review/brief-spine.js.map +1 -0
  64. package/daemon/dist/domain/review/compose.d.ts +228 -0
  65. package/daemon/dist/domain/review/compose.d.ts.map +1 -0
  66. package/daemon/dist/domain/review/compose.js +773 -0
  67. package/daemon/dist/domain/review/compose.js.map +1 -0
  68. package/daemon/dist/domain/review/freeze.d.ts +31 -0
  69. package/daemon/dist/domain/review/freeze.d.ts.map +1 -0
  70. package/daemon/dist/domain/review/freeze.js +257 -0
  71. package/daemon/dist/domain/review/freeze.js.map +1 -0
  72. package/daemon/dist/domain/review/gather.d.ts +100 -0
  73. package/daemon/dist/domain/review/gather.d.ts.map +1 -0
  74. package/daemon/dist/domain/review/gather.js +676 -0
  75. package/daemon/dist/domain/review/gather.js.map +1 -0
  76. package/daemon/dist/domain/review/types.d.ts +258 -0
  77. package/daemon/dist/domain/review/types.d.ts.map +1 -0
  78. package/daemon/dist/domain/review/types.js +35 -0
  79. package/daemon/dist/domain/review/types.js.map +1 -0
  80. package/daemon/dist/domain/scope/scope-approve.d.ts +64 -0
  81. package/daemon/dist/domain/scope/scope-approve.d.ts.map +1 -0
  82. package/daemon/dist/domain/scope/scope-approve.js +153 -0
  83. package/daemon/dist/domain/scope/scope-approve.js.map +1 -0
  84. package/daemon/dist/domain/scope/scope-audit.d.ts +9 -1
  85. package/daemon/dist/domain/scope/scope-audit.d.ts.map +1 -1
  86. package/daemon/dist/domain/scope/scope-audit.js +173 -1
  87. package/daemon/dist/domain/scope/scope-audit.js.map +1 -1
  88. package/daemon/dist/domain/topology/multi-rig-launcher.d.ts +49 -0
  89. package/daemon/dist/domain/topology/multi-rig-launcher.d.ts.map +1 -0
  90. package/daemon/dist/domain/topology/multi-rig-launcher.js +132 -0
  91. package/daemon/dist/domain/topology/multi-rig-launcher.js.map +1 -0
  92. package/daemon/dist/domain/topology/remote-up-leaf.d.ts +29 -0
  93. package/daemon/dist/domain/topology/remote-up-leaf.d.ts.map +1 -0
  94. package/daemon/dist/domain/topology/remote-up-leaf.js +60 -0
  95. package/daemon/dist/domain/topology/remote-up-leaf.js.map +1 -0
  96. package/daemon/dist/domain/topology/topology-manifest.d.ts +34 -0
  97. package/daemon/dist/domain/topology/topology-manifest.d.ts.map +1 -0
  98. package/daemon/dist/domain/topology/topology-manifest.js +195 -0
  99. package/daemon/dist/domain/topology/topology-manifest.js.map +1 -0
  100. package/daemon/dist/domain/types.d.ts +5 -0
  101. package/daemon/dist/domain/types.d.ts.map +1 -1
  102. package/daemon/dist/domain/types.js.map +1 -1
  103. package/daemon/dist/domain/up-command-router.d.ts +1 -1
  104. package/daemon/dist/domain/up-command-router.d.ts.map +1 -1
  105. package/daemon/dist/domain/up-command-router.js +34 -2
  106. package/daemon/dist/domain/up-command-router.js.map +1 -1
  107. package/daemon/dist/domain/user-settings/settings-store.d.ts +17 -0
  108. package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
  109. package/daemon/dist/domain/user-settings/settings-store.js +109 -0
  110. package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
  111. package/daemon/dist/domain/workspace/default-workspace-scaffold.d.ts.map +1 -1
  112. package/daemon/dist/domain/workspace/default-workspace-scaffold.js +51 -6
  113. package/daemon/dist/domain/workspace/default-workspace-scaffold.js.map +1 -1
  114. package/daemon/dist/domain/workspace/workspace-doctor.d.ts +11 -0
  115. package/daemon/dist/domain/workspace/workspace-doctor.d.ts.map +1 -1
  116. package/daemon/dist/domain/workspace/workspace-doctor.js +82 -0
  117. package/daemon/dist/domain/workspace/workspace-doctor.js.map +1 -1
  118. package/daemon/dist/routes/config.d.ts.map +1 -1
  119. package/daemon/dist/routes/config.js +15 -6
  120. package/daemon/dist/routes/config.js.map +1 -1
  121. package/daemon/dist/routes/files.d.ts.map +1 -1
  122. package/daemon/dist/routes/files.js +45 -1
  123. package/daemon/dist/routes/files.js.map +1 -1
  124. package/daemon/dist/routes/mission-control.d.ts.map +1 -1
  125. package/daemon/dist/routes/mission-control.js +67 -2
  126. package/daemon/dist/routes/mission-control.js.map +1 -1
  127. package/daemon/dist/routes/queue.d.ts +8 -2
  128. package/daemon/dist/routes/queue.d.ts.map +1 -1
  129. package/daemon/dist/routes/queue.js +44 -4
  130. package/daemon/dist/routes/queue.js.map +1 -1
  131. package/daemon/dist/routes/review.d.ts +3 -0
  132. package/daemon/dist/routes/review.d.ts.map +1 -0
  133. package/daemon/dist/routes/review.js +169 -0
  134. package/daemon/dist/routes/review.js.map +1 -0
  135. package/daemon/dist/routes/scope-approve.d.ts +3 -0
  136. package/daemon/dist/routes/scope-approve.d.ts.map +1 -0
  137. package/daemon/dist/routes/scope-approve.js +55 -0
  138. package/daemon/dist/routes/scope-approve.js.map +1 -0
  139. package/daemon/dist/routes/scope-audit.d.ts.map +1 -1
  140. package/daemon/dist/routes/scope-audit.js +28 -1
  141. package/daemon/dist/routes/scope-audit.js.map +1 -1
  142. package/daemon/dist/routes/slices.d.ts.map +1 -1
  143. package/daemon/dist/routes/slices.js +49 -9
  144. package/daemon/dist/routes/slices.js.map +1 -1
  145. package/daemon/dist/routes/terminal-ws.d.ts.map +1 -1
  146. package/daemon/dist/routes/terminal-ws.js +58 -21
  147. package/daemon/dist/routes/terminal-ws.js.map +1 -1
  148. package/daemon/dist/routes/up.d.ts.map +1 -1
  149. package/daemon/dist/routes/up.js +84 -0
  150. package/daemon/dist/routes/up.js.map +1 -1
  151. package/daemon/dist/routes/workspace.js +2 -2
  152. package/daemon/dist/server.d.ts +1 -0
  153. package/daemon/dist/server.d.ts.map +1 -1
  154. package/daemon/dist/server.js +14 -1
  155. package/daemon/dist/server.js.map +1 -1
  156. package/daemon/dist/startup.d.ts.map +1 -1
  157. package/daemon/dist/startup.js +18 -3
  158. package/daemon/dist/startup.js.map +1 -1
  159. package/daemon/docs/reference/agent-startup-guide.md +1 -1
  160. package/daemon/docs/reference/product-factory-vps-runbook.md +132 -0
  161. package/daemon/docs/reference/sdlc-conventions.md +187 -0
  162. package/daemon/specs/agents/conveyor/builder/agent.yaml +1 -1
  163. package/daemon/specs/agents/conveyor/builder/guidance/role.md +1 -0
  164. package/daemon/specs/agents/conveyor/lead/agent.yaml +1 -1
  165. package/daemon/specs/agents/conveyor/lead/guidance/role.md +1 -0
  166. package/daemon/specs/agents/conveyor/planner/agent.yaml +1 -1
  167. package/daemon/specs/agents/conveyor/planner/guidance/role.md +1 -0
  168. package/daemon/specs/agents/conveyor/reviewer/agent.yaml +1 -1
  169. package/daemon/specs/agents/conveyor/reviewer/guidance/role.md +1 -0
  170. package/daemon/specs/agents/design/product-designer/agent.yaml +1 -1
  171. package/daemon/specs/agents/design/product-designer/guidance/role.md +1 -0
  172. package/daemon/specs/agents/development/implementer/agent.yaml +1 -1
  173. package/daemon/specs/agents/development/implementer/guidance/role.md +1 -0
  174. package/daemon/specs/agents/development/qa/agent.yaml +1 -1
  175. package/daemon/specs/agents/development/qa/guidance/role.md +1 -0
  176. package/daemon/specs/agents/orchestration/orchestrator/agent.yaml +1 -1
  177. package/daemon/specs/agents/orchestration/orchestrator/guidance/role.md +1 -0
  178. package/daemon/specs/agents/product-management/pm/agent.yaml +1 -0
  179. package/daemon/specs/agents/product-management/pm/guidance/role.md +13 -0
  180. package/daemon/specs/agents/review/independent-reviewer/agent.yaml +1 -1
  181. package/daemon/specs/agents/review/independent-reviewer/guidance/role.md +1 -0
  182. package/daemon/specs/agents/shared/agent.yaml +2 -0
  183. package/daemon/specs/agents/shared/skills/core/mission-slice-sop/SKILL.md +115 -0
  184. package/daemon/specs/agents/shared/skills/core/openrig-operator/SKILL.md +4 -4
  185. package/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md +69 -23
  186. package/daemon/specs/agents/shared/skills/pods/orchestration-team/SKILL.md +2 -2
  187. package/daemon/specs/rigs/focused/pm-team/CULTURE.md +4 -0
  188. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/guidance/role.md +1 -1
  189. package/daemon/specs/rigs/launch/kernel/agents/operator/agent/startup/context.md +1 -1
  190. package/daemon/specs/rigs/preview/product-team/CULTURE.md +4 -0
  191. package/dist/build-info.d.ts +8 -0
  192. package/dist/build-info.d.ts.map +1 -0
  193. package/dist/build-info.js +11 -0
  194. package/dist/build-info.js.map +1 -0
  195. package/dist/commands/config-init-workspace.d.ts.map +1 -1
  196. package/dist/commands/config-init-workspace.js +20 -4
  197. package/dist/commands/config-init-workspace.js.map +1 -1
  198. package/dist/commands/doctor.js +2 -2
  199. package/dist/commands/doctor.js.map +1 -1
  200. package/dist/commands/expand.js +1 -1
  201. package/dist/commands/expand.js.map +1 -1
  202. package/dist/commands/file.d.ts +7 -0
  203. package/dist/commands/file.d.ts.map +1 -0
  204. package/dist/commands/file.js +77 -0
  205. package/dist/commands/file.js.map +1 -0
  206. package/dist/commands/host.d.ts +34 -0
  207. package/dist/commands/host.d.ts.map +1 -0
  208. package/dist/commands/host.js +448 -0
  209. package/dist/commands/host.js.map +1 -0
  210. package/dist/commands/proof.d.ts +54 -0
  211. package/dist/commands/proof.d.ts.map +1 -0
  212. package/dist/commands/proof.js +311 -0
  213. package/dist/commands/proof.js.map +1 -0
  214. package/dist/commands/ps.d.ts +41 -7
  215. package/dist/commands/ps.d.ts.map +1 -1
  216. package/dist/commands/ps.js +343 -131
  217. package/dist/commands/ps.js.map +1 -1
  218. package/dist/commands/queue.d.ts.map +1 -1
  219. package/dist/commands/queue.js +76 -0
  220. package/dist/commands/queue.js.map +1 -1
  221. package/dist/commands/scope.d.ts.map +1 -1
  222. package/dist/commands/scope.js +140 -3
  223. package/dist/commands/scope.js.map +1 -1
  224. package/dist/commands/send.js +13 -4
  225. package/dist/commands/send.js.map +1 -1
  226. package/dist/commands/up.d.ts +2 -0
  227. package/dist/commands/up.d.ts.map +1 -1
  228. package/dist/commands/up.js +69 -2
  229. package/dist/commands/up.js.map +1 -1
  230. package/dist/commands/workspace.js +6 -6
  231. package/dist/commands/workspace.js.map +1 -1
  232. package/dist/config-store.d.ts +4 -0
  233. package/dist/config-store.d.ts.map +1 -1
  234. package/dist/config-store.js +68 -0
  235. package/dist/config-store.js.map +1 -1
  236. package/dist/cross-host-executor.d.ts +1 -0
  237. package/dist/cross-host-executor.d.ts.map +1 -1
  238. package/dist/cross-host-executor.js +4 -1
  239. package/dist/cross-host-executor.js.map +1 -1
  240. package/dist/daemon-lifecycle.d.ts.map +1 -1
  241. package/dist/daemon-lifecycle.js +4 -1
  242. package/dist/daemon-lifecycle.js.map +1 -1
  243. package/dist/host-registry.d.ts +9 -0
  244. package/dist/host-registry.d.ts.map +1 -1
  245. package/dist/host-registry.js +32 -2
  246. package/dist/host-registry.js.map +1 -1
  247. package/dist/index.d.ts.map +1 -1
  248. package/dist/index.js +7 -0
  249. package/dist/index.js.map +1 -1
  250. package/dist/lib/file-transfer.d.ts +89 -0
  251. package/dist/lib/file-transfer.d.ts.map +1 -0
  252. package/dist/lib/file-transfer.js +300 -0
  253. package/dist/lib/file-transfer.js.map +1 -0
  254. package/dist/lib/hosts/fanout-contract.d.ts +33 -0
  255. package/dist/lib/hosts/fanout-contract.d.ts.map +1 -0
  256. package/dist/lib/hosts/fanout-contract.js +23 -0
  257. package/dist/lib/hosts/fanout-contract.js.map +1 -0
  258. package/dist/lib/scope/scope-audit.d.ts +9 -1
  259. package/dist/lib/scope/scope-audit.d.ts.map +1 -1
  260. package/dist/lib/scope/scope-audit.js +173 -1
  261. package/dist/lib/scope/scope-audit.js.map +1 -1
  262. package/dist/lib/scope/templates.d.ts +1 -0
  263. package/dist/lib/scope/templates.d.ts.map +1 -1
  264. package/dist/lib/scope/templates.js +9 -0
  265. package/dist/lib/scope/templates.js.map +1 -1
  266. package/dist/lib/scope-templates/backlog-deprecation.md +16 -0
  267. package/dist/lib/scope-templates/backlog-tech-debt.md +16 -0
  268. package/dist/lib/scope-templates/bug-fix.md +16 -0
  269. package/dist/lib/scope-templates/implementation-prd.md +33 -0
  270. package/dist/lib/scope-templates/mission-placeholder.md +1 -1
  271. package/dist/lib/scope-templates/mission-progress.md +1 -1
  272. package/dist/lib/scope-templates/mission-release.md +1 -1
  273. package/dist/lib/scope-templates/placeholder.md +11 -7
  274. package/dist/lib/scope-templates/proof.md +5 -1
  275. package/dist/lib/scope-templates/release-feature.md +14 -6
  276. package/dist/lib/scope-templates/research.md +16 -0
  277. package/dist/lib/scope-templates/slice-progress.md +1 -1
  278. package/dist/version.d.ts +5 -0
  279. package/dist/version.d.ts.map +1 -1
  280. package/dist/version.js +11 -1
  281. package/dist/version.js.map +1 -1
  282. package/package.json +1 -1
  283. package/ui/dist/assets/index-D69ZhNIr.js +598 -0
  284. package/ui/dist/assets/index-DJYun-8d.css +32 -0
  285. package/ui/dist/index.html +2 -2
  286. package/ui/dist/assets/index-DYZniQcc.js +0 -597
  287. package/ui/dist/assets/index-trcb4Yf_.css +0 -32
@@ -0,0 +1,300 @@
1
+ // OPR.0.4.4.18 — `rig file copy` core: path grammar, the FR-4 security wall,
2
+ // the closed rsync argv builder, and result classification.
3
+ //
4
+ // SECURITY CELL (plan §3 + §7a/§7a-2, guard-cleared over three rounds):
5
+ // five independent layers, every rejection BEFORE any spawn —
6
+ // 1. GRAMMAR (fail-closed): a path is remote iff it carries an explicit
7
+ // `<hostId>:` qualifier; no inference from cwd/env/session (FR-2).
8
+ // Local-prefix escapes (`/ ./ ../ ~`); an id-shaped colon prefix MUST
9
+ // resolve or fail loudly (N18-1); leading-dash operands are refused
10
+ // outright with the ./ escape taught (G18-P1 belt).
11
+ // 2. NORMALIZATION: local paths are ~-expanded + path.resolve'd and the
12
+ // RESOLVED form is what is checked and transported; remote paths
13
+ // reject ANY raw '..' SEGMENT BEFORE normalization (G18-C1: normalize
14
+ // COLLAPSES '..' in absolute paths, so a post-normalize check is dead
15
+ // code), then '.'/'//' are collapsed for the shipped form.
16
+ // 3. THE REMOTE CHARSET WALL (G18-P3 — supersedes a --protect-args pin:
17
+ // this platform's rsync is OPENRSYNC, which rejects -s; never assume
18
+ // GNU parity on macOS): remote paths must be ABSOLUTE (arch Q5) and
19
+ // match ^[A-Za-z0-9._/-]+$ — every allowed character is POSIX-shell-
20
+ // inert, so the ssh-invoked remote shell has NOTHING to interpret:
21
+ // no word-split (space excluded), no glob (*?[] excluded), no
22
+ // expansion (~ $ excluded), no metacharacters. Rejections TEACH
23
+ // (offending char named + workaround; arch note, N18-2-revisitable).
24
+ // 4. THE `--` OPERAND PIN (G18-P1 suspenders): the builder ALWAYS places
25
+ // `--` before the two path operands — a flag-shaped path can never
26
+ // parse as an option even if a future caller bypasses the refusal.
27
+ // 5. NO SHELL locally: spawn("rsync", argv) with argv arrays; the -e ssh
28
+ // string is built ONLY from registry fields (user shape-checked).
29
+ // Deletion-class flags are UNREACHABLE (property-tested, incl. the
30
+ // `-s`/--protect-args ABSENCE uniformity pin).
31
+ //
32
+ // DENY WALL (FR-4, arch-endorsed SHORT CLOSED NAMED list — extension = a
33
+ // ruling, never a growing blocklist): local side denies resolved paths under
34
+ // ~/.openrig (live rig state — crash-safety class), ~/.ssh, ~/.codex,
35
+ // ~/.claude (credential/shared-singleton classes); remote side denies any
36
+ // path CONTAINING one of those dot-directory segments (conservative-over-
37
+ // broad: /srv/backup/.ssh/x is still a credentials dir; false positives are
38
+ // loud + revisitable, false negatives are the corruption class).
39
+ import os from "node:os";
40
+ import path from "node:path";
41
+ import { spawn as nodeSpawn } from "node:child_process";
42
+ import { loadHostRegistry, resolveHost } from "./../host-registry.js";
43
+ import { looksLikePermissionGate } from "./../cross-host-executor.js";
44
+ import { getDefaultOpenRigPath, getOpenRigHome } from "./../openrig-compat.js";
45
+ const HOST_ID_SHAPE = /^[A-Za-z0-9_-]+$/;
46
+ const SSH_USER_SHAPE = /^[A-Za-z0-9._-]+$/;
47
+ export const REMOTE_PATH_CHARSET = /^[A-Za-z0-9._/-]+$/;
48
+ /** The FR-4 short closed named deny list (dot-directory segments). */
49
+ export const DENIED_SEGMENTS = [".openrig", ".ssh", ".codex", ".claude"];
50
+ export function parseFilePathArg(raw) {
51
+ if (raw === "")
52
+ return { ok: false, error: "empty path operand" };
53
+ if (raw.startsWith("-")) {
54
+ // G18-P1 belt: a flag-shaped operand is refused outright; the ./ escape
55
+ // is the honest way to address such a file.
56
+ return {
57
+ ok: false,
58
+ error: `path operand '${raw}' begins with '-' and could be mistaken for an option. If this is a real local file, address it with the ./ prefix (./${raw}).`,
59
+ };
60
+ }
61
+ // Local-prefix escapes: these are ALWAYS local, colon or not (N18-1).
62
+ if (raw.startsWith("/") || raw.startsWith("./") || raw.startsWith("../") || raw.startsWith("~")) {
63
+ return { ok: true, arg: { kind: "local", path: raw } };
64
+ }
65
+ const colon = raw.indexOf(":");
66
+ if (colon >= 0) {
67
+ const prefix = raw.slice(0, colon);
68
+ const rest = raw.slice(colon + 1);
69
+ if (HOST_ID_SHAPE.test(prefix)) {
70
+ // Id-shaped prefix: this IS a host qualifier — it must resolve or the
71
+ // command fails loudly (fail-closed; never a silent local fallback).
72
+ if (rest === "")
73
+ return { ok: false, error: `remote path missing after '${prefix}:' — expected ${prefix}:<absolute-path>` };
74
+ return { ok: true, arg: { kind: "remote", hostId: prefix, path: rest } };
75
+ }
76
+ // Non-id-shaped prefix (contains '/', empty, etc.): scp's lived
77
+ // behavior — the whole operand is a local path.
78
+ return { ok: true, arg: { kind: "local", path: raw } };
79
+ }
80
+ return { ok: true, arg: { kind: "local", path: raw } };
81
+ }
82
+ // ── the deny wall + normalization ──────────────────────────────────────────
83
+ function expandLocalTilde(p) {
84
+ if (p === "~")
85
+ return os.homedir();
86
+ if (p.startsWith("~/"))
87
+ return path.join(os.homedir(), p.slice(2));
88
+ return p;
89
+ }
90
+ /** Local side: ~-expand, resolve (traversal collapses here), then deny-check
91
+ * the RESOLVED form. The resolved form is what rsync receives. */
92
+ export function checkLocalPath(raw) {
93
+ const resolved = path.resolve(expandLocalTilde(raw));
94
+ const home = os.homedir();
95
+ const activeOpenRigHome = path.resolve(getOpenRigHome());
96
+ const activeHostsRegistry = path.resolve(getDefaultOpenRigPath("hosts.yaml"));
97
+ if (resolved === activeHostsRegistry) {
98
+ return {
99
+ ok: false,
100
+ error: `refused: '${raw}' resolves to the active hosts registry (${activeHostsRegistry}) — the registry itself is not a copy source/target in v0. This is the FR-4 default-deny wall (live OpenRig state; extension requires a ruling).`,
101
+ };
102
+ }
103
+ for (const segment of DENIED_SEGMENTS) {
104
+ const deniedRoot = path.join(home, segment);
105
+ if (resolved === deniedRoot || resolved.startsWith(deniedRoot + path.sep)) {
106
+ return {
107
+ ok: false,
108
+ error: `refused: '${raw}' resolves into ${deniedRoot} — ${segment === ".openrig" ? "live OpenRig state (incl. the hosts registry) is not a copy source/target in v0 (crash-safety)" : "credential/agent-home directories are not copy sources/targets"}. This is the FR-4 default-deny wall (a short closed list; extension requires a ruling).`,
109
+ };
110
+ }
111
+ }
112
+ if (activeOpenRigHome !== path.join(home, ".openrig") &&
113
+ (resolved === activeOpenRigHome || resolved.startsWith(activeOpenRigHome + path.sep))) {
114
+ return {
115
+ ok: false,
116
+ error: `refused: '${raw}' resolves into the active OPENRIG_HOME (${activeOpenRigHome}) — live OpenRig state is not a copy source/target in v0 (crash-safety). This is the FR-4 default-deny wall (a short closed list; extension requires a ruling).`,
117
+ };
118
+ }
119
+ return { ok: true, normalizedPath: resolved };
120
+ }
121
+ /** Remote side: posix-normalize, then the wall — absolute-only (arch Q5),
122
+ * no surviving traversal, the shell-inert charset (G18-P3), and the
123
+ * denied-segment list. Errors TEACH (offending char + workaround). */
124
+ export function checkRemotePath(raw, hostId) {
125
+ const charsetViolation = [...raw].find((ch) => !REMOTE_PATH_CHARSET.test(ch));
126
+ if (charsetViolation !== undefined) {
127
+ const shown = charsetViolation === " " ? "a space" : `'${charsetViolation}'`;
128
+ return {
129
+ ok: false,
130
+ error: `refused: remote path for '${hostId}' contains ${shown}, which is outside the v0 remote-path character set [A-Za-z0-9._/-]. v0 keeps remote paths shell-inert by construction${charsetViolation === " " ? " — use a space-free staging path and rename on the far side" : ""}. (v0-conservative; widening is a ruling.)`,
131
+ };
132
+ }
133
+ if (!raw.startsWith("/")) {
134
+ return { ok: false, error: `refused: remote paths are ABSOLUTE-only in v0 (got '${raw}' for host '${hostId}') — remote ~/relative resolution would smuggle in remote-side semantics. Use the full path.` };
135
+ }
136
+ // G18-C1 (guard code-review): the RAW path is checked for '..' BEFORE
137
+ // normalization — posix.normalize fully COLLAPSES '..' in absolute paths
138
+ // (/srv/../../etc/passwd → /etc/passwd), so a post-normalize "surviving
139
+ // .." check is dead code and the climb escapes silently. In an
140
+ // absolute-only grammar a '..' segment is never necessary; ANY '..'
141
+ // rejects outright (no benign/escaping distinction — remote symlinks
142
+ // make that unreasonable to judge from here).
143
+ if (raw.split("/").includes("..")) {
144
+ return { ok: false, error: `refused: remote path '${raw}' contains a '..' traversal segment (FR-4). Remote paths are absolute-only — write the final path without '..'.` };
145
+ }
146
+ const normalized = path.posix.normalize(raw);
147
+ for (const part of normalized.split("/")) {
148
+ if (DENIED_SEGMENTS.includes(part)) {
149
+ return {
150
+ ok: false,
151
+ error: `refused: remote path '${raw}' contains the denied directory segment '${part}' (credential/rig-state class; conservative-over-broad by design — FR-4's short closed list).`,
152
+ };
153
+ }
154
+ }
155
+ return { ok: true, normalizedPath: normalized };
156
+ }
157
+ function resolveRemoteSide(hostId, rawPath, deps) {
158
+ const loader = deps.registryLoader ?? loadHostRegistry;
159
+ const reg = loader();
160
+ if (!reg.ok)
161
+ return { ok: false, error: reg.error, code: "registry_error" };
162
+ const resolved = resolveHost(reg.registry, hostId);
163
+ if (!resolved.ok)
164
+ return { ok: false, error: resolved.error, code: "unknown_host" };
165
+ if (resolved.host.transport !== "ssh") {
166
+ return {
167
+ ok: false,
168
+ error: `host '${hostId}' uses transport '${resolved.host.transport}' — v0 file movement is ssh/rsync only. Register an ssh entry for this host or move the file another way.`,
169
+ code: "unsupported_transport",
170
+ };
171
+ }
172
+ if (resolved.host.user !== undefined && !SSH_USER_SHAPE.test(resolved.host.user)) {
173
+ return { ok: false, error: `registry entry '${hostId}' has a user field outside [A-Za-z0-9._-] — refusing to place it on an ssh command line`, code: "invalid_registry_user" };
174
+ }
175
+ const pathCheck = checkRemotePath(rawPath, hostId);
176
+ if (!pathCheck.ok)
177
+ return { ok: false, error: pathCheck.error, code: "denied_path" };
178
+ return { ok: true, side: { kind: "remote", path: pathCheck.normalizedPath, host: resolved.host } };
179
+ }
180
+ /** Validate the whole invocation — EVERY rejection here happens before any
181
+ * spawn (the fail-closed set; spawn-spy-asserted in tests). */
182
+ export function planFileCopy(rawSrc, rawDst, opts = {}) {
183
+ const srcParse = parseFilePathArg(rawSrc);
184
+ if (!srcParse.ok)
185
+ return { ok: false, error: srcParse.error, code: "bad_operand" };
186
+ const dstParse = parseFilePathArg(rawDst);
187
+ if (!dstParse.ok)
188
+ return { ok: false, error: dstParse.error, code: "bad_operand" };
189
+ if (srcParse.arg.kind === "remote" && dstParse.arg.kind === "remote") {
190
+ return { ok: false, error: "remote-to-remote is not in v0; pull then push (two explicit transfers — never a silent relay through this host)", code: "remote_to_remote" };
191
+ }
192
+ const sides = [];
193
+ for (const parsed of [srcParse.arg, dstParse.arg]) {
194
+ if (parsed.kind === "remote") {
195
+ const side = resolveRemoteSide(parsed.hostId, parsed.path, opts);
196
+ if (!side.ok)
197
+ return { ok: false, error: side.error, code: side.code };
198
+ sides.push(side.side);
199
+ }
200
+ else {
201
+ const check = checkLocalPath(parsed.path);
202
+ if (!check.ok)
203
+ return { ok: false, error: check.error, code: "denied_path" };
204
+ sides.push({ kind: "local", path: check.normalizedPath });
205
+ }
206
+ }
207
+ return { ok: true, plan: { src: sides[0], dst: sides[1], dryRun: opts.dryRun === true } };
208
+ }
209
+ // ── the CLOSED argv builder ─────────────────────────────────────────────────
210
+ function sideOperand(side) {
211
+ if (side.kind === "local")
212
+ return side.path;
213
+ return `${side.host.target}:${side.path}`;
214
+ }
215
+ /** The ONLY place rsync arguments are assembled. Properties (test-asserted):
216
+ * no deletion-class flag ever present; NO -s/--protect-args (uniform argv
217
+ * across GNU rsync and openrsync — the G18-P3 absence pin); `--` ALWAYS
218
+ * precedes the two path operands; option positions are fixed. */
219
+ export function buildRsyncArgv(plan) {
220
+ const argv = ["--archive", "--itemize-changes", "--stats"];
221
+ if (plan.dryRun)
222
+ argv.push("--dry-run");
223
+ const remote = plan.src.kind === "remote" ? plan.src : plan.dst.kind === "remote" ? plan.dst : null;
224
+ if (remote) {
225
+ const sshParts = ["ssh", "-o", "ConnectTimeout=10"];
226
+ if (remote.host.user)
227
+ sshParts.push("-l", remote.host.user);
228
+ argv.push("-e", sshParts.join(" "));
229
+ }
230
+ argv.push("--", sideOperand(plan.src), sideOperand(plan.dst));
231
+ return argv;
232
+ }
233
+ const CONNECTION_FAILURE_PATTERNS = [
234
+ /connection refused/i,
235
+ /connection timed out/i,
236
+ /connection unexpectedly closed/i,
237
+ /could not resolve hostname/i,
238
+ /no route to host/i,
239
+ /operation timed out/i,
240
+ ];
241
+ function parseStats(stdout) {
242
+ const bytes = stdout.match(/Total transferred file size:\s*([\d,.]+)\s*bytes/i);
243
+ const files = stdout.match(/Number of (?:regular )?files transferred:\s*([\d,.]+)/i);
244
+ const num = (m) => (m ? Number(m[1].replace(/[,.](?=\d{3})/g, "").replace(/,/g, "")) : undefined);
245
+ return { bytesTransferred: num(bytes), filesTransferred: num(files) };
246
+ }
247
+ /** rsync's classes are the executor taxonomy MINUS remote-daemon-unreachable
248
+ * (rsync needs no daemon — arch ruling 2). */
249
+ export function classifyRsyncResult(exitCode, stdout, stderr) {
250
+ if (exitCode === 0) {
251
+ return { ok: true, failedStep: "none", exitCode, stdout, stderr, ...parseStats(stdout) };
252
+ }
253
+ if (looksLikePermissionGate(stderr)) {
254
+ return {
255
+ ok: false,
256
+ failedStep: "permission-gate",
257
+ exitCode,
258
+ stdout,
259
+ stderr,
260
+ hint: "See openrig-work/field-notes/2026-04-29-l4-3-d6-claude-keychain-over-ssh-diagnostic.md for guidance on Keychain-over-SSH issues.",
261
+ };
262
+ }
263
+ if (exitCode === 255 || CONNECTION_FAILURE_PATTERNS.some((re) => re.test(stderr))) {
264
+ return { ok: false, failedStep: "ssh-unreachable", exitCode, stdout, stderr };
265
+ }
266
+ return { ok: false, failedStep: "remote-command-failed", exitCode, stdout, stderr };
267
+ }
268
+ export async function runFileCopy(plan, deps = {}) {
269
+ const argv = buildRsyncArgv(plan);
270
+ const spawn = deps.spawn ?? nodeSpawn;
271
+ const child = spawn("rsync", argv, { stdio: ["ignore", "pipe", "pipe"] });
272
+ let stdout = "";
273
+ let stderr = "";
274
+ let spawnFailed = null;
275
+ child.stdout?.on("data", (chunk) => {
276
+ stdout += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
277
+ });
278
+ child.stderr?.on("data", (chunk) => {
279
+ stderr += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
280
+ });
281
+ const exitCode = await new Promise((resolve) => {
282
+ child.on("error", (err) => {
283
+ spawnFailed = err;
284
+ resolve(null);
285
+ });
286
+ child.on("close", (code) => resolve(code));
287
+ });
288
+ if (spawnFailed !== null && spawnFailed.code === "ENOENT") {
289
+ return {
290
+ ok: false,
291
+ failedStep: "rsync-missing",
292
+ exitCode: null,
293
+ stdout,
294
+ stderr,
295
+ hint: "rsync is not installed locally. macOS: `brew install rsync` (or use the system openrsync ≥ the one shipped with your OS); Linux: install the rsync package.",
296
+ };
297
+ }
298
+ return classifyRsyncResult(exitCode, stdout, stderr);
299
+ }
300
+ //# sourceMappingURL=file-transfer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-transfer.js","sourceRoot":"","sources":["../../src/lib/file-transfer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /** The local host's id literal in every P3/P4 payload (arch pin: defined
2
+ * ONCE here, imported everywhere, never retyped). Matches slice 11's
3
+ * topology aggregate literal. */
4
+ export declare const LOCAL_HOST_ID = "local";
5
+ /** CLOSED enum (arch pin A). `unsupported-transport` is R15-2's explicit
6
+ * class (an SSH-declared host is never a silently thinner payload);
7
+ * `auth-failed` is distinct from `unreachable` because the operator fix
8
+ * differs (rotate/set the bearer vs check the host). Extending this set =
9
+ * a cross-PRD contract change (slices 15 AND 21 re-review). */
10
+ export type PerHostStatusKind = "ok" | "unreachable" | "unsupported-transport" | "auth-failed";
11
+ export interface PerHostStatus {
12
+ hostId: string;
13
+ status: PerHostStatusKind;
14
+ /** Honest failure detail, rendered muted next to the host chip/row. */
15
+ error?: string;
16
+ /** ADDITIVE OPTIONAL detail (arch adjudication): the shipped CLI
17
+ * FailedStep vocabulary when a transport-level step classified the
18
+ * failure. Never load-bearing — `status` is the contract. */
19
+ failedStep?: string;
20
+ }
21
+ /** items + per-host status: EVERY subscribed host appears in `hosts` on
22
+ * EVERY payload — ok, unreachable, auth-failed, or unsupported-transport.
23
+ * Absence is a contract violation (omission-proof; never all-or-nothing,
24
+ * never silent thinning). */
25
+ export interface AggregatedPayload<T> {
26
+ items: T[];
27
+ hosts: PerHostStatus[];
28
+ }
29
+ /** Contract-level completeness predicate (arch pin B: asserted as near to
30
+ * the contract as tests allow): true iff every expected host id appears
31
+ * exactly once in the payload's hosts array. */
32
+ export declare function hostsCovered(payload: AggregatedPayload<unknown>, expectedHostIds: string[]): boolean;
33
+ //# sourceMappingURL=fanout-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fanout-contract.d.ts","sourceRoot":"","sources":["../../../src/lib/hosts/fanout-contract.ts"],"names":[],"mappings":"AAUA;;kCAEkC;AAClC,eAAO,MAAM,aAAa,UAAU,CAAC;AAErC;;;;gEAIgE;AAChE,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,aAAa,GAAG,uBAAuB,GAAG,aAAa,CAAC;AAE/F,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;kEAE8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;8BAG8B;AAC9B,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED;;iDAEiD;AACjD,wBAAgB,YAAY,CAAC,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAIpG"}
@@ -0,0 +1,23 @@
1
+ // OPR.0.4.4.15 — THE shared intra-P4 fan-out payload contract.
2
+ //
3
+ // ONE contract for the whole P4 packet (arch adjudication 2026-07-05,
4
+ // cross-PRD interface cell): slice 15's aggregated For-You attention feed
5
+ // AND slice 21's `rig ps --all-hosts` rollup both speak AggregatedPayload —
6
+ // items plus a per-host structured status array. Slice 15 defines this
7
+ // module (first lander, per the whichever-builds-first rule named in both
8
+ // PRDs); slice 21 IMPORTS it. Any change here is a CROSS-PRD contract
9
+ // change requiring re-review of BOTH slices — never driver discretion.
10
+ /** The local host's id literal in every P3/P4 payload (arch pin: defined
11
+ * ONCE here, imported everywhere, never retyped). Matches slice 11's
12
+ * topology aggregate literal. */
13
+ export const LOCAL_HOST_ID = "local";
14
+ /** Contract-level completeness predicate (arch pin B: asserted as near to
15
+ * the contract as tests allow): true iff every expected host id appears
16
+ * exactly once in the payload's hosts array. */
17
+ export function hostsCovered(payload, expectedHostIds) {
18
+ const seen = new Map();
19
+ for (const h of payload.hosts)
20
+ seen.set(h.hostId, (seen.get(h.hostId) ?? 0) + 1);
21
+ return expectedHostIds.every((id) => seen.get(id) === 1) && payload.hosts.length === expectedHostIds.length;
22
+ }
23
+ //# sourceMappingURL=fanout-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fanout-contract.js","sourceRoot":"","sources":["../../../src/lib/hosts/fanout-contract.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,EAAE;AACF,sEAAsE;AACtE,0EAA0E;AAC1E,4EAA4E;AAC5E,uEAAuE;AACvE,0EAA0E;AAC1E,sEAAsE;AACtE,uEAAuE;AAEvE;;kCAEkC;AAClC,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AA6BrC;;iDAEiD;AACjD,MAAM,UAAU,YAAY,CAAC,OAAmC,EAAE,eAAyB;IACzF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK;QAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,MAAM,CAAC;AAC9G,CAAC"}
@@ -1,6 +1,6 @@
1
1
  export type RailStatus = "present" | "missing" | "malformed" | "readme-only";
2
2
  export type FindingSeverity = "high" | "medium" | "low" | "info";
3
- export type FindingKind = "missing_progress" | "registration_ghost" | "missing_id" | "id_convention_violation" | "orphan_progress" | "missing_mission_brief" | "malformed_mission_brief" | "missing_mission_notes" | "missing_proof" | "progress_not_updated_on_commit";
3
+ export type FindingKind = "missing_progress" | "registration_ghost" | "missing_id" | "id_convention_violation" | "orphan_progress" | "missing_mission_brief" | "malformed_mission_brief" | "missing_mission_notes" | "missing_proof" | "progress_not_updated_on_commit" | "proof_artifact_c1_invalid" | "missing_impl_prd" | "missing_intent_section" | "mini_requirements_missing_or_malformed" | "proof_contract_missing_or_malformed" | "ui_slice_missing_mockup";
4
4
  export interface AuditFinding {
5
5
  kind: FindingKind;
6
6
  severity: FindingSeverity;
@@ -30,11 +30,19 @@ export interface ScopeAuditInput {
30
30
  sliceStatus?: string | null;
31
31
  sliceTouchedByRecentCommit?: boolean;
32
32
  progressTouchedByRecentCommit?: boolean;
33
+ proofArtifacts?: Array<{
34
+ path: string;
35
+ frontmatterRaw: string | null;
36
+ }>;
37
+ implementationPrdExists?: boolean;
38
+ readmeContent?: string | null;
39
+ implementationPrdContent?: string | null;
33
40
  }
34
41
  export interface ScopeAuditResult {
35
42
  railStatus: RailStatus;
36
43
  findings: AuditFinding[];
37
44
  frontmatterError: string | null;
38
45
  }
46
+ export declare const MISSION_BRIEF_HEADERS: string[];
39
47
  export declare function classifyScopeItem(input: ScopeAuditInput): ScopeAuditResult;
40
48
  //# sourceMappingURL=scope-audit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope-audit.d.ts","sourceRoot":"","sources":["../../../src/lib/scope/scope-audit.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AACjE,MAAM,MAAM,WAAW,GACnB,kBAAkB,GAClB,oBAAoB,GACpB,YAAY,GACZ,yBAAyB,GACzB,iBAAiB,GACjB,uBAAuB,GACvB,yBAAyB,GACzB,uBAAuB,GACvB,eAAe,GACf,gCAAgC,CAAC;AAErC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAQ5B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA2DD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAwK1E"}
1
+ {"version":3,"file":"scope-audit.d.ts","sourceRoot":"","sources":["../../../src/lib/scope/scope-audit.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AACjE,MAAM,MAAM,WAAW,GACnB,kBAAkB,GAClB,oBAAoB,GACpB,YAAY,GACZ,yBAAyB,GACzB,iBAAiB,GACjB,uBAAuB,GACvB,yBAAyB,GACzB,uBAAuB,GACvB,eAAe,GACf,gCAAgC,GAGhC,2BAA2B,GAC3B,kBAAkB,GAIlB,wBAAwB,GACxB,wCAAwC,GACxC,qCAAqC,GACrC,yBAAyB,CAAC;AAE9B,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAQ5B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,6BAA6B,CAAC,EAAE,OAAO,CAAC;IAMxC,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAGxE,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAKlC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAMD,eAAO,MAAM,qBAAqB,UAA4E,CAAC;AA2H/G,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAqR1E"}
@@ -1,6 +1,10 @@
1
1
  import * as YAML from "yaml";
2
2
  import { isMissionDotId, isSliceDotId } from "./dot-id.js";
3
- const MISSION_BRIEF_HEADERS = ["What & why", "Building", "Progress", "Proven", "Needs you", "Pointers"];
3
+ // OPR.0.4.4.20 FR-8: exported so the review brief-spine writer conforms to
4
+ // the SAME pinned exact-order schema this audit enforces (parity by
5
+ // construction — the generated output can never trip malformed_mission_brief
6
+ // without this file changing too).
7
+ export const MISSION_BRIEF_HEADERS = ["What & why", "Building", "Progress", "Proven", "Needs you", "Pointers"];
4
8
  function childPath(parent, child) {
5
9
  return parent.endsWith("/") ? `${parent}${child}` : `${parent}/${child}`;
6
10
  }
@@ -44,6 +48,71 @@ function statusRequiresProof(status) {
44
48
  || normalized.includes("proven")
45
49
  || normalized.includes("promoted");
46
50
  }
51
+ // OPR.0.4.4.19 FR-10 (C7) — the statusRequiresProof pattern applied one
52
+ // phase earlier: a slice whose status implies SPECCED/BUILDING (or later)
53
+ // must carry IMPLEMENTATION-PRD.md at its root ("no build without a
54
+ // specifying doc"). Shaping / pre-spec statuses are NOT violations.
55
+ function statusRequiresSpec(status) {
56
+ if (!status)
57
+ return false;
58
+ const normalized = status.toLowerCase().trim();
59
+ return normalized.includes("build")
60
+ || normalized.includes("implement")
61
+ || normalized.includes("in-progress")
62
+ || normalized.includes("review")
63
+ || normalized.includes("qa")
64
+ || statusRequiresProof(normalized);
65
+ }
66
+ // OPR.0.4.4.19 FR-10 (C1) — the ratified closed sets (BR-4; source of truth
67
+ // for the drop path lives in the CLI proof command; this mirrored file
68
+ // carries its own copy because both scope-audit copies must stay
69
+ // self-contained + byte-identical. Extending the sets is a pm-lead
70
+ // convention change, made in BOTH places).
71
+ const C1_REQUIRED_FIELDS = ["slice", "candidate_sha", "artifact_type", "verdict", "money_evidence"];
72
+ const C1_ARTIFACT_TYPES = ["guard", "qa", "rev1-r1", "rev1-r2", "adjudication"];
73
+ const C1_VERDICTS = ["CLEAR", "BLOCKING", "CONCERNING", "PASS", "NOT-CLEAR"];
74
+ /** Validate one proof artifact's raw frontmatter against the C1 contract.
75
+ * Returns null when valid; else the human-readable problem list. */
76
+ function c1ArtifactProblems(frontmatterRaw) {
77
+ if (frontmatterRaw === null) {
78
+ return [`no frontmatter header at all (required C1 fields: ${C1_REQUIRED_FIELDS.join(", ")})`];
79
+ }
80
+ let parsed = null;
81
+ try {
82
+ parsed = YAML.parse(frontmatterRaw);
83
+ }
84
+ catch (err) {
85
+ return [`frontmatter fails to parse: ${err instanceof Error ? err.message : String(err)}`];
86
+ }
87
+ const fm = parsed && typeof parsed === "object" && !Array.isArray(parsed)
88
+ ? parsed
89
+ : {};
90
+ const problems = [];
91
+ const missing = C1_REQUIRED_FIELDS.filter((f) => typeof fm[f] !== "string" || fm[f].trim().length === 0);
92
+ if (missing.length > 0)
93
+ problems.push(`missing field(s): ${missing.join(", ")}`);
94
+ if (typeof fm.artifact_type === "string" && !C1_ARTIFACT_TYPES.includes(fm.artifact_type)) {
95
+ problems.push(`artifact_type '${fm.artifact_type}' not in closed set (${C1_ARTIFACT_TYPES.join(" | ")})`);
96
+ }
97
+ if (typeof fm.verdict === "string" && !C1_VERDICTS.includes(fm.verdict)) {
98
+ problems.push(`verdict '${fm.verdict}' not in closed set (${C1_VERDICTS.join(" | ")})`);
99
+ }
100
+ return problems.length > 0 ? problems : null;
101
+ }
102
+ // OPR.0.4.4.23 — markdown H2-section helpers for the convention-section
103
+ // advisories. Headings are literals owned by this file ("Intent",
104
+ // "Proof contract", "Intent visual") — no user input reaches the regex.
105
+ function hasH2(content, heading) {
106
+ return new RegExp(`^##\\s+${heading}\\s*$`, "m").test(content);
107
+ }
108
+ function h2Body(content, heading) {
109
+ const match = new RegExp(`^##\\s+${heading}\\s*$`, "m").exec(content);
110
+ if (!match)
111
+ return null;
112
+ const rest = content.slice(match.index + match[0].length);
113
+ const next = rest.search(/^##\s+/m);
114
+ return next === -1 ? rest : rest.slice(0, next);
115
+ }
47
116
  // A mission is ACTIVE unless its status names a terminal / archived state.
48
117
  // The SOP wants missing_mission_notes to fire for an ACTIVE mission only — a
49
118
  // shipped/archived mission no longer needs a live continuity file. No status
@@ -198,6 +267,109 @@ export function classifyScopeItem(input) {
198
267
  remediation: "Add PROOF.md at the slice root and put verification artifacts under proof/ per the slice-closeout SOP.",
199
268
  });
200
269
  }
270
+ // OPR.0.4.4.19 FR-10 (C1 backstop) — flag proof/ artifacts missing the
271
+ // C1 header or carrying out-of-set values. The backstop catches what
272
+ // bypassed the drop path (raw file writes are never gated at write
273
+ // time; this is where they surface).
274
+ for (const artifact of input.proofArtifacts ?? []) {
275
+ const problems = c1ArtifactProblems(artifact.frontmatterRaw);
276
+ if (problems) {
277
+ findings.push({
278
+ kind: "proof_artifact_c1_invalid",
279
+ severity: "medium",
280
+ path: artifact.path,
281
+ message: `Proof artifact violates the C1 header contract: ${problems.join("; ")}.`,
282
+ remediation: `Re-drop via: rig proof add <slice> --artifact-type <${C1_ARTIFACT_TYPES.join("|")}> --verdict <${C1_VERDICTS.join("|")}> --candidate-sha <sha> --money-evidence "<line>" — or add the missing frontmatter fields in place.`,
283
+ });
284
+ }
285
+ }
286
+ // OPR.0.4.4.19 FR-10 (C7 backstop) — a specced/building-or-later slice
287
+ // must carry IMPLEMENTATION-PRD.md (the pinned expected filename) at its
288
+ // root. Status-gated: shaping is not a violation. Inert when the caller
289
+ // provided no fs context (implementationPrdExists undefined).
290
+ if (input.implementationPrdExists === false && statusRequiresSpec(status)) {
291
+ findings.push({
292
+ kind: "missing_impl_prd",
293
+ severity: "medium",
294
+ path: childPath(input.path, "IMPLEMENTATION-PRD.md"),
295
+ message: `Slice status "${status}" implies specced/building but there is no IMPLEMENTATION-PRD.md at the slice root (C7: no build without a specifying doc).`,
296
+ remediation: "Author (or relocate) the slice's IMPLEMENTATION-PRD.md at the slice root — the pinned C7 filename — before building continues.",
297
+ });
298
+ }
299
+ // OPR.0.4.4.23 — SDLC convention-section advisories (SSOT:
300
+ // docs/reference/sdlc-conventions.md). Structurally fail-open: the
301
+ // audit command flips its exit code on HIGH findings only, and these
302
+ // are low/info by construction — they record and advise, never gate.
303
+ // Inert when the caller provided no content context (undefined inputs).
304
+ if (typeof input.readmeContent === "string" && !hasH2(input.readmeContent, "Intent")) {
305
+ findings.push({
306
+ kind: "missing_intent_section",
307
+ severity: "low",
308
+ path: childPath(input.path, "README.md"),
309
+ message: "Slice README has no `## Intent` section, so the Living Notes UI cannot project its INTENT section.",
310
+ remediation: "Add `## Intent` carrying the recorded intent verbatim (conventions SSOT: docs/reference/sdlc-conventions.md; `rig scope slice create` scaffolds it).",
311
+ });
312
+ }
313
+ const contractSource = typeof input.implementationPrdContent === "string"
314
+ ? { content: input.implementationPrdContent, path: childPath(input.path, "IMPLEMENTATION-PRD.md") }
315
+ : typeof input.readmeContent === "string"
316
+ ? { content: input.readmeContent, path: childPath(input.path, "README.md") }
317
+ : null;
318
+ if (contractSource) {
319
+ // OPR.0.4.4.23 rev1-r2 B1 (PRD L34 guard F-3): well-formed
320
+ // `## Mini-requirements` — the PLAN leg of the Living Notes
321
+ // projection. Same source-selection policy as the proof contract
322
+ // (PRD preferred, README fallback). Well-formed = the heading plus
323
+ // at least one numbered list item; a heading over prose-only is
324
+ // malformed (no usable requirements projection).
325
+ const miniBody = h2Body(contractSource.content, "Mini-requirements");
326
+ const hasNumberedItem = miniBody !== null && /^\s*\d+\.\s+\S/m.test(miniBody);
327
+ if (!hasNumberedItem) {
328
+ findings.push({
329
+ kind: "mini_requirements_missing_or_malformed",
330
+ severity: "low",
331
+ path: contractSource.path,
332
+ message: miniBody === null
333
+ ? "No `## Mini-requirements` section — the Living Notes PLAN section has no concise requirements tier to project."
334
+ : "`## Mini-requirements` carries no numbered items (`1. …`) — the one-glance requirement tier is where approval starts.",
335
+ remediation: "Add `## Mini-requirements` with a numbered list of observable outcomes (conventions SSOT: docs/reference/sdlc-conventions.md; for a small slice this may BE the whole PRD).",
336
+ });
337
+ }
338
+ const contractBody = h2Body(contractSource.content, "Proof contract");
339
+ const hasCheckboxItem = contractBody !== null && /^\s*-\s*\[[ xX]\]/m.test(contractBody);
340
+ if (!hasCheckboxItem) {
341
+ findings.push({
342
+ kind: "proof_contract_missing_or_malformed",
343
+ severity: "low",
344
+ path: contractSource.path,
345
+ message: contractBody === null
346
+ ? "No `## Proof contract` section — the DELIVERED pairing has no promised-deliverables source to join proof against."
347
+ : "`## Proof contract` carries no checkbox deliverables (`- [ ] …`) — the DELIVERED pairing joins proof against those items.",
348
+ remediation: "Add `## Proof contract` with one checkbox line per promised deliverable, written as an observable outcome (conventions SSOT: docs/reference/sdlc-conventions.md).",
349
+ });
350
+ }
351
+ if (typeof input.readmeContent === "string") {
352
+ const visualBody = h2Body(input.readmeContent, "Intent visual");
353
+ const isUiSlice = visualBody !== null && !/\bN\/A\b/i.test(visualBody);
354
+ // rev1-r2 B2: a mockup is PRESENT only via a real markdown
355
+ // image/media ref in `## Intent visual` or an explicit plannedRef
356
+ // token in the proof contract. Generic prose containing the word
357
+ // "mockup" (the scaffold placeholder says "name their planned
358
+ // mockup") is NOT a reference and must not suppress the advisory.
359
+ const hasMockupRef = /!\[/.test(visualBody ?? "")
360
+ || /plannedRef/i.test(contractBody ?? "")
361
+ || /!\[/.test(contractBody ?? "");
362
+ if (isUiSlice && !hasMockupRef) {
363
+ findings.push({
364
+ kind: "ui_slice_missing_mockup",
365
+ severity: "info",
366
+ path: childPath(input.path, "README.md"),
367
+ message: "Slice declares an Intent visual (UI slice) but no mockup reference is present — a UI slice with no mockup in its locked set is an incomplete plan.",
368
+ remediation: "Attach the planned mockup: an image ref in `## Intent visual` or a plannedRef on the proof-contract deliverable (conventions SSOT: docs/reference/sdlc-conventions.md §3).",
369
+ });
370
+ }
371
+ }
372
+ }
201
373
  // committed-without-PROGRESS. Fires only on POSITIVE evidence: the most
202
374
  // recent commit (HEAD) touched this slice AND definitively did NOT touch its
203
375
  // PROGRESS.md. Inputs are git-derived and CLI-only; when git context is