@lannguyensi/harness 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +9 -0
  3. package/dist/cli/adopt/derive.d.ts +15 -0
  4. package/dist/cli/adopt/derive.js +34 -7
  5. package/dist/cli/adopt/derive.js.map +1 -1
  6. package/dist/cli/adopt/index.d.ts +30 -0
  7. package/dist/cli/adopt/index.js +23 -3
  8. package/dist/cli/adopt/index.js.map +1 -1
  9. package/dist/cli/apply/generate-settings.d.ts +14 -1
  10. package/dist/cli/apply/generate-settings.js +7 -3
  11. package/dist/cli/apply/generate-settings.js.map +1 -1
  12. package/dist/cli/approve/stdin-report.js +2 -0
  13. package/dist/cli/approve/stdin-report.js.map +1 -1
  14. package/dist/cli/approve/understanding.js +28 -0
  15. package/dist/cli/approve/understanding.js.map +1 -1
  16. package/dist/cli/doctor/claude-mcp.d.ts +50 -0
  17. package/dist/cli/doctor/claude-mcp.js +170 -0
  18. package/dist/cli/doctor/claude-mcp.js.map +1 -0
  19. package/dist/cli/doctor/codex.d.ts +8 -0
  20. package/dist/cli/doctor/codex.js +8 -1
  21. package/dist/cli/doctor/codex.js.map +1 -1
  22. package/dist/cli/doctor/format.js +60 -11
  23. package/dist/cli/doctor/format.js.map +1 -1
  24. package/dist/cli/doctor/index.d.ts +46 -0
  25. package/dist/cli/doctor/index.js +155 -29
  26. package/dist/cli/doctor/index.js.map +1 -1
  27. package/dist/cli/doctor/npm-bin-path.js +42 -0
  28. package/dist/cli/doctor/npm-bin-path.js.map +1 -1
  29. package/dist/cli/doctor/types.d.ts +37 -0
  30. package/dist/cli/doctor/types.js.map +1 -1
  31. package/dist/cli/dry-run.js +5 -0
  32. package/dist/cli/dry-run.js.map +1 -1
  33. package/dist/cli/explain.js +16 -3
  34. package/dist/cli/explain.js.map +1 -1
  35. package/dist/cli/index.js +328 -11
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/init/agent-tasks-auth.js +32 -0
  38. package/dist/cli/init/agent-tasks-auth.js.map +1 -1
  39. package/dist/cli/init/composer.js +13 -40
  40. package/dist/cli/init/composer.js.map +1 -1
  41. package/dist/cli/init/dependencies.js +21 -0
  42. package/dist/cli/init/dependencies.js.map +1 -1
  43. package/dist/cli/init/detect.d.ts +20 -3
  44. package/dist/cli/init/detect.js +34 -36
  45. package/dist/cli/init/detect.js.map +1 -1
  46. package/dist/cli/init/index.d.ts +18 -0
  47. package/dist/cli/init/index.js +28 -2
  48. package/dist/cli/init/index.js.map +1 -1
  49. package/dist/cli/init/interactive.d.ts +41 -0
  50. package/dist/cli/init/interactive.js +421 -6
  51. package/dist/cli/init/interactive.js.map +1 -1
  52. package/dist/cli/init/profiles.d.ts +2 -2
  53. package/dist/cli/init/profiles.js +10 -1
  54. package/dist/cli/init/profiles.js.map +1 -1
  55. package/dist/cli/init/templates.d.ts +1 -1
  56. package/dist/cli/init/templates.js +217 -8
  57. package/dist/cli/init/templates.js.map +1 -1
  58. package/dist/cli/list.js +3 -1
  59. package/dist/cli/list.js.map +1 -1
  60. package/dist/cli/main.js +10 -0
  61. package/dist/cli/main.js.map +1 -1
  62. package/dist/cli/pack/hook-bootstrap.d.ts +24 -0
  63. package/dist/cli/pack/hook-bootstrap.js +47 -1
  64. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  65. package/dist/cli/pack/hook-branch-protection.js +9 -1
  66. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  67. package/dist/cli/pack/hook-codex-post-tool-use.d.ts +25 -0
  68. package/dist/cli/pack/hook-codex-post-tool-use.js +160 -0
  69. package/dist/cli/pack/hook-codex-post-tool-use.js.map +1 -0
  70. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +1 -0
  71. package/dist/cli/pack/hook-codex-pre-tool-use.js +43 -10
  72. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  73. package/dist/cli/pack/hook-codex-stop.js +1 -8
  74. package/dist/cli/pack/hook-codex-stop.js.map +1 -1
  75. package/dist/cli/pack/hook-post-merge-gate-record.d.ts +30 -0
  76. package/dist/cli/pack/hook-post-merge-gate-record.js +153 -0
  77. package/dist/cli/pack/hook-post-merge-gate-record.js.map +1 -0
  78. package/dist/cli/pack/hook-post-merge-gate.d.ts +28 -0
  79. package/dist/cli/pack/hook-post-merge-gate.js +244 -0
  80. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -0
  81. package/dist/cli/pack/hook-post-tool-use.js +16 -131
  82. package/dist/cli/pack/hook-post-tool-use.js.map +1 -1
  83. package/dist/cli/pack/hook-pre-tool-use.js +54 -2
  84. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  85. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +148 -2
  86. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +1 -1
  87. package/dist/cli/pack/hook-stay-in-scope.js +63 -20
  88. package/dist/cli/pack/hook-stay-in-scope.js.map +1 -1
  89. package/dist/cli/pack/hook-track-active-claim.js +33 -9
  90. package/dist/cli/pack/hook-track-active-claim.js.map +1 -1
  91. package/dist/cli/pack/index.d.ts +2 -1
  92. package/dist/cli/pack/index.js +2 -1
  93. package/dist/cli/pack/index.js.map +1 -1
  94. package/dist/cli/pack/mutate.d.ts +19 -0
  95. package/dist/cli/pack/mutate.js +38 -0
  96. package/dist/cli/pack/mutate.js.map +1 -1
  97. package/dist/cli/pack/reseed.d.ts +19 -0
  98. package/dist/cli/pack/reseed.js +118 -0
  99. package/dist/cli/pack/reseed.js.map +1 -0
  100. package/dist/cli/pack/understanding-report-schema-hint.js +1 -1
  101. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  102. package/dist/cli/pause/index.js +12 -3
  103. package/dist/cli/pause/index.js.map +1 -1
  104. package/dist/cli/record/index.d.ts +64 -0
  105. package/dist/cli/record/index.js +211 -0
  106. package/dist/cli/record/index.js.map +1 -0
  107. package/dist/cli/session-start/index.d.ts +21 -0
  108. package/dist/cli/session-start/index.js +160 -25
  109. package/dist/cli/session-start/index.js.map +1 -1
  110. package/dist/cli/session-start/toolchain-parity.d.ts +149 -0
  111. package/dist/cli/session-start/toolchain-parity.js +618 -0
  112. package/dist/cli/session-start/toolchain-parity.js.map +1 -0
  113. package/dist/cli/uninstall/index.d.ts +73 -1
  114. package/dist/cli/uninstall/index.js +134 -2
  115. package/dist/cli/uninstall/index.js.map +1 -1
  116. package/dist/cli/validate/checks.js +8 -0
  117. package/dist/cli/validate/checks.js.map +1 -1
  118. package/dist/io/claude-mcp.d.ts +258 -0
  119. package/dist/io/claude-mcp.js +544 -0
  120. package/dist/io/claude-mcp.js.map +1 -0
  121. package/dist/policies/ledger-client.d.ts +34 -1
  122. package/dist/policies/ledger-client.js +21 -16
  123. package/dist/policies/ledger-client.js.map +1 -1
  124. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -2
  125. package/dist/policy-packs/builtin/branch-protection-runtime.js +4 -2
  126. package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
  127. package/dist/policy-packs/builtin/branch-protection.d.ts +10 -1
  128. package/dist/policy-packs/builtin/branch-protection.js +17 -0
  129. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  130. package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +195 -0
  131. package/dist/policy-packs/builtin/post-merge-gate-runtime.js +290 -0
  132. package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -0
  133. package/dist/policy-packs/builtin/post-merge-gate.d.ts +56 -0
  134. package/dist/policy-packs/builtin/post-merge-gate.js +291 -0
  135. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -0
  136. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +28 -13
  137. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  138. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +197 -24
  139. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +342 -40
  140. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  141. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +26 -1
  142. package/dist/policy-packs/builtin/understanding-before-execution.js +302 -17
  143. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  144. package/dist/policy-packs/registry.d.ts +23 -2
  145. package/dist/policy-packs/registry.js +31 -2
  146. package/dist/policy-packs/registry.js.map +1 -1
  147. package/dist/policy-packs/ux-compare.d.ts +14 -0
  148. package/dist/policy-packs/ux-compare.js +58 -0
  149. package/dist/policy-packs/ux-compare.js.map +1 -0
  150. package/dist/policy-packs/ux-drift-check.d.ts +27 -0
  151. package/dist/policy-packs/ux-drift-check.js +78 -0
  152. package/dist/policy-packs/ux-drift-check.js.map +1 -0
  153. package/dist/probes/mcp.d.ts +13 -0
  154. package/dist/probes/mcp.js +57 -0
  155. package/dist/probes/mcp.js.map +1 -1
  156. package/dist/runtime/approval-signing.d.ts +82 -0
  157. package/dist/runtime/approval-signing.js +275 -0
  158. package/dist/runtime/approval-signing.js.map +1 -0
  159. package/dist/runtime/git-context.d.ts +50 -0
  160. package/dist/runtime/git-context.js +108 -1
  161. package/dist/runtime/git-context.js.map +1 -1
  162. package/dist/runtime/hermetic-spawn-guard.d.ts +25 -0
  163. package/dist/runtime/hermetic-spawn-guard.js +136 -0
  164. package/dist/runtime/hermetic-spawn-guard.js.map +1 -0
  165. package/dist/runtime/intercept.js +62 -5
  166. package/dist/runtime/intercept.js.map +1 -1
  167. package/dist/runtime/ledger-writer.d.ts +27 -0
  168. package/dist/runtime/ledger-writer.js +46 -0
  169. package/dist/runtime/ledger-writer.js.map +1 -0
  170. package/dist/runtime/pause-sentinel.js +14 -3
  171. package/dist/runtime/pause-sentinel.js.map +1 -1
  172. package/dist/runtime/read-only-bash.js +118 -0
  173. package/dist/runtime/read-only-bash.js.map +1 -1
  174. package/dist/runtime/recovery-git-commit.d.ts +22 -0
  175. package/dist/runtime/recovery-git-commit.js +284 -0
  176. package/dist/runtime/recovery-git-commit.js.map +1 -0
  177. package/dist/runtime/tool-name-aliases.d.ts +1 -0
  178. package/dist/runtime/tool-name-aliases.js +7 -1
  179. package/dist/runtime/tool-name-aliases.js.map +1 -1
  180. package/dist/schema/index.d.ts +94 -42
  181. package/dist/schema/index.js +5 -0
  182. package/dist/schema/index.js.map +1 -1
  183. package/dist/schema/policies.d.ts +56 -44
  184. package/dist/schema/policies.js +79 -9
  185. package/dist/schema/policies.js.map +1 -1
  186. package/dist/schema/toolchain-parity.d.ts +18 -0
  187. package/dist/schema/toolchain-parity.js +35 -0
  188. package/dist/schema/toolchain-parity.js.map +1 -0
  189. package/package.json +4 -2
