@compr/opscontext-mcp 2.8.4 → 2.9.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/CHANGELOG.md CHANGED
@@ -4,6 +4,68 @@ All notable changes to OpsContext for AI Agents (previously ContextEngine — MC
4
4
 
5
5
  > Entries for 2.2.0 through 2.4.0 were not backfilled here; see `docs/sessions/SESSION_19` through `SESSION_21` for those releases.
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [2.9.0] 2026-09-25: credentials and prompt words stay out of the audit log
10
+
11
+ ### Security
12
+
13
+ - **The public repo is now a release copy** (`scripts/release-public.sh`, LOCK
14
+ `[PUBLIC_REPO_ONLY_FROM_RELEASE_SCRIPT]`). The working repo goes private; the public one
15
+ receives only the code, tests, README, licence and change list, checked for forbidden paths
16
+ and scanned for credential shapes before every push. The licence server code is no longer public.
17
+
18
+ - **Captured text is redacted before it reaches the audit log** (`src/secret-shapes.ts`,
19
+ `src/http-server.ts`, LOCK `[CAPTURE-IS-REDACTED-AT-THE-DOOR]`). The Claude Code hook sent the
20
+ first 4,000 characters of every prompt and 200 of every command unfiltered. Every capture event
21
+ now passes one set of credential shapes (vendor keys, passwords inside URLs, `sshpass -p`,
22
+ `mysql -p`, `Bearer`, `curl -u`, `--password`, `name = value`) at the one receiver, from every
23
+ surface.
24
+ - **`contextengine audit-scrub [--apply --reason "..."]`** removes the same shapes from records
25
+ written before, and acknowledges every rewrite on the chain, so `audit-verify` still passes
26
+ (LOCK `[SCRUB-IS-ACKNOWLEDGED-REDACTION]`). Dry run by default, idempotent.
27
+ - **The search index never holds a credential** (LOCK `[INDEX-NEVER-SERVES-A-CREDENTIAL]`): every
28
+ chunk is redacted as the index is built, including dotenv-derived chunks, whose masking had
29
+ skipped the password inside a database URL.
30
+ - **Prompts and AI responses are no longer stored as text** (LOCK `[PROMPT-TEXT-IS-NOT-KEPT]`):
31
+ the audit log keeps their length and a keyed fingerprint, so an exact repeat is still caught;
32
+ commands keep their redacted text. `OPSCONTEXT_KEEP_PROMPT_TEXT=1` restores the old behaviour.
33
+ - **The Claude Code hook keeps the prompt and the receiver's secret off curl's command line**,
34
+ where every program on the machine could read them (LOCK `[HOOK-KEEPS-PROMPT-AND-SECRET-OFF-ARGV]`).
35
+ Existing installs get it with `contextengine install-claude-hook`.
36
+
37
+ ### Fixed
38
+
39
+ - **`contextengine servers` reported a server as current when only a module other than the entry
40
+ script had changed** (LOCK `[BUILD-HASH-COVERS-EVERY-MODULE]`): the build fingerprint now covers
41
+ every `.js` file of the build.
42
+
43
+ - **Two overlapping rotations could erase archived history** (`src/audit.ts`, LOCKs
44
+ `[ROTATION-HOLDS-THE-LOCK-BEFORE-IT-PLANS]` and `[SEGMENT-IS-NEVER-OVERWRITTEN]`). Only the
45
+ automatic rotation took the rotate lock; the manual `audit-rotate` did not. On 2026-09-15 two
46
+ rotations both wrote `audit-0020.jsonl`, the second replaced the first, and 51,174 records
47
+ vanished (`audit-verify`: one orphan). Every rotation now takes the lock before it plans,
48
+ numbers its segment from the highest existing one, puts it in place without ever replacing a
49
+ file, and stops if the live log changed under it. Replayed in two real processes: the old
50
+ build erased 200,000 of 300,000 records; the new one lost nothing at any timing, even with its
51
+ lock broken.
52
+
53
+ ### Added
54
+
55
+ - **`contextengine audit-restore <file> [--apply --reason "..."]`** (LOCK
56
+ `[RESTORE-ONLY-CLOSES-A-PROVEN-GAP]`). Puts a lost block of records back from a backup, only
57
+ into a gap `audit-verify` reports, only at a segment boundary, and only if the block is the
58
+ original: strictly linear, every hash valid, joined to both sides of the gap. Dry run by
59
+ default; `--apply` re-verifies, removes its own segment unless the chain improved by exactly
60
+ that gap, and records an `audit.restore` event. The 51,174 records above were restored from a
61
+ Time Machine snapshot with it, and the chain verifies again.
62
+
63
+ ### Docs
64
+
65
+ - **The npm page matches the new behaviour**: Browser Capture and the Claude Code step no longer
66
+ say prompts are stored as text, the privacy table says what the package version is used for
67
+ now, and every link points to the public repo `FASTPROD/opscontext-mcp`.
68
+
7
69
  ## [2.8.4] 2026-09-17: a gate on complexity, a watch on doubled hooks
8
70
 
9
71
  ### Added
package/README.md CHANGED
@@ -12,7 +12,7 @@ Claude Code, Cursor, and Copilot write code without seeing your servers — so t
12
12
 
13
13
  OpsContext is an [MCP](https://modelcontextprotocol.io) server. It runs locally, snapshots your live infra (PM2 processes, nginx config, Docker containers, git status, cron jobs, redacted env), and exposes it via tools your AI coding agents (Claude Code, Cursor, Copilot, Windsurf, OpenClaw) can call in real time. Everything stays on your machine — no telemetry, no code uploads.
14
14
 
15
- > **🌐 Browser Capture (Phase 1, shipped 2026-06):** OpsContext now captures prompts + assistant responses + tool calls from **Claude.ai**, **ChatGPT.com**, *and* your **Claude Code** terminal sessions into the same hash-chained audit log. Cross-surface drift detection becomes possible (e.g. catch when a model says one thing in the browser and another in the terminal). See [Step 3](#3-capture-browser--claude-code-events-optional) below.
15
+ > **🌐 Browser Capture (Phase 1, shipped 2026-06):** OpsContext now records prompts, assistant responses and tool calls from **Claude.ai**, **ChatGPT.com**, *and* your **Claude Code** terminal sessions in the same hash-chained audit log. Since 2.9.0 a prompt or a response is kept as its length and a keyed fingerprint, never its words; commands are kept with credentials redacted. Cross-surface drift detection becomes possible (e.g. catch when a model says one thing in the browser and another in the terminal). See [Step 3](#3-capture-browser--claude-code-events-optional) below.
16
16
 
17
17
  ## Why
18
18
 
@@ -35,8 +35,8 @@ Plus the persistent-memory + search features carried forward from the contexteng
35
35
  - ⚡ **Instant startup** — keyword search ready immediately, embeddings load in background
36
36
  - 💾 **Session Persistence** — AI agents can save/restore context across conversations
37
37
  - 💡 **Learning Store** — permanent operational rules that auto-surface in search results
38
- - �️ **Protocol Firewall** — progressive enforcement that ensures agents commit, document, and save learnings
39
- - �🔌 **Plugin Adapters** — extend with custom data sources (Notion, Jira, RSS, etc.)
38
+ - 🛡️ **Protocol Firewall** — progressive enforcement that ensures agents commit, document, and save learnings
39
+ - 🔌 **Plugin Adapters** — extend with custom data sources (Notion, Jira, RSS, etc.)
40
40
  - 🧩 **MCP native** — works with any MCP-compatible client (VS Code, Claude, Cursor, OpenClaw)
41
41
 
42
42
  ### What OpsContext is NOT
@@ -167,7 +167,7 @@ npm i -g @compr/opscontext-mcp && opscontext install-claude-hook
167
167
 
168
168
  (Prefer the global install here: the hook scripts keep absolute paths to the CLI, and an `npx` cache copy can be pruned.)
169
169
 
170
- Adds `UserPromptSubmit`, `PostToolUse`, and `SessionStart` hook entries to `~/.claude/settings.json` so every Claude Code prompt + tool call lands in the same audit log as the browser events, plus a `Stop` entry: the **session gate** (2.7.0). A Claude Code turn cannot end while the repo's OpsContext session is older than the last commit; the agent is told which session to save, which session doc to update, and how far the agent docs are behind. No more "did you save the session?" at the end of a day. Details: `npx @compr/opscontext-mcp session-gate --help`.
170
+ Adds `UserPromptSubmit`, `PostToolUse`, and `SessionStart` hook entries to `~/.claude/settings.json` so every Claude Code prompt (kept as a length and a keyed fingerprint, not its words) and tool call (credentials redacted) lands in the same audit log as the browser events, plus a `Stop` entry: the **session gate** (2.7.0). A Claude Code turn cannot end while the repo's OpsContext session is older than the last commit; the agent is told which session to save, which session doc to update, and how far the agent docs are behind. No more "did you save the session?" at the end of a day. Details: `npx @compr/opscontext-mcp session-gate --help`.
171
171
 
172
172
  Verify:
173
173
  ```bash
@@ -539,7 +539,7 @@ Everything happens locally — search, scoring, learnings, sessions, embeddings.
539
539
  | License key (`CE-XXXX-...`) | Activation + daily heartbeat | Validate subscription |
540
540
  | Machine ID (SHA-256 hash) | Activation + daily heartbeat | Enforce machine limit |
541
541
  | Email | Activation only | Tie the licence to an account |
542
- | Package version | Activation only | Serve a compatible module bundle |
542
+ | Package version | Activation only | Recorded with your activation, so support knows which version a machine runs |
543
543
  | Platform/arch (e.g., `darwin/arm64`) | Activation only | Compatibility check |
544
544
  | Licence bundle version | Daily heartbeat | Compatibility marker carried in the signed licence |
545
545
 
@@ -553,7 +553,7 @@ That is the complete list. The activation request sends exactly six fields and t
553
553
 
554
554
  One file in the published package is deliberately unreadable: `dist/rubric.js`, which holds the scoring thresholds (what earns which points). Those values are commercial IP under [BSL-1.1](LICENSE), and knowing them exactly makes an AI-readiness score easy to game by padding files to hit a number rather than doing the work.
555
555
 
556
- **What that hides: values. What it does not hide: behaviour.** No code path, network call, file access, or data flow is concealed anywhere in this package. The scoring logic itself, every collector, the search ranker, and both network calls above ship as readable JavaScript — and the full source is public at [FASTPROD/ContextEngine](https://github.com/FASTPROD/ContextEngine). If a privacy claim on this page were false, the code that broke it would be right there to find.
556
+ **What that hides: values. What it does not hide: behaviour.** No code path, network call, file access, or data flow is concealed anywhere in this package. The scoring logic itself, every collector, the search ranker, and both network calls above ship as readable JavaScript — and the full source is public at [FASTPROD/ContextEngine](https://github.com/FASTPROD/opscontext-mcp). If a privacy claim on this page were false, the code that broke it would be right there to find.
557
557
 
558
558
  ### Why this matters
559
559
 
@@ -573,19 +573,19 @@ For commercial licensing: [yannick@compr.ch](mailto:yannick@compr.ch)
573
573
 
574
574
  ## Publisher
575
575
 
576
- **OpsContext is built by PROD LLC**, an operating brand of **[CSS LLC](https://compr.fr)** — a Swiss company incorporated in 2005.
576
+ **OpsContext is built by PROD LLC**, an operating brand of **CSS LLC** (Cross Stream Solutions Sàrl), a Swiss company incorporated in 2005. The engineering team works under the FASTPROD name, which is also the GitHub organisation hosting this repository.
577
577
 
578
578
  The VS Code Marketplace lists the extension under the legal-parent publisher ID `css-llc`; the npm package is published under the `@compr` scope. Both belong to the same entity.
579
579
 
580
- PROD LLC also operates these product brands:
580
+ PROD LLC also operates these products. The full, current list is on **[compr.fr](https://compr.fr)**.
581
581
 
582
- | Brand | What it does | Site |
582
+ | Product | What it does | Site |
583
583
  |---|---|---|
584
- | **FASTPROD** | DevOps + sysadmin operator (the team behind this project) | [fast-prod.com](https://fast-prod.com) |
585
- | **CROWLR** | Crawling + monitoring platform | [crowlr.io](https://crowlr.io) · [admin.crowlr.com](https://admin.crowlr.com) |
586
- | **KONIVE** | (product) | [konive.com](https://konive.com) |
587
- | **INVOC** | (product) | [invoc.io](https://invoc.io) · [invoc.me](https://invoc.me) |
588
- | **PLANK** | (product) | [plank.io](https://plank.io) |
589
- | **compR** | Portfolio + benchmark widget | [compr.fr](https://compr.fr) · [compr.app](https://compr.app) |
590
-
591
- Contact: [yannick@compr.ch](mailto:yannick@compr.ch). Full corporate disclosure at [docs/about.md](docs/about.md).
584
+ | **CROWLR** | Recruitment software: applicant tracking for companies, a job app for candidates, live event sensing | [admin.crowlr.com](https://admin.crowlr.com) · [app.crowlr.com](https://app.crowlr.com) · [crowlr.io](https://www.crowlr.io) |
585
+ | **KONIVE** | AI career agent: salary negotiation and job matching | [konive.com](https://konive.com) |
586
+ | **INVOC** | Grocery scanner app for shoppers, brand monitoring for food companies | [invoc.io](https://invoc.io) |
587
+ | **INVOC.me** | Demand forecasting shared by operations, sales and finance | [invoc.me](https://invoc.me) |
588
+ | **PLANK** | Hyperlocal social app for iOS and Android | [plank.io](https://plank.io) |
589
+ | **compR** | Company site, and candidate credibility scoring | [compr.fr](https://compr.fr) · [compr.app](https://compr.app) |
590
+
591
+ Contact: [yannick@compr.ch](mailto:yannick@compr.ch). Full corporate disclosure at [docs/about.md](https://github.com/FASTPROD/opscontext-mcp/blob/main/docs/about.md).
@@ -91,10 +91,18 @@ esac
91
91
  [ -n "$PAYLOAD" ] || exit 0
92
92
 
93
93
  # POST with 1s hard timeout. Any error → silent (curl >/dev/null 2>&1, exit 0).
94
- curl -sS --max-time 1.0 \
94
+ # [LOCKED] [HOOK-KEEPS-PROMPT-AND-SECRET-OFF-ARGV] - 2026-09-25
95
+ # [NEVER] put $PAYLOAD or $SECRET in curl's arguments (`--data "..."`, `-H "...: $SECRET"`).
96
+ # WHY: a process's arguments are readable by every program on the machine (`ps`), for as long
97
+ # as curl runs, up to the 1 s timeout. Until 2.8.4 each prompt (up to 4,000 characters)
98
+ # and the receiver's shared secret rode there.
99
+ # FIX: printf is a shell builtin, so it starts no process: it pipes the body into curl's
100
+ # standard input (--data-binary @-), and writes the header into a process-substitution
101
+ # file (-H @file, curl 7.55 and later), which only curl reads.
102
+ printf '%s' "{\"events\":[$PAYLOAD]}" | curl -sS --max-time 1.0 \
95
103
  -H "Content-Type: application/json" \
96
- -H "X-OpsContext-Secret: $SECRET" \
97
- --data "{\"events\":[$PAYLOAD]}" \
104
+ -H @<(printf 'X-OpsContext-Secret: %s\n' "$SECRET") \
105
+ --data-binary @- \
98
106
  "$ENDPOINT" >/dev/null 2>&1
99
107
 
100
108
  exit 0
package/dist/audit.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type AuditEvent = "learning.save" | "learning.delete" | "learning.store_unreadable" | "learning.store_shrink_refused" | "learning.store_growth_refused" | "server.start" | "server.role" | "index.write" | "learning.import" | "learning.export" | "session.save" | "session.delete" | "activation.activate" | "activation.deactivate" | "activation.heartbeat" | "activation.signature_reject" | "activation.legacy_signature" | "firewall.escalate" | "hook.block" | "hook.bypass" | "policy.skipped" | "browser.prompt" | "browser.response" | "browser.tool_call" | "browser.session_start" | "browser.session_end" | "browser.capture_miss" | "vscode.prompt_submit" | "vscode.tool_call" | "vscode.session_start" | "drift.detected" | "notification.fired" | "community.sync_ok" | "community.sync_error" | "audit.rotate" | "audit.redact";
1
+ export type AuditEvent = "learning.save" | "learning.delete" | "learning.store_unreadable" | "learning.store_shrink_refused" | "learning.store_growth_refused" | "server.start" | "server.role" | "index.write" | "learning.import" | "learning.export" | "session.save" | "session.delete" | "activation.activate" | "activation.deactivate" | "activation.heartbeat" | "activation.signature_reject" | "activation.legacy_signature" | "firewall.escalate" | "hook.block" | "hook.bypass" | "policy.skipped" | "browser.prompt" | "browser.response" | "browser.tool_call" | "browser.session_start" | "browser.session_end" | "browser.capture_miss" | "vscode.prompt_submit" | "vscode.tool_call" | "vscode.session_start" | "drift.detected" | "notification.fired" | "community.sync_ok" | "community.sync_error" | "audit.rotate" | "audit.redact" | "audit.restore";
2
2
  export interface AuditRecord {
3
3
  ts: string;
4
4
  event: AuditEvent;
@@ -26,11 +26,17 @@ export interface RotationPlan {
26
26
  segmentFile: string | null;
27
27
  /** Set when the rotation must not run, with the reason. */
28
28
  refusedReason: string | null;
29
+ /** Hash of the first live record and the live record count the plan was computed on. The
30
+ * rotation only slices a log that still starts there. [LOCK] [ROTATION-HOLDS-THE-LOCK-BEFORE-IT-PLANS] */
31
+ firstLiveHash?: string | null;
32
+ liveCount?: number;
29
33
  }
30
34
  export interface RotationResult extends RotationPlan {
31
35
  rotated: boolean;
32
36
  bytesArchived: number;
33
37
  bytesRemaining: number;
38
+ /** True when another rotation held the rotate lock, so nothing was read or written. */
39
+ inProgress?: boolean;
34
40
  }
35
41
  export interface RotateOptions {
36
42
  /** Archive records older than this many days. Minimum 1. */
@@ -64,6 +70,20 @@ export declare function planRotation(opts?: RotateOptions): RotationPlan;
64
70
  * Refuses to run on a chain that does not currently verify: rotating a log with altered
65
71
  * or orphaned records would bake the damage into an append-only segment and make the
66
72
  * cause unrecoverable. Forks are fine — they are concurrency, not tampering.
73
+ *
74
+ * [LOCKED] [ROTATION-HOLDS-THE-LOCK-BEFORE-IT-PLANS] - 2026-09-24
75
+ * [NEVER] let any caller rotate without the rotate lock, and never plan (choose the segment
76
+ * name, count the slice) before holding it.
77
+ * WHY: on 2026-09-15 at 12:44:46Z and 12:44:51Z two rotations both wrote audit-0020.jsonl. The
78
+ * manual `audit-rotate` command called this function directly, and only autoRotateAuditLog()
79
+ * took the lock. The second run planned while the first was mid-write (same segment name),
80
+ * then applied its old count to the live log the first run had already cut: it archived the
81
+ * first run's remainder into the same file name and erased the first run's segment. 51,174
82
+ * records vanished; verifyChain() reported an orphan. They were put back on 2026-09-24 from a
83
+ * Time Machine local snapshot with restoreSegment().
84
+ * FIX: this function takes the lock first and plans under it, so the manual command, auto-rotation
85
+ * and anything added later all go through one lock. The slice is refused unless the live log
86
+ * still starts with the record the plan read. A held lock returns inProgress, untouched.
67
87
  */
68
88
  export declare function rotateAuditLog(opts?: RotateOptions): RotationResult;
69
89
  /**
@@ -81,6 +101,8 @@ export declare function rotateAuditLog(opts?: RotateOptions): RotationResult;
81
101
  * rotation buys ~a day of quiet. A dedicated rotate lock (O_EXCL, stale after 10 min,
82
102
  * long enough to verify a 500k-record chain) makes late starters return "in progress"
83
103
  * without touching the log. Opt out with CONTEXTENGINE_AUTO_ROTATE=0.
104
+ * 2026-09-24: the lock is now taken inside rotateAuditLog(), not here, because the manual
105
+ * command bypassed it. [LOCK] [ROTATION-HOLDS-THE-LOCK-BEFORE-IT-PLANS]
84
106
  */
85
107
  export declare const AUTO_ROTATE_TRIGGER: number;
86
108
  /** Count newline-terminated lines without parsing. The live log is small by construction. */
@@ -162,8 +184,94 @@ export declare function acknowledgeRedaction(indices: number[], reason: string,
162
184
  }>;
163
185
  record: AuditRecord | null;
164
186
  };
187
+ export interface RestorePlan {
188
+ /** Why the block cannot be restored; null when it fits. */
189
+ refusedReason: string | null;
190
+ records: number;
191
+ firstTs: string | null;
192
+ lastTs: string | null;
193
+ /** The segment the block goes right after. */
194
+ after: string | null;
195
+ /** The segment, or "audit.log", whose first record the block reconnects. */
196
+ before: string | null;
197
+ /** The name the restored segment gets. */
198
+ segmentFile: string | null;
199
+ /** History index, before the restore, of the orphan the block closes. */
200
+ orphanIndex: number | null;
201
+ }
202
+ export interface RestoreResult extends RestorePlan {
203
+ restored: boolean;
204
+ record: AuditRecord | null;
205
+ }
206
+ /**
207
+ * Put a lost block of records back into the archive, from a backup.
208
+ *
209
+ * [LOCKED] [RESTORE-ONLY-CLOSES-A-PROVEN-GAP] - 2026-09-24
210
+ * [NEVER] put records into the archive unless every record hashes correctly, each names the one
211
+ * before it as its parent, the block's first parent is the last record of the segment it
212
+ * follows, the record after the gap names the block's last record as its parent, and none
213
+ * of the block's records is already in the log. Together these admit only the original
214
+ * records: a substitute block would need a SHA-256 preimage.
215
+ * WHY: the archive is evidence. A restore that accepted any block would be a sanctioned way to
216
+ * rewrite history, the one thing the chain exists to make visible. First real use: the
217
+ * 51,174 records erased on 2026-09-15 (see [SEGMENT-IS-NEVER-OVERWRITTEN]), found whole in a
218
+ * Time Machine local snapshot on 2026-09-24.
219
+ * FIX: only a hole the verifier already reports can be filled, and only at a segment boundary.
220
+ * Dry run by default. Apply takes the rotate lock before it plans, never overwrites a file,
221
+ * re-verifies, removes its own segment unless the chain has exactly one orphan fewer and no
222
+ * new damage, and records itself as an `audit.restore` event with a reason.
223
+ */
224
+ export declare function restoreSegment(file: string, opts?: {
225
+ apply?: boolean;
226
+ reason?: string;
227
+ actor?: string;
228
+ }): RestoreResult;
229
+ export interface ScrubFileReport {
230
+ name: string;
231
+ records: number;
232
+ redacted: number;
233
+ }
234
+ export interface ScrubReport {
235
+ applied: boolean;
236
+ refusedReason: string | null;
237
+ files: ScrubFileReport[];
238
+ redactedRecords: number;
239
+ counts: Record<string, number>;
240
+ /** The audit.redact records appended to acknowledge the rewrite. */
241
+ acknowledgements: AuditRecord[];
242
+ }
243
+ type Redactor = (payload: Record<string, unknown>) => {
244
+ value: Record<string, unknown>;
245
+ counts: Record<string, number>;
246
+ changed: boolean;
247
+ };
248
+ /**
249
+ * Remove credentials from records already written, and acknowledge each rewrite on the chain.
250
+ *
251
+ * [LOCKED] [SCRUB-IS-ACKNOWLEDGED-REDACTION] - 2026-09-25
252
+ * [NEVER] rewrite a record's content without appending the audit.redact acknowledgement that
253
+ * binds its original hash to its new content, and never touch a record that is not a
254
+ * capture record or a line the redactor left unchanged.
255
+ * WHY: the credentials found on 2026-09-24 sat in 45 archived segments and the live log. Segments
256
+ * are never overwritten ([SEGMENT-IS-NEVER-OVERWRITTEN]); this is the one sanctioned
257
+ * exception, because leaving a live Stripe key or a database password in an evidence
258
+ * archive forever is worse than an acknowledged edit. Without the acknowledgement the
259
+ * verifier would, truthfully, call every scrubbed record altered.
260
+ * FIX: dry run by default. With apply: under the rotate lock (no rotation moves records while we
261
+ * rewrite), each segment is rewritten via temp file and rename only where a line changed,
262
+ * the live log under the append lock (appends wait, none is lost), then one audit.redact
263
+ * record per 100 rewrites names each original hash and its new content hash
264
+ * ([REDACTION-IS-A-CHAINED-RECORD]). Running it again changes nothing.
265
+ */
266
+ export declare function scrubAuditLog(opts: {
267
+ apply?: boolean;
268
+ reason?: string;
269
+ redact: Redactor;
270
+ actor?: string;
271
+ }): ScrubReport;
165
272
  export declare function filterByRange(records: AuditRecord[], since?: string, until?: string): AuditRecord[];
166
273
  export declare function toCsv(records: AuditRecord[]): string;
167
274
  export declare function resetCacheForTest(): void;
168
275
  export declare function safeAppend(event: AuditEvent, payload: Record<string, unknown>, actor?: string): void;
276
+ export {};
169
277
  //# sourceMappingURL=audit.d.ts.map