@orcareplay/core 0.1.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/dist/blobs.d.ts +21 -0
- package/dist/blobs.d.ts.map +1 -0
- package/dist/blobs.js +97 -0
- package/dist/blobs.js.map +1 -0
- package/dist/graph.d.ts +106 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +393 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +115 -0
- package/dist/paths.js.map +1 -0
- package/dist/reader.d.ts +45 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +152 -0
- package/dist/reader.js.map +1 -0
- package/dist/redaction.d.ts +48 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +245 -0
- package/dist/redaction.js.map +1 -0
- package/dist/writer.d.ts +66 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +182 -0
- package/dist/writer.js.map +1 -0
- package/package.json +18 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface RunRef {
|
|
2
|
+
runId: string;
|
|
3
|
+
/** RFC3339, from the manifest when it is readable, else the directory's mtime. */
|
|
4
|
+
createdAt: string;
|
|
5
|
+
dir: string;
|
|
6
|
+
/** Set when this run was forked from another (spec §1). Absent on a plain recording. */
|
|
7
|
+
parentRun?: string;
|
|
8
|
+
forkPoint?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function orcaDir(cwd: string): string;
|
|
11
|
+
export declare function runsDir(cwd: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Create `.orca/runs`, and make the store ignore itself.
|
|
14
|
+
*
|
|
15
|
+
* A recording is the conversation the model saw — which is your source — plus shell output, a
|
|
16
|
+
* snapshot of the whole workspace, and an environment allowlist. SECURITY.md says to treat one as
|
|
17
|
+
* roughly as sensitive as a shell history plus a heap dump. It was also landing in `git status` as
|
|
18
|
+
* an untracked directory, one `git add -A` away from being committed and pushed, in the working
|
|
19
|
+
* tree of the project it just recorded.
|
|
20
|
+
*
|
|
21
|
+
* `.orca/.gitignore` containing `*` is git's own idiom for a directory that excludes itself: no
|
|
22
|
+
* edit to the user's `.gitignore`, nothing to remember, and it works in a repo orca has never seen
|
|
23
|
+
* before. Written once at creation and never rewritten, so anyone who deliberately wants their
|
|
24
|
+
* traces tracked can delete it and orca will not put it back.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ensureRunsDir(cwd: string): Promise<string>;
|
|
27
|
+
/** Run ids reach us from argv, so the pattern check is also the path-traversal guard. */
|
|
28
|
+
export declare function runDirFor(cwd: string, runId: string): string;
|
|
29
|
+
/** Every recorded run in the workspace, newest first. */
|
|
30
|
+
export declare function listRuns(cwd: string): Promise<RunRef[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Resolves the CLI's run argument: `last` for the newest run, anything else as a run id.
|
|
33
|
+
*
|
|
34
|
+
* `last` skips replay traces. Since exact replay started writing one, the newest run in a
|
|
35
|
+
* directory is usually a report about the run before it, and every command defaults to `last` — so
|
|
36
|
+
* that quietly redirected all of them. The sharpest case is the line in the README:
|
|
37
|
+
* `orca scrub last --match my-hostname` scrubbed the empty trace, found nothing, and said "nothing
|
|
38
|
+
* matched — the trace is unchanged", while the secret sat in the recording beside it. A security
|
|
39
|
+
* tool reporting clean because it searched the wrong thing is worse than one that fails.
|
|
40
|
+
*
|
|
41
|
+
* Naming a replay trace explicitly still resolves it, and when a directory holds nothing else it
|
|
42
|
+
* is still returned — a selector that refuses to resolve is not an improvement.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveRunSelector(cwd: string, selector: string): Promise<RunRef>;
|
|
45
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYhE;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG5D;AA6BD,yDAAyD;AACzD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAc7D;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAevF"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { RUN_ID_PATTERN } from '@orcareplay/schema';
|
|
4
|
+
export function orcaDir(cwd) {
|
|
5
|
+
return join(cwd, '.orca');
|
|
6
|
+
}
|
|
7
|
+
export function runsDir(cwd) {
|
|
8
|
+
return join(orcaDir(cwd), 'runs');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create `.orca/runs`, and make the store ignore itself.
|
|
12
|
+
*
|
|
13
|
+
* A recording is the conversation the model saw — which is your source — plus shell output, a
|
|
14
|
+
* snapshot of the whole workspace, and an environment allowlist. SECURITY.md says to treat one as
|
|
15
|
+
* roughly as sensitive as a shell history plus a heap dump. It was also landing in `git status` as
|
|
16
|
+
* an untracked directory, one `git add -A` away from being committed and pushed, in the working
|
|
17
|
+
* tree of the project it just recorded.
|
|
18
|
+
*
|
|
19
|
+
* `.orca/.gitignore` containing `*` is git's own idiom for a directory that excludes itself: no
|
|
20
|
+
* edit to the user's `.gitignore`, nothing to remember, and it works in a repo orca has never seen
|
|
21
|
+
* before. Written once at creation and never rewritten, so anyone who deliberately wants their
|
|
22
|
+
* traces tracked can delete it and orca will not put it back.
|
|
23
|
+
*/
|
|
24
|
+
export async function ensureRunsDir(cwd) {
|
|
25
|
+
const dir = runsDir(cwd);
|
|
26
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
27
|
+
const ignore = join(orcaDir(cwd), '.gitignore');
|
|
28
|
+
if (!(await stat(ignore).catch(() => null))) {
|
|
29
|
+
await writeFile(ignore, '# Recordings hold source, shell output and workspace snapshots. Not for committing.\n*\n', { mode: 0o600, flag: 'wx' }).catch(() => undefined);
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
/** Run ids reach us from argv, so the pattern check is also the path-traversal guard. */
|
|
34
|
+
export function runDirFor(cwd, runId) {
|
|
35
|
+
if (!RUN_ID_PATTERN.test(runId))
|
|
36
|
+
throw new Error(`not a valid run id: ${JSON.stringify(runId)}`);
|
|
37
|
+
return join(runsDir(cwd), runId);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The manifest facts a listing needs, read in one pass.
|
|
41
|
+
*
|
|
42
|
+
* Everything here degrades rather than throws: a run whose manifest is missing or truncated is
|
|
43
|
+
* still worth listing — that is exactly the crashed run someone is looking for.
|
|
44
|
+
*/
|
|
45
|
+
async function factsOf(dir) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = JSON.parse(await readFile(join(dir, 'manifest.json'), 'utf8'));
|
|
48
|
+
const at = raw.created_at;
|
|
49
|
+
if (typeof at === 'string' && !Number.isNaN(Date.parse(at))) {
|
|
50
|
+
return {
|
|
51
|
+
createdAt: at,
|
|
52
|
+
...(typeof raw.parent_run === 'string' ? { parentRun: raw.parent_run } : {}),
|
|
53
|
+
...(typeof raw.fork_point === 'number' ? { forkPoint: raw.fork_point } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Fall through to the directory's mtime.
|
|
59
|
+
}
|
|
60
|
+
return { createdAt: new Date((await stat(dir)).mtimeMs).toISOString() };
|
|
61
|
+
}
|
|
62
|
+
/** Every recorded run in the workspace, newest first. */
|
|
63
|
+
export async function listRuns(cwd) {
|
|
64
|
+
const root = runsDir(cwd);
|
|
65
|
+
const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
|
|
66
|
+
const runs = [];
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
if (!e.isDirectory() || !RUN_ID_PATTERN.test(e.name))
|
|
69
|
+
continue;
|
|
70
|
+
const dir = join(root, e.name);
|
|
71
|
+
runs.push({ runId: e.name, dir, ...(await factsOf(dir)) });
|
|
72
|
+
}
|
|
73
|
+
return runs.sort((a, b) => a.createdAt === b.createdAt
|
|
74
|
+
? b.runId.localeCompare(a.runId)
|
|
75
|
+
: b.createdAt.localeCompare(a.createdAt));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A run that describes another run rather than being one: an exact replay's own trace.
|
|
79
|
+
*
|
|
80
|
+
* It holds divergences and nothing else — no exchanges, no blobs, no filesystem store. A fork is
|
|
81
|
+
* not one of these: it has a `fork_point`, real exchanges and a worktree, and is a thing you act
|
|
82
|
+
* on.
|
|
83
|
+
*/
|
|
84
|
+
function isReplayTrace(run) {
|
|
85
|
+
return run.parentRun !== undefined && run.forkPoint === undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves the CLI's run argument: `last` for the newest run, anything else as a run id.
|
|
89
|
+
*
|
|
90
|
+
* `last` skips replay traces. Since exact replay started writing one, the newest run in a
|
|
91
|
+
* directory is usually a report about the run before it, and every command defaults to `last` — so
|
|
92
|
+
* that quietly redirected all of them. The sharpest case is the line in the README:
|
|
93
|
+
* `orca scrub last --match my-hostname` scrubbed the empty trace, found nothing, and said "nothing
|
|
94
|
+
* matched — the trace is unchanged", while the secret sat in the recording beside it. A security
|
|
95
|
+
* tool reporting clean because it searched the wrong thing is worse than one that fails.
|
|
96
|
+
*
|
|
97
|
+
* Naming a replay trace explicitly still resolves it, and when a directory holds nothing else it
|
|
98
|
+
* is still returned — a selector that refuses to resolve is not an improvement.
|
|
99
|
+
*/
|
|
100
|
+
export async function resolveRunSelector(cwd, selector) {
|
|
101
|
+
if (selector === 'last') {
|
|
102
|
+
const runs = await listRuns(cwd);
|
|
103
|
+
const newest = runs.find((r) => !isReplayTrace(r)) ?? runs[0];
|
|
104
|
+
if (!newest) {
|
|
105
|
+
throw new Error(`no runs recorded in ${runsDir(cwd)} — record one first: orca record -- <your agent>`);
|
|
106
|
+
}
|
|
107
|
+
return newest;
|
|
108
|
+
}
|
|
109
|
+
const dir = runDirFor(cwd, selector);
|
|
110
|
+
const found = (await listRuns(cwd)).find((r) => r.dir === dir);
|
|
111
|
+
if (!found)
|
|
112
|
+
throw new Error(`no run ${selector} in ${runsDir(cwd)} — list runs with: orca list`);
|
|
113
|
+
return found;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
|
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { BlobRef, Manifest, TraceEvent } from '@orcareplay/schema';
|
|
2
|
+
/** A line the reader could not use. Reported, never repaired (spec §6). */
|
|
3
|
+
export interface ReadProblem {
|
|
4
|
+
/** 1-based line number in events.jsonl; 0 for a problem with the file itself. */
|
|
5
|
+
line: number;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
export interface IntegrityResult {
|
|
9
|
+
/** True only for a sealed run whose events file still hashes to the digest it was sealed with. */
|
|
10
|
+
ok: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Why `ok` is what it is. `unsealed` is not `mismatch`, and collapsing the two is how a run whose
|
|
13
|
+
* recorder was killed gets reported as tampered: nothing changed under it, there was simply never
|
|
14
|
+
* a digest to check it against. Callers that only care whether the bytes are trustworthy read
|
|
15
|
+
* `ok`; callers that tell a person what happened have to tell those two apart.
|
|
16
|
+
*/
|
|
17
|
+
state: 'verified' | 'unsealed' | 'mismatch';
|
|
18
|
+
/** Absent when the run was never sealed, so there is nothing to check against. */
|
|
19
|
+
expected?: string;
|
|
20
|
+
actual?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reader for one recorded run.
|
|
24
|
+
*
|
|
25
|
+
* Two tolerances are mandated by spec §2 and both are deliberate: a run that crashed mid-write
|
|
26
|
+
* leaves a partial final line, and a trace written by a newer Orca may carry event types this
|
|
27
|
+
* build has never heard of. Neither may take down the whole trace — but neither is hidden either,
|
|
28
|
+
* so anything skipped shows up in {@link problems}.
|
|
29
|
+
*/
|
|
30
|
+
export declare class TraceReader {
|
|
31
|
+
#private;
|
|
32
|
+
private constructor();
|
|
33
|
+
static open(runDir: string): Promise<TraceReader>;
|
|
34
|
+
get runDir(): string;
|
|
35
|
+
manifest(): Manifest;
|
|
36
|
+
/** Lines skipped by the most recent read pass. */
|
|
37
|
+
problems(): ReadProblem[];
|
|
38
|
+
events(): Promise<TraceEvent[]>;
|
|
39
|
+
stream(): AsyncIterableIterator<TraceEvent>;
|
|
40
|
+
blob(ref: BlobRef | string): Promise<Uint8Array>;
|
|
41
|
+
/** The event's payload, reading it back from its blob when it was spilled (spec §2.2). */
|
|
42
|
+
resolvePayload(event: TraceEvent): Promise<unknown>;
|
|
43
|
+
verifyIntegrity(): Promise<IntegrityResult>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIxE,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,kGAAkG;IAClG,EAAE,EAAE,OAAO,CAAC;IACZ;;;;;OAKG;IACH,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;IAC5C,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;;GAOG;AACH,qBAAa,WAAW;;IAOtB,OAAO;WAOM,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAkBvD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,QAAQ,IAAI,QAAQ;IAIpB,kDAAkD;IAClD,QAAQ,IAAI,WAAW,EAAE;IAInB,MAAM,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAM9B,MAAM,IAAI,qBAAqB,CAAC,UAAU,CAAC;IAiC5C,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAItD,0FAA0F;IACpF,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAYnD,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC;CAiClD"}
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { EVENT_TYPES, assertManifest, isBlobRef, validateEvent } from '@orcareplay/schema';
|
|
6
|
+
import { BlobStore, sha256File } from './blobs.js';
|
|
7
|
+
const KNOWN_TYPES = new Set(EVENT_TYPES);
|
|
8
|
+
/**
|
|
9
|
+
* Reader for one recorded run.
|
|
10
|
+
*
|
|
11
|
+
* Two tolerances are mandated by spec §2 and both are deliberate: a run that crashed mid-write
|
|
12
|
+
* leaves a partial final line, and a trace written by a newer Orca may carry event types this
|
|
13
|
+
* build has never heard of. Neither may take down the whole trace — but neither is hidden either,
|
|
14
|
+
* so anything skipped shows up in {@link problems}.
|
|
15
|
+
*/
|
|
16
|
+
export class TraceReader {
|
|
17
|
+
#runDir;
|
|
18
|
+
#eventsPath;
|
|
19
|
+
#manifest;
|
|
20
|
+
#blobs;
|
|
21
|
+
#problems = [];
|
|
22
|
+
constructor(runDir, manifest) {
|
|
23
|
+
this.#runDir = runDir;
|
|
24
|
+
this.#eventsPath = join(runDir, 'events.jsonl');
|
|
25
|
+
this.#manifest = manifest;
|
|
26
|
+
this.#blobs = new BlobStore(join(runDir, 'blobs'));
|
|
27
|
+
}
|
|
28
|
+
static async open(runDir) {
|
|
29
|
+
const path = join(runDir, 'manifest.json');
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = await readFile(path, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new Error(`not an orca run: cannot read ${path}`);
|
|
36
|
+
}
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error(`invalid manifest: ${path} is not JSON`);
|
|
43
|
+
}
|
|
44
|
+
assertManifest(parsed);
|
|
45
|
+
return new TraceReader(runDir, parsed);
|
|
46
|
+
}
|
|
47
|
+
get runDir() {
|
|
48
|
+
return this.#runDir;
|
|
49
|
+
}
|
|
50
|
+
manifest() {
|
|
51
|
+
return this.#manifest;
|
|
52
|
+
}
|
|
53
|
+
/** Lines skipped by the most recent read pass. */
|
|
54
|
+
problems() {
|
|
55
|
+
return [...this.#problems];
|
|
56
|
+
}
|
|
57
|
+
async events() {
|
|
58
|
+
const out = [];
|
|
59
|
+
for await (const e of this.stream())
|
|
60
|
+
out.push(e);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
async *stream() {
|
|
64
|
+
this.#problems = [];
|
|
65
|
+
if (!(await stat(this.#eventsPath).catch(() => null))) {
|
|
66
|
+
this.#problems.push({ line: 0, reason: `${this.#eventsPath} is missing` });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const rl = createInterface({
|
|
70
|
+
input: createReadStream(this.#eventsPath),
|
|
71
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
72
|
+
});
|
|
73
|
+
// One line is held back so the last one can be told apart from the rest: only the last may
|
|
74
|
+
// legitimately be half-written.
|
|
75
|
+
let held;
|
|
76
|
+
let n = 0;
|
|
77
|
+
try {
|
|
78
|
+
for await (const raw of rl) {
|
|
79
|
+
n += 1;
|
|
80
|
+
if (raw.trim().length === 0)
|
|
81
|
+
continue;
|
|
82
|
+
if (held) {
|
|
83
|
+
const e = this.#parse(held, false);
|
|
84
|
+
if (e)
|
|
85
|
+
yield e;
|
|
86
|
+
}
|
|
87
|
+
held = { line: n, text: raw };
|
|
88
|
+
}
|
|
89
|
+
if (held) {
|
|
90
|
+
const e = this.#parse(held, true);
|
|
91
|
+
if (e)
|
|
92
|
+
yield e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
rl.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async blob(ref) {
|
|
100
|
+
return this.#blobs.get(ref);
|
|
101
|
+
}
|
|
102
|
+
/** The event's payload, reading it back from its blob when it was spilled (spec §2.2). */
|
|
103
|
+
async resolvePayload(event) {
|
|
104
|
+
const payload = event.payload;
|
|
105
|
+
if (!isBlobRef(payload))
|
|
106
|
+
return payload;
|
|
107
|
+
const text = new TextDecoder().decode(await this.blob(payload));
|
|
108
|
+
if (payload.media_type !== undefined && !payload.media_type.includes('json'))
|
|
109
|
+
return text;
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(text);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new Error(`blob ${payload.$blob} is not JSON; read it with blob() instead`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async verifyIntegrity() {
|
|
118
|
+
const expected = this.#manifest.integrity?.events_sha256;
|
|
119
|
+
const actual = await sha256File(this.#eventsPath);
|
|
120
|
+
if (expected === undefined)
|
|
121
|
+
return { ok: false, state: 'unsealed', actual };
|
|
122
|
+
return expected === actual
|
|
123
|
+
? { ok: true, state: 'verified', expected, actual }
|
|
124
|
+
: { ok: false, state: 'mismatch', expected, actual };
|
|
125
|
+
}
|
|
126
|
+
#parse(held, isLast) {
|
|
127
|
+
let value;
|
|
128
|
+
try {
|
|
129
|
+
value = JSON.parse(held.text);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
const reason = isLast ? 'truncated final line' : 'malformed JSON';
|
|
133
|
+
this.#problems.push({ line: held.line, reason });
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const type = value.type;
|
|
137
|
+
if (typeof type !== 'string' || !KNOWN_TYPES.has(type)) {
|
|
138
|
+
this.#problems.push({
|
|
139
|
+
line: held.line,
|
|
140
|
+
reason: `unknown event type ${JSON.stringify(type)}`,
|
|
141
|
+
});
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const result = validateEvent(value);
|
|
145
|
+
if (!result.valid) {
|
|
146
|
+
this.#problems.push({ line: held.line, reason: result.errors.join('; ') });
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RedactionRecord } from '@orcareplay/schema';
|
|
2
|
+
/** Bump when a rule is added or changed, so old traces stay interpretable. */
|
|
3
|
+
export declare const REDACTION_POLICY_VERSION = 3;
|
|
4
|
+
/** Environment capture is allowlist-only (spec §5). Everything else is denied. */
|
|
5
|
+
export declare const DEFAULT_ENV_ALLOWLIST: string[];
|
|
6
|
+
/**
|
|
7
|
+
* Request headers whose value is never written, whatever it looks like (spec §5).
|
|
8
|
+
*
|
|
9
|
+
* The single list. There were three — here, in the recording proxy, and in the TLS interceptor —
|
|
10
|
+
* and they had drifted: `api-key` and `x-goog-api-key` were known to exactly one of them, so the
|
|
11
|
+
* same Azure or Google credential was stripped on the intercepted path and written on the recorded
|
|
12
|
+
* one. Nothing about a header set that has to stay identical in three places will keep it that
|
|
13
|
+
* way, so it is defined once and imported.
|
|
14
|
+
*/
|
|
15
|
+
export declare const AUTH_REQUEST_HEADERS: string[];
|
|
16
|
+
/** A response can hand out credentials too. `set-cookie` is a session, not metadata. */
|
|
17
|
+
export declare const AUTH_RESPONSE_HEADERS: string[];
|
|
18
|
+
/** Every header name whose value is never written, in either direction. */
|
|
19
|
+
export declare const AUTH_HEADERS: string[];
|
|
20
|
+
export interface RedactionOptions {
|
|
21
|
+
/** Per-run salt. Defaults to fresh randomness, and is deliberately never persisted. */
|
|
22
|
+
salt?: string;
|
|
23
|
+
/** Replaces {@link DEFAULT_ENV_ALLOWLIST} outright — this is a deny-by-default control. */
|
|
24
|
+
envAllowlist?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface RedactionResult<T> {
|
|
27
|
+
value: T;
|
|
28
|
+
hits: RedactionRecord[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write-path redactor (spec §5).
|
|
32
|
+
*
|
|
33
|
+
* The placeholder is `<secret:kind:hash8>` where hash8 is sha256(salt + secret) truncated, so the
|
|
34
|
+
* same secret always yields the same placeholder — replay still matches structurally — while the
|
|
35
|
+
* secret itself is unrecoverable. The salt is per run and never written down, which is what stops
|
|
36
|
+
* a short secret from being brute-forced out of a published trace.
|
|
37
|
+
*/
|
|
38
|
+
export declare class Redactor {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(opts?: RedactionOptions);
|
|
41
|
+
redactString(s: string, context?: string): RedactionResult<string>;
|
|
42
|
+
redactHeaders(headers: Record<string, string>): RedactionResult<Record<string, string>>;
|
|
43
|
+
redactEnv(env: Record<string, string | undefined>): Record<string, string>;
|
|
44
|
+
/** Every removal so far, aggregated by rule and identifier. Never contains a value. */
|
|
45
|
+
records(): RedactionRecord[];
|
|
46
|
+
rulesFired(): Record<string, number>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,8EAA8E;AAK9E,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,UAUjC,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,UAQhC,CAAC;AAEF,wFAAwF;AACxF,eAAO,MAAM,qBAAqB,UAAiB,CAAC;AAEpD,2EAA2E;AAC3E,eAAO,MAAM,YAAY,UAAsD,CAAC;AA0GhF,MAAM,WAAW,gBAAgB;IAC/B,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,eAAe,EAAE,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,qBAAa,QAAQ;;gBAKP,IAAI,GAAE,gBAAqB;IAKvC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC;IAOlE,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAcvF,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAY1E,uFAAuF;IACvF,OAAO,IAAI,eAAe,EAAE;IAI5B,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CA6DrC"}
|