@bookedsolid/rea 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATING.md CHANGED
@@ -389,6 +389,145 @@ per finding and produces more consistent verdicts — fewer
389
389
  same-code-different-verdict round-trips. Trade-off is push-gate
390
390
  latency.
391
391
 
392
+ ## Node-binary hook scanner (added in 0.32.0)
393
+
394
+ Pre-0.32.0 every `.claude/hooks/*.sh` carried the full gate body in
395
+ bash. Adversarial review consistently caught bash-only edge cases that
396
+ were structurally unfixable in shell — multi-line awk encodings,
397
+ ANSI-C escapes, deep nested-shell decoding. 0.32.0 pivoted the entire
398
+ hook surface to a Node-binary scanner: hooks became thin shims (~20-80
399
+ LOC each) that delegate the actual gate work to `rea hook <name>` —
400
+ which runs the canonical scanner inside `dist/cli/index.js`.
401
+
402
+ **Consumer impact:**
403
+
404
+ - Run `pnpm install` (or `npm install`) after upgrading to 0.32.0+ so
405
+ `dist/cli/index.js` is built and the shims have something to call.
406
+ - `.claude/hooks/*.sh` files on disk are noticeably smaller after
407
+ `rea upgrade`; this is the canonical post-0.32.0 shape, not a
408
+ truncation. `rea doctor` will tell you if a shim is the wrong
409
+ vintage.
410
+ - The audit trail is unchanged: hooks still emit `rea.bash_scan`-class
411
+ records to `.rea/audit.jsonl` with the same field shape.
412
+ - Performance is materially better — single Node startup per scan
413
+ instead of an awk/sed pipeline per pattern.
414
+
415
+ If `rea doctor` reports `policy-reader Tier 1 (rea CLI)` as `warn:
416
+ dist not found`, you skipped the build step. Run `pnpm install`.
417
+
418
+ ## Graceful-degradation policy reader (added in 0.37.0)
419
+
420
+ The shimmed hooks need to read `.rea/policy.yaml` from a bash context
421
+ that may or may not have python3, jq, or rea's CLI on PATH. 0.37.0
422
+ formalized a 4-tier reader ladder:
423
+
424
+ 1. **Tier 1** — `rea hook policy-get` (requires `dist/cli/index.js`)
425
+ 2. **Tier 2** — `python3 + stdlib yaml` (PyYAML) — handles flow-form
426
+ 3. **Tier 3** — POSIX `awk` block-form parser (the always-available floor)
427
+ 4. **Fail-closed** — every tier unreachable: shim refuses the action
428
+
429
+ Tier 1 → 2 → 3 fallthrough is silent at hook-runtime; that's
430
+ intentional (graceful degradation), but means an unreachable Tier 1 +
431
+ unreachable Tier 2 can silently downgrade flow-form policy lookups to
432
+ block-form-only. `rea doctor` (0.39.0+) surfaces all three tier
433
+ reachabilities so you can spot the gap.
434
+
435
+ **Consumer impact:**
436
+
437
+ - If you use FLOW-form YAML for any policy block (e.g.
438
+ `blocked_paths: [.env, ".env.*"]`), make sure either the rea CLI
439
+ dist is present OR `python3 + PyYAML` is installed. With ONLY awk
440
+ reachable, flow-form lookups silently no-op on every shim
441
+ fallthrough path and your declared policy isn't enforced.
442
+ - Install PyYAML on CI runners: `pip3 install pyyaml`. On consumer
443
+ developer machines, it's almost always already present (macOS ships
444
+ it; major Linux distros bundle it with python3).
445
+ - For list-valued policy keys (`blocked_paths`, `protected_writes`),
446
+ the loader iterates the resulting JSON via jq OR python3. Have at
447
+ least one on PATH or `rea doctor` (0.42.0+) will report `fail` on
448
+ the `policy-reader Tier 3 (awk)` row with a list-walker-specific
449
+ remediation message.
450
+
451
+ ## Shim runtime extraction (added in 0.38.0)
452
+
453
+ Cosmetic-only refactor: every `.claude/hooks/*.sh` shim now sources
454
+ `hooks/_lib/shim-runtime.sh` for shared boilerplate (env loading,
455
+ tier classification, audit-event emission). **No consumer action
456
+ required** — the change is byte-equivalent at the gate surface. New
457
+ shims you author can adopt the same runtime by sourcing the shared
458
+ helper; documented in the shim authoring guide.
459
+
460
+ ## Doctor health surfaces for the policy reader (added in 0.39.0)
461
+
462
+ `rea doctor` gained explicit reachability checks for the 4-tier
463
+ ladder, the dist invokability probe, and a sandbox-containment check
464
+ on the resolved `dist/cli/index.js` path. Output lines you'll see:
465
+
466
+ - `policy-reader Tier 1 (rea CLI)` — pass/warn based on dist
467
+ presence + actual invocation
468
+ - `policy-reader Tier 2 (python3 + PyYAML)` — pass/warn based on
469
+ python3 + import yaml succeeding
470
+ - `policy-reader Tier 3 (awk)` — pass when awk present; warn or
471
+ fail conditional on whether other tiers cover the gap (0.40.0
472
+ refined the verdict logic; 0.42.0 hardened the list-walker
473
+ predicate)
474
+ - `policy-reader effective floor` — summary verdict across all three
475
+ - `policy-reader jq (JSON accelerator)` — info-level, calls out
476
+ Tier 1/2 perf when jq is absent
477
+
478
+ **Consumer action:** run `rea doctor` after each upgrade. The lines
479
+ above accurately reflect what your shims will do at runtime — a
480
+ `warn` is not a hard failure but signals a posture worth knowing
481
+ about (e.g. flow-form policy silently no-ops). A `fail` on any tier
482
+ row IS a hard failure that the doctor exits non-zero on.
483
+
484
+ ## Upgrade preview + audit summary (added in 0.41.0)
485
+
486
+ Two new consumer-facing commands rolled out:
487
+
488
+ ### `rea upgrade --check`
489
+
490
+ Dry-run preview of what `rea upgrade` would write, file-by-file, with
491
+ unified diffs. JSON output via `--json`. Always exits 0 — this is a
492
+ preview, not a gate. Use it before any non-trivial rea upgrade to
493
+ sanity-check the diff:
494
+
495
+ ```bash
496
+ rea upgrade --check # human-readable table + diffs
497
+ rea upgrade --check --json # machine-readable for CI
498
+ rea upgrade --check --no-diff # counts + paths only
499
+ ```
500
+
501
+ 0.42.0 added the same settings-schema validation that `rea upgrade`
502
+ itself runs — if the merged settings would fail schema parse (typo'd
503
+ hook event, malformed hook command, …), the preview surfaces the
504
+ `WOULD REFUSE` message rather than promising a write the real
505
+ upgrade would refuse. The `settings_validation` field in the JSON
506
+ output carries the structured outcome.
507
+
508
+ ### `rea audit summary`
509
+
510
+ High-level rollup of the audit log: counts by `tool_name`, `tier`,
511
+ `status`, `session`, the time window covered, and a sample-verified
512
+ chain-integrity check. `--since <duration>` (e.g. `24h`, `7d`, `2w`)
513
+ narrows to a recent window:
514
+
515
+ ```bash
516
+ rea audit summary # all time
517
+ rea audit summary --since 24h # last 24 hours
518
+ rea audit summary --since 7d --json # last week, JSON
519
+ ```
520
+
521
+ 0.42.0 hardened the rotated-file walk: pre-0.42.0 `--since` pruned
522
+ rotated audit segments by filename stamp, which is wall-clock at the
523
+ rotation INSTANT — not the earliest record contained. A rotated file
524
+ from N days ago can contain records from N+M days ago when the
525
+ rotation cycle was long, so pruning by filename silently dropped
526
+ in-window records. Post-0.42.0 the walker reads every rotated file
527
+ under `--since` and lets the per-record timestamp filter drop the
528
+ out-of-window entries. Correctness over micro-optimization;
529
+ `rea audit summary` performance is unchanged in practice.
530
+
392
531
  ## Policy knobs worth setting
