@lannguyensi/harness 0.44.0 → 0.46.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 (234) hide show
  1. package/CHANGELOG.md +1245 -0
  2. package/dist/cli/adopt/derive.d.ts +16 -7
  3. package/dist/cli/adopt/derive.js +19 -2
  4. package/dist/cli/adopt/derive.js.map +1 -1
  5. package/dist/cli/adopt/index.js +18 -3
  6. package/dist/cli/adopt/index.js.map +1 -1
  7. package/dist/cli/apply/apply.d.ts +1 -0
  8. package/dist/cli/apply/apply.js +62 -6
  9. package/dist/cli/apply/apply.js.map +1 -1
  10. package/dist/cli/apply/generate-codex-config.js +5 -6
  11. package/dist/cli/apply/generate-codex-config.js.map +1 -1
  12. package/dist/cli/apply/generate-opencode-config.d.ts +58 -0
  13. package/dist/cli/apply/generate-opencode-config.js +210 -0
  14. package/dist/cli/apply/generate-opencode-config.js.map +1 -0
  15. package/dist/cli/apply/generate-settings.d.ts +90 -32
  16. package/dist/cli/apply/generate-settings.js +133 -10
  17. package/dist/cli/apply/generate-settings.js.map +1 -1
  18. package/dist/cli/apply/index.d.ts +2 -1
  19. package/dist/cli/apply/index.js +2 -1
  20. package/dist/cli/apply/index.js.map +1 -1
  21. package/dist/cli/apply/next-steps.d.ts +20 -0
  22. package/dist/cli/apply/next-steps.js +60 -0
  23. package/dist/cli/apply/next-steps.js.map +1 -1
  24. package/dist/cli/approve/stdin-report.d.ts +15 -0
  25. package/dist/cli/approve/stdin-report.js +1 -1
  26. package/dist/cli/approve/stdin-report.js.map +1 -1
  27. package/dist/cli/approve/understanding.d.ts +75 -0
  28. package/dist/cli/approve/understanding.js +150 -2
  29. package/dist/cli/approve/understanding.js.map +1 -1
  30. package/dist/cli/audit.d.ts +1 -1
  31. package/dist/cli/audit.js +3 -2
  32. package/dist/cli/audit.js.map +1 -1
  33. package/dist/cli/doctor/claude-mcp.d.ts +19 -1
  34. package/dist/cli/doctor/claude-mcp.js +36 -10
  35. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  36. package/dist/cli/doctor/codex.d.ts +3 -1
  37. package/dist/cli/doctor/codex.js +6 -9
  38. package/dist/cli/doctor/codex.js.map +1 -1
  39. package/dist/cli/doctor/format.js +94 -8
  40. package/dist/cli/doctor/format.js.map +1 -1
  41. package/dist/cli/doctor/index.d.ts +23 -0
  42. package/dist/cli/doctor/index.js +106 -2
  43. package/dist/cli/doctor/index.js.map +1 -1
  44. package/dist/cli/doctor/opencode.d.ts +24 -0
  45. package/dist/cli/doctor/opencode.js +107 -0
  46. package/dist/cli/doctor/opencode.js.map +1 -0
  47. package/dist/cli/doctor/target-checks.d.ts +11 -0
  48. package/dist/cli/doctor/target-checks.js +25 -0
  49. package/dist/cli/doctor/target-checks.js.map +1 -0
  50. package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
  51. package/dist/cli/doctor/toolchain-parity.js +154 -0
  52. package/dist/cli/doctor/toolchain-parity.js.map +1 -0
  53. package/dist/cli/doctor/types.d.ts +85 -1
  54. package/dist/cli/doctor/types.js +1 -1
  55. package/dist/cli/doctor/types.js.map +1 -1
  56. package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
  57. package/dist/cli/doctor/understanding-mode-env.js +83 -0
  58. package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
  59. package/dist/cli/dry-run.js +32 -17
  60. package/dist/cli/dry-run.js.map +1 -1
  61. package/dist/cli/explain.d.ts +1 -1
  62. package/dist/cli/explain.js +1 -1
  63. package/dist/cli/explain.js.map +1 -1
  64. package/dist/cli/index.js +64 -5
  65. package/dist/cli/index.js.map +1 -1
  66. package/dist/cli/init/composer.js +36 -13
  67. package/dist/cli/init/composer.js.map +1 -1
  68. package/dist/cli/init/interactive.d.ts +7 -4
  69. package/dist/cli/init/interactive.js +79 -27
  70. package/dist/cli/init/interactive.js.map +1 -1
  71. package/dist/cli/init/profiles.d.ts +2 -2
  72. package/dist/cli/init/profiles.js +32 -1
  73. package/dist/cli/init/profiles.js.map +1 -1
  74. package/dist/cli/init/templates.d.ts +2 -1
  75. package/dist/cli/init/templates.js +130 -20
  76. package/dist/cli/init/templates.js.map +1 -1
  77. package/dist/cli/pack/approve-escape.js +69 -8
  78. package/dist/cli/pack/approve-escape.js.map +1 -1
  79. package/dist/cli/pack/hook-branch-protection.js +1 -1
  80. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  81. package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
  82. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  83. package/dist/cli/pack/hook-post-merge-gate.js +1 -1
  84. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
  85. package/dist/cli/pack/hook-pre-tool-use.js +91 -4
  86. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  87. package/dist/cli/pack/hook-solution-acceptance.js +19 -2
  88. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  89. package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
  90. package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
  91. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  92. package/dist/cli/pause/index.js +1 -1
  93. package/dist/cli/pause/index.js.map +1 -1
  94. package/dist/cli/policy/intercept.d.ts +148 -0
  95. package/dist/cli/policy/intercept.js +379 -48
  96. package/dist/cli/policy/intercept.js.map +1 -1
  97. package/dist/cli/session-start/index.d.ts +12 -3
  98. package/dist/cli/session-start/index.js +67 -11
  99. package/dist/cli/session-start/index.js.map +1 -1
  100. package/dist/cli/session-start/stale-base-check.d.ts +71 -0
  101. package/dist/cli/session-start/stale-base-check.js +379 -0
  102. package/dist/cli/session-start/stale-base-check.js.map +1 -0
  103. package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
  104. package/dist/cli/session-start/toolchain-parity.js +244 -36
  105. package/dist/cli/session-start/toolchain-parity.js.map +1 -1
  106. package/dist/cli/smoke/assertions.d.ts +9 -2
  107. package/dist/cli/smoke/assertions.js +9 -2
  108. package/dist/cli/smoke/assertions.js.map +1 -1
  109. package/dist/cli/uninstall/index.js +18 -2
  110. package/dist/cli/uninstall/index.js.map +1 -1
  111. package/dist/cli/validate/checks.d.ts +3 -1
  112. package/dist/cli/validate/checks.js +298 -3
  113. package/dist/cli/validate/checks.js.map +1 -1
  114. package/dist/io/claude-mcp.d.ts +122 -1
  115. package/dist/io/claude-mcp.js +214 -12
  116. package/dist/io/claude-mcp.js.map +1 -1
  117. package/dist/io/duration.js.map +1 -0
  118. package/dist/io/expand-home.js.map +1 -0
  119. package/dist/io/extract.js.map +1 -0
  120. package/dist/io/harness-lock.js +1 -1
  121. package/dist/io/harness-lock.js.map +1 -1
  122. package/dist/{runtime → io}/ledger-record.d.ts +1 -1
  123. package/dist/io/ledger-record.js.map +1 -0
  124. package/dist/policies/index.d.ts +2 -2
  125. package/dist/policies/index.js +2 -2
  126. package/dist/policies/index.js.map +1 -1
  127. package/dist/policies/ledger-client.js +10 -6
  128. package/dist/policies/ledger-client.js.map +1 -1
  129. package/dist/policies/producers.js +1 -1
  130. package/dist/policies/producers.js.map +1 -1
  131. package/dist/policies/requires.js +2 -2
  132. package/dist/policies/requires.js.map +1 -1
  133. package/dist/policy-packs/builtin/branch-protection.js +28 -3
  134. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  135. package/dist/policy-packs/builtin/post-merge-gate.js +45 -9
  136. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
  137. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +139 -8
  138. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +246 -12
  139. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  140. package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
  141. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
  142. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
  143. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
  144. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
  145. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
  146. package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
  147. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
  148. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
  149. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
  150. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
  151. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
  152. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
  153. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
  154. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
  155. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
  156. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
  157. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
  158. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
  159. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
  160. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
  161. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
  162. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
  163. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
  164. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
  165. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
  166. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
  167. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
  168. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  169. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
  170. package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
  171. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  172. package/dist/policy-packs/registry.d.ts +11 -0
  173. package/dist/policy-packs/registry.js +1 -1
  174. package/dist/policy-packs/registry.js.map +1 -1
  175. package/dist/policy-packs/runtime.d.ts +1 -1
  176. package/dist/policy-packs/runtime.js +10 -1
  177. package/dist/policy-packs/runtime.js.map +1 -1
  178. package/dist/runtime/agent-facing.js +1 -1
  179. package/dist/runtime/agent-facing.js.map +1 -1
  180. package/dist/runtime/bash-match-facts.d.ts +9 -7
  181. package/dist/runtime/bash-match-facts.js +9 -7
  182. package/dist/runtime/bash-match-facts.js.map +1 -1
  183. package/dist/runtime/bash-match-registry.d.ts +75 -38
  184. package/dist/runtime/bash-match-registry.js +279 -232
  185. package/dist/runtime/bash-match-registry.js.map +1 -1
  186. package/dist/runtime/bash-prefix-parse.d.ts +7 -0
  187. package/dist/runtime/bash-prefix-parse.js +205 -17
  188. package/dist/runtime/bash-prefix-parse.js.map +1 -1
  189. package/dist/runtime/command-normalize.d.ts +112 -0
  190. package/dist/runtime/command-normalize.js +411 -38
  191. package/dist/runtime/command-normalize.js.map +1 -1
  192. package/dist/runtime/hermetic-spawn-guard.js +6 -0
  193. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  194. package/dist/runtime/index.d.ts +2 -2
  195. package/dist/runtime/index.js +2 -2
  196. package/dist/runtime/index.js.map +1 -1
  197. package/dist/runtime/intercept.d.ts +78 -3
  198. package/dist/runtime/intercept.js +151 -39
  199. package/dist/runtime/intercept.js.map +1 -1
  200. package/dist/runtime/ledger-add.js +1 -1
  201. package/dist/runtime/ledger-add.js.map +1 -1
  202. package/dist/runtime/read-only-bash.js +303 -24
  203. package/dist/runtime/read-only-bash.js.map +1 -1
  204. package/dist/schema/doctor.d.ts +25 -0
  205. package/dist/schema/doctor.js +23 -0
  206. package/dist/schema/doctor.js.map +1 -0
  207. package/dist/schema/extract.js +1 -1
  208. package/dist/schema/extract.js.map +1 -1
  209. package/dist/schema/index.d.ts +76 -0
  210. package/dist/schema/index.js +9 -0
  211. package/dist/schema/index.js.map +1 -1
  212. package/dist/schema/requires.js +2 -2
  213. package/dist/schema/requires.js.map +1 -1
  214. package/dist/schema/risk.d.ts +7 -0
  215. package/dist/schema/risk.js +23 -0
  216. package/dist/schema/risk.js.map +1 -1
  217. package/dist/schema/stale-base-check.d.ts +18 -0
  218. package/dist/schema/stale-base-check.js +37 -0
  219. package/dist/schema/stale-base-check.js.map +1 -0
  220. package/dist/schema/toolchain-parity.d.ts +3 -0
  221. package/dist/schema/toolchain-parity.js +13 -0
  222. package/dist/schema/toolchain-parity.js.map +1 -1
  223. package/package.json +3 -2
  224. package/dist/policies/duration.js.map +0 -1
  225. package/dist/policies/extract.js.map +0 -1
  226. package/dist/runtime/expand-home.js.map +0 -1
  227. package/dist/runtime/ledger-record.js.map +0 -1
  228. /package/dist/{policies → io}/duration.d.ts +0 -0
  229. /package/dist/{policies → io}/duration.js +0 -0
  230. /package/dist/{runtime → io}/expand-home.d.ts +0 -0
  231. /package/dist/{runtime → io}/expand-home.js +0 -0
  232. /package/dist/{policies → io}/extract.d.ts +0 -0
  233. /package/dist/{policies → io}/extract.js +0 -0
  234. /package/dist/{runtime → io}/ledger-record.js +0 -0