@@ -0,0 +1,275 @@
1
+ // harness/f9485cc7 — HMAC signing for Understanding Gate / branch-protection
2
+ // approval markers.
3
+ //
4
+ // BACKGROUND: the marker at `harness.generated/.approvals/<id>` is the
5
+ // canonical "operator said go" signal (agent-tasks/88ca4bb3). Its integrity
6
+ // has rested entirely on an UNENFORCED invariant: "no configured MCP exposes
7
+ // a filesystem-write primitive" (understanding-before-execution.md
8
+ // "Approval state"). Edit / Write / Bash are gated by the same PreToolUse
9
+ // hook this marker satisfies, so today the agent has no path to WRITE the
10
+ // marker file. But nothing technical stops a future MCP tool with local
11
+ // file-write from silently reopening that hole: its tool name would not
12
+ // match the pack's `Edit|Write|Bash` blocker matcher, so a write through it
13
+ // would land un-gated. Before this module, the marker's contract was
14
+ // "existence is enough" (see the removed `checkApprovalMarker` comment) —
15
+ // exactly the shape a forger only needs a bare filesystem-write for. This
16
+ // module makes forging also require the operator-side HMAC key, so a marker
17
+ // planted through any write-primitive the blocker does not enumerate still
18
+ // fails the gate unless it carries a valid signature.
19
+ //
20
+ // HONEST TRUST MODEL (read this before assuming more than it delivers):
21
+ //
22
+ // - This does NOT create a read boundary. The `understanding-before-
23
+ // execution` PreToolUse blocker matches tool names `Edit|Write|Bash`
24
+ // only — it does not gate `Read`, and no permission profile in this
25
+ // repo scopes `Read` away from any path (see
26
+ // `src/policy-packs/permission-translator.ts`: `read` maps to
27
+ // `["Read", "Glob", "Grep"]` with `allow:true` in every v1 profile).
28
+ // So ANY file on local disk — including the signing key, wherever it
29
+ // lives — is readable by the ordinary, always-available `Read` tool,
30
+ // approved or not. Storing the key outside `harness.generated/` would
31
+ // not change this; the write barrier is per-tool-name, not per-path.
32
+ // - The residual this leaves: an agent (or anything running as the same
33
+ // OS user) that can (a) read arbitrary local files — true today, by
34
+ // design, via `Read` — AND (b) write to `harness.generated/.approvals/`
35
+ // through some primitive NOT covered by the `Edit|Write|Bash` blocker
36
+ // matcher, CAN forge a validly-signed marker: it reads the key with
37
+ // Read, computes the HMAC itself, and writes the result through the
38
+ // uncovered write path. Signing does not close this combined case; it
39
+ // closes the narrower (and, before this, the ONLY defended) case where
40
+ // the forger has a write primitive but no matching ability to also
41
+ // recompute a valid signature for it (e.g. a narrow single-purpose
42
+ // write channel, or a human/script that copies bytes without reading
43
+ // back the operator's key file), and it makes ANY forgery attempt one
44
+ // step more expensive and forensically visible (a key read is a
45
+ // distinguishable event; a bare unsigned-JSON write no longer is).
46
+ // - Given the agent and operator share one OS user account on this
47
+ // machine, this is deliberately pragmatic defense-in-depth, not a hard
48
+ // authorization boundary. It is documented here in full rather than
49
+ // oversold.
50
+ //
51
+ // KEY STORAGE: `<generatedDir>/.approval-signing.key` — a raw 32-byte
52
+ // secret, mode 0600, generated lazily on first use (by `harness approve
53
+ // understanding` / `harness approve branch-protection`, or by `harness
54
+ // init` — see src/cli/init/index.ts). Colocated with `harness.generated/`
55
+ // (not a separate home-dir path) so:
56
+ // - it inherits the SAME test-isolation guarantee every marker/report
57
+ // path already has (every call site here already threads an explicit,
58
+ // test-injectable `generatedDir` — no new parameter, no new way to
59
+ // leak into a real `~/.harness/` during a test run);
60
+ // - it is gitignored by the SAME existing convention as the rest of
61
+ // `harness.generated/` (docs/ARCHITECTURE.md: "harness.generated/ ...
62
+ // is .gitignore'd", unlike `harness.yaml` itself, which operators
63
+ // often check into a dotfiles repo) — a home-dir-anchored path could
64
+ // land inside a version-controlled directory when the operator
65
+ // resolves `harness.yaml` via `--config` into a repo tree; this can't;
66
+ // - `harness apply` never wipes it (apply only ever writes its own known
67
+ // files under `harness.generated/`; unknown siblings survive
68
+ // byte-for-byte — the same guarantee `.approvals/` and
69
+ // `.pending-approval` already rely on).
70
+ // `harness uninstall` DOES remove it (it rm -rf's the whole
71
+ // `generatedDir`) — a deliberate, rare, operator-initiated teardown, so
72
+ // losing the key (and therefore invalidating every marker it signed) is
73
+ // an acceptable side effect, not a silent regression.
74
+ //
75
+ // ROTATION: delete `<generatedDir>/.approval-signing.key` (or call
76
+ // `rotateSigningKey`). The next `writeApprovalMarker` /
77
+ // `writeBranchProtectionMarker` / any signature check lazily regenerates
78
+ // it. Every marker signed under the OLD key immediately fails verification
79
+ // (forged:true) and the gate demands re-approval — this is the intended,
80
+ // documented blast radius of a rotation, not a bug.
81
+ //
82
+ // BACK-COMPAT: strict, no migration window. A marker written before this
83
+ // feature shipped (or hand-written without the key) carries no `signature`
84
+ // field and is REJECTED (see `verifyMarkerSignature`), exactly like a
85
+ // forgery. Operator impact: upgrading harness on a machine with a live,
86
+ // previously-approved session invalidates that approval; the operator runs
87
+ // `harness approve understanding` (and/or `harness approve
88
+ // branch-protection`) once more. Chosen over a grace-period migration
89
+ // because a migration window is, by construction, a window where the
90
+ // exact vulnerability this task closes (an unsigned marker satisfies the
91
+ // gate) still holds.
92
+ import * as crypto from "node:crypto";
93
+ import * as fs from "node:fs";
94
+ import * as path from "node:path";
95
+ /** Versioned algorithm tag, so a future re-key scheme can reject mismatches explicitly rather than guess. */
96
+ export const SIGNING_ALG = "hmac-sha256-v1";
97
+ /** Basename of the signing-key file, a sibling of `.approvals/` under `generatedDir`. */
98
+ export const SIGNING_KEY_BASENAME = ".approval-signing.key";
99
+ const KEY_BYTES = 32;
100
+ /** Filesystem path of the signing key for a given `generatedDir`. */
101
+ export function signingKeyPathFor(generatedDir) {
102
+ return path.join(generatedDir, SIGNING_KEY_BASENAME);
103
+ }
104
+ /**
105
+ * Read the signing key, generating one (0600, `crypto.randomBytes(32)`) on
106
+ * first use. Race-safe-ish: a concurrent create loses the `wx` (exclusive
107
+ * create) race and falls back to reading whatever the winner wrote, rather
108
+ * than throwing or clobbering it. A key file shorter than `KEY_BYTES`
109
+ * (truncated / corrupted) is treated as absent and regenerated, since a
110
+ * short key would only weaken every future signature.
111
+ */
112
+ export function getOrCreateSigningKey(generatedDir) {
113
+ const filePath = signingKeyPathFor(generatedDir);
114
+ fs.mkdirSync(generatedDir, { recursive: true });
115
+ let fileExisted = false;
116
+ try {
117
+ const existing = fs.readFileSync(filePath);
118
+ fileExisted = true;
119
+ if (existing.length >= KEY_BYTES) {
120
+ return { key: existing, filePath, created: false };
121
+ }
122
+ // Falls through: truncated/corrupt key file, regenerate below. `flag:
123
+ // "w"` (not "wx") is used below precisely BECAUSE the file already
124
+ // exists here — an exclusive create would collide with it and, on
125
+ // EEXIST, re-read the SAME truncated bytes back, silently failing to
126
+ // ever repair a corrupt key.
127
+ }
128
+ catch (err) {
129
+ if (err.code !== "ENOENT")
130
+ throw err;
131
+ }
132
+ const fresh = crypto.randomBytes(KEY_BYTES);
133
+ if (fileExisted) {
134
+ // Known-corrupt/truncated file: overwrite unconditionally. No
135
+ // meaningful create-race to defend against — the file is already
136
+ // known bad, so unconditionally replacing it can only improve on
137
+ // the previous (unusable) contents.
138
+ fs.writeFileSync(filePath, fresh, { mode: 0o600 });
139
+ try {
140
+ fs.chmodSync(filePath, 0o600);
141
+ }
142
+ catch {
143
+ /* best-effort */
144
+ }
145
+ return { key: fresh, filePath, created: true };
146
+ }
147
+ try {
148
+ fs.writeFileSync(filePath, fresh, { mode: 0o600, flag: "wx" });
149
+ return { key: fresh, filePath, created: true };
150
+ }
151
+ catch (err) {
152
+ if (err.code === "EEXIST") {
153
+ // Lost the create race to a concurrent caller; use what they wrote.
154
+ return { key: fs.readFileSync(filePath), filePath, created: false };
155
+ }
156
+ throw err;
157
+ }
158
+ }
159
+ /**
160
+ * Force a fresh key, overwriting any existing one. Every marker signed
161
+ * under the old key stops verifying immediately (see module doc). Not
162
+ * currently wired to a CLI verb (documented as "delete the file" for v1);
163
+ * exported so a future `harness approve rotate-key`-style verb, or a test,
164
+ * can call it directly without re-deriving the path convention.
165
+ */
166
+ export function rotateSigningKey(generatedDir) {
167
+ const filePath = signingKeyPathFor(generatedDir);
168
+ fs.mkdirSync(generatedDir, { recursive: true });
169
+ const fresh = crypto.randomBytes(KEY_BYTES);
170
+ fs.writeFileSync(filePath, fresh, { mode: 0o600 });
171
+ try {
172
+ fs.chmodSync(filePath, 0o600);
173
+ }
174
+ catch {
175
+ /* best-effort on platforms where chmod is a no-op (e.g. some Windows fs) */
176
+ }
177
+ return { key: fresh, filePath, created: true };
178
+ }
179
+ /** sha256 hex digest of a string, used to bind a marker to a persisted report's content. */
180
+ export function sha256Hex(content) {
181
+ return crypto.createHash("sha256").update(content, "utf8").digest("hex");
182
+ }
183
+ function canonicalPayload(markerId, approvedAt, approvedBy, reportContentHash) {
184
+ // Fixed key order + JSON string-escaping makes this injective: there is
185
+ // no (markerId, approvedAt, approvedBy, reportContentHash) tuple that
186
+ // collides with a different tuple's encoding, unlike naive delimiter
187
+ // concatenation (e.g. "a"+"bc" vs "ab"+"c").
188
+ return JSON.stringify({ markerId, approvedAt, approvedBy, reportContentHash });
189
+ }
190
+ /**
191
+ * Sign a marker's fields for `markerId` (the marker's lookup key — a raw
192
+ * sessionId for the understanding-gate session marker, `task-<id>` for a
193
+ * task-scoped marker, `branch-protection-<sessionId>` for the
194
+ * branch-protection twin). Binding `markerId` into the signed payload is
195
+ * what stops a validly-signed marker from being copied/renamed onto a
196
+ * DIFFERENT id and still verifying — verification always recomputes the
197
+ * HMAC using the id the caller is checking, not one read back out of the
198
+ * marker body.
199
+ */
200
+ export function signMarker(generatedDir, markerId, marker) {
201
+ const { key } = getOrCreateSigningKey(generatedDir);
202
+ const reportContentHash = marker.reportContentHash ?? null;
203
+ const signature = crypto
204
+ .createHmac("sha256", key)
205
+ .update(canonicalPayload(markerId, marker.approvedAt, marker.approvedBy, reportContentHash))
206
+ .digest("hex");
207
+ return {
208
+ approvedAt: marker.approvedAt,
209
+ approvedBy: marker.approvedBy,
210
+ reportContentHash,
211
+ alg: SIGNING_ALG,
212
+ signature,
213
+ };
214
+ }
215
+ /**
216
+ * Verify a parsed marker JSON body against `markerId`. Never throws: any
217
+ * failure to obtain the signing key (permission error, disk issue) is
218
+ * treated as a verification failure, not an exception — the gate-side
219
+ * caller must fail closed (not approved) rather than crash the hook
220
+ * process, which the surrounding PreToolUse blocker would otherwise turn
221
+ * into an uncontrolled failure mode. That specific failure mode is tagged
222
+ * `kind: "key-unavailable"` so the caller can tell "we couldn't even check"
223
+ * apart from "we checked and it's invalid".
224
+ */
225
+ export function verifyMarkerSignature(generatedDir, markerId, payload) {
226
+ const approvedAt = payload["approvedAt"];
227
+ const approvedBy = payload["approvedBy"];
228
+ const signature = payload["signature"];
229
+ const alg = payload["alg"];
230
+ if (typeof approvedAt !== "string" || approvedAt.length === 0) {
231
+ return { ok: false, reason: "missing approvedAt" };
232
+ }
233
+ if (typeof approvedBy !== "string" || approvedBy.length === 0) {
234
+ return { ok: false, reason: "missing approvedBy" };
235
+ }
236
+ if (typeof signature !== "string" || signature.length === 0) {
237
+ return {
238
+ ok: false,
239
+ reason: "missing signature (legacy pre-signing marker, or forged file)",
240
+ };
241
+ }
242
+ if (alg !== SIGNING_ALG) {
243
+ return { ok: false, reason: `unrecognized or missing alg (got ${JSON.stringify(alg)})` };
244
+ }
245
+ const reportContentHash = typeof payload["reportContentHash"] === "string"
246
+ ? payload["reportContentHash"]
247
+ : null;
248
+ let key;
249
+ try {
250
+ ({ key } = getOrCreateSigningKey(generatedDir));
251
+ }
252
+ catch (err) {
253
+ return {
254
+ ok: false,
255
+ reason: `signing key unavailable (${err instanceof Error ? err.message : String(err)})`,
256
+ kind: "key-unavailable",
257
+ };
258
+ }
259
+ const expected = crypto
260
+ .createHmac("sha256", key)
261
+ .update(canonicalPayload(markerId, approvedAt, approvedBy, reportContentHash))
262
+ .digest();
263
+ // No try/catch around Buffer.from(signature, "hex"): unlike JSON.parse,
264
+ // Buffer.from with a "hex" encoding never throws on malformed input — it
265
+ // silently stops decoding at the first invalid pair and returns whatever
266
+ // it managed to decode so far (review LOW 3). The length-mismatch check
267
+ // below is what actually rejects a truncated/malformed decode; relying on
268
+ // an exception here would have been dead code.
269
+ const actual = Buffer.from(signature, "hex");
270
+ if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) {
271
+ return { ok: false, reason: "signature verification failed (tampered or forged)" };
272
+ }
273
+ return { ok: true };
274
+ }
275
+ //# sourceMappingURL=approval-signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-signing.js","sourceRoot":"","sources":["../../src/runtime/approval-signing.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,27 @@ export interface GitRepoContext {
17
17
  */
