@de-otio/repo-aegis-core 0.2.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 (191) hide show
  1. package/dist/age.d.ts +32 -0
  2. package/dist/age.d.ts.map +1 -0
  3. package/dist/age.js +98 -0
  4. package/dist/age.js.map +1 -0
  5. package/dist/audit-log.d.ts +50 -0
  6. package/dist/audit-log.d.ts.map +1 -0
  7. package/dist/audit-log.js +183 -0
  8. package/dist/audit-log.js.map +1 -0
  9. package/dist/audit-log.test.d.ts +2 -0
  10. package/dist/audit-log.test.d.ts.map +1 -0
  11. package/dist/audit-log.test.js +181 -0
  12. package/dist/audit-log.test.js.map +1 -0
  13. package/dist/deny-set.d.ts +43 -0
  14. package/dist/deny-set.d.ts.map +1 -0
  15. package/dist/deny-set.js +165 -0
  16. package/dist/deny-set.js.map +1 -0
  17. package/dist/deny-set.test.d.ts +2 -0
  18. package/dist/deny-set.test.d.ts.map +1 -0
  19. package/dist/deny-set.test.js +155 -0
  20. package/dist/deny-set.test.js.map +1 -0
  21. package/dist/exceptions.d.ts +96 -0
  22. package/dist/exceptions.d.ts.map +1 -0
  23. package/dist/exceptions.js +143 -0
  24. package/dist/exceptions.js.map +1 -0
  25. package/dist/exit-codes.d.ts +4 -0
  26. package/dist/exit-codes.d.ts.map +1 -0
  27. package/dist/exit-codes.js +6 -0
  28. package/dist/exit-codes.js.map +1 -0
  29. package/dist/first-touch.d.ts +57 -0
  30. package/dist/first-touch.d.ts.map +1 -0
  31. package/dist/first-touch.js +112 -0
  32. package/dist/first-touch.js.map +1 -0
  33. package/dist/import-graph.test.d.ts +2 -0
  34. package/dist/import-graph.test.d.ts.map +1 -0
  35. package/dist/import-graph.test.js +210 -0
  36. package/dist/import-graph.test.js.map +1 -0
  37. package/dist/index.d.ts +37 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +68 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/lock.d.ts +22 -0
  42. package/dist/lock.d.ts.map +1 -0
  43. package/dist/lock.js +86 -0
  44. package/dist/lock.js.map +1 -0
  45. package/dist/lock.test.d.ts +2 -0
  46. package/dist/lock.test.d.ts.map +1 -0
  47. package/dist/lock.test.js +125 -0
  48. package/dist/lock.test.js.map +1 -0
  49. package/dist/paths.d.ts +22 -0
  50. package/dist/paths.d.ts.map +1 -0
  51. package/dist/paths.js +46 -0
  52. package/dist/paths.js.map +1 -0
  53. package/dist/paths.test.d.ts +2 -0
  54. package/dist/paths.test.d.ts.map +1 -0
  55. package/dist/paths.test.js +78 -0
  56. package/dist/paths.test.js.map +1 -0
  57. package/dist/redaction.d.ts +29 -0
  58. package/dist/redaction.d.ts.map +1 -0
  59. package/dist/redaction.js +48 -0
  60. package/dist/redaction.js.map +1 -0
  61. package/dist/redaction.test.d.ts +2 -0
  62. package/dist/redaction.test.d.ts.map +1 -0
  63. package/dist/redaction.test.js +67 -0
  64. package/dist/redaction.test.js.map +1 -0
  65. package/dist/regex-safety.d.ts +87 -0
  66. package/dist/regex-safety.d.ts.map +1 -0
  67. package/dist/regex-safety.js +322 -0
  68. package/dist/regex-safety.js.map +1 -0
  69. package/dist/regex-safety.test.d.ts +2 -0
  70. package/dist/regex-safety.test.d.ts.map +1 -0
  71. package/dist/regex-safety.test.js +149 -0
  72. package/dist/regex-safety.test.js.map +1 -0
  73. package/dist/registry-mutate.d.ts +35 -0
  74. package/dist/registry-mutate.d.ts.map +1 -0
  75. package/dist/registry-mutate.js +149 -0
  76. package/dist/registry-mutate.js.map +1 -0
  77. package/dist/registry-mutate.test.d.ts +2 -0
  78. package/dist/registry-mutate.test.d.ts.map +1 -0
  79. package/dist/registry-mutate.test.js +96 -0
  80. package/dist/registry-mutate.test.js.map +1 -0
  81. package/dist/registry.d.ts +64 -0
  82. package/dist/registry.d.ts.map +1 -0
  83. package/dist/registry.js +120 -0
  84. package/dist/registry.js.map +1 -0
  85. package/dist/registry.test.d.ts +2 -0
  86. package/dist/registry.test.d.ts.map +1 -0
  87. package/dist/registry.test.js +316 -0
  88. package/dist/registry.test.js.map +1 -0
  89. package/dist/remote-url.d.ts +18 -0
  90. package/dist/remote-url.d.ts.map +1 -0
  91. package/dist/remote-url.js +66 -0
  92. package/dist/remote-url.js.map +1 -0
  93. package/dist/remote-url.test.d.ts +2 -0
  94. package/dist/remote-url.test.d.ts.map +1 -0
  95. package/dist/remote-url.test.js +116 -0
  96. package/dist/remote-url.test.js.map +1 -0
  97. package/dist/render.d.ts +54 -0
  98. package/dist/render.d.ts.map +1 -0
  99. package/dist/render.js +182 -0
  100. package/dist/render.js.map +1 -0
  101. package/dist/render.test.d.ts +2 -0
  102. package/dist/render.test.d.ts.map +1 -0
  103. package/dist/render.test.js +152 -0
  104. package/dist/render.test.js.map +1 -0
  105. package/dist/repo.d.ts +40 -0
  106. package/dist/repo.d.ts.map +1 -0
  107. package/dist/repo.js +214 -0
  108. package/dist/repo.js.map +1 -0
  109. package/dist/repo.test.d.ts +2 -0
  110. package/dist/repo.test.d.ts.map +1 -0
  111. package/dist/repo.test.js +234 -0
  112. package/dist/repo.test.js.map +1 -0
  113. package/dist/scan.d.ts +103 -0
  114. package/dist/scan.d.ts.map +1 -0
  115. package/dist/scan.js +436 -0
  116. package/dist/scan.js.map +1 -0
  117. package/dist/scan.test.d.ts +2 -0
  118. package/dist/scan.test.d.ts.map +1 -0
  119. package/dist/scan.test.js +437 -0
  120. package/dist/scan.test.js.map +1 -0
  121. package/dist/schemas.d.ts +50 -0
  122. package/dist/schemas.d.ts.map +1 -0
  123. package/dist/schemas.js +190 -0
  124. package/dist/schemas.js.map +1 -0
  125. package/dist/secret-markers.d.ts +34 -0
  126. package/dist/secret-markers.d.ts.map +1 -0
  127. package/dist/secret-markers.js +118 -0
  128. package/dist/secret-markers.js.map +1 -0
  129. package/dist/secret-markers.test.d.ts +2 -0
  130. package/dist/secret-markers.test.d.ts.map +1 -0
  131. package/dist/secret-markers.test.js +154 -0
  132. package/dist/secret-markers.test.js.map +1 -0
  133. package/dist/trust-boundary.d.ts +33 -0
  134. package/dist/trust-boundary.d.ts.map +1 -0
  135. package/dist/trust-boundary.js +77 -0
  136. package/dist/trust-boundary.js.map +1 -0
  137. package/dist/trust-boundary.test.d.ts +2 -0
  138. package/dist/trust-boundary.test.d.ts.map +1 -0
  139. package/dist/trust-boundary.test.js +170 -0
  140. package/dist/trust-boundary.test.js.map +1 -0
  141. package/dist/types.d.ts +47 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +8 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/working-tree.d.ts +38 -0
  146. package/dist/working-tree.d.ts.map +1 -0
  147. package/dist/working-tree.js +133 -0
  148. package/dist/working-tree.js.map +1 -0
  149. package/dist/working-tree.test.d.ts +2 -0
  150. package/dist/working-tree.test.d.ts.map +1 -0
  151. package/dist/working-tree.test.js +162 -0
  152. package/dist/working-tree.test.js.map +1 -0
  153. package/package.json +40 -0
  154. package/src/age.ts +113 -0
  155. package/src/audit-log.test.ts +222 -0
  156. package/src/audit-log.ts +215 -0
  157. package/src/deny-set.test.ts +208 -0
  158. package/src/deny-set.ts +231 -0
  159. package/src/exceptions.ts +134 -0
  160. package/src/exit-codes.ts +5 -0
  161. package/src/first-touch.ts +172 -0
  162. package/src/import-graph.test.ts +239 -0
  163. package/src/index.ts +191 -0
  164. package/src/lock.test.ts +151 -0
  165. package/src/lock.ts +88 -0
  166. package/src/paths.test.ts +94 -0
  167. package/src/paths.ts +55 -0
  168. package/src/redaction.test.ts +81 -0
  169. package/src/redaction.ts +49 -0
  170. package/src/regex-safety.test.ts +194 -0
  171. package/src/regex-safety.ts +349 -0
  172. package/src/registry-mutate.test.ts +134 -0
  173. package/src/registry-mutate.ts +185 -0
  174. package/src/registry.test.ts +460 -0
  175. package/src/registry.ts +178 -0
  176. package/src/remote-url.test.ts +121 -0
  177. package/src/remote-url.ts +78 -0
  178. package/src/render.test.ts +206 -0
  179. package/src/render.ts +215 -0
  180. package/src/repo.test.ts +275 -0
  181. package/src/repo.ts +245 -0
  182. package/src/scan.test.ts +580 -0
  183. package/src/scan.ts +531 -0
  184. package/src/schemas.ts +207 -0
  185. package/src/secret-markers.test.ts +183 -0
  186. package/src/secret-markers.ts +145 -0
  187. package/src/trust-boundary.test.ts +198 -0
  188. package/src/trust-boundary.ts +98 -0
  189. package/src/types.ts +55 -0
  190. package/src/working-tree.test.ts +193 -0
  191. package/src/working-tree.ts +130 -0
