@flowdot.ai/guardian-agent 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/LICENSE +40 -0
- package/README.md +281 -0
- package/ROADMAP.md +109 -0
- package/dist/audit/attestor.d.ts +102 -0
- package/dist/audit/attestor.d.ts.map +1 -0
- package/dist/audit/attestor.js +103 -0
- package/dist/audit/attestor.js.map +1 -0
- package/dist/audit/chain.d.ts +30 -0
- package/dist/audit/chain.d.ts.map +1 -0
- package/dist/audit/chain.js +65 -0
- package/dist/audit/chain.js.map +1 -0
- package/dist/audit/correlation.d.ts +114 -0
- package/dist/audit/correlation.d.ts.map +1 -0
- package/dist/audit/correlation.js +259 -0
- package/dist/audit/correlation.js.map +1 -0
- package/dist/audit/index.d.ts +13 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +8 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/reader.d.ts +30 -0
- package/dist/audit/reader.d.ts.map +1 -0
- package/dist/audit/reader.js +85 -0
- package/dist/audit/reader.js.map +1 -0
- package/dist/audit/signature.d.ts +39 -0
- package/dist/audit/signature.d.ts.map +1 -0
- package/dist/audit/signature.js +73 -0
- package/dist/audit/signature.js.map +1 -0
- package/dist/audit/stats.d.ts +106 -0
- package/dist/audit/stats.d.ts.map +1 -0
- package/dist/audit/stats.js +196 -0
- package/dist/audit/stats.js.map +1 -0
- package/dist/audit/writer.d.ts +96 -0
- package/dist/audit/writer.d.ts.map +1 -0
- package/dist/audit/writer.js +263 -0
- package/dist/audit/writer.js.map +1 -0
- package/dist/cli/guardian-baseline.d.ts +42 -0
- package/dist/cli/guardian-baseline.d.ts.map +1 -0
- package/dist/cli/guardian-baseline.js +265 -0
- package/dist/cli/guardian-baseline.js.map +1 -0
- package/dist/cli/guardian-correlator.d.ts +47 -0
- package/dist/cli/guardian-correlator.d.ts.map +1 -0
- package/dist/cli/guardian-correlator.js +217 -0
- package/dist/cli/guardian-correlator.js.map +1 -0
- package/dist/cli/guardian-verify.d.ts +30 -0
- package/dist/cli/guardian-verify.d.ts.map +1 -0
- package/dist/cli/guardian-verify.js +149 -0
- package/dist/cli/guardian-verify.js.map +1 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -0
- package/dist/estop/heartbeat.d.ts +94 -0
- package/dist/estop/heartbeat.d.ts.map +1 -0
- package/dist/estop/heartbeat.js +135 -0
- package/dist/estop/heartbeat.js.map +1 -0
- package/dist/estop/hub.d.ts +76 -0
- package/dist/estop/hub.d.ts.map +1 -0
- package/dist/estop/hub.js +167 -0
- package/dist/estop/hub.js.map +1 -0
- package/dist/estop/index.d.ts +12 -0
- package/dist/estop/index.d.ts.map +1 -0
- package/dist/estop/index.js +6 -0
- package/dist/estop/index.js.map +1 -0
- package/dist/estop/local.d.ts +31 -0
- package/dist/estop/local.d.ts.map +1 -0
- package/dist/estop/local.js +101 -0
- package/dist/estop/local.js.map +1 -0
- package/dist/estop/middleware.d.ts +36 -0
- package/dist/estop/middleware.d.ts.map +1 -0
- package/dist/estop/middleware.js +40 -0
- package/dist/estop/middleware.js.map +1 -0
- package/dist/estop/poller.d.ts +36 -0
- package/dist/estop/poller.d.ts.map +1 -0
- package/dist/estop/poller.js +85 -0
- package/dist/estop/poller.js.map +1 -0
- package/dist/estop/types.d.ts +31 -0
- package/dist/estop/types.d.ts.map +1 -0
- package/dist/estop/types.js +5 -0
- package/dist/estop/types.js.map +1 -0
- package/dist/gate/async-callback.d.ts +27 -0
- package/dist/gate/async-callback.d.ts.map +1 -0
- package/dist/gate/async-callback.js +79 -0
- package/dist/gate/async-callback.js.map +1 -0
- package/dist/gate/cli.d.ts +29 -0
- package/dist/gate/cli.d.ts.map +1 -0
- package/dist/gate/cli.js +83 -0
- package/dist/gate/cli.js.map +1 -0
- package/dist/gate/data-channel.d.ts +41 -0
- package/dist/gate/data-channel.d.ts.map +1 -0
- package/dist/gate/data-channel.js +132 -0
- package/dist/gate/data-channel.js.map +1 -0
- package/dist/gate/index.d.ts +13 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/index.js +7 -0
- package/dist/gate/index.js.map +1 -0
- package/dist/gate/options.d.ts +90 -0
- package/dist/gate/options.d.ts.map +1 -0
- package/dist/gate/options.js +131 -0
- package/dist/gate/options.js.map +1 -0
- package/dist/gate/programmatic.d.ts +9 -0
- package/dist/gate/programmatic.d.ts.map +1 -0
- package/dist/gate/programmatic.js +20 -0
- package/dist/gate/programmatic.js.map +1 -0
- package/dist/gate/two-key.d.ts +90 -0
- package/dist/gate/two-key.d.ts.map +1 -0
- package/dist/gate/two-key.js +78 -0
- package/dist/gate/two-key.js.map +1 -0
- package/dist/gate/types.d.ts +25 -0
- package/dist/gate/types.d.ts.map +1 -0
- package/dist/gate/types.js +5 -0
- package/dist/gate/types.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/notify/console.d.ts +13 -0
- package/dist/notify/console.d.ts.map +1 -0
- package/dist/notify/console.js +27 -0
- package/dist/notify/console.js.map +1 -0
- package/dist/notify/index.d.ts +8 -0
- package/dist/notify/index.d.ts.map +1 -0
- package/dist/notify/index.js +4 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/multi.d.ts +14 -0
- package/dist/notify/multi.d.ts.map +1 -0
- package/dist/notify/multi.js +22 -0
- package/dist/notify/multi.js.map +1 -0
- package/dist/notify/types.d.ts +21 -0
- package/dist/notify/types.d.ts.map +1 -0
- package/dist/notify/types.js +5 -0
- package/dist/notify/types.js.map +1 -0
- package/dist/notify/webhook.d.ts +21 -0
- package/dist/notify/webhook.d.ts.map +1 -0
- package/dist/notify/webhook.js +37 -0
- package/dist/notify/webhook.js.map +1 -0
- package/dist/policy/attribution.d.ts +61 -0
- package/dist/policy/attribution.d.ts.map +1 -0
- package/dist/policy/attribution.js +116 -0
- package/dist/policy/attribution.js.map +1 -0
- package/dist/policy/evaluator.d.ts +36 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +211 -0
- package/dist/policy/evaluator.js.map +1 -0
- package/dist/policy/index.d.ts +11 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +7 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/integrity.d.ts +17 -0
- package/dist/policy/integrity.d.ts.map +1 -0
- package/dist/policy/integrity.js +31 -0
- package/dist/policy/integrity.js.map +1 -0
- package/dist/policy/loader.d.ts +9 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +124 -0
- package/dist/policy/loader.js.map +1 -0
- package/dist/policy/site-key.d.ts +22 -0
- package/dist/policy/site-key.d.ts.map +1 -0
- package/dist/policy/site-key.js +48 -0
- package/dist/policy/site-key.js.map +1 -0
- package/dist/policy/store.d.ts +45 -0
- package/dist/policy/store.d.ts.map +1 -0
- package/dist/policy/store.js +223 -0
- package/dist/policy/store.js.map +1 -0
- package/dist/policy/types.d.ts +72 -0
- package/dist/policy/types.d.ts.map +1 -0
- package/dist/policy/types.js +5 -0
- package/dist/policy/types.js.map +1 -0
- package/dist/runtime/capability.d.ts +125 -0
- package/dist/runtime/capability.d.ts.map +1 -0
- package/dist/runtime/capability.js +121 -0
- package/dist/runtime/capability.js.map +1 -0
- package/dist/runtime/honeytokens.d.ts +104 -0
- package/dist/runtime/honeytokens.d.ts.map +1 -0
- package/dist/runtime/honeytokens.js +115 -0
- package/dist/runtime/honeytokens.js.map +1 -0
- package/dist/runtime/multi-rate-limiter.d.ts +90 -0
- package/dist/runtime/multi-rate-limiter.d.ts.map +1 -0
- package/dist/runtime/multi-rate-limiter.js +133 -0
- package/dist/runtime/multi-rate-limiter.js.map +1 -0
- package/dist/runtime/runtime.d.ts +94 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +276 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ed25519 signatures for audit-log records. SPEC §2.6.
|
|
3
|
+
*
|
|
4
|
+
* The signature is computed over the canonical bytes of the record with
|
|
5
|
+
* `signature: null`. Library uses Node's built-in `crypto.sign` /
|
|
6
|
+
* `crypto.verify` for ed25519 — no native deps.
|
|
7
|
+
*/
|
|
8
|
+
import { type KeyObject } from 'node:crypto';
|
|
9
|
+
import type { AuditRecord } from '../types.js';
|
|
10
|
+
declare const PREFIX = "ed25519:";
|
|
11
|
+
/** Generated key pair. */
|
|
12
|
+
export interface Ed25519KeyPair {
|
|
13
|
+
privateKey: KeyObject;
|
|
14
|
+
publicKey: KeyObject;
|
|
15
|
+
}
|
|
16
|
+
/** Generate a fresh ed25519 key pair. */
|
|
17
|
+
export declare function generateEd25519KeyPair(): Ed25519KeyPair;
|
|
18
|
+
/** Load an ed25519 private key from a PEM string or Buffer. */
|
|
19
|
+
export declare function loadPrivateKey(pem: string | Buffer): KeyObject;
|
|
20
|
+
/** Load an ed25519 public key from a PEM string or Buffer. */
|
|
21
|
+
export declare function loadPublicKey(pem: string | Buffer): KeyObject;
|
|
22
|
+
/**
|
|
23
|
+
* Sign a record's canonical bytes. Returns `ed25519:<base64url>` per SPEC §2.6.
|
|
24
|
+
*
|
|
25
|
+
* The record passed in MUST have `signature: null` (canonicalizeForHash strips
|
|
26
|
+
* it, but we keep the convention clean).
|
|
27
|
+
*/
|
|
28
|
+
export declare function signRecord(record: AuditRecord, privateKey: KeyObject): string;
|
|
29
|
+
/**
|
|
30
|
+
* Verify a record's signature. Returns true iff the signature matches the
|
|
31
|
+
* record's canonical bytes under the given public key.
|
|
32
|
+
*
|
|
33
|
+
* Records without a signature field (`null`) return false — caller must
|
|
34
|
+
* decide whether to allow unsigned records (typically only at compatibility
|
|
35
|
+
* boundaries).
|
|
36
|
+
*/
|
|
37
|
+
export declare function verifyRecord(record: AuditRecord, publicKey: KeyObject): boolean;
|
|
38
|
+
export { PREFIX as SIGNATURE_PREFIX };
|
|
39
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/audit/signature.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,QAAA,MAAM,MAAM,aAAa,CAAC;AAE1B,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,yCAAyC;AACzC,wBAAgB,sBAAsB,IAAI,cAAc,CAGvD;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,GAAG,MAAM,CAI7E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAY/E;AAeD,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ed25519 signatures for audit-log records. SPEC §2.6.
|
|
3
|
+
*
|
|
4
|
+
* The signature is computed over the canonical bytes of the record with
|
|
5
|
+
* `signature: null`. Library uses Node's built-in `crypto.sign` /
|
|
6
|
+
* `crypto.verify` for ed25519 — no native deps.
|
|
7
|
+
*/
|
|
8
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, } from 'node:crypto';
|
|
9
|
+
import { canonicalizeForHash } from './chain.js';
|
|
10
|
+
const PREFIX = 'ed25519:';
|
|
11
|
+
/** Generate a fresh ed25519 key pair. */
|
|
12
|
+
export function generateEd25519KeyPair() {
|
|
13
|
+
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
|
14
|
+
return { privateKey, publicKey };
|
|
15
|
+
}
|
|
16
|
+
/** Load an ed25519 private key from a PEM string or Buffer. */
|
|
17
|
+
export function loadPrivateKey(pem) {
|
|
18
|
+
return createPrivateKey(pem);
|
|
19
|
+
}
|
|
20
|
+
/** Load an ed25519 public key from a PEM string or Buffer. */
|
|
21
|
+
export function loadPublicKey(pem) {
|
|
22
|
+
return createPublicKey(pem);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign a record's canonical bytes. Returns `ed25519:<base64url>` per SPEC §2.6.
|
|
26
|
+
*
|
|
27
|
+
* The record passed in MUST have `signature: null` (canonicalizeForHash strips
|
|
28
|
+
* it, but we keep the convention clean).
|
|
29
|
+
*/
|
|
30
|
+
export function signRecord(record, privateKey) {
|
|
31
|
+
const canonical = canonicalizeForHash(record);
|
|
32
|
+
const sig = sign(null, canonical, privateKey);
|
|
33
|
+
return PREFIX + base64url(sig);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Verify a record's signature. Returns true iff the signature matches the
|
|
37
|
+
* record's canonical bytes under the given public key.
|
|
38
|
+
*
|
|
39
|
+
* Records without a signature field (`null`) return false — caller must
|
|
40
|
+
* decide whether to allow unsigned records (typically only at compatibility
|
|
41
|
+
* boundaries).
|
|
42
|
+
*/
|
|
43
|
+
export function verifyRecord(record, publicKey) {
|
|
44
|
+
if (record.signature == null)
|
|
45
|
+
return false;
|
|
46
|
+
if (typeof record.signature !== 'string')
|
|
47
|
+
return false;
|
|
48
|
+
if (!record.signature.startsWith(PREFIX))
|
|
49
|
+
return false;
|
|
50
|
+
const sigBytes = base64urlDecode(record.signature.slice(PREFIX.length));
|
|
51
|
+
if (sigBytes === null)
|
|
52
|
+
return false;
|
|
53
|
+
const canonical = canonicalizeForHash(record);
|
|
54
|
+
try {
|
|
55
|
+
return verify(null, canonical, publicKey, sigBytes);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ---- base64url helpers --------------------------------------------------
|
|
62
|
+
function base64url(buf) {
|
|
63
|
+
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
64
|
+
}
|
|
65
|
+
function base64urlDecode(s) {
|
|
66
|
+
// Validate characters — Node Buffer.from is permissive otherwise.
|
|
67
|
+
if (!/^[A-Za-z0-9_-]*$/.test(s))
|
|
68
|
+
return null;
|
|
69
|
+
const normalized = s.replace(/-/g, '+').replace(/_/g, '/');
|
|
70
|
+
return Buffer.from(normalized, 'base64');
|
|
71
|
+
}
|
|
72
|
+
export { PREFIX as SIGNATURE_PREFIX };
|
|
73
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/audit/signature.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,IAAI,EACJ,MAAM,GAEP,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,GAAG,UAAU,CAAC;AAQ1B,yCAAyC;AACzC,MAAM,UAAU,sBAAsB;IACpC,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACjE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACnC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,GAAoB;IACjD,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,aAAa,CAAC,GAAoB;IAChD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAmB,EAAE,UAAqB;IACnE,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAC9C,OAAO,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB,EAAE,SAAoB;IACpE,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,4EAA4E;AAE5E,SAAS,SAAS,CAAC,GAAW;IAC5B,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,eAAe,CAAC,CAAS;IAChC,kEAAkE;IAClE,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3D,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,OAAO,EAAE,MAAM,IAAI,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral baselines + descriptive statistics on audit-record streams.
|
|
3
|
+
* SPEC §13 (v0.5.0+). Used by the offline `guardian-baseline` CLI; NEVER
|
|
4
|
+
* consulted by the supervisor in the hot path (that would re-introduce
|
|
5
|
+
* judgment).
|
|
6
|
+
*
|
|
7
|
+
* All functions are pure: same input → same output. The "is this
|
|
8
|
+
* deviation significant?" question is mathematically grounded (mean + σ
|
|
9
|
+
* thresholds) but operationally judgment-laden, so the library produces
|
|
10
|
+
* descriptive reports and lets the operator decide what to do.
|
|
11
|
+
*/
|
|
12
|
+
import type { AuditRecord } from '../types.js';
|
|
13
|
+
export interface AgentProfile {
|
|
14
|
+
/** agent_id this profile describes. */
|
|
15
|
+
agent_id: string;
|
|
16
|
+
/** Schema version (bumped on incompatible profile-shape changes). */
|
|
17
|
+
v: '1';
|
|
18
|
+
/** Number of distinct session_id values observed. */
|
|
19
|
+
session_count: number;
|
|
20
|
+
/** Total records consumed (across kinds). */
|
|
21
|
+
total_records: number;
|
|
22
|
+
/** Total `tool_call` records observed. */
|
|
23
|
+
tool_call_count: number;
|
|
24
|
+
/** Mean events per session. */
|
|
25
|
+
avg_session_length_events: number;
|
|
26
|
+
/** Standard deviation of events-per-session. */
|
|
27
|
+
stddev_session_length_events: number;
|
|
28
|
+
/** Mean session wall-clock duration in ms. */
|
|
29
|
+
avg_session_duration_ms: number;
|
|
30
|
+
/** Standard deviation of session wall-clock duration in ms. */
|
|
31
|
+
stddev_session_duration_ms: number;
|
|
32
|
+
/** Per-tool call counts. Keys are tool names, values are integer counts. */
|
|
33
|
+
tool_frequency: Record<string, number>;
|
|
34
|
+
/** Counts per hour-of-day [0..23] from the record's `ts`. */
|
|
35
|
+
hour_of_day: number[];
|
|
36
|
+
/**
|
|
37
|
+
* Counts per audit-record kind. Useful for spotting shifts like
|
|
38
|
+
* "this agent suddenly emits a lot of denials."
|
|
39
|
+
*/
|
|
40
|
+
kind_frequency: Record<string, number>;
|
|
41
|
+
/** Counts per audit status. */
|
|
42
|
+
status_frequency: Record<string, number>;
|
|
43
|
+
/** ISO-8601 timestamp of the earliest record seen. */
|
|
44
|
+
first_ts: string | null;
|
|
45
|
+
/** ISO-8601 timestamp of the latest record seen. */
|
|
46
|
+
last_ts: string | null;
|
|
47
|
+
/** Stamp recording when the profile itself was produced. */
|
|
48
|
+
generated_at: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build an {@link AgentProfile} for a single agent_id from a flat list of
|
|
52
|
+
* audit records. Records belonging to other agent_ids are skipped silently
|
|
53
|
+
* — callers can either pre-filter or use {@link analyzeMultiAgent} to get
|
|
54
|
+
* profiles per agent.
|
|
55
|
+
*
|
|
56
|
+
* Deterministic given the same input.
|
|
57
|
+
*/
|
|
58
|
+
export declare function analyzeAgent(records: AuditRecord[], agentId: string): AgentProfile;
|
|
59
|
+
/**
|
|
60
|
+
* Bucket records by agent_id and build a profile for each. Returns a Map
|
|
61
|
+
* keyed by agent_id (declaration order — Map insertion order matches first
|
|
62
|
+
* occurrence in the input stream, for stable test output).
|
|
63
|
+
*/
|
|
64
|
+
export declare function analyzeMultiAgent(records: AuditRecord[]): Map<string, AgentProfile>;
|
|
65
|
+
export interface Deviation {
|
|
66
|
+
/** Which metric deviated. */
|
|
67
|
+
metric: string;
|
|
68
|
+
/** Observed value in the candidate session. */
|
|
69
|
+
observed: number;
|
|
70
|
+
/** Baseline mean (or count, depending on metric). */
|
|
71
|
+
baseline: number;
|
|
72
|
+
/** Number of σ from baseline. `null` when baseline has σ=0 (degenerate). */
|
|
73
|
+
sigma: number | null;
|
|
74
|
+
/** Free-form human label. */
|
|
75
|
+
note: string;
|
|
76
|
+
}
|
|
77
|
+
export interface DeviationReport {
|
|
78
|
+
agent_id: string;
|
|
79
|
+
/** ISO-8601 of the candidate session's first record. */
|
|
80
|
+
candidate_first_ts: string | null;
|
|
81
|
+
/** ISO-8601 of the candidate session's last record. */
|
|
82
|
+
candidate_last_ts: string | null;
|
|
83
|
+
/** All deviations exceeding the configured threshold, in observed order. */
|
|
84
|
+
deviations: Deviation[];
|
|
85
|
+
}
|
|
86
|
+
export interface CompareOptions {
|
|
87
|
+
/** σ threshold for flagging. Default 3. */
|
|
88
|
+
sigmaThreshold?: number;
|
|
89
|
+
/** Minimum sessions required in baseline before σ checks are meaningful. */
|
|
90
|
+
minBaselineSessions?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compare a candidate profile against a saved baseline. Returns the set of
|
|
94
|
+
* deviations that exceeded `sigmaThreshold` σ. When σ is undefined (the
|
|
95
|
+
* baseline only has one session, so σ=0), the metric is reported with
|
|
96
|
+
* `sigma: null` and is included only if the observed value differs from
|
|
97
|
+
* baseline at all.
|
|
98
|
+
*
|
|
99
|
+
* Deviation list is ordered by metric name for stable output.
|
|
100
|
+
*/
|
|
101
|
+
export declare function compareToBaseline(candidate: AgentProfile, baseline: AgentProfile, options?: CompareOptions): DeviationReport;
|
|
102
|
+
/** Arithmetic mean. Returns 0 for empty input. */
|
|
103
|
+
export declare function mean(values: readonly number[]): number;
|
|
104
|
+
/** Population standard deviation. Returns 0 for inputs of length < 2. */
|
|
105
|
+
export declare function stddev(values: readonly number[]): number;
|
|
106
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/audit/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,CAAC,EAAE,GAAG,CAAC;IACP,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,yBAAyB,EAAE,MAAM,CAAC;IAClC,gDAAgD;IAChD,4BAA4B,EAAE,MAAM,CAAC;IACrC,8CAA8C;IAC9C,uBAAuB,EAAE,MAAM,CAAC;IAChC,+DAA+D;IAC/D,0BAA0B,EAAE,MAAM,CAAC;IACnC,4EAA4E;IAC5E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,6DAA6D;IAC7D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,sDAAsD;IACtD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oDAAoD;IACpD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAGlF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAenF;AAwED,MAAM,WAAW,SAAS;IACxB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,uDAAuD;IACvD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,4EAA4E;IAC5E,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,YAAY,EACvB,QAAQ,EAAE,YAAY,EACtB,OAAO,GAAE,cAAmB,GAC3B,eAAe,CAkCjB;AAsCD,kDAAkD;AAClD,wBAAgB,IAAI,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAKtD;AAED,yEAAyE;AACzE,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CASxD"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavioral baselines + descriptive statistics on audit-record streams.
|
|
3
|
+
* SPEC §13 (v0.5.0+). Used by the offline `guardian-baseline` CLI; NEVER
|
|
4
|
+
* consulted by the supervisor in the hot path (that would re-introduce
|
|
5
|
+
* judgment).
|
|
6
|
+
*
|
|
7
|
+
* All functions are pure: same input → same output. The "is this
|
|
8
|
+
* deviation significant?" question is mathematically grounded (mean + σ
|
|
9
|
+
* thresholds) but operationally judgment-laden, so the library produces
|
|
10
|
+
* descriptive reports and lets the operator decide what to do.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Build an {@link AgentProfile} for a single agent_id from a flat list of
|
|
14
|
+
* audit records. Records belonging to other agent_ids are skipped silently
|
|
15
|
+
* — callers can either pre-filter or use {@link analyzeMultiAgent} to get
|
|
16
|
+
* profiles per agent.
|
|
17
|
+
*
|
|
18
|
+
* Deterministic given the same input.
|
|
19
|
+
*/
|
|
20
|
+
export function analyzeAgent(records, agentId) {
|
|
21
|
+
const filtered = records.filter((r) => r.agent_id === agentId);
|
|
22
|
+
return buildProfile(filtered, agentId);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Bucket records by agent_id and build a profile for each. Returns a Map
|
|
26
|
+
* keyed by agent_id (declaration order — Map insertion order matches first
|
|
27
|
+
* occurrence in the input stream, for stable test output).
|
|
28
|
+
*/
|
|
29
|
+
export function analyzeMultiAgent(records) {
|
|
30
|
+
const groups = new Map();
|
|
31
|
+
for (const r of records) {
|
|
32
|
+
let g = groups.get(r.agent_id);
|
|
33
|
+
if (!g) {
|
|
34
|
+
g = [];
|
|
35
|
+
groups.set(r.agent_id, g);
|
|
36
|
+
}
|
|
37
|
+
g.push(r);
|
|
38
|
+
}
|
|
39
|
+
const out = new Map();
|
|
40
|
+
for (const [agentId, group] of groups) {
|
|
41
|
+
out.set(agentId, buildProfile(group, agentId));
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function buildProfile(records, agentId) {
|
|
46
|
+
// Session bookkeeping
|
|
47
|
+
const sessionEvents = new Map();
|
|
48
|
+
for (const r of records) {
|
|
49
|
+
const list = sessionEvents.get(r.session_id) ?? [];
|
|
50
|
+
list.push(r);
|
|
51
|
+
sessionEvents.set(r.session_id, list);
|
|
52
|
+
}
|
|
53
|
+
const eventsPerSession = [];
|
|
54
|
+
const durationsMs = [];
|
|
55
|
+
for (const list of sessionEvents.values()) {
|
|
56
|
+
eventsPerSession.push(list.length);
|
|
57
|
+
if (list.length < 2) {
|
|
58
|
+
durationsMs.push(0);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const sorted = [...list].sort((a, b) => a.ts.localeCompare(b.ts));
|
|
62
|
+
const first = sorted[0].ts;
|
|
63
|
+
const last = sorted[sorted.length - 1].ts;
|
|
64
|
+
durationsMs.push(new Date(last).getTime() - new Date(first).getTime());
|
|
65
|
+
}
|
|
66
|
+
const toolFrequency = {};
|
|
67
|
+
const kindFrequency = {};
|
|
68
|
+
const statusFrequency = {};
|
|
69
|
+
const hourOfDay = new Array(24).fill(0);
|
|
70
|
+
let toolCallCount = 0;
|
|
71
|
+
let firstTs = null;
|
|
72
|
+
let lastTs = null;
|
|
73
|
+
for (const r of records) {
|
|
74
|
+
kindFrequency[r.kind] = (kindFrequency[r.kind] ?? 0) + 1;
|
|
75
|
+
statusFrequency[r.status] = (statusFrequency[r.status] ?? 0) + 1;
|
|
76
|
+
if (r.kind === 'tool_call' && r.tool) {
|
|
77
|
+
toolCallCount += 1;
|
|
78
|
+
toolFrequency[r.tool.name] = (toolFrequency[r.tool.name] ?? 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
const hour = new Date(r.ts).getUTCHours();
|
|
81
|
+
if (!Number.isNaN(hour)) {
|
|
82
|
+
hourOfDay[hour] = hourOfDay[hour] + 1;
|
|
83
|
+
}
|
|
84
|
+
if (firstTs === null || r.ts < firstTs)
|
|
85
|
+
firstTs = r.ts;
|
|
86
|
+
if (lastTs === null || r.ts > lastTs)
|
|
87
|
+
lastTs = r.ts;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
agent_id: agentId,
|
|
91
|
+
v: '1',
|
|
92
|
+
session_count: sessionEvents.size,
|
|
93
|
+
total_records: records.length,
|
|
94
|
+
tool_call_count: toolCallCount,
|
|
95
|
+
avg_session_length_events: mean(eventsPerSession),
|
|
96
|
+
stddev_session_length_events: stddev(eventsPerSession),
|
|
97
|
+
avg_session_duration_ms: mean(durationsMs),
|
|
98
|
+
stddev_session_duration_ms: stddev(durationsMs),
|
|
99
|
+
tool_frequency: toolFrequency,
|
|
100
|
+
hour_of_day: hourOfDay,
|
|
101
|
+
kind_frequency: kindFrequency,
|
|
102
|
+
status_frequency: statusFrequency,
|
|
103
|
+
first_ts: firstTs,
|
|
104
|
+
last_ts: lastTs,
|
|
105
|
+
generated_at: new Date().toISOString(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compare a candidate profile against a saved baseline. Returns the set of
|
|
110
|
+
* deviations that exceeded `sigmaThreshold` σ. When σ is undefined (the
|
|
111
|
+
* baseline only has one session, so σ=0), the metric is reported with
|
|
112
|
+
* `sigma: null` and is included only if the observed value differs from
|
|
113
|
+
* baseline at all.
|
|
114
|
+
*
|
|
115
|
+
* Deviation list is ordered by metric name for stable output.
|
|
116
|
+
*/
|
|
117
|
+
export function compareToBaseline(candidate, baseline, options = {}) {
|
|
118
|
+
const threshold = options.sigmaThreshold ?? 3;
|
|
119
|
+
const minSessions = options.minBaselineSessions ?? 2;
|
|
120
|
+
const deviations = [];
|
|
121
|
+
if (baseline.session_count >= minSessions) {
|
|
122
|
+
pushIfDeviated(deviations, 'avg_session_length_events', candidate.avg_session_length_events, baseline.avg_session_length_events, baseline.stddev_session_length_events, threshold);
|
|
123
|
+
pushIfDeviated(deviations, 'avg_session_duration_ms', candidate.avg_session_duration_ms, baseline.avg_session_duration_ms, baseline.stddev_session_duration_ms, threshold);
|
|
124
|
+
}
|
|
125
|
+
// Tool-frequency: flag any tool that appears in candidate >0 times but
|
|
126
|
+
// 0 times in baseline (and vice versa, though that's less interesting).
|
|
127
|
+
const baselineTools = new Set(Object.keys(baseline.tool_frequency));
|
|
128
|
+
for (const [tool, count] of Object.entries(candidate.tool_frequency)) {
|
|
129
|
+
if (!baselineTools.has(tool)) {
|
|
130
|
+
deviations.push({
|
|
131
|
+
metric: `tool_frequency.${tool}`,
|
|
132
|
+
observed: count,
|
|
133
|
+
baseline: 0,
|
|
134
|
+
sigma: null,
|
|
135
|
+
note: 'tool not present in baseline',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Stable order.
|
|
140
|
+
deviations.sort((a, b) => a.metric.localeCompare(b.metric));
|
|
141
|
+
return {
|
|
142
|
+
agent_id: candidate.agent_id,
|
|
143
|
+
candidate_first_ts: candidate.first_ts,
|
|
144
|
+
candidate_last_ts: candidate.last_ts,
|
|
145
|
+
deviations,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function pushIfDeviated(out, metric, observed, baselineMean, baselineStddev, threshold) {
|
|
149
|
+
if (baselineStddev === 0) {
|
|
150
|
+
if (observed !== baselineMean) {
|
|
151
|
+
out.push({
|
|
152
|
+
metric,
|
|
153
|
+
observed,
|
|
154
|
+
baseline: baselineMean,
|
|
155
|
+
sigma: null,
|
|
156
|
+
note: 'baseline σ=0; any difference is flagged',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const sigma = Math.abs(observed - baselineMean) / baselineStddev;
|
|
162
|
+
if (sigma >= threshold) {
|
|
163
|
+
out.push({
|
|
164
|
+
metric,
|
|
165
|
+
observed,
|
|
166
|
+
baseline: baselineMean,
|
|
167
|
+
sigma,
|
|
168
|
+
note: `${sigma.toFixed(2)}σ from baseline mean ${baselineMean.toFixed(2)}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Small stats helpers
|
|
174
|
+
// ============================================================================
|
|
175
|
+
/** Arithmetic mean. Returns 0 for empty input. */
|
|
176
|
+
export function mean(values) {
|
|
177
|
+
if (values.length === 0)
|
|
178
|
+
return 0;
|
|
179
|
+
let sum = 0;
|
|
180
|
+
for (const v of values)
|
|
181
|
+
sum += v;
|
|
182
|
+
return sum / values.length;
|
|
183
|
+
}
|
|
184
|
+
/** Population standard deviation. Returns 0 for inputs of length < 2. */
|
|
185
|
+
export function stddev(values) {
|
|
186
|
+
if (values.length < 2)
|
|
187
|
+
return 0;
|
|
188
|
+
const m = mean(values);
|
|
189
|
+
let sumSq = 0;
|
|
190
|
+
for (const v of values) {
|
|
191
|
+
const d = v - m;
|
|
192
|
+
sumSq += d * d;
|
|
193
|
+
}
|
|
194
|
+
return Math.sqrt(sumSq / values.length);
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/audit/stats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8CH;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAsB,EAAE,OAAe;IAClE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC/D,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAsB;IACtD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC/B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,EAAE,CAAC;YACP,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC5C,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACtC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,YAAY,CAAC,OAAsB,EAAE,OAAe;IAC3D,sBAAsB;IACtB,MAAM,aAAa,GAAG,IAAI,GAAG,EAAyB,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACb,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,gBAAgB,GAAa,EAAE,CAAC;IACtC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1C,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,KAAK,GAAI,MAAM,CAAC,CAAC,CAAiB,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAiB,CAAC,EAAE,CAAC;QAC3D,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,aAAa,GAA2B,EAAE,CAAC;IACjD,MAAM,aAAa,GAA2B,EAAE,CAAC;IACjD,MAAM,eAAe,GAA2B,EAAE,CAAC;IACnD,MAAM,SAAS,GAAa,IAAI,KAAK,CAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,OAAO,GAAkB,IAAI,CAAC;IAClC,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACzD,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YACrC,aAAa,IAAI,CAAC,CAAC;YACnB,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,SAAS,CAAC,IAAI,CAAC,GAAI,SAAS,CAAC,IAAI,CAAY,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,GAAG,OAAO;YAAE,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC;QACvD,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,GAAG,MAAM;YAAE,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,OAAO;QACjB,CAAC,EAAE,GAAG;QACN,aAAa,EAAE,aAAa,CAAC,IAAI;QACjC,aAAa,EAAE,OAAO,CAAC,MAAM;QAC7B,eAAe,EAAE,aAAa;QAC9B,yBAAyB,EAAE,IAAI,CAAC,gBAAgB,CAAC;QACjD,4BAA4B,EAAE,MAAM,CAAC,gBAAgB,CAAC;QACtD,uBAAuB,EAAE,IAAI,CAAC,WAAW,CAAC;QAC1C,0BAA0B,EAAE,MAAM,CAAC,WAAW,CAAC;QAC/C,cAAc,EAAE,aAAa;QAC7B,WAAW,EAAE,SAAS;QACtB,cAAc,EAAE,aAAa;QAC7B,gBAAgB,EAAE,eAAe;QACjC,QAAQ,EAAE,OAAO;QACjB,OAAO,EAAE,MAAM;QACf,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;AACJ,CAAC;AAoCD;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAuB,EACvB,QAAsB,EACtB,UAA0B,EAAE;IAE5B,MAAM,SAAS,GAAG,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,mBAAmB,IAAI,CAAC,CAAC;IACrD,MAAM,UAAU,GAAgB,EAAE,CAAC;IAEnC,IAAI,QAAQ,CAAC,aAAa,IAAI,WAAW,EAAE,CAAC;QAC1C,cAAc,CAAC,UAAU,EAAE,2BAA2B,EAAE,SAAS,CAAC,yBAAyB,EAAE,QAAQ,CAAC,yBAAyB,EAAE,QAAQ,CAAC,4BAA4B,EAAE,SAAS,CAAC,CAAC;QACnL,cAAc,CAAC,UAAU,EAAE,yBAAyB,EAAE,SAAS,CAAC,uBAAuB,EAAE,QAAQ,CAAC,uBAAuB,EAAE,QAAQ,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC;IAC7K,CAAC;IAED,uEAAuE;IACvE,wEAAwE;IACxE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE,kBAAkB,IAAI,EAAE;gBAChC,QAAQ,EAAE,KAAK;gBACf,QAAQ,EAAE,CAAC;gBACX,KAAK,EAAE,IAAI;gBACX,IAAI,EAAE,8BAA8B;aACrC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IAE5D,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,kBAAkB,EAAE,SAAS,CAAC,QAAQ;QACtC,iBAAiB,EAAE,SAAS,CAAC,OAAO;QACpC,UAAU;KACX,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,GAAgB,EAChB,MAAc,EACd,QAAgB,EAChB,YAAoB,EACpB,cAAsB,EACtB,SAAiB;IAEjB,IAAI,cAAc,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC9B,GAAG,CAAC,IAAI,CAAC;gBACP,MAAM;gBACN,QAAQ;gBACR,QAAQ,EAAE,YAAY;gBACtB,KAAK,EAAE,IAAI;gBACX,IAAI,EAAE,yCAAyC;aAChD,CAAC,CAAC;QACL,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,YAAY,CAAC,GAAG,cAAc,CAAC;IACjE,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;QACvB,GAAG,CAAC,IAAI,CAAC;YACP,MAAM;YACN,QAAQ;YACR,QAAQ,EAAE,YAAY;YACtB,KAAK;YACL,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;SAC3E,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E,kDAAkD;AAClD,MAAM,UAAU,IAAI,CAAC,MAAyB;IAC5C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,GAAG,IAAI,CAAC,CAAC;IACjC,OAAO,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;AAC7B,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,MAAM,CAAC,MAAyB;IAC9C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-writer append-only JSONL audit log with hash chain. SPEC §2.
|
|
3
|
+
*/
|
|
4
|
+
import type { KeyObject } from 'node:crypto';
|
|
5
|
+
import type { AuditRecord, AuditRecordInput } from '../types.js';
|
|
6
|
+
import type { Attestor } from './attestor.js';
|
|
7
|
+
export interface AuditLogWriterOptions {
|
|
8
|
+
/** Path to the JSONL audit log file. Created if absent. */
|
|
9
|
+
path: string;
|
|
10
|
+
/** Agent id stamped onto every record. */
|
|
11
|
+
agentId: string;
|
|
12
|
+
/** Session id stamped onto every record. */
|
|
13
|
+
sessionId: string;
|
|
14
|
+
/** File permission mode. Defaults to 0o600. */
|
|
15
|
+
fileMode?: number;
|
|
16
|
+
/** ed25519 private key. When set, every record is signed. SPEC §2.6 (v0.5+). */
|
|
17
|
+
signWith?: KeyObject;
|
|
18
|
+
/**
|
|
19
|
+
* Called once on open() when an existing log is reopened. Fires with the
|
|
20
|
+
* last record so the host can detect unclean shutdown (last record was
|
|
21
|
+
* anything other than session_close).
|
|
22
|
+
*
|
|
23
|
+
* Not called on a fresh file (no existing tip). Errors thrown inside the
|
|
24
|
+
* callback do not prevent the writer from opening — they propagate to the
|
|
25
|
+
* caller of open().
|
|
26
|
+
*/
|
|
27
|
+
onTipRecovered?: (lastRecord: AuditRecord) => void | Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* External attestor. When set, the writer publishes the current chain
|
|
30
|
+
* head every {@link attestEvery} records and on close(). Each successful
|
|
31
|
+
* publish emits an `x_chain_attested` audit row; failures emit
|
|
32
|
+
* `x_chain_attestation_failed`. Attestor errors are NEVER fatal. SPEC §2.7.
|
|
33
|
+
*/
|
|
34
|
+
attestor?: Attestor;
|
|
35
|
+
/** Records between attestations. Default 100. Ignored when attestor is unset. */
|
|
36
|
+
attestEvery?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Attest the final chain head when close() is called. Default true.
|
|
39
|
+
* Ignored when attestor is unset.
|
|
40
|
+
*/
|
|
41
|
+
attestOnClose?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Append-only writer. Internal queue ensures the hash chain is strictly
|
|
45
|
+
* ordered even when callers `append` concurrently.
|
|
46
|
+
*/
|
|
47
|
+
export declare class AuditLogWriter {
|
|
48
|
+
readonly path: string;
|
|
49
|
+
readonly agentId: string;
|
|
50
|
+
readonly sessionId: string;
|
|
51
|
+
private fileMode;
|
|
52
|
+
private handle;
|
|
53
|
+
private openPromise;
|
|
54
|
+
private closed;
|
|
55
|
+
private _tipHash;
|
|
56
|
+
private queue;
|
|
57
|
+
private readonly signWith;
|
|
58
|
+
private readonly onTipRecovered;
|
|
59
|
+
private readonly attestor;
|
|
60
|
+
private readonly attestEvery;
|
|
61
|
+
private readonly attestOnClose;
|
|
62
|
+
private appendedCount;
|
|
63
|
+
private attestationInFlight;
|
|
64
|
+
constructor(options: AuditLogWriterOptions);
|
|
65
|
+
/** Tip of the hash chain (the last appended record's hash). */
|
|
66
|
+
get tipHash(): string;
|
|
67
|
+
/** Open the underlying file (idempotent + single-flight). Recovers tip hash if file exists. */
|
|
68
|
+
open(): Promise<void>;
|
|
69
|
+
/** Append a record. Computes hash chain; serializes via internal queue. */
|
|
70
|
+
append(input: AuditRecordInput): Promise<AuditRecord>;
|
|
71
|
+
/** Flush and close the file handle. Idempotent. */
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Publish the current chain head to the configured attestor and emit
|
|
75
|
+
* `x_chain_attested` (success) or `x_chain_attestation_failed` (error).
|
|
76
|
+
* Idempotent on no-attestor or in-flight: callers can fire-and-forget.
|
|
77
|
+
*
|
|
78
|
+
* Exposed publicly so consumers can force a flush (e.g., on a
|
|
79
|
+
* manually-triggered checkpoint).
|
|
80
|
+
*/
|
|
81
|
+
runAttestation(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Internal-only append that bypasses the closed check (used by close() to
|
|
84
|
+
* write the final attestation row) and skips the attestation hook (to
|
|
85
|
+
* avoid recursion when writing attestation outcome rows).
|
|
86
|
+
*/
|
|
87
|
+
private appendInternal;
|
|
88
|
+
private writeOne;
|
|
89
|
+
/**
|
|
90
|
+
* Read the last non-empty record from the existing log. Returns null when
|
|
91
|
+
* the file is empty or contains only blank lines. Used during open() to
|
|
92
|
+
* seed the hash chain and expose the recovered record to onTipRecovered.
|
|
93
|
+
*/
|
|
94
|
+
private recoverTipRecord;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/audit/writer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAEjB;IACd,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;IAChD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;IAIxC,OAAO,CAAC,aAAa,CAAK;IAK1B,OAAO,CAAC,mBAAmB,CAAS;gBAExB,OAAO,EAAE,qBAAqB;IAe1C,+DAA+D;IAC/D,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,+FAA+F;IACzF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B,2EAA2E;IACrE,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;IAgB3D,mDAAmD;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB5B;;;;;;;OAOG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDrC;;;;OAIG;YACW,cAAc;YAWd,QAAQ;IAyDtB;;;;OAIG;YACW,gBAAgB;CAW/B"}
|