@lannguyensi/harness 0.61.0 → 0.63.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 (48) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/dist/cli/apply/install-codex-config.d.ts +8 -5
  4. package/dist/cli/apply/install-codex-config.js +196 -25
  5. package/dist/cli/apply/install-codex-config.js.map +1 -1
  6. package/dist/cli/approve/understanding.d.ts +36 -2
  7. package/dist/cli/approve/understanding.js +191 -42
  8. package/dist/cli/approve/understanding.js.map +1 -1
  9. package/dist/cli/bounded-stdin.d.ts +15 -2
  10. package/dist/cli/bounded-stdin.js +32 -3
  11. package/dist/cli/bounded-stdin.js.map +1 -1
  12. package/dist/cli/explain-policy.d.ts +11 -0
  13. package/dist/cli/explain-policy.js +41 -3
  14. package/dist/cli/explain-policy.js.map +1 -1
  15. package/dist/cli/gc/index.d.ts +6 -2
  16. package/dist/cli/gc/index.js +22 -7
  17. package/dist/cli/gc/index.js.map +1 -1
  18. package/dist/cli/index.js +15 -14
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/pack/auto-approve-path.js +29 -3
  21. package/dist/cli/pack/auto-approve-path.js.map +1 -1
  22. package/dist/cli/pack/hook-bootstrap.d.ts +0 -12
  23. package/dist/cli/pack/hook-bootstrap.js +1 -27
  24. package/dist/cli/pack/hook-bootstrap.js.map +1 -1
  25. package/dist/cli/pack/hook-codex-pre-tool-use.js +4 -2
  26. package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
  27. package/dist/cli/pack/hook-pre-tool-use.js +19 -5
  28. package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
  29. package/dist/cli/pack/hook-runtime-reality.js +2 -2
  30. package/dist/cli/pack/hook-runtime-reality.js.map +1 -1
  31. package/dist/cli/policy/intercept.js +75 -103
  32. package/dist/cli/policy/intercept.js.map +1 -1
  33. package/dist/cli/policy/risk-envelope-enrichment.d.ts +40 -0
  34. package/dist/cli/policy/risk-envelope-enrichment.js +108 -0
  35. package/dist/cli/policy/risk-envelope-enrichment.js.map +1 -0
  36. package/dist/io/atomic-write.d.ts +37 -1
  37. package/dist/io/atomic-write.js +61 -8
  38. package/dist/io/atomic-write.js.map +1 -1
  39. package/dist/io/display-path.d.ts +11 -0
  40. package/dist/io/display-path.js +29 -0
  41. package/dist/io/display-path.js.map +1 -0
  42. package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +1 -1
  43. package/dist/policy-packs/builtin/understanding-before-execution/index.js +1 -1
  44. package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
  45. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +152 -21
  46. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +286 -48
  47. package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -1
  48. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/io/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAY,aAAa,EAAE,MAAM,MAAM,CAAC;AAM/C,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,OAAe,EACf,UAA8B,EAAE;IAEhC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC5E,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;IAC5D,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC1B,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IACD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,MAAsC;IAEtC,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC;IACZ,iFAAiF;IACjF,sEAAsE;IACtE,4EAA4E;IAC5E,uEAAuE;IACvE,8BAA8B;IAC9B,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/io/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAY,aAAa,EAAE,MAAM,MAAM,CAAC;AAM/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,EAAE,CAAC,SAAS,CAAC,QAAQ;IACrB,EAAE,CAAC,SAAS,CAAC,OAAO;IACpB,EAAE,CAAC,SAAS,CAAC,MAAM;IACnB,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;AAEjC,6FAA6F;AAC7F,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,OAAe,EACf,UAA8B,EAAE,EAChC,SAAuB,gBAAgB;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1E,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAC1B,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,MAAsC;IAEtC,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC;IACZ,iFAAiF;IACjF,sEAAsE;IACtE,4EAA4E;IAC5E,uEAAuE;IACvE,8BAA8B;IAC9B,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `value` as one double-quoted, single-line literal that is safe to print:
3
+ * `JSON.stringify` escapes `"`, `\`, every C0 control character (ESC as
4
+ * `\u001b`, CR as `\r`, LF as `\n`) and lone surrogates, and DEL and the C1
5
+ * range, which JSON leaves raw (U+009B is a one-byte CSI on terminals that
6
+ * honour C1), are escaped here as `\uXXXX`. Unicode format characters
7
+ * (including bidi overrides) and line/paragraph separators are escaped too.
8
+ * Supplementary characters use two UTF-16 `\uXXXX` escapes so the result
9
+ * stays a JSON literal that parses back to the original string.
10
+ */
11
+ export declare function escapeForDisplay(value: string): string;
@@ -0,0 +1,29 @@
1
+ // Safe rendering of a path (or any other agent-chosen string) into text an
2
+ // operator reads on a terminal.
3
+ //
4
+ // A file name in a directory the gated agent can write is attacker-chosen:
5
+ // POSIX permits every byte but `/` and NUL in it, so it can carry an ESC
6
+ // (an OSC 52 clipboard write, a screen clear), a CR (overwrite the line it
7
+ // lands on) or a newline (forge an extra diagnostic-looking line). A path
8
+ // that reaches stderr therefore goes through this one helper; it ESCAPES
9
+ // rather than strips, so the operator can still see which name is meant and
10
+ // can copy its escaped form into a recovery command.
11
+ //
12
+ // Lives in `src/io/` for the same reason `project-name.ts` does: it is a
13
+ // leaf utility any layer may use (`io/` may not import from `runtime/`).
14
+ /** Controls JSON leaves raw, Unicode format characters and line separators. */
15
+ const UNSAFE_DISPLAY_CHARACTERS = /[\u007f-\u009f\p{Cf}\u2028\u2029]/gu;
16
+ /**
17
+ * `value` as one double-quoted, single-line literal that is safe to print:
18
+ * `JSON.stringify` escapes `"`, `\`, every C0 control character (ESC as
19
+ * `\u001b`, CR as `\r`, LF as `\n`) and lone surrogates, and DEL and the C1
20
+ * range, which JSON leaves raw (U+009B is a one-byte CSI on terminals that
21
+ * honour C1), are escaped here as `\uXXXX`. Unicode format characters
22
+ * (including bidi overrides) and line/paragraph separators are escaped too.
23
+ * Supplementary characters use two UTF-16 `\uXXXX` escapes so the result
24
+ * stays a JSON literal that parses back to the original string.
25
+ */
26
+ export function escapeForDisplay(value) {
27
+ return JSON.stringify(value).replace(UNSAFE_DISPLAY_CHARACTERS, (ch) => ch.split("").map((unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`).join(""));
28
+ }
29
+ //# sourceMappingURL=display-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display-path.js","sourceRoot":"","sources":["../../src/io/display-path.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gCAAgC;AAChC,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,qDAAqD;AACrD,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AAEzE,+EAA+E;AAC/E,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAExE;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAClC,yBAAyB,EACzB,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CACtG,CAAC;AACJ,CAAC"}
@@ -6,7 +6,7 @@ export interface ApprovalCheckResult {
6
6
  }
7
7
  export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
8
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, type SkippedReportEntry, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, type FindReportOptions, type FindReportSelection, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, type PersistedReportEvidence, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, type ReportFileReadFailure, type BoundedReportRead, readReportFileBounded, type UnhashableReportReason, type ReportFileHash, hashReportFile, type ApprovalMarkerKind, type MarkerReportBinding, type ApprovedReportHashVerification, verifyApprovedReportHash, } from "./persisted-reports.js";
9
+ export { type PersistedReport, REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, type SkippedReportEntry, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, type FindReportOptions, type FindReportSelection, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, type PersistedReportEvidence, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, MAX_HOOK_LISTING_ENTRIES, type BoundedDirNames, listDirNamesBounded, type ReportFileReadFailure, type BoundedReportRead, type ReadBudget, readReportFileBounded, type UnhashableReportReason, type ReportFileHash, hashReportFile, type ApprovalMarkerKind, type MarkerReportBinding, type ApprovedReportHashVerification, verifyApprovedReportHash, } from "./persisted-reports.js";
10
10
  export { type ApprovalLifecycle, parseApprovalLifecycle } from "./lifecycle.js";
11
11
  export { AUTO_APPROVED_BY_PREFIX, CLAUDE_CODE_HARNESS, autoApprovedByFor, type ParsedAutoApprovedBy, parseAutoApprovedBy, autoApprovedLedgerTagFor, type AutoApproveConfig, parseAutoApprove, permissionModeAllowed, harnessAllowed, AUTO_APPROVE_HARNESS_VALUES, DEFAULT_AUTO_APPROVE_HARNESSES, DEFAULT_REPORT_SCAN_MAX_WAIT_MS, REPORT_SCAN_MAX_WAIT_CEILING_MS, parseReportScanMaxWait, } from "./auto-approve.js";
12
12
  export { type AutoApproveDefaultConfig, defaultAutoApproveConfig, renderAutoApproveSnippet, } from "./auto-approve-default.js";
@@ -37,7 +37,7 @@
37
37
  // harness gate; `harness approve understanding` is the approval path.
38
38
  export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
39
39
  export { APPROVAL_MARKER_DIRNAME, approvalMarkerPathFor, writeApprovalMarker, checkApprovalMarker, clearApprovalMarker, } from "./markers.js";
40
- export { REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, readReportFileBounded, hashReportFile, verifyApprovedReportHash, } from "./persisted-reports.js";
40
+ export { REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, MAX_HOOK_LISTING_ENTRIES, listDirNamesBounded, readReportFileBounded, hashReportFile, verifyApprovedReportHash, } from "./persisted-reports.js";
41
41
  export { parseApprovalLifecycle } from "./lifecycle.js";
42
42
  export { AUTO_APPROVED_BY_PREFIX, CLAUDE_CODE_HARNESS, autoApprovedByFor, parseAutoApprovedBy, autoApprovedLedgerTagFor, parseAutoApprove, permissionModeAllowed, harnessAllowed, AUTO_APPROVE_HARNESS_VALUES, DEFAULT_AUTO_APPROVE_HARNESSES, DEFAULT_REPORT_SCAN_MAX_WAIT_MS, REPORT_SCAN_MAX_WAIT_CEILING_MS, parseReportScanMaxWait, } from "./auto-approve.js";
43
43
  // D-004 shipped default (task 8f637efd): the one `auto_approve` snippet
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/index.ts"],"names":[],"mappings":"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"}
@@ -55,16 +55,16 @@ export declare function defaultReportsDir(cwd?: string): string;
55
55
  export declare function reportsDirForManifest(manifestPath: string): string;
56
56
  /**
57
57
  * List persisted reports under `dir`, newest-first by creation time
58
- * (JSON `createdAt`, falling back to the filename ISO prefix, falling
59
- * back to mtime). Missing directory returns []. Any I/O error on a
60
- * single file is silently skipped; the caller falls through to the
61
- * ledger result.
58
+ * (JSON `createdAt`, then the filename ISO prefix, then mtime). Missing
59
+ * directory returns []; an I/O error on a single file skips it. Reads each
60
+ * regular file IN FULL, so it must not list the agent-writable reports
61
+ * directory (use {@link listPersistedReportsBoundedWithSkips}); no `src`
62
+ * caller remains, it stays for its tests and the pack's export surface.
62
63
  *
63
64
  * Creation time, NOT mtime, is the sort key: `harness approve
64
65
  * understanding` rewrites the report it flips, which bumps mtime and
65
66
  * made an old just-approved report sort as the freshest
66
- * (harness-discovery C1). mtime survives only as the last-resort
67
- * fallback for files that carry neither timestamp.
67
+ * (harness-discovery C1). mtime is the last-resort fallback.
68
68
  */
69
69
  export declare function listPersistedReports(dir: string): PersistedReport[];
70
70
  /**
@@ -276,6 +276,86 @@ export declare function describeBoundaryExpiry(report: Pick<PersistedReport, "ap
276
276
  * producers refuse to approve such a report.
277
277
  */
278
278
  export declare const MAX_HASHED_REPORT_BYTES: number;
279
+ /**
280
+ * Budget the gate-read hash scan spends before it gives up (32 MiB), charged
281
+ * per `*.json` entry as the larger of the bytes read and
282
+ * {@link MIN_SCAN_ENTRY_COST_BYTES}. The per-file cap bounds one file, not
283
+ * their number: 160 planted 1 MiB key-heavy files took 15.9 to 17.6 s of scan
284
+ * time, past the 15 s PreToolUse budget, and a hook past its budget is a
285
+ * non-blocking error for the runtime (an allow). Charging bytes alone left the
286
+ * entry count open: entries that cost no bytes (tiny files, directories named
287
+ * `*.json`, files over the cap, unreadable ones) still cost an open each, and
288
+ * 400k tiny files took about 22 s of CPU. A scan that spent the budget without
289
+ * finding every wanted hash stops and the check fails closed with the mismatch
290
+ * reason. With the floor the scan opens at most 8192 entries and reads about
291
+ * 32 MiB (the last file read may add up to the 1 MiB per-file cap), whichever
292
+ * the entries are. Real reports are a few kilobytes, so the budget covers
293
+ * thousands of them. The scan reads the newest file first, which is where the
294
+ * approved report usually is.
295
+ *
296
+ * The other full-directory reads on the PreToolUse path spend the same
297
+ * budget with the same floor, each its own (a fresh {@link ReadBudget} per
298
+ * listing, not one shared across the hook call): the evidence read, the
299
+ * auto-approval precondition listing, the subagent-delegation lookup and the
300
+ * parse-error log lookup each read at most about 32 MiB, and none opens an
301
+ * entry once the directory holds more than {@link MAX_HOOK_LISTING_ENTRIES}.
302
+ */
303
+ export declare const MAX_HASH_SCAN_BYTES: number;
304
+ /**
305
+ * Least a `*.json` entry is charged against {@link MAX_HASH_SCAN_BYTES}
306
+ * whatever the read returned (4 KiB, about one real report), so entries that
307
+ * read nothing or little still spend the budget at a rate that bounds their
308
+ * count: 32 MiB / 4 KiB = 8192 entries.
309
+ */
310
+ export declare const MIN_SCAN_ENTRY_COST_BYTES = 4096;
311
+ /**
312
+ * Most entries (8192, the hash scan's budget divided by its per-entry floor)
313
+ * a PreToolUse-path reader accepts in one agent-writable directory. The hash
314
+ * scan stops at its budget, but four more readers on the same hook path used
315
+ * to open every entry (the evidence read, the auto-approval precondition
316
+ * listing, the subagent-delegation lookup, the parse-error log lookup), so a
317
+ * directory of hundreds of thousands of planted entries took the hook past the
318
+ * 15 s PreToolUse budget, which the runtime treats as an allow (measured at
319
+ * 400k planted entries: 408195 opens and 28.9 s of CPU in the Claude Code
320
+ * hook). The count alone left each entry readable up to the 1 MiB per-file
321
+ * cap, so every one of those readers also charges the entries it reads against
322
+ * {@link MAX_HASH_SCAN_BYTES} (see {@link ReadBudget}).
323
+ *
324
+ * A reader does not take a newest window of such a directory: a partial view
325
+ * would let a hidden newer entry change a selection (an auto-approval would
326
+ * adopt an older pending report while a newer, hidden one is already
327
+ * approved). Past either bound (more entries, or more report data than the
328
+ * byte budget) it returns nothing and reports the directory as too large to
329
+ * read, and each reader treats that as no evidence: the evidence read
330
+ * reports no report, the auto-approval path declines, the delegation lookup
331
+ * captures nothing and the parse-error lookup finds nothing. None of them
332
+ * allows. The directory listing stops as soon as the bound is crossed. See
333
+ * {@link listDirNamesBounded}.
334
+ */
335
+ export declare const MAX_HOOK_LISTING_ENTRIES: number;
336
+ /** Result of {@link listDirNamesBounded}. */
337
+ export interface BoundedDirNames {
338
+ /** Entry names that end with the suffix, in ascending byte order; empty when `truncated`. */
339
+ names: string[];
340
+ /** True when the directory holds more matching entries than `maxEntries`, or more entries in all than `maxScanned`. */
341
+ truncated: boolean;
342
+ }
343
+ /**
344
+ * Names under `dir` ending in `suffix`, or none and `truncated: true` when
345
+ * there are more than `maxEntries` of them. The one listing the bounded
346
+ * PreToolUse-path readers share, so the bound lives in one place; a missing or
347
+ * unreadable directory lists nothing and is not `truncated`.
348
+ *
349
+ * The listing reads the directory one entry at a time and stops as soon as
350
+ * `maxEntries + 1` matching names have been seen, or `maxScanned` entries of
351
+ * any name (default: {@link HOOK_LISTING_SCAN_FACTOR} times `maxEntries`),
352
+ * so millions of planted names, matching or not, cost a bounded number of
353
+ * directory reads and no allocation of their names. The directory handle is
354
+ * always closed. An unbounded `maxEntries` reads the whole directory. The
355
+ * names come back in ascending byte order (the `fs.readdirSync` order), not
356
+ * in the filesystem's iteration order.
357
+ */
358
+ export declare function listDirNamesBounded(dir: string, suffix: string, maxEntries: number, maxScanned?: number): BoundedDirNames;
279
359
  /** Result of {@link walkContainerDepth}. */
280
360
  export interface ContainerDepthWalk {
281
361
  /** True when some array/object nests deeper than the walk's `max` levels. */
@@ -313,7 +393,15 @@ export declare function walkContainerDepth(value: unknown, max: number): Contain
313
393
  */
314
394
  export declare function canonicalReportHash(report: Record<string, unknown>): string | null;
315
395
  /** Why {@link readReportFileBounded} returned no content. */
316
- export type ReportFileReadFailure = "unreadable" | "not-regular" | "too-large" | "grew";
396
+ export type ReportFileReadFailure = "unreadable" | "not-regular" | "too-large" | "grew" | "over-budget";
397
+ /**
398
+ * Bytes a bounded multi-entry reader has spent against
399
+ * {@link MAX_HASH_SCAN_BYTES}; pass one object to every
400
+ * {@link readReportFileBounded} call of the same listing.
401
+ */
402
+ export interface ReadBudget {
403
+ spent: number;
404
+ }
317
405
  export type BoundedReportRead = {
318
406
  ok: true;
319
407
  raw: string;
@@ -337,8 +425,22 @@ export type BoundedReportRead = {
337
425
  * (never more than the cap plus one byte); a file that grows into that byte
338
426
  * while being read is refused too. The descriptor is always closed. Never
339
427
  * throws.
428
+ *
429
+ * `opts.noFollow` adds `O_NOFOLLOW` to the open: a path that is a symbolic
430
+ * link (whatever it points at, a dangling link included) is refused as
431
+ * not-regular by the open itself (`ELOOP`), with no earlier `lstat` for a
432
+ * swap to race. Off by default, so every hook read keeps following links
433
+ * exactly as before; `harness approve understanding` turns it on.
434
+ *
435
+ * `opts.budget` charges the read against {@link MAX_HASH_SCAN_BYTES} the way
436
+ * the hash scan does (the larger of the `fstat` size and
437
+ * {@link MIN_SCAN_ENTRY_COST_BYTES} per entry, taken before the read); once
438
+ * the budget is spent the call opens nothing and returns `over-budget`.
340
439
  */
341
- export declare function readReportFileBounded(filePath: string): BoundedReportRead;
440
+ export declare function readReportFileBounded(filePath: string, opts?: {
441
+ noFollow?: boolean;
442
+ budget?: ReadBudget;
443
+ }): BoundedReportRead;
342
444
  /** Why a report file has no canonical hash. */
343
445
  export type UnhashableReportReason = ReportFileReadFailure | "not-json-object" | "too-deep";
344
446
  export type ReportFileHash = {
@@ -357,9 +459,13 @@ export type ReportFileHash = {
357
459
  * any of these (the `detail` is its message); the gate-read scan counts such
358
460
  * a file as one that matches nothing. Never throws.
359
461
  */
360
- export declare function hashReportFile(filePath: string): ReportFileHash;
462
+ export declare function hashReportFile(filePath: string, opts?: {
463
+ noFollow?: boolean;
464
+ }): ReportFileHash;
361
465
  /** Canonical hash of the report file at `filePath` ({@link hashReportFile}); null when it has none. */
362
- export declare function canonicalReportHashOfFile(filePath: string): string | null;
466
+ export declare function canonicalReportHashOfFile(filePath: string, opts?: {
467
+ noFollow?: boolean;
468
+ }): string | null;
363
469
  /** Marker kind whose signed report hash the gate checks. */
364
470
  export type ApprovalMarkerKind = "task" | "session";
365
471
  /** One matched marker's signed `reportContentHash`, tagged with its kind. */
@@ -412,11 +518,26 @@ export type ApprovedReportHashVerification = {
412
518
  * the "no report file" allow, the same as before this check existed. The
413
519
  * marker and the operator's approval itself are untouched in both cases;
414
520
  * only the audit trail loses its claim to be the approved content. (3) The
415
- * cap bounds each file, not their number: the scan time grows with the
416
- * total volume of report files, so an agent that plants hundreds of
417
- * megabytes of files just under the cap can still push one scan past the
418
- * hook's time budget, which the runtime treats as a non-blocking error (the
419
- * same allow as before this check existed).
521
+ * cap bounds each file, not their number, so the scan charges every `*.json`
522
+ * entry at least `MIN_SCAN_ENTRY_COST_BYTES` against `MAX_HASH_SCAN_BYTES`
523
+ * (bytes read or the floor, whichever is larger, whatever the read returned):
524
+ * past the budget the check denies with the mismatch reason (fail closed).
525
+ * That bounds the entries opened (8192) and the bytes read (about 32 MiB, the
526
+ * last file read may add up to the 1 MiB per-file cap). Three costs remain.
527
+ * A legitimate approval whose report sits behind that much newer report data
528
+ * (by name, descending) is denied; re-approving writes a new, newest report
529
+ * and recovers only when the newer entries are real reports: planted `*.json`
530
+ * names that sort after the timestamped report names (for example names
531
+ * starting with a letter) stay newer than any new report and keep denying
532
+ * until removed. The `readdir` plus name sort of the directory itself are not
533
+ * charged, so a directory of millions of entries still costs that listing
534
+ * before this scan starts (the scan runs when a signed marker is present). The
535
+ * hash scan is not the whole hook: the other full-directory reads on it (the
536
+ * evidence read, the auto-approval listing, the delegation lookup and the
537
+ * parse-error lookup) list the directory entry by entry and stop once
538
+ * {@link MAX_HOOK_LISTING_ENTRIES} entries are crossed, and charge the entries
539
+ * they read against the same byte budget; past either bound they open or read
540
+ * nothing more and fail closed.
420
541
  *
421
542
  * Removing the approved report while other report files remain is a deny:
422
543
  * nothing on disk carries the signed content any more. `harness gc --apply`
@@ -429,8 +550,8 @@ export type ApprovedReportHashVerification = {
429
550
  * fallback is consulted only when the primary fails the check, so a task
430
551
  * marker signed for another report does not hide a session marker that
431
552
  * verifies. Unchanged behaviour: a null hash on the accepted marker (no
432
- * report resolved, or `--force` over a report the producer could not hash:
433
- * too deeply nested or over the size cap) and a reports directory with no
553
+ * report resolved, or `--force` over a report nested too deeply to hash,
554
+ * never one over the size cap) and a reports directory with no
434
555
  * `*.json` entry at all both allow. A forced approval of a hashable report
435
556
  * signs its hash and is checked like any other.
436
557
  *
@@ -465,9 +586,12 @@ export interface SkippedReportEntry {
465
586
  *
466
587
  * A caller that must not let the selection fall back to an older report when
467
588
  * a newer one is unreadable (the auto-approval precondition) declines when
468
- * `skipped` is not empty. The operator commands (`harness approve
469
- * understanding`, `harness gc`) keep the unbounded {@link listPersistedReports}
470
- * on purpose: they must still see an oversized report to refuse or age it.
589
+ * `skipped` is not empty. The operator commands read through this listing as
590
+ * well, so a planted oversized or non-regular entry cannot crash or hang
591
+ * them, and they still see it: `harness approve understanding` refuses
592
+ * while `skipped` is not empty (it passes `refuseSymlinks`, which also
593
+ * refuses a symbolic link at the open, whatever it points at), and
594
+ * `harness gc` reports each skipped entry and leaves it in place.
471
595
  *
472
596
  * Placement: this block sits at the end of the file, and
473
597
  * {@link readPersistedReport} takes the already-read text (`boundedRaw`), so
@@ -475,9 +599,16 @@ export interface SkippedReportEntry {
475
599
  * docs/decisions/2026-08-27-ug-auto-mode-approval.md cites in this file stay
476
600
  * put (tests/decisions-citations-resolve.test.ts pins them).
477
601
  */
478
- export declare function listPersistedReportsBoundedWithSkips(dir: string): {
602
+ export declare function listPersistedReportsBoundedWithSkips(dir: string, opts?: {
603
+ refuseSymlinks?: boolean;
604
+ maxEntries?: number;
605
+ }): {
479
606
  reports: PersistedReport[];
480
607
  skipped: SkippedReportEntry[];
608
+ /** True when `opts.maxEntries` was set and the directory holds more `*.json` entries, or more report data than the byte budget; no report is returned. */
609
+ truncated: boolean;
610
+ /** Why the listing was `truncated`, as a clause that follows the subject ("holds more than ..."); empty otherwise. */
611
+ truncatedDetail: string;
481
612
  };
482
613
  /**
483
614
  * {@link listPersistedReportsBoundedWithSkips} without the skipped entries,