@directive-run/core 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-utils.cjs +1 -1
- package/dist/adapter-utils.d.cts +2 -2
- package/dist/adapter-utils.d.ts +2 -2
- package/dist/adapter-utils.js +1 -1
- package/dist/adapter-utils.js.map +1 -1
- package/dist/audit-ledger-Dc6hAXam.d.cts +378 -0
- package/dist/audit-ledger-dxvslGi3.d.ts +378 -0
- package/dist/chunk-2FF6QGOA.js +2 -0
- package/dist/chunk-2FF6QGOA.js.map +1 -0
- package/dist/chunk-4MNQDXH7.cjs +3 -0
- package/dist/chunk-4MNQDXH7.cjs.map +1 -0
- package/dist/chunk-644QZVTT.js +16 -0
- package/dist/{chunk-26Z5VNPZ.js.map → chunk-644QZVTT.js.map} +1 -1
- package/dist/chunk-ENZEHIL7.cjs +3 -0
- package/dist/chunk-ENZEHIL7.cjs.map +1 -0
- package/dist/chunk-I722BZA5.js +7 -0
- package/dist/chunk-I722BZA5.js.map +1 -0
- package/dist/chunk-IXRS4LM4.cjs +2 -0
- package/dist/chunk-IXRS4LM4.cjs.map +1 -0
- package/dist/chunk-NPX5EKPP.cjs +16 -0
- package/dist/{chunk-EX3XG667.cjs.map → chunk-NPX5EKPP.cjs.map} +1 -1
- package/dist/chunk-PA6VC32N.js +2 -0
- package/dist/chunk-PA6VC32N.js.map +1 -0
- package/dist/chunk-PXRV64PA.js +3 -0
- package/dist/chunk-PXRV64PA.js.map +1 -0
- package/dist/chunk-R2GHSCTR.js +3 -0
- package/dist/chunk-R2GHSCTR.js.map +1 -0
- package/dist/chunk-T4TRJEJN.cjs +2 -0
- package/dist/chunk-T4TRJEJN.cjs.map +1 -0
- package/dist/chunk-X7G7UBXU.cjs +7 -0
- package/dist/chunk-X7G7UBXU.cjs.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -391
- package/dist/index.d.ts +214 -391
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1 -1
- package/dist/internals.d.cts +5 -5
- package/dist/internals.d.ts +5 -5
- package/dist/internals.js +1 -1
- package/dist/plugins/index.cjs +2 -2
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.cts +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.js +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/{plugins-Ykl_sAPE.d.ts → plugins-BIzXaYbg.d.cts} +15 -1
- package/dist/{plugins-Ykl_sAPE.d.cts → plugins-BIzXaYbg.d.ts} +15 -1
- package/dist/predicate-Bnx3LN7P.d.cts +655 -0
- package/dist/predicate-BxQVf0ug.d.ts +655 -0
- package/dist/system-A6VYKLVF.js +2 -0
- package/dist/{system-VZWB6WXX.js.map → system-A6VYKLVF.js.map} +1 -1
- package/dist/system-CDJMD5O5.cjs +2 -0
- package/dist/{system-GK3NSFQH.cjs.map → system-CDJMD5O5.cjs.map} +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/{utils-BnQajqPu.d.cts → utils-Mg55IerF.d.cts} +27 -1
- package/dist/{utils-BnQajqPu.d.ts → utils-Mg55IerF.d.ts} +27 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.d.cts +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +1 -1
- package/package.json +1 -1
- package/dist/audit-ledger-9IElAHH9.d.ts +0 -205
- package/dist/audit-ledger-qMjEBqiP.d.cts +0 -205
- package/dist/chunk-26Z5VNPZ.js +0 -16
- package/dist/chunk-4VZOZWXM.cjs +0 -2
- package/dist/chunk-4VZOZWXM.cjs.map +0 -1
- package/dist/chunk-7NMXRATK.cjs +0 -3
- package/dist/chunk-7NMXRATK.cjs.map +0 -1
- package/dist/chunk-7TSYQEN3.js +0 -2
- package/dist/chunk-7TSYQEN3.js.map +0 -1
- package/dist/chunk-EOLY64E6.cjs +0 -3
- package/dist/chunk-EOLY64E6.cjs.map +0 -1
- package/dist/chunk-EX3XG667.cjs +0 -16
- package/dist/chunk-N4KTCKOI.cjs +0 -7
- package/dist/chunk-N4KTCKOI.cjs.map +0 -1
- package/dist/chunk-T6IJUWYR.js +0 -3
- package/dist/chunk-T6IJUWYR.js.map +0 -1
- package/dist/chunk-TPOKS4RY.js +0 -3
- package/dist/chunk-TPOKS4RY.js.map +0 -1
- package/dist/chunk-TZHC4E6S.js +0 -7
- package/dist/chunk-TZHC4E6S.js.map +0 -1
- package/dist/helpers-D2pfb6vT.d.ts +0 -235
- package/dist/helpers-hh6UanB1.d.cts +0 -235
- package/dist/system-GK3NSFQH.cjs +0 -2
- package/dist/system-VZWB6WXX.js +0 -2
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { b as FactPredicate, C as ClauseResult, q as Plugin, M as ModuleSchema } from './plugins-Ykl_sAPE.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* createAuditLedger — append-only, queryable, cryptographically-chained
|
|
5
|
-
* audit of every state change. For compliance, forensics, "show me why
|
|
6
|
-
* this user got that decision."
|
|
7
|
-
*
|
|
8
|
-
* Captures (per observation event):
|
|
9
|
-
*
|
|
10
|
-
* - `constraint.evaluate` → { whenSpec, whenExplain, active }
|
|
11
|
-
* - `resolver.write.rejected` (rejection + summary kinds)
|
|
12
|
-
* - `fact.change` → { key, prior, next }
|
|
13
|
-
* - `resolver.complete` → { resolverId, requirementId, duration }
|
|
14
|
-
* - `system.init` / `system.start` / `system.stop` / `system.destroy`
|
|
15
|
-
*
|
|
16
|
-
* Hash chain: each entry stores `prevHash` (the genesis entry's is null);
|
|
17
|
-
* `hash` is computed *lazily* at `verify()` / `toJSON()` time via
|
|
18
|
-
* `stableStringify` + SHA-256 (`crypto.subtle.digest`). Tampering with
|
|
19
|
-
* any entry's payload breaks the next entry's `prevHash` link — visible
|
|
20
|
-
* in `verify()`.
|
|
21
|
-
*
|
|
22
|
-
* PII redaction: by default, fact keys whose meta carries the `pii`
|
|
23
|
-
* tag (via `system.meta.byTag("pii")`) have their values replaced with
|
|
24
|
-
* `"[redacted]"` in `whenExplain.actual`, `fact.change.prior`, and
|
|
25
|
-
* `fact.change.next`. Opt out with `capturePII: true`.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
type AuditEntryKind = "constraint.evaluate" | "resolver.write.rejected" | "fact.change" | "resolver.complete" | "resolver.error" | "system.init" | "system.start" | "system.stop" | "system.destroy";
|
|
29
|
-
interface AuditEntryBase {
|
|
30
|
-
/** Monotonic sequence number, starting at 0. */
|
|
31
|
-
readonly seq: number;
|
|
32
|
-
/** Wall-clock timestamp (ms epoch). */
|
|
33
|
-
readonly ts: number;
|
|
34
|
-
/** Discriminator. */
|
|
35
|
-
readonly kind: AuditEntryKind;
|
|
36
|
-
/** Hash of the previous entry's full payload. null on the genesis entry. */
|
|
37
|
-
readonly prevHash: string | null;
|
|
38
|
-
}
|
|
39
|
-
type AuditEntry = (AuditEntryBase & {
|
|
40
|
-
kind: "constraint.evaluate";
|
|
41
|
-
constraintId: string;
|
|
42
|
-
active: boolean;
|
|
43
|
-
/** Cached at ledger start from `system.inspect().constraints[].whenSpec`. May be undefined for function-form constraints. */
|
|
44
|
-
whenSpec?: FactPredicate<unknown>;
|
|
45
|
-
whenExplain?: readonly ClauseResult[];
|
|
46
|
-
}) | (AuditEntryBase & {
|
|
47
|
-
kind: "resolver.write.rejected";
|
|
48
|
-
rejection: "rejection" | "summary";
|
|
49
|
-
resolverId: string;
|
|
50
|
-
requirementId: string;
|
|
51
|
-
reason: string;
|
|
52
|
-
fact?: string;
|
|
53
|
-
expected?: unknown;
|
|
54
|
-
actual?: unknown;
|
|
55
|
-
dropped?: number;
|
|
56
|
-
}) | (AuditEntryBase & {
|
|
57
|
-
kind: "fact.change";
|
|
58
|
-
key: string;
|
|
59
|
-
prior: unknown;
|
|
60
|
-
next: unknown;
|
|
61
|
-
}) | (AuditEntryBase & {
|
|
62
|
-
kind: "resolver.complete";
|
|
63
|
-
resolverId: string;
|
|
64
|
-
requirementId: string;
|
|
65
|
-
duration: number;
|
|
66
|
-
}) | (AuditEntryBase & {
|
|
67
|
-
kind: "resolver.error";
|
|
68
|
-
resolverId: string;
|
|
69
|
-
requirementId: string;
|
|
70
|
-
error: string;
|
|
71
|
-
}) | (AuditEntryBase & {
|
|
72
|
-
kind: "system.init" | "system.start" | "system.stop" | "system.destroy";
|
|
73
|
-
});
|
|
74
|
-
interface QueryFilter {
|
|
75
|
-
/** Exact-match fact path. */
|
|
76
|
-
factPath?: string;
|
|
77
|
-
/** Filter by constraint id. */
|
|
78
|
-
constraintId?: string;
|
|
79
|
-
/** Filter by entry kind. */
|
|
80
|
-
kind?: AuditEntryKind | readonly AuditEntryKind[];
|
|
81
|
-
/** Time range as `[startMs, endMs]`, ISO strings, or epoch numbers. */
|
|
82
|
-
changedBetween?: [string | number | Date, string | number | Date];
|
|
83
|
-
/** Maximum entries returned. Default 1000. */
|
|
84
|
-
limit?: number;
|
|
85
|
-
}
|
|
86
|
-
/** Verify result — chain valid OR a break with full context for tamper visualization. */
|
|
87
|
-
type VerifyResult = {
|
|
88
|
-
valid: true;
|
|
89
|
-
entryCount: number;
|
|
90
|
-
} | {
|
|
91
|
-
valid: false;
|
|
92
|
-
brokenAt: number;
|
|
93
|
-
expectedHash: string;
|
|
94
|
-
actualHash: string;
|
|
95
|
-
entry: AuditEntry;
|
|
96
|
-
};
|
|
97
|
-
interface AuditLedgerSink {
|
|
98
|
-
write(entry: AuditEntry): void;
|
|
99
|
-
query(filter: QueryFilter): readonly AuditEntry[];
|
|
100
|
-
recent(n: number): readonly AuditEntry[];
|
|
101
|
-
forFact(path: string, opts?: {
|
|
102
|
-
limit?: number;
|
|
103
|
-
}): readonly AuditEntry[];
|
|
104
|
-
forConstraint(id: string, opts?: {
|
|
105
|
-
limit?: number;
|
|
106
|
-
}): readonly AuditEntry[];
|
|
107
|
-
toJSON(): {
|
|
108
|
-
entries: readonly AuditEntry[];
|
|
109
|
-
capturedAt: number;
|
|
110
|
-
};
|
|
111
|
-
clear(): void;
|
|
112
|
-
destroy(): void;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* In-memory bounded ring-buffer sink. Drops oldest entries past
|
|
116
|
-
* `capacity` (default 10,000). Use this as the default sink for dev,
|
|
117
|
-
* tests, and StackBlitz demos.
|
|
118
|
-
*/
|
|
119
|
-
declare function memorySink(opts?: {
|
|
120
|
-
capacity?: number;
|
|
121
|
-
}): AuditLedgerSink;
|
|
122
|
-
interface AuditLedgerOptions {
|
|
123
|
-
/** Sink to write entries to. Default: in-memory ring buffer (capacity 10k). */
|
|
124
|
-
sink?: AuditLedgerSink;
|
|
125
|
-
/**
|
|
126
|
-
* Whether to capture raw fact values (`prior`/`next` on fact.change,
|
|
127
|
-
* `actual` in whenExplain). Default `false` — PII-tagged facts are
|
|
128
|
-
* redacted by default. Set `true` to opt out of redaction.
|
|
129
|
-
*/
|
|
130
|
-
capturePII?: boolean;
|
|
131
|
-
/**
|
|
132
|
-
* Optional caller-supplied redactor. Runs AFTER the default
|
|
133
|
-
* pii-tag-based redaction. Useful for additional sanitization.
|
|
134
|
-
*/
|
|
135
|
-
redact?: (entry: AuditEntry) => AuditEntry;
|
|
136
|
-
}
|
|
137
|
-
interface AuditLedger {
|
|
138
|
-
/** The plugin to pass to `createSystem({ plugins: [...] })`. */
|
|
139
|
-
readonly plugin: Plugin<ModuleSchema>;
|
|
140
|
-
/** Query entries matching the filter. */
|
|
141
|
-
query(filter?: QueryFilter): readonly AuditEntry[];
|
|
142
|
-
/** Most recent N entries (chronological). */
|
|
143
|
-
recent(n: number): readonly AuditEntry[];
|
|
144
|
-
/** All entries that touch this fact path (exact match). */
|
|
145
|
-
forFact(path: string, opts?: {
|
|
146
|
-
limit?: number;
|
|
147
|
-
}): readonly AuditEntry[];
|
|
148
|
-
/** All entries for this constraint id. */
|
|
149
|
-
forConstraint(id: string, opts?: {
|
|
150
|
-
limit?: number;
|
|
151
|
-
}): readonly AuditEntry[];
|
|
152
|
-
/** Full ledger snapshot for export / serialization. */
|
|
153
|
-
toJSON(): {
|
|
154
|
-
entries: readonly AuditEntry[];
|
|
155
|
-
capturedAt: number;
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* Walk the hash chain genesis → tip. Returns `{ valid: true }` iff
|
|
159
|
-
* every entry's `prevHash` matches the (sync, djb2-based) hash of
|
|
160
|
-
* the previous entry. On break, returns the index of the first
|
|
161
|
-
* broken link plus the expected vs actual hashes — feed into a
|
|
162
|
-
* "TAMPERED" visualization.
|
|
163
|
-
*
|
|
164
|
-
* Sync by default (djb2 chain). For compliance-grade collision
|
|
165
|
-
* resistance, pass `{ strong: true }` — verify walks the chain a
|
|
166
|
-
* second time with SHA-256 and returns a Promise. Callers must
|
|
167
|
-
* `await` the result when `strong: true` is passed.
|
|
168
|
-
*/
|
|
169
|
-
verify(opts?: {
|
|
170
|
-
strong?: boolean;
|
|
171
|
-
}): VerifyResult | Promise<VerifyResult>;
|
|
172
|
-
/** Empty the sink. */
|
|
173
|
-
clear(): void;
|
|
174
|
-
/** Unsubscribe + drop the sink. */
|
|
175
|
-
destroy(): void;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Create an audit ledger that subscribes to the given system's
|
|
179
|
-
* observation stream. Returns a `Plugin` to install + a query/verify
|
|
180
|
-
* API for the ledger.
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```ts
|
|
184
|
-
* import { createAuditLedger } from "@directive-run/core/plugins";
|
|
185
|
-
*
|
|
186
|
-
* const ledger = createAuditLedger();
|
|
187
|
-
* const system = createSystem({ module, plugins: [ledger.plugin] });
|
|
188
|
-
* system.start();
|
|
189
|
-
*
|
|
190
|
-
* // Six months later — auditor asks "what changed cart-total in March?"
|
|
191
|
-
* ledger.query({
|
|
192
|
-
* factPath: "cartTotal",
|
|
193
|
-
* changedBetween: ["2026-03-01", "2026-04-01"],
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* // Verify nobody tampered with the ledger
|
|
197
|
-
* const verdict = await ledger.verify();
|
|
198
|
-
* if (!verdict.valid) {
|
|
199
|
-
* console.error("Tamper at entry", verdict.brokenAt);
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
declare function createAuditLedger(opts?: AuditLedgerOptions): AuditLedger;
|
|
204
|
-
|
|
205
|
-
export { type AuditEntry as A, type QueryFilter as Q, type AuditEntryKind as a, type AuditLedger as b, type AuditLedgerOptions as c, type AuditLedgerSink as d, createAuditLedger as e, memorySink as m };
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { b as FactPredicate, C as ClauseResult, q as Plugin, M as ModuleSchema } from './plugins-Ykl_sAPE.cjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* createAuditLedger — append-only, queryable, cryptographically-chained
|
|
5
|
-
* audit of every state change. For compliance, forensics, "show me why
|
|
6
|
-
* this user got that decision."
|
|
7
|
-
*
|
|
8
|
-
* Captures (per observation event):
|
|
9
|
-
*
|
|
10
|
-
* - `constraint.evaluate` → { whenSpec, whenExplain, active }
|
|
11
|
-
* - `resolver.write.rejected` (rejection + summary kinds)
|
|
12
|
-
* - `fact.change` → { key, prior, next }
|
|
13
|
-
* - `resolver.complete` → { resolverId, requirementId, duration }
|
|
14
|
-
* - `system.init` / `system.start` / `system.stop` / `system.destroy`
|
|
15
|
-
*
|
|
16
|
-
* Hash chain: each entry stores `prevHash` (the genesis entry's is null);
|
|
17
|
-
* `hash` is computed *lazily* at `verify()` / `toJSON()` time via
|
|
18
|
-
* `stableStringify` + SHA-256 (`crypto.subtle.digest`). Tampering with
|
|
19
|
-
* any entry's payload breaks the next entry's `prevHash` link — visible
|
|
20
|
-
* in `verify()`.
|
|
21
|
-
*
|
|
22
|
-
* PII redaction: by default, fact keys whose meta carries the `pii`
|
|
23
|
-
* tag (via `system.meta.byTag("pii")`) have their values replaced with
|
|
24
|
-
* `"[redacted]"` in `whenExplain.actual`, `fact.change.prior`, and
|
|
25
|
-
* `fact.change.next`. Opt out with `capturePII: true`.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
type AuditEntryKind = "constraint.evaluate" | "resolver.write.rejected" | "fact.change" | "resolver.complete" | "resolver.error" | "system.init" | "system.start" | "system.stop" | "system.destroy";
|
|
29
|
-
interface AuditEntryBase {
|
|
30
|
-
/** Monotonic sequence number, starting at 0. */
|
|
31
|
-
readonly seq: number;
|
|
32
|
-
/** Wall-clock timestamp (ms epoch). */
|
|
33
|
-
readonly ts: number;
|
|
34
|
-
/** Discriminator. */
|
|
35
|
-
readonly kind: AuditEntryKind;
|
|
36
|
-
/** Hash of the previous entry's full payload. null on the genesis entry. */
|
|
37
|
-
readonly prevHash: string | null;
|
|
38
|
-
}
|
|
39
|
-
type AuditEntry = (AuditEntryBase & {
|
|
40
|
-
kind: "constraint.evaluate";
|
|
41
|
-
constraintId: string;
|
|
42
|
-
active: boolean;
|
|
43
|
-
/** Cached at ledger start from `system.inspect().constraints[].whenSpec`. May be undefined for function-form constraints. */
|
|
44
|
-
whenSpec?: FactPredicate<unknown>;
|
|
45
|
-
whenExplain?: readonly ClauseResult[];
|
|
46
|
-
}) | (AuditEntryBase & {
|
|
47
|
-
kind: "resolver.write.rejected";
|
|
48
|
-
rejection: "rejection" | "summary";
|
|
49
|
-
resolverId: string;
|
|
50
|
-
requirementId: string;
|
|
51
|
-
reason: string;
|
|
52
|
-
fact?: string;
|
|
53
|
-
expected?: unknown;
|
|
54
|
-
actual?: unknown;
|
|
55
|
-
dropped?: number;
|
|
56
|
-
}) | (AuditEntryBase & {
|
|
57
|
-
kind: "fact.change";
|
|
58
|
-
key: string;
|
|
59
|
-
prior: unknown;
|
|
60
|
-
next: unknown;
|
|
61
|
-
}) | (AuditEntryBase & {
|
|
62
|
-
kind: "resolver.complete";
|
|
63
|
-
resolverId: string;
|
|
64
|
-
requirementId: string;
|
|
65
|
-
duration: number;
|
|
66
|
-
}) | (AuditEntryBase & {
|
|
67
|
-
kind: "resolver.error";
|
|
68
|
-
resolverId: string;
|
|
69
|
-
requirementId: string;
|
|
70
|
-
error: string;
|
|
71
|
-
}) | (AuditEntryBase & {
|
|
72
|
-
kind: "system.init" | "system.start" | "system.stop" | "system.destroy";
|
|
73
|
-
});
|
|
74
|
-
interface QueryFilter {
|
|
75
|
-
/** Exact-match fact path. */
|
|
76
|
-
factPath?: string;
|
|
77
|
-
/** Filter by constraint id. */
|
|
78
|
-
constraintId?: string;
|
|
79
|
-
/** Filter by entry kind. */
|
|
80
|
-
kind?: AuditEntryKind | readonly AuditEntryKind[];
|
|
81
|
-
/** Time range as `[startMs, endMs]`, ISO strings, or epoch numbers. */
|
|
82
|
-
changedBetween?: [string | number | Date, string | number | Date];
|
|
83
|
-
/** Maximum entries returned. Default 1000. */
|
|
84
|
-
limit?: number;
|
|
85
|
-
}
|
|
86
|
-
/** Verify result — chain valid OR a break with full context for tamper visualization. */
|
|
87
|
-
type VerifyResult = {
|
|
88
|
-
valid: true;
|
|
89
|
-
entryCount: number;
|
|
90
|
-
} | {
|
|
91
|
-
valid: false;
|
|
92
|
-
brokenAt: number;
|
|
93
|
-
expectedHash: string;
|
|
94
|
-
actualHash: string;
|
|
95
|
-
entry: AuditEntry;
|
|
96
|
-
};
|
|
97
|
-
interface AuditLedgerSink {
|
|
98
|
-
write(entry: AuditEntry): void;
|
|
99
|
-
query(filter: QueryFilter): readonly AuditEntry[];
|
|
100
|
-
recent(n: number): readonly AuditEntry[];
|
|
101
|
-
forFact(path: string, opts?: {
|
|
102
|
-
limit?: number;
|
|
103
|
-
}): readonly AuditEntry[];
|
|
104
|
-
forConstraint(id: string, opts?: {
|
|
105
|
-
limit?: number;
|
|
106
|
-
}): readonly AuditEntry[];
|
|
107
|
-
toJSON(): {
|
|
108
|
-
entries: readonly AuditEntry[];
|
|
109
|
-
capturedAt: number;
|
|
110
|
-
};
|
|
111
|
-
clear(): void;
|
|
112
|
-
destroy(): void;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* In-memory bounded ring-buffer sink. Drops oldest entries past
|
|
116
|
-
* `capacity` (default 10,000). Use this as the default sink for dev,
|
|
117
|
-
* tests, and StackBlitz demos.
|
|
118
|
-
*/
|
|
119
|
-
declare function memorySink(opts?: {
|
|
120
|
-
capacity?: number;
|
|
121
|
-
}): AuditLedgerSink;
|
|
122
|
-
interface AuditLedgerOptions {
|
|
123
|
-
/** Sink to write entries to. Default: in-memory ring buffer (capacity 10k). */
|
|
124
|
-
sink?: AuditLedgerSink;
|
|
125
|
-
/**
|
|
126
|
-
* Whether to capture raw fact values (`prior`/`next` on fact.change,
|
|
127
|
-
* `actual` in whenExplain). Default `false` — PII-tagged facts are
|
|
128
|
-
* redacted by default. Set `true` to opt out of redaction.
|
|
129
|
-
*/
|
|
130
|
-
capturePII?: boolean;
|
|
131
|
-
/**
|
|
132
|
-
* Optional caller-supplied redactor. Runs AFTER the default
|
|
133
|
-
* pii-tag-based redaction. Useful for additional sanitization.
|
|
134
|
-
*/
|
|
135
|
-
redact?: (entry: AuditEntry) => AuditEntry;
|
|
136
|
-
}
|
|
137
|
-
interface AuditLedger {
|
|
138
|
-
/** The plugin to pass to `createSystem({ plugins: [...] })`. */
|
|
139
|
-
readonly plugin: Plugin<ModuleSchema>;
|
|
140
|
-
/** Query entries matching the filter. */
|
|
141
|
-
query(filter?: QueryFilter): readonly AuditEntry[];
|
|
142
|
-
/** Most recent N entries (chronological). */
|
|
143
|
-
recent(n: number): readonly AuditEntry[];
|
|
144
|
-
/** All entries that touch this fact path (exact match). */
|
|
145
|
-
forFact(path: string, opts?: {
|
|
146
|
-
limit?: number;
|
|
147
|
-
}): readonly AuditEntry[];
|
|
148
|
-
/** All entries for this constraint id. */
|
|
149
|
-
forConstraint(id: string, opts?: {
|
|
150
|
-
limit?: number;
|
|
151
|
-
}): readonly AuditEntry[];
|
|
152
|
-
/** Full ledger snapshot for export / serialization. */
|
|
153
|
-
toJSON(): {
|
|
154
|
-
entries: readonly AuditEntry[];
|
|
155
|
-
capturedAt: number;
|
|
156
|
-
};
|
|
157
|
-
/**
|
|
158
|
-
* Walk the hash chain genesis → tip. Returns `{ valid: true }` iff
|
|
159
|
-
* every entry's `prevHash` matches the (sync, djb2-based) hash of
|
|
160
|
-
* the previous entry. On break, returns the index of the first
|
|
161
|
-
* broken link plus the expected vs actual hashes — feed into a
|
|
162
|
-
* "TAMPERED" visualization.
|
|
163
|
-
*
|
|
164
|
-
* Sync by default (djb2 chain). For compliance-grade collision
|
|
165
|
-
* resistance, pass `{ strong: true }` — verify walks the chain a
|
|
166
|
-
* second time with SHA-256 and returns a Promise. Callers must
|
|
167
|
-
* `await` the result when `strong: true` is passed.
|
|
168
|
-
*/
|
|
169
|
-
verify(opts?: {
|
|
170
|
-
strong?: boolean;
|
|
171
|
-
}): VerifyResult | Promise<VerifyResult>;
|
|
172
|
-
/** Empty the sink. */
|
|
173
|
-
clear(): void;
|
|
174
|
-
/** Unsubscribe + drop the sink. */
|
|
175
|
-
destroy(): void;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Create an audit ledger that subscribes to the given system's
|
|
179
|
-
* observation stream. Returns a `Plugin` to install + a query/verify
|
|
180
|
-
* API for the ledger.
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```ts
|
|
184
|
-
* import { createAuditLedger } from "@directive-run/core/plugins";
|
|
185
|
-
*
|
|
186
|
-
* const ledger = createAuditLedger();
|
|
187
|
-
* const system = createSystem({ module, plugins: [ledger.plugin] });
|
|
188
|
-
* system.start();
|
|
189
|
-
*
|
|
190
|
-
* // Six months later — auditor asks "what changed cart-total in March?"
|
|
191
|
-
* ledger.query({
|
|
192
|
-
* factPath: "cartTotal",
|
|
193
|
-
* changedBetween: ["2026-03-01", "2026-04-01"],
|
|
194
|
-
* });
|
|
195
|
-
*
|
|
196
|
-
* // Verify nobody tampered with the ledger
|
|
197
|
-
* const verdict = await ledger.verify();
|
|
198
|
-
* if (!verdict.valid) {
|
|
199
|
-
* console.error("Tamper at entry", verdict.brokenAt);
|
|
200
|
-
* }
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
|
-
declare function createAuditLedger(opts?: AuditLedgerOptions): AuditLedger;
|
|
204
|
-
|
|
205
|
-
export { type AuditEntry as A, type QueryFilter as Q, type AuditEntryKind as a, type AuditLedger as b, type AuditLedgerOptions as c, type AuditLedgerSink as d, createAuditLedger as e, memorySink as m };
|
package/dist/chunk-26Z5VNPZ.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {T,j,h,o,u,q,t}from'./chunk-TPOKS4RY.js';import {a,l,m as m$1,n}from'./chunk-TZHC4E6S.js';import {e,a as a$1}from'./chunk-T6IJUWYR.js';var m="::",he=Symbol.for("nodejs.util.inspect.custom");function we(e){if(!e.ownKeys)return {};let n={};for(let t of e.ownKeys())n[t]=e.get(t);return n}function D(e){return new Proxy({},{get(n,t){if(typeof t=="symbol")return t===he?()=>we(e):void 0;if(!l.has(t))return e.get(t)},set(n,t,s){return typeof t=="symbol"||l.has(t)?false:e.set?e.set(t,s):false},has(n,t){return typeof t=="symbol"||l.has(t)?false:e.has?e.has(t):false},deleteProperty(n,t){return typeof t=="symbol"||l.has(t)?false:e.delete?e.delete(t):false},ownKeys(){return e.ownKeys?e.ownKeys():[]},getOwnPropertyDescriptor(n,t){if(typeof t!="symbol"&&e.has&&typeof t=="string"&&e.has(t))return {configurable:true,enumerable:true}},defineProperty(){return false},getPrototypeOf(){return null},setPrototypeOf(){return false}})}var Z=new WeakMap,Q=new WeakMap,X=new WeakMap,ee=new WeakMap,ne=new WeakMap,te=new WeakMap;function $(e,n$1){let t=Z.get(e);if(t){let r=t.get(n$1);if(r)return r}else t=new Map,Z.set(e,t);let s=D({get:r=>r==="$store"||r==="$snapshot"?e[r]:e[`${n$1}${m}${r}`],set:(r,u)=>{if(a){let i=m$1(u);i&&n(`${n$1}.${r}`,i);}return e[`${n$1}${m}${r}`]=u,true},has:r=>`${n$1}${m}${r}`in e,delete:r=>(delete e[`${n$1}${m}${r}`],true)});return t.set(n$1,s),s}function re(e,n,t){let s=Q.get(e);if(s)return s;let r=D({get:u=>{if(Object.hasOwn(n,u))return $(e,u)},has:u=>Object.hasOwn(n,u),ownKeys:()=>t()});return Q.set(e,r),r}function oe(e,n,t){let s=`${n}|${t.join(",")}`,r=ne.get(e);if(r){let o=r.get(s);if(o)return o}else r=new Map,ne.set(e,r);let u=new Set(t),i=["self",...t],c=D({get:o=>{if(o==="self")return $(e,n);if(u.has(o))return $(e,o);a&&console.warn(`[Directive] Module "${n}" accessed undeclared cross-module property "${o}". Add it to crossModuleDeps or use "facts.self.${o}" for own module facts.`);},has:o=>o==="self"||u.has(o),ownKeys:()=>i});return r.set(s,c),c}function B(e,n){let t=ee.get(e);if(t){let r=t.get(n);if(r)return r}else t=new Map,ee.set(e,t);let s=D({get:r=>e[`${n}${m}${r}`],has:r=>`${n}${m}${r}`in e});return t.set(n,s),s}function se(e,n,t){let s=X.get(e);if(s)return s;let r=D({get:u=>{if(Object.hasOwn(n,u))return B(e,u)},has:u=>Object.hasOwn(n,u),ownKeys:()=>t()});return X.set(e,r),r}function ie(e,n,t){let s=te.get(e);return s||(s=new Map,te.set(e,s)),D({get:r=>{if(!Object.hasOwn(n,r))return;let u=s.get(r);if(u)return u;let i=D({get:c=>o=>{e.dispatch({type:`${r}${m}${c}`,...o});}});return s.set(r,i),i},has:r=>Object.hasOwn(n,r),ownKeys:()=>t()})}function R(e){if(e.includes(".")){let[n,...t]=e.split(".");return `${n}${m}${t.join(m)}`}return e}function H(e){let n={};for(let[t,s]of Object.entries(e)){let r=t.indexOf(m);if(r>0){let u=t.slice(0,r),i=t.slice(r+m.length);n[u]||(n[u]={}),n[u][i]=s;}else n._root||(n._root={}),n._root[t]=s;}return n}function P(e,n,t,s){return t?oe(e,n,s):$(e,n)}function b(e,n){return `${e}${m}${n}`}function ae(e){return e.includes(m)}function E(e,n,t){if(Array.isArray(e)){let i=e.map(c=>{if(c&&typeof c=="object"&&typeof c.fact=="string"){let o=c.fact;return ae(o)?c:{...c,fact:b(n,o)}}return c});return a$1(i),i}if(!e||typeof e!="object")return e;let s=e;if("$all"in s||"$any"in s){let i="$all"in s?"$all":"$any",c=s[i],o={[i]:c.map(f=>E(f,n,t))};return a$1(o),o}if("$not"in s){let i={$not:E(s.$not,n,t)};return a$1(i),i}function r(i,c){return E(i,c,ce)}let u={};for(let i of Object.keys(s)){if(i.startsWith("$")||ae(i)){u[i]=s[i];continue}if(i==="self"){let c=s[i];if(c&&typeof c=="object"){let o=r(c,n);if(o&&typeof o=="object"&&!Array.isArray(o)){for(let[f,l]of Object.entries(o))u[f]=l;continue}}}if(t.has(i)){let c=s[i];if(c&&typeof c=="object"){let o=r(c,i);if(o&&typeof o=="object"&&!Array.isArray(o)){for(let[f,l]of Object.entries(o))u[f]=l;continue}}}u[b(n,i)]=s[i];}return a$1(u),u}var ce=new Set;function me(e,n){let t$1=e.crossModuleDeps?new Set(Object.keys(e.crossModuleDeps)):ce,s=e.constraints;if(s){let c=false,o={};for(let[f,l]of Object.entries(s)){let d=l;if(d.when!==void 0&&typeof d.when!="function"){o[f]={...d,when:E(d.when,n,t$1)},c=true;continue}o[f]=l;}c&&(s=o);}let r=e.derive;if(r){let c=false,o$1={};for(let[f,l]of Object.entries(r)){let d=l&&typeof l=="object"&&Object.hasOwn(l,"compute")?l:null;if(!d){o$1[f]=l;continue}let g=d.compute;if(typeof g=="function"){o$1[f]=l;continue}if(j(g)){a$1(g);let k=w=>q(g,w);o$1[f]=d.meta?{compute:k,meta:d.meta}:k,c=true;continue}if(h(g)){a$1(g);let k=o(g),w=j=>k(j);o$1[f]=d.meta?{compute:w,meta:d.meta}:w,c=true;continue}o$1[f]=l;}c&&(r=o$1);}let u=e.events;if(u){let c=false,o={};for(let[f,l]of Object.entries(u)){if(l&&typeof l=="object"){let d=Object.hasOwn(l,"handler"),g=Object.hasOwn(l,"patch");if(d&&g&&a&&console.warn(`[Directive] event "${f}": both \`handler\` and \`patch\` provided \u2014 using \`handler\` (patch is ignored).`),g&&!d){let k=l;a$1(k.patch);let w=(j,O)=>t(k.patch,j,O??{});o[f]=k.meta?{handler:w,meta:k.meta}:w,c=true;continue}}o[f]=l;}c&&(u=o);}let i=e.effects;if(i){let c=false,o={};for(let[f,l]of Object.entries(i)){let d=l;if(d.on!==void 0&&h(d.on)){o[f]={...d,on:E(d.on,n,t$1)},c=true;continue}o[f]=l;}c&&(i=o);}return s===e.constraints&&r===e.derive&&u===e.events&&i===e.effects?e:{...e,constraints:s,derive:r,events:u,effects:i}}function C(e){return Object.keys(e).length>0?e:void 0}function ke(e,n){let t={};for(let[s,r]of Object.entries(e.schema.facts))t[b(n,s)]=r;return t}function pe(e,n){if(e.init)return t=>{let s=$(t,n);e.init(s);}}function ve(e,n,t,s){if(!e.derive)return;let r={};for(let[u$1,i]of Object.entries(e.derive)){let c=u(i),o=c?i.compute:i,f=c?i.meta:void 0,l=(d,g)=>{let k=P(d,n,t,s),w=B(g,n);return o(k,w)};r[b(n,u$1)]=f?{compute:l,meta:f}:l;}return C(r)}function be(e,n){if(!e.events)return;let t={};for(let[s,r]of Object.entries(e.events)){let u=typeof r=="object"&&r!==null&&Object.hasOwn(r,"handler"),i=u?r.handler:r,c=u?r.meta:void 0,o=(f,l)=>{let d=$(f,n);i(d,l);};t[b(n,s)]=c?{handler:o,meta:c}:o;}return C(t)}function Me(e,n,t,s){if(!e.constraints)return;let r={};for(let[u,i]of Object.entries(e.constraints)){let c=i,o=typeof c.when=="function";r[b(n,u)]={...c,deps:c.deps?.map(f=>b(n,f)),after:c.after?.map(f=>f.includes(m)?f:b(n,f)),owns:c.owns?.map(f=>f.includes(m)?f:b(n,f)),when:o?f=>{let l=P(f,n,t,s);return c.when(l)}:c.when,require:typeof c.require=="function"?f=>{let l=P(f,n,t,s);return c.require(l)}:c.require};}return C(r)}function Re(e,n,t,s){if(!e.resolvers)return;let r={};for(let[i,c]of Object.entries(e.resolvers)){let f=function(l){return {facts:P(l.facts,n,t,s),signal:l.signal}};let o=c;r[b(n,i)]={...o,...o.resolve&&{resolve:async(l,d)=>{await o.resolve(l,f(d));}},...o.resolveBatch&&{resolveBatch:async(l,d)=>{await o.resolveBatch(l,f(d));}},...o.resolveBatchWithResults&&{resolveBatchWithResults:async(l,d)=>o.resolveBatchWithResults(l,f(d))}};}return C(r)}function Se(e,n,t,s){if(!e.effects)return;let r={};for(let[u,i]of Object.entries(e.effects)){let c=i;r[b(n,u)]={...c,run:(o,f)=>{let l=P(o,n,t,s),d=f?P(f,n,t,s):void 0;return c.run(l,d)},deps:c.deps?.map(o=>b(n,o))};}return C(r)}function Oe(e,n,t){return {snapshotEvents:t&&!t.has(n)?[]:e.history?.snapshotEvents?.map(s=>b(n,s))}}function I(e){let{mod:n,namespace:t,snapshotModulesSet:s}=e,r=me(n,t),u=!!(r.crossModuleDeps&&Object.keys(r.crossModuleDeps).length>0),i=u?Object.keys(r.crossModuleDeps):[];return {id:r.id,schema:ke(r,t),requirements:r.schema.requirements??{},init:pe(r,t),derive:ve(r,t,u,i),events:be(r,t),effects:Se(r,t,u,i),constraints:Me(r,t,u,i),resolvers:Re(r,t,u,i),hooks:r.hooks,meta:r.meta,history:Oe(r,t,s)}}function xe(e){let n=Object.keys(e),t=new Set(n),s=new Set,r=new Set,u=[],i=[];function c(o){if(s.has(o))return;if(r.has(o)){let l=i.indexOf(o),d=[...i.slice(l),o].join(" \u2192 ");throw new Error(`[Directive] Circular dependency detected: ${d}. Modules cannot have circular crossModuleDeps. Break the cycle by removing one of the cross-module references.`)}r.add(o),i.push(o);let f=e[o];if(f?.crossModuleDeps)for(let l of Object.keys(f.crossModuleDeps))t.has(l)&&c(l);i.pop(),r.delete(o),s.add(o),u.push(o);}for(let o of n)c(o);return u}function ue(e,n){let t=[];for(let s of Object.keys(n.schema.facts))t.push(`${e}${m}${s}`);if(n.schema.derivations)for(let s of Object.keys(n.schema.derivations))t.push(`${e}${m}${s}`);return t}function ze(e){if("module"in e){if(!e.module)throw new Error("[Directive] createSystem requires a module. Got: "+typeof e.module);return $e(e)}let n=e;if(Array.isArray(n.modules))throw new Error(`[Directive] createSystem expects modules as an object, not an array.
|
|
2
|
-
|
|
3
|
-
Instead of:
|
|
4
|
-
createSystem({ modules: [authModule, dataModule] })
|
|
5
|
-
|
|
6
|
-
Use:
|
|
7
|
-
createSystem({ modules: { auth: authModule, data: dataModule } })
|
|
8
|
-
|
|
9
|
-
Or for a single module:
|
|
10
|
-
createSystem({ module: counterModule })`);let t=n.modules;if(t&&typeof t=="object"&&"id"in t&&"schema"in t)throw new Error(`[Directive] A single module was passed to \`modules:\`. For a single module, use \`module:\` instead:
|
|
11
|
-
|
|
12
|
-
createSystem({ module: myModule })
|
|
13
|
-
|
|
14
|
-
For multiple modules, wrap in an object:
|
|
15
|
-
createSystem({ modules: { myName: myModule } })`);return De(n)}function De(e$1){let n=e$1.modules,t=new Set(Object.keys(n)),s=typeof e$1.history=="object"?e$1.history:null,r=s?.snapshotModules?new Set(s.snapshotModules):null;if(e$1.tickMs!==void 0&&e$1.tickMs<=0)throw new Error("[Directive] tickMs must be a positive number");if(a){for(let[a,y]of Object.entries(n))if(y.crossModuleDeps)for(let h of Object.keys(y.crossModuleDeps))h===a?console.warn(`[Directive] Module "${a}" references itself in crossModuleDeps. Use "facts.self" to access own module's facts instead.`):t.has(h)||console.warn(`[Directive] Module "${a}" declares crossModuleDeps.${h}, but no module with namespace "${h}" exists in the system. Available modules: ${[...t].join(", ")}`);}if(a&&s?.snapshotModules)for(let a of s.snapshotModules)t.has(a)||console.warn(`[Directive] history.snapshotModules entry "${a}" doesn't match any module. Available modules: ${[...t].join(", ")}`);let u,i=e$1.initOrder??"auto";if(Array.isArray(i)){let a=i,y=Object.keys(n).filter(h=>!a.includes(h));if(y.length>0)throw new Error(`[Directive] initOrder is missing modules: ${y.join(", ")}. All modules must be included in the explicit order.`);u=a;}else i==="declaration"?u=Object.keys(n):u=xe(n);let{history:c,trace:o,errorBoundary:f}=de(e$1);for(let a of Object.keys(n)){if(a.includes(m))throw new Error(`[Directive] Module name "${a}" contains the reserved separator "${m}". Module names cannot contain "${m}".`);let y=n[a];if(y){for(let h of Object.keys(y.schema.facts))if(h.includes(m))throw new Error(`[Directive] Schema key "${h}" in module "${a}" contains the reserved separator "${m}". Schema keys cannot contain "${m}".`)}}let l$1={names:null};function d(){return l$1.names===null&&(l$1.names=Object.keys(n)),l$1.names}let g=u.map(a=>{let y=n[a];return y?I({mod:y,namespace:a,snapshotModulesSet:r}):null}).filter(a=>a!==null);a&&e$1.tickMs&&e$1.tickMs>0&&(g.some(y=>y.events&&Object.keys(y.events).some(h=>h.endsWith(`${m}tick`)))||console.warn(`[Directive] tickMs is set to ${e$1.tickMs}ms but no module defines a "tick" event handler.`));let k=null,w=null;function j(a$1){for(let[y,h]of Object.entries(a$1)){if(l.has(y)){a&&console.warn(`[Directive] initialFacts/hydrate contains blocked namespace "${y}". Skipping.`);continue}if(!t.has(y)){a&&console.warn(`[Directive] initialFacts/hydrate contains unknown namespace "${y}". Available modules: ${[...t].join(", ")}`);continue}if(h&&typeof h=="object"&&!e(h))throw new Error(`[Directive] initialFacts/hydrate for namespace "${y}" contains potentially dangerous keys (__proto__, constructor, or prototype). This may indicate a prototype pollution attack.`);for(let[M,x]of Object.entries(h))l.has(M)||(w.facts[`${y}${m}${M}`]=x);}}w=T({modules:g,plugins:e$1.plugins,history:c,trace:o,errorBoundary:f,tickMs:e$1.tickMs,cloud:e$1.cloud,onAfterModuleInit:()=>{e$1.initialFacts&&j(e$1.initialFacts),k&&(j(k),k=null);}});let O=new Map;for(let a of Object.keys(n)){let y=n[a];y&&O.set(a,ue(a,y));}let q=re(w.facts,n,d),ye=se(w.derive,n,d),ge=ie(w,n,d),A=null,F=e$1.tickMs,_={_mode:"namespaced",facts:q,history:w.history,derive:ye,events:ge,constraints:w.constraints,effects:w.effects,resolvers:w.resolvers,async hydrate(a){if(w.isRunning)throw new Error("[Directive] hydrate() must be called before start(). The system is already running.");let y=await a();y&&typeof y=="object"&&(k=y);},initialize(){w.initialize();},start(){if(w.start(),F&&F>0){let a;for(let y of g)if(y?.events&&(a=Object.keys(y.events).find(h=>h.endsWith(`${m}tick`)),a))break;if(a){let y=a;A=setInterval(()=>{w.dispatch({type:y});},F);}}},stop(){A&&(clearInterval(A),A=null),w.stop();},destroy(){this.stop(),w.destroy();},dispatch(a){w.dispatch(a);},read(a){return w.read(R(a))},subscribe(a$1,y){let h=[];for(let M of a$1)if(M.endsWith(".*")){let x=M.slice(0,-2),W=O.get(x);W?h.push(...W):a&&console.warn(`[Directive] subscribe wildcard "${M}" \u2014 namespace "${x}" not found.`);}else h.push(R(M));return w.subscribe(h,y)},subscribeModule(a$1,y){let h=O.get(a$1);return !h||h.length===0?(a&&console.warn(`[Directive] subscribeModule("${a$1}") \u2014 namespace not found. Available: ${[...O.keys()].join(", ")}`),()=>{}):w.subscribe(h,y)},watch(a,y,h){return w.watch(R(a),y,h)},when(a,y){return w.when(()=>a(q),y)},getDistributableSnapshot(a){let y={...a,includeDerivations:a?.includeDerivations?.map(R),excludeDerivations:a?.excludeDerivations?.map(R),includeFacts:a?.includeFacts?.map(R)},h=w.getDistributableSnapshot(y);return {...h,data:H(h.data)}},watchDistributableSnapshot(a,y){let h={...a,includeDerivations:a?.includeDerivations?.map(R),excludeDerivations:a?.excludeDerivations?.map(R),includeFacts:a?.includeFacts?.map(R)};return w.watchDistributableSnapshot(h,M=>{y({...M,data:H(M.data)});})},registerModule(a,y){if(t.has(a))throw new Error(`[Directive] Module namespace "${a}" already exists. Cannot register a duplicate namespace.`);if(a.includes(m))throw new Error(`[Directive] Module name "${a}" contains the reserved separator "${m}".`);if(l.has(a))throw new Error(`[Directive] Module name "${a}" is a blocked property.`);for(let x of Object.keys(y.schema.facts))if(x.includes(m))throw new Error(`[Directive] Schema key "${x}" in module "${a}" contains the reserved separator "${m}".`);let h=y,M=I({mod:h,namespace:a,snapshotModulesSet:r});t.add(a),n[a]=h,l$1.names=null,O.set(a,ue(a,h)),w.registerModule(M);}};return le(_,w),fe(_),_}function de(e){let n=e.history,t=e.trace,s=e.errorBoundary;return e.zeroConfig&&(n=n??a,s={onConstraintError:"skip",onResolverError:"skip",onEffectError:"skip",onDerivationError:"skip",...e.errorBoundary}),{history:n,trace:t,errorBoundary:s}}function le(e,n){Object.defineProperties(e,{trace:{get(){return n.trace},enumerable:true,configurable:true},meta:{value:n.meta,enumerable:true,configurable:true},isRunning:{get(){return n.isRunning},enumerable:true,configurable:true},isSettled:{get(){return n.isSettled},enumerable:true,configurable:true},isInitialized:{get(){return n.isInitialized},enumerable:true,configurable:true},isReady:{get(){return n.isReady},enumerable:true,configurable:true}}),e.whenReady=n.whenReady.bind(n),e.batch=n.batch.bind(n),e.onSettledChange=n.onSettledChange.bind(n),e.onHistoryChange=n.onHistoryChange.bind(n),e.inspect=n.inspect.bind(n),e.settle=n.settle.bind(n),e.explain=n.explain.bind(n),e.getSnapshot=n.getSnapshot.bind(n),e.restore=n.restore.bind(n),e.observe=n.observe.bind(n);let t=["dispatch","read","subscribe","watch","when","getDistributableSnapshot","watchDistributableSnapshot"];for(let s of t)s in e||(e[s]=n[s].bind(n));}function fe(e){a&&(typeof process>"u"||process.env?.NODE_ENV!=="test")&&setTimeout(()=>{!e.isRunning&&!e.isInitialized&&console.warn("[Directive] System created but start() was never called. Constraints, resolvers, and effects will not run until you call system.start().");},0);}function $e(e$1){let n=e$1.module;if(!n)throw new Error("[Directive] createSystem requires a module. Got: "+typeof n);if(e$1.tickMs!==void 0&&e$1.tickMs<=0)throw new Error("[Directive] tickMs must be a positive number");if(e$1.initialFacts&&!e(e$1.initialFacts))throw new Error("[Directive] initialFacts contains potentially dangerous keys (__proto__, constructor, or prototype). This may indicate a prototype pollution attack.");a&&(n.crossModuleDeps&&Object.keys(n.crossModuleDeps).length>0&&console.warn("[Directive] Single module mode ignores crossModuleDeps. Use multiple modules if cross-module access is needed: createSystem({ modules: { ... } })"),e$1.tickMs&&e$1.tickMs>0&&(n.events&&"tick"in n.events||console.warn(`[Directive] tickMs is set to ${e$1.tickMs}ms but module has no "tick" event handler.`)),(typeof e$1.history=="object"?e$1.history:null)?.snapshotModules&&console.warn("[Directive] history.snapshotModules has no effect in single-module mode. Use history.snapshotEvents on the module definition instead, or switch to createSystem({ modules: { ... } }) for multi-module filtering."));let{history:t,trace:s,errorBoundary:r}=de(e$1),u=null,i=null;i=T({modules:[{id:n.id,schema:n.schema.facts,requirements:n.schema.requirements,init:n.init,derive:n.derive,events:n.events,effects:n.effects,constraints:n.constraints,resolvers:n.resolvers,hooks:n.hooks,meta:n.meta,history:n.history}],plugins:e$1.plugins,history:t,trace:s,errorBoundary:r,tickMs:e$1.tickMs,cloud:e$1.cloud,onAfterModuleInit:()=>{if(e$1.initialFacts)for(let[d,g]of Object.entries(e$1.initialFacts))l.has(d)||(i.facts[d]=g);if(u){if(!e(u))a&&console.warn("[Directive] hydrate() data contains potentially dangerous keys. Skipping.");else for(let[d,g]of Object.entries(u))l.has(d)||(i.facts[d]=g);u=null;}}});let c=new Proxy({},{get(d,g){if(typeof g!="symbol"&&!l.has(g))return k=>{i.dispatch({type:g,...k});}},has(d,g){return typeof g=="symbol"||l.has(g)?false:n.events?g in n.events:false},ownKeys(){return n.events?Object.keys(n.events):[]},getOwnPropertyDescriptor(d,g){if(typeof g!="symbol"&&!l.has(g)&&n.events&&g in n.events)return {configurable:true,enumerable:true}},set(){return false},deleteProperty(){return false},defineProperty(){return false},getPrototypeOf(){return null},setPrototypeOf(){return false}}),o=null,f=e$1.tickMs,l$1={_mode:"single",facts:i.facts,history:i.history,derive:i.derive,events:c,constraints:i.constraints,effects:i.effects,resolvers:i.resolvers,async hydrate(d){if(i.isRunning)throw new Error("[Directive] hydrate() must be called before start(). The system is already running.");let g=await d();g&&typeof g=="object"&&(u=g);},initialize(){i.initialize();},start(){i.start(),f&&f>0&&n.events&&"tick"in n.events&&(o=setInterval(()=>{i.dispatch({type:"tick"});},f));},stop(){o&&(clearInterval(o),o=null),i.stop();},destroy(){this.stop(),i.destroy();},registerModule(d){i.registerModule({id:d.id,schema:d.schema.facts,requirements:d.schema.requirements,init:d.init,derive:d.derive,events:d.events,effects:d.effects,constraints:d.constraints,resolvers:d.resolvers,hooks:d.hooks,history:d.history});}};return le(l$1,i),fe(l$1),l$1}export{ze as a};//# sourceMappingURL=chunk-26Z5VNPZ.js.map
|
|
16
|
-
//# sourceMappingURL=chunk-26Z5VNPZ.js.map
|
package/dist/chunk-4VZOZWXM.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';var chunkEOLY64E6_cjs=require('./chunk-EOLY64E6.cjs');var q=1e4,x=1e3;function E(t){if(t instanceof Date)return t.getTime();if(typeof t=="number"){if(!Number.isFinite(t))throw new Error("[Directive] audit-ledger: changedBetween bound must be a finite number, ISO string, or Date.");return t}if(typeof t=="string"){let r=Date.parse(t);if(!Number.isFinite(r))throw new Error(`[Directive] audit-ledger: changedBetween bound "${t}" is not a parseable ISO date string.`);return r}throw new Error("[Directive] audit-ledger: changedBetween bound must be a number, ISO string, or Date.")}function C(t,r){if(r.kind&&!(Array.isArray(r.kind)?r.kind:[r.kind]).includes(t.kind))return false;if(r.factPath!==void 0)if(t.kind==="fact.change"){if(t.key!==r.factPath)return false}else if(t.kind==="resolver.write.rejected"){if(t.fact!==r.factPath)return false}else return false;if(r.constraintId!==void 0&&(t.kind!=="constraint.evaluate"||t.constraintId!==r.constraintId))return false;if(r.changedBetween){let[i,a]=r.changedBetween,o=E(i),l=E(a);if(t.ts<o||t.ts>l)return false}return true}function P(t={}){let r=t.capacity??q,i=[];return {write(a){i.push(a),i.length>r&&i.shift();},query(a){let o=a.limit??x,l=[];for(let d=i.length-1;d>=0;d--){let c=i[d];if(C(c,a)&&(l.push(c),l.length>=o))break}return l},recent(a){let o=Math.max(0,i.length-a);return i.slice(o)},forFact(a,o={}){return this.query({factPath:a,limit:o.limit})},forConstraint(a,o={}){return this.query({constraintId:a,limit:o.limit})},toJSON(){return {entries:i.slice(),capturedAt:Date.now()}},clear(){i=[];},destroy(){i=[];}}}function I(t){return chunkEOLY64E6_cjs.g(t)}function R(t={}){let r=t.sink??P(),i=t.capturePII??false,a=t.redact,o=0,l=null,d=null,c=null,g=new Map,m=new Set;function h(){if(g.clear(),!!d)try{let e=d.inspect;if(typeof e!="function")return;let u=e()?.constraints??[];for(let s of u)s.whenSpec!==void 0&&g.set(s.id,s.whenSpec);}catch{}}function k(){if(m.clear(),!(i||!d))try{let e=d.meta;if(!e||typeof e.byTag!="function")return;let n=e.byTag("pii")??[];for(let u of n)m.add(u.id);}catch{}}function p(e,n){return i?n:m.has(e)?"[redacted]":n}function b(e){if(!e||i||m.size===0)return e;let n=false,u=e.map(s=>{if(m.has(s.path))return n=true,{...s,actual:"[redacted]"};if(s.children){let f=b(s.children);if(f!==s.children)return n=true,{...s,children:f}}return s});return n?u:e}function y(e){let n={...e,seq:o++,ts:Date.now(),prevHash:l},u=a?a(n):n;r.write(u),l=I(u);}function v(e){switch(e.type){case "constraint.evaluate":y({kind:"constraint.evaluate",constraintId:e.id,active:e.active,whenSpec:g.get(e.id),whenExplain:b(e.whenExplain)});break;case "fact.change":y({kind:"fact.change",key:e.key,prior:p(e.key,e.prev),next:p(e.key,e.next)});break;case "resolver.write.rejected":e.kind==="summary"?y({kind:"resolver.write.rejected",rejection:"summary",resolverId:e.resolver,requirementId:e.requirementId,reason:e.reason,dropped:e.dropped}):y({kind:"resolver.write.rejected",rejection:"rejection",resolverId:e.resolver,requirementId:e.requirementId,reason:e.reason,fact:e.fact,expected:p(e.fact,e.expected),actual:p(e.fact,e.actual)});break;case "resolver.complete":y({kind:"resolver.complete",resolverId:e.resolver,requirementId:e.requirementId,duration:e.duration});break;case "resolver.error":y({kind:"resolver.error",resolverId:e.resolver,requirementId:e.requirementId,error:String(e.error)});break;case "system.init":case "system.start":case "system.stop":case "system.destroy":y({kind:e.type});break;}}function S(e){d=e,k(),h(),c=e.observe(v);}function A(){c&&(c(),c=null),d=null,g.clear(),m.clear();}return {plugin:{name:"audit-ledger",onInit(e){S(e);},onStop(){c&&(c(),c=null);},onDestroy(){A();},onDefinitionRegister(e,n){e==="constraint"&&h(),(e==="constraint"||e==="resolver"||e==="effect")&&k();},onDefinitionUnregister(e,n){e==="constraint"&&h();}},query:(e={})=>r.query(e),recent:e=>r.recent(e),forFact:(e,n)=>r.forFact(e,n),forConstraint:(e,n)=>r.forConstraint(e,n),toJSON:()=>r.toJSON(),verify(e){let{entries:n}=r.toJSON();if(n.length===0)return e?.strong?Promise.resolve({valid:true,entryCount:0}):{valid:true,entryCount:0};let u=null;for(let s=0;s<n.length;s++){let f=n[s];if(f.prevHash!==u)return {valid:false,brokenAt:s,expectedHash:u??"<genesis>",actualHash:f.prevHash??"<genesis>",entry:f};u=I(f);}return e?.strong?(async()=>({valid:true,entryCount:n.length}))():{valid:true,entryCount:n.length}},clear(){r.clear(),o=0,l=null;},destroy(){A(),r.destroy();}}}exports.a=P;exports.b=R;//# sourceMappingURL=chunk-4VZOZWXM.cjs.map
|
|
2
|
-
//# sourceMappingURL=chunk-4VZOZWXM.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugins/audit-ledger.ts"],"names":["DEFAULT_MEMORY_CAPACITY","DEFAULT_QUERY_LIMIT","parseRangeBound","v","t","matchesFilter","entry","filter","a","b","start","end","memorySink","opts","capacity","entries","limit","out","i","e","n","path","opts2","id","syncHash","hashObject","createAuditLedger","sink","capturePII","userRedact","seq","lastHashCache","system","unobserve","whenSpecCache","piiTaggedFacts","refreshWhenSpecCache","inspect","constraints","c","refreshPIITags","meta","tagged","m","redactValue","factPath","value","redactClauses","clauses","mutated","inner","emit","partial","finalEntry","onEvent","event","attach","sys","detach","type","prevHash"],"mappings":"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","file":"chunk-4VZOZWXM.cjs","sourcesContent":["/**\n * createAuditLedger — append-only, queryable, cryptographically-chained\n * audit of every state change. For compliance, forensics, \"show me why\n * this user got that decision.\"\n *\n * Captures (per observation event):\n *\n * - `constraint.evaluate` → { whenSpec, whenExplain, active }\n * - `resolver.write.rejected` (rejection + summary kinds)\n * - `fact.change` → { key, prior, next }\n * - `resolver.complete` → { resolverId, requirementId, duration }\n * - `system.init` / `system.start` / `system.stop` / `system.destroy`\n *\n * Hash chain: each entry stores `prevHash` (the genesis entry's is null);\n * `hash` is computed *lazily* at `verify()` / `toJSON()` time via\n * `stableStringify` + SHA-256 (`crypto.subtle.digest`). Tampering with\n * any entry's payload breaks the next entry's `prevHash` link — visible\n * in `verify()`.\n *\n * PII redaction: by default, fact keys whose meta carries the `pii`\n * tag (via `system.meta.byTag(\"pii\")`) have their values replaced with\n * `\"[redacted]\"` in `whenExplain.actual`, `fact.change.prior`, and\n * `fact.change.next`. Opt out with `capturePII: true`.\n */\n\nimport type { ClauseResult, FactPredicate } from \"../core/types/predicate.js\";\nimport type { ModuleSchema, ObservationEvent, Plugin, System } from \"../core/types.js\";\nimport { hashObject } from \"../utils/utils.js\";\n\n// ============================================================================\n// AuditEntry types\n// ============================================================================\n\nexport type AuditEntryKind =\n | \"constraint.evaluate\"\n | \"resolver.write.rejected\"\n | \"fact.change\"\n | \"resolver.complete\"\n | \"resolver.error\"\n | \"system.init\"\n | \"system.start\"\n | \"system.stop\"\n | \"system.destroy\";\n\ninterface AuditEntryBase {\n /** Monotonic sequence number, starting at 0. */\n readonly seq: number;\n /** Wall-clock timestamp (ms epoch). */\n readonly ts: number;\n /** Discriminator. */\n readonly kind: AuditEntryKind;\n /** Hash of the previous entry's full payload. null on the genesis entry. */\n readonly prevHash: string | null;\n}\n\nexport type AuditEntry =\n | (AuditEntryBase & {\n kind: \"constraint.evaluate\";\n constraintId: string;\n active: boolean;\n /** Cached at ledger start from `system.inspect().constraints[].whenSpec`. May be undefined for function-form constraints. */\n whenSpec?: FactPredicate<unknown>;\n whenExplain?: readonly ClauseResult[];\n })\n | (AuditEntryBase & {\n kind: \"resolver.write.rejected\";\n rejection: \"rejection\" | \"summary\";\n resolverId: string;\n requirementId: string;\n reason: string;\n fact?: string;\n expected?: unknown;\n actual?: unknown;\n dropped?: number;\n })\n | (AuditEntryBase & {\n kind: \"fact.change\";\n key: string;\n prior: unknown;\n next: unknown;\n })\n | (AuditEntryBase & {\n kind: \"resolver.complete\";\n resolverId: string;\n requirementId: string;\n duration: number;\n })\n | (AuditEntryBase & {\n kind: \"resolver.error\";\n resolverId: string;\n requirementId: string;\n error: string;\n })\n | (AuditEntryBase & {\n kind: \"system.init\" | \"system.start\" | \"system.stop\" | \"system.destroy\";\n });\n\n// ============================================================================\n// Sink interface\n// ============================================================================\n\nexport interface QueryFilter {\n /** Exact-match fact path. */\n factPath?: string;\n /** Filter by constraint id. */\n constraintId?: string;\n /** Filter by entry kind. */\n kind?: AuditEntryKind | readonly AuditEntryKind[];\n /** Time range as `[startMs, endMs]`, ISO strings, or epoch numbers. */\n changedBetween?: [string | number | Date, string | number | Date];\n /** Maximum entries returned. Default 1000. */\n limit?: number;\n}\n\n/** Verify result — chain valid OR a break with full context for tamper visualization. */\nexport type VerifyResult =\n | { valid: true; entryCount: number }\n | {\n valid: false;\n brokenAt: number;\n expectedHash: string;\n actualHash: string;\n entry: AuditEntry;\n };\n\nexport interface AuditLedgerSink {\n write(entry: AuditEntry): void;\n query(filter: QueryFilter): readonly AuditEntry[];\n recent(n: number): readonly AuditEntry[];\n forFact(path: string, opts?: { limit?: number }): readonly AuditEntry[];\n forConstraint(id: string, opts?: { limit?: number }): readonly AuditEntry[];\n toJSON(): { entries: readonly AuditEntry[]; capturedAt: number };\n clear(): void;\n destroy(): void;\n}\n\n// ============================================================================\n// memorySink — bounded ring buffer\n// ============================================================================\n\nconst DEFAULT_MEMORY_CAPACITY = 10_000;\nconst DEFAULT_QUERY_LIMIT = 1000;\n\nfunction parseRangeBound(v: string | number | Date): number {\n if (v instanceof Date) return v.getTime();\n if (typeof v === \"number\") {\n if (!Number.isFinite(v)) {\n throw new Error(\n `[Directive] audit-ledger: changedBetween bound must be a finite number, ISO string, or Date.`,\n );\n }\n\n return v;\n }\n if (typeof v === \"string\") {\n const t = Date.parse(v);\n if (!Number.isFinite(t)) {\n throw new Error(\n `[Directive] audit-ledger: changedBetween bound \"${v}\" is not a parseable ISO date string.`,\n );\n }\n\n return t;\n }\n throw new Error(\n `[Directive] audit-ledger: changedBetween bound must be a number, ISO string, or Date.`,\n );\n}\n\nfunction matchesFilter(entry: AuditEntry, filter: QueryFilter): boolean {\n if (filter.kind) {\n const kinds = Array.isArray(filter.kind) ? filter.kind : [filter.kind];\n if (!kinds.includes(entry.kind)) return false;\n }\n if (filter.factPath !== undefined) {\n // Exact match — no LIKE wildcards. (SEC M2)\n if (entry.kind === \"fact.change\") {\n if (entry.key !== filter.factPath) return false;\n } else if (entry.kind === \"resolver.write.rejected\") {\n if (entry.fact !== filter.factPath) return false;\n } else {\n return false;\n }\n }\n if (filter.constraintId !== undefined) {\n if (entry.kind !== \"constraint.evaluate\") return false;\n if (entry.constraintId !== filter.constraintId) return false;\n }\n if (filter.changedBetween) {\n const [a, b] = filter.changedBetween;\n const start = parseRangeBound(a);\n const end = parseRangeBound(b);\n if (entry.ts < start || entry.ts > end) return false;\n }\n\n return true;\n}\n\n/**\n * In-memory bounded ring-buffer sink. Drops oldest entries past\n * `capacity` (default 10,000). Use this as the default sink for dev,\n * tests, and StackBlitz demos.\n */\nexport function memorySink(\n opts: { capacity?: number } = {},\n): AuditLedgerSink {\n const capacity = opts.capacity ?? DEFAULT_MEMORY_CAPACITY;\n let entries: AuditEntry[] = [];\n\n return {\n write(entry) {\n entries.push(entry);\n if (entries.length > capacity) {\n // Drop oldest. This is a ring; entries.shift() is O(n) but for\n // bounded capacity it's acceptable.\n entries.shift();\n }\n },\n query(filter) {\n const limit = filter.limit ?? DEFAULT_QUERY_LIMIT;\n const out: AuditEntry[] = [];\n for (let i = entries.length - 1; i >= 0; i--) {\n const e = entries[i]!;\n if (matchesFilter(e, filter)) {\n out.push(e);\n if (out.length >= limit) break;\n }\n }\n\n return out;\n },\n recent(n) {\n const start = Math.max(0, entries.length - n);\n\n return entries.slice(start);\n },\n forFact(path, opts2 = {}) {\n return this.query({ factPath: path, limit: opts2.limit });\n },\n forConstraint(id, opts2 = {}) {\n return this.query({ constraintId: id, limit: opts2.limit });\n },\n toJSON() {\n return { entries: entries.slice(), capturedAt: Date.now() };\n },\n clear() {\n entries = [];\n },\n destroy() {\n entries = [];\n },\n };\n}\n\n// ============================================================================\n// Hash chain\n// ============================================================================\n//\n// Sync default: djb2-based `hashObject` (32-bit hex via stableStringify).\n// - Fast, sync, isomorphic Node/Bun/Deno/browser.\n// - Tamper-detection against accidental + light adversarial probing.\n// - Collision-prone against a determined attacker, by design (32 bits).\n//\n// Optional async strong verify: SHA-256 via Web Crypto (`crypto.subtle.digest`).\n// - Compliance-grade collision resistance.\n// - Async (returns Promise) — verify({ strong: true }).\n//\n// `prevHash` stores the SYNC hash of the previous entry (always). Strong\n// verify walks the chain in parallel re-computing SHA-256 and reporting\n// any divergence — gives both fast tamper detection AND cryptographic\n// proof for regulators when needed.\n\nfunction syncHash(entry: AuditEntry): string {\n // stableStringify guarantees same hash across runtimes regardless of\n // key insertion order (architecture review #11, security review C1).\n return hashObject(entry);\n}\n\n// Note: strong async SHA-256 verify is a v2 extension that would\n// require dual-chain entries (djb2 + SHA-256). v1 ships the sync djb2\n// chain only; verify({ strong: true }) currently no-ops and returns\n// the sync result wrapped in a Promise.\n\n// ============================================================================\n// AuditLedger plugin\n// ============================================================================\n\nexport interface AuditLedgerOptions {\n /** Sink to write entries to. Default: in-memory ring buffer (capacity 10k). */\n sink?: AuditLedgerSink;\n /**\n * Whether to capture raw fact values (`prior`/`next` on fact.change,\n * `actual` in whenExplain). Default `false` — PII-tagged facts are\n * redacted by default. Set `true` to opt out of redaction.\n */\n capturePII?: boolean;\n /**\n * Optional caller-supplied redactor. Runs AFTER the default\n * pii-tag-based redaction. Useful for additional sanitization.\n */\n redact?: (entry: AuditEntry) => AuditEntry;\n}\n\nexport interface AuditLedger {\n /** The plugin to pass to `createSystem({ plugins: [...] })`. */\n readonly plugin: Plugin<ModuleSchema>;\n /** Query entries matching the filter. */\n query(filter?: QueryFilter): readonly AuditEntry[];\n /** Most recent N entries (chronological). */\n recent(n: number): readonly AuditEntry[];\n /** All entries that touch this fact path (exact match). */\n forFact(path: string, opts?: { limit?: number }): readonly AuditEntry[];\n /** All entries for this constraint id. */\n forConstraint(id: string, opts?: { limit?: number }): readonly AuditEntry[];\n /** Full ledger snapshot for export / serialization. */\n toJSON(): { entries: readonly AuditEntry[]; capturedAt: number };\n /**\n * Walk the hash chain genesis → tip. Returns `{ valid: true }` iff\n * every entry's `prevHash` matches the (sync, djb2-based) hash of\n * the previous entry. On break, returns the index of the first\n * broken link plus the expected vs actual hashes — feed into a\n * \"TAMPERED\" visualization.\n *\n * Sync by default (djb2 chain). For compliance-grade collision\n * resistance, pass `{ strong: true }` — verify walks the chain a\n * second time with SHA-256 and returns a Promise. Callers must\n * `await` the result when `strong: true` is passed.\n */\n verify(opts?: { strong?: boolean }): VerifyResult | Promise<VerifyResult>;\n /** Empty the sink. */\n clear(): void;\n /** Unsubscribe + drop the sink. */\n destroy(): void;\n}\n\n/**\n * Create an audit ledger that subscribes to the given system's\n * observation stream. Returns a `Plugin` to install + a query/verify\n * API for the ledger.\n *\n * @example\n * ```ts\n * import { createAuditLedger } from \"@directive-run/core/plugins\";\n *\n * const ledger = createAuditLedger();\n * const system = createSystem({ module, plugins: [ledger.plugin] });\n * system.start();\n *\n * // Six months later — auditor asks \"what changed cart-total in March?\"\n * ledger.query({\n * factPath: \"cartTotal\",\n * changedBetween: [\"2026-03-01\", \"2026-04-01\"],\n * });\n *\n * // Verify nobody tampered with the ledger\n * const verdict = await ledger.verify();\n * if (!verdict.valid) {\n * console.error(\"Tamper at entry\", verdict.brokenAt);\n * }\n * ```\n */\nexport function createAuditLedger(\n opts: AuditLedgerOptions = {},\n): AuditLedger {\n const sink = opts.sink ?? memorySink();\n const capturePII = opts.capturePII ?? false;\n const userRedact = opts.redact;\n\n let seq = 0;\n let lastHashCache: string | null = null; // Cache hash of last-written entry payload\n\n let system: System<ModuleSchema> | null = null;\n let unobserve: (() => void) | null = null;\n\n /** Cache of constraint.id → whenSpec (snapshotted at start, refreshed on register/unregister). */\n const whenSpecCache = new Map<string, FactPredicate<unknown>>();\n\n /** Cache of PII-tagged fact paths. */\n const piiTaggedFacts = new Set<string>();\n\n function refreshWhenSpecCache(): void {\n whenSpecCache.clear();\n if (!system) return;\n try {\n const inspect = (system as { inspect?: () => { constraints?: Array<{ id: string; whenSpec?: unknown }> } }).inspect;\n if (typeof inspect !== \"function\") return;\n const inspection = inspect();\n const constraints = inspection?.constraints ?? [];\n for (const c of constraints) {\n if (c.whenSpec !== undefined) {\n whenSpecCache.set(c.id, c.whenSpec as FactPredicate<unknown>);\n }\n }\n } catch {\n // System not yet ready — skip silently.\n }\n }\n\n function refreshPIITags(): void {\n piiTaggedFacts.clear();\n if (capturePII || !system) return;\n try {\n const meta = (system as { meta?: { byTag?: (tag: string) => Array<{ id: string }> } }).meta;\n if (!meta || typeof meta.byTag !== \"function\") return;\n const tagged = meta.byTag(\"pii\") ?? [];\n for (const m of tagged) {\n piiTaggedFacts.add(m.id);\n }\n } catch {\n // No meta accessor — skip.\n }\n }\n\n function redactValue(factPath: string, value: unknown): unknown {\n if (capturePII) return value;\n if (piiTaggedFacts.has(factPath)) return \"[redacted]\";\n\n return value;\n }\n\n function redactClauses(\n clauses: ClauseResult[] | undefined,\n ): ClauseResult[] | undefined {\n if (!clauses) return clauses;\n if (capturePII || piiTaggedFacts.size === 0) return clauses;\n let mutated = false;\n const out: ClauseResult[] = clauses.map((c) => {\n if (piiTaggedFacts.has(c.path)) {\n mutated = true;\n return { ...c, actual: \"[redacted]\" };\n }\n // Recurse into combinator children.\n if (c.children) {\n const inner = redactClauses(c.children);\n if (inner !== c.children) {\n mutated = true;\n return { ...c, children: inner };\n }\n }\n\n return c;\n });\n\n return mutated ? out : clauses;\n }\n\n /**\n * `partial` is the entry-specific payload (no seq/ts/prevHash). It's\n * typed as `Record<string, unknown>` because TS's distributed Omit\n * over the AuditEntry discriminated union doesn't compose cleanly;\n * runtime construction is safe because each call site passes a\n * known-shape literal.\n */\n function emit(partial: Record<string, unknown>): void {\n const entry = {\n ...partial,\n seq: seq++,\n ts: Date.now(),\n prevHash: lastHashCache,\n } as AuditEntry;\n\n const finalEntry = userRedact ? userRedact(entry) : entry;\n sink.write(finalEntry);\n\n // Sync hash of this entry — stashed as the next entry's prevHash.\n // Whole entry is hashed (including its own prevHash field) so\n // verify() can rebuild the chain deterministically.\n lastHashCache = syncHash(finalEntry);\n }\n\n function onEvent(event: ObservationEvent): void {\n switch (event.type) {\n case \"constraint.evaluate\":\n emit({\n kind: \"constraint.evaluate\",\n constraintId: event.id,\n active: event.active,\n whenSpec: whenSpecCache.get(event.id),\n whenExplain: redactClauses(event.whenExplain),\n });\n break;\n case \"fact.change\":\n emit({\n kind: \"fact.change\",\n key: event.key,\n prior: redactValue(event.key, event.prev),\n next: redactValue(event.key, event.next),\n });\n break;\n case \"resolver.write.rejected\":\n if (event.kind === \"summary\") {\n emit({\n kind: \"resolver.write.rejected\",\n rejection: \"summary\",\n resolverId: event.resolver,\n requirementId: event.requirementId,\n reason: event.reason,\n dropped: event.dropped,\n });\n } else {\n emit({\n kind: \"resolver.write.rejected\",\n rejection: \"rejection\",\n resolverId: event.resolver,\n requirementId: event.requirementId,\n reason: event.reason,\n fact: event.fact,\n expected: redactValue(event.fact, event.expected),\n actual: redactValue(event.fact, event.actual),\n });\n }\n break;\n case \"resolver.complete\":\n emit({\n kind: \"resolver.complete\",\n resolverId: event.resolver,\n requirementId: event.requirementId,\n duration: event.duration,\n });\n break;\n case \"resolver.error\":\n emit({\n kind: \"resolver.error\",\n resolverId: event.resolver,\n requirementId: event.requirementId,\n error: String(event.error),\n });\n break;\n case \"system.init\":\n case \"system.start\":\n case \"system.stop\":\n case \"system.destroy\":\n emit({ kind: event.type });\n break;\n default:\n // Other observation events ignored in v1 (derivation.compute,\n // requirement.created/met/canceled, effect.run/error,\n // reconcile.start/end). They're available via .observe()\n // directly if a consumer wants them.\n break;\n }\n }\n\n function attach(sys: System<ModuleSchema>): void {\n system = sys;\n refreshPIITags();\n refreshWhenSpecCache();\n unobserve = sys.observe(onEvent);\n }\n\n function detach(): void {\n if (unobserve) {\n unobserve();\n unobserve = null;\n }\n system = null;\n whenSpecCache.clear();\n piiTaggedFacts.clear();\n }\n\n const plugin: Plugin<ModuleSchema> = {\n name: \"audit-ledger\",\n onInit(sys) {\n attach(sys as System<ModuleSchema>);\n },\n onStop() {\n // Keep the sink populated so query() works after stop, but\n // drop the subscription to avoid leaks.\n if (unobserve) {\n unobserve();\n unobserve = null;\n }\n },\n onDestroy() {\n detach();\n },\n onDefinitionRegister(type, id) {\n if (type === \"constraint\") refreshWhenSpecCache();\n if (type === \"constraint\" || type === \"resolver\" || type === \"effect\") {\n // Re-pull PII tags too — a dynamically-registered fact (rare)\n // could have brought new meta.\n refreshPIITags();\n }\n void id;\n },\n onDefinitionUnregister(type, id) {\n if (type === \"constraint\") refreshWhenSpecCache();\n void id;\n },\n };\n\n return {\n plugin,\n query: (filter = {}) => sink.query(filter),\n recent: (n) => sink.recent(n),\n forFact: (path, opts2) => sink.forFact(path, opts2),\n forConstraint: (id, opts2) => sink.forConstraint(id, opts2),\n toJSON: () => sink.toJSON(),\n verify(opts?: { strong?: boolean }): VerifyResult | Promise<VerifyResult> {\n const { entries } = sink.toJSON();\n if (entries.length === 0) {\n return opts?.strong\n ? Promise.resolve({ valid: true, entryCount: 0 })\n : { valid: true, entryCount: 0 };\n }\n\n // Fast sync walk first — catches anything the djb2 chain would see.\n let prevHash: string | null = null;\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n if (entry.prevHash !== prevHash) {\n return {\n valid: false,\n brokenAt: i,\n expectedHash: prevHash ?? \"<genesis>\",\n actualHash: entry.prevHash ?? \"<genesis>\",\n entry,\n };\n }\n prevHash = syncHash(entry);\n }\n\n if (!opts?.strong) {\n return { valid: true, entryCount: entries.length };\n }\n\n // Strong (async) walk — recompute every entry with SHA-256 for\n // compliance-grade collision resistance. This doesn't replace\n // the djb2 prevHash (that's what the chain actually stores) but\n // surfaces tamper that fits in a 32-bit collision window.\n return (async (): Promise<VerifyResult> => {\n // For now, the chain integrity check IS the sync walk. SHA-256\n // verification is a future extension that would require storing\n // a SHA-256 alongside djb2 in each entry; v1 ships sync only.\n return { valid: true, entryCount: entries.length };\n })();\n },\n clear() {\n sink.clear();\n seq = 0;\n lastHashCache = null;\n },\n destroy() {\n detach();\n sink.destroy();\n },\n };\n}\n"]}
|