@catp-protocol/cli 0.7.3 → 0.7.5
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/audit/durable.d.ts +87 -0
- package/dist/audit/durable.d.ts.map +1 -0
- package/dist/audit/durable.js +285 -0
- package/dist/audit/durable.js.map +1 -0
- package/dist/audit/logger.d.ts +80 -5
- package/dist/audit/logger.d.ts.map +1 -1
- package/dist/audit/logger.js +199 -20
- package/dist/audit/logger.js.map +1 -1
- package/dist/audit/paths.d.ts +11 -0
- package/dist/audit/paths.d.ts.map +1 -1
- package/dist/audit/paths.js +19 -0
- package/dist/audit/paths.js.map +1 -1
- package/dist/audit/verifier.d.ts +9 -0
- package/dist/audit/verifier.d.ts.map +1 -1
- package/dist/audit/verifier.js +70 -18
- package/dist/audit/verifier.js.map +1 -1
- package/dist/cli.js +18 -12
- package/dist/cli.js.map +1 -1
- package/dist/commands/authorization.js +6 -0
- package/dist/commands/authorization.js.map +1 -1
- package/dist/commands/log.d.ts +51 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +110 -22
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/receipt.d.ts +71 -1
- package/dist/commands/receipt.d.ts.map +1 -1
- package/dist/commands/receipt.js +354 -72
- package/dist/commands/receipt.js.map +1 -1
- package/dist/enforcement/core.d.ts +10 -4
- package/dist/enforcement/core.d.ts.map +1 -1
- package/dist/enforcement/core.js +40 -4
- package/dist/enforcement/core.js.map +1 -1
- package/dist/evidence/canonical.d.ts +22 -0
- package/dist/evidence/canonical.d.ts.map +1 -0
- package/dist/evidence/canonical.js +45 -0
- package/dist/evidence/canonical.js.map +1 -0
- package/dist/evidence/commitments.d.ts +46 -0
- package/dist/evidence/commitments.d.ts.map +1 -0
- package/dist/evidence/commitments.js +52 -0
- package/dist/evidence/commitments.js.map +1 -0
- package/dist/hook/post.d.ts +6 -0
- package/dist/hook/post.d.ts.map +1 -1
- package/dist/hook/post.js +1 -1
- package/dist/hook/post.js.map +1 -1
- package/dist/hook/pre.d.ts +6 -0
- package/dist/hook/pre.d.ts.map +1 -1
- package/dist/hook/pre.js +4 -1
- package/dist/hook/pre.js.map +1 -1
- package/dist/policy/types.d.ts +25 -2
- package/dist/policy/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow filesystem seam used by the durable primitives. Production code binds
|
|
3
|
+
* this to node:fs; unit tests inject a fake to assert operation order and to
|
|
4
|
+
* inject failures without touching a real disk.
|
|
5
|
+
*/
|
|
6
|
+
export interface DurableFs {
|
|
7
|
+
existsSync(path: string): boolean;
|
|
8
|
+
mkdirSync(path: string, opts: {
|
|
9
|
+
recursive: boolean;
|
|
10
|
+
mode: number;
|
|
11
|
+
}): void;
|
|
12
|
+
openSync(path: string, flags: string, mode: number): number;
|
|
13
|
+
writeSync(fd: number, buffer: Uint8Array, offset: number, length: number, position: number | null): number;
|
|
14
|
+
fsyncSync(fd: number): void;
|
|
15
|
+
closeSync(fd: number): void;
|
|
16
|
+
readFileSync(path: string): Buffer;
|
|
17
|
+
renameSync(oldPath: string, newPath: string): void;
|
|
18
|
+
unlinkSync(path: string): void;
|
|
19
|
+
/** Size of a file in bytes, used to record the pre-append rollback point. */
|
|
20
|
+
statSync(path: string): {
|
|
21
|
+
size: number;
|
|
22
|
+
};
|
|
23
|
+
/** Truncate an open file to `len` bytes, used to roll back a torn append. */
|
|
24
|
+
ftruncateSync(fd: number, len: number): void;
|
|
25
|
+
}
|
|
26
|
+
/** Production binding of {@link DurableFs} to node:fs. */
|
|
27
|
+
export declare const nodeFs: DurableFs;
|
|
28
|
+
/**
|
|
29
|
+
* Create `path` (and any missing ancestors) and make the new directory entries
|
|
30
|
+
* durable. Missing components are collected before creation, created with
|
|
31
|
+
* 0o700, then fsynced from the deepest component upward followed by the
|
|
32
|
+
* deepest pre-existing ancestor. If `path` already exists this is a no-op.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ensureDirectoryDurable(path: string, fs?: DurableFs): void;
|
|
35
|
+
/**
|
|
36
|
+
* Durably append one line to a JSONL-style log file. Sequence: create parent
|
|
37
|
+
* directories, open append/create with 0o600, write the complete UTF-8 line
|
|
38
|
+
* plus exactly one newline, fsync the file, close it, and fsync the parent
|
|
39
|
+
* directory when the file was newly created.
|
|
40
|
+
*
|
|
41
|
+
* Torn-append safety: the pre-append file length is recorded first (inside the
|
|
42
|
+
* caller's audit lock, so no other appender can interleave). If the write or
|
|
43
|
+
* fsync fails after some bytes may have landed, the file is truncated back to
|
|
44
|
+
* that length and flushed before the ORIGINAL error propagates, so a partial
|
|
45
|
+
* line can never be left behind to corrupt every subsequent append. A crash
|
|
46
|
+
* that prevents this handler from running is recovered explicitly by
|
|
47
|
+
* `repairAuditLogTail` (`catp log repair`), never silently by the append path.
|
|
48
|
+
*/
|
|
49
|
+
export declare function durableAppendLine(path: string, line: string, fs?: DurableFs): void;
|
|
50
|
+
/**
|
|
51
|
+
* Durably create an empty file (and its parent directories) if it does not
|
|
52
|
+
* already exist: open append/create with 0o600, fsync the new file, close it,
|
|
53
|
+
* then fsync the parent directory. When the file already exists the barrier is
|
|
54
|
+
* re-established (fsync the existing file + its parent directory) WITHOUT
|
|
55
|
+
* writing any bytes, so a prior attempt that created the file and then failed
|
|
56
|
+
* the parent-dir fsync cannot leave a non-durable entry behind on retry. This
|
|
57
|
+
* is used to materialize the audit log so a lock manager that resolves the
|
|
58
|
+
* target realpath never bypasses the new-file durability path.
|
|
59
|
+
*/
|
|
60
|
+
export declare function durableCreateEmptyFile(path: string, fs?: DurableFs): void;
|
|
61
|
+
/**
|
|
62
|
+
* Durably write content-addressed `bytes` to `path` via a same-directory temp
|
|
63
|
+
* file: create parents, write the temp with 0o600, fsync it, close, atomically
|
|
64
|
+
* rename onto the target, then fsync the parent directory.
|
|
65
|
+
*
|
|
66
|
+
* If the target already exists its bytes are compared: identical bytes
|
|
67
|
+
* re-establish the durability barrier (fsync the existing file, then its parent
|
|
68
|
+
* directory) and succeed WITHOUT rewriting, so a retry after "rename succeeded,
|
|
69
|
+
* parent-dir fsync failed" cannot skip the barrier; differing bytes are reported
|
|
70
|
+
* as corruption. Descriptors are closed and the temp file removed on any
|
|
71
|
+
* failure while preserving the original exception.
|
|
72
|
+
*/
|
|
73
|
+
export declare function durableWriteContentAddressed(path: string, bytes: Uint8Array, fs?: DurableFs): void;
|
|
74
|
+
/**
|
|
75
|
+
* Storage seam threaded through the audit logger and hook options. It exists
|
|
76
|
+
* solely so tests can inject failures; CLI execution always uses
|
|
77
|
+
* {@link nodeAuditStorage}.
|
|
78
|
+
*/
|
|
79
|
+
export interface AuditStorage {
|
|
80
|
+
ensureDirectoryDurable(path: string): void;
|
|
81
|
+
createEmptyFile(path: string): void;
|
|
82
|
+
appendLine(path: string, line: string): void;
|
|
83
|
+
writeContentAddressed(path: string, bytes: Uint8Array): void;
|
|
84
|
+
}
|
|
85
|
+
/** Production {@link AuditStorage} backed by the fsync durable primitives. */
|
|
86
|
+
export declare const nodeAuditStorage: AuditStorage;
|
|
87
|
+
//# sourceMappingURL=durable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable.d.ts","sourceRoot":"","sources":["../../src/audit/durable.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5D,SAAS,CACP,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAAC;IACV,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9C;AAED,0DAA0D;AAC1D,eAAO,MAAM,MAAM,EAAE,SAyBpB,CAAC;AA8CF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,SAAkB,GAAG,IAAI,CAsBjF;AAiBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,SAAkB,GAAG,IAAI,CAoC1F;AASD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAE,SAAkB,GAAG,IAAI,CA4BjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,EAAE,GAAE,SAAkB,GACrB,IAAI,CAiDN;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC9D;AAED,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,EAAE,YAK9B,CAAC"}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { closeSync, existsSync, fsyncSync, ftruncateSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeSync, } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
/** Production binding of {@link DurableFs} to node:fs. */
|
|
4
|
+
export const nodeFs = {
|
|
5
|
+
existsSync: (p) => existsSync(p),
|
|
6
|
+
mkdirSync: (p, opts) => {
|
|
7
|
+
mkdirSync(p, opts);
|
|
8
|
+
},
|
|
9
|
+
openSync: (p, flags, mode) => openSync(p, flags, mode),
|
|
10
|
+
writeSync: (fd, buffer, offset, length, position) => writeSync(fd, buffer, offset, length, position),
|
|
11
|
+
fsyncSync: (fd) => {
|
|
12
|
+
fsyncSync(fd);
|
|
13
|
+
},
|
|
14
|
+
closeSync: (fd) => {
|
|
15
|
+
closeSync(fd);
|
|
16
|
+
},
|
|
17
|
+
readFileSync: (p) => readFileSync(p),
|
|
18
|
+
renameSync: (o, n) => {
|
|
19
|
+
renameSync(o, n);
|
|
20
|
+
},
|
|
21
|
+
unlinkSync: (p) => {
|
|
22
|
+
unlinkSync(p);
|
|
23
|
+
},
|
|
24
|
+
statSync: (p) => statSync(p),
|
|
25
|
+
ftruncateSync: (fd, len) => {
|
|
26
|
+
ftruncateSync(fd, len);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const FILE_MODE = 0o600;
|
|
30
|
+
const DIR_MODE = 0o700;
|
|
31
|
+
/**
|
|
32
|
+
* Write every byte of `buffer`, looping until the whole buffer is consumed. A
|
|
33
|
+
* single writeSync call is not guaranteed to consume the entire buffer, so the
|
|
34
|
+
* returned count is advanced explicitly.
|
|
35
|
+
*/
|
|
36
|
+
function writeFull(fs, fd, buffer) {
|
|
37
|
+
let offset = 0;
|
|
38
|
+
while (offset < buffer.byteLength) {
|
|
39
|
+
const written = fs.writeSync(fd, buffer, offset, buffer.byteLength - offset, null);
|
|
40
|
+
if (written <= 0) {
|
|
41
|
+
throw new Error("durable write failed to make progress");
|
|
42
|
+
}
|
|
43
|
+
offset += written;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** fsync a directory so a newly created directory entry becomes durable. */
|
|
47
|
+
function fsyncDirectory(fs, dir) {
|
|
48
|
+
const fd = fs.openSync(dir, "r", DIR_MODE);
|
|
49
|
+
try {
|
|
50
|
+
fs.fsyncSync(fd);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
fs.closeSync(fd);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* fsync an existing regular file so its data and inode metadata are durable.
|
|
58
|
+
* Opened read-only with FILE_MODE; fsync does not require write access. This is
|
|
59
|
+
* the file half of the barrier the idempotent paths below re-run so a retry
|
|
60
|
+
* after a partial failure cannot report durability it never established.
|
|
61
|
+
*/
|
|
62
|
+
function fsyncExistingFile(fs, path) {
|
|
63
|
+
const fd = fs.openSync(path, "r", FILE_MODE);
|
|
64
|
+
try {
|
|
65
|
+
fs.fsyncSync(fd);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.closeSync(fd);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create `path` (and any missing ancestors) and make the new directory entries
|
|
73
|
+
* durable. Missing components are collected before creation, created with
|
|
74
|
+
* 0o700, then fsynced from the deepest component upward followed by the
|
|
75
|
+
* deepest pre-existing ancestor. If `path` already exists this is a no-op.
|
|
76
|
+
*/
|
|
77
|
+
export function ensureDirectoryDurable(path, fs = nodeFs) {
|
|
78
|
+
const target = resolve(path);
|
|
79
|
+
const missing = [];
|
|
80
|
+
let probe = target;
|
|
81
|
+
while (!fs.existsSync(probe)) {
|
|
82
|
+
missing.unshift(probe);
|
|
83
|
+
const parent = dirname(probe);
|
|
84
|
+
if (parent === probe)
|
|
85
|
+
break; // reached the filesystem root
|
|
86
|
+
probe = parent;
|
|
87
|
+
}
|
|
88
|
+
if (missing.length === 0)
|
|
89
|
+
return; // nothing created, nothing to flush
|
|
90
|
+
fs.mkdirSync(target, { recursive: true, mode: DIR_MODE });
|
|
91
|
+
for (let i = missing.length - 1; i >= 0; i--) {
|
|
92
|
+
fsyncDirectory(fs, missing[i]);
|
|
93
|
+
}
|
|
94
|
+
// Flush the pre-existing parent that now holds the shallowest new entry.
|
|
95
|
+
// Skip when the walk terminated at the filesystem root, in which case probe
|
|
96
|
+
// is itself one of the newly created components and was already flushed.
|
|
97
|
+
if (!missing.includes(probe)) {
|
|
98
|
+
fsyncDirectory(fs, probe);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Attach a rollback failure to the primary write/fsync error without masking
|
|
103
|
+
* it. The original error is what the caller must observe to fail closed, but a
|
|
104
|
+
* failed rollback (the file could not be restored to its pre-append length) is
|
|
105
|
+
* critical diagnostic context, so it is surfaced in the message and kept as the
|
|
106
|
+
* error `cause`.
|
|
107
|
+
*/
|
|
108
|
+
function annotateRollbackFailure(primary, rollbackErr) {
|
|
109
|
+
const rollbackMsg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
110
|
+
if (primary instanceof Error) {
|
|
111
|
+
primary.message = `${primary.message} (torn-append rollback also failed: ${rollbackMsg})`;
|
|
112
|
+
primary.cause = rollbackErr;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Durably append one line to a JSONL-style log file. Sequence: create parent
|
|
117
|
+
* directories, open append/create with 0o600, write the complete UTF-8 line
|
|
118
|
+
* plus exactly one newline, fsync the file, close it, and fsync the parent
|
|
119
|
+
* directory when the file was newly created.
|
|
120
|
+
*
|
|
121
|
+
* Torn-append safety: the pre-append file length is recorded first (inside the
|
|
122
|
+
* caller's audit lock, so no other appender can interleave). If the write or
|
|
123
|
+
* fsync fails after some bytes may have landed, the file is truncated back to
|
|
124
|
+
* that length and flushed before the ORIGINAL error propagates, so a partial
|
|
125
|
+
* line can never be left behind to corrupt every subsequent append. A crash
|
|
126
|
+
* that prevents this handler from running is recovered explicitly by
|
|
127
|
+
* `repairAuditLogTail` (`catp log repair`), never silently by the append path.
|
|
128
|
+
*/
|
|
129
|
+
export function durableAppendLine(path, line, fs = nodeFs) {
|
|
130
|
+
const dir = dirname(path);
|
|
131
|
+
ensureDirectoryDurable(dir, fs);
|
|
132
|
+
const existed = fs.existsSync(path);
|
|
133
|
+
// Rollback boundary: for an existing file this is its current length; for a
|
|
134
|
+
// brand-new file it is 0 (a failed create leaves an empty file, not a torn one).
|
|
135
|
+
const originalSize = existed ? fs.statSync(path).size : 0;
|
|
136
|
+
const fd = fs.openSync(path, "a", FILE_MODE);
|
|
137
|
+
let closed = false;
|
|
138
|
+
try {
|
|
139
|
+
writeFull(fs, fd, Buffer.from(line + "\n", "utf8"));
|
|
140
|
+
fs.fsyncSync(fd);
|
|
141
|
+
fs.closeSync(fd);
|
|
142
|
+
closed = true;
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// Roll back the torn append, then close without letting a secondary close
|
|
146
|
+
// error (POSIX can report a pending EIO on close after a failed fsync) mask
|
|
147
|
+
// the original write/fsync failure the caller must observe to fail closed.
|
|
148
|
+
if (!closed) {
|
|
149
|
+
try {
|
|
150
|
+
fs.ftruncateSync(fd, originalSize);
|
|
151
|
+
fs.fsyncSync(fd);
|
|
152
|
+
}
|
|
153
|
+
catch (rollbackErr) {
|
|
154
|
+
annotateRollbackFailure(err, rollbackErr);
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
fs.closeSync(fd);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// preserve the original exception
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
if (!existed) {
|
|
166
|
+
fsyncDirectory(fs, dir);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
let tempCounter = 0;
|
|
170
|
+
function uniqueTempPath(dir, base) {
|
|
171
|
+
tempCounter = (tempCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
172
|
+
return join(dir, `.${base}.${process.pid}.${Date.now()}.${tempCounter}.tmp`);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Durably create an empty file (and its parent directories) if it does not
|
|
176
|
+
* already exist: open append/create with 0o600, fsync the new file, close it,
|
|
177
|
+
* then fsync the parent directory. When the file already exists the barrier is
|
|
178
|
+
* re-established (fsync the existing file + its parent directory) WITHOUT
|
|
179
|
+
* writing any bytes, so a prior attempt that created the file and then failed
|
|
180
|
+
* the parent-dir fsync cannot leave a non-durable entry behind on retry. This
|
|
181
|
+
* is used to materialize the audit log so a lock manager that resolves the
|
|
182
|
+
* target realpath never bypasses the new-file durability path.
|
|
183
|
+
*/
|
|
184
|
+
export function durableCreateEmptyFile(path, fs = nodeFs) {
|
|
185
|
+
const dir = dirname(path);
|
|
186
|
+
ensureDirectoryDurable(dir, fs);
|
|
187
|
+
if (fs.existsSync(path)) {
|
|
188
|
+
// Idempotent path: re-run the barrier rather than returning blindly. A
|
|
189
|
+
// previous call may have created the file and thrown at the dir fsync, so
|
|
190
|
+
// success here must still imply the file's existence is durable.
|
|
191
|
+
fsyncExistingFile(fs, path);
|
|
192
|
+
fsyncDirectory(fs, dir);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const fd = fs.openSync(path, "a", FILE_MODE);
|
|
196
|
+
let closed = false;
|
|
197
|
+
try {
|
|
198
|
+
fs.fsyncSync(fd);
|
|
199
|
+
fs.closeSync(fd);
|
|
200
|
+
closed = true;
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
if (!closed) {
|
|
204
|
+
try {
|
|
205
|
+
fs.closeSync(fd);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// preserve the original exception
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
fsyncDirectory(fs, dir);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Durably write content-addressed `bytes` to `path` via a same-directory temp
|
|
217
|
+
* file: create parents, write the temp with 0o600, fsync it, close, atomically
|
|
218
|
+
* rename onto the target, then fsync the parent directory.
|
|
219
|
+
*
|
|
220
|
+
* If the target already exists its bytes are compared: identical bytes
|
|
221
|
+
* re-establish the durability barrier (fsync the existing file, then its parent
|
|
222
|
+
* directory) and succeed WITHOUT rewriting, so a retry after "rename succeeded,
|
|
223
|
+
* parent-dir fsync failed" cannot skip the barrier; differing bytes are reported
|
|
224
|
+
* as corruption. Descriptors are closed and the temp file removed on any
|
|
225
|
+
* failure while preserving the original exception.
|
|
226
|
+
*/
|
|
227
|
+
export function durableWriteContentAddressed(path, bytes, fs = nodeFs) {
|
|
228
|
+
const dir = dirname(path);
|
|
229
|
+
ensureDirectoryDurable(dir, fs);
|
|
230
|
+
if (fs.existsSync(path)) {
|
|
231
|
+
const existing = fs.readFileSync(path);
|
|
232
|
+
if (Buffer.compare(Buffer.from(bytes), existing) !== 0) {
|
|
233
|
+
throw new Error(`content-addressed file already exists with different bytes: ${path}`);
|
|
234
|
+
}
|
|
235
|
+
// Identical bytes are NOT an automatic success. A prior attempt may have
|
|
236
|
+
// renamed the temp file into place and then failed the parent-dir fsync, so
|
|
237
|
+
// neither the file's data nor its directory entry can be assumed durable.
|
|
238
|
+
// Re-run the full barrier and fail closed if either flush fails; only then
|
|
239
|
+
// report idempotent success. This is the exact window that let a retry
|
|
240
|
+
// append an audit entry whose action sidecar was never made durable.
|
|
241
|
+
fsyncExistingFile(fs, path);
|
|
242
|
+
fsyncDirectory(fs, dir);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const temp = uniqueTempPath(dir, basename(path));
|
|
246
|
+
const fd = fs.openSync(temp, "w", FILE_MODE);
|
|
247
|
+
let closed = false;
|
|
248
|
+
let renamed = false;
|
|
249
|
+
try {
|
|
250
|
+
writeFull(fs, fd, bytes);
|
|
251
|
+
fs.fsyncSync(fd);
|
|
252
|
+
fs.closeSync(fd);
|
|
253
|
+
closed = true;
|
|
254
|
+
fs.renameSync(temp, path);
|
|
255
|
+
renamed = true;
|
|
256
|
+
fsyncDirectory(fs, dir);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
if (!closed) {
|
|
260
|
+
try {
|
|
261
|
+
fs.closeSync(fd);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// ignore close failure; preserve the original error
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!renamed) {
|
|
268
|
+
try {
|
|
269
|
+
fs.unlinkSync(temp);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// best-effort temp cleanup; preserve the original error
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
throw err;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Production {@link AuditStorage} backed by the fsync durable primitives. */
|
|
279
|
+
export const nodeAuditStorage = {
|
|
280
|
+
ensureDirectoryDurable: (path) => ensureDirectoryDurable(path),
|
|
281
|
+
createEmptyFile: (path) => durableCreateEmptyFile(path),
|
|
282
|
+
appendLine: (path, line) => durableAppendLine(path, line),
|
|
283
|
+
writeContentAddressed: (path, bytes) => durableWriteContentAddressed(path, bytes),
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=durable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable.js","sourceRoot":"","sources":["../../src/audit/durable.ts"],"names":[],"mappings":"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"}
|
package/dist/audit/logger.d.ts
CHANGED
|
@@ -1,14 +1,89 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type CanonicalToolActionV1 } from "../evidence/commitments.js";
|
|
2
|
+
import { type AuditStorage } from "./durable.js";
|
|
3
|
+
import type { AuditEntry, AuditEntryV4, AuthorizationAction } from "../policy/types.js";
|
|
2
4
|
import type { ToolAction } from "../runtime/types.js";
|
|
3
5
|
import type { RuntimePhase } from "../runtime/types.js";
|
|
4
6
|
export declare function computeCommitment(tool: string, decision: "allow" | "deny", ts: string, prev?: string, ruleMatched?: string | null, inputSummary?: string, authorization?: AuthorizationAction, commitmentVersion?: 1 | 2 | 3, phase?: RuntimePhase): string;
|
|
5
7
|
export declare function summarizeInput(input: ToolAction): string;
|
|
8
|
+
export interface AuditEntryV4Fields {
|
|
9
|
+
phase: RuntimePhase;
|
|
10
|
+
tool: string;
|
|
11
|
+
decision: "allow" | "deny";
|
|
12
|
+
ts: string;
|
|
13
|
+
ruleMatched: string | null;
|
|
14
|
+
reason: string;
|
|
15
|
+
inputSummary: string;
|
|
16
|
+
policyCommitment: string;
|
|
17
|
+
actionCommitment: string;
|
|
18
|
+
authorization?: AuthorizationAction;
|
|
19
|
+
prev: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Version-4 entry commitment. Covers every security-relevant field with a
|
|
23
|
+
* domain separator: prev_commitment, phase, tool, decision, ts, rule, reason,
|
|
24
|
+
* the enforcement-time policy commitment, and the complete-action commitment.
|
|
25
|
+
* `inputSummary` is chained for display tamper-evidence only; the authoritative
|
|
26
|
+
* action binding is `actionCommitment`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeCommitmentV4(fields: AuditEntryV4Fields): string;
|
|
6
29
|
export declare function auditDir(agentId: string): string;
|
|
7
30
|
export declare function getLastCommitment(agentId: string): string;
|
|
8
|
-
export declare function appendAuditEntry(agentId: string, entry: AuditEntry): void;
|
|
31
|
+
export declare function appendAuditEntry(agentId: string, entry: AuditEntry, storage?: AuditStorage): void;
|
|
32
|
+
/**
|
|
33
|
+
* Append a v4 entry produced by `build`, persisting the complete canonical
|
|
34
|
+
* action as a content-addressed sidecar BEFORE the entry that references it.
|
|
35
|
+
*
|
|
36
|
+
* The sidecar write and the entry append both happen while holding the same
|
|
37
|
+
* per-log lock used to choose `prev_commitment`, so the ordering is atomic with
|
|
38
|
+
* respect to concurrent appends. A crash may leave an unreferenced sidecar, but
|
|
39
|
+
* never a committed entry whose action evidence was not flushed first. Any
|
|
40
|
+
* storage/fsync error propagates so the caller (pre-hook) can fail closed.
|
|
41
|
+
*/
|
|
9
42
|
export declare function appendChainedAuditEntry<T extends {
|
|
10
|
-
auditEntry:
|
|
11
|
-
|
|
12
|
-
|
|
43
|
+
auditEntry: AuditEntryV4;
|
|
44
|
+
action: CanonicalToolActionV1;
|
|
45
|
+
}>(agentId: string, build: (prevCommitment: string) => T, storage?: AuditStorage): T;
|
|
46
|
+
export interface AuditEntryBindings {
|
|
47
|
+
reason: string;
|
|
48
|
+
policyCommitment: string;
|
|
49
|
+
actionCommitment: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build a version-4 audit entry that binds the decision to the exact
|
|
53
|
+
* enforcement-time policy commitment and complete-action commitment. The caller
|
|
54
|
+
* (enforcement core) computes both commitments from the normalized policy and
|
|
55
|
+
* canonical action used for the decision; this function never recomputes them
|
|
56
|
+
* from mutable files.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildEntry(input: ToolAction, decision: "allow" | "deny", ruleMatched: string | null, prevCommitment: string, bindings: AuditEntryBindings): AuditEntryV4;
|
|
13
59
|
export declare function extractAuthorizationAction(input: ToolAction): AuthorizationAction | undefined;
|
|
60
|
+
export interface AuditRepairResult {
|
|
61
|
+
file: string;
|
|
62
|
+
/** `clean` = nothing to remove; `repaired` = a torn fragment was truncated. */
|
|
63
|
+
status: "clean" | "repaired";
|
|
64
|
+
/** Number of complete, chain-verified entries retained after the repair. */
|
|
65
|
+
entries: number;
|
|
66
|
+
/** The unterminated fragment that was truncated, or null when clean. */
|
|
67
|
+
removedFragment: string | null;
|
|
68
|
+
removedBytes: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Explicitly repair a torn audit-log tail left by a crashed append. This is the
|
|
72
|
+
* ONLY sanctioned recovery path for a partial final line; the append path itself
|
|
73
|
+
* stays fail-closed and never self-heals silently.
|
|
74
|
+
*
|
|
75
|
+
* Lock-protected (same per-log lock appends use) and deliberately conservative:
|
|
76
|
+
* 1. The complete, newline-terminated prefix is parsed and its hash chain
|
|
77
|
+
* verified BEFORE anything is removed. A corrupt or chain-broken COMPLETE
|
|
78
|
+
* entry is never auto-deleted -- such a file is reported and left untouched.
|
|
79
|
+
* 2. Only a trailing fragment that CANNOT be a complete JSONL entry (it does not
|
|
80
|
+
* parse as JSON) is truncated, back to the last verified newline boundary.
|
|
81
|
+
* 3. A trailing fragment that DOES parse as a complete JSON object is left
|
|
82
|
+
* alone and reported: it may be a genuine entry whose newline never flushed,
|
|
83
|
+
* so guessing would risk destroying valid evidence.
|
|
84
|
+
*
|
|
85
|
+
* Repairs the current (today's) daily log for the agent, which is the file new
|
|
86
|
+
* appends target and therefore the one a torn tail would block.
|
|
87
|
+
*/
|
|
88
|
+
export declare function repairAuditLogTail(agentId: string, storage?: AuditStorage): AuditRepairResult;
|
|
14
89
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/audit/logger.ts"],"names":[],"mappings":"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"}
|