@agentguard-run/burn 0.2.7 → 0.3.1

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 (60) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +78 -0
  3. package/dist/src/adapters/codex.js +6 -0
  4. package/dist/src/canvas.d.ts +36 -0
  5. package/dist/src/canvas.js +132 -0
  6. package/dist/src/cli.d.ts +2 -0
  7. package/dist/src/cli.js +116 -25
  8. package/dist/src/defaults.d.ts +1 -1
  9. package/dist/src/defaults.js +4 -1
  10. package/dist/src/frames.d.ts +42 -0
  11. package/dist/src/frames.js +108 -0
  12. package/dist/src/gateway.js +3 -0
  13. package/dist/src/hook/pre-tool-use.d.ts +1 -0
  14. package/dist/src/hook/pre-tool-use.js +12 -1
  15. package/dist/src/idle/cache.d.ts +8 -0
  16. package/dist/src/idle/cache.js +70 -0
  17. package/dist/src/idle/classify.d.ts +13 -0
  18. package/dist/src/idle/classify.js +133 -0
  19. package/dist/src/idle/cli.d.ts +2 -0
  20. package/dist/src/idle/cli.js +72 -0
  21. package/dist/src/idle/collect.d.ts +38 -0
  22. package/dist/src/idle/collect.js +543 -0
  23. package/dist/src/idle/hook.d.ts +17 -0
  24. package/dist/src/idle/hook.js +61 -0
  25. package/dist/src/idle/platform.d.ts +25 -0
  26. package/dist/src/idle/platform.js +154 -0
  27. package/dist/src/idle/reap.d.ts +41 -0
  28. package/dist/src/idle/reap.js +262 -0
  29. package/dist/src/idle/render.d.ts +10 -0
  30. package/dist/src/idle/render.js +108 -0
  31. package/dist/src/idle/types.d.ts +56 -0
  32. package/dist/src/idle/types.js +8 -0
  33. package/dist/src/install.js +4 -2
  34. package/dist/src/live-panel.d.ts +42 -0
  35. package/dist/src/live-panel.js +283 -0
  36. package/dist/src/policy.d.ts +3 -1
  37. package/dist/src/policy.js +49 -3
  38. package/dist/src/presentation.d.ts +5 -0
  39. package/dist/src/presentation.js +23 -0
  40. package/dist/src/recording.d.ts +22 -0
  41. package/dist/src/recording.js +102 -0
  42. package/dist/src/render-recording.d.ts +15 -0
  43. package/dist/src/render-recording.js +200 -0
  44. package/dist/src/replay/render.js +5 -0
  45. package/dist/src/types.d.ts +6 -0
  46. package/docs/LIVE_PANEL_2026_09.md +55 -0
  47. package/docs/assets/burn-live-sep19-stop-1920.png +0 -0
  48. package/docs/assets/burn-live-sep19-stop-390.png +0 -0
  49. package/docs/assets/burn-live-sep19.evidence.json +288 -0
  50. package/docs/assets/burn-live-sep19.gif +0 -0
  51. package/docs/assets/burn-live-sep19.jsonl +45 -0
  52. package/docs/assets/burn-live-sep19.mp4 +0 -0
  53. package/docs/assets/burn-live-sep19.mp4.provenance.json +292 -0
  54. package/docs/assets/burn-live-sep19.observations.jsonl +45 -0
  55. package/docs/assets/burn-live-sep19.receipt.json +38 -0
  56. package/docs/burn-idle-audit.md +71 -0
  57. package/docs/burn-render.md +37 -0
  58. package/fixtures/live-panel-104x35.txt +35 -0
  59. package/fixtures/live-panel-80x24.txt +24 -0
  60. package/package.json +4 -2
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderCanvasPng = renderCanvasPng;
4
+ exports.renderRecording = renderRecording;
5
+ /** Offline PNG and video rendering from validated, recorded Canvas data. */
6
+ const node_fs_1 = require("node:fs");
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_child_process_1 = require("node:child_process");
10
+ const node_util_1 = require("node:util");
11
+ const node_path_1 = require("node:path");
12
+ const canvas_1 = require("@napi-rs/canvas");
13
+ const canvas_2 = require("./canvas");
14
+ const frames_1 = require("./frames");
15
+ const recording_1 = require("./recording");
16
+ const run = (0, node_util_1.promisify)(node_child_process_1.execFile);
17
+ const CELL_WIDTH = 18, CELL_HEIGHT = 28;
18
+ const FONT_ALIAS = 'BurnRecordingMono';
19
+ const FONT_PATHS = [
20
+ '/System/Library/Fonts/Menlo.ttc',
21
+ '/Library/Fonts/Menlo.ttc',
22
+ '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',
23
+ '/usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono.ttf',
24
+ '/usr/share/fonts/TTF/DejaVuSansMono.ttf',
25
+ ];
26
+ async function font() {
27
+ for (const path of FONT_PATHS) {
28
+ try {
29
+ await (0, promises_1.access)(path, node_fs_1.constants.R_OK);
30
+ if (canvas_1.GlobalFonts.registerFromPath(path, FONT_ALIAS) && canvas_1.GlobalFonts.has(FONT_ALIAS))
31
+ return path;
32
+ }
33
+ catch { /* Try the next installed local font. */ }
34
+ }
35
+ throw new Error('Recording render needs an installed Menlo or DejaVu Sans Mono font; no font was downloaded.');
36
+ }
37
+ async function executable(requested) {
38
+ const paths = (0, node_path_1.isAbsolute)(requested) || requested.includes('/') || requested.includes('\\')
39
+ ? [(0, node_path_1.resolve)(requested)] : (process.env.PATH || '').split(node_path_1.delimiter).filter(Boolean).map(path => (0, node_path_1.resolve)(path, requested));
40
+ for (const path of paths) {
41
+ try {
42
+ await (0, promises_1.access)(path, node_fs_1.constants.X_OK);
43
+ if ((await (0, promises_1.lstat)(path)).isFile() || (await (0, promises_1.lstat)(path)).isSymbolicLink())
44
+ return path;
45
+ }
46
+ catch { /* Not on this PATH entry. */ }
47
+ }
48
+ return null;
49
+ }
50
+ async function requireUnused(path) {
51
+ try {
52
+ await (0, promises_1.lstat)(path);
53
+ }
54
+ catch (error) {
55
+ if (error.code === 'ENOENT')
56
+ return;
57
+ throw error;
58
+ }
59
+ throw new Error(`Refusing to overwrite existing output: ${path}`);
60
+ }
61
+ /** For display only; execution always passes an argument array without a shell. */
62
+ function shellWord(value) { return `'${value.replace(/'/g, "'\\''")}'`; }
63
+ /** FFmpeg represents concat timestamps in microseconds; Date.parse truncates below milliseconds. */
64
+ function timestampMicros(at) {
65
+ const milliseconds = Date.parse(at);
66
+ if (!Number.isFinite(milliseconds))
67
+ throw new Error('Recording timestamps must be valid and ordered.');
68
+ const fraction = /\.(\d+)(?:Z|[+-]\d{2}:?\d{2})$/i.exec(at)?.[1] ?? '';
69
+ return BigInt(milliseconds) * 1000n + BigInt(fraction.slice(3, 6).padEnd(3, '0'));
70
+ }
71
+ async function renderCanvasPng(snapshot, width = 1920, height = 1080) {
72
+ await font();
73
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 64 || height < 64 || width > 4096 || height > 4096)
74
+ throw new Error('Canvas raster dimensions must be integers between 64 and 4096.');
75
+ const canvas = (0, canvas_1.createCanvas)(width, height);
76
+ const cellWidth = Math.min(CELL_WIDTH, Math.max(1, Math.floor((width - 32) / snapshot.width)));
77
+ const cellHeight = Math.min(CELL_HEIGHT, Math.max(1, Math.floor((height - 32) / snapshot.height)), Math.round(cellWidth * CELL_HEIGHT / CELL_WIDTH));
78
+ const paddingX = Math.floor((canvas.width - snapshot.width * cellWidth) / 2);
79
+ const paddingY = Math.floor((canvas.height - snapshot.height * cellHeight) / 2);
80
+ const ctx = canvas.getContext('2d');
81
+ ctx.fillStyle = '#0B1117';
82
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
83
+ ctx.font = `${26 * cellWidth / CELL_WIDTH}px "${FONT_ALIAS}"`;
84
+ ctx.textBaseline = 'alphabetic';
85
+ for (let row = 0; row < snapshot.height; row++)
86
+ for (let col = 0; col < snapshot.width; col++) {
87
+ const cell = snapshot.cells[row][col];
88
+ if (cell.char === ' ')
89
+ continue;
90
+ ctx.fillStyle = canvas_2.CANVAS_PALETTE[cell.color === 'none' ? 'white' : cell.color].hex;
91
+ const x = paddingX + col * cellWidth, y = paddingY + row * cellHeight;
92
+ if (cell.char === '█')
93
+ ctx.fillRect(x, y, cellWidth, cellHeight);
94
+ else if (cell.char === '▀')
95
+ ctx.fillRect(x, y, cellWidth, cellHeight / 2);
96
+ else if (cell.char === '▄')
97
+ ctx.fillRect(x, y + cellHeight / 2, cellWidth, cellHeight / 2);
98
+ else if (cell.char === '─')
99
+ ctx.fillRect(x, y + cellHeight / 2, cellWidth, 1);
100
+ else
101
+ ctx.fillText(cell.char, x + 1, y + 23 * cellHeight / CELL_HEIGHT, Math.max(1, cellWidth - 2));
102
+ }
103
+ return canvas.encode('png');
104
+ }
105
+ async function renderRecording(source, options) {
106
+ const sourcePath = (0, node_path_1.resolve)(source), mp4Path = (0, node_path_1.resolve)(options.mp4);
107
+ const gifPath = options.gif ? (0, node_path_1.resolve)(options.gif) : null;
108
+ const provenancePath = `${mp4Path}.provenance.json`;
109
+ for (const path of [sourcePath, mp4Path, provenancePath, ...(gifPath ? [gifPath] : [])]) {
110
+ if (/[\x00-\x1f\x7f]/.test(path))
111
+ throw new Error('Recording paths must not contain control characters.');
112
+ }
113
+ if (new Set([sourcePath, mp4Path, provenancePath, ...(gifPath ? [gifPath] : [])]).size !== (gifPath ? 4 : 3)) {
114
+ throw new Error('Recording source and each output must use different paths.');
115
+ }
116
+ for (const path of [mp4Path, provenancePath, ...(gifPath ? [gifPath] : [])])
117
+ await requireUnused(path);
118
+ const sourceStat = await (0, promises_1.stat)(sourcePath);
119
+ if (!sourceStat.isFile() || sourceStat.size > 128 * 1024 * 1024)
120
+ throw new Error('Recording must be a regular local file no larger than 128 MB.');
121
+ const sourceBytes = await (0, promises_1.readFile)(sourcePath);
122
+ const frames = await (0, recording_1.readRecording)(sourcePath);
123
+ if (!frames.length)
124
+ throw new Error('Recording contains no frames.');
125
+ const times = frames.map(frame => timestampMicros(frame.at));
126
+ const videoIndices = [];
127
+ for (let index = 0; index < frames.length; index++) {
128
+ if (index && times[index] < times[index - 1])
129
+ throw new Error('Recording timestamps must be valid and ordered.');
130
+ // No elapsed time exists between equal timestamps. Keep the latest state in video,
131
+ // while retaining every observation as its own PNG and provenance entry.
132
+ if (index === frames.length - 1 || times[index] !== times[index + 1])
133
+ videoIndices.push(index);
134
+ }
135
+ if (!sourceBytes.equals(await (0, promises_1.readFile)(sourcePath)))
136
+ throw new Error('Recording changed while it was being read; render the saved file again.');
137
+ const sourceHash = (0, node_crypto_1.createHash)('sha256').update(sourceBytes).digest('hex');
138
+ const fontPath = await font();
139
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(mp4Path), { recursive: true });
140
+ if (gifPath)
141
+ await (0, promises_1.mkdir)((0, node_path_1.dirname)(gifPath), { recursive: true });
142
+ const framesDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_path_1.dirname)(mp4Path), `${(0, node_path_1.basename)(mp4Path)}.frames-`));
143
+ const concatPath = (0, node_path_1.join)(framesDir, 'frames.ffconcat');
144
+ const lines = ['ffconcat version 1.0'];
145
+ for (let index = 0; index < frames.length; index++) {
146
+ const frame = frames[index];
147
+ const name = `frame-${String(index).padStart(6, '0')}.png`;
148
+ const image = (0, frames_1.renderFrame)(frame.data, { recorded: true, at: frame.at, width: frame.viewport?.width ?? 104, height: frame.viewport?.height ?? 35, colour: true });
149
+ await (0, promises_1.writeFile)((0, node_path_1.join)(framesDir, name), await renderCanvasPng(image.toJSON()), { flag: 'wx', mode: 0o600 });
150
+ }
151
+ for (const [position, index] of videoIndices.entries()) {
152
+ const next = videoIndices[position + 1];
153
+ const seconds = next === undefined ? 1 : Number(times[next] - times[index]) / 1_000_000;
154
+ lines.push(`file 'frame-${String(index).padStart(6, '0')}.png'`, 'option framerate 1000000', `duration ${seconds.toFixed(6)}`);
155
+ }
156
+ // The concat demuxer needs a terminal file to honor the final frame hold.
157
+ lines.push(`file 'frame-${String(frames.length - 1).padStart(6, '0')}.png'`, 'option framerate 1000000');
158
+ await (0, promises_1.writeFile)(concatPath, `${lines.join('\n')}\n`, { flag: 'wx', mode: 0o600 });
159
+ const ffmpeg = options.ffmpeg === null ? null : await executable(options.ffmpeg || 'ffmpeg');
160
+ const common = ['-hide_banner', '-loglevel', 'error', '-nostdin', '-n', '-protocol_whitelist', 'file,pipe', '-f', 'concat', '-safe', '0', '-i', concatPath];
161
+ // B-frame reordering can collapse the MP4 track duration to rapid DTS steps even
162
+ // when the final presentation timestamp correctly includes the one-second hold.
163
+ const mp4Args = [...common, '-fps_mode', 'vfr', '-c:v', 'libx264', '-bf', '0', '-enc_time_base', '1:1000000', '-video_track_timescale', '1000000', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', mp4Path];
164
+ const gifArgs = [...common, '-fps_mode', 'vfr', '-frames:v', String(videoIndices.length), '-final_delay', '100', '-vf', 'split[a][b];[a]palettegen[p];[b][p]paletteuse', '-loop', '0', ...(gifPath ? [gifPath] : [])];
165
+ const command = [ffmpeg || 'ffmpeg', ...mp4Args].map(shellWord).join(' ')
166
+ + (gifPath ? `\n${[ffmpeg || 'ffmpeg', ...gifArgs].map(shellWord).join(' ')}` : '');
167
+ const provenance = {
168
+ source_sha256: sourceHash,
169
+ renderer_version: require('../../package.json').version,
170
+ rendered_at: new Date().toISOString(),
171
+ font: { family: FONT_ALIAS, path: fontPath },
172
+ frame_count: frames.length,
173
+ frame_times: frames.map(frame => frame.at),
174
+ frame_viewports: frames.map(frame => frame.viewport ?? { width: 104, height: 35 }),
175
+ video_frame_indices: videoIndices,
176
+ video_timestamp_precision: 'microseconds; equal timestamps retain the last frame in video and every PNG',
177
+ final_hold_seconds: 1,
178
+ gif_timestamp_precision: 'centiseconds; terminal concat sentinel excluded; final displayed frame holds one second',
179
+ ffmpeg_command: command,
180
+ format: 'Recorded viewport (legacy 104x35), centered 18x28 pixel cells, 1920x1080 PNG',
181
+ video_status: ffmpeg ? 'rendering' : 'ffmpeg_unavailable_png_frames_written',
182
+ };
183
+ await (0, promises_1.writeFile)(provenancePath, `${JSON.stringify(provenance, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
184
+ if (ffmpeg) {
185
+ try {
186
+ await run(ffmpeg, mp4Args, { timeout: 300_000, maxBuffer: 1024 * 1024 });
187
+ if (gifPath)
188
+ await run(ffmpeg, gifArgs, { timeout: 300_000, maxBuffer: 1024 * 1024 });
189
+ provenance.video_status = 'complete';
190
+ }
191
+ catch (error) {
192
+ provenance.video_status = 'failed_png_frames_written';
193
+ throw new Error(`Video encoding failed; PNG frames remain at ${framesDir}: ${error.message}`);
194
+ }
195
+ finally {
196
+ await (0, promises_1.writeFile)(provenancePath, `${JSON.stringify(provenance, null, 2)}\n`, { mode: 0o600 });
197
+ }
198
+ }
199
+ return { framesDir, provenancePath, mp4: ffmpeg ? mp4Path : null, gif: ffmpeg && gifPath ? gifPath : null, ffmpegCommand: command, frameCount: frames.length };
200
+ }
@@ -10,6 +10,7 @@ exports.comparison = comparison;
10
10
  exports.renderReplay = renderReplay;
11
11
  exports.renderStop = renderStop;
12
12
  exports.renderSessionRow = renderSessionRow;
13
+ const frames_1 = require("../frames");
13
14
  const evaluate_1 = require("../detectors/evaluate");
14
15
  const C = {
15
16
  reset: '\x1b[0m',
@@ -117,6 +118,8 @@ function scenarioUsd(tokens, cacheShare) {
117
118
  }
118
119
  function renderReplay(summary, opts = {}) {
119
120
  const on = opts.colour ?? Boolean(process.stdout.isTTY);
121
+ if (on)
122
+ return (0, frames_1.renderFrame)({ kind: 'replay', summary }, { colour: true }).render();
120
123
  const top = opts.top ?? 6;
121
124
  const out = [];
122
125
  const cacheShare = summary.totalTokens
@@ -175,6 +178,8 @@ function renderReplay(summary, opts = {}) {
175
178
  // ---------------------------------------------------------------------------
176
179
  function renderStop(report, opts = {}) {
177
180
  const on = opts.colour ?? false;
181
+ if (on)
182
+ return (0, frames_1.renderFrame)({ kind: 'report', report, subject: opts.subject, outcome: 'blocked' }, { colour: true }).render();
178
183
  const lead = report.findings.find((f) => f.verdict === 'STOP')?.summary ?? 'Fan-out ceiling reached.';
179
184
  const w = 64;
180
185
  const bar = '─'.repeat(w);
@@ -80,6 +80,10 @@ export interface BurnReport {
80
80
  };
81
81
  }
82
82
  export interface Thresholds {
83
+ /** Additive local idle-resource warnings; no automatic process termination. */
84
+ idle_session_warn_hours?: number;
85
+ idle_browser_warn_minutes?: number;
86
+ orphan_workspace_warn_days?: number;
83
87
  fanout: {
84
88
  warn: number;
85
89
  stop: number;
@@ -131,6 +135,8 @@ export interface Thresholds {
131
135
  }
132
136
  export type Mode = 'shadow' | 'enforce';
133
137
  export interface Policy {
138
+ /** Shared threshold file; relative paths resolve from the Burn home. */
139
+ teamPolicyFile?: string;
134
140
  mode: Mode;
135
141
  thresholds: Thresholds;
136
142
  /** Additive local usage advisories. Older policy files use these defaults. */
@@ -0,0 +1,55 @@
1
+ # Burn live instrument panel
2
+
3
+ `agentguard-burn live` reads the local session cache and decision ledgers every half second. It does not change admission, write session state, or open a socket. Use `--session ID` to select a session, `--once` for one frame, and `--record run.jsonl` for a local structured recording. `q` quits and `p` pauses display updates. An observed STOP holds its reason for two seconds while intake continues, then releases the latest view.
4
+
5
+ ```sh
6
+ agentguard-burn live --session e86f091b --record local-run.jsonl
7
+ agentguard-burn live --replay local-run.jsonl
8
+ agentguard-burn render local-run.jsonl --mp4 local-run.mp4 --gif local-run.gif
9
+ ```
10
+
11
+ The panel uses the existing Canvas and palette. SPAWNS and TOKENS IN WINDOW are observed bucket sums over the policy's active-minute window, including inherited team thresholds. The rate bar becomes amber at 80 percent of the configured STOP ceiling and red at the ceiling. REPLAY SHARE is session cache-read tokens divided by all observed tokens. It is a cache-read proxy, not measured repeated semantic work. No numeric value is synthesized when the corresponding source is unavailable.
12
+
13
+ The right column shows the last eight locally recorded decisions. Historical ledgers record an action such as `spawn`, rather than the host's precise tool name; the panel displays that action. At fewer than 80 columns the right column is omitted, leaving full-width counters and the STOP card. The tested geometries are 104x35, 80x24 and a 43-column compact view rendered natively at 390 pixels.
14
+
15
+ Colored STOP reports use the same panel without reading files or delaying a hook. Their missing token-window, window-length and elapsed values remain unknown. They do not claim a two-second interactive hold. Existing plain-text cards and hook protocol bytes use their previous paths.
16
+
17
+ ## Verification
18
+
19
+ The full Node22 suite passed **265/265**, zero failures or skips, with `AGENTGUARD_STRESS=1`. The fixture stress test admitted exactly 40 of 240 concurrent hooks, denied 200, and verified the receipt chain. Full output is retained in `/private/tmp/burn-rulepack-panel-tests-final.log`. The first sandboxed attempt could not bind its local conformance server; the full permitted rerun passed.
20
+
21
+ ## September 19 evidence
22
+
23
+ The site figure comes from `assets/stop-capture.txt` and `docs/redesign-2026-09/hero/REPORT.md`, which explicitly disclose prepared synthetic history and a real hook decision (receipt `4cb15245-a57e-463c-aed5-32b143c5d719`). The older `docs/STOP_CLIP_2026-09.md` describes another prepared capture. Those captures do not establish a naturally observed rate storm.
24
+
25
+ The search covered all 13 receipts and 148 decision rows in the current local Burn ledgers, not only the selected session. September 19 UTC contains 11 rows in each ledger and two STOPs; September 19 Pacific contains 12 rows and one STOP. Every STOP uses fanout plus duplicate-work; neither date scope contains a spawn-rate finding. The evidence file records source hashes and both date boundaries. This search does not claim coverage of deleted or alternate-home ledgers.
26
+
27
+ The actual signed receipt is `d2f3ceec-816c-41ad-9e1d-23401eeb44f3`, session digest `3fa090ccbdfeaaa5de7b660af6dfb8dddfac5112e7a95188a0be494a90740263`. Ed25519 verification passed. Its timestamp is `2026-09-19T14:56:57.824Z`. The gate was historical **lifetime fanout: 48 proposed spawns against 40**, with a duplicate-work WARN. It was not the current 16-spawns-per-15-active-minutes gate.
28
+
29
+ The current deduplicating reader, restricted to the receipt timestamp, finds 47 observed spawns, 1,628,919,976 total tokens and 1,599,279,923 cache-read tokens. Its final 15-active-minute window has **one spawn and 8,086,525 tokens**. These are a fresh reconstruction from the parent and its 45 child transcripts. They are not the historical inflated usage totals retained in the signed receipt. Live state and policy were not rewritten.
30
+
31
+ The bounded clip starts at the real usage observation `14:56:18.247Z`. The next observed spawn is at `14:56:57.749Z`, followed 75 milliseconds later by the signed block. The counters show the actual zero-to-one spawn change. The reference rate bar stays below 16. The requested storm and rate-ceiling crossing cannot be substantiated from this event.
32
+
33
+ Original observation times are preserved. One-second display-clock ticks add no observations. The STOP card remains for two seconds, followed by the renderer's one-second final frame. The source observation interval lasts 39.577 seconds; the MP4 lasts 42.577001 seconds. GIF stores centisecond timing and lasts 42.58 seconds. Earlier session activity and later observations are outside the clip; prior decision rows provide context. Every frame says `RECORDED RUN · 1x`.
34
+
35
+ Assets live in `docs/assets/`:
36
+
37
+ - `burn-live-sep19.mp4` and `burn-live-sep19.gif`: 1920x1080.
38
+ - `burn-live-sep19-stop-1920.png`: the actual STOP frame.
39
+ - `burn-live-sep19-stop-390.png`: native compact Canvas rendering at 390x540.
40
+ - `burn-live-sep19.jsonl`: content-free frames written through the actual `live --replay --record` command.
41
+ - `burn-live-sep19.observations.jsonl`: the reconstructed input; CLI output preserves its frame data, timestamps and viewport exactly.
42
+ - `burn-live-sep19.receipt.json`: original signed receipt.
43
+ - `burn-live-sep19.evidence.json`: input hashes, receipt verification, timing, omissions, and media hashes.
44
+ - `burn-live-sep19.mp4.provenance.json`: renderer, font, frame times and encoding command.
45
+
46
+ Re-render the shipped recording without reading any private session data:
47
+
48
+ ```sh
49
+ agentguard-burn live --replay docs/assets/burn-live-sep19.jsonl --once --no-color --record /tmp/burn-review.jsonl
50
+ agentguard-burn render /tmp/burn-review.jsonl --mp4 /tmp/burn-review.mp4 --gif /tmp/burn-review.gif
51
+ ```
52
+
53
+ The non-TTY replay above checks the recording path; its stdout is not a timed terminal capture. The rendered media uses the original frame timestamps. The showcase itself was run through this CLI path, with frame equality checked before encoding.
54
+
55
+ Reconstruct with `npm run build` then `node scripts/record-september19.cjs /tmp/burn-september19-review`. The script requires the matching verified local receipt and transcripts and refuses to overwrite outputs. It exports numbers, opaque identifiers, action labels and detector reasons. It does not export tool inputs, outputs, prompts, source paths or file contents.
@@ -0,0 +1,288 @@
1
+ {
2
+ "receipt_verified": true,
3
+ "ledger_search": [
4
+ {
5
+ "ledger": "receipts.ndjson",
6
+ "scope": "UTC",
7
+ "total_rows": 13,
8
+ "date_rows": 11,
9
+ "stop_rows": 2,
10
+ "spawn_rate_rows": 0
11
+ },
12
+ {
13
+ "ledger": "receipts.ndjson",
14
+ "scope": "Pacific",
15
+ "total_rows": 13,
16
+ "date_rows": 12,
17
+ "stop_rows": 1,
18
+ "spawn_rate_rows": 0
19
+ },
20
+ {
21
+ "ledger": "decisions.ndjson",
22
+ "scope": "UTC",
23
+ "total_rows": 148,
24
+ "date_rows": 11,
25
+ "stop_rows": 2,
26
+ "spawn_rate_rows": 0
27
+ },
28
+ {
29
+ "ledger": "decisions.ndjson",
30
+ "scope": "Pacific",
31
+ "total_rows": 148,
32
+ "date_rows": 12,
33
+ "stop_rows": 1,
34
+ "spawn_rate_rows": 0
35
+ }
36
+ ],
37
+ "site_capture_sources": [
38
+ {
39
+ "relative_path": "assets/stop-capture.txt",
40
+ "sha256": "9da285376fe618a3130955c31c7ab3d35b5e6de45963da0a9911ccaea707ea09",
41
+ "discloses_prepared_history": true
42
+ },
43
+ {
44
+ "relative_path": "docs/redesign-2026-09/hero/REPORT.md",
45
+ "sha256": "b84096959799fa04041b426024d561347239f83fe30890b8618cf96f47dbce5d",
46
+ "discloses_prepared_history": true
47
+ },
48
+ {
49
+ "relative_path": "docs/STOP_CLIP_2026-09.md",
50
+ "sha256": "71f11b67e9fb7b52941bc32ac557eedfbf7ca9fa8cf3d9e0d3b286aca98b41b0",
51
+ "discloses_prepared_history": true
52
+ }
53
+ ],
54
+ "cli_recording": {
55
+ "command": "agentguard-burn live --replay burn-live-sep19.observations.jsonl --once --no-color --record burn-live-sep19.jsonl",
56
+ "frame_data_timestamps_and_viewport_identical": true,
57
+ "stdout_is_not_a_timed_capture": true
58
+ },
59
+ "receipt_sha256": "b46871a3cfeb203fababf81946d30acd22479b7b2af616370092c712d9da948c",
60
+ "receipt_id": "d2f3ceec-816c-41ad-9e1d-23401eeb44f3",
61
+ "session_digest": "3fa090ccbdfeaaa5de7b660af6dfb8dddfac5112e7a95188a0be494a90740263",
62
+ "receipts_ledger_sha256": "b7fa92561e0e9d9a201eebe031a90ed4106dae020d4645506b342ecc09065898",
63
+ "decisions_ledger_sha256": "503331af4e695f28f830302805558075c640f428b52246422c55419bbd3741cd",
64
+ "source_files": [
65
+ {
66
+ "role": "parent transcript",
67
+ "sha256": "e03804372ffa73c526b863096261d9020ff6d1f4ec6ec49d257c843b430a05cd"
68
+ },
69
+ {
70
+ "role": "child transcript",
71
+ "sha256": "fb72efad9b6a3a3f892cc7cdfb13c5e6ce2f6a100b42c1e9ec8f35b08c23c6ed"
72
+ },
73
+ {
74
+ "role": "child transcript",
75
+ "sha256": "a177e5d3f2b2ac692e3855ecc7a956873f327fc25aadf502bfe61eb24a79d7f0"
76
+ },
77
+ {
78
+ "role": "child transcript",
79
+ "sha256": "9a2ec39f738ac309a7562f935484f7d74432571a75ba7a78de12b02a6cb7b201"
80
+ },
81
+ {
82
+ "role": "child transcript",
83
+ "sha256": "5e962fbc8a654b1980dc41fe600638d20778146807cee149f8d7112a99b58e7a"
84
+ },
85
+ {
86
+ "role": "child transcript",
87
+ "sha256": "24cfb0864c80c3e7195942c4ed2ef85b561ed09f5e51f65002e6d2256bbb6199"
88
+ },
89
+ {
90
+ "role": "child transcript",
91
+ "sha256": "283eea347311a0458b899e0c6bb97dc516b1480dc744c66a18fd7ce5fa6aaa5e"
92
+ },
93
+ {
94
+ "role": "child transcript",
95
+ "sha256": "5d980691f48e18d853da98c3afbef5bb6cc6d9202f2dde44b745e16395d3e452"
96
+ },
97
+ {
98
+ "role": "child transcript",
99
+ "sha256": "49d1fc94f4dbdb395f341271b9ef53a3a25166985923a5f0c5113a80df74f47a"
100
+ },
101
+ {
102
+ "role": "child transcript",
103
+ "sha256": "f2d02db65cdc10b429c928e7f270ff6278058a6375d593418f78d1b7f9d2f237"
104
+ },
105
+ {
106
+ "role": "child transcript",
107
+ "sha256": "b11301e2a2a47607b7450de56f6fef328d2b461fd2102493dcd318c530bfca9f"
108
+ },
109
+ {
110
+ "role": "child transcript",
111
+ "sha256": "5b49e66fc92a0a9206460f225eb545896e51df91d0050eb6caf29e4c373a2d1f"
112
+ },
113
+ {
114
+ "role": "child transcript",
115
+ "sha256": "4361b129375583a79da9580bcbb3be8065efb08e917ab4281334a19a1907300d"
116
+ },
117
+ {
118
+ "role": "child transcript",
119
+ "sha256": "ce22debd96b8d85c612dd12d3f3ebf97508f52c95d41470dc3e69a81edd516d0"
120
+ },
121
+ {
122
+ "role": "child transcript",
123
+ "sha256": "31d1b33e684a143879742dfaa2d703903b44d661310e528117a6df9eaedfd58d"
124
+ },
125
+ {
126
+ "role": "child transcript",
127
+ "sha256": "e9dbc8b6eda13e8ca545bcb25dae13d502bf569ac638f8202f45791d66128a7e"
128
+ },
129
+ {
130
+ "role": "child transcript",
131
+ "sha256": "88ea801434684e01ebe3d9b159ca38e0ddd813f24a3f8271e581496edb37214d"
132
+ },
133
+ {
134
+ "role": "child transcript",
135
+ "sha256": "93b3fbf110320d0a54307a0dd265e53a374a70218333baeacc0262ebdcdffaa9"
136
+ },
137
+ {
138
+ "role": "child transcript",
139
+ "sha256": "dbc3df30407aae1d14ae1053e954e57588e17f933013f5ecf41b06fedfb3a028"
140
+ },
141
+ {
142
+ "role": "child transcript",
143
+ "sha256": "796c81f284d2a0be91c05393797ef8a891a9f20baad841449a4d3e434e0df33b"
144
+ },
145
+ {
146
+ "role": "child transcript",
147
+ "sha256": "4894840661069bbe53ee7304740113e3ed8aca4c7ac180f61acd8b112faed08f"
148
+ },
149
+ {
150
+ "role": "child transcript",
151
+ "sha256": "bafd6a55cfd94a992ae4890ee845b0fc7f49b4410f23896afc7f83ec0cbeb494"
152
+ },
153
+ {
154
+ "role": "child transcript",
155
+ "sha256": "e04cc31b3b80dd3b775a534a15ab0b8fef7fc8cfd65f827780e9e0acd29eb4e3"
156
+ },
157
+ {
158
+ "role": "child transcript",
159
+ "sha256": "fe9e02cba15dbfe7676d89d3510f422b8acc8b1f026b8d8f37b5477ce2049c23"
160
+ },
161
+ {
162
+ "role": "child transcript",
163
+ "sha256": "593738b8533015d9504033b0afe66e123b6aedfe71e69ebbaec2f80c422ce2f8"
164
+ },
165
+ {
166
+ "role": "child transcript",
167
+ "sha256": "11da50efa0d68b99fc1c3f39c21f1192d88d631174c98500a3a224e35ca3de0c"
168
+ },
169
+ {
170
+ "role": "child transcript",
171
+ "sha256": "3543da8cb0dc23998cdfb048d8576b98be1562ea2c585055433e3c95fe3eb051"
172
+ },
173
+ {
174
+ "role": "child transcript",
175
+ "sha256": "c6460a8f8d6071af147957033c86b60fbaa684a653ff5743a8261112dc495a41"
176
+ },
177
+ {
178
+ "role": "child transcript",
179
+ "sha256": "5543a25e5979c136308bd1c49b5d20eef53851a01f5a622e5d9b72d9c01ca399"
180
+ },
181
+ {
182
+ "role": "child transcript",
183
+ "sha256": "9833600b9338c297a8fe86d6ae8d783f80403a8510f6b1192baaa03a0736a5bc"
184
+ },
185
+ {
186
+ "role": "child transcript",
187
+ "sha256": "d0d1e705d3b000d91df754fea2c0af93074529980500d94f53e83aac6eefe400"
188
+ },
189
+ {
190
+ "role": "child transcript",
191
+ "sha256": "17848e0ba0d0ee2508ff824478144a435a7b7d12e66c3cb255a1805b84ef5e47"
192
+ },
193
+ {
194
+ "role": "child transcript",
195
+ "sha256": "a5ed36b7d17ec3da2e98ef8e3bb0456e894d18e918012f679ca45a03c0971e84"
196
+ },
197
+ {
198
+ "role": "child transcript",
199
+ "sha256": "e7afa6e688eeb97c224cd687639191bfb5730f77ae2c0f426d253b90d7f22416"
200
+ },
201
+ {
202
+ "role": "child transcript",
203
+ "sha256": "884764f0a6ac584da1a34c2c60270efc068e3b5c1f4cad79d92a5baf1d6151c0"
204
+ },
205
+ {
206
+ "role": "child transcript",
207
+ "sha256": "835365b6736af0c6389925d8490d5a173773093d4cab6a5be0481f97d8c58e73"
208
+ },
209
+ {
210
+ "role": "child transcript",
211
+ "sha256": "454038ed5bf3aec9183bf955ccc8cea9e77ca512a5fcbbf0db9fb4d04795f100"
212
+ },
213
+ {
214
+ "role": "child transcript",
215
+ "sha256": "862a476a9ce4ca026d251216d76549a2fcb46d5421851ed6ac29efb56ebaf169"
216
+ },
217
+ {
218
+ "role": "child transcript",
219
+ "sha256": "082a34a2d20f9c696de84d70c6b24afe72612c2ff588d86f7e07ead995194767"
220
+ },
221
+ {
222
+ "role": "child transcript",
223
+ "sha256": "78a319db493ac9f052551d71c6118fd8ba658080b2b4915a5683d808d66c07b9"
224
+ },
225
+ {
226
+ "role": "child transcript",
227
+ "sha256": "bfb1bd3e7281a3ac79a6c3bd4dbe40e149997e45b82b1e05c05892e888f2730f"
228
+ },
229
+ {
230
+ "role": "child transcript",
231
+ "sha256": "613a74992c51ff6cf42d073e314c938c5484769b1c2b384a431b6a260c68f154"
232
+ },
233
+ {
234
+ "role": "child transcript",
235
+ "sha256": "9f08151f521f16e5795856c32d6206df7c84fb2be7c503a5061ad387298a4623"
236
+ },
237
+ {
238
+ "role": "child transcript",
239
+ "sha256": "3b8c3bce6332be6facf9b2e961580ed21ba69e157d9d0fec0902d15516d61abd"
240
+ },
241
+ {
242
+ "role": "child transcript",
243
+ "sha256": "6e953d95757b5924fc8534712e20be460a7987ff9c0d31dd6cd5bfb88dc56e96"
244
+ },
245
+ {
246
+ "role": "child transcript",
247
+ "sha256": "5e50d304214b48d9023b4c32fa85c01e3de80f1679c21f3afc8301d538265d4f"
248
+ }
249
+ ],
250
+ "clip_start": "2026-09-19T14:56:18.247Z",
251
+ "signed_stop_at": "2026-09-19T14:56:57.824Z",
252
+ "clip_end": "2026-09-19T14:56:59.824Z",
253
+ "original_observation_times": [
254
+ "2026-09-19T14:56:18.247Z",
255
+ "2026-09-19T14:56:57.749Z"
256
+ ],
257
+ "display_ticks": "One-second display-clock ticks plus original transcript and receipt timestamps. Counts change only at original observations. No time compression.",
258
+ "historical_gate": {
259
+ "detector": "fanout",
260
+ "scope": "lifetime",
261
+ "observed": 48,
262
+ "threshold": 40
263
+ },
264
+ "current_reader_at_block": {
265
+ "total_tokens": 1628919976,
266
+ "total_cache_read": 1599279923,
267
+ "completed_spawns": 47,
268
+ "window_spawns": 1,
269
+ "window_tokens": 8086525
270
+ },
271
+ "measurement": "Current deduplicating readAll reader, session plus its own child transcripts, filtered through the signed receipt timestamp. No live state was rewritten.",
272
+ "limits": [
273
+ "The requested 16-spawns-in-15-active-minutes storm is not supported by this event. The displayed current-reference rate bar remains below its ceiling.",
274
+ "The historical receipt retains legacy inflated token totals. The panel uses a fresh deduplicated recount, not those legacy totals.",
275
+ "All observations before the clip start and after the signed block are outside this bounded clip. Prior decisions remain visible as historical context.",
276
+ "The stored decision rows name the action spawn, not the host tool name. No tool name is invented.",
277
+ "Replay share means session cache-read tokens divided by all observed tokens. It is a proxy, not a measurement of repeated semantic work.",
278
+ "The display holds the STOP reason for two seconds after the signed timestamp. The renderer then holds the final released frame for one second."
279
+ ],
280
+ "output_sha256": {
281
+ "burn-live-sep19.jsonl": "2404866fa112d9423918b1d4612bf2c853191c7c5b2a1b4066d667fc53b4127d",
282
+ "burn-live-sep19.observations.jsonl": "1649ba637a7a528aca977a705aa1a90e19a50f6b12de98572615d8f1d6361a8f",
283
+ "burn-live-sep19-stop-390.png": "e6bf186febff7849ddd696f2632e438813dfb2019720bd740fdae22235df12d8",
284
+ "burn-live-sep19.mp4": "c9459e58dfaa18685c0622512178385ed8bd19ed045df14e34935cdab494f697",
285
+ "burn-live-sep19.gif": "bbc6c701e31b2089fa921b8ecd599d5df74e5201162f9471a2e0c75a2fd663f6",
286
+ "burn-live-sep19-stop-1920.png": "54e2c10b857b11987a9fd23c2b4c312eb254b1b3308199047dc0f8192e5bf191"
287
+ }
288
+ }
Binary file