393
532
 
394
533
  For consumers with a long-running migration branch (>30 commits since
package/README.md CHANGED
@@ -9,8 +9,13 @@
9
9
  [![DCO](https://img.shields.io/badge/DCO-required-green)](https://developercertificate.org/)
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org/)
11
11
 
12
- > Status: `0.11.0` — published to npm with SLSA v1 provenance. See
13
- > [CHANGELOG.md](./CHANGELOG.md) for the per-release history.
12
+ > Status: `0.41.0` — published to npm with SLSA v1 provenance. See
13
+ > [CHANGELOG.md](./CHANGELOG.md) for the per-release history. The
14
+ > hook-port marathon (0.32→0.35) replaced every shell hook body with
15
+ > a Node-binary shim; the bash files in `.claude/hooks/` are now
16
+ > ~30-line stubs that fork `rea hook scan-bash` / `scan-write` for
17
+ > the real work. See [Architecture](#architecture) for the runtime
18
+ > picture.
14
19
 
15
20
  REA is a single npm package that gates and audits agentic tool calls made by
16
21
  Claude Code — shell commands, filesystem writes, and MCP tool invocations —
@@ -38,6 +43,7 @@ the [migration section](#migration-from-010x) below.
38
43
  - [Quickstart](#quickstart)
39
44
  - [What REA is](#what-rea-is)
40
45
  - [What REA is NOT](#what-rea-is-not)
46
+ - [Architecture](#architecture)
41
47
  - [The pre-push Codex gate](#the-pre-push-codex-gate)
42
48
  - [MCP gateway](#mcp-gateway)
43
49
  - [Policy file](#policy-file)
@@ -141,31 +147,39 @@ does not prevent the kill-switch from firing.
141
147
 
142
148
  ### 3. A hook layer
143
149
 
144
- Eleven shell scripts ship in `hooks/` and are copied into `.claude/hooks/`
145
- by `rea init`. All eleven are wired into the default `.claude/settings.json`
146
- and fire on Claude Code's `PreToolUse` / `PostToolUse` events (secret
147
- scanning, dangerous-command interception, blocked-path enforcement,
148
- settings protection, attribution rejection, env-file protection,
149
- disclosure-policy routing, dependency audit, changeset security,
150
- PR-issue-link advisory, architecture advisory). Each hook uses
151
- `set -euo pipefail` (or `set -uo pipefail` for stdin-JSON consumers) and
152
- runs a HALT check near the top. See [Hooks shipped](#hooks-shipped) for
153
- the full inventory.
154
-
155
- **Bash-tier scanner (parser-backed since 0.23.0).** Two hooks
156
- `protected-paths-bash-gate.sh` and `blocked-paths-bash-gate.sh` are
157
- shims that forward stdin to `rea hook scan-bash`, a CLI subcommand
158
- that parses the Bash command via `mvdan-sh@0.10.1`, walks the AST,
159
- and emits a verdict JSON. Pre-0.23.0 these were 500-line bash regex
160
- pipelines; the rewrite closes 24 known-bypass classes
161
- (helix-021..023 + discord-ops Round 13 + codex round 1) by replacing
162
- re-tokenization heuristics with structural matches against the parsed
163
- argv tree. The other nine hooks remain regex-based bash. The shim
164
- re-verifies the verdict JSON shape on return so a tampered
165
- `REA_NODE_CLI` env var cannot bypass. See
150
+ Fourteen hook scripts ship in `hooks/` and are copied into
151
+ `.claude/hooks/` by `rea init`. All fourteen are wired into the default
152
+ `.claude/settings.json` and fire on Claude Code's `PreToolUse` /
153
+ `PostToolUse` events (secret scanning, dangerous-command interception,
154
+ blocked-path enforcement, settings protection, attribution rejection,
155
+ env-file protection, disclosure-policy routing, dependency audit,
156
+ changeset security, PR-issue-link advisory, architecture advisory,
157
+ local-review enforcement, protected-paths + blocked-paths bash-tier
158
+ parity). Each hook performs a HALT check near the top. See
159
+ [Hooks shipped](#hooks-shipped) for the full inventory.
160
+
161
+ **Node-binary scanners (since 0.32.0).** The hook-port marathon
162
+ (0.32.0 0.35.0) replaced every shell hook body with a Node-binary
163
+ shim. The bash files in `.claude/hooks/` are now ~30-line stubs that
164
+ fork `rea hook scan-bash` / `rea hook scan-write` (the AST-walker and
165
+ write-tier scanner respectively) and re-verify the verdict JSON shape
166
+ on return a tampered `REA_NODE_CLI` env var cannot bypass. The
167
+ parser-tier walker (`mvdan-sh@0.10.1`) handles Bash AST grammar
168
+ exhaustively; the write-tier scanner handles `Write`/`Edit`/
169
+ `MultiEdit`/`NotebookEdit` payloads against the same policy. See
166
170
  [`docs/architecture/bash-scanner.md`](docs/architecture/bash-scanner.md)
167
- for the AST-walker design and [`docs/migration/0.23.0.md`](docs/migration/0.23.0.md)
168
- for consumer migration notes.
171
+ and [`docs/migration/0.23.0.md`](docs/migration/0.23.0.md) for the
172
+ walker design and consumer migration notes.
173
+
174
+ **Four-tier policy reader.** The shim infrastructure honors a
175
+ four-tier ladder when loading policy (`hooks/_lib/policy-reader.sh`):
176
+ Tier 1 is `rea hook policy-get` (the dist CLI), Tier 2 is
177
+ `python3 + PyYAML`, Tier 3 is `awk` (block-form only), and an
178
+ optional `jq` accelerator for JSON walks. `rea doctor` probes every
179
+ tier and surfaces which one(s) are reachable in the operator's
180
+ environment — pre-0.39.0 a stale dist + missing PyYAML would
181
+ silently no-op flow-form policy when `awk` was the only working
182
+ tier. See `Self-validation` below.
169
183
 
170
184
  The hook layer runs independently of the MCP gateway — bypassing one does
171
185
  not disable the other. That redundancy is intentional.
@@ -220,6 +234,71 @@ The non-goals are the product.
220
234
 
221
235
  ---
222
236
 
237
+ ## Architecture
238
+
239
+ REA ships as a single npm package that delivers five runtime surfaces:
240
+
241
+ 1. **Node-binary CLI** (`dist/cli/index.js`) — the `rea` command, with
242
+ subcommands for install (`init`/`upgrade`), runtime
243
+ (`serve`/`check`/`status`/`doctor`), kill-switch (`freeze`/
244
+ `unfreeze`), audit (`rotate`/`verify`/`summary`/`specialists`),
245
+ review (`review`/`preflight`/`hook push-gate`), and hook
246
+ evaluation (`hook scan-bash`/`scan-write`/`policy-get`).
247
+ 2. **Shell hook shims** (`hooks/*.sh` → `.claude/hooks/*.sh`) — ~30
248
+ lines apiece. Each shim performs a HALT check, then forks the
249
+ corresponding `rea hook scan-*` Node CLI for the real verdict.
250
+ Pre-0.32.0 these were 500+ line bash regex pipelines; the rewrite
251
+ closed 24 known bypass classes and removed the bash hot-path
252
+ entirely. The shims still ship as bash so Claude Code's hook
253
+ matcher (which spawns sh) works without a Node prerequisite at
254
+ hook-fire time — the Node CLI is invoked from inside.
255
+ 3. **Husky hooks** (`.husky/commit-msg`, `.husky/pre-push`,
256
+ `.husky/prepare-commit-msg`) — written by `rea init`. Pre-push
257
+ runs `rea hook push-gate` (stateless Codex review). Commit-msg
258
+ blocks AI attribution and DCO violations. Prepare-commit-msg
259
+ optionally appends a `Co-Authored-By:` trailer when configured.
260
+ Extension surfaces in `.husky/{commit-msg,pre-push}.d/*` are
261
+ sourced after rea's body for layering commitlint, lint-staged,
262
+ etc.
263
+ 4. **MCP gateway** (`rea serve`) — a stdio MCP server started by
264
+ Claude Code via `.mcp.json`. Proxies downstream MCPs declared in
265
+ `.rea/registry.yaml` through a fixed middleware chain (audit,
266
+ kill-switch, tier, policy, blocked-paths, rate-limit, breaker,
267
+ injection, redact, size-cap). See [MCP gateway](#mcp-gateway).
268
+ 5. **Four-tier policy reader** (`hooks/_lib/policy-reader.sh`) — the
269
+ shared helper that bash shims use to read `.rea/policy.yaml`.
270
+ Tier 1 calls `rea hook policy-get` (full YAML semantics). Tier 2
271
+ falls back to `python3 + PyYAML` when the CLI is unreachable.
272
+ Tier 3 falls back to `awk` for the block-form subset. `jq` is an
273
+ optional JSON accelerator. Each tier downgrades silently to the
274
+ next; `rea doctor` probes the ladder explicitly so operators see
275
+ exactly which tier(s) work in their environment.
276
+
277
+ ### Self-validation
278
+
279
+ `rea doctor` validates every install surface in one shot:
280
+
281
+ - `.rea/policy.yaml` parses against the strict zod schema
282
+ - `.rea/` directory layout (HALT, registry, fingerprints, audit log)
283
+ - `.claude/settings.json` schema + every shipped hook registered
284
+ - `.husky/commit-msg`, `.husky/pre-push`, `.husky/prepare-commit-msg`
285
+ exist + have the expected marker, with husky-9 stub indirection
286
+ followed transparently
287
+ - `codex` binary on `PATH` when `policy.review.codex_required: true`
288
+ - Per-tier policy reader probe (`rea hook policy-get` → `python3 +
289
+ PyYAML` → `awk` → optional `jq`) so silent flow-form no-ops are
290
+ caught at install time, not at first hook fire
291
+ - Optional `--smoke` drives the real delegation-capture hook
292
+ end-to-end (writes a probe audit record + verifies chain integrity)
293
+ - Optional `--drift` reports per-file SHA drift vs. the install
294
+ manifest without mutating
295
+ - `--strict` promotes settings-schema warnings to hard fail (for CI)
296
+
297
+ This repo dogfoods every check — see `.rea/` and `.claude/` in the
298
+ checkout for the canonical `bst-internal` profile layout.
299
+
300
+ ---
301
+
223
302
  ## The pre-push Codex gate
224
303
 
225
304
  The 0.11.0 gate is stateless. Every `git push` runs Codex on the diff, and
@@ -864,12 +943,27 @@ Sync `.claude/`, `.husky/`, and managed fragments with this rea version.
864
943
  Prompts on drift; silently refreshes unmodified files.
865
944
 
866
945
  ```bash
867
- rea upgrade --dry-run # show what would change; write nothing
868
- rea upgrade # interactive
869
- rea upgrade -y # non-interactive, keep drifted files
870
- rea upgrade --force # non-interactive, overwrite drift
946
+ rea upgrade --dry-run # rehearse the interactive flow; write nothing
947
+ rea upgrade --check # structured preview + unified diffs (0.41.0)
948
+ rea upgrade --check --json # machine-readable preview document
949
+ rea upgrade --check --no-diff # paths + counts only (large repos)
950
+ rea upgrade # interactive
951
+ rea upgrade -y # non-interactive, keep drifted files
952
+ rea upgrade --force # non-interactive, overwrite drift
871
953
  ```
872
954
 
955
+ `--check` and `--dry-run` are distinct:
956
+
957
+ - `--dry-run` rehearses the full interactive flow with writes
958
+ suppressed (prompts still fire, output streams in classification
959
+ order). Useful locally to walk through the same prompts you'd see
960
+ during a real upgrade.
961
+ - `--check` is the structured, non-interactive preview: emits a
962
+ summary table + unified diffs per modified file, exits 0
963
+ regardless of what would change. The shape mirrors
964
+ `terraform plan` / `npm install --dry-run` — wire it into CI to
965
+ surface the changes an upgrade PR would produce.
966
+
873
967
  ### `rea serve`
874
968
 
875
969
  Start the MCP gateway. Invoked by Claude Code via `.mcp.json`; not a
@@ -908,27 +1002,50 @@ rea status --json # pipe to jq
908
1002
 
909
1003
  ### `rea doctor`
910
1004
 
911
- Validate the install — policy parses, `.rea/` layout, hooks, Codex plugin
912
- presence, TOFU fingerprint store.
1005
+ Validate the install — policy parses, `.rea/` layout, hooks, Codex
1006
+ plugin presence, TOFU fingerprint store, husky stub indirection,
1007
+ and the four-tier policy reader ladder (`rea hook policy-get` →
1008
+ `python3 + PyYAML` → `awk` → optional `jq`).
913
1009
 
914
1010
  ```bash
915
1011
  rea doctor
916
1012
  rea doctor --metrics # also print 7-day Codex telemetry summary
917
1013
  rea doctor --drift # report drift vs. install manifest (read-only)
1014
+ rea doctor --smoke # exercise delegation-capture hook end-to-end
1015
+ rea doctor --strict # 0.30.0 — promote settings-schema warnings to fail
918
1016
  ```
919
1017
 
920
- In non-git directories the commit-msg and pre-push checks are skipped
921
- cleanly. Audit hash-chain integrity is verified by `rea audit verify`,
922
- not by `rea doctor`.
1018
+ In non-git directories the commit-msg and pre-push checks are
1019
+ skipped cleanly. Audit hash-chain integrity is verified by `rea
1020
+ audit verify`, not by `rea doctor`. Each policy-reader tier is
1021
+ probed independently so silent flow-form no-ops (e.g. stale dist +
1022
+ missing PyYAML, with awk handling block-form only) surface at
1023
+ install time instead of at first hook fire.
923
1024
 
924
- ### `rea audit rotate` / `rea audit verify`
1025
+ ### `rea audit rotate` / `rea audit verify` / `rea audit summary` / `rea audit specialists`
925
1026
 
926
1027
  ```bash
927
1028
  rea audit rotate # force rotation now
928
1029
  rea audit verify # re-hash the chain; exit 1 on first tamper
929
1030
  rea audit verify --since <file> # walk forward from a rotated file
1031
+
1032
+ rea audit summary # 0.41.0 — counts by tool/tier/session/status
1033
+ rea audit summary --since 24h # filter to last 24h (units: s/m/h/d/w)
1034
+ rea audit summary --since 7d --json # machine-readable rollup for jq
1035
+
1036
+ rea audit specialists # delegation-telemetry roll-up
1037
+ rea audit specialists --session all # show every session (default: $CLAUDE_SESSION_ID)
1038
+ rea audit specialists --since <file> # extend the walk through rotated files
930
1039
  ```
931
1040
 
1041
+ `rea audit summary` is the high-level overview reader: total events,
1042
+ counts grouped by `tool_name` / tier / status / session, the time
1043
+ window covered, and a sample-verified chain-integrity check. Note
1044
+ that `--since` for `summary` is a duration (`24h`, `7d`) — distinct
1045
+ from `--since <rotated-file>` on `verify` / `specialists` which
1046
+ anchors on a rotated-audit basename. Use `rea audit verify` for the
1047
+ rigorous per-record re-hash; `summary` only samples.
1048
+
932
1049
  ### `rea tofu list` / `rea tofu accept`
933
1050
 
934
1051
  ```bash
@@ -0,0 +1,160 @@
1
+ /**
2
+ * `rea audit summary` — high-level audit-log overview (0.41.0).
3
+ *
4
+ * The audit log is rich and `rea audit specialists` already exists for
5
+ * one narrow event-class. `rea audit summary` complements it with a
6
+ * broad rollup: total events, counts by `tool_name`, by tier, by
7
+ * session, by status, the time window covered, and a sample-verified
8
+ * chain-integrity check.
9
+ *
10
+ * # Filtering
11
+ *
12
+ * `--since <duration>` accepts a compact duration string
13
+ * (`s`/`m`/`h`/`d`/`w`) and filters records by `timestamp >= now -
14
+ * duration`. Examples: `24h`, `7d`, `90m`, `2w`. This is DIFFERENT
15
+ * from `rea audit verify --since <file>` / `rea audit specialists
16
+ * --since <file>` which take a rotated-file ANCHOR, not a duration —
17
+ * the two `--since` semantics serve different needs (anchor for chain
18
+ * walks, duration for summarization) and we accept the surface area
19
+ * cost. The duration form is what consumers reach for when asking
20
+ * "what happened in the last day?".
21
+ *
22
+ * # Chain integrity
23
+ *
24
+ * `rea audit verify` does the rigorous per-record re-hash. `summary`
25
+ * samples up to `CHAIN_SAMPLE_SIZE` records, evenly spaced through
26
+ * the filtered window, and reports `ok` / `tampered` / `unsampled`
27
+ * (window empty). Operators who suspect tampering should still run
28
+ * `rea audit verify` for an authoritative answer.
29
+ *
30
+ * # JSON output
31
+ *
32
+ * {
33
+ * "schema_version": 1,
34
+ * "window_seconds": 86400,
35
+ * "window_start": "2026-05-15T13:42:00Z",
36
+ * "window_end": "2026-05-16T13:42:00Z",
37
+ * "files_scanned": ["/abs/path/.rea/audit.jsonl"],
38
+ * "total_events": 1247,
39
+ * "by_tool_name": { "Bash": 612, "Edit": 289, … },
40
+ * "by_tier": { "read": 683, "write": 416, "destructive": 148 },
41
+ * "by_status": { "allowed": 1242, "denied": 5, "error": 0 },
42
+ * "by_session": { "session-abc…": 312, "session-def…": 935 },
43
+ * "session_count": 8,
44
+ * "earliest_timestamp": "2026-05-15T13:43:01.103Z",
45
+ * "latest_timestamp": "2026-05-16T13:41:57.842Z",
46
+ * "chain_integrity": "ok",
47
+ * "chain_samples_verified": 12
48
+ * }
49
+ *
50
+ * # Walk scope
51
+ *
52
+ * v1 walks the current `.rea/audit.jsonl` plus EVERY rotated file
53
+ * whose latest record falls within the window. Older rotated files
54
+ * are skipped — they cannot contain in-window records. When `--since`
55
+ * is omitted, no time filter is applied and the walk covers the
56
+ * current `audit.jsonl` only (operators wanting historical depth
57
+ * should pass `--since <DUR>`).
58
+ */
59
+ import type { Command } from 'commander';
60
+ export declare const AUDIT_SUMMARY_SCHEMA_VERSION = 1;
61
+ /** Hard cap on chain-integrity samples. Keeps `rea audit summary`
62
+ * fast even on large logs while still surfacing obvious tampering. */
63
+ export declare const CHAIN_SAMPLE_SIZE = 12;
64
+ /**
65
+ * Thrown by `computeAuditSummary` when `--since` cannot be parsed.
66
+ * The commander wrapper exits 1.
67
+ */
68
+ export declare class AuditSummarySinceError extends Error {
69
+ constructor(message: string);
70
+ }
71
+ /** Three-state classification for chain integrity. */
72
+ export type ChainIntegrity = 'ok' | 'tampered' | 'unsampled';
73
+ export interface AuditSummaryResult {
74
+ schema_version: typeof AUDIT_SUMMARY_SCHEMA_VERSION;
75
+ /** Window length in seconds. `null` when no `--since` filter was set. */
76
+ window_seconds: number | null;
77
+ /** Inclusive window start. `null` when no filter; otherwise the
78
+ * computed cutoff (now - duration). */
79
+ window_start: string | null;
80
+ /** Window end — always `now` when filter is set; `null` otherwise. */
81
+ window_end: string | null;
82
+ /** Absolute paths of audit files walked. */
83
+ files_scanned: string[];
84
+ /**
85
+ * 0.42.0 codex round 4 P2 + round 6 P2 (2026-05-16) — reserved for
86
+ * future use; ALWAYS EMPTY in 0.42.0. The original intent (round 4)
87
+ * was to soft-skip rotated segments that the operator could not
88
+ * read (e.g. EACCES/EPERM after a backup restore). Round 6 showed
89
+ * the soft-skip was unsound: without per-segment time-range
90
+ * metadata we cannot prove a skipped file is out-of-scope for the
91
+ * `--since` window, so a silent skip risks an undercount + a
92
+ * misleading `chain_integrity: ok`. The current implementation
93
+ * therefore throws on any non-ENOENT read error; this field is
94
+ * kept in the public schema so a future release that ships
95
+ * per-segment time-range metadata can populate it without breaking
96
+ * JSON consumers.
97
+ */
98
+ unreadable_segments: string[];
99
+ total_events: number;
100
+ by_tool_name: Record<string, number>;
101
+ by_tier: Record<string, number>;
102
+ by_status: Record<string, number>;
103
+ by_session: Record<string, number>;
104
+ session_count: number;
105
+ /** Earliest in-window `timestamp` seen. `null` when no records. */
106
+ earliest_timestamp: string | null;
107
+ /** Latest in-window `timestamp` seen. `null` when no records. */
108
+ latest_timestamp: string | null;
109
+ chain_integrity: ChainIntegrity;
110
+ /** Number of samples actually verified. Always `<= CHAIN_SAMPLE_SIZE`. */
111
+ chain_samples_verified: number;
112
+ }
113
+ export interface ComputeAuditSummaryOptions {
114
+ /** Override CWD. Tests set this; production uses `process.cwd()`. */
115
+ baseDir?: string;
116
+ /** Raw `--since` value (e.g. `24h`, `7d`). Parsed via parseDuration. */
117
+ since?: string;
118
+ /** Test seam — pin "now" for deterministic window calculations. */
119
+ now?: Date;
120
+ }
121
+ /**
122
+ * Parse a compact duration string into seconds. Accepts:
123
+ *
124
+ * - `<N>s` — seconds
125
+ * - `<N>m` — minutes
126
+ * - `<N>h` — hours
127
+ * - `<N>d` — days
128
+ * - `<N>w` — weeks (7 days)
129
+ *
130
+ * `N` must be a positive integer with no whitespace. Returns the
131
+ * number of seconds; throws `AuditSummarySinceError` on parse failure.
132
+ *
133
+ * We deliberately do not accept bare numbers (would be ambiguous) or
134
+ * fractional units (no real use case; complicates rendering).
135
+ */
136
+ export declare function parseDurationSeconds(raw: string): number;
137
+ /**
138
+ * Compute the summary. Pure (read-only). Throws
139
+ * `AuditSummarySinceError` on bad `--since`; everything else is
140
+ * surfaced via the result.
141
+ */
142
+ export declare function computeAuditSummary(options?: ComputeAuditSummaryOptions): Promise<AuditSummaryResult>;
143
+ /**
144
+ * Render the result as a human-readable terminal block. Designed for
145
+ * the default `rea audit summary` invocation; `--json` callers bypass
146
+ * this entirely.
147
+ */
148
+ export declare function renderAuditSummary(result: AuditSummaryResult): string;
149
+ export interface RunAuditSummaryOptions {
150
+ since?: string;
151
+ json?: boolean;
152
+ /** Test seam — pin "now". */
153
+ now?: Date;
154
+ }
155
+ /** Commander entrypoint. */
156
+ export declare function runAuditSummary(options: RunAuditSummaryOptions): Promise<void>;
157
+ /**
158
+ * Register `rea audit summary` under the `audit` command group.
159
+ */
160
+ export declare function registerAuditSummaryCommand(auditCommand: Command): void;