@lannguyensi/harness 0.44.0 → 0.45.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 (231) hide show
  1. package/CHANGELOG.md +1213 -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/approve/stdin-report.d.ts +15 -0
  22. package/dist/cli/approve/stdin-report.js +1 -1
  23. package/dist/cli/approve/stdin-report.js.map +1 -1
  24. package/dist/cli/approve/understanding.d.ts +75 -0
  25. package/dist/cli/approve/understanding.js +150 -2
  26. package/dist/cli/approve/understanding.js.map +1 -1
  27. package/dist/cli/audit.d.ts +1 -1
  28. package/dist/cli/audit.js +3 -2
  29. package/dist/cli/audit.js.map +1 -1
  30. package/dist/cli/doctor/claude-mcp.d.ts +19 -1
  31. package/dist/cli/doctor/claude-mcp.js +36 -10
  32. package/dist/cli/doctor/claude-mcp.js.map +1 -1
  33. package/dist/cli/doctor/codex.d.ts +3 -1
  34. package/dist/cli/doctor/codex.js +6 -9
  35. package/dist/cli/doctor/codex.js.map +1 -1
  36. package/dist/cli/doctor/format.js +94 -8
  37. package/dist/cli/doctor/format.js.map +1 -1
  38. package/dist/cli/doctor/index.d.ts +23 -0
  39. package/dist/cli/doctor/index.js +106 -2
  40. package/dist/cli/doctor/index.js.map +1 -1
  41. package/dist/cli/doctor/opencode.d.ts +24 -0
  42. package/dist/cli/doctor/opencode.js +107 -0
  43. package/dist/cli/doctor/opencode.js.map +1 -0
  44. package/dist/cli/doctor/target-checks.d.ts +11 -0
  45. package/dist/cli/doctor/target-checks.js +25 -0
  46. package/dist/cli/doctor/target-checks.js.map +1 -0
  47. package/dist/cli/doctor/toolchain-parity.d.ts +63 -0
  48. package/dist/cli/doctor/toolchain-parity.js +154 -0
  49. package/dist/cli/doctor/toolchain-parity.js.map +1 -0
  50. package/dist/cli/doctor/types.d.ts +85 -1
  51. package/dist/cli/doctor/types.js +1 -1
  52. package/dist/cli/doctor/types.js.map +1 -1
  53. package/dist/cli/doctor/understanding-mode-env.d.ts +39 -0
  54. package/dist/cli/doctor/understanding-mode-env.js +83 -0
  55. package/dist/cli/doctor/understanding-mode-env.js.map +1 -0
  56. package/dist/cli/dry-run.js +32 -17
  57. package/dist/cli/dry-run.js.map +1 -1
  58. package/dist/cli/explain.d.ts +1 -1
  59. package/dist/cli/explain.js +1 -1
  60. package/dist/cli/explain.js.map +1 -1
  61. package/dist/cli/index.js +58 -4
  62. package/dist/cli/index.js.map +1 -1
  63. package/dist/cli/init/composer.js +36 -13
  64. package/dist/cli/init/composer.js.map +1 -1
  65. package/dist/cli/init/interactive.d.ts +7 -4
  66. package/dist/cli/init/interactive.js +79 -27
  67. package/dist/cli/init/interactive.js.map +1 -1
  68. package/dist/cli/init/profiles.d.ts +2 -2
  69. package/dist/cli/init/profiles.js +32 -1
  70. package/dist/cli/init/profiles.js.map +1 -1
  71. package/dist/cli/init/templates.d.ts +2 -1
  72. package/dist/cli/init/templates.js +130 -20
  73. package/dist/cli/init/templates.js.map +1 -1
  74. package/dist/cli/pack/approve-escape.js +69 -8
  75. package/dist/cli/pack/approve-escape.js.map +1 -1
  76. package/dist/cli/pack/hook-branch-protection.js +1 -1
  77. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  78. package/dist/cli/pack/hook-codex-pre-tool-use.js +27 -1
  79. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  80. package/dist/cli/pack/hook-post-merge-gate.js +1 -1
  81. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -1
  82. package/dist/cli/pack/hook-pre-tool-use.js +91 -4
  83. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  84. package/dist/cli/pack/hook-solution-acceptance.js +19 -2
  85. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  86. package/dist/cli/pack/understanding-report-schema-hint.d.ts +13 -0
  87. package/dist/cli/pack/understanding-report-schema-hint.js +52 -3
  88. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  89. package/dist/cli/pause/index.js +1 -1
  90. package/dist/cli/pause/index.js.map +1 -1
  91. package/dist/cli/policy/intercept.d.ts +148 -0
  92. package/dist/cli/policy/intercept.js +379 -48
  93. package/dist/cli/policy/intercept.js.map +1 -1
  94. package/dist/cli/session-start/index.d.ts +12 -3
  95. package/dist/cli/session-start/index.js +67 -11
  96. package/dist/cli/session-start/index.js.map +1 -1
  97. package/dist/cli/session-start/stale-base-check.d.ts +71 -0
  98. package/dist/cli/session-start/stale-base-check.js +379 -0
  99. package/dist/cli/session-start/stale-base-check.js.map +1 -0
  100. package/dist/cli/session-start/toolchain-parity.d.ts +142 -8
  101. package/dist/cli/session-start/toolchain-parity.js +244 -36
  102. package/dist/cli/session-start/toolchain-parity.js.map +1 -1
  103. package/dist/cli/smoke/assertions.d.ts +9 -2
  104. package/dist/cli/smoke/assertions.js +9 -2
  105. package/dist/cli/smoke/assertions.js.map +1 -1
  106. package/dist/cli/uninstall/index.js +18 -2
  107. package/dist/cli/uninstall/index.js.map +1 -1
  108. package/dist/cli/validate/checks.d.ts +3 -1
  109. package/dist/cli/validate/checks.js +298 -3
  110. package/dist/cli/validate/checks.js.map +1 -1
  111. package/dist/io/claude-mcp.d.ts +122 -1
  112. package/dist/io/claude-mcp.js +214 -12
  113. package/dist/io/claude-mcp.js.map +1 -1
  114. package/dist/io/duration.js.map +1 -0
  115. package/dist/io/expand-home.js.map +1 -0
  116. package/dist/io/extract.js.map +1 -0
  117. package/dist/io/harness-lock.js +1 -1
  118. package/dist/io/harness-lock.js.map +1 -1
  119. package/dist/{runtime → io}/ledger-record.d.ts +1 -1
  120. package/dist/io/ledger-record.js.map +1 -0
  121. package/dist/policies/index.d.ts +2 -2
  122. package/dist/policies/index.js +2 -2
  123. package/dist/policies/index.js.map +1 -1
  124. package/dist/policies/ledger-client.js +10 -6
  125. package/dist/policies/ledger-client.js.map +1 -1
  126. package/dist/policies/producers.js +1 -1
  127. package/dist/policies/producers.js.map +1 -1
  128. package/dist/policies/requires.js +2 -2
  129. package/dist/policies/requires.js.map +1 -1
  130. package/dist/policy-packs/builtin/branch-protection.js +28 -3
  131. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  132. package/dist/policy-packs/builtin/post-merge-gate.js +45 -9
  133. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -1
  134. package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +139 -8
  135. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +246 -12
  136. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  137. package/dist/policy-packs/builtin/solution-acceptance.js +17 -3
  138. package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
  139. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.d.ts +25 -0
  140. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js +89 -0
  141. package/dist/policy-packs/builtin/understanding-before-execution/active-claim.js.map +1 -0
  142. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +13 -0
  143. package/dist/policy-packs/builtin/understanding-before-execution/index.js +39 -0
  144. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -0
  145. package/dist/policy-packs/builtin/understanding-before-execution/ledger.d.ts +25 -0
  146. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js +56 -0
  147. package/dist/policy-packs/builtin/understanding-before-execution/ledger.js.map +1 -0
  148. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.d.ts +20 -0
  149. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +79 -0
  150. package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -0
  151. package/dist/policy-packs/builtin/understanding-before-execution/markers.d.ts +103 -0
  152. package/dist/policy-packs/builtin/understanding-before-execution/markers.js +203 -0
  153. package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -0
  154. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +177 -0
  155. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +309 -0
  156. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -0
  157. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.d.ts +106 -0
  158. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +209 -0
  159. package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -0
  160. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.d.ts +78 -0
  161. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js +185 -0
  162. package/dist/policy-packs/builtin/understanding-before-execution/task-markers.js.map +1 -0
  163. package/dist/policy-packs/builtin/understanding-before-execution-runtime.d.ts +1 -536
  164. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js +9 -1095
  165. package/dist/policy-packs/builtin/understanding-before-execution-runtime.js.map +1 -1
  166. package/dist/policy-packs/builtin/understanding-before-execution.d.ts +109 -1
  167. package/dist/policy-packs/builtin/understanding-before-execution.js +183 -15
  168. package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
  169. package/dist/policy-packs/registry.d.ts +11 -0
  170. package/dist/policy-packs/registry.js +1 -1
  171. package/dist/policy-packs/registry.js.map +1 -1
  172. package/dist/policy-packs/runtime.d.ts +1 -1
  173. package/dist/policy-packs/runtime.js +10 -1
  174. package/dist/policy-packs/runtime.js.map +1 -1
  175. package/dist/runtime/agent-facing.js +1 -1
  176. package/dist/runtime/agent-facing.js.map +1 -1
  177. package/dist/runtime/bash-match-facts.d.ts +9 -7
  178. package/dist/runtime/bash-match-facts.js +9 -7
  179. package/dist/runtime/bash-match-facts.js.map +1 -1
  180. package/dist/runtime/bash-match-registry.d.ts +75 -38
  181. package/dist/runtime/bash-match-registry.js +279 -232
  182. package/dist/runtime/bash-match-registry.js.map +1 -1
  183. package/dist/runtime/bash-prefix-parse.d.ts +7 -0
  184. package/dist/runtime/bash-prefix-parse.js +205 -17
  185. package/dist/runtime/bash-prefix-parse.js.map +1 -1
  186. package/dist/runtime/command-normalize.d.ts +112 -0
  187. package/dist/runtime/command-normalize.js +411 -38
  188. package/dist/runtime/command-normalize.js.map +1 -1
  189. package/dist/runtime/hermetic-spawn-guard.js +6 -0
  190. package/dist/runtime/hermetic-spawn-guard.js.map +1 -1
  191. package/dist/runtime/index.d.ts +2 -2
  192. package/dist/runtime/index.js +2 -2
  193. package/dist/runtime/index.js.map +1 -1
  194. package/dist/runtime/intercept.d.ts +78 -3
  195. package/dist/runtime/intercept.js +151 -39
  196. package/dist/runtime/intercept.js.map +1 -1
  197. package/dist/runtime/ledger-add.js +1 -1
  198. package/dist/runtime/ledger-add.js.map +1 -1
  199. package/dist/runtime/read-only-bash.js +303 -24
  200. package/dist/runtime/read-only-bash.js.map +1 -1
  201. package/dist/schema/doctor.d.ts +25 -0
  202. package/dist/schema/doctor.js +23 -0
  203. package/dist/schema/doctor.js.map +1 -0
  204. package/dist/schema/extract.js +1 -1
  205. package/dist/schema/extract.js.map +1 -1
  206. package/dist/schema/index.d.ts +76 -0
  207. package/dist/schema/index.js +9 -0
  208. package/dist/schema/index.js.map +1 -1
  209. package/dist/schema/requires.js +2 -2
  210. package/dist/schema/requires.js.map +1 -1
  211. package/dist/schema/risk.d.ts +7 -0
  212. package/dist/schema/risk.js +23 -0
  213. package/dist/schema/risk.js.map +1 -1
  214. package/dist/schema/stale-base-check.d.ts +18 -0
  215. package/dist/schema/stale-base-check.js +37 -0
  216. package/dist/schema/stale-base-check.js.map +1 -0
  217. package/dist/schema/toolchain-parity.d.ts +3 -0
  218. package/dist/schema/toolchain-parity.js +13 -0
  219. package/dist/schema/toolchain-parity.js.map +1 -1
  220. package/package.json +3 -2
  221. package/dist/policies/duration.js.map +0 -1
  222. package/dist/policies/extract.js.map +0 -1
  223. package/dist/runtime/expand-home.js.map +0 -1
  224. package/dist/runtime/ledger-record.js.map +0 -1
  225. /package/dist/{policies → io}/duration.d.ts +0 -0
  226. /package/dist/{policies → io}/duration.js +0 -0
  227. /package/dist/{runtime → io}/expand-home.d.ts +0 -0
  228. /package/dist/{runtime → io}/expand-home.js +0 -0
  229. /package/dist/{policies → io}/extract.d.ts +0 -0
  230. /package/dist/{policies → io}/extract.js +0 -0
  231. /package/dist/{runtime → io}/ledger-record.js +0 -0
