@voltro/plugin-audit 0.84.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 +1215 -0
- package/THIRD-PARTY-NOTICES.md +5 -89
- package/dist/chain.js +205 -0
- package/dist/index.d.ts +75 -14
- package/dist/index.js +301 -375
- 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 +5 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/invocationIdentity.ts
|
|
2
|
+
var e = (e) => {
|
|
3
|
+
if (typeof e != "object" || !e || Array.isArray(e)) return null;
|
|
4
|
+
let t = e.invocationId;
|
|
5
|
+
return typeof t == "string" && t.trim().length > 0 ? t : null;
|
|
6
|
+
}, t = (t) => {
|
|
7
|
+
let n = t.outcome;
|
|
8
|
+
return typeof n == "object" && n && !Array.isArray(n) && n.invocationId !== void 0 && e(n) === null ? !1 : (t.invocationId ?? null) === e(n);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { e as auditInvocationIdOf, t as hasConsistentAuditInvocationId };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { hasConsistentAuditInvocationId as e } from "./invocationIdentity.js";
|
|
2
|
+
import { auditLogTable as t } from "./store.js";
|
|
3
|
+
import { and as n, columnRef as r, eq as i, isNull as a, neq as o, or as s, queryFor as c } from "@voltro/database";
|
|
4
|
+
//#region src/invocationQuery.ts
|
|
5
|
+
var l = (e, t) => s(i(e, r(t)), n(a(e), a(t))), u = () => c(t).as("audit").leftJoin(t.tableName, "completed", n(i("completed.invocationId", r("audit.id")), s(i("completed.status", "ok"), i("completed.status", "error")), i("completed.tag", r("audit.tag")), i("completed.traceId", r("audit.traceId")), i("completed.at", r("audit.at")), l("completed.subjectId", "audit.subjectId"), l("completed.tenantId", "audit.tenantId"), l("completed.scopeKey", "audit.scopeKey"))).where(s(o("audit.status", "unknown"), a("completed.id"))), d = (t) => {
|
|
6
|
+
if (!e(t)) throw TypeError("Audit invocation lookup does not match its protected outcome");
|
|
7
|
+
let n = t.id, r = t.invocationId;
|
|
8
|
+
if (typeof n != "string") throw TypeError("Audit row identity is unavailable");
|
|
9
|
+
if (typeof r != "string") return n;
|
|
10
|
+
if (t.status === "unknown" ? n !== r : n !== r + ":outcome") throw TypeError("Audit invocation receipt identity is inconsistent");
|
|
11
|
+
return r;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { d as auditInvocationIdentity, u as auditInvocationQuery };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/redactionShape.ts
|
|
2
|
+
var e = /^[A-Za-z_$][A-Za-z0-9_$]*$/, t = (e, t) => e === 0 || t <= 0 || e >= t ? `string(${e})` : `string(<${t})`, n = (n, r) => n.length <= 64 && e.test(n) ? n : `<key:${t(n.length, r)}>`, r = (e, i = 0, a = 16) => {
|
|
3
|
+
if (e === null) return "null";
|
|
4
|
+
if (e === void 0) return "undefined";
|
|
5
|
+
let o = typeof e;
|
|
6
|
+
if (o === "string") return t(e.length, a);
|
|
7
|
+
if (o === "number" || o === "boolean" || o === "bigint" || o === "symbol" || o === "function") return o;
|
|
8
|
+
if (e instanceof Date) return "date";
|
|
9
|
+
if (i >= 4) return Array.isArray(e) ? "array(…)" : "object(…)";
|
|
10
|
+
if (Array.isArray(e)) {
|
|
11
|
+
let t = e.slice(0, 32).map((e) => r(e, i + 1, a));
|
|
12
|
+
return e.length > 32 ? [...t, `…${e.length - 32} more`] : t;
|
|
13
|
+
}
|
|
14
|
+
if (o === "object") {
|
|
15
|
+
let t = Object.entries(e), o = {};
|
|
16
|
+
for (let [e, s] of t.slice(0, 32)) o[n(e, a)] = r(s, i + 1, a);
|
|
17
|
+
return t.length > 32 && (o["…"] = `${t.length - 32} more key(s)`), o;
|
|
18
|
+
}
|
|
19
|
+
return o;
|
|
20
|
+
}, i = (e, t = 16) => ({ __redacted: r(e, 0, t) });
|
|
21
|
+
//#endregion
|
|
22
|
+
export { r as redactionShape, i as shapeRedaction };
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { AUDIT_LOG_TABLE as e } from "./tableName.js";
|
|
2
|
+
import { auditInvocationIdOf as t, hasConsistentAuditInvocationId as n } from "./invocationIdentity.js";
|
|
3
|
+
import { auditRowHash as r, makeAuditChain as i } from "./chain.js";
|
|
4
|
+
import { Effect as a, Result as o, Semaphore as s } from "effect";
|
|
5
|
+
import { and as c, eq as l, generateId as u, id as d, integer as f, json as p, resolveActorSnapshot as m, scopeKeyOf as h, table as g, text as _, timestamp as v } from "@voltro/database";
|
|
6
|
+
import { isDeepStrictEqual as y } from "node:util";
|
|
7
|
+
//#region src/store.ts
|
|
8
|
+
var b = g(e, {
|
|
9
|
+
id: d({ prefix: "audit" }),
|
|
10
|
+
tag: _(),
|
|
11
|
+
at: v(),
|
|
12
|
+
subjectId: _().nullable(),
|
|
13
|
+
tenantId: _().nullable(),
|
|
14
|
+
traceId: _(),
|
|
15
|
+
invocationId: _().nullable(),
|
|
16
|
+
status: _(),
|
|
17
|
+
durationMs: _(),
|
|
18
|
+
subject: p(),
|
|
19
|
+
actor: p().nullable(),
|
|
20
|
+
scope: p().nullable(),
|
|
21
|
+
scopeKey: _().nullable(),
|
|
22
|
+
metadata: p().nullable(),
|
|
23
|
+
impersonation: p().nullable(),
|
|
24
|
+
input: p(),
|
|
25
|
+
outcome: p(),
|
|
26
|
+
errorTag: _().nullable(),
|
|
27
|
+
hashVersion: f().nullable(),
|
|
28
|
+
chainId: _().nullable(),
|
|
29
|
+
seq: f().nullable(),
|
|
30
|
+
prevHash: _().nullable(),
|
|
31
|
+
hash: _().nullable()
|
|
32
|
+
}).index("byAuditTag", ["tag"]).index("byAuditTrace", ["traceId"]).index("byAuditInvocation", ["invocationId", "status"]).index("byAuditSubjectStatus", [
|
|
33
|
+
"subjectId",
|
|
34
|
+
"status",
|
|
35
|
+
"at"
|
|
36
|
+
]).index("byAuditTenantStatus", [
|
|
37
|
+
"tenantId",
|
|
38
|
+
"status",
|
|
39
|
+
"at"
|
|
40
|
+
]).index("byAuditScope", ["scopeKey", "at"]).index("byAuditChain", ["chainId", "seq"]), x = [b], S = (e) => ({
|
|
41
|
+
tag: e.tag,
|
|
42
|
+
at: new Date(e.ts),
|
|
43
|
+
subjectId: e.subject.id ?? null,
|
|
44
|
+
tenantId: e.subject.tenantId ?? null,
|
|
45
|
+
traceId: e.traceId,
|
|
46
|
+
invocationId: t(e.outcome),
|
|
47
|
+
status: e.outcome.kind,
|
|
48
|
+
durationMs: String(e.outcome.durationMs),
|
|
49
|
+
subject: e.subject,
|
|
50
|
+
actor: e.actor ?? null,
|
|
51
|
+
scope: e.scope ?? null,
|
|
52
|
+
scopeKey: h(e.scope),
|
|
53
|
+
metadata: e.metadata ?? null,
|
|
54
|
+
impersonation: e.impersonation ?? null,
|
|
55
|
+
input: e.input,
|
|
56
|
+
outcome: e.outcome,
|
|
57
|
+
errorTag: e.outcome.kind === "error" ? C(e.outcome.error) : null
|
|
58
|
+
}), C = (e) => {
|
|
59
|
+
if (typeof e != "object" || !e) return null;
|
|
60
|
+
let t = e._tag;
|
|
61
|
+
return typeof t == "string" && t !== "" ? t : null;
|
|
62
|
+
}, w = async (t, n) => t.query({
|
|
63
|
+
table: e,
|
|
64
|
+
predicate: l("traceId", n),
|
|
65
|
+
order: [{
|
|
66
|
+
column: "at",
|
|
67
|
+
direction: "asc"
|
|
68
|
+
}]
|
|
69
|
+
}), T = async (t, n, r = {}) => {
|
|
70
|
+
let i = h(n);
|
|
71
|
+
if (i === null) return [];
|
|
72
|
+
let a = l("scopeKey", i);
|
|
73
|
+
return t.query({
|
|
74
|
+
table: e,
|
|
75
|
+
predicate: r.status === void 0 ? a : c(a, l("status", r.status)),
|
|
76
|
+
order: [{
|
|
77
|
+
column: "at",
|
|
78
|
+
direction: "desc"
|
|
79
|
+
}],
|
|
80
|
+
take: r.limit ?? 100
|
|
81
|
+
});
|
|
82
|
+
}, E = async (t, n, r = {}) => {
|
|
83
|
+
let i = l("subjectId", n);
|
|
84
|
+
return t.query({
|
|
85
|
+
table: e,
|
|
86
|
+
predicate: r.status === void 0 ? i : c(i, l("status", r.status)),
|
|
87
|
+
order: [{
|
|
88
|
+
column: "at",
|
|
89
|
+
direction: "desc"
|
|
90
|
+
}],
|
|
91
|
+
take: r.limit ?? 100
|
|
92
|
+
});
|
|
93
|
+
}, D = (e, t) => {
|
|
94
|
+
let i = {
|
|
95
|
+
chainId: t.chainId,
|
|
96
|
+
seq: t.seq,
|
|
97
|
+
prevHash: t.prevHash
|
|
98
|
+
}, a = {
|
|
99
|
+
...S(e),
|
|
100
|
+
actor: e.actor === void 0 ? t.actor ?? null : e.actor,
|
|
101
|
+
hashVersion: t.hashVersion ?? null,
|
|
102
|
+
id: t.id
|
|
103
|
+
};
|
|
104
|
+
if (!n(t) || typeof i.chainId != "string" || !Number.isSafeInteger(i.seq) || i.seq < 1 || typeof t.hash != "string" || r(t, i) !== t.hash || r(a, i) !== t.hash || !y(t.impersonation ?? null, a.impersonation ?? null)) throw Error("Audit receipt identity already belongs to different or tampered content");
|
|
105
|
+
}, O = (t) => {
|
|
106
|
+
let c = i().chainId, d = s.makeUnsafe(1), f = {
|
|
107
|
+
seq: 0,
|
|
108
|
+
hash: null
|
|
109
|
+
}, p = typeof t.query == "function" ? t : void 0;
|
|
110
|
+
return (i, s) => a.runPromise(d.withPermits(1)(a.result(a.tryPromise({
|
|
111
|
+
try: async () => {
|
|
112
|
+
if (s !== void 0) {
|
|
113
|
+
if (typeof s != "string" || s.trim().length === 0) throw TypeError("Audit receipt identity must be a non-empty string");
|
|
114
|
+
if (p === void 0) throw TypeError("Audit receipt replay requires readable persistence");
|
|
115
|
+
let t = (await p.query({
|
|
116
|
+
table: e,
|
|
117
|
+
predicate: l("id", s),
|
|
118
|
+
order: [],
|
|
119
|
+
take: 1
|
|
120
|
+
})).find((e) => e.id === s);
|
|
121
|
+
if (t !== void 0) {
|
|
122
|
+
D(i, t);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
let a = i.actor !== void 0 || p === void 0 ? i : {
|
|
127
|
+
...i,
|
|
128
|
+
actor: await m(p, i.subject.id ?? null, i.subject.type)
|
|
129
|
+
};
|
|
130
|
+
if (p !== void 0) {
|
|
131
|
+
let t = (await p.query({
|
|
132
|
+
table: e,
|
|
133
|
+
predicate: l("chainId", c),
|
|
134
|
+
order: [{
|
|
135
|
+
column: "seq",
|
|
136
|
+
direction: "desc"
|
|
137
|
+
}],
|
|
138
|
+
take: 1
|
|
139
|
+
})).filter((e) => e.chainId === c).sort((e, t) => Number(t.seq) - Number(e.seq))[0];
|
|
140
|
+
if (t === void 0) f = {
|
|
141
|
+
seq: 0,
|
|
142
|
+
hash: null
|
|
143
|
+
};
|
|
144
|
+
else {
|
|
145
|
+
let e = Number(t.seq);
|
|
146
|
+
if (!Number.isSafeInteger(e) || e < 1 || typeof t.hash != "string") throw Error("Audit writer persisted chain tip is invalid");
|
|
147
|
+
f = {
|
|
148
|
+
seq: e,
|
|
149
|
+
hash: t.hash
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let o = {
|
|
154
|
+
...S(a),
|
|
155
|
+
id: s ?? u({
|
|
156
|
+
kind: "typeid",
|
|
157
|
+
prefix: "audit"
|
|
158
|
+
}, "_voltro_audit_log"),
|
|
159
|
+
hashVersion: 2
|
|
160
|
+
};
|
|
161
|
+
if (!n(o)) throw TypeError("Audit invocation identity is invalid");
|
|
162
|
+
let d = {
|
|
163
|
+
chainId: c,
|
|
164
|
+
seq: f.seq + 1,
|
|
165
|
+
prevHash: f.hash
|
|
166
|
+
}, h = r(o, d);
|
|
167
|
+
await t.insert(e, {
|
|
168
|
+
...o,
|
|
169
|
+
...s === void 0 ? {} : { id: s },
|
|
170
|
+
...d,
|
|
171
|
+
hash: h
|
|
172
|
+
}), f = {
|
|
173
|
+
seq: d.seq,
|
|
174
|
+
hash: h
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
catch: (e) => e
|
|
178
|
+
})))).then((e) => {
|
|
179
|
+
if (o.isFailure(e)) throw e.failure;
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
//#endregion
|
|
183
|
+
export { e as AUDIT_LOG_TABLE, D as assertAuditReceipt, T as auditByScope, E as auditBySubject, w as auditByTrace, S as auditEventToRow, b as auditLogTable, x as auditLogTables, O as dataStoreAuditSink };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
import { Subject as t } from "@voltro/protocol";
|
|
3
|
+
//#region src/transactionalCapture.ts
|
|
4
|
+
var n = e.Struct({
|
|
5
|
+
id: e.String,
|
|
6
|
+
type: e.String,
|
|
7
|
+
displayName: e.NullOr(e.String),
|
|
8
|
+
email: e.NullOr(e.String)
|
|
9
|
+
}), r = {
|
|
10
|
+
ts: e.Number,
|
|
11
|
+
tag: e.String,
|
|
12
|
+
subject: t,
|
|
13
|
+
actor: e.NullOr(n),
|
|
14
|
+
traceId: e.String,
|
|
15
|
+
input: e.Unknown,
|
|
16
|
+
scope: e.optional(e.Unknown),
|
|
17
|
+
metadata: e.optional(e.Unknown),
|
|
18
|
+
impersonation: e.optional(e.Unknown)
|
|
19
|
+
}, i = e.Struct({
|
|
20
|
+
version: e.Literal(1),
|
|
21
|
+
event: e.Struct({
|
|
22
|
+
...r,
|
|
23
|
+
outcome: e.Struct({
|
|
24
|
+
kind: e.Literal("ok"),
|
|
25
|
+
value: e.Unknown,
|
|
26
|
+
durationMs: e.Number
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
}), a = e.Struct({
|
|
30
|
+
version: e.Literal(1),
|
|
31
|
+
event: e.Struct({
|
|
32
|
+
...r,
|
|
33
|
+
outcome: e.Struct({
|
|
34
|
+
kind: e.Literal("error"),
|
|
35
|
+
error: e.Unknown,
|
|
36
|
+
durationMs: e.Number
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
}), o = e.Struct({
|
|
40
|
+
version: e.Literal(1),
|
|
41
|
+
event: e.Struct({
|
|
42
|
+
...r,
|
|
43
|
+
outcome: e.Struct({
|
|
44
|
+
kind: e.Literal("unknown"),
|
|
45
|
+
invocationId: e.String,
|
|
46
|
+
durationMs: e.Number
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
}), s = e.Struct({
|
|
50
|
+
version: e.Literal(1),
|
|
51
|
+
event: e.Struct({
|
|
52
|
+
...r,
|
|
53
|
+
outcome: e.Union([e.Struct({
|
|
54
|
+
kind: e.Literal("ok"),
|
|
55
|
+
invocationId: e.String,
|
|
56
|
+
value: e.Unknown,
|
|
57
|
+
durationMs: e.Number
|
|
58
|
+
}), e.Struct({
|
|
59
|
+
kind: e.Literal("error"),
|
|
60
|
+
invocationId: e.String,
|
|
61
|
+
error: e.Unknown,
|
|
62
|
+
durationMs: e.Number
|
|
63
|
+
})])
|
|
64
|
+
})
|
|
65
|
+
}), c = (e) => {
|
|
66
|
+
if (!Number.isSafeInteger(e.ts) || e.ts < 0 || !Number.isFinite(e.outcome.durationMs) || e.outcome.durationMs < 0 || e.tag.trim().length === 0) throw TypeError("Audit intent has invalid timing or procedure identity");
|
|
67
|
+
if (e.outcome.invocationId !== void 0 && e.outcome.invocationId.trim().length === 0) throw TypeError("Audit invocation identity must be non-empty");
|
|
68
|
+
if (e.outcome.kind === "unknown" && e.outcome.durationMs !== 0) throw TypeError("Unknown audit outcome has no completed duration");
|
|
69
|
+
return e;
|
|
70
|
+
}, l = (t) => c(e.decodeUnknownSync(i, { onExcessProperty: "error" })(t).event), u = (t) => c(e.decodeUnknownSync(a, { onExcessProperty: "error" })(t).event), d = (t) => c(e.decodeUnknownSync(o, { onExcessProperty: "error" })(t).event), f = (t) => c(e.decodeUnknownSync(s, { onExcessProperty: "error" })(t).event), p = (e, t) => {
|
|
71
|
+
if (e == null) return null;
|
|
72
|
+
if (typeof e == "string" || typeof e == "boolean" || typeof e == "number" && Number.isFinite(e)) return e;
|
|
73
|
+
if (typeof e != "object") throw TypeError("Audit intent requires portable JSON values");
|
|
74
|
+
if (e instanceof Date) {
|
|
75
|
+
if (!Number.isFinite(e.getTime())) throw TypeError("Audit intent requires valid dates");
|
|
76
|
+
return e.toISOString();
|
|
77
|
+
}
|
|
78
|
+
if (t.has(e)) throw TypeError("Audit intent cannot contain cyclic values");
|
|
79
|
+
if (!Array.isArray(e) && Object.getPrototypeOf(e) !== Object.prototype && Object.getPrototypeOf(e) !== null) throw TypeError("Audit intent requires plain JSON objects");
|
|
80
|
+
if (Object.getOwnPropertySymbols(e).length !== 0) throw TypeError("Audit intent cannot contain symbol keys");
|
|
81
|
+
t.add(e);
|
|
82
|
+
try {
|
|
83
|
+
return Array.isArray(e) ? Array.from(e, (e) => p(e, t)) : Object.fromEntries(Object.entries(e).map(([e, n]) => [e, p(n, t)]));
|
|
84
|
+
} finally {
|
|
85
|
+
t.delete(e);
|
|
86
|
+
}
|
|
87
|
+
}, m = (e, t) => {
|
|
88
|
+
let n = Object.fromEntries(Object.entries(e.subject).filter(([, e]) => e !== void 0));
|
|
89
|
+
return {
|
|
90
|
+
version: 1,
|
|
91
|
+
event: t(JSON.parse(JSON.stringify(p({
|
|
92
|
+
version: 1,
|
|
93
|
+
event: {
|
|
94
|
+
...e,
|
|
95
|
+
subject: n
|
|
96
|
+
}
|
|
97
|
+
}, /* @__PURE__ */ new Set()))))
|
|
98
|
+
};
|
|
99
|
+
}, h = (e) => m(e, l), g = (e) => m(e, u), _ = (e) => m(e, d), v = (e) => m(e, f);
|
|
100
|
+
//#endregion
|
|
101
|
+
export { d as decodeAuditAdmissionIntent, u as decodeAuditFailureIntent, l as decodeAuditIntent, f as decodeAuditInvocationOutcomeIntent, _ as freezeAuditAdmissionIntent, g as freezeAuditFailureIntent, h as freezeAuditIntent, v as freezeAuditInvocationOutcomeIntent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/plugin-audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.86.0",
|
|
4
4
|
"description": "Audit plugin — ships the `audit()` schema mixin (createdAt/updatedAt/createdBy/updatedBy → Actor) plus an optional interceptor that records every mutation and action (queries on request) to a configurable sink (console / memory / datastore / custom function).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
"node": ">=24.0.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@voltro/database": "0.
|
|
42
|
-
"@voltro/logger": "0.
|
|
43
|
-
"@voltro/protocol": "0.
|
|
41
|
+
"@voltro/database": "0.86.0",
|
|
42
|
+
"@voltro/logger": "0.86.0",
|
|
43
|
+
"@voltro/protocol": "0.86.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"effect": "
|
|
46
|
+
"effect": "4.0.1"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|