18
18
  sha: string;
19
19
  }
20
+ export interface GitEntry {
21
+ /** Directory that contains the `.git` entry (the work-tree root). */
22
+ worktreeRoot: string;
23
+ /** Resolved git directory — for a `.git` file, its `gitdir:` target. */
24
+ gitDir: string;
25
+ }
26
+ /**
27
+ * Walk up from `startDir` looking for a `.git` entry. Handles both the
28
+ * common `.git` directory and the `.git` *file* form used by linked
29
+ * worktrees and submodules. The walk is bounded so a pathologically
30
+ * deep cwd cannot spin.
31
+ *
32
+ * Exported (task T-001, record-verbs) so `cli/record/index.ts` can
33
+ * locate the same `.git` directory this module resolves `repo`/
34
+ * `branch`/`sha` from, without re-walking the tree with duplicate
35
+ * logic — its base-branch resolution needs the raw git directory (to
36
+ * read `refs/remotes/origin/HEAD` / `packed-refs`), which
37
+ * `resolveGitContext`'s return shape does not expose. Behavior is
38
+ * unchanged; this is a visibility-only change.
39
+ */
40
+ export declare function findGitEntry(startDir: string): GitEntry | null;
20
41
  /**
21
42
  * Resolve `{ repo, branch, sha }` for a working directory. Returns empty
22
43
  * strings (never throws) when `cwd` is not inside a git work tree, or
@@ -24,3 +45,32 @@ export interface GitRepoContext {
24
45
  * fall through to their own behaviour.
25
46
  */
