@lannguyensi/harness 0.39.0 → 0.41.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 (127) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/cli/approve/stdin-report.d.ts +42 -0
  3. package/dist/cli/approve/stdin-report.js +143 -0
  4. package/dist/cli/approve/stdin-report.js.map +1 -0
  5. package/dist/cli/approve/understanding.d.ts +20 -0
  6. package/dist/cli/approve/understanding.js +44 -0
  7. package/dist/cli/approve/understanding.js.map +1 -1
  8. package/dist/cli/diff/git.d.ts +5 -0
  9. package/dist/cli/diff/git.js +25 -3
  10. package/dist/cli/diff/git.js.map +1 -1
  11. package/dist/cli/doctor/codex.d.ts +8 -0
  12. package/dist/cli/doctor/codex.js +8 -1
  13. package/dist/cli/doctor/codex.js.map +1 -1
  14. package/dist/cli/doctor/format.js +23 -11
  15. package/dist/cli/doctor/format.js.map +1 -1
  16. package/dist/cli/doctor/index.d.ts +36 -0
  17. package/dist/cli/doctor/index.js +131 -29
  18. package/dist/cli/doctor/index.js.map +1 -1
  19. package/dist/cli/doctor/types.d.ts +26 -0
  20. package/dist/cli/dry-run.js +5 -0
  21. package/dist/cli/dry-run.js.map +1 -1
  22. package/dist/cli/explain.js +16 -3
  23. package/dist/cli/explain.js.map +1 -1
  24. package/dist/cli/index.js +109 -6
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/init/composer.js +10 -31
  27. package/dist/cli/init/composer.js.map +1 -1
  28. package/dist/cli/init/index.d.ts +18 -0
  29. package/dist/cli/init/index.js +28 -2
  30. package/dist/cli/init/index.js.map +1 -1
  31. package/dist/cli/init/interactive.d.ts +8 -0
  32. package/dist/cli/init/interactive.js +22 -1
  33. package/dist/cli/init/interactive.js.map +1 -1
  34. package/dist/cli/init/profiles.d.ts +2 -2
  35. package/dist/cli/init/profiles.js +11 -2
  36. package/dist/cli/init/profiles.js.map +1 -1
  37. package/dist/cli/init/templates.d.ts +1 -1
  38. package/dist/cli/init/templates.js +174 -4
  39. package/dist/cli/init/templates.js.map +1 -1
  40. package/dist/cli/list.js +3 -1
  41. package/dist/cli/list.js.map +1 -1
  42. package/dist/cli/pack/approve-escape.d.ts +19 -0
  43. package/dist/cli/pack/approve-escape.js +120 -0
  44. package/dist/cli/pack/approve-escape.js.map +1 -0
  45. package/dist/cli/pack/hook-bootstrap.d.ts +24 -0
  46. package/dist/cli/pack/hook-bootstrap.js +47 -1
  47. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  48. package/dist/cli/pack/hook-branch-protection.js +9 -1
  49. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  50. package/dist/cli/pack/hook-codex-post-tool-use.d.ts +25 -0
  51. package/dist/cli/pack/hook-codex-post-tool-use.js +160 -0
  52. package/dist/cli/pack/hook-codex-post-tool-use.js.map +1 -0
  53. package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +1 -0
  54. package/dist/cli/pack/hook-codex-pre-tool-use.js +43 -10
  55. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  56. package/dist/cli/pack/hook-codex-stop.js +1 -8
  57. package/dist/cli/pack/hook-codex-stop.js.map +1 -1
  58. package/dist/cli/pack/hook-post-tool-use.js +16 -131
  59. package/dist/cli/pack/hook-post-tool-use.js.map +1 -1
  60. package/dist/cli/pack/hook-pre-tool-use.js +69 -26
  61. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  62. package/dist/cli/pack/hook-stay-in-scope.js +63 -20
  63. package/dist/cli/pack/hook-stay-in-scope.js.map +1 -1
  64. package/dist/cli/pack/hook-track-active-claim.js +33 -9
  65. package/dist/cli/pack/hook-track-active-claim.js.map +1 -1
  66. package/dist/cli/pack/index.d.ts +2 -1
  67. package/dist/cli/pack/index.js +2 -1
  68. package/dist/cli/pack/index.js.map +1 -1
  69. package/dist/cli/pack/mutate.d.ts +19 -0
  70. package/dist/cli/pack/mutate.js +38 -0
  71. package/dist/cli/pack/mutate.js.map +1 -1
  72. package/dist/cli/pack/reseed.d.ts +19 -0
  73. package/dist/cli/pack/reseed.js +118 -0
  74. package/dist/cli/pack/reseed.js.map +1 -0
  75. package/dist/cli/pack/understanding-report-schema-hint.js +12 -1
  76. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  77. package/dist/cli/pause/index.js +12 -3
  78. package/dist/cli/pause/index.js.map +1 -1
  79. package/dist/cli/session-start/index.d.ts +14 -0
  80. package/dist/cli/session-start/index.js +42 -2
  81. package/dist/cli/session-start/index.js.map +1 -1
  82. package/dist/cli/validate/checks.js +8 -0
  83. package/dist/cli/validate/checks.js.map +1 -1
  84. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +4 -2
  85. package/dist/policy-packs/builtin/branch-protection-runtime.js +4 -2
  86. package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
  87. package/dist/policy-packs/builtin/branch-protection.d.ts +10 -1
  88. package/dist/policy-packs/builtin/branch-protection.js +17 -0
  89. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  90. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +197 -24
  91. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +342 -40
  92. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  93. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +26 -1
  94. package/dist/policy-packs/builtin/understanding-before-execution.js +302 -17
  95. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  96. package/dist/policy-packs/registry.d.ts +22 -1
  97. package/dist/policy-packs/registry.js +19 -2
  98. package/dist/policy-packs/registry.js.map +1 -1
  99. package/dist/policy-packs/ux-compare.d.ts +14 -0
  100. package/dist/policy-packs/ux-compare.js +58 -0
  101. package/dist/policy-packs/ux-compare.js.map +1 -0
  102. package/dist/policy-packs/ux-drift-check.d.ts +27 -0
  103. package/dist/policy-packs/ux-drift-check.js +78 -0
  104. package/dist/policy-packs/ux-drift-check.js.map +1 -0
  105. package/dist/probes/mcp.d.ts +13 -0
  106. package/dist/probes/mcp.js +57 -0
  107. package/dist/probes/mcp.js.map +1 -1
  108. package/dist/runtime/approval-signing.d.ts +82 -0
  109. package/dist/runtime/approval-signing.js +275 -0
  110. package/dist/runtime/approval-signing.js.map +1 -0
  111. package/dist/runtime/intercept.js +62 -5
  112. package/dist/runtime/intercept.js.map +1 -1
  113. package/dist/runtime/ledger-add.js +5 -1
  114. package/dist/runtime/ledger-add.js.map +1 -1
  115. package/dist/runtime/pause-sentinel.js +14 -3
  116. package/dist/runtime/pause-sentinel.js.map +1 -1
  117. package/dist/runtime/recovery-git-commit.d.ts +22 -0
  118. package/dist/runtime/recovery-git-commit.js +284 -0
  119. package/dist/runtime/recovery-git-commit.js.map +1 -0
  120. package/dist/runtime/tool-name-aliases.d.ts +1 -0
  121. package/dist/runtime/tool-name-aliases.js +7 -1
  122. package/dist/runtime/tool-name-aliases.js.map +1 -1
  123. package/dist/schema/index.d.ts +53 -42
  124. package/dist/schema/policies.d.ts +56 -44
  125. package/dist/schema/policies.js +79 -9
  126. package/dist/schema/policies.js.map +1 -1
  127. package/package.json +2 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ux-compare.js","sourceRoot":"","sources":["../../src/policy-packs/ux-compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Manifest } from "../schema/index.js";
