@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.
Files changed (188) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +281 -0
  3. package/ROADMAP.md +109 -0
  4. package/dist/audit/attestor.d.ts +102 -0
  5. package/dist/audit/attestor.d.ts.map +1 -0
  6. package/dist/audit/attestor.js +103 -0
  7. package/dist/audit/attestor.js.map +1 -0
  8. package/dist/audit/chain.d.ts +30 -0
  9. package/dist/audit/chain.d.ts.map +1 -0
  10. package/dist/audit/chain.js +65 -0
  11. package/dist/audit/chain.js.map +1 -0
  12. package/dist/audit/correlation.d.ts +114 -0
  13. package/dist/audit/correlation.d.ts.map +1 -0
  14. package/dist/audit/correlation.js +259 -0
  15. package/dist/audit/correlation.js.map +1 -0
  16. package/dist/audit/index.d.ts +13 -0
  17. package/dist/audit/index.d.ts.map +1 -0
  18. package/dist/audit/index.js +8 -0
  19. package/dist/audit/index.js.map +1 -0
  20. package/dist/audit/reader.d.ts +30 -0
  21. package/dist/audit/reader.d.ts.map +1 -0
  22. package/dist/audit/reader.js +85 -0
  23. package/dist/audit/reader.js.map +1 -0
  24. package/dist/audit/signature.d.ts +39 -0
  25. package/dist/audit/signature.d.ts.map +1 -0
  26. package/dist/audit/signature.js +73 -0
  27. package/dist/audit/signature.js.map +1 -0
  28. package/dist/audit/stats.d.ts +106 -0
  29. package/dist/audit/stats.d.ts.map +1 -0
  30. package/dist/audit/stats.js +196 -0
  31. package/dist/audit/stats.js.map +1 -0
  32. package/dist/audit/writer.d.ts +96 -0
  33. package/dist/audit/writer.d.ts.map +1 -0
  34. package/dist/audit/writer.js +263 -0
  35. package/dist/audit/writer.js.map +1 -0
  36. package/dist/cli/guardian-baseline.d.ts +42 -0
  37. package/dist/cli/guardian-baseline.d.ts.map +1 -0
  38. package/dist/cli/guardian-baseline.js +265 -0
  39. package/dist/cli/guardian-baseline.js.map +1 -0
  40. package/dist/cli/guardian-correlator.d.ts +47 -0
  41. package/dist/cli/guardian-correlator.d.ts.map +1 -0
  42. package/dist/cli/guardian-correlator.js +217 -0
  43. package/dist/cli/guardian-correlator.js.map +1 -0
  44. package/dist/cli/guardian-verify.d.ts +30 -0
  45. package/dist/cli/guardian-verify.d.ts.map +1 -0
  46. package/dist/cli/guardian-verify.js +149 -0
  47. package/dist/cli/guardian-verify.js.map +1 -0
  48. package/dist/errors.d.ts +28 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +40 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/estop/heartbeat.d.ts +94 -0
  53. package/dist/estop/heartbeat.d.ts.map +1 -0
  54. package/dist/estop/heartbeat.js +135 -0
  55. package/dist/estop/heartbeat.js.map +1 -0
  56. package/dist/estop/hub.d.ts +76 -0
  57. package/dist/estop/hub.d.ts.map +1 -0
  58. package/dist/estop/hub.js +167 -0
  59. package/dist/estop/hub.js.map +1 -0
  60. package/dist/estop/index.d.ts +12 -0
  61. package/dist/estop/index.d.ts.map +1 -0
  62. package/dist/estop/index.js +6 -0
  63. package/dist/estop/index.js.map +1 -0
  64. package/dist/estop/local.d.ts +31 -0
  65. package/dist/estop/local.d.ts.map +1 -0
  66. package/dist/estop/local.js +101 -0
  67. package/dist/estop/local.js.map +1 -0
  68. package/dist/estop/middleware.d.ts +36 -0
  69. package/dist/estop/middleware.d.ts.map +1 -0
  70. package/dist/estop/middleware.js +40 -0
  71. package/dist/estop/middleware.js.map +1 -0
  72. package/dist/estop/poller.d.ts +36 -0
  73. package/dist/estop/poller.d.ts.map +1 -0
  74. package/dist/estop/poller.js +85 -0
  75. package/dist/estop/poller.js.map +1 -0
  76. package/dist/estop/types.d.ts +31 -0
  77. package/dist/estop/types.d.ts.map +1 -0
  78. package/dist/estop/types.js +5 -0
  79. package/dist/estop/types.js.map +1 -0
  80. package/dist/gate/async-callback.d.ts +27 -0
  81. package/dist/gate/async-callback.d.ts.map +1 -0
  82. package/dist/gate/async-callback.js +79 -0
  83. package/dist/gate/async-callback.js.map +1 -0
  84. package/dist/gate/cli.d.ts +29 -0
  85. package/dist/gate/cli.d.ts.map +1 -0
  86. package/dist/gate/cli.js +83 -0
  87. package/dist/gate/cli.js.map +1 -0
  88. package/dist/gate/data-channel.d.ts +41 -0
  89. package/dist/gate/data-channel.d.ts.map +1 -0
  90. package/dist/gate/data-channel.js +132 -0
  91. package/dist/gate/data-channel.js.map +1 -0
  92. package/dist/gate/index.d.ts +13 -0
  93. package/dist/gate/index.d.ts.map +1 -0
  94. package/dist/gate/index.js +7 -0
  95. package/dist/gate/index.js.map +1 -0
  96. package/dist/gate/options.d.ts +90 -0
  97. package/dist/gate/options.d.ts.map +1 -0
  98. package/dist/gate/options.js +131 -0
  99. package/dist/gate/options.js.map +1 -0
  100. package/dist/gate/programmatic.d.ts +9 -0
  101. package/dist/gate/programmatic.d.ts.map +1 -0
  102. package/dist/gate/programmatic.js +20 -0
  103. package/dist/gate/programmatic.js.map +1 -0
  104. package/dist/gate/two-key.d.ts +90 -0
  105. package/dist/gate/two-key.d.ts.map +1 -0
  106. package/dist/gate/two-key.js +78 -0
  107. package/dist/gate/two-key.js.map +1 -0
  108. package/dist/gate/types.d.ts +25 -0
  109. package/dist/gate/types.d.ts.map +1 -0
  110. package/dist/gate/types.js +5 -0
  111. package/dist/gate/types.js.map +1 -0
  112. package/dist/index.d.ts +33 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.js +26 -0
  115. package/dist/index.js.map +1 -0
  116. package/dist/notify/console.d.ts +13 -0
  117. package/dist/notify/console.d.ts.map +1 -0
  118. package/dist/notify/console.js +27 -0
  119. package/dist/notify/console.js.map +1 -0
  120. package/dist/notify/index.d.ts +8 -0
  121. package/dist/notify/index.d.ts.map +1 -0
  122. package/dist/notify/index.js +4 -0
  123. package/dist/notify/index.js.map +1 -0
  124. package/dist/notify/multi.d.ts +14 -0
  125. package/dist/notify/multi.d.ts.map +1 -0
  126. package/dist/notify/multi.js +22 -0
  127. package/dist/notify/multi.js.map +1 -0
  128. package/dist/notify/types.d.ts +21 -0
  129. package/dist/notify/types.d.ts.map +1 -0
  130. package/dist/notify/types.js +5 -0
  131. package/dist/notify/types.js.map +1 -0
  132. package/dist/notify/webhook.d.ts +21 -0
  133. package/dist/notify/webhook.d.ts.map +1 -0
  134. package/dist/notify/webhook.js +37 -0
  135. package/dist/notify/webhook.js.map +1 -0
  136. package/dist/policy/attribution.d.ts +61 -0
  137. package/dist/policy/attribution.d.ts.map +1 -0
  138. package/dist/policy/attribution.js +116 -0
  139. package/dist/policy/attribution.js.map +1 -0
  140. package/dist/policy/evaluator.d.ts +36 -0
  141. package/dist/policy/evaluator.d.ts.map +1 -0
  142. package/dist/policy/evaluator.js +211 -0
  143. package/dist/policy/evaluator.js.map +1 -0
  144. package/dist/policy/index.d.ts +11 -0
  145. package/dist/policy/index.d.ts.map +1 -0
  146. package/dist/policy/index.js +7 -0
  147. package/dist/policy/index.js.map +1 -0
  148. package/dist/policy/integrity.d.ts +17 -0
  149. package/dist/policy/integrity.d.ts.map +1 -0
  150. package/dist/policy/integrity.js +31 -0
  151. package/dist/policy/integrity.js.map +1 -0
  152. package/dist/policy/loader.d.ts +9 -0
  153. package/dist/policy/loader.d.ts.map +1 -0
  154. package/dist/policy/loader.js +124 -0
  155. package/dist/policy/loader.js.map +1 -0
  156. package/dist/policy/site-key.d.ts +22 -0
  157. package/dist/policy/site-key.d.ts.map +1 -0
  158. package/dist/policy/site-key.js +48 -0
  159. package/dist/policy/site-key.js.map +1 -0
  160. package/dist/policy/store.d.ts +45 -0
  161. package/dist/policy/store.d.ts.map +1 -0
  162. package/dist/policy/store.js +223 -0
  163. package/dist/policy/store.js.map +1 -0
  164. package/dist/policy/types.d.ts +72 -0
  165. package/dist/policy/types.d.ts.map +1 -0
  166. package/dist/policy/types.js +5 -0
  167. package/dist/policy/types.js.map +1 -0
  168. package/dist/runtime/capability.d.ts +125 -0
  169. package/dist/runtime/capability.d.ts.map +1 -0
  170. package/dist/runtime/capability.js +121 -0
  171. package/dist/runtime/capability.js.map +1 -0
  172. package/dist/runtime/honeytokens.d.ts +104 -0
  173. package/dist/runtime/honeytokens.d.ts.map +1 -0
  174. package/dist/runtime/honeytokens.js +115 -0
  175. package/dist/runtime/honeytokens.js.map +1 -0
  176. package/dist/runtime/multi-rate-limiter.d.ts +90 -0
  177. package/dist/runtime/multi-rate-limiter.d.ts.map +1 -0
  178. package/dist/runtime/multi-rate-limiter.js +133 -0
  179. package/dist/runtime/multi-rate-limiter.js.map +1 -0
  180. package/dist/runtime/runtime.d.ts +94 -0
  181. package/dist/runtime/runtime.d.ts.map +1 -0
  182. package/dist/runtime/runtime.js +276 -0
  183. package/dist/runtime/runtime.js.map +1 -0
  184. package/dist/types.d.ts +97 -0
  185. package/dist/types.d.ts.map +1 -0
  186. package/dist/types.js +5 -0
  187. package/dist/types.js.map +1 -0
  188. 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":"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"}
@@ -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"}