@voltro/plugin-audit 0.85.0 → 0.86.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/CHANGELOG.md +666 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/chain.js +205 -0
- package/dist/index.d.ts +74 -13
- package/dist/index.js +299 -404
- package/dist/invocationIdentity.js +11 -0
- package/dist/invocationQuery.js +14 -0
- package/dist/redactionShape.js +22 -0
- package/dist/store.js +183 -0
- package/dist/tableName.js +4 -0
- package/dist/transactionalCapture.js +101 -0
- package/package.json +4 -4
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Third-Party Notices — @voltro/plugin-audit
|
|
2
2
|
|
|
3
|
-
This package
|
|
3
|
+
This package uses the open-source software listed below. Each is the
|
|
4
4
|
property of its respective copyright holders and is used under the terms of
|
|
5
5
|
its license. This file is provided for attribution; it grants no rights in
|
|
6
6
|
@voltro/plugin-audit itself, which is proprietary (see LICENSE).
|
|
7
7
|
|
|
8
|
-
Generated from the resolved runtime dependency closure (
|
|
8
|
+
Generated from the resolved runtime dependency closure (4 packages).
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -37,34 +37,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
37
37
|
SOFTWARE.
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
## layerr@3.0.0
|
|
41
|
-
|
|
42
|
-
License: MIT
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
MIT License
|
|
46
|
-
|
|
47
|
-
Copyright (c) 2020 Perry Mitchell
|
|
48
|
-
|
|
49
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
50
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
51
|
-
in the Software without restriction, including without limitation the rights
|
|
52
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
53
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
54
|
-
furnished to do so, subject to the following conditions:
|
|
55
|
-
|
|
56
|
-
The above copyright notice and this permission notice shall be included in all
|
|
57
|
-
copies or substantial portions of the Software.
|
|
58
|
-
|
|
59
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
60
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
61
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
62
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
63
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
64
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
65
|
-
SOFTWARE.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
40
|
## typeid-js@1.2.0
|
|
69
41
|
|
|
70
42
|
License: Apache-2.0
|
|
@@ -273,14 +245,14 @@ Apache License
|
|
|
273
245
|
limitations under the License.
|
|
274
246
|
```
|
|
275
247
|
|
|
276
|
-
##
|
|
248
|
+
## ulid@3.0.2
|
|
277
249
|
|
|
278
250
|
License: MIT
|
|
279
251
|
|
|
280
252
|
```
|
|
281
|
-
MIT License
|
|
253
|
+
The MIT License (MIT)
|
|
282
254
|
|
|
283
|
-
Copyright (c)
|
|
255
|
+
Copyright (c) 2017 Alizain Feerasta
|
|
284
256
|
|
|
285
257
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
286
258
|
of this software and associated documentation files (the "Software"), to deal
|
package/dist/chain.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { AUDIT_LOG_TABLE as e } from "./tableName.js";
|
|
2
|
+
import { auditInvocationIdOf as t, hasConsistentAuditInvocationId as n } from "./invocationIdentity.js";
|
|
3
|
+
import { and as r, column as i, eq as a, generateId as o, gt as s, isNotNull as c, isNull as l, lte as u, or as d } from "@voltro/database";
|
|
4
|
+
import { createHash as f, createHmac as p, randomBytes as m } from "node:crypto";
|
|
5
|
+
//#region src/chain.ts
|
|
6
|
+
var h = [
|
|
7
|
+
"tag",
|
|
8
|
+
"at",
|
|
9
|
+
"subjectId",
|
|
10
|
+
"tenantId",
|
|
11
|
+
"traceId",
|
|
12
|
+
"status",
|
|
13
|
+
"durationMs",
|
|
14
|
+
"subject",
|
|
15
|
+
"actor",
|
|
16
|
+
"scope",
|
|
17
|
+
"metadata",
|
|
18
|
+
"input",
|
|
19
|
+
"outcome",
|
|
20
|
+
"errorTag"
|
|
21
|
+
], g = [
|
|
22
|
+
...h,
|
|
23
|
+
"hashVersion",
|
|
24
|
+
"id",
|
|
25
|
+
"impersonation",
|
|
26
|
+
"scopeKey"
|
|
27
|
+
], _ = (e) => {
|
|
28
|
+
if (e == null) return "null";
|
|
29
|
+
if (e instanceof Date) return JSON.stringify(e.toISOString());
|
|
30
|
+
let t = typeof e;
|
|
31
|
+
return t === "number" || t === "boolean" || t === "string" ? JSON.stringify(e) : t === "bigint" ? JSON.stringify(String(e)) : Array.isArray(e) ? `[${e.map(_).join(",")}]` : t === "object" ? `{${Object.entries(e).filter(([, e]) => e !== void 0).sort(([e], [t]) => e < t ? -1 : +(e > t)).map(([e, t]) => `${JSON.stringify(e)}:${_(t)}`).join(",")}}` : JSON.stringify(String(e));
|
|
32
|
+
}, v = (e) => e instanceof Date ? e.toISOString() : e, y = (e, t) => _(Object.fromEntries((t === 2 ? g : h).map((t) => [t, v(e[t] ?? null)]))), b = (e) => {
|
|
33
|
+
let t = process.env.VOLTRO_AUDIT_CHAIN_SECRET;
|
|
34
|
+
return t ? p("sha256", t).update(e, "utf8").digest("hex") : f("sha256").update(e, "utf8").digest("hex");
|
|
35
|
+
}, x = (e, t) => {
|
|
36
|
+
let n = e.hashVersion ?? 1;
|
|
37
|
+
if (n !== 1 && n !== 2) throw TypeError("Unsupported audit hash version");
|
|
38
|
+
return b(JSON.stringify([
|
|
39
|
+
t.chainId,
|
|
40
|
+
t.seq,
|
|
41
|
+
t.prevHash,
|
|
42
|
+
y(e, n)
|
|
43
|
+
]));
|
|
44
|
+
}, S = (e = m(12).toString("base64url")) => {
|
|
45
|
+
let n = 0, r = null;
|
|
46
|
+
return {
|
|
47
|
+
chainId: e,
|
|
48
|
+
link: (i) => {
|
|
49
|
+
n += 1;
|
|
50
|
+
let a = {
|
|
51
|
+
...i,
|
|
52
|
+
invocationId: t(i.outcome),
|
|
53
|
+
id: i.id ?? o({
|
|
54
|
+
kind: "typeid",
|
|
55
|
+
prefix: "audit"
|
|
56
|
+
}, "_voltro_audit_log"),
|
|
57
|
+
hashVersion: 2
|
|
58
|
+
}, s = x(a, {
|
|
59
|
+
chainId: e,
|
|
60
|
+
seq: n,
|
|
61
|
+
prevHash: r
|
|
62
|
+
}), c = {
|
|
63
|
+
...a,
|
|
64
|
+
chainId: e,
|
|
65
|
+
seq: n,
|
|
66
|
+
prevHash: r,
|
|
67
|
+
hash: s
|
|
68
|
+
};
|
|
69
|
+
return r = s, c;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
async function* C(t, n) {
|
|
74
|
+
let o = n.chainId === void 0 ? void 0 : a("chainId", n.chainId), f = (t, n, r) => ({
|
|
75
|
+
table: e,
|
|
76
|
+
predicate: t,
|
|
77
|
+
order: r,
|
|
78
|
+
take: n,
|
|
79
|
+
skip: void 0,
|
|
80
|
+
projection: void 0
|
|
81
|
+
});
|
|
82
|
+
if (n.limit !== void 0) {
|
|
83
|
+
yield* await t.query(f(o, n.limit, [{
|
|
84
|
+
column: "chainId",
|
|
85
|
+
direction: "asc"
|
|
86
|
+
}, {
|
|
87
|
+
column: "seq",
|
|
88
|
+
direction: "asc"
|
|
89
|
+
}]));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let p = [...await t.query({
|
|
93
|
+
...f(o, void 0, [{
|
|
94
|
+
column: "chainId",
|
|
95
|
+
direction: "asc"
|
|
96
|
+
}]),
|
|
97
|
+
groupBy: ["chainId"],
|
|
98
|
+
aggregations: [
|
|
99
|
+
i("chainId"),
|
|
100
|
+
{
|
|
101
|
+
op: "max",
|
|
102
|
+
column: "seq",
|
|
103
|
+
alias: "lastSeq"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
op: "max",
|
|
107
|
+
column: "id",
|
|
108
|
+
alias: "lastId"
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
})].sort((e, t) => String(e.chainId) < String(t.chainId) ? -1 : +(String(e.chainId) > String(t.chainId)));
|
|
112
|
+
for (let e of p) {
|
|
113
|
+
let n = e.chainId, i = n == null ? l("chainId") : a("chainId", n), o = n == null ? [!1] : [!1, !0];
|
|
114
|
+
for (let p of o) {
|
|
115
|
+
if (p && e.lastSeq == null) continue;
|
|
116
|
+
let o = [i, u("id", e.lastId)];
|
|
117
|
+
n != null && o.push(p ? r(c("seq"), u("seq", e.lastSeq)) : l("seq"));
|
|
118
|
+
let m;
|
|
119
|
+
for (;;) {
|
|
120
|
+
let e = m === void 0 ? void 0 : p ? d(s("seq", m.seq), r(a("seq", m.seq), s("id", m.id))) : s("id", m.id), n = await t.query(f(r(...o, ...e ? [e] : []), 1e3, [...p ? [{
|
|
121
|
+
column: "seq",
|
|
122
|
+
direction: "asc"
|
|
123
|
+
}] : [], {
|
|
124
|
+
column: "id",
|
|
125
|
+
direction: "asc"
|
|
126
|
+
}]));
|
|
127
|
+
if (yield* n, n.length < 1e3) break;
|
|
128
|
+
let i = n[n.length - 1];
|
|
129
|
+
if (i.id === void 0 || i.id === m?.id) throw Error("audit verification cursor did not advance");
|
|
130
|
+
m = i;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
var w = async (e, t = {}) => {
|
|
136
|
+
let r = [], i = [], a = 0, o = 0, s = 0, c = null, l = () => {
|
|
137
|
+
c &&= (i.push({
|
|
138
|
+
chainId: c.chainId,
|
|
139
|
+
from: c.from,
|
|
140
|
+
to: c.last,
|
|
141
|
+
tip: c.tip,
|
|
142
|
+
prunedPrefix: c.from > 1
|
|
143
|
+
}), null);
|
|
144
|
+
};
|
|
145
|
+
for await (let i of C(e, t)) {
|
|
146
|
+
let e = i.chainId, t = i.hash, u = i.seq;
|
|
147
|
+
if (typeof e != "string" || typeof t != "string" || u == null) {
|
|
148
|
+
a += 1;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
let d = Number(u), f = typeof i.prevHash == "string" ? i.prevHash : null, p = typeof i.id == "string" ? i.id : void 0;
|
|
152
|
+
s += 1, !c || c.chainId !== e ? (l(), c = {
|
|
153
|
+
chainId: e,
|
|
154
|
+
from: d,
|
|
155
|
+
last: d,
|
|
156
|
+
tip: t
|
|
157
|
+
}) : (d === c.last + 1 ? f !== c.tip && r.push({
|
|
158
|
+
kind: "broken-link",
|
|
159
|
+
chainId: e,
|
|
160
|
+
seq: d,
|
|
161
|
+
...p ? { rowId: p } : {},
|
|
162
|
+
detail: `prevHash does not match the hash of seq ${c.last}`
|
|
163
|
+
}) : r.push({
|
|
164
|
+
kind: "gap",
|
|
165
|
+
chainId: e,
|
|
166
|
+
seq: c.last + 1,
|
|
167
|
+
detail: `chain jumps from seq ${c.last} to ${d} — ${d - c.last - 1} row(s) missing`
|
|
168
|
+
}), c.last = d, c.tip = t);
|
|
169
|
+
let m = i.hashVersion ?? 1;
|
|
170
|
+
m !== 1 && m !== 2 ? r.push({
|
|
171
|
+
kind: "tampered",
|
|
172
|
+
chainId: e,
|
|
173
|
+
seq: d,
|
|
174
|
+
...p ? { rowId: p } : {},
|
|
175
|
+
detail: "unsupported audit hash version; the row cannot be verified"
|
|
176
|
+
}) : (m === 1 && (o += 1), n(i) ? x(i, {
|
|
177
|
+
chainId: e,
|
|
178
|
+
seq: d,
|
|
179
|
+
prevHash: f
|
|
180
|
+
}) !== t && r.push({
|
|
181
|
+
kind: "tampered",
|
|
182
|
+
chainId: e,
|
|
183
|
+
seq: d,
|
|
184
|
+
...p ? { rowId: p } : {},
|
|
185
|
+
detail: "the row content does not hash to its stored hash — a column was altered"
|
|
186
|
+
}) : r.push({
|
|
187
|
+
kind: "tampered",
|
|
188
|
+
chainId: e,
|
|
189
|
+
seq: d,
|
|
190
|
+
...p ? { rowId: p } : {},
|
|
191
|
+
detail: "the invocation lookup does not match its protected outcome"
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
return l(), {
|
|
195
|
+
ok: r.length === 0,
|
|
196
|
+
rowsChecked: s,
|
|
197
|
+
unchainedRows: a,
|
|
198
|
+
legacyHashRows: o,
|
|
199
|
+
chains: i,
|
|
200
|
+
issues: r,
|
|
201
|
+
keyed: !!process.env.VOLTRO_AUDIT_CHAIN_SECRET
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
//#endregion
|
|
205
|
+
export { x as auditRowHash, S as makeAuditChain, w as verifyAuditChain };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { AuditActor } from '@voltro/database';
|
|
|
2
2
|
import { ColumnDefinition } from '@voltro/database';
|
|
3
3
|
import { Effect } from 'effect';
|
|
4
4
|
import { MixinDefinition } from '@voltro/database';
|
|
5
|
+
import { Query } from '@voltro/database';
|
|
6
|
+
import { Row } from '@voltro/database';
|
|
7
|
+
import { SchemaTable } from '@voltro/database';
|
|
5
8
|
import { Subject } from '@voltro/protocol';
|
|
6
9
|
import { TableIndex } from '@voltro/database';
|
|
7
|
-
import { TableLike } from '@voltro/database';
|
|
8
10
|
import { VoltroPlugin } from '@voltro/protocol';
|
|
9
11
|
|
|
10
12
|
export declare const audit: () => MixinDefinition<{
|
|
@@ -72,6 +74,8 @@ export declare type AuditChainIssueKind = 'tampered' | 'broken-link' | 'gap';
|
|
|
72
74
|
|
|
73
75
|
/** The columns the chain adds to `_voltro_audit_log`. */
|
|
74
76
|
export declare interface AuditChainLink {
|
|
77
|
+
/** NULL/1 is historical coverage; new writers use format 2. */
|
|
78
|
+
readonly hashVersion?: number | null;
|
|
75
79
|
/** Which writer's chain this row belongs to. Null for an unchained row. */
|
|
76
80
|
readonly chainId: string;
|
|
77
81
|
/** 1-based position within `chainId`. */
|
|
@@ -102,6 +106,9 @@ export declare interface AuditChainVerification {
|
|
|
102
106
|
* Not an `issue` (a pre-chain deployment is not tampering) but never
|
|
103
107
|
* silently dropped either. */
|
|
104
108
|
readonly unchainedRows: number;
|
|
109
|
+
/** Rows using the historical digest, which did not cover impersonation or
|
|
110
|
+
* the derived scope key. Optional for compatibility with older result DTOs. */
|
|
111
|
+
readonly legacyHashRows?: number;
|
|
105
112
|
readonly chains: ReadonlyArray<AuditChainSummary>;
|
|
106
113
|
readonly issues: ReadonlyArray<AuditChainIssue>;
|
|
107
114
|
/** Whether the digest was keyed. An `ok: true` from an UNKEYED run is a
|
|
@@ -125,7 +132,7 @@ export declare interface AuditEvent {
|
|
|
125
132
|
* (anonymous, system). Absent is honest; a fabricated placeholder would be
|
|
126
133
|
* the thing this field exists to prevent.
|
|
127
134
|
*/
|
|
128
|
-
readonly actor?: AuditActor | undefined;
|
|
135
|
+
readonly actor?: AuditActor | null | undefined;
|
|
129
136
|
/** The app's own scoping dimension — opaque, stored verbatim, filterable.
|
|
130
137
|
* `.with(tenant())` is one level too coarse for a per-team trail. */
|
|
131
138
|
readonly scope?: unknown;
|
|
@@ -158,22 +165,38 @@ export declare interface AuditEvent {
|
|
|
158
165
|
readonly kind: 'ok';
|
|
159
166
|
readonly value: unknown;
|
|
160
167
|
readonly durationMs: number;
|
|
168
|
+
readonly invocationId?: string;
|
|
161
169
|
} | {
|
|
162
170
|
readonly kind: 'error';
|
|
163
171
|
readonly error: unknown;
|
|
164
172
|
readonly durationMs: number;
|
|
173
|
+
readonly invocationId?: string;
|
|
174
|
+
} | {
|
|
175
|
+
readonly kind: 'unknown';
|
|
176
|
+
readonly invocationId: string;
|
|
177
|
+
readonly durationMs: number;
|
|
165
178
|
};
|
|
166
179
|
}
|
|
167
180
|
|
|
168
181
|
/** Map an {@link AuditEvent} to an audit-log row. */
|
|
169
182
|
export declare const auditEventToRow: (event: AuditEvent) => Record<string, unknown>;
|
|
170
183
|
|
|
184
|
+
/** Stable display identity when an unknown admission acquires a terminal row.
|
|
185
|
+
* A pagination cursor still uses the selected raw row's actual ordering key. */
|
|
186
|
+
export declare const auditInvocationIdentity: (row: Readonly<Record<string, unknown>>) => string;
|
|
187
|
+
|
|
188
|
+
/** One logical invocation per result, before filtering/order/pagination. Raw
|
|
189
|
+
* admissions remain immutable. Qualify caller predicates/order with audit. */
|
|
190
|
+
export declare const auditInvocationQuery: () => Query<Row, string, {
|
|
191
|
+
completed: Row;
|
|
192
|
+
}>;
|
|
193
|
+
|
|
171
194
|
/**
|
|
172
195
|
* The slice of a built `Table` this package exposes — annotated explicitly so
|
|
173
196
|
* @voltro/database's private column-builder class doesn't leak across the
|
|
174
197
|
* package boundary (TS4094). Only name + columns + indexes are needed.
|
|
175
198
|
*/
|
|
176
|
-
export declare interface AuditLogTable extends
|
|
199
|
+
export declare interface AuditLogTable extends SchemaTable {
|
|
177
200
|
readonly fields: Record<string, ColumnDefinition<unknown>>;
|
|
178
201
|
readonly appliedIndexes: ReadonlyArray<TableIndex>;
|
|
179
202
|
}
|
|
@@ -200,6 +223,32 @@ export declare const auditLogTables: ReadonlyArray<AuditLogTable>;
|
|
|
200
223
|
export declare const auditPlugin: (options?: AuditPluginOptions) => VoltroPlugin;
|
|
201
224
|
|
|
202
225
|
export declare interface AuditPluginOptions {
|
|
226
|
+
/** Successful registered mutations may capture a required redacted intent on
|
|
227
|
+
* the current transaction outbox. Requires the literal datastore sink and the
|
|
228
|
+
* supported Runtime/CLI hook. Default best-effort recording is unchanged.
|
|
229
|
+
* Required scope/metadata/redaction/encoding failures roll back the mutation.
|
|
230
|
+
* Callbacks must remain free of external side effects: replay may repeat them.
|
|
231
|
+
* Refusals, actions, workflows and HTTP remain best effort in this mode. */
|
|
232
|
+
readonly mutationCapture?: 'best-effort' | 'transactional-outbox';
|
|
233
|
+
/** Registered mutation/action/workflow failures enqueue a redacted outcome
|
|
234
|
+
* after the business transaction has rolled back. Requires datastore sink
|
|
235
|
+
* and the current host's root outcome capability. Capture failures preserve
|
|
236
|
+
* the original operation error and report only bounded diagnostics. Query,
|
|
237
|
+
* HTTP and successful non-mutation capture are unchanged. */
|
|
238
|
+
readonly failureCapture?: 'best-effort' | 'outbox';
|
|
239
|
+
/** Successful registered actions and workflow-start calls enqueue a frozen
|
|
240
|
+
* redacted outcome through the host's root outbox. Requires datastore sink.
|
|
241
|
+
* This is durable delivery after completion, not a transaction around external
|
|
242
|
+
* side effects: admission failures preserve the successful business result and
|
|
243
|
+
* emit a bounded diagnostic. Queries, HTTP and mutation capture are unchanged. */
|
|
244
|
+
readonly successCapture?: 'best-effort' | 'outbox';
|
|
245
|
+
/** Required admission for selected action/workflow-start calls. Persist an
|
|
246
|
+
* immutable privacy-safe unknown outcome before executing their effect, then
|
|
247
|
+
* correlate the terminal outcome by invocationId. Requires datastore sink,
|
|
248
|
+
* success/failure outbox capture and record: all. Admission failure prevents
|
|
249
|
+
* execution; interruption leaves unknown evidence without claiming rollback.
|
|
250
|
+
* Mutations, queries and HTTP are unchanged. */
|
|
251
|
+
readonly invocationCapture?: 'best-effort' | 'required';
|
|
203
252
|
/**
|
|
204
253
|
* Namespace for this plugin's inspect surface + any rpc tags it contributes. Default `audit`.
|
|
205
254
|
*
|
|
@@ -306,7 +355,7 @@ export declare interface AuditPluginOptions {
|
|
|
306
355
|
* between an indexed read path and stuffing `teamId` into `metadata` and
|
|
307
356
|
* scanning — a column documented as a free-form note is not a read path.
|
|
308
357
|
*
|
|
309
|
-
*
|
|
358
|
+
* In default best-effort mode, throwing never fails the mutation: a scope that cannot
|
|
310
359
|
* be derived is null, the same answer as not configuring one.
|
|
311
360
|
*/
|
|
312
361
|
readonly resolveScope?: (ctx: {
|
|
@@ -323,10 +372,10 @@ export declare interface AuditPluginOptions {
|
|
|
323
372
|
*
|
|
324
373
|
* Resolved AFTER the call, so it sees the result (`outcome.kind === 'ok'`
|
|
325
374
|
* with `value`, or `'error'` with the error) — the note for a failed removal
|
|
326
|
-
* is a different note. May be async.
|
|
327
|
-
* `undefined` records no note
|
|
328
|
-
*
|
|
329
|
-
*
|
|
375
|
+
* is a different note. May be async. In default best-effort mode, throwing, rejecting, or returning
|
|
376
|
+
* `undefined` records no note. Resolution is awaited on the interceptor's
|
|
377
|
+
* recording path, but failure does not replace the call's original outcome.
|
|
378
|
+
* Transactional capture requires successful callback resolution.
|
|
330
379
|
*
|
|
331
380
|
* ```ts
|
|
332
381
|
* auditPlugin({
|
|
@@ -470,6 +519,13 @@ export declare interface AuditPluginOptions {
|
|
|
470
519
|
* "who really did this" is not app-controlled metadata, it is the event.
|
|
471
520
|
*/
|
|
472
521
|
readonly redactSubject?: 'metadata' | 'metadata-shape' | 'none' | ((subject: Subject) => unknown);
|
|
522
|
+
/** Project resolved actor displayName/email before capture or sink delivery.
|
|
523
|
+
* The framework retains id/type even if JavaScript returns extra fields.
|
|
524
|
+
* Return both labels as strings or null. The synchronous callback must be
|
|
525
|
+
* pure: required capture failure aborts its transaction; refusal admission
|
|
526
|
+
* keeps the original error. Decisions are frozen before enqueue, not rerun
|
|
527
|
+
* during delivery. Absent preserves the existing policy; no actor stays null. */
|
|
528
|
+
readonly redactActor?: (actor: Readonly<AuditActor>) => Pick<AuditActor, 'displayName' | 'email'>;
|
|
473
529
|
/**
|
|
474
530
|
* What happens to `AuditEvent.outcome`'s payload before it is handed to the
|
|
475
531
|
* sink.
|
|
@@ -555,12 +611,17 @@ export declare type AuditSink = (event: AuditEvent) => void | Promise<void> | Ef
|
|
|
555
611
|
export declare const clearAuditBuffer: () => void;
|
|
556
612
|
|
|
557
613
|
/**
|
|
558
|
-
* A DataStore-backed
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
614
|
+
* A DataStore-backed sink. Optional receiptId identifies one logical append
|
|
615
|
+
* across delivery retries and sink/process reconstruction. Stable receipts
|
|
616
|
+
* require a query-capable store and are refused if their content differs.
|
|
617
|
+
*
|
|
618
|
+
* A per-writer semaphore serializes append attempts. Read-capable stores derive
|
|
619
|
+
* the next link from persisted state, so rejected inserts and lost write
|
|
620
|
+
* acknowledgements cannot advance or fork the writer's chain. Insert-only
|
|
621
|
+
* substitutes retain the old surface, with in-memory state advanced on success.
|
|
622
|
+
* This is append recovery, not durable event capture before business commit.
|
|
562
623
|
*/
|
|
563
|
-
export declare const dataStoreAuditSink: (store: AuditDataStore) => (event: AuditEvent) => Promise<void>;
|
|
624
|
+
export declare const dataStoreAuditSink: (store: AuditDataStore) => (event: AuditEvent, receiptId?: string) => Promise<void>;
|
|
564
625
|
|
|
565
626
|
/** Mint a fresh chain. `chainId` is 96 bits of CSPRNG — collisions across
|
|
566
627
|
* replicas would merge two chains into one apparent fork. */
|