@@ -0,0 +1,89 @@
1
+ // Active-claim tracking (harness/494fd1e5), split out of the former
2
+ // monolithic understanding-before-execution-runtime.ts (structural
3
+ // concentration slice 2, agent-tasks 348a4d42). Pure move: see
4
+ // src/policy-packs/builtin/understanding-before-execution/index.ts for
5
+ // the re-exported public surface.
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { atomicWriteFile } from "../../../io/atomic-write.js";
9
+ // Active-claim tracking (harness/494fd1e5). When the agent calls
10
+ // `mcp__agent-tasks__task_start`, a PostToolUse hook writes the claimed
11
+ // task id to a stable file. `harness approve understanding` reads it
12
+ // when --task is absent and auto-supplies it as the task-scoped marker
13
+ // target. This closes the v1 ergonomics gap from PR #184 where the
14
+ // operator had to type the taskId by hand.
15
+ //
16
+ // File contract: a single line containing just the taskId. No JSON,
17
+ // no metadata. Operators can `cat` it to debug. The post-tool-use
18
+ // hook on task_finish / task_abandon removes the file.
19
+ export const ACTIVE_CLAIM_FILENAME = "active-claim";
20
+ export function activeClaimPathFor(generatedDir) {
21
+ return path.join(generatedDir, ACTIVE_CLAIM_FILENAME);
22
+ }
23
+ function rejectMalformedClaimId(taskId) {
24
+ if (taskId.length === 0) {
25
+ throw new Error("taskId is empty");
26
+ }
27
+ if (taskId.includes("\n") ||
28
+ taskId.includes("\r") ||
29
+ taskId.includes("/") ||
30
+ taskId.includes("\\") ||
31
+ taskId.includes("..")) {
32
+ throw new Error(`taskId contains forbidden characters (newline / path-separator / traversal): ${JSON.stringify(taskId)}`);
33
+ }
34
+ }
35
+ /**
36
+ * Hook-side: write the active claim file. Atomic. Called from the
37
+ * track-active-claim PostToolUse hook on `task_start`. The body is
38
+ * just the taskId (no JSON) so an operator running
39
+ * `cat ~/.claude/harness.generated/active-claim` sees the id directly.
40
+ */
41
+ export function writeActiveClaim(generatedDir, taskId) {
42
+ rejectMalformedClaimId(taskId);
43
+ const filePath = activeClaimPathFor(generatedDir);
44
+ atomicWriteFile(filePath, `${taskId}\n`);
45
+ return filePath;
46
+ }
47
+ /**
48
+ * Operator-side: read the active claim file. Returns the taskId or
49
+ * null when the file is absent / unreadable / empty. `harness approve
50
+ * understanding` calls this when --task is absent, then passes the
51
+ * resolved id to writeTaskApprovalMarker.
52
+ *
53
+ * Defense-in-depth: if the on-disk content fails the same
54
+ * path-traversal / newline check that gates writes, the read returns
55
+ * null instead of surfacing a poisoned id. The write side guards
56
+ * against a malformed taskId reaching the file in the first place,
57
+ * but a stale file authored before this guard (or hand-edited)
58
+ * shouldn't escalate into a forged task-marker write downstream.
59
+ */
60
+ export function readActiveClaim(generatedDir) {
61
+ const filePath = activeClaimPathFor(generatedDir);
62
+ let raw;
63
+ try {
64
+ raw = fs.readFileSync(filePath, "utf8");
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ const trimmed = raw.trim();
70
+ if (trimmed.length === 0)
71
+ return null;
72
+ try {
73
+ rejectMalformedClaimId(trimmed);
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ return trimmed;
79
+ }
80
+ /** Hook-side: remove the active claim file. Idempotent. */
81
+ export function clearActiveClaim(generatedDir) {
82
+ try {
83
+ fs.rmSync(activeClaimPathFor(generatedDir));
84
+ }
85
+ catch {
86
+ /* already gone */
87
+ }
88
+ }
89
+ //# sourceMappingURL=active-claim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-claim.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/active-claim.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mEAAmE;AACnE,+DAA+D;AAC/D,uEAAuE;AACvE,kCAAkC;AAElC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,iEAAiE;AACjE,wEAAwE;AACxE,qEAAqE;AACrE,uEAAuE;AACvE,mEAAmE;AACnE,2CAA2C;AAC3C,EAAE;AACF,oEAAoE;AACpE,kEAAkE;AAClE,uDAAuD;AAEvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,cAAc,CAAC;AAEpD,MAAM,UAAU,kBAAkB,CAAC,YAAoB;IACrD,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAc;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACrC,CAAC;IACD,IACE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QACpB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrB,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EACrB,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gFAAgF,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CACzG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB,EAAE,MAAc;IACnE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAClD,eAAe,CAAC,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,CAAC;IACzC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAClD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type ApprovalSource = "marker" | "ledger" | "persisted-report" | "none" | "recovery-commit";
2
+ export interface ApprovalCheckResult {
3
+ approved: boolean;
4
+ source: ApprovalSource;
5
+ detail: string;
6
+ }
7
+ export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
8
+ export { APPROVAL_MARKER_DIRNAME, approvalMarkerPathFor, type ApprovalMarker, writeApprovalMarker, type MarkerCheck, type CheckApprovalMarkerOptions, checkApprovalMarker, clearApprovalMarker, } from "./markers.js";
9
+ export { type PersistedReport, REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, type FindReportOptions, type FindReportSelection, selectReportForSession, findLatestReportForSession, type PersistedReportApprovalCheck, expirePersistedReport, checkPersistedReport, } from "./persisted-reports.js";
10
+ export { type ApprovalLifecycle, parseApprovalLifecycle } from "./lifecycle.js";
11
+ export { APPROVAL_MARKER_TASK_PREFIX, taskApprovalMarkerPathFor, writeTaskApprovalMarker, checkActiveClaimApprovalMarker, type OperatorMarkerApproval, checkOperatorApprovalMarkers, clearTaskApprovalMarker, } from "./task-markers.js";
12
+ export { DEFAULT_BASH_TOOL_NAMES, toolNameMatchesAny, bashCommandMatchesAny, extractBashCommandFromToolInput, extractTaskIdFromToolInput, extractTasksTransitionStatusFromToolInput, type PostToolUseBoundaryMatch, matchPostToolUseBoundary, type ApplyPostToolUseExpiryResult, applyPostToolUseExpiry, describePostToolUseExpiry, } from "./post-tool-use-boundary.js";
13
+ export { ACTIVE_CLAIM_FILENAME, activeClaimPathFor, writeActiveClaim, readActiveClaim, clearActiveClaim, } from "./active-claim.js";
@@ -0,0 +1,39 @@
1
+ // Phase 6 #4 — runtime helpers for the understanding-before-execution pack.
2
+ //
3
+ // Two-source approval check that the harness-side PreToolUse blocker
4
+ // consults:
5
+ //
6
+ // 1. Filesystem marker `<generatedDir>/.approvals/<sessionId>` written
7
+ // by `harness approve understanding` from the operator's shell.
8
+ // Canonical for harnessed sessions. Replaces the ledger-substring
9
+ // check that shipped through v0.13.0 (agent-tasks/88ca4bb3): the
10
+ // agent has direct MCP access to the same ledger that gate path
11
+ // consulted, so any agent could write `understanding-approved:<sid>`
12
+ // itself and self-approve. Edit / Write / Bash are all gated by
13
+ // this same PreToolUse hook, and the configured MCP servers do not
14
+ // expose filesystem writes, so the marker file is reachable only
15
+ // from a process the operator launched (their `!`-shell or any
16
+ // other un-hooked terminal). Operator-side: writeApprovalMarker
17
+ // below. Forensics: the ledger row is still written by
18
+ // `harness approve understanding`, for audit only.
19
+ // 2. Persisted JSON report under `.understanding-gate/reports/`
20
+ // (canonical for solo `@lannguyensi/understanding-gate` users).
21
+ // The package writes one file per session; the latest with
22
+ // `approvalStatus: "approved"` matching the session_id wins. The
23
+ // report is flipped to "approved" by `harness approve`; the
24
+ // agent's Stop hook only writes `pending` reports and cannot flip
25
+ // them (Edit/Write/Bash gated), so this source is also operator-
26
+ // authored.
27
+ //
28
+ // Either source approves. The persisted-report fallback is what makes a
29
+ // solo user without grounding-mcp wired still able to approve via the
30
+ // package's CLI; the marker path is what makes a harnessed session see
31
+ // the approval immediately on the next tool call.
32
+ export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
33
+ export { APPROVAL_MARKER_DIRNAME, approvalMarkerPathFor, writeApprovalMarker, checkApprovalMarker, clearApprovalMarker, } from "./markers.js";
34
+ export { REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, selectReportForSession, findLatestReportForSession, expirePersistedReport, checkPersistedReport, } from "./persisted-reports.js";
35
+ export { parseApprovalLifecycle } from "./lifecycle.js";
36
+ export { APPROVAL_MARKER_TASK_PREFIX, taskApprovalMarkerPathFor, writeTaskApprovalMarker, checkActiveClaimApprovalMarker, checkOperatorApprovalMarkers, clearTaskApprovalMarker, } from "./task-markers.js";
37
+ export { DEFAULT_BASH_TOOL_NAMES, toolNameMatchesAny, bashCommandMatchesAny, extractBashCommandFromToolInput, extractTaskIdFromToolInput, extractTasksTransitionStatusFromToolInput, matchPostToolUseBoundary, applyPostToolUseExpiry, describePostToolUseExpiry, } from "./post-tool-use-boundary.js";
38
+ export { ACTIVE_CLAIM_FILENAME, activeClaimPathFor, writeActiveClaim, readActiveClaim, clearActiveClaim, } from "./active-claim.js";
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,qEAAqE;AACrE,YAAY;AACZ,EAAE;AACF,yEAAyE;AACzE,qEAAqE;AACrE,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AACrE,0EAA0E;AAC1E,qEAAqE;AACrE,wEAAwE;AACxE,sEAAsE;AACtE,oEAAoE;AACpE,qEAAqE;AACrE,4DAA4D;AAC5D,wDAAwD;AACxD,kEAAkE;AAClE,qEAAqE;AACrE,gEAAgE;AAChE,sEAAsE;AACtE,iEAAiE;AACjE,uEAAuE;AACvE,sEAAsE;AACtE,iBAAiB;AACjB,EAAE;AACF,wEAAwE;AACxE,sEAAsE;AACtE,uEAAuE;AACvE,kDAAkD;AAelD,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EAErB,mBAAmB,EAGnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,EAC5B,gCAAgC,EAGhC,sBAAsB,EACtB,0BAA0B,EAE1B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAA0B,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,uBAAuB,EACvB,8BAA8B,EAE9B,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,+BAA+B,EAC/B,0BAA0B,EAC1B,yCAAyC,EAEzC,wBAAwB,EAExB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type LedgerEntry } from "../../../policies/index.js";
2
+ export declare const APPROVED_LEDGER_TAG_PREFIX = "understanding-approved:";
3
+ /** Build the per-session ledger tag the pack searches for. */
4
+ export declare function approvedLedgerTagFor(sessionId: string): string;
5
+ /**
6
+ * Phase 6 #6 — substring-pollution defence shared by every PreToolUse
7
+ * blocker (Claude Code + Codex). Drops policy_decision rows so a
8
+ * `understanding-approved:<sess>` substring inside an audit row's
9
+ * JSON-encoded `reason` cannot accidentally satisfy the gate.
10
+ */
11
+ export declare function isPolicyDecisionRow(e: LedgerEntry): boolean;
12
+ /**
13
+ * Match a ledger fetch against the per-session approval tag. Kept for
14
+ * the audit / forensics path only: a ledger entry tagged
15
+ * `understanding-approved:<sid>` is no longer a sufficient signal to
16
+ * unblock the gate (agent-tasks/88ca4bb3: the agent has the same MCP
17
+ * surface and could self-write the row). Use `checkApprovalMarker`
18
+ * for the gate decision; this helper now serves `harness audit` /
19
+ * `harness explain --trace` style read paths that surface the
20
+ * historic ledger trail without granting approval power.
21
+ */
22
+ export declare function matchLedgerEntries(entries: LedgerEntry[], sessionId: string): {
23
+ matched: boolean;
24
+ detail: string;
25
+ };
@@ -0,0 +1,56 @@
1
+ // Ledger audit/forensics helpers, split out of the former monolithic
2
+ // understanding-before-execution-runtime.ts (structural concentration
3
+ // slice 2, agent-tasks 348a4d42). Pure move: see
4
+ // src/policy-packs/builtin/understanding-before-execution/index.ts for
5
+ // the re-exported public surface.
6
+ import { POLICY_DECISION_TYPE } from "../../../io/ledger-record.js";
7
+ import { APPROVAL_MARKER_DIRNAME } from "./markers.js";
8
+ export const APPROVED_LEDGER_TAG_PREFIX = "understanding-approved:";
9
+ /** Build the per-session ledger tag the pack searches for. */
10
+ export function approvedLedgerTagFor(sessionId) {
11
+ return `${APPROVED_LEDGER_TAG_PREFIX}${sessionId}`;
12
+ }
13
+ /**
14
+ * Phase 6 #6 — substring-pollution defence shared by every PreToolUse
15
+ * blocker (Claude Code + Codex). Drops policy_decision rows so a
16
+ * `understanding-approved:<sess>` substring inside an audit row's
17
+ * JSON-encoded `reason` cannot accidentally satisfy the gate.
18
+ */
19
+ export function isPolicyDecisionRow(e) {
20
+ if (e.type === POLICY_DECISION_TYPE)
21
+ return true;
22
+ if (typeof e.content === "string" && e.content.startsWith(`${POLICY_DECISION_TYPE}:`)) {
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+ /**
28
+ * Match a ledger fetch against the per-session approval tag. Kept for
29
+ * the audit / forensics path only: a ledger entry tagged
30
+ * `understanding-approved:<sid>` is no longer a sufficient signal to
31
+ * unblock the gate (agent-tasks/88ca4bb3: the agent has the same MCP
32
+ * surface and could self-write the row). Use `checkApprovalMarker`
33
+ * for the gate decision; this helper now serves `harness audit` /
34
+ * `harness explain --trace` style read paths that surface the
35
+ * historic ledger trail without granting approval power.
36
+ */
37
+ export function matchLedgerEntries(entries, sessionId) {
38
+ const wanted = approvedLedgerTagFor(sessionId);
39
+ let scanned = 0;
40
+ for (const e of entries) {
41
+ if (isPolicyDecisionRow(e))
42
+ continue;
43
+ scanned += 1;
44
+ if (typeof e.content === "string" && e.content.includes(wanted)) {
45
+ return {
46
+ matched: true,
47
+ detail: `audit: ledger tag ${wanted} present at ${e.createdAt} (no longer satisfies the gate; see harness.generated/${APPROVAL_MARKER_DIRNAME}/${sessionId})`,
48
+ };
49
+ }
50
+ }
51
+ return {
52
+ matched: false,
53
+ detail: `no ledger entry matched ${wanted} (scanned ${scanned} non-policy_decision row(s))`,
54
+ };
55
+ }
56
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/ledger.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,sEAAsE;AACtE,iDAAiD;AACjD,uEAAuE;AACvE,kCAAkC;AAElC,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,CAAC,MAAM,0BAA0B,GAAG,yBAAyB,CAAC;AAEpE,8DAA8D;AAC9D,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO,GAAG,0BAA0B,GAAG,SAAS,EAAE,CAAC;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAc;IAChD,IAAI,CAAC,CAAC,IAAI,KAAK,oBAAoB;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,oBAAoB,GAAG,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAsB,EACtB,SAAiB;IAEjB,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,mBAAmB,CAAC,CAAC,CAAC;YAAE,SAAS;QACrC,OAAO,IAAI,CAAC,CAAC;QACb,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAChE,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,qBAAqB,MAAM,eAAe,CAAC,CAAC,SAAS,yDAAyD,uBAAuB,IAAI,SAAS,GAAG;aAC9J,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO;QACL,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,2BAA2B,MAAM,aAAa,OAAO,8BAA8B;KAC5F,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ export interface ApprovalLifecycle {
2
+ /** Tool-name patterns whose successful PostToolUse expires the marker. */
3
+ expireOnToolMatch: string[];
4
+ /** Pre-compiled regex patterns matched against `Bash` tool_input.command. */
5
+ expireOnBashMatch: RegExp[];
6
+ /** Max marker age in milliseconds. Undefined means no TTL. */
7
+ maxAgeMs?: number;
8
+ /** Whether the operator explicitly opted out via `{ mode: "session" }`. */
9
+ legacyMode: boolean;
10
+ }
11
+ /**
12
+ * Parse the optional `approval_lifecycle` block from a pack config.
13
+ * Best-effort: malformed values fall back to the default (no expiry,
14
+ * legacyMode=false) and write a one-line warning to the supplied
15
+ * stderr. The PreToolUse / PostToolUse hooks must keep working even
16
+ * when the operator typed a typo in the YAML.
17
+ */
18
+ export declare function parseApprovalLifecycle(raw: unknown, stderr?: {
19
+ write: (s: string) => void;
20
+ } | null): ApprovalLifecycle;
@@ -0,0 +1,79 @@
1
+ // `approval_lifecycle` pack-config parsing, split out of the former
2
+ // monolithic understanding-before-execution-runtime.ts (structural
3
+ // concentration slice 2, agent-tasks 348a4d42). Pure move: see
4
+ // src/policy-packs/builtin/understanding-before-execution/index.ts for
5
+ // the re-exported public surface.
6
+ import { InvalidDurationError, parseDurationSeconds } from "../../../policies/index.js";
7
+ const DEFAULT_LIFECYCLE = {
8
+ expireOnToolMatch: [],
9
+ expireOnBashMatch: [],
10
+ legacyMode: false,
11
+ };
12
+ /**
13
+ * Parse the optional `approval_lifecycle` block from a pack config.
14
+ * Best-effort: malformed values fall back to the default (no expiry,
15
+ * legacyMode=false) and write a one-line warning to the supplied
16
+ * stderr. The PreToolUse / PostToolUse hooks must keep working even
17
+ * when the operator typed a typo in the YAML.
18
+ */
19
+ export function parseApprovalLifecycle(raw, stderr) {
20
+ if (raw === undefined || raw === null)
21
+ return DEFAULT_LIFECYCLE;
22
+ if (typeof raw !== "object" || Array.isArray(raw)) {
23
+ stderr?.write(`harness pack hook: config.approval_lifecycle ignored (expected object, got ${typeof raw})\n`);
24
+ return DEFAULT_LIFECYCLE;
25
+ }
26
+ const obj = raw;
27
+ if (obj["mode"] === "session") {
28
+ return { expireOnToolMatch: [], expireOnBashMatch: [], legacyMode: true };
29
+ }
30
+ const expireOnToolMatch = [];
31
+ const list = obj["expire_on_tool_match"];
32
+ if (Array.isArray(list)) {
33
+ for (const v of list) {
34
+ if (typeof v === "string" && v.length > 0)
35
+ expireOnToolMatch.push(v);
36
+ }
37
+ }
38
+ else if (list !== undefined) {
39
+ stderr?.write(`harness pack hook: config.approval_lifecycle.expire_on_tool_match ignored (expected string[], got ${typeof list})\n`);
40
+ }
41
+ const expireOnBashMatch = [];
42
+ const bashList = obj["expire_on_bash_match"];
43
+ if (Array.isArray(bashList)) {
44
+ for (const v of bashList) {
45
+ if (typeof v !== "string" || v.length === 0)
46
+ continue;
47
+ try {
48
+ expireOnBashMatch.push(new RegExp(v));
49
+ }
50
+ catch (err) {
51
+ stderr?.write(`harness pack hook: config.approval_lifecycle.expire_on_bash_match entry ignored ("${v}"): ${err.message}\n`);
52
+ }
53
+ }
54
+ }
55
+ else if (bashList !== undefined) {
56
+ stderr?.write(`harness pack hook: config.approval_lifecycle.expire_on_bash_match ignored (expected string[], got ${typeof bashList})\n`);
57
+ }
58
+ let maxAgeMs;
59
+ const maxAgeRaw = obj["max_age"];
60
+ if (typeof maxAgeRaw === "string" && maxAgeRaw.length > 0) {
61
+ try {
62
+ maxAgeMs = parseDurationSeconds(maxAgeRaw) * 1_000;
63
+ }
64
+ catch (err) {
65
+ const msg = err instanceof InvalidDurationError ? err.message : String(err);
66
+ stderr?.write(`harness pack hook: config.approval_lifecycle.max_age ignored: ${msg}\n`);
67
+ }
68
+ }
69
+ else if (maxAgeRaw !== undefined) {
70
+ stderr?.write(`harness pack hook: config.approval_lifecycle.max_age ignored (expected duration string like "4h", got ${typeof maxAgeRaw})\n`);
71
+ }
72
+ return {
73
+ expireOnToolMatch,
74
+ expireOnBashMatch,
75
+ ...(maxAgeMs !== undefined && { maxAgeMs }),
76
+ legacyMode: false,
77
+ };
78
+ }
79
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ export declare const APPROVAL_MARKER_DIRNAME = ".approvals";
2
+ /** Filesystem path of the per-session approval marker. */
3
+ export declare function approvalMarkerPathFor(generatedDir: string, sessionId: string): string;
4
+ export interface ApprovalMarker {
5
+ approvedAt: string;
6
+ approvedBy: string;
7
+ /**
8
+ * sha256 hex of the persisted-report content this approval is bound to
9
+ * at sign-time (harness/f9485cc7). Optional on write (defaults to
10
+ * `null` — a marker with no report to bind, e.g. `harness approve
11
+ * branch-protection`, or a ledger-only approval); always present
12
+ * (possibly `null`) on a marker `checkApprovalMarker` returns.
13
+ */
14
+ reportContentHash?: string | null;
15
+ }
16
+ /**
17
+ * Operator-side: write the marker file the gate consults. Atomic so a
18
+ * crash mid-write cannot leave a half-empty file the gate would accept
19
+ * as approved. Caller is `harness approve understanding`, which the
20
+ * operator runs from their un-hooked shell; if the agent could call
21
+ * this path the gate's value would collapse, so it lives behind the
22
+ * approve CLI rather than as a generally importable verb.
23
+ *
24
+ * The marker is HMAC-signed (harness/f9485cc7) over (sessionId,
25
+ * approvedAt, approvedBy, reportContentHash) using an operator-side key
26
+ * lazily generated at `<generatedDir>/.approval-signing.key` — see
27
+ * `src/runtime/approval-signing.ts` for the key-management contract and
28
+ * the honest trust model. Mode 0600 on the marker file itself, best-
29
+ * effort (matches the signing key's own permission convention); this is
30
+ * defense-in-depth alongside the signature, not a substitute for it.
31
+ */
32
+ export declare function writeApprovalMarker(generatedDir: string, sessionId: string, marker: ApprovalMarker): string;
33
+ export interface MarkerCheck {
34
+ matched: boolean;
35
+ detail: string;
36
+ marker: ApprovalMarker | null;
37
+ /**
38
+ * True when `matched` is false SPECIFICALLY because a readable marker
39
+ * existed but its `approvedAt` exceeded `opts.maxAgeMs`
40
+ * (agent-tasks/d8ee60ca) — as opposed to the marker being absent
41
+ * (never approved, or cleared by a task-completion boundary tool via
42
+ * `clearApprovalMarker`), corrupted, or the session id malformed.
43
+ * False whenever `matched` is true. This is the signal task 6e888423's
44
+ * recovery-git-commit exemption keys off: "this session/task DID have
45
+ * a real operator approval and it merely aged out" is safe to treat
46
+ * differently from "this session/task was never approved" or "a task
47
+ * boundary just cleared it for a new task" — see
48
+ * src/runtime/recovery-git-commit.ts for the full argument.
49
+ */
50
+ expired: boolean;
51
+ /**
52
+ * True when a marker FILE existed at the expected path but failed
53
+ * signature verification (harness/f9485cc7): missing `signature`/`alg`
54
+ * fields (a legacy pre-signing marker, or a marker planted without the
55
+ * operator-side key), a mismatched `alg`, or a signature that does not
56
+ * verify against the current key. Distinct from the marker being
57
+ * simply absent (`forged: false`, `matched: false`) so a caller can
58
+ * log/alert on an active forgery attempt instead of the routine
59
+ * "nobody has approved yet" case. `matched` is always false when
60
+ * `forged` is true — a forged marker never satisfies the gate,
61
+ * regardless of `approval_lifecycle.max_age`. A transient I/O read
62
+ * failure (`forged: false`, `matched: false`) is kept distinct from
63
+ * both: it is neither "no marker" nor evidence of tampering, just a
64
+ * marker whose integrity cannot be proven right now.
65
+ */
66
+ forged: boolean;
67
+ }
68
+ export interface CheckApprovalMarkerOptions {
69
+ /**
70
+ * Max marker age in milliseconds (agent-tasks/d8ee60ca). When set,
71
+ * a marker whose `approvedAt` is older than `now - maxAgeMs` is
72
+ * treated as expired and returns `matched:false` with an
73
+ * "expired" detail. When omitted, the marker has no TTL — the
74
+ * legacy contract (one approval per session, no expiry).
75
+ */
76
+ maxAgeMs?: number;
77
+ /** Override "now" for deterministic tests. */
78
+ now?: Date;
79
+ }
80
+ /**
81
+ * Gate-side: is the per-session marker file present, readable, AND
82
+ * validly signed? (harness/f9485cc7 — this replaced the earlier
83
+ * "existence is enough" contract: a marker's mere presence used to
84
+ * satisfy the gate even with a malformed or corrupted body, on the
85
+ * theory that Edit/Write/Bash being gated meant only the operator could
86
+ * ever create the file. That theory holds only as long as no OTHER
87
+ * write primitive reaches `harness.generated/` — exactly the invariant
88
+ * this signing scheme no longer has to trust blindly.) A marker with no
89
+ * `signature`, a wrong `alg`, or a signature that fails to verify is
90
+ * REJECTED — see `MarkerCheck.forged` — with the SAME `matched: false`
91
+ * outcome as no marker at all, but a distinct, forgery-specific detail
92
+ * string so callers can tell the two apart for audit.
93
+ *
94
+ * `opts.maxAgeMs` (agent-tasks/d8ee60ca): when set, a validly-signed
95
+ * marker whose `approvedAt` is older than the cutoff returns
96
+ * `matched:false` with an "expired" detail so the agent gets the same
97
+ * "no approval" UX as a never-approved session and must re-approve.
98
+ * Expiry is only evaluated AFTER signature verification succeeds — an
99
+ * unsigned/forged marker is rejected outright, never "expired".
100
+ */
101
+ export declare function checkApprovalMarker(generatedDir: string, sessionId: string, opts?: CheckApprovalMarkerOptions): MarkerCheck;
102
+ /** Clear the per-session marker (used by `harness approve --revoke` and tests). */
103
+ export declare function clearApprovalMarker(generatedDir: string, sessionId: string): void;