2
+ export type PolicyPackUxDriftField = "ux" | "producers";
3
+ export interface PolicyPackUxDrift {
4
+ packIndex: number;
5
+ packName: string;
6
+ /** Which sub-field(s) diverge from the shipped template. */
7
+ fields: PolicyPackUxDriftField[];
8
+ message: string;
9
+ }
10
+ /**
11
+ * Walks `manifest.policy_packs` in declared order. For each enabled
12
+ * builtin pack that has a registered shipped default (see
13
+ * `resolveBuiltinDefaultConfig`), compares the operator's declared
14
+ * `config.ux` / `config.producers` (when present) against that default
15
+ * and reports a divergence for each sub-field that differs.
16
+ *
17
+ * A pack that never declared `config.ux` (or `config.producers`) at all
18
+ * is NOT flagged here: that is a distinct, pre-existing gap (the pack
19
+ * falls back to its legacy engine-vocabulary deny text) rather than a
20
+ * stale COPY of the shipped text, and is out of scope for this check —
21
+ * see task 68b9ad9c's framing (an operator whose manifest predates a
22
+ * wording fix has a STALE `ux.run`, not a MISSING one). A malformed
23
+ * `config.ux` / `config.producers` (already reported separately by
24
+ * `checkPolicyPackConfigs`) is treated as diverging here too, rather
25
+ * than being silently skipped.
26
+ */
27
+ export declare function checkPolicyPackUxDrift(manifest: Manifest): PolicyPackUxDrift[];
@@ -0,0 +1,78 @@
1
+ // Per-pack `config.ux` / `config.producers` divergence check (task
2
+ // 68b9ad9c). Used by `harness doctor` to warn when an enabled builtin
3
+ // pack's agent-facing deny-message text no longer matches the shipped
4
+ // template for that pack.
5
+ //
6
+ // Motivation (dogfood 2026-07-10, harness 0.40.0): the understanding-gate
7
+ // deny message is entirely driven by `config.ux` when the operator has
8
+ // declared one — the init templates (Solo/Team/Full/Custom) were fixed to
9
+ // teach a new submission form, but that fix only reaches manifests
10
+ // generated AFTER the fix landed. An operator who installed before the fix
11
+ // has an on-disk `config.ux` that still teaches the old wording, and
12
+ // nothing short of a hand-edit ever updated it: `harness apply` projects
13
+ // the manifest OUT to settings.json, it never re-seeds pack config FROM
14
+ // the shipped template back INTO the manifest.
15
+ //
16
+ // This check is the read side of that gap (the warning); `harness pack
17
+ // reseed <name>` (src/cli/pack/reseed.ts) is the opt-in write side. Both
18
+ // consume the same canonical default via `resolveBuiltinDefaultConfig`,
19
+ // and the same comparison helpers (ux-compare.ts), so they can never
20
+ // independently drift on what "the shipped template" means.
21
+ //
22
+ // Deliberately doctor-only (not `harness validate`), mirroring
23
+ // `checkPolicyPackVersions`: this is an informational nudge about
24
+ // wording that still functions, not a schema violation.
25
+ import { resolveBuiltinDefaultConfig } from "./registry.js";
26
+ import { producersEqual, safeParseProducers, safeParseUx, uxEqual } from "./ux-compare.js";
27
+ /**
28
+ * Walks `manifest.policy_packs` in declared order. For each enabled
29
+ * builtin pack that has a registered shipped default (see
30
+ * `resolveBuiltinDefaultConfig`), compares the operator's declared
31
+ * `config.ux` / `config.producers` (when present) against that default
32
+ * and reports a divergence for each sub-field that differs.
33
+ *
34
+ * A pack that never declared `config.ux` (or `config.producers`) at all
35
+ * is NOT flagged here: that is a distinct, pre-existing gap (the pack
36
+ * falls back to its legacy engine-vocabulary deny text) rather than a
37
+ * stale COPY of the shipped text, and is out of scope for this check —
38
+ * see task 68b9ad9c's framing (an operator whose manifest predates a
39
+ * wording fix has a STALE `ux.run`, not a MISSING one). A malformed
40
+ * `config.ux` / `config.producers` (already reported separately by
41
+ * `checkPolicyPackConfigs`) is treated as diverging here too, rather
42
+ * than being silently skipped.
43
+ */
44
+ export function checkPolicyPackUxDrift(manifest) {
45
+ const out = [];
46
+ manifest.policy_packs.forEach((pack, packIndex) => {
47
+ if (!pack.enabled)
48
+ return;
49
+ const canonical = resolveBuiltinDefaultConfig(pack);
50
+ if (!canonical)
51
+ return;
52
+ const fields = [];
53
+ const currentUx = pack.config["ux"];
54
+ if (canonical.ux && currentUx !== undefined) {
55
+ const parsed = safeParseUx(currentUx);
56
+ if (!parsed || !uxEqual(parsed, canonical.ux))
57
+ fields.push("ux");
58
+ }
59
+ const currentProducers = pack.config["producers"];
60
+ if (canonical.producers && currentProducers !== undefined) {
61
+ const parsed = safeParseProducers(currentProducers);
62
+ if (!parsed || !producersEqual(parsed, canonical.producers))
63
+ fields.push("producers");
64
+ }
65
+ if (fields.length === 0)
66
+ return;
67
+ out.push({
68
+ packIndex,
69
+ packName: pack.name,
70
+ fields,
71
+ message: `pack "${pack.name}" config.${fields.join(" / config.")} diverges from the shipped ` +
72
+ `builtin template; a supported update exists. Review with ` +
73
+ `\`harness pack reseed ${pack.name} --dry-run\` and apply with \`harness pack reseed ${pack.name}\`.`,
74
+ });
75
+ });
76
+ return out;
77
+ }
78
+ //# sourceMappingURL=ux-drift-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ux-drift-check.js","sourceRoot":"","sources":["../../src/policy-packs/ux-drift-check.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,sEAAsE;AACtE,sEAAsE;AACtE,0BAA0B;AAC1B,EAAE;AACF,0EAA0E;AAC1E,uEAAuE;AACvE,0EAA0E;AAC1E,mEAAmE;AACnE,2EAA2E;AAC3E,qEAAqE;AACrE,yEAAyE;AACzE,wEAAwE;AACxE,+CAA+C;AAC/C,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,wEAAwE;AACxE,qEAAqE;AACrE,4DAA4D;AAC5D,EAAE;AACF,+DAA+D;AAC/D,kEAAkE;AAClE,wDAAwD;AAExD,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAa3F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAkB;IACvD,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE;QAChD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,MAAM,SAAS,GAAG,2BAA2B,CAAC,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,MAAM,MAAM,GAA6B,EAAE,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAClD,IAAI,SAAS,CAAC,SAAS,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxF,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAEhC,GAAG,CAAC,IAAI,CAAC;YACP,SAAS;YACT,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,MAAM;YACN,OAAO,EACL,SAAS,IAAI,CAAC,IAAI,YAAY,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,6BAA6B;gBACpF,2DAA2D;gBAC3D,yBAAyB,IAAI,CAAC,IAAI,qDAAqD,IAAI,CAAC,IAAI,KAAK;SACxG,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -6,6 +6,19 @@ export type McpProbeOutcome = {
6
6
  kind: "error";