package/dist/age.d.ts ADDED
@@ -0,0 +1,32 @@
1
+ export declare class AgeNotFoundError extends Error {
2
+ readonly code: "AGE_NOT_FOUND";
3
+ constructor();
4
+ }
5
+ export declare class AgeError extends Error {
6
+ action: "encrypt" | "decrypt";
7
+ stderr: string;
8
+ readonly code: "AGE_ERROR";
9
+ constructor(action: "encrypt" | "decrypt", stderr: string);
10
+ }
11
+ export interface EncryptOptions {
12
+ /** Recipients (pubkey strings starting with `age1...` or `ssh-ed25519 ...`). */
13
+ recipients?: string[];
14
+ /** Path to a file holding one recipient per line. */
15
+ recipientFile?: string;
16
+ }
17
+ /**
18
+ * Encrypt a file with age. Returns the ciphertext as a Buffer (binary
19
+ * format by default; ASCII-armoured if -a is added later). Caller is
20
+ * responsible for writing it.
21
+ */
22
+ export declare function encryptFile(plainPath: string, opts: EncryptOptions): Buffer;
23
+ export interface DecryptOptions {
24
+ /** Path to an age identity file (one or more identities). */
25
+ identityFile: string;
26
+ }
27
+ /**
28
+ * Decrypt a file with age. Returns the cleartext as a Buffer.
29
+ */
30
+ export declare function decryptFile(cipherPath: string, opts: DecryptOptions): Buffer;
31
+ export declare function writeBufferTo(target: string, buf: Buffer): void;
32
+ //# sourceMappingURL=age.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age.d.ts","sourceRoot":"","sources":["../src/age.ts"],"names":[],"mappings":"AAWA,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAG,eAAe,CAAU;;CAO1C;AAED,qBAAa,QAAS,SAAQ,KAAK;IAEd,MAAM,EAAE,SAAS,GAAG,SAAS;IAAS,MAAM,EAAE,MAAM;IADvE,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;gBAClB,MAAM,EAAE,SAAS,GAAG,SAAS,EAAS,MAAM,EAAE,MAAM;CAGxE;AAaD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CA6B3E;AAED,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAkB5E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAE/D"}
package/dist/age.js ADDED
@@ -0,0 +1,98 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Richard Myers and contributors.
3
+ // Thin wrapper around the `age` CLI for symmetric file encryption /
4
+ // decryption. Lives in core so multiple packages (scan,
5
+ // cli/registry-encrypt) can share the same machinery without each
6
+ // shelling out to age independently. The `age` binary is a runtime
7
+ // dependency: callers receive `AgeNotFoundError` if it isn't on PATH.
8
+ import { spawnSync } from "node:child_process";
9
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
+ export class AgeNotFoundError extends Error {
11
+ code = "AGE_NOT_FOUND";
12
+ constructor() {
13
+ super("the `age` binary is required but was not found on PATH; " +
14
+ "install from https://age-encryption.org");
15
+ }
16
+ }
17
+ export class AgeError extends Error {
18
+ action;
19
+ stderr;
20
+ code = "AGE_ERROR";
21
+ constructor(action, stderr) {
22
+ super(`age ${action} failed: ${stderr.trim() || "(no stderr)"}`);
23
+ this.action = action;
24
+ this.stderr = stderr;
25
+ }
26
+ }
27
+ function ageBinary() {
28
+ const probe = spawnSync("age", ["--version"], {
29
+ encoding: "utf8",
30
+ stdio: ["ignore", "pipe", "pipe"],
31
+ });
32
+ if (probe.error || probe.status !== 0) {
33
+ throw new AgeNotFoundError();
34
+ }
35
+ return "age";
36
+ }
37
+ /**
38
+ * Encrypt a file with age. Returns the ciphertext as a Buffer (binary
39
+ * format by default; ASCII-armoured if -a is added later). Caller is
40
+ * responsible for writing it.
41
+ */
42
+ export function encryptFile(plainPath, opts) {
43
+ if (!existsSync(plainPath)) {
44
+ throw new Error(`source file not found: ${plainPath}`);
45
+ }
46
+ if ((!opts.recipients || opts.recipients.length === 0) && !opts.recipientFile) {
47
+ throw new Error("encrypt requires at least one --recipient or --recipient-file");
48
+ }
49
+ const args = [];
50
+ for (const r of opts.recipients ?? []) {
51
+ args.push("-r", r);
52
+ }
53
+ if (opts.recipientFile) {
54
+ if (!existsSync(opts.recipientFile)) {
55
+ throw new Error(`recipient file not found: ${opts.recipientFile}`);
56
+ }
57
+ args.push("-R", opts.recipientFile);
58
+ }
59
+ const bin = ageBinary();
60
+ const plaintext = readFileSync(plainPath);
61
+ const result = spawnSync(bin, args, {
62
+ input: plaintext,
63
+ timeout: 30_000,
64
+ });
65
+ if (result.error)
66
+ throw result.error;
67
+ if (result.status !== 0) {
68
+ throw new AgeError("encrypt", (result.stderr ?? Buffer.from("")).toString("utf8"));
69
+ }
70
+ return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
71
+ }
72
+ /**
73
+ * Decrypt a file with age. Returns the cleartext as a Buffer.
74
+ */
75
+ export function decryptFile(cipherPath, opts) {
76
+ if (!existsSync(cipherPath)) {
77
+ throw new Error(`source file not found: ${cipherPath}`);
78
+ }
79
+ if (!existsSync(opts.identityFile)) {
80
+ throw new Error(`identity file not found: ${opts.identityFile}`);
81
+ }
82
+ const bin = ageBinary();
83
+ const ciphertext = readFileSync(cipherPath);
84
+ const result = spawnSync(bin, ["-d", "-i", opts.identityFile], {
85
+ input: ciphertext,
86
+ timeout: 30_000,
87
+ });
88
+ if (result.error)
89
+ throw result.error;
90
+ if (result.status !== 0) {
91
+ throw new AgeError("decrypt", (result.stderr ?? Buffer.from("")).toString("utf8"));
92
+ }
93
+ return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
94
+ }
95
+ export function writeBufferTo(target, buf) {
96
+ writeFileSync(target, buf, { mode: 0o600 });
97
+ }
98
+ //# sourceMappingURL=age.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"age.js","sourceRoot":"","sources":["../src/age.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ export interface AuditRecord {
2
+ /** ISO 8601 timestamp; auto-populated by appendAuditRecord. */
3
+ ts: string;
4
+ /** Action name, e.g. "engagements-add", "allow", "deny". */
5
+ action: string;
6
+ /** Operator identity from `process.env.USER` (falls back to "unknown"). */
7
+ actor: string;
8
+ /** Process working directory at time of action. */
9
+ cwd?: string;
10
+ /** Git toplevel for the repo the action targeted, when applicable. */
11
+ repo?: string;
12
+ /** Single engagement id (allow/deny single, engagements add/end/remove). */
13
+ engagement?: string;
14
+ /** Multiple engagement ids (variadic allow/deny). */
15
+ engagements?: string[];
16
+ /** Free-form structural details. NEVER include literal marker patterns. */
17
+ details?: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * Write `{ enabled }` to the opts file (preserving the rotateBytes
21
+ * setting if one was configured). Used by the CLI's
22
+ * `audit-log on`/`audit-log off` subcommands. Creates the state/ dir
23
+ * with chmod 700 if missing; the file itself is chmod 600.
24
+ */
25
+ export declare function setAuditLogEnabled(enabled: boolean): void;
26
+ /**
27
+ * Read the on/off bit for inspection (used by `audit-log path` and the
28
+ * CLI's "show" subcommand to surface the active state). Does NOT enable
29
+ * the log; that's setAuditLogEnabled's job.
30
+ */
31
+ export declare function isAuditLogEnabled(): boolean;
32
+ /**
33
+ * Path to the active audit-log file. Re-exported here as well as from
34
+ * paths.ts so callers reaching for the writer don't have to hop modules.
35
+ */
36
+ export declare function activeAuditLogPath(): string;
37
+ /**
38
+ * Append one record to the active audit log. ts and actor are filled in
39
+ * here so callers don't have to plumb them through.
40
+ *
41
+ * Fast-path no-op when the audit log is OFF (config absent or
42
+ * `enabled: false`). When ON, runs under withLockSync so concurrent
43
+ * writers can't interleave a half-written line.
44
+ *
45
+ * Marker safety contract: callers MUST NOT pass literal marker patterns
46
+ * or matched substrings in `details`. Engagement ids, counts, file
47
+ * paths, and class names are fine; pattern source strings are not.
48
+ */
49
+ export declare function appendAuditRecord(rec: Omit<AuditRecord, "ts" | "actor">): void;
50
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAuCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAYzD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AA8BD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,CAqC9E"}
@@ -0,0 +1,183 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Richard Myers and contributors.
3
+ // Operator audit log for repo-aegis.
4
+ //
5
+ // What this is: an append-only JSON Lines file that records
6
+ // state-changing operations (allow / deny / engagements add ... /
7
+ // install hooks / etc) so the operator can answer compliance questions
8
+ // like "did I allow customer-A in this repo on date X" from a single
9
+ // file rather than a git-archaeology session across .git/config and
10
+ // the engagement registry.
11
+ //
12
+ // Default state: OFF. The function is a fast-path no-op if the config
13
+ // file (`<home>/state/audit-log.json`) is absent or has
14
+ // `enabled: false`. Existing users see no behaviour change after
15
+ // upgrade.
16
+ //
17
+ // Marker safety: this module never writes literal marker patterns or
18
+ // matched substrings. Callers pass structural metadata only (engagement
19
+ // ids, action names, counts). The reviewer-test in `audit-log.test.ts`
20
+ // asserts that registry-resident literal patterns never appear in the
21
+ // audit log even when the recorded engagement-id matches.
22
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
23
+ import { dirname } from "node:path";
24
+ import { auditLogPath as defaultAuditLogPath, statePath } from "./paths.js";
25
+ import { withLockSync } from "./lock.js";
26
+ /**
27
+ * Default rotation threshold: 10 MiB. Tuned so the active log stays
28
+ * readable end-to-end with `cat` / `tail` without paging tools, but big
29
+ * enough that a year of routine operator work fits in a handful of
30
+ * rotated files.
31
+ */
32
+ const DEFAULT_ROTATE_BYTES = 10 * 1024 * 1024;
33
+ /**
34
+ * Path to the audit-log opts file. Lives under `state/` alongside the
35
+ * other operator-controlled flags (leak-context-mode, registry.encrypted).
36
+ */
37
+ function auditLogConfigPath() {
38
+ return `${statePath()}/audit-log.json`;
39
+ }
40
+ /**
41
+ * Read the opts file. Absent / unparseable / `enabled: false` all map
42
+ * to `{ enabled: false }`. Returning a typed shape keeps the call site
43
+ * tidy: the caller checks one boolean.
44
+ */
45
+ function readConfig() {
46
+ const path = auditLogConfigPath();
47
+ if (!existsSync(path))
48
+ return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(readFileSync(path, "utf8"));
52
+ }
53
+ catch {
54
+ return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
55
+ }
56
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
57
+ return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
58
+ }
59
+ const obj = parsed;
60
+ const enabled = obj["enabled"] === true;
61
+ const rb = obj["rotateBytes"];
62
+ const rotateBytes = typeof rb === "number" && rb > 0 ? rb : DEFAULT_ROTATE_BYTES;
63
+ return { enabled, rotateBytes };
64
+ }
65
+ /**
66
+ * Write `{ enabled }` to the opts file (preserving the rotateBytes
67
+ * setting if one was configured). Used by the CLI's
68
+ * `audit-log on`/`audit-log off` subcommands. Creates the state/ dir
69
+ * with chmod 700 if missing; the file itself is chmod 600.
70
+ */
71
+ export function setAuditLogEnabled(enabled) {
72
+ const path = auditLogConfigPath();
73
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
74
+ let prev = { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
75
+ if (existsSync(path))
76
+ prev = readConfig();
77
+ const next = { enabled, rotateBytes: prev.rotateBytes };
78
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
79
+ try {
80
+ chmodSync(path, 0o600);
81
+ }
82
+ catch {
83
+ /* platform-restricted */
84
+ }
85
+ }
86
+ /**
87
+ * Read the on/off bit for inspection (used by `audit-log path` and the
88
+ * CLI's "show" subcommand to surface the active state). Does NOT enable
89
+ * the log; that's setAuditLogEnabled's job.
90
+ */
91
+ export function isAuditLogEnabled() {
92
+ return readConfig().enabled;
93
+ }
94
+ /**
95
+ * Path to the active audit-log file. Re-exported here as well as from
96
+ * paths.ts so callers reaching for the writer don't have to hop modules.
97
+ */
98
+ export function activeAuditLogPath() {
99
+ return defaultAuditLogPath();
100
+ }
101
+ /**
102
+ * Rotate the active log if it exceeds rotateBytes. The rotated copy is
103
+ * named `audit.log.<iso>` (colons stripped so the filename is portable
104
+ * across filesystems). A fresh empty file is created in place.
105
+ *
106
+ * Caller must hold the registry lock; rotation reads + renames the
107
+ * active log and is racy without it.
108
+ */
109
+ function maybeRotate(activePath, rotateBytes) {
110
+ if (!existsSync(activePath))
111
+ return;
112
+ let size = 0;
113
+ try {
114
+ size = statSync(activePath).size;
115
+ }
116
+ catch {
117
+ return;
118
+ }
119
+ if (size < rotateBytes)
120
+ return;
121
+ // Filename-safe ISO: 2026-05-02T12-34-56.789Z
122
+ const isoStamp = new Date().toISOString().replace(/:/g, "-");
123
+ const rotated = `${activePath}.${isoStamp}`;
124
+ renameSync(activePath, rotated);
125
+ try {
126
+ chmodSync(rotated, 0o600);
127
+ }
128
+ catch {
129
+ /* platform-restricted */
130
+ }
131
+ }
132
+ /**
133
+ * Append one record to the active audit log. ts and actor are filled in
134
+ * here so callers don't have to plumb them through.
135
+ *
136
+ * Fast-path no-op when the audit log is OFF (config absent or
137
+ * `enabled: false`). When ON, runs under withLockSync so concurrent
138
+ * writers can't interleave a half-written line.
139
+ *
140
+ * Marker safety contract: callers MUST NOT pass literal marker patterns
141
+ * or matched substrings in `details`. Engagement ids, counts, file
142
+ * paths, and class names are fine; pattern source strings are not.
143
+ */
144
+ export function appendAuditRecord(rec) {
145
+ const cfg = readConfig();
146
+ if (!cfg.enabled)
147
+ return;
148
+ const full = {
149
+ ts: new Date().toISOString(),
150
+ action: rec.action,
151
+ actor: process.env["USER"] ?? "unknown",
152
+ ...(rec.cwd !== undefined && { cwd: rec.cwd }),
153
+ ...(rec.repo !== undefined && { repo: rec.repo }),
154
+ ...(rec.engagement !== undefined && { engagement: rec.engagement }),
155
+ ...(rec.engagements !== undefined && { engagements: rec.engagements }),
156
+ ...(rec.details !== undefined && { details: rec.details }),
157
+ };
158
+ const path = activeAuditLogPath();
159
+ const dir = dirname(path);
160
+ try {
161
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
162
+ }
163
+ catch {
164
+ /* dir already exists or platform-restricted; appendFileSync will surface real errors */
165
+ }
166
+ withLockSync(() => {
167
+ maybeRotate(path, cfg.rotateBytes);
168
+ const line = JSON.stringify(full) + "\n";
169
+ if (!existsSync(path)) {
170
+ writeFileSync(path, line, { mode: 0o600 });
171
+ }
172
+ else {
173
+ appendFileSync(path, line);
174
+ }
175
+ try {
176
+ chmodSync(path, 0o600);
177
+ }
178
+ catch {
179
+ /* platform-restricted */
180
+ }
181
+ });
182
+ }
183
+ //# sourceMappingURL=audit-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,qDAAqD;AACrD,qCAAqC;AACrC,EAAE;AACF,4DAA4D;AAC5D,kEAAkE;AAClE,uEAAuE;AACvE,qEAAqE;AACrE,oEAAoE;AACpE,2BAA2B;AAC3B,EAAE;AACF,sEAAsE;AACtE,wDAAwD;AACxD,iEAAiE;AACjE,WAAW;AACX,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,uEAAuE;AACvE,sEAAsE;AACtE,0DAA0D;AAE1D,OAAO,EACL,cAAc,EACd,SAAS,EACT,UAAU,EACV,SAAS,EACT,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,IAAI,mBAAmB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AA0B9C;;;GAGG;AACH,SAAS,kBAAkB;IACzB,OAAO,GAAG,SAAS,EAAE,iBAAiB,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpF,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;IACxC,MAAM,EAAE,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC;IAC9B,MAAM,WAAW,GAAG,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,CAAC;IACjF,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,IAAI,IAAI,GAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACjF,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1C,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;IACxD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,EAAE,CAAC,OAAO,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,mBAAmB,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,UAAkB,EAAE,WAAmB;IAC1D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO;IACpC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,IAAI,IAAI,GAAG,WAAW;QAAE,OAAO;IAC/B,8CAA8C;IAC9C,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC;IAC5C,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAsC;IACtE,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO;IAEzB,MAAM,IAAI,GAAgB;QACxB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QACvC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QAC9C,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC;QACjD,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;QACtE,GAAG,CAAC,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;KAC3D,CAAC;IAEF,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC;QACH,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,wFAAwF;IAC1F,CAAC;IAED,YAAY,CAAC,GAAG,EAAE;QAChB,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=audit-log.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.test.d.ts","sourceRoot":"","sources":["../src/audit-log.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,181 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Richard Myers and contributors.
3
+ import { describe, it, beforeEach, afterEach } from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { appendAuditRecord, isAuditLogEnabled, setAuditLogEnabled, activeAuditLogPath, } from "./audit-log.js";
9
+ import { auditLogPath, statePath } from "./paths.js";
10
+ let home;
11
+ let originalHome;
12
+ beforeEach(() => {
13
+ home = mkdtempSync(join(tmpdir(), "repo-aegis-audit-log-"));
14
+ originalHome = process.env["REPO_AEGIS_HOME"];
15
+ process.env["REPO_AEGIS_HOME"] = home;
16
+ });
17
+ afterEach(() => {
18
+ if (originalHome === undefined)
19
+ delete process.env["REPO_AEGIS_HOME"];
20
+ else
21
+ process.env["REPO_AEGIS_HOME"] = originalHome;
22
+ rmSync(home, { recursive: true, force: true });
23
+ });
24
+ describe("appendAuditRecord — disabled by default", () => {
25
+ it("is a no-op when no config file exists", () => {
26
+ assert.equal(isAuditLogEnabled(), false);
27
+ appendAuditRecord({ action: "allow", engagement: "customer-a" });
28
+ // Active log path must NOT have been created.
29
+ assert.equal(existsSync(auditLogPath()), false, "audit.log should not exist when disabled");
30
+ });
31
+ it("is a no-op when config sets enabled: false", () => {
32
+ mkdirSync(statePath(), { recursive: true, mode: 0o700 });
33
+ writeFileSync(join(statePath(), "audit-log.json"), JSON.stringify({ enabled: false }));
34
+ assert.equal(isAuditLogEnabled(), false);
35
+ appendAuditRecord({ action: "deny", engagement: "customer-a" });
36
+ assert.equal(existsSync(auditLogPath()), false);
37
+ });
38
+ it("is a no-op when config file is malformed JSON", () => {
39
+ mkdirSync(statePath(), { recursive: true, mode: 0o700 });
40
+ writeFileSync(join(statePath(), "audit-log.json"), "{this is not json");
41
+ assert.equal(isAuditLogEnabled(), false);
42
+ appendAuditRecord({ action: "allow", engagement: "customer-a" });
43
+ assert.equal(existsSync(auditLogPath()), false);
44
+ });
45
+ });
46
+ describe("setAuditLogEnabled / isAuditLogEnabled", () => {
47
+ it("turns the audit log on and off", () => {
48
+ assert.equal(isAuditLogEnabled(), false);
49
+ setAuditLogEnabled(true);
50
+ assert.equal(isAuditLogEnabled(), true);
51
+ setAuditLogEnabled(false);
52
+ assert.equal(isAuditLogEnabled(), false);
53
+ });
54
+ it("creates state/ with the right permissions", () => {
55
+ setAuditLogEnabled(true);
56
+ assert.ok(existsSync(statePath()));
57
+ assert.ok(existsSync(join(statePath(), "audit-log.json")));
58
+ });
59
+ it("activeAuditLogPath matches paths.ts auditLogPath()", () => {
60
+ assert.equal(activeAuditLogPath(), auditLogPath());
61
+ });
62
+ });
63
+ describe("appendAuditRecord — enabled", () => {
64
+ beforeEach(() => {
65
+ setAuditLogEnabled(true);
66
+ });
67
+ it("appends a JSONL record with auto-populated ts + actor", () => {
68
+ appendAuditRecord({
69
+ action: "allow",
70
+ engagement: "customer-a",
71
+ cwd: "/tmp/some-repo",
72
+ });
73
+ const body = readFileSync(auditLogPath(), "utf8");
74
+ assert.match(body, /\n$/, "JSONL records must end with a newline");
75
+ const lines = body.split("\n").filter(l => l.length > 0);
76
+ assert.equal(lines.length, 1);
77
+ const rec = JSON.parse(lines[0]);
78
+ assert.equal(rec["action"], "allow");
79
+ assert.equal(rec["engagement"], "customer-a");
80
+ assert.equal(rec["cwd"], "/tmp/some-repo");
81
+ // ts must parse as a date
82
+ assert.ok(typeof rec["ts"] === "string");
83
+ const t = new Date(rec["ts"]);
84
+ assert.ok(!Number.isNaN(t.getTime()), "ts must be a parseable ISO 8601 timestamp");
85
+ // actor is process.env.USER or "unknown"
86
+ assert.ok(typeof rec["actor"] === "string");
87
+ });
88
+ it("appends multiple records as separate lines", () => {
89
+ appendAuditRecord({ action: "allow", engagement: "customer-a" });
90
+ appendAuditRecord({ action: "deny", engagement: "customer-a" });
91
+ appendAuditRecord({
92
+ action: "engagements-add",
93
+ engagement: "customer-b",
94
+ details: { markerCount: 2 },
95
+ });
96
+ const body = readFileSync(auditLogPath(), "utf8");
97
+ const lines = body.split("\n").filter(l => l.length > 0);
98
+ assert.equal(lines.length, 3);
99
+ const actions = lines.map(l => JSON.parse(l).action);
100
+ assert.deepEqual(actions, ["allow", "deny", "engagements-add"]);
101
+ });
102
+ it("preserves the engagements (plural) array", () => {
103
+ appendAuditRecord({
104
+ action: "allow",
105
+ engagements: ["customer-a", "customer-b"],
106
+ });
107
+ const body = readFileSync(auditLogPath(), "utf8");
108
+ const rec = JSON.parse(body.trim());
109
+ assert.deepEqual(rec.engagements, ["customer-a", "customer-b"]);
110
+ });
111
+ it("falls back to actor 'unknown' when USER env is unset", () => {
112
+ const prev = process.env["USER"];
113
+ delete process.env["USER"];
114
+ try {
115
+ appendAuditRecord({ action: "allow", engagement: "customer-a" });
116
+ const body = readFileSync(auditLogPath(), "utf8");
117
+ const rec = JSON.parse(body.trim());
118
+ assert.equal(rec.actor, "unknown");
119
+ }
120
+ finally {
121
+ if (prev !== undefined)
122
+ process.env["USER"] = prev;
123
+ }
124
+ });
125
+ });
126
+ describe("appendAuditRecord — rotation", () => {
127
+ it("rotates when file size meets or exceeds rotateBytes", () => {
128
+ // Configure an aggressively small threshold so a couple of records
129
+ // trip the rotation logic.
130
+ mkdirSync(statePath(), { recursive: true, mode: 0o700 });
131
+ writeFileSync(join(statePath(), "audit-log.json"), JSON.stringify({ enabled: true, rotateBytes: 200 }));
132
+ // Each record is ~120 bytes; two of them push past 200.
133
+ appendAuditRecord({
134
+ action: "engagements-add",
135
+ engagement: "customer-aaaaaaaaaaaaaaaa",
136
+ details: { padding: "x".repeat(50) },
137
+ });
138
+ appendAuditRecord({
139
+ action: "engagements-add",
140
+ engagement: "customer-bbbbbbbbbbbbbbbb",
141
+ details: { padding: "y".repeat(50) },
142
+ });
143
+ // The third call should detect the size threshold and rotate before
144
+ // the third record is written. After the call we expect (a) a
145
+ // rotated `audit.log.*` file and (b) a fresh `audit.log` containing
146
+ // only the third record.
147
+ appendAuditRecord({
148
+ action: "engagements-add",
149
+ engagement: "customer-c",
150
+ });
151
+ const files = readdirSync(statePath());
152
+ const rotated = files.filter(f => f.startsWith("audit.log."));
153
+ assert.ok(rotated.length >= 1, `expected at least one rotated log; got: ${files.join(",")}`);
154
+ const active = readFileSync(auditLogPath(), "utf8");
155
+ const lines = active.split("\n").filter(l => l.length > 0);
156
+ assert.equal(lines.length, 1, "fresh active log should hold exactly the post-rotation record");
157
+ assert.equal(JSON.parse(lines[0]).engagement, "customer-c");
158
+ });
159
+ });
160
+ describe("appendAuditRecord — marker scrub (reviewer test)", () => {
161
+ it("never persists a literal marker pattern even when the engagement-id matches", () => {
162
+ setAuditLogEnabled(true);
163
+ // Simulate a real-looking marker pattern from the registry. The
164
+ // contract: even though we record the engagement id, the literal
165
+ // pattern itself must NOT appear in the audit log.
166
+ const literalMarker = "Z9LITERAL-MARKER-PATTERN-Z9";
167
+ // The caller is supposed to pass structural metadata only. Pass
168
+ // engagement-id (allowed) and a marker COUNT in details (allowed);
169
+ // do NOT pass the literal pattern itself.
170
+ appendAuditRecord({
171
+ action: "engagements-add",
172
+ engagement: "customer-z",
173
+ details: { markerCount: 1 },
174
+ });
175
+ const body = readFileSync(auditLogPath(), "utf8");
176
+ assert.ok(!body.includes(literalMarker), `audit log must not contain the literal marker pattern; got:\n${body}`);
177
+ // The engagement id is fine (it's an opaque identifier the operator picks).
178
+ assert.match(body, /customer-z/);
179
+ });
180
+ });
181
+ //# sourceMappingURL=audit-log.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.test.js","sourceRoot":"","sources":["../src/audit-log.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { RepoConfig } from "./repo.js";
2
+ export declare const ALWAYS_FILE_STEM = "_always";
3
+ export interface DenySetFile {
4
+ stem: string;
5
+ path: string;
6
+ }
7
+ export interface DenySet {
8
+ files: DenySetFile[];
9
+ patterns: string[];
10
+ /**
11
+ * Parallel to `patterns`: `patternSources[i]` is the file stem (engagement
12
+ * id or `_always`) that pattern i was loaded from. Used by scanText to
13
+ * attribute each hit to its source engagement, surfaced as
14
+ * {@link ScanHit.engagement}.
15
+ *
16
+ * Optional for backward compatibility with fixtures and ad-hoc DenySet
17
+ * literals; runtime callers (computeDenySet) always populate it. When
18
+ * absent or length-mismatched, scanText falls back to no-attribution.
19
+ */
20
+ patternSources?: string[];
21
+ combinedRegex: string;
22
+ warnings: string[];
23
+ }
24
+ export interface DenySetOptions {
25
+ markersDir?: string;
26
+ /**
27
+ * Path to the cache file. Default: `<home>/state/deny-set.cache.json`.
28
+ * Pass `null` to disable caching entirely (useful for tests).
29
+ */
30
+ cachePath?: string | null;
31
+ }
32
+ /**
33
+ * Compute the per-repo deny set. Class-aware:
34
+ *
35
+ * - `public-eligible` / `private-strict`: full union (every marker file).
36
+ * Engagement field on the repo is ignored; if set, a warning is emitted.
37
+ * - `customer-coupled`: union of `_always.txt` + every per-engagement file
38
+ * whose stem is NOT in this repo's `engagements` list.
39
+ * - `scratch`: same set as `customer-coupled`, but the caller (the CLI's
40
+ * `check`) treats hits as advisory and exits 0.
41
+ */
42
+ export declare function computeDenySet(repo: RepoConfig, opts?: DenySetOptions): DenySet;
43
+ //# sourceMappingURL=deny-set.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deny-set.d.ts","sourceRoot":"","sources":["../src/deny-set.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAsED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAoGnF"}