26
47
  export declare function resolveGitContext(cwd: string): GitRepoContext;
48
+ /**
49
+ * Resolve the remote's default branch name from `<gitDir>/refs/remotes/
50
+ * origin/HEAD`. Loose symbolic ref first (the normal shape: `ref: refs/
51
+ * remotes/origin/<name>`, written by `git clone` / `git remote set-head
52
+ * origin -a`). When that loose file is absent, falls back to
53
+ * `packed-refs`: some git versions / tooling pack `refs/remotes/origin/
54
+ * HEAD` as a plain `<sha> <ref>` entry instead of a symref, which loses
55
+ * the branch NAME directly — recovered here by matching that sha
56
+ * against another packed `refs/remotes/origin/<name>` entry that shares
57
+ * it (mirrors the loose-then-packed shape `resolveBranchSha` uses
58
+ * above, adapted since packed-refs has no symref concept). Returns null
59
+ * when neither source resolves a name.
60
+ */
61
+ export declare function resolveOriginHeadBase(gitDir: string): string | null;
62
+ /**
63
+ * Resolve the actual shared git directory for `gitDir`, following the
64
+ * `commondir` file linked worktrees write. `git worktree add` gives each
65
+ * worktree its own private `.git` FILE pointing at `<main>/.git/
66
+ * worktrees/<name>/` (what `findGitEntry` returns as `gitDir`), but
67
+ * `refs/remotes/origin/HEAD` and `packed-refs` are NOT duplicated there
68
+ * — they live only in the shared common dir, reachable via that
69
+ * per-worktree directory's own `commondir` file (a path, normally
70
+ * `../..`, relative to the per-worktree directory itself; see
71
+ * `git-worktree(1)`). Without this indirection, `resolveOriginHeadBase`
72
+ * would look for those refs in the empty per-worktree directory and
73
+ * always miss. Returns `gitDir` unchanged when no `commondir` file
74
+ * exists (the normal, non-worktree case).
75
+ */
76
+ export declare function resolveCommonDir(gitDir: string): string;
@@ -35,8 +35,16 @@ const SHA_RE = /^[0-9a-f]{40}$/;
35
35
  * common `.git` directory and the `.git` *file* form used by linked