7
7
  latencyMs: number;
8
8
  message: string;
9
+ /**
10
+ * Set when the failure is a `spawn` `ENOENT` (the declared binary
11
+ * could not be resolved at all, as opposed to the process starting
12
+ * and then misbehaving). Doctor uses this to decide whether a
13
+ * PATH-shadow remediation hint applies (harness/7f8fb4bc).
14
+ */
15
+ enoent?: boolean;
16
+ /**
17
+ * One-line PATH-shadow remediation, populated by the doctor layer
18
+ * (which knows the resolved npm global bin dir) when an `enoent`
19
+ * binary is found to actually exist there, just not on PATH.
20
+ */
21
+ pathHint?: string;
9
22
  }
10
23
  /**
11
24
  * Server exited with code 0 BEFORE the doctor received a JSON-RPC
@@ -15,6 +15,29 @@ function commandToArgs(server) {
15
15
  const args = list.slice(1).map(expandHomePath);
16
16
  return { exe, args };
17
17
  }
18
+ /**
19
+ * Build the `error` outcome for a spawn-time failure (as opposed to a
20
+ * failure after the process started). `ENOENT` — the binary is not
21
+ * resolvable at all — gets the "not found on PATH" wording the doctor
22
+ * summary and PATH-shadow hint (added by the doctor layer) key off of;
23
+ * any other errno (e.g. `EACCES`) still reports gracefully but without
24
+ * implying a PATH problem.
25
+ */
26
+ function spawnErrorOutcome(exe, err, latencyMs) {
27
+ if (err?.code === "ENOENT") {
28
+ return {
29
+ kind: "error",
30
+ latencyMs,
31
+ message: `${exe} not found on PATH (spawn ENOENT)`,
32
+ enoent: true,
33
+ };
34
+ }
35
+ return {
36
+ kind: "error",
37
+ latencyMs,
38
+ message: `spawn ${exe} failed: ${err?.message ?? "unknown spawn error"}`,
39
+ };
40
+ }
18
41
  async function runRealProbe(server, opts) {
19
42
  if (server.enabled === false) {
20
43
  return { name: server.name, outcome: { kind: "disabled" } };
@@ -49,6 +72,18 @@ async function runRealProbe(server, opts) {
49
72
  let processExited = false;
50
73
  let exitCode = null;
51
74
  let exitSignal = null;
75
+ // A declared MCP whose binary cannot be resolved (spawn ENOENT, or any
76
+ // other spawn-time failure such as EACCES) emits an 'error' event on
77
+ // `child` ASYNCHRONOUSLY, not a synchronous throw from `spawn()` above.
78
+ // Node's EventEmitter treats an 'error' event with zero listeners as an
79
+ // unhandled exception that crashes the whole process — this is exactly
80
+ // the "spawn grounding-mcp ENOENT ... emitted 'error' event on
81
+ // ChildProcess" dogfood crash (harness/7f8fb4bc). Listening here turns
82
+ // it into a normal, per-server doctor outcome instead.
83
+ let spawnErr = null;
84
+ child.on("error", (err) => {
85
+ spawnErr = err;
86
+ });
52
87
  child.stderr.on("data", (chunk) => {
53
88
  stderrBuf += chunk.toString("utf8");
54
89
  });
@@ -105,6 +140,14 @@ async function runRealProbe(server, opts) {
105
140
  child.once("exit", () => resolve("exit"));
106
141
  });
107
142
  }
143
+ function waitForSpawnError() {
144
+ return new Promise((resolve) => {
145
+ if (spawnErr)
146
+ resolve("spawn-error");
147
+ else
148
+ child.once("error", () => resolve("spawn-error"));
149
+ });
150
+ }
108
151
  const timers = new Set();
109
152
  function timeoutPromise() {
110
153
  return new Promise((resolve) => {
@@ -130,7 +173,14 @@ async function runRealProbe(server, opts) {
130
173
  }),
131
174
  waitForExit(),
132
175
  timeoutPromise(),
176
+ waitForSpawnError(),
133
177
  ]);
178
+ if (initResult === "spawn-error") {
179
+ return {
180
+ name: server.name,
181
+ outcome: spawnErrorOutcome(exe, spawnErr, Date.now() - start),
182
+ };
183
+ }
134
184
  if (initResult === "exit") {
135
185
  const latencyMs = Date.now() - start;
136
186
  // Clean exit (code 0) before responding looks like a config issue
@@ -179,7 +229,14 @@ async function runRealProbe(server, opts) {
179
229
  }),
180
230
  waitForExit(),
181
231
  timeoutPromise(),
232
+ waitForSpawnError(),
182
233
  ]);