@@ -1,536 +1 @@
1
- import { type LedgerEntry } from "../../policies/index.js";
2
- export declare const APPROVED_LEDGER_TAG_PREFIX = "understanding-approved:";
3
- export declare const APPROVAL_MARKER_DIRNAME = ".approvals";
4
- export type ApprovalSource = "marker" | "ledger" | "persisted-report" | "none" | "recovery-commit";
5
- export interface ApprovalCheckResult {
6
- approved: boolean;
7
- source: ApprovalSource;
8
- detail: string;
9
- }
10
- export interface PersistedReport {
11
- filePath: string;
12
- sessionId: string | null;
13
- approvalStatus: string | null;
14
- approvedAt: string | null;
15
- /**
16
- * ISO timestamp the producer stamped when it wrote the report; null
17
- * for legacy reports without the field.
18
- */
19
- createdAt: string | null;
20
- /**
21
- * Effective creation time in epoch ms, resolved `createdAt` →
22
- * filename ISO prefix → file mtime. Unlike mtime alone this survives
23
- * the approval rewrite (which bumps mtime and would otherwise make a
24
- * weeks-old report sort as the freshest, harness-discovery C1).
25
- */
26
- createdAtMs: number;
27
- }
28
- /**
29
- * Env var the persisted-report directory can be set from. Honored by
30
- * harness (`defaultReportsDir` below + emitted by `harness apply` onto
31
- * the pack-contributed hook commands) AND by `@lannguyensi/understanding-gate`
32
- * (its `core/persistence.js:resolveReportDir` reads the same name), so
33
- * the three actors that touch the directory — Stop hook (package),
34
- * PreToolUse blocker (harness), `harness approve understanding` — can
35
- * agree on the path regardless of each process's cwd.
36
- */
37
- export declare const REPORTS_DIR_ENV = "UNDERSTANDING_GATE_REPORT_DIR";
38
- /**
39
- * Resolve the persisted-report directory. Precedence:
40
- * 1. `UNDERSTANDING_GATE_REPORT_DIR` (taken verbatim — apply emits an
41
- * absolute path, operator-exported values are shell-expanded before
42
- * we see them).
43
- * 2. `<cwd>/.understanding-gate/reports` — backward-compat fallback.
44
- * Callers that have a stable anchor (the manifest directory) pass
45
- * it as `cwd` so the fallback agrees with whatever path apply
46
- * baked into the hook commands.
47
- */
48
- export declare function defaultReportsDir(cwd?: string): string;
49
- /**
50
- * Project root anchor for the reports directory: `<dir-of-manifest>/.understanding-gate/reports`.
51
- * Used by `harness apply` to bake an absolute, manifest-anchored value into
52
- * the pack-contributed hook commands' env, and by `harness approve` as the
53
- * fallback when `UNDERSTANDING_GATE_REPORT_DIR` is unset.
54
- */
55
- export declare function reportsDirForManifest(manifestPath: string): string;
56
- /** Build the per-session ledger tag the pack searches for. */
57
- export declare function approvedLedgerTagFor(sessionId: string): string;
58
- /**
59
- * List persisted reports under `dir`, newest-first by creation time
60
- * (JSON `createdAt`, falling back to the filename ISO prefix, falling
61
- * back to mtime). Missing directory returns []. Any I/O error on a
62
- * single file is silently skipped; the caller falls through to the
63
- * ledger result.
64
- *
65
- * Creation time, NOT mtime, is the sort key: `harness approve
66
- * understanding` rewrites the report it flips, which bumps mtime and
67
- * made an old just-approved report sort as the freshest
68
- * (harness-discovery C1). mtime survives only as the last-resort
69
- * fallback for files that carry neither timestamp.
70
- */
71
- export declare function listPersistedReports(dir: string): PersistedReport[];
72
- /**
73
- * Maximum age a sessionId-null report may have for the tolerant
74
- * fallback to adopt it on the `harness approve understanding` path.
75
- * Sized for the real flow (Stop hook persists at turn end, operator
76
- * approves within minutes) with slack for a slow read-through. Live
77
- * repro that motivated it: a 17-day-old pending report got adopted,
78
- * validated, and stamped for a fresh session because the producer had
79
- * silently failed to persist the fresh report (harness-discovery C1,
80
- * friction-log #67).
81
- */
82
- export declare const TOLERANT_FALLBACK_MAX_AGE_MS: number;
83
- /**
84
- * Tolerance for a sessionId-less candidate whose `createdAt` lies in
85
- * the FUTURE relative to the approve-time clock. A future creation
86
- * time is suspect either way: a forged `createdAt` (the producer's
87
- * Metadata block lets the agent author it) or serious clock skew.
88
- * Beyond this skew the fallback rejects the candidate just like a
89
- * stale one rather than trusting a timestamp that cannot be right.
90
- */
91
- export declare const TOLERANT_FALLBACK_FUTURE_SKEW_MS: number;
92
- export interface FindReportOptions {
93
- /**
94
- * Behaviour of the sessionId-null tolerant fallback (older Stop-hook
95
- * package versions write reports without a `sessionId` field):
96
- * - `"any"` (default): adopt the freshest sessionId-null report
97
- * regardless of its `approvalStatus`. The gate read path
98
- * (`checkPersistedReport`) and post-tool-use expiry
99
- * (`expirePersistedReport`) rely on this so they keep finding the
100
- * session's own report.
101
- * - `"uncompleted"`: skip sessionId-null reports whose
102
- * `approvalStatus` is a terminal `approved` / `expired`. Such a
103
- * report belongs to a prior, finished approval cycle (often from
104
- * a different task days ago) and must not be silently re-adopted
105
- * as the current session's approval. `harness approve
106
- * understanding` passes this so it never flips a stale unrelated
107
- * report into the live session (harness/0dce3880 friction #1).
108
- */
109
- tolerantFallback?: "any" | "uncompleted";
110
- /**
111
- * Maximum age (relative to `now`) of a sessionId-null candidate the
112
- * tolerant fallback may adopt; older candidates are skipped and
113
- * surfaced via `FindReportSelection.staleRejected`. Strict sessionId
114
- * matches are never age-limited. Unset means no limit (the legacy
115
- * gate-read / expiry contract).
116
- */
117
- maxFallbackAgeMs?: number;
118
- /** Clock anchor for the age computation; defaults to the wall clock. */
119
- now?: Date;
120
- }
121
- export interface FindReportSelection {
122
- report: PersistedReport | null;
123
- /**
124
- * True when `report` was adopted via the sessionId-null tolerant
125
- * fallback rather than a strict sessionId match. Callers that bind
126
- * the report to a session (the approve flow) surface this loudly so
127
- * the operator can verify the adoption.
128
- */
129
- fallbackAdopted: boolean;
130
- /**
131
- * sessionId-null candidates skipped for exceeding `maxFallbackAgeMs`,
132
- * newest first. Lets the caller distinguish "no report at all" from
133
- * "only stale candidates existed", which are different failures: the
134
- * latter usually means the producer failed to persist the fresh
135
- * report (harness-discovery C1).
136
- */
137
- staleRejected: PersistedReport[];
138
- }
139
- /**
140
- * Select the freshest report for a given session_id, or the freshest
141
- * applicable report when the persisted file lacks a sessionId field
142
- * (older package versions). `report: null` when nothing matches.
143
- *
144
- * The strict (sessionId-equals) match always wins. The tolerant
145
- * fallback's appetite is controlled by `opts.tolerantFallback` and
146
- * `opts.maxFallbackAgeMs` — see `FindReportOptions`. The selection
147
- * result carries enough context (`fallbackAdopted`, `staleRejected`)
148
- * for the caller to be loud about non-strict adoptions.
149
- */
150
- export declare function selectReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): FindReportSelection;
151
- /**
152
- * Back-compat wrapper around `selectReportForSession` for callers that
153
- * only need the report (the gate read and expiry paths).
154
- */
155
- export declare function findLatestReportForSession(reports: PersistedReport[], sessionId: string, opts?: FindReportOptions): PersistedReport | null;
156
- export interface PersistedReportApprovalCheck {
157
- approved: boolean;
158
- detail: string;
159
- report: PersistedReport | null;
160
- }
161
- /**
162
- * Phase 6 #6 — substring-pollution defence shared by every PreToolUse
163
- * blocker (Claude Code + Codex). Drops policy_decision rows so a
164
- * `understanding-approved:<sess>` substring inside an audit row's
165
- * JSON-encoded `reason` cannot accidentally satisfy the gate.
166
- */
167
- export declare function isPolicyDecisionRow(e: LedgerEntry): boolean;
168
- /**
169
- * Match a ledger fetch against the per-session approval tag. Kept for
170
- * the audit / forensics path only: a ledger entry tagged
171
- * `understanding-approved:<sid>` is no longer a sufficient signal to
172
- * unblock the gate (agent-tasks/88ca4bb3: the agent has the same MCP
173
- * surface and could self-write the row). Use `checkApprovalMarker`
174
- * for the gate decision; this helper now serves `harness audit` /
175
- * `harness explain --trace` style read paths that surface the
176
- * historic ledger trail without granting approval power.
177
- */
178
- export declare function matchLedgerEntries(entries: LedgerEntry[], sessionId: string): {
179
- matched: boolean;
180
- detail: string;
181
- };
182
- /** Filesystem path of the per-session approval marker. */
183
- export declare function approvalMarkerPathFor(generatedDir: string, sessionId: string): string;
184
- export interface ApprovalMarker {
185
- approvedAt: string;
186
- approvedBy: string;
187
- /**
188
- * sha256 hex of the persisted-report content this approval is bound to
189
- * at sign-time (harness/f9485cc7). Optional on write (defaults to
190
- * `null` — a marker with no report to bind, e.g. `harness approve
191
- * branch-protection`, or a ledger-only approval); always present
192
- * (possibly `null`) on a marker `checkApprovalMarker` returns.
193
- */
194
- reportContentHash?: string | null;
195
- }
196
- /**
197
- * Operator-side: write the marker file the gate consults. Atomic so a
198
- * crash mid-write cannot leave a half-empty file the gate would accept
199
- * as approved. Caller is `harness approve understanding`, which the
200
- * operator runs from their un-hooked shell; if the agent could call
201
- * this path the gate's value would collapse, so it lives behind the
202
- * approve CLI rather than as a generally importable verb.
203
- *
204
- * The marker is HMAC-signed (harness/f9485cc7) over (sessionId,
205
- * approvedAt, approvedBy, reportContentHash) using an operator-side key
206
- * lazily generated at `<generatedDir>/.approval-signing.key` — see
207
- * `src/runtime/approval-signing.ts` for the key-management contract and
208
- * the honest trust model. Mode 0600 on the marker file itself, best-
209
- * effort (matches the signing key's own permission convention); this is
210
- * defense-in-depth alongside the signature, not a substitute for it.
211
- */
212
- export declare function writeApprovalMarker(generatedDir: string, sessionId: string, marker: ApprovalMarker): string;
213
- export interface MarkerCheck {
214
- matched: boolean;
215
- detail: string;
216
- marker: ApprovalMarker | null;
217
- /**
218
- * True when `matched` is false SPECIFICALLY because a readable marker
219
- * existed but its `approvedAt` exceeded `opts.maxAgeMs`
220
- * (agent-tasks/d8ee60ca) — as opposed to the marker being absent
221
- * (never approved, or cleared by a task-completion boundary tool via
222
- * `clearApprovalMarker`), corrupted, or the session id malformed.
223
- * False whenever `matched` is true. This is the signal task 6e888423's
224
- * recovery-git-commit exemption keys off: "this session/task DID have
225
- * a real operator approval and it merely aged out" is safe to treat
226
- * differently from "this session/task was never approved" or "a task
227
- * boundary just cleared it for a new task" — see
228
- * src/runtime/recovery-git-commit.ts for the full argument.
229
- */
230
- expired: boolean;
231
- /**
232
- * True when a marker FILE existed at the expected path but failed
233
- * signature verification (harness/f9485cc7): missing `signature`/`alg`
234
- * fields (a legacy pre-signing marker, or a marker planted without the
235
- * operator-side key), a mismatched `alg`, or a signature that does not
236
- * verify against the current key. Distinct from the marker being
237
- * simply absent (`forged: false`, `matched: false`) so a caller can
238
- * log/alert on an active forgery attempt instead of the routine
239
- * "nobody has approved yet" case. `matched` is always false when
240
- * `forged` is true — a forged marker never satisfies the gate,
241
- * regardless of `approval_lifecycle.max_age`. A transient I/O read
242
- * failure (`forged: false`, `matched: false`) is kept distinct from
243
- * both: it is neither "no marker" nor evidence of tampering, just a
244
- * marker whose integrity cannot be proven right now.
245
- */
246
- forged: boolean;
247
- }
248
- export interface CheckApprovalMarkerOptions {
249
- /**
250
- * Max marker age in milliseconds (agent-tasks/d8ee60ca). When set,
251
- * a marker whose `approvedAt` is older than `now - maxAgeMs` is
252
- * treated as expired and returns `matched:false` with an
253
- * "expired" detail. When omitted, the marker has no TTL — the
254
- * legacy contract (one approval per session, no expiry).
255
- */
256
- maxAgeMs?: number;
257
- /** Override "now" for deterministic tests. */
258
- now?: Date;
259
- }
260
- /**
261
- * Gate-side: is the per-session marker file present, readable, AND
262
- * validly signed? (harness/f9485cc7 — this replaced the earlier
263
- * "existence is enough" contract: a marker's mere presence used to
264
- * satisfy the gate even with a malformed or corrupted body, on the
265
- * theory that Edit/Write/Bash being gated meant only the operator could
266
- * ever create the file. That theory holds only as long as no OTHER
267
- * write primitive reaches `harness.generated/` — exactly the invariant
268
- * this signing scheme no longer has to trust blindly.) A marker with no
269
- * `signature`, a wrong `alg`, or a signature that fails to verify is
270
- * REJECTED — see `MarkerCheck.forged` — with the SAME `matched: false`
271
- * outcome as no marker at all, but a distinct, forgery-specific detail
272
- * string so callers can tell the two apart for audit.
273
- *
274
- * `opts.maxAgeMs` (agent-tasks/d8ee60ca): when set, a validly-signed
275
- * marker whose `approvedAt` is older than the cutoff returns
276
- * `matched:false` with an "expired" detail so the agent gets the same
277
- * "no approval" UX as a never-approved session and must re-approve.
278
- * Expiry is only evaluated AFTER signature verification succeeds — an
279
- * unsigned/forged marker is rejected outright, never "expired".
280
- */
281
- export declare function checkApprovalMarker(generatedDir: string, sessionId: string, opts?: CheckApprovalMarkerOptions): MarkerCheck;
282
- export interface ApprovalLifecycle {
283
- /** Tool-name patterns whose successful PostToolUse expires the marker. */
284
- expireOnToolMatch: string[];
285
- /** Pre-compiled regex patterns matched against `Bash` tool_input.command. */
286
- expireOnBashMatch: RegExp[];
287
- /** Max marker age in milliseconds. Undefined means no TTL. */
288
- maxAgeMs?: number;
289
- /** Whether the operator explicitly opted out via `{ mode: "session" }`. */
290
- legacyMode: boolean;
291
- }
292
- /**
293
- * Parse the optional `approval_lifecycle` block from a pack config.
294
- * Best-effort: malformed values fall back to the default (no expiry,
295
- * legacyMode=false) and write a one-line warning to the supplied
296
- * stderr. The PreToolUse / PostToolUse hooks must keep working even
297
- * when the operator typed a typo in the YAML.
298
- */
299
- export declare function parseApprovalLifecycle(raw: unknown, stderr?: {
300
- write: (s: string) => void;
301
- } | null): ApprovalLifecycle;
302
- /**
303
- * Flip the latest matching persisted report's approvalStatus to
304
- * `expired` so it no longer satisfies the gate's persisted-report
305
- * fallback (harness/1ee26e77 follow-up: post-tool-use expiry was
306
- * marker-only; the persisted report at .understanding-gate/reports/
307
- * silently kept satisfying the gate even after task_finish deleted
308
- * the marker).
309
- *
310
- * Atomic rewrite. Preserves the rest of the report body so the audit
311
- * trail (the operator's actual Understanding text + previous approval
312
- * timestamps) stays intact; only the status fields change.
313
- *
314
- * Returns `{ ok: true, filePath, previousStatus }` on success,
315
- * `{ ok: false, reason }` when no matching report exists or rewrite
316
- * failed. Non-throwing: caller (post-tool-use hook) uses this as a
317
- * best-effort cleanup so a missing report dir or unrelated I/O issue
318
- * does not escalate into a session-breaking hook failure.
319
- */
320
- export declare function expirePersistedReport(reportsDir: string, sessionId: string, now?: Date): {
321
- ok: true;
322
- filePath: string;
323
- previousStatus: string | null;
324
- } | {
325
- ok: false;
326
- reason: string;
327
- };
328
- /** Clear the per-session marker (used by `harness approve --revoke` and tests). */
329
- export declare function clearApprovalMarker(generatedDir: string, sessionId: string): void;
330
- export declare const APPROVAL_MARKER_TASK_PREFIX = "task-";
331
- /** Filesystem path of a per-task approval marker. */
332
- export declare function taskApprovalMarkerPathFor(generatedDir: string, taskId: string): string;
333
- /**
334
- * Operator-side: write a task-scoped marker file. Atomic. Caller is
335
- * `harness approve understanding --task <id>`. The session marker is
336
- * written separately by the same caller for back-compat.
337
- *
338
- * Signed the same way as `writeApprovalMarker` (harness/f9485cc7), using
339
- * `task-<id>` (the exact string `checkActiveClaimApprovalMarker` looks
340
- * up) as the signed markerId — so a validly-signed task marker cannot be
341
- * copied onto a different task id (or the session marker's id) and still
342
- * verify.
343
- */
344
- export declare function writeTaskApprovalMarker(generatedDir: string, taskId: string, marker: ApprovalMarker): string;
345
- /**
346
- * Gate-side: resolve the active agent-tasks claim (via `active-claim`)
347
- * and check ONLY that task's approval marker. When no active claim is
348
- * recorded, this returns `matched:false` so the caller falls through
349
- * to the session marker — preserving the legacy contract for solo /
350
- * non-agent-tasks workflows that never call `task_start`.
351
- *
352
- * Replaces the v1 "any task marker satisfies the gate" behaviour
353
- * (PR #198): a stale approval from a different, already-completed task
354
- * was silently authorising every Edit/Write/Bash in the next session
355
- * because the scan returned the first existing marker regardless of
356
- * which task the agent had actually claimed.
357
- *
358
- * Same safety filters as `checkApprovalMarker` (signature verification,
359
- * symlink rejection, optional freshness via `maxAgeMs`); the only
360
- * difference is the filename suffix derived from `active-claim`.
361
- */
362
- export declare function checkActiveClaimApprovalMarker(generatedDir: string, opts?: CheckApprovalMarkerOptions): MarkerCheck;
363
- export interface OperatorMarkerApproval {
364
- matched: boolean;
365
- /** Which marker satisfied the gate; null when neither matched. */
366
- source: "task" | "session" | null;
367
- /** Detail of the decisive check (the match, or the session-scoped miss). */
368
- detail: string;
369
- /** Task-scoped check detail, for callers that trace the fall-through. */
370
- taskCheckDetail: string;
371
- /**
372
- * True when EITHER the task-scoped or the session-scoped marker
373
- * existed but aged past `approval_lifecycle.max_age` (task 6e888423).
374
- * False when `matched` is true, and false when a marker was simply
375
- * absent (never approved, or cleared by a task-completion boundary
376
- * tool) — distinguishing "this identity had a real approval that
377
- * merely expired" from "this identity was never approved" / "a new
378
- * task just started". PreToolUse hooks use this (never on its own —
379
- * always alongside `isRecoveryGitCommit`) to decide whether a bare
380
- * recovery `git commit` may proceed without a fresh Understanding
381
- * Report; see src/runtime/recovery-git-commit.ts.
382
- */
383
- expired: boolean;
384
- /**
385
- * True when EITHER the task-scoped or the session-scoped marker
386
- * existed but FAILED signature verification (harness/f9485cc7) —
387
- * missing/invalid signature, wrong `alg`, or tampered payload. Distinct
388
- * from a marker simply being absent, so a caller can log a forgery
389
- * attempt distinctly from the routine "not approved yet" case.
390
- */
391
- forged: boolean;
392
- }
393
- /**
394
- * Shared marker resolution for the Claude and Codex understanding-gate
395
- * PreToolUse hooks (task e7c2ec3c): parse `approval_lifecycle` from the
396
- * pack config, consult the task-scoped (active-claim) marker first and
397
- * the session-scoped marker second, both under the same TTL. One code
398
- * path on purpose — the Codex hook previously called the bare session
399
- * check, so `max_age` and task-scoping silently applied only to Claude
400
- * sessions.
401
- */
402
- export declare function checkOperatorApprovalMarkers(generatedDir: string, sessionId: string, packConfig: unknown, stderr?: {
403
- write: (s: string) => void;
404
- } | null): OperatorMarkerApproval;
405
- /** Clear a specific task-scoped marker. Used by the post-tool-use hook. */
406
- export declare function clearTaskApprovalMarker(generatedDir: string, taskId: string): void;
407
- /**
408
- * Tool names treated as "the Bash tool" for `expire_on_bash_match`
409
- * command-regex matching. Claude Code has exactly one Bash tool name;
410
- * a runtime with shell-tool aliases (e.g. Codex's `shell` /
411
- * `exec_command` / `functions.exec_command`) passes its own set.
412
- */
413
- export declare const DEFAULT_BASH_TOOL_NAMES: ReadonlySet<string>;
414
- /**
415
- * Tool-name membership test against `expire_on_tool_match`. No GLOB
416
- * wildcard expansion by design (agent-tasks/d8ee60ca): operators write
417
- * the exact tool name they mean.
418
- *
419
- * It IS alias-aware, though: a tool name is compared against `patterns`
420
- * after expanding it through `expandToolNameAliases` (shell-tool
421
- * aliases `Bash`/`shell`/`exec_command`/`functions.exec_command`, and
422
- * MCP tool-name variants — server hyphen/underscore swap, the
423
- * `mcp__server__.tool` dotted form). This mirrors the normalization
424
- * `harness policy intercept`'s `policyMatchesEvent` already applies to
425
- * the incoming `event.tool_name` (`src/runtime/intercept.ts`, commit
426
- * 9aacbcd "Fix Codex hook tool matching") — that fix exists precisely
427
- * because Codex can emit an MCP tool name in one of these variant
428
- * forms for the identical tool. Before this, a Codex session sending
429
- * `mcp__agent-tasks__.task_finish` (dotted) or an underscore-server
430
- * variant would silently never expire the marker: the Codex generator
431
- * alias-expands the EMITTED TOML `matcher` (`expandCodexHookMatchPattern`
432
- * in `generate-codex-config.ts`) so Codex's own dispatcher still
433
- * invokes the hook command, but this function's comparison against the
434
- * canonical `expire_on_tool_match` config list would reject the
435
- * variant `tool_name` once inside the hook body — the boundary would
436
- * silently never fire even though the hook ran (review finding on task
437
- * a1348c89).
438
- */
439
- export declare function toolNameMatchesAny(toolName: string, patterns: readonly string[]): boolean;
440
- /** First `expire_on_bash_match` regex the command satisfies, or
441
- * undefined. Patterns are pre-compiled by `parseApprovalLifecycle`
442
- * (invalid ones already dropped with a warning). Empty command
443
- * short-circuits to undefined. */
444
- export declare function bashCommandMatchesAny(command: string, patterns: readonly RegExp[]): RegExp | undefined;
445
- /** Pull the Bash command out of a tool_input payload, `""` when absent
446
- * or malformed. */
447
- export declare function extractBashCommandFromToolInput(toolInput: unknown): string;
448
- /** Pull `taskId` out of an MCP tool_input payload (`""` when absent /
449
- * malformed). Task-boundary agent-tasks verbs carry this as a
450
- * top-level string field. */
451
- export declare function extractTaskIdFromToolInput(toolInput: unknown): string;
452
- /** Pull the legacy v1 `tasks_transition` `status` field out of a
453
- * tool_input payload (`""` when absent / malformed). Only `"done"`
454
- * releases the work claim; the caller treats any other value as
455
- * keep-claim. */
456
- export declare function extractTasksTransitionStatusFromToolInput(toolInput: unknown): string;
457
- export interface PostToolUseBoundaryMatch {
458
- /** Final match decision: `toolNameMatched || bashRegex !== undefined`. */
459
- matched: boolean;
460
- /** Tool-name match, refined by the tasks_transition status filter. */
461
- toolNameMatched: boolean;
462
- /** Tool-name match BEFORE the tasks_transition status filter — lets
463
- * the caller distinguish "not in the list" from "in the list, but
464
- * status keeps the claim" for its diagnostic. */
465
- rawToolNameMatched: boolean;
466
- /** The `expire_on_bash_match` regex the command satisfied, if any. */
467
- bashRegex: RegExp | undefined;
468
- }
469
- /**
470
- * Decide whether `toolName` (+ its `tool_input`) crosses one of the
471
- * configured `approval_lifecycle` boundaries. Pure — no filesystem
472
- * access. Callers branch on `.matched` before touching marker/report
473
- * state (see `applyPostToolUseExpiry`).
474
- */
475
- export declare function matchPostToolUseBoundary(toolName: string, toolInput: unknown, lifecycle: Pick<ApprovalLifecycle, "expireOnToolMatch" | "expireOnBashMatch">, bashToolNames?: ReadonlySet<string>): PostToolUseBoundaryMatch;
476
- export interface ApplyPostToolUseExpiryResult {
477
- /** Did the session marker exist before this call cleared it? */
478
- wasMarkerPresent: boolean;
479
- /** Was a task-scoped marker also cleared? Only possible when
480
- * `toolNameMatched` (Bash regex boundaries never carry a taskId). */
481
- taskMarkerCleared: boolean;
482
- /** The task id whose marker was cleared, `""` when none. */
483
- clearedTaskId: string;
484
- /** Did the persisted-report expiry succeed? */
485
- persistedReportExpired: boolean;
486
- /** Full expiry result (success detail or skip reason) for diagnostics. */
487
- reportExpiry: ReturnType<typeof expirePersistedReport>;
488
- }
489
- /**
490
- * Side-effecting: clear the session marker, the task-scoped marker
491
- * (when `toolNameMatched` and `tool_input.taskId` names an existing
492
- * marker), and expire the persisted report. Call ONLY after
493
- * `matchPostToolUseBoundary(...).matched` is true — this function does
494
- * not re-check the match itself. Mirrors the Claude hook's original
495
- * side effects exactly so both runtimes share one clearing
496
- * implementation (task a1348c89).
497
- */
498
- export declare function applyPostToolUseExpiry(generatedDir: string, sessionId: string, toolInput: unknown, toolNameMatched: boolean, reportsDir: string, now?: Date): ApplyPostToolUseExpiryResult;
499
- /**
500
- * Compose the stderr diagnostic line for a matched-and-applied PostToolUse
501
- * expiry. Shared by `harness pack hook post-tool-use` (Claude) and
502
- * `harness pack hook codex-post-tool-use` (Codex) — before this extraction
503
- * the two hooks each built this line by hand from the same four pieces
504
- * (`matchSource`/`taskNote`/`reportNote`/the present-vs-absent branch),
505
- * which `check:duplication` flagged as new clones the moment the Codex
506
- * sibling landed (task a1348c89). `hookLabel` is the caller's own
507
- * `harness pack hook ...` command string so the message still names the
508
- * right verb.
509
- */
510
- export declare function describePostToolUseExpiry(hookLabel: string, sessionId: string, toolName: string, bashRegex: RegExp | undefined, expiry: ApplyPostToolUseExpiryResult): string;
511
- export declare const ACTIVE_CLAIM_FILENAME = "active-claim";
512
- export declare function activeClaimPathFor(generatedDir: string): string;
513
- /**
514
- * Hook-side: write the active claim file. Atomic. Called from the
515
- * track-active-claim PostToolUse hook on `task_start`. The body is
516
- * just the taskId (no JSON) so an operator running
517
- * `cat ~/.claude/harness.generated/active-claim` sees the id directly.
518
- */
519
- export declare function writeActiveClaim(generatedDir: string, taskId: string): string;
520
- /**
521
- * Operator-side: read the active claim file. Returns the taskId or
522
- * null when the file is absent / unreadable / empty. `harness approve
523
- * understanding` calls this when --task is absent, then passes the
524
- * resolved id to writeTaskApprovalMarker.
525
- *
526
- * Defense-in-depth: if the on-disk content fails the same
527
- * path-traversal / newline check that gates writes, the read returns
528
- * null instead of surfacing a poisoned id. The write side guards
529
- * against a malformed taskId reaching the file in the first place,
530
- * but a stale file authored before this guard (or hand-edited)
531
- * shouldn't escalate into a forged task-marker write downstream.
532
- */
533
- export declare function readActiveClaim(generatedDir: string): string | null;
534
- /** Hook-side: remove the active claim file. Idempotent. */
535
- export declare function clearActiveClaim(generatedDir: string): void;
536
- export declare function checkPersistedReport(reportsDir: string, sessionId: string): PersistedReportApprovalCheck;
1
+ export * from "./understanding-before-execution/index.js";