36
36
  * worktrees and submodules. The walk is bounded so a pathologically
37
37
  * deep cwd cannot spin.
38
+ *
39
+ * Exported (task T-001, record-verbs) so `cli/record/index.ts` can
40
+ * locate the same `.git` directory this module resolves `repo`/
41
+ * `branch`/`sha` from, without re-walking the tree with duplicate
42
+ * logic — its base-branch resolution needs the raw git directory (to
43
+ * read `refs/remotes/origin/HEAD` / `packed-refs`), which
44
+ * `resolveGitContext`'s return shape does not expose. Behavior is
45
+ * unchanged; this is a visibility-only change.
38
46
  */
39
- function findGitEntry(startDir) {
47
+ export function findGitEntry(startDir) {
40
48
  let dir = path.resolve(startDir);
41
49
  for (let depth = 0; depth < 128; depth++) {
42
50
  const dotGit = path.join(dir, ".git");
@@ -143,4 +151,103 @@ export function resolveGitContext(cwd) {
143
151
  }
144
152
  return { repo, branch, sha };
145
153
  }
154
+ // ---------------------------------------------------------------------------
155
+ // Default-branch resolution (offline, no `gh`/`git` subprocess).
156
+ //
157
+ // Originally written for `harness record review`'s `--base` fallback
158
+ // (task T-001, record-verbs) and lived only in `cli/record/index.ts`.
159
+ // Exported here (task post-merge-gate, T-001) so
160
+ // `policy-packs/builtin/post-merge-gate-runtime.ts` can resolve the same
161
+ // "what's the default branch to switch back to" answer for its deny
162
+ // message without a policy-pack module reaching into `cli/`. Behavior is
163
+ // unchanged; this is a visibility/location move, not a rewrite — see
164
+ // `findGitEntry`'s doc comment above for the identical precedent
165
+ // (record/index.ts reusing this module's git-dir walk instead of
166
+ // duplicating it).
167
+ // ---------------------------------------------------------------------------
168
+ // `.git/refs/remotes/origin/HEAD` on a normal clone: a symbolic ref
169
+ // pointing at the remote's default branch.
170
+ const ORIGIN_HEAD_REF_RE = /^ref:\s*refs\/remotes\/origin\/(.+)$/;
171
+ const ORIGIN_HEAD_REF_PATH = "refs/remotes/origin/HEAD";
172
+ const ORIGIN_REMOTE_PREFIX = "refs/remotes/origin/";
173
+ /**
174
+ * Resolve the remote's default branch name from `<gitDir>/refs/remotes/
175
+ * origin/HEAD`. Loose symbolic ref first (the normal shape: `ref: refs/
176
+ * remotes/origin/<name>`, written by `git clone` / `git remote set-head
177
+ * origin -a`). When that loose file is absent, falls back to
178
+ * `packed-refs`: some git versions / tooling pack `refs/remotes/origin/
179
+ * HEAD` as a plain `<sha> <ref>` entry instead of a symref, which loses
180
+ * the branch NAME directly — recovered here by matching that sha
181
+ * against another packed `refs/remotes/origin/<name>` entry that shares
182
+ * it (mirrors the loose-then-packed shape `resolveBranchSha` uses
183
+ * above, adapted since packed-refs has no symref concept). Returns null
184
+ * when neither source resolves a name.
185
+ */
186
+ export function resolveOriginHeadBase(gitDir) {
187
+ try {
188
+ const raw = fs
189
+ .readFileSync(path.join(gitDir, "refs", "remotes", "origin", "HEAD"), "utf8")
190
+ .trim();
191
+ const match = ORIGIN_HEAD_REF_RE.exec(raw);
192
+ if (match)
193
+ return match[1].trim();
194
+ }
195
+ catch {
196
+ /* loose symref missing — try packed-refs */
197
+ }
198
+ try {
199
+ const packed = fs.readFileSync(path.join(gitDir, "packed-refs"), "utf8");
200
+ let headSha = null;
201
+ const entries = [];
202
+ for (const rawLine of packed.split("\n")) {
203
+ const line = rawLine.trim();
204
+ if (line === "" || line.startsWith("#") || line.startsWith("^"))
205
+ continue;
206
+ const parts = line.split(/\s+/);
207
+ const sha = parts[0];
208
+ const ref = parts[1];
209
+ if (!sha || !ref || !SHA_RE.test(sha))
210
+ continue;
211
+ if (ref === ORIGIN_HEAD_REF_PATH)
212
+ headSha = sha;
213
+ else
214
+ entries.push({ sha, ref });
215
+ }
216
+ if (headSha) {
217
+ const match = entries.find((e) => e.sha === headSha && e.ref.startsWith(ORIGIN_REMOTE_PREFIX));
218
+ if (match)
219
+ return match.ref.slice(ORIGIN_REMOTE_PREFIX.length);
220
+ }
221
+ }
222
+ catch {
223
+ /* packed-refs missing too — caller treats null as "unresolvable" */
224
+ }
225
+ return null;
226
+ }
227
+ /**
228
+ * Resolve the actual shared git directory for `gitDir`, following the
229
+ * `commondir` file linked worktrees write. `git worktree add` gives each
230
+ * worktree its own private `.git` FILE pointing at `<main>/.git/
231
+ * worktrees/<name>/` (what `findGitEntry` returns as `gitDir`), but
232
+ * `refs/remotes/origin/HEAD` and `packed-refs` are NOT duplicated there
233
+ * — they live only in the shared common dir, reachable via that
234
+ * per-worktree directory's own `commondir` file (a path, normally
235
+ * `../..`, relative to the per-worktree directory itself; see
236
+ * `git-worktree(1)`). Without this indirection, `resolveOriginHeadBase`
237
+ * would look for those refs in the empty per-worktree directory and
238
+ * always miss. Returns `gitDir` unchanged when no `commondir` file
239
+ * exists (the normal, non-worktree case).
240
+ */
241
+ export function resolveCommonDir(gitDir) {
242
+ try {
243
+ const raw = fs.readFileSync(path.join(gitDir, "commondir"), "utf8").trim();
244
+ if (raw.length > 0) {
245
+ return path.isAbsolute(raw) ? raw : path.resolve(gitDir, raw);
246
+ }
247
+ }
248
+ catch {
249
+ /* no commondir file — gitDir already IS the common dir */
250
+ }
251
+ return gitDir;
252
+ }
146
253
  //# sourceMappingURL=git-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-context.js","sourceRoot":"","sources":["../../src/runtime/git-context.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,aAAa;AACb,EAAE;AACF,uEAAuE;AACvE,gEAAgE;AAChE,uEAAuE;AACvE,uEAAuE;AACvE,sEAAsE;AACtE,gEAAgE;AAChE,EAAE;AACF,mEAAmE;AACnE,mEAAmE;AACnE,sEAAsE;AACtE,gEAAgE;AAChE,sCAAsC;AACtC,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,mEAAmE;AACnE,gEAAgE;AAChE,mDAAmD;AAEnD,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAsBlC,MAAM,KAAK,GAAmB,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAEhE,uEAAuE;AACvE,yBAAyB;AACzB,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,uEAAuE;AACvE,oDAAoD;AACpD,MAAM,WAAW,GAAG,4BAA4B,CAAC;AACjD,sEAAsE;AACtE,MAAM,MAAM,GAAG,gBAAgB,CAAC;AAShC;;;;;GAKG;AACH,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACtC,IAAI,IAA0B,CAAC;QAC/B,IAAI,CAAC;YACH,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,SAAS,CAAC;QACnB,CAAC;QACD,IAAI,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,CAAC;gBACH,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE;oBAAE,OAAO,IAAI,CAAC;YACpE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC/C,CAAC;QACD,IAAI,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;YACnB,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrE,IAAI,KAAK;oBAAE,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,sEAAsE;YACxE,CAAC;YACD,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;QACvC,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,0BAA0B;QAC3D,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,MAAc,EAAE,MAAc;IACtD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE;aACb,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC;aAChE,IAAI,EAAE,CAAC;QACV,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;IAC1C,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,cAAc,MAAM,EAAE,CAAC;QACtC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC1E,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACxC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC;QAC5D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;IAC/D,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;YAC7E,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;gBAC1B,GAAG,GAAG,gBAAgB,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC/C,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC7B,yDAAyD;gBACzD,GAAG,GAAG,IAAI,CAAC;YACb,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AAC/B,CAAC"}
1
+ {"version":3,"file":"git-context.js","sourceRoot":"","sources":["../../src/runtime/git-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Thrown by {@link assertNoRealSpawnInTests} when a real-spawn code path
3
+ * runs under vitest without a test-injected fake. Callers whose
4
+ * surrounding try/catch degrades ordinary runner failures to a warning
5
+ * MUST re-throw instances of this class past that catch — never let it
6
+ * be treated as an ordinary (optional, warn-and-continue) failure.
7
+ */
8
+ export declare class HermeticSpawnViolationError extends Error {
9
+ constructor(binaryLabel: string, hint: string);
10
+ }
11
+ /**
12
+ * Guard for a "real spawn" implementation: throws
13
+ * {@link HermeticSpawnViolationError} when called under vitest, so an
14
+ * accidental real spawn in a test fails hard instead of silently
15
+ * running. No-op outside of vitest (production behavior is unchanged),
16
+ * and no-op when `HARNESS_ALLOW_REAL_SPAWN=1` is set (see module doc) —
17
+ * the first such no-op under vitest prints a one-time stderr warning so
18
+ * the disabled guard is visible rather than silent.
19
+ *
20
+ * @param binaryLabel human-readable name of what would be spawned (e.g.
21
+ * `"npx orchestrator-workflow init"`), used in the error message.
22
+ * @param hint one line telling the test author what to do instead (e.g.
23
+ * "inject a fake `owInitSpawn` runner").
24
+ */
25
+ export declare function assertNoRealSpawnInTests(binaryLabel: string, hint: string): void;