234
+ if (verbResult === "spawn-error") {
235
+ return {
236
+ name: server.name,
237
+ outcome: spawnErrorOutcome(exe, spawnErr, Date.now() - start),
238
+ };
239
+ }
183
240
  if (verbResult === "exit") {
184
241
  const latencyMs = Date.now() - start;
185
242
  if (exitCode === 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/probes/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/probes/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /** Versioned algorithm tag, so a future re-key scheme can reject mismatches explicitly rather than guess. */
2
+ export declare const SIGNING_ALG = "hmac-sha256-v1";
3
+ /** Basename of the signing-key file, a sibling of `.approvals/` under `generatedDir`. */
4
+ export declare const SIGNING_KEY_BASENAME = ".approval-signing.key";
5
+ /** Filesystem path of the signing key for a given `generatedDir`. */
6
+ export declare function signingKeyPathFor(generatedDir: string): string;
7
+ export interface SigningKeyHandle {
8
+ key: Buffer;
9
+ filePath: string;
10
+ /** True when this call generated a fresh key (first use / post-rotation). */
11
+ created: boolean;
12
+ }
13
+ /**
14
+ * Read the signing key, generating one (0600, `crypto.randomBytes(32)`) on
15
+ * first use. Race-safe-ish: a concurrent create loses the `wx` (exclusive
16
+ * create) race and falls back to reading whatever the winner wrote, rather
17
+ * than throwing or clobbering it. A key file shorter than `KEY_BYTES`
18
+ * (truncated / corrupted) is treated as absent and regenerated, since a
19
+ * short key would only weaken every future signature.
20
+ */
21
+ export declare function getOrCreateSigningKey(generatedDir: string): SigningKeyHandle;
22
+ /**
23
+ * Force a fresh key, overwriting any existing one. Every marker signed
24
+ * under the old key stops verifying immediately (see module doc). Not
25
+ * currently wired to a CLI verb (documented as "delete the file" for v1);
26
+ * exported so a future `harness approve rotate-key`-style verb, or a test,
27
+ * can call it directly without re-deriving the path convention.
28
+ */
29
+ export declare function rotateSigningKey(generatedDir: string): SigningKeyHandle;
30
+ /** sha256 hex digest of a string, used to bind a marker to a persisted report's content. */
31
+ export declare function sha256Hex(content: string): string;
32
+ export interface SignedMarkerFields {
33
+ approvedAt: string;
34
+ approvedBy: string;
35
+ /** sha256 hex of the persisted-report content this approval is bound to; null when no report exists to bind (e.g. branch-protection, or a ledger-only approval). */
36
+ reportContentHash: string | null;
37
+ alg: string;
38
+ signature: string;
39
+ }
40
+ /**
41
+ * Sign a marker's fields for `markerId` (the marker's lookup key — a raw
42
+ * sessionId for the understanding-gate session marker, `task-<id>` for a
43
+ * task-scoped marker, `branch-protection-<sessionId>` for the
44
+ * branch-protection twin). Binding `markerId` into the signed payload is
45
+ * what stops a validly-signed marker from being copied/renamed onto a
46
+ * DIFFERENT id and still verifying — verification always recomputes the
47
+ * HMAC using the id the caller is checking, not one read back out of the
48
+ * marker body.
49
+ */
50
+ export declare function signMarker(generatedDir: string, markerId: string, marker: {
51
+ approvedAt: string;
52
+ approvedBy: string;
53
+ reportContentHash?: string | null;
54
+ }): SignedMarkerFields;
55
+ export type SignatureVerification = {
56
+ ok: true;
57
+ } | {
58
+ ok: false;
59
+ reason: string;
60
+ /**
61
+ * `"key-unavailable"` when verification could not even run because
62
+ * obtaining the signing key itself failed (permission error, disk
63
+ * issue) — a fail-closed I/O problem, not evidence of tampering.
64
+ * Callers (`checkApprovalMarker`) use this to classify the outcome
65
+ * distinctly from an actual forged/invalid signature (review LOW 1,
66
+ * harness/f9485cc7): a broken key file must not read as an active
67
+ * forgery attempt in audit output. Omitted for every other failure
68
+ * (missing field, wrong alg, bad signature).
69
+ */
70
+ kind?: "key-unavailable";
71
+ };
72
+ /**
73
+ * Verify a parsed marker JSON body against `markerId`. Never throws: any
74
+ * failure to obtain the signing key (permission error, disk issue) is
75
+ * treated as a verification failure, not an exception — the gate-side
76
+ * caller must fail closed (not approved) rather than crash the hook
77
+ * process, which the surrounding PreToolUse blocker would otherwise turn
78
+ * into an uncontrolled failure mode. That specific failure mode is tagged
79
+ * `kind: "key-unavailable"` so the caller can tell "we couldn't even check"
80
+ * apart from "we checked and it's invalid".
81
+ */
82
+ export declare function verifyMarkerSignature(generatedDir: string, markerId: string, payload: Record<string, unknown>): SignatureVerification;
@@ -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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"}