@talosprotocol/contracts 1.1.5
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/index.cjs +332 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +158 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +290 -0
- package/dist/index.js.map +1 -0
- package/package.json +74 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Base64UrlError: () => Base64UrlError,
|
|
24
|
+
assertCursorInvariant: () => assertCursorInvariant,
|
|
25
|
+
base64urlDecodeToBytes: () => base64urlDecodeToBytes,
|
|
26
|
+
base64urlDecodeToUtf8: () => base64urlDecodeToUtf8,
|
|
27
|
+
base64urlEncodeBytes: () => base64urlEncodeBytes,
|
|
28
|
+
base64urlEncodeUtf8: () => base64urlEncodeUtf8,
|
|
29
|
+
checkCursorContinuity: () => checkCursorContinuity,
|
|
30
|
+
compareCursor: () => compareCursor,
|
|
31
|
+
createEvidenceBundle: () => createEvidenceBundle,
|
|
32
|
+
decodeCursor: () => decodeCursor,
|
|
33
|
+
deriveCursor: () => deriveCursor,
|
|
34
|
+
isCanonicalLowerUuid: () => isCanonicalLowerUuid,
|
|
35
|
+
isUuidV7: () => isUuidV7,
|
|
36
|
+
orderingCompare: () => orderingCompare,
|
|
37
|
+
redactEvent: () => redactEvent,
|
|
38
|
+
redactGatewaySnapshot: () => redactGatewaySnapshot
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/infrastructure/base64url.ts
|
|
43
|
+
var Base64UrlError = class extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "Base64UrlError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
50
|
+
var RE_VALID = /^[A-Za-z0-9\-_]*$/;
|
|
51
|
+
function toUtf8Bytes(s) {
|
|
52
|
+
return new TextEncoder().encode(s);
|
|
53
|
+
}
|
|
54
|
+
function fromUtf8Bytes(b) {
|
|
55
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(b);
|
|
56
|
+
}
|
|
57
|
+
function base64urlEncodeBytes(bytes) {
|
|
58
|
+
if (bytes.length === 0) return "";
|
|
59
|
+
let out = "";
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i + 3 <= bytes.length) {
|
|
62
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
63
|
+
out += ALPHABET[n >>> 18 & 63];
|
|
64
|
+
out += ALPHABET[n >>> 12 & 63];
|
|
65
|
+
out += ALPHABET[n >>> 6 & 63];
|
|
66
|
+
out += ALPHABET[n & 63];
|
|
67
|
+
i += 3;
|
|
68
|
+
}
|
|
69
|
+
const rem = bytes.length - i;
|
|
70
|
+
if (rem === 1) {
|
|
71
|
+
const n = bytes[i];
|
|
72
|
+
out += ALPHABET[n >>> 2 & 63];
|
|
73
|
+
out += ALPHABET[n << 4 & 63];
|
|
74
|
+
} else if (rem === 2) {
|
|
75
|
+
const n = bytes[i] << 8 | bytes[i + 1];
|
|
76
|
+
out += ALPHABET[n >>> 10 & 63];
|
|
77
|
+
out += ALPHABET[n >>> 4 & 63];
|
|
78
|
+
out += ALPHABET[n << 2 & 63];
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function decodeChar(c) {
|
|
83
|
+
const idx = ALPHABET.indexOf(c);
|
|
84
|
+
if (idx === -1) throw new Base64UrlError(`Invalid base64url character: ${c}`);
|
|
85
|
+
return idx;
|
|
86
|
+
}
|
|
87
|
+
function base64urlDecodeToBytes(s) {
|
|
88
|
+
if (s.length === 0) return new Uint8Array(0);
|
|
89
|
+
if (s.includes("=")) throw new Base64UrlError("Padding is not allowed");
|
|
90
|
+
if (!RE_VALID.test(s))
|
|
91
|
+
throw new Base64UrlError("Non-base64url characters present");
|
|
92
|
+
if (s.length % 4 === 1) throw new Base64UrlError("Invalid base64url length");
|
|
93
|
+
const out = [];
|
|
94
|
+
let i = 0;
|
|
95
|
+
while (i < s.length) {
|
|
96
|
+
const remain = s.length - i;
|
|
97
|
+
if (remain >= 4) {
|
|
98
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]), c = decodeChar(s[i + 2]), d = decodeChar(s[i + 3]);
|
|
99
|
+
const n = a << 18 | b << 12 | c << 6 | d;
|
|
100
|
+
out.push(n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
101
|
+
i += 4;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (remain === 2) {
|
|
105
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]);
|
|
106
|
+
const n = a << 18 | b << 12;
|
|
107
|
+
out.push(n >>> 16 & 255);
|
|
108
|
+
i += 2;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (remain === 3) {
|
|
112
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]), c = decodeChar(s[i + 2]);
|
|
113
|
+
const n = a << 18 | b << 12 | c << 6;
|
|
114
|
+
out.push(n >>> 16 & 255, n >>> 8 & 255);
|
|
115
|
+
i += 3;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw new Base64UrlError("Invalid base64url length");
|
|
119
|
+
}
|
|
120
|
+
const decoded = new Uint8Array(out);
|
|
121
|
+
const recoded = base64urlEncodeBytes(decoded);
|
|
122
|
+
if (recoded !== s) throw new Base64UrlError("Non-canonical base64url form");
|
|
123
|
+
return decoded;
|
|
124
|
+
}
|
|
125
|
+
function base64urlEncodeUtf8(s) {
|
|
126
|
+
return base64urlEncodeBytes(toUtf8Bytes(s));
|
|
127
|
+
}
|
|
128
|
+
function base64urlDecodeToUtf8(s) {
|
|
129
|
+
return fromUtf8Bytes(base64urlDecodeToBytes(s));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/infrastructure/uuidv7.ts
|
|
133
|
+
var RE_UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
134
|
+
function isUuidV7(id) {
|
|
135
|
+
if (!RE_UUID.test(id)) return false;
|
|
136
|
+
const version = id[14];
|
|
137
|
+
if (version !== "7") return false;
|
|
138
|
+
const variant = id[19].toLowerCase();
|
|
139
|
+
return variant === "8" || variant === "9" || variant === "a" || variant === "b";
|
|
140
|
+
}
|
|
141
|
+
function isCanonicalLowerUuid(id) {
|
|
142
|
+
return id === id.toLowerCase();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/domain/logic/cursor.ts
|
|
146
|
+
function isValidUnixSecondsInt(n) {
|
|
147
|
+
return typeof n === "number" && Number.isInteger(n) && n >= 0 && Number.isSafeInteger(n);
|
|
148
|
+
}
|
|
149
|
+
function isCanonicalTimestampString(s) {
|
|
150
|
+
if (!/^\d+$/.test(s)) return false;
|
|
151
|
+
if (s.length > 1 && s.startsWith("0")) return false;
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
function deriveCursor(timestamp, eventId) {
|
|
155
|
+
if (!isValidUnixSecondsInt(timestamp)) {
|
|
156
|
+
throw new Error("timestamp must be unix seconds integer");
|
|
157
|
+
}
|
|
158
|
+
const plain = `${String(timestamp)}:${eventId}`;
|
|
159
|
+
return base64urlEncodeUtf8(plain);
|
|
160
|
+
}
|
|
161
|
+
function decodeCursor(cursor) {
|
|
162
|
+
const raw = base64urlDecodeToUtf8(cursor);
|
|
163
|
+
const colonIndex = raw.indexOf(":");
|
|
164
|
+
if (colonIndex === -1)
|
|
165
|
+
throw new Error("cursor frame must be '{timestamp}:{event_id}'");
|
|
166
|
+
const tsStr = raw.slice(0, colonIndex);
|
|
167
|
+
const eventId = raw.slice(colonIndex + 1);
|
|
168
|
+
if (!isCanonicalTimestampString(tsStr))
|
|
169
|
+
throw new Error("timestamp is not canonical base-10");
|
|
170
|
+
const tsNum = Number(tsStr);
|
|
171
|
+
if (!isValidUnixSecondsInt(tsNum))
|
|
172
|
+
throw new Error("timestamp is out of range");
|
|
173
|
+
if (!isUuidV7(eventId)) throw new Error("event_id is not uuidv7");
|
|
174
|
+
if (!isCanonicalLowerUuid(eventId))
|
|
175
|
+
throw new Error("event_id must be lowercase canonical uuid");
|
|
176
|
+
return { timestamp: tsNum, event_id: eventId };
|
|
177
|
+
}
|
|
178
|
+
function compareCursor(a, b) {
|
|
179
|
+
const da = decodeCursor(a);
|
|
180
|
+
const db = decodeCursor(b);
|
|
181
|
+
if (da.timestamp < db.timestamp) return -1;
|
|
182
|
+
if (da.timestamp > db.timestamp) return 1;
|
|
183
|
+
if (da.event_id < db.event_id) return -1;
|
|
184
|
+
if (da.event_id > db.event_id) return 1;
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
function assertCursorInvariant(event) {
|
|
188
|
+
if (!isValidUnixSecondsInt(event.timestamp) || typeof event.event_id !== "string" || typeof event.cursor !== "string") {
|
|
189
|
+
return { ok: false, derived: "", reason: "INVALID_FRAME" };
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
decodeCursor(event.cursor);
|
|
193
|
+
} catch {
|
|
194
|
+
const derived2 = deriveCursor(event.timestamp, event.event_id);
|
|
195
|
+
return { ok: false, derived: derived2, reason: "INVALID_FRAME" };
|
|
196
|
+
}
|
|
197
|
+
const derived = deriveCursor(event.timestamp, event.event_id);
|
|
198
|
+
if (event.cursor !== derived)
|
|
199
|
+
return { ok: false, derived, reason: "CURSOR_MISMATCH" };
|
|
200
|
+
if (!isUuidV7(event.event_id) || !isCanonicalLowerUuid(event.event_id)) {
|
|
201
|
+
return { ok: false, derived, reason: "INVALID_FRAME" };
|
|
202
|
+
}
|
|
203
|
+
return { ok: true, derived };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/domain/logic/continuity.ts
|
|
207
|
+
function checkCursorContinuity(events, _expectedPredecessor) {
|
|
208
|
+
if (events.length <= 1) {
|
|
209
|
+
return { status: "CONTINUOUS", gaps: [] };
|
|
210
|
+
}
|
|
211
|
+
const gaps = [];
|
|
212
|
+
for (let i = 0; i < events.length - 1; i++) {
|
|
213
|
+
const current = events[i];
|
|
214
|
+
const next = events[i + 1];
|
|
215
|
+
const cmp = compareCursor(current.cursor, next.cursor);
|
|
216
|
+
if (cmp === 1) {
|
|
217
|
+
gaps.push({
|
|
218
|
+
from_cursor: current.cursor,
|
|
219
|
+
to_cursor: next.cursor,
|
|
220
|
+
detected_at: Date.now()
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (gaps.length > 0) {
|
|
225
|
+
return { status: "GAP_DETECTED", gaps };
|
|
226
|
+
}
|
|
227
|
+
return { status: "CONTINUOUS", gaps: [] };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/domain/logic/ordering.ts
|
|
231
|
+
function orderingCompare(a, b) {
|
|
232
|
+
if (a.timestamp > b.timestamp) return -1;
|
|
233
|
+
if (a.timestamp < b.timestamp) return 1;
|
|
234
|
+
if (a.event_id > b.event_id) return -1;
|
|
235
|
+
if (a.event_id < b.event_id) return 1;
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/domain/logic/redaction.ts
|
|
240
|
+
function redactEvent(event, level) {
|
|
241
|
+
if (level === "none") return event;
|
|
242
|
+
const copy = structuredClone(event);
|
|
243
|
+
if (level === "safe_default" || level === "strict") {
|
|
244
|
+
if (copy.request && typeof copy.request === "object") {
|
|
245
|
+
const req = copy.request;
|
|
246
|
+
if (req.headers) {
|
|
247
|
+
delete req.headers["authorization"];
|
|
248
|
+
delete req.headers["cookie"];
|
|
249
|
+
delete req.headers["x-api-key"];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (level === "strict") {
|
|
254
|
+
if (copy.input) {
|
|
255
|
+
copy.input = "[REDACTED]";
|
|
256
|
+
}
|
|
257
|
+
if (copy.request && typeof copy.request === "object") {
|
|
258
|
+
const req = copy.request;
|
|
259
|
+
req.headers = void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return copy;
|
|
263
|
+
}
|
|
264
|
+
function redactGatewaySnapshot(snapshot) {
|
|
265
|
+
const copy = { ...snapshot };
|
|
266
|
+
delete copy.internal_endpoints;
|
|
267
|
+
delete copy.keys;
|
|
268
|
+
delete copy.tokens;
|
|
269
|
+
return copy;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/domain/logic/evidence-bundle.ts
|
|
273
|
+
function createEvidenceBundle(params) {
|
|
274
|
+
const level = params.redactionLevel ?? "safe_default";
|
|
275
|
+
const sortedEvents = [...params.events].sort(
|
|
276
|
+
(a, b) => compareCursor(a.cursor, b.cursor)
|
|
277
|
+
);
|
|
278
|
+
const redactedEvents = sortedEvents.map((e) => redactEvent(e, level));
|
|
279
|
+
const by_outcome = { OK: 0, DENY: 0, ERROR: 0 };
|
|
280
|
+
const by_denial_reason = {};
|
|
281
|
+
redactedEvents.forEach((e) => {
|
|
282
|
+
const outcome = e.outcome || "OK";
|
|
283
|
+
by_outcome[outcome] = (by_outcome[outcome] || 0) + 1;
|
|
284
|
+
if (typeof e.reason === "string") {
|
|
285
|
+
by_denial_reason[e.reason] = (by_denial_reason[e.reason] || 0) + 1;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
const summary = {
|
|
289
|
+
total_events: redactedEvents.length,
|
|
290
|
+
by_outcome,
|
|
291
|
+
by_denial_reason,
|
|
292
|
+
cursor_continuity: "UNKNOWN"
|
|
293
|
+
// Continuity check is computationally expensive for large bundles
|
|
294
|
+
};
|
|
295
|
+
const redactedSnapshot = params.gatewaySnapshot ? redactGatewaySnapshot(params.gatewaySnapshot) : void 0;
|
|
296
|
+
return {
|
|
297
|
+
metadata: {
|
|
298
|
+
schema_version: "1.1.0",
|
|
299
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
300
|
+
source: {
|
|
301
|
+
dashboard_version: params.dashboardVersion,
|
|
302
|
+
gateway_version: params.gatewaySnapshot?.version
|
|
303
|
+
},
|
|
304
|
+
filters_applied: params.filters,
|
|
305
|
+
cursor_range: params.cursorRange,
|
|
306
|
+
redaction_level: level
|
|
307
|
+
},
|
|
308
|
+
events: redactedEvents,
|
|
309
|
+
integrity_summary: summary,
|
|
310
|
+
gateway_snapshot: redactedSnapshot
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
314
|
+
0 && (module.exports = {
|
|
315
|
+
Base64UrlError,
|
|
316
|
+
assertCursorInvariant,
|
|
317
|
+
base64urlDecodeToBytes,
|
|
318
|
+
base64urlDecodeToUtf8,
|
|
319
|
+
base64urlEncodeBytes,
|
|
320
|
+
base64urlEncodeUtf8,
|
|
321
|
+
checkCursorContinuity,
|
|
322
|
+
compareCursor,
|
|
323
|
+
createEvidenceBundle,
|
|
324
|
+
decodeCursor,
|
|
325
|
+
deriveCursor,
|
|
326
|
+
isCanonicalLowerUuid,
|
|
327
|
+
isUuidV7,
|
|
328
|
+
orderingCompare,
|
|
329
|
+
redactEvent,
|
|
330
|
+
redactGatewaySnapshot
|
|
331
|
+
});
|
|
332
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/infrastructure/base64url.ts","../src/infrastructure/uuidv7.ts","../src/domain/logic/cursor.ts","../src/domain/logic/continuity.ts","../src/domain/logic/ordering.ts","../src/domain/logic/redaction.ts","../src/domain/logic/evidence-bundle.ts"],"sourcesContent":["// typescript/src/index.ts\n// Public API exports for @talosprotocol/contracts\n//\n// This file is the ONLY supported import path for this package.\n// All exports are stable and backward-compatible.\n\n// ============================================================================\n// Infrastructure: Encoding & Validation Utilities\n// ============================================================================\n\nexport {\n Base64UrlError,\n base64urlEncodeBytes,\n base64urlDecodeToBytes,\n base64urlEncodeUtf8,\n base64urlDecodeToUtf8,\n} from \"./infrastructure/base64url.js\";\n\nexport { isUuidV7, isCanonicalLowerUuid } from \"./infrastructure/uuidv7.js\";\n\n// ============================================================================\n// Domain Types\n// ============================================================================\n\n// Cursor types\nexport type {\n CursorValidationReason,\n CursorValidationResult,\n DecodedCursor,\n} from \"./domain/types/cursor.types.js\";\n\n// Event types\nexport type {\n AuditEvent,\n GatewayStatus,\n Outcome,\n RedactionLevel,\n AuditFilters,\n Comparator,\n} from \"./domain/types/event.types.js\";\n\n// Bundle types\nexport type {\n EvidenceBundleMetadata,\n IntegritySummary,\n EvidenceBundle,\n CursorGap,\n ContinuityCheckResult,\n} from \"./domain/types/bundle.types.js\";\n\n// ============================================================================\n// Domain Logic\n// ============================================================================\n\n// Cursor operations\nexport {\n deriveCursor,\n decodeCursor,\n compareCursor,\n assertCursorInvariant,\n} from \"./domain/logic/cursor.js\";\n\n// Continuity checking\nexport { checkCursorContinuity } from \"./domain/logic/continuity.js\";\n\n// Event ordering\nexport { orderingCompare } from \"./domain/logic/ordering.js\";\n\n// Redaction\nexport {\n redactEvent,\n redactGatewaySnapshot,\n} from \"./domain/logic/redaction.js\";\n\n// Evidence bundle creation\nexport { createEvidenceBundle } from \"./domain/logic/evidence-bundle.js\";\n","// src/infrastructure/base64url.ts\n// Strict base64url encoding/decoding - NO btoa/atob, NO padding\n// Uses TextEncoder/TextDecoder for universal runtime support (Node, Browser, Edge)\n\nexport class Base64UrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"Base64UrlError\";\n }\n}\n\nconst ALPHABET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst RE_VALID = /^[A-Za-z0-9\\-_]*$/;\n\nfunction toUtf8Bytes(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\nfunction fromUtf8Bytes(b: Uint8Array): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(b);\n}\n\nexport function base64urlEncodeBytes(bytes: Uint8Array): string {\n if (bytes.length === 0) return \"\";\n\n let out = \"\";\n let i = 0;\n\n while (i + 3 <= bytes.length) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n out += ALPHABET[(n >>> 18) & 63]!;\n out += ALPHABET[(n >>> 12) & 63]!;\n out += ALPHABET[(n >>> 6) & 63]!;\n out += ALPHABET[n & 63]!;\n i += 3;\n }\n\n const rem = bytes.length - i;\n if (rem === 1) {\n const n = bytes[i]!;\n out += ALPHABET[(n >>> 2) & 63]!;\n out += ALPHABET[(n << 4) & 63]!;\n // no padding\n } else if (rem === 2) {\n const n = (bytes[i]! << 8) | bytes[i + 1]!;\n out += ALPHABET[(n >>> 10) & 63]!;\n out += ALPHABET[(n >>> 4) & 63]!;\n out += ALPHABET[(n << 2) & 63]!;\n // no padding\n }\n\n return out;\n}\n\nfunction decodeChar(c: string): number {\n const idx = ALPHABET.indexOf(c);\n if (idx === -1) throw new Base64UrlError(`Invalid base64url character: ${c}`);\n return idx;\n}\n\nexport function base64urlDecodeToBytes(s: string): Uint8Array {\n if (s.length === 0) return new Uint8Array(0);\n\n // Reject padding and non-url alphabet\n if (s.includes(\"=\")) throw new Base64UrlError(\"Padding is not allowed\");\n if (!RE_VALID.test(s))\n throw new Base64UrlError(\"Non-base64url characters present\");\n // length mod 4 == 1 is impossible for canonical base64url\n if (s.length % 4 === 1) throw new Base64UrlError(\"Invalid base64url length\");\n\n const out: number[] = [];\n let i = 0;\n\n while (i < s.length) {\n const remain = s.length - i;\n\n if (remain >= 4) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!),\n c = decodeChar(s[i + 2]!),\n d = decodeChar(s[i + 3]!);\n const n = (a << 18) | (b << 12) | (c << 6) | d;\n out.push((n >>> 16) & 255, (n >>> 8) & 255, n & 255);\n i += 4;\n continue;\n }\n\n if (remain === 2) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!);\n const n = (a << 18) | (b << 12);\n out.push((n >>> 16) & 255);\n i += 2;\n continue;\n }\n\n if (remain === 3) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!),\n c = decodeChar(s[i + 2]!);\n const n = (a << 18) | (b << 12) | (c << 6);\n out.push((n >>> 16) & 255, (n >>> 8) & 255);\n i += 3;\n continue;\n }\n\n throw new Base64UrlError(\"Invalid base64url length\");\n }\n\n const decoded = new Uint8Array(out);\n // Strict canonical check: re-encode must match exactly\n const recoded = base64urlEncodeBytes(decoded);\n if (recoded !== s) throw new Base64UrlError(\"Non-canonical base64url form\");\n return decoded;\n}\n\nexport function base64urlEncodeUtf8(s: string): string {\n return base64urlEncodeBytes(toUtf8Bytes(s));\n}\n\nexport function base64urlDecodeToUtf8(s: string): string {\n return fromUtf8Bytes(base64urlDecodeToBytes(s));\n}\n","// src/infrastructure/uuidv7.ts\n// Strict UUIDv7 validation - pure regex, no external dependencies\n\nconst RE_UUID =\n /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n\nexport function isUuidV7(id: string): boolean {\n if (!RE_UUID.test(id)) return false;\n // version nibble is first nibble of 3rd group (position 14)\n const version = id[14]!;\n if (version !== \"7\") return false;\n\n // variant is first nibble of 4th group (position 19): 8, 9, a, b\n const variant = id[19]!.toLowerCase();\n return (\n variant === \"8\" || variant === \"9\" || variant === \"a\" || variant === \"b\"\n );\n}\n\nexport function isCanonicalLowerUuid(id: string): boolean {\n return id === id.toLowerCase();\n}\n","// src/domain/logic/cursor.ts\n// Cursor derivation, validation, and comparison\n// D4=B: invalid frames do not throw from validation, return INVALID_FRAME\n\nimport {\n base64urlDecodeToUtf8,\n base64urlEncodeUtf8,\n} from \"../../infrastructure/base64url.js\";\nimport { isUuidV7, isCanonicalLowerUuid } from \"../../infrastructure/uuidv7.js\";\nimport type {\n CursorValidationResult,\n DecodedCursor,\n} from \"../types/cursor.types.js\";\n\nfunction isValidUnixSecondsInt(n: unknown): n is number {\n return (\n typeof n === \"number\" &&\n Number.isInteger(n) &&\n n >= 0 &&\n Number.isSafeInteger(n)\n );\n}\n\nfunction isCanonicalTimestampString(s: string): boolean {\n if (!/^\\d+$/.test(s)) return false;\n if (s.length > 1 && s.startsWith(\"0\")) return false;\n return true;\n}\n\n/**\n * Derive a cursor from timestamp and event_id.\n * cursor = base64url(utf8(\"{timestamp}:{event_id}\"))\n */\nexport function deriveCursor(timestamp: number, eventId: string): string {\n if (!isValidUnixSecondsInt(timestamp)) {\n throw new Error(\"timestamp must be unix seconds integer\");\n }\n const plain = `${String(timestamp)}:${eventId}`;\n return base64urlEncodeUtf8(plain);\n}\n\n/**\n * Decode a cursor to {timestamp, event_id}.\n * Throws on invalid cursor format.\n */\nexport function decodeCursor(cursor: string): DecodedCursor {\n const raw = base64urlDecodeToUtf8(cursor);\n const colonIndex = raw.indexOf(\":\");\n if (colonIndex === -1)\n throw new Error(\"cursor frame must be '{timestamp}:{event_id}'\");\n\n const tsStr = raw.slice(0, colonIndex);\n const eventId = raw.slice(colonIndex + 1);\n\n if (!isCanonicalTimestampString(tsStr))\n throw new Error(\"timestamp is not canonical base-10\");\n const tsNum = Number(tsStr);\n if (!isValidUnixSecondsInt(tsNum))\n throw new Error(\"timestamp is out of range\");\n\n if (!isUuidV7(eventId)) throw new Error(\"event_id is not uuidv7\");\n if (!isCanonicalLowerUuid(eventId))\n throw new Error(\"event_id must be lowercase canonical uuid\");\n\n return { timestamp: tsNum, event_id: eventId };\n}\n\n/**\n * Compare two cursors.\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n * Compares by (timestamp, event_id) lexicographically.\n */\nexport function compareCursor(a: string, b: string): -1 | 0 | 1 {\n const da = decodeCursor(a);\n const db = decodeCursor(b);\n\n if (da.timestamp < db.timestamp) return -1;\n if (da.timestamp > db.timestamp) return 1;\n\n if (da.event_id < db.event_id) return -1;\n if (da.event_id > db.event_id) return 1;\n return 0;\n}\n\n/**\n * Validate that an event's cursor matches the derived cursor.\n * D4=B: Does not throw on invalid frames, returns { ok: false, reason: \"INVALID_FRAME\" }\n */\nexport function assertCursorInvariant(event: {\n timestamp: unknown;\n event_id: unknown;\n cursor: unknown;\n}): CursorValidationResult {\n // Frame checks first (D4=B: do not throw)\n if (\n !isValidUnixSecondsInt(event.timestamp) ||\n typeof event.event_id !== \"string\" ||\n typeof event.cursor !== \"string\"\n ) {\n return { ok: false, derived: \"\", reason: \"INVALID_FRAME\" };\n }\n\n // Decode cursor must be strict base64url + canonical frame. If it fails, INVALID_FRAME.\n try {\n decodeCursor(event.cursor);\n } catch {\n const derived = deriveCursor(event.timestamp, event.event_id);\n return { ok: false, derived, reason: \"INVALID_FRAME\" };\n }\n\n // Derived cursor must match exactly (CURSOR_MISMATCH)\n const derived = deriveCursor(event.timestamp, event.event_id);\n if (event.cursor !== derived)\n return { ok: false, derived, reason: \"CURSOR_MISMATCH\" };\n\n // Also enforce event_id canonical uuidv7 for the event frame\n if (!isUuidV7(event.event_id) || !isCanonicalLowerUuid(event.event_id)) {\n return { ok: false, derived, reason: \"INVALID_FRAME\" };\n }\n\n return { ok: true, derived };\n}\n","// src/domain/logic/continuity.ts\n// Cursor continuity checking\n\nimport { compareCursor } from \"./cursor.js\";\nimport type {\n CursorGap,\n ContinuityCheckResult,\n} from \"../types/bundle.types.js\";\n\n/**\n * Check cursor continuity based on contract-defined rules.\n * Gap detection requires cursor predecessor relation, not heuristics.\n * For v1.1, we verify strict ordering.\n */\nexport function checkCursorContinuity(\n events: Array<{ cursor: string; timestamp: number }>,\n _expectedPredecessor?: (cursor: string) => string | null,\n): ContinuityCheckResult {\n if (events.length <= 1) {\n return { status: \"CONTINUOUS\", gaps: [] };\n }\n\n const gaps: CursorGap[] = [];\n\n // Verify ordering\n for (let i = 0; i < events.length - 1; i++) {\n const current = events[i];\n const next = events[i + 1];\n\n // If next cursor is Lexicographically smaller than current, strict ordering violation\n const cmp = compareCursor(current.cursor, next.cursor);\n if (cmp === 1) {\n // Order violation isn't exactly a \"gap\" but a \"discontinuity\"\n // For v1.1, we treat this as a Gap for the sake of the interface\n gaps.push({\n from_cursor: current.cursor,\n to_cursor: next.cursor,\n detected_at: Date.now(),\n });\n }\n }\n\n // FUTURE(v1.2): Implement actual rigorous predecessor check using Merkle links if available\n // For now, we only check monotonic ordering.\n\n if (gaps.length > 0) {\n return { status: \"GAP_DETECTED\", gaps };\n }\n\n return { status: \"CONTINUOUS\", gaps: [] };\n}\n","// src/domain/logic/ordering.ts\n// Event ordering comparison: (timestamp DESC, event_id DESC)\n\n/**\n * Compare two events for ordering.\n * Returns -1 if a should come before b (a is \"greater\" in DESC order)\n * Returns 0 if equal\n * Returns 1 if a should come after b\n *\n * Ordering: timestamp DESC, then event_id DESC\n */\nexport function orderingCompare(\n a: { timestamp: number; event_id: string },\n b: { timestamp: number; event_id: string },\n): -1 | 0 | 1 {\n // DESC timestamp: higher timestamp comes first\n if (a.timestamp > b.timestamp) return -1;\n if (a.timestamp < b.timestamp) return 1;\n\n // DESC event_id: higher event_id comes first (lexicographic)\n if (a.event_id > b.event_id) return -1;\n if (a.event_id < b.event_id) return 1;\n return 0;\n}\n","// src/domain/logic/redaction.ts\n// Event and gateway snapshot redaction\n\nimport type {\n AuditEvent,\n GatewayStatus,\n RedactionLevel,\n} from \"../types/event.types.js\";\n\nexport function redactEvent(\n event: AuditEvent,\n level: RedactionLevel,\n): AuditEvent {\n if (level === \"none\") return event;\n\n const copy = structuredClone(event); // Deep copy\n\n // Common redactions for safe_default\n if (level === \"safe_default\" || level === \"strict\") {\n if (copy.request && typeof copy.request === \"object\") {\n const req = copy.request as {\n headers?: Record<string, string>;\n [key: string]: unknown;\n };\n if (req.headers) {\n // Strip auth headers\n delete req.headers[\"authorization\"];\n delete req.headers[\"cookie\"];\n delete req.headers[\"x-api-key\"];\n }\n }\n }\n\n if (level === \"strict\") {\n // Strict redaction - strip inputs and all headers\n if (copy.input) {\n copy.input = \"[REDACTED]\";\n }\n if (copy.request && typeof copy.request === \"object\") {\n const req = copy.request as {\n headers?: Record<string, string>;\n [key: string]: unknown;\n };\n req.headers = undefined; // Remove all headers\n }\n }\n\n return copy;\n}\n\nexport function redactGatewaySnapshot(\n snapshot: GatewayStatus,\n): Partial<GatewayStatus> {\n const copy = { ...snapshot };\n\n // Always strip these\n delete copy.internal_endpoints;\n delete copy.keys;\n delete copy.tokens;\n\n return copy;\n}\n","// src/domain/logic/evidence-bundle.ts\n// Evidence bundle creation - pure deterministic transformation\n\nimport { compareCursor } from \"./cursor.js\";\nimport { redactEvent, redactGatewaySnapshot } from \"./redaction.js\";\nimport type {\n AuditEvent,\n GatewayStatus,\n RedactionLevel,\n AuditFilters,\n Outcome,\n} from \"../types/event.types.js\";\nimport type {\n EvidenceBundle,\n IntegritySummary,\n} from \"../types/bundle.types.js\";\n\nexport interface CreateEvidenceBundleParams {\n events: AuditEvent[];\n redactionLevel?: RedactionLevel;\n gatewaySnapshot?: GatewayStatus;\n filters?: AuditFilters;\n cursorRange?: { start?: string; end?: string };\n dashboardVersion: string;\n}\n\nexport function createEvidenceBundle(\n params: CreateEvidenceBundleParams,\n): EvidenceBundle {\n const level = params.redactionLevel ?? \"safe_default\";\n\n // 1. Sort events by cursor (canonical order)\n const sortedEvents = [...params.events].sort((a, b) =>\n compareCursor(a.cursor, b.cursor),\n );\n\n // 2. Apply redaction based on level\n const redactedEvents = sortedEvents.map((e) => redactEvent(e, level));\n\n // 3. Compute integrity summary\n const by_outcome: Record<Outcome, number> = { OK: 0, DENY: 0, ERROR: 0 };\n const by_denial_reason: Record<string, number> = {};\n\n redactedEvents.forEach((e) => {\n const outcome = (e.outcome as Outcome) || \"OK\";\n by_outcome[outcome] = (by_outcome[outcome] || 0) + 1;\n\n if (typeof e.reason === \"string\") {\n by_denial_reason[e.reason] = (by_denial_reason[e.reason] || 0) + 1;\n }\n });\n\n const summary: IntegritySummary = {\n total_events: redactedEvents.length,\n by_outcome,\n by_denial_reason,\n cursor_continuity: \"UNKNOWN\", // Continuity check is computationally expensive for large bundles\n };\n\n // 4. Strip secrets from gateway snapshot\n const redactedSnapshot = params.gatewaySnapshot\n ? redactGatewaySnapshot(params.gatewaySnapshot)\n : undefined;\n\n return {\n metadata: {\n schema_version: \"1.1.0\",\n generated_at: new Date().toISOString(),\n source: {\n dashboard_version: params.dashboardVersion,\n gateway_version: params.gatewaySnapshot?.version,\n },\n filters_applied: params.filters,\n cursor_range: params.cursorRange,\n redaction_level: level,\n },\n events: redactedEvents,\n integrity_summary: summary,\n gateway_snapshot: redactedSnapshot,\n };\n}\n"],"mappings":"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","names":["derived"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
declare class Base64UrlError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
declare function base64urlEncodeBytes(bytes: Uint8Array): string;
|
|
5
|
+
declare function base64urlDecodeToBytes(s: string): Uint8Array;
|
|
6
|
+
declare function base64urlEncodeUtf8(s: string): string;
|
|
7
|
+
declare function base64urlDecodeToUtf8(s: string): string;
|
|
8
|
+
|
|
9
|
+
declare function isUuidV7(id: string): boolean;
|
|
10
|
+
declare function isCanonicalLowerUuid(id: string): boolean;
|
|
11
|
+
|
|
12
|
+
type CursorValidationReason = "CURSOR_MISMATCH" | "INVALID_FRAME";
|
|
13
|
+
type CursorValidationResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
derived: string;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
derived: string;
|
|
19
|
+
reason: CursorValidationReason;
|
|
20
|
+
};
|
|
21
|
+
type DecodedCursor = {
|
|
22
|
+
timestamp: number;
|
|
23
|
+
event_id: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface AuditEvent {
|
|
27
|
+
event_id: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
cursor: string;
|
|
30
|
+
outcome?: Outcome;
|
|
31
|
+
reason?: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
request?: {
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface GatewayStatus {
|
|
40
|
+
version: string;
|
|
41
|
+
internal_endpoints?: string[];
|
|
42
|
+
keys?: Record<string, string>;
|
|
43
|
+
tokens?: Record<string, string>;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
type Outcome = "OK" | "DENY" | "ERROR";
|
|
47
|
+
type RedactionLevel = "none" | "safe_default" | "strict";
|
|
48
|
+
interface AuditFilters {
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
type Comparator<T> = (a: T, b: T) => -1 | 0 | 1;
|
|
52
|
+
|
|
53
|
+
interface EvidenceBundleMetadata {
|
|
54
|
+
schema_version: string;
|
|
55
|
+
generated_at: string;
|
|
56
|
+
source: {
|
|
57
|
+
dashboard_version: string;
|
|
58
|
+
gateway_version?: string;
|
|
59
|
+
};
|
|
60
|
+
filters_applied?: AuditFilters;
|
|
61
|
+
cursor_range?: {
|
|
62
|
+
start?: string;
|
|
63
|
+
end?: string;
|
|
64
|
+
};
|
|
65
|
+
redaction_level: RedactionLevel;
|
|
66
|
+
}
|
|
67
|
+
interface IntegritySummary {
|
|
68
|
+
total_events: number;
|
|
69
|
+
by_outcome: Record<Outcome, number>;
|
|
70
|
+
by_denial_reason: Record<string, number>;
|
|
71
|
+
cursor_continuity: "VERIFIED" | "GAPS_DETECTED" | "UNKNOWN";
|
|
72
|
+
}
|
|
73
|
+
interface EvidenceBundle {
|
|
74
|
+
metadata: EvidenceBundleMetadata;
|
|
75
|
+
events: AuditEvent[];
|
|
76
|
+
integrity_summary: IntegritySummary;
|
|
77
|
+
gateway_snapshot?: Partial<GatewayStatus>;
|
|
78
|
+
}
|
|
79
|
+
interface CursorGap {
|
|
80
|
+
from_cursor: string;
|
|
81
|
+
to_cursor: string;
|
|
82
|
+
expected_events?: number;
|
|
83
|
+
detected_at: number;
|
|
84
|
+
}
|
|
85
|
+
interface ContinuityCheckResult {
|
|
86
|
+
status: "CONTINUOUS" | "GAP_DETECTED" | "UNKNOWN";
|
|
87
|
+
gaps: CursorGap[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Derive a cursor from timestamp and event_id.
|
|
92
|
+
* cursor = base64url(utf8("{timestamp}:{event_id}"))
|
|
93
|
+
*/
|
|
94
|
+
declare function deriveCursor(timestamp: number, eventId: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Decode a cursor to {timestamp, event_id}.
|
|
97
|
+
* Throws on invalid cursor format.
|
|
98
|
+
*/
|
|
99
|
+
declare function decodeCursor(cursor: string): DecodedCursor;
|
|
100
|
+
/**
|
|
101
|
+
* Compare two cursors.
|
|
102
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
103
|
+
* Compares by (timestamp, event_id) lexicographically.
|
|
104
|
+
*/
|
|
105
|
+
declare function compareCursor(a: string, b: string): -1 | 0 | 1;
|
|
106
|
+
/**
|
|
107
|
+
* Validate that an event's cursor matches the derived cursor.
|
|
108
|
+
* D4=B: Does not throw on invalid frames, returns { ok: false, reason: "INVALID_FRAME" }
|
|
109
|
+
*/
|
|
110
|
+
declare function assertCursorInvariant(event: {
|
|
111
|
+
timestamp: unknown;
|
|
112
|
+
event_id: unknown;
|
|
113
|
+
cursor: unknown;
|
|
114
|
+
}): CursorValidationResult;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check cursor continuity based on contract-defined rules.
|
|
118
|
+
* Gap detection requires cursor predecessor relation, not heuristics.
|
|
119
|
+
* For v1.1, we verify strict ordering.
|
|
120
|
+
*/
|
|
121
|
+
declare function checkCursorContinuity(events: Array<{
|
|
122
|
+
cursor: string;
|
|
123
|
+
timestamp: number;
|
|
124
|
+
}>, _expectedPredecessor?: (cursor: string) => string | null): ContinuityCheckResult;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Compare two events for ordering.
|
|
128
|
+
* Returns -1 if a should come before b (a is "greater" in DESC order)
|
|
129
|
+
* Returns 0 if equal
|
|
130
|
+
* Returns 1 if a should come after b
|
|
131
|
+
*
|
|
132
|
+
* Ordering: timestamp DESC, then event_id DESC
|
|
133
|
+
*/
|
|
134
|
+
declare function orderingCompare(a: {
|
|
135
|
+
timestamp: number;
|
|
136
|
+
event_id: string;
|
|
137
|
+
}, b: {
|
|
138
|
+
timestamp: number;
|
|
139
|
+
event_id: string;
|
|
140
|
+
}): -1 | 0 | 1;
|
|
141
|
+
|
|
142
|
+
declare function redactEvent(event: AuditEvent, level: RedactionLevel): AuditEvent;
|
|
143
|
+
declare function redactGatewaySnapshot(snapshot: GatewayStatus): Partial<GatewayStatus>;
|
|
144
|
+
|
|
145
|
+
interface CreateEvidenceBundleParams {
|
|
146
|
+
events: AuditEvent[];
|
|
147
|
+
redactionLevel?: RedactionLevel;
|
|
148
|
+
gatewaySnapshot?: GatewayStatus;
|
|
149
|
+
filters?: AuditFilters;
|
|
150
|
+
cursorRange?: {
|
|
151
|
+
start?: string;
|
|
152
|
+
end?: string;
|
|
153
|
+
};
|
|
154
|
+
dashboardVersion: string;
|
|
155
|
+
}
|
|
156
|
+
declare function createEvidenceBundle(params: CreateEvidenceBundleParams): EvidenceBundle;
|
|
157
|
+
|
|
158
|
+
export { type AuditEvent, type AuditFilters, Base64UrlError, type Comparator, type ContinuityCheckResult, type CursorGap, type CursorValidationReason, type CursorValidationResult, type DecodedCursor, type EvidenceBundle, type EvidenceBundleMetadata, type GatewayStatus, type IntegritySummary, type Outcome, type RedactionLevel, assertCursorInvariant, base64urlDecodeToBytes, base64urlDecodeToUtf8, base64urlEncodeBytes, base64urlEncodeUtf8, checkCursorContinuity, compareCursor, createEvidenceBundle, decodeCursor, deriveCursor, isCanonicalLowerUuid, isUuidV7, orderingCompare, redactEvent, redactGatewaySnapshot };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
declare class Base64UrlError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
declare function base64urlEncodeBytes(bytes: Uint8Array): string;
|
|
5
|
+
declare function base64urlDecodeToBytes(s: string): Uint8Array;
|
|
6
|
+
declare function base64urlEncodeUtf8(s: string): string;
|
|
7
|
+
declare function base64urlDecodeToUtf8(s: string): string;
|
|
8
|
+
|
|
9
|
+
declare function isUuidV7(id: string): boolean;
|
|
10
|
+
declare function isCanonicalLowerUuid(id: string): boolean;
|
|
11
|
+
|
|
12
|
+
type CursorValidationReason = "CURSOR_MISMATCH" | "INVALID_FRAME";
|
|
13
|
+
type CursorValidationResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
derived: string;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
derived: string;
|
|
19
|
+
reason: CursorValidationReason;
|
|
20
|
+
};
|
|
21
|
+
type DecodedCursor = {
|
|
22
|
+
timestamp: number;
|
|
23
|
+
event_id: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface AuditEvent {
|
|
27
|
+
event_id: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
cursor: string;
|
|
30
|
+
outcome?: Outcome;
|
|
31
|
+
reason?: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
request?: {
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface GatewayStatus {
|
|
40
|
+
version: string;
|
|
41
|
+
internal_endpoints?: string[];
|
|
42
|
+
keys?: Record<string, string>;
|
|
43
|
+
tokens?: Record<string, string>;
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
type Outcome = "OK" | "DENY" | "ERROR";
|
|
47
|
+
type RedactionLevel = "none" | "safe_default" | "strict";
|
|
48
|
+
interface AuditFilters {
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
type Comparator<T> = (a: T, b: T) => -1 | 0 | 1;
|
|
52
|
+
|
|
53
|
+
interface EvidenceBundleMetadata {
|
|
54
|
+
schema_version: string;
|
|
55
|
+
generated_at: string;
|
|
56
|
+
source: {
|
|
57
|
+
dashboard_version: string;
|
|
58
|
+
gateway_version?: string;
|
|
59
|
+
};
|
|
60
|
+
filters_applied?: AuditFilters;
|
|
61
|
+
cursor_range?: {
|
|
62
|
+
start?: string;
|
|
63
|
+
end?: string;
|
|
64
|
+
};
|
|
65
|
+
redaction_level: RedactionLevel;
|
|
66
|
+
}
|
|
67
|
+
interface IntegritySummary {
|
|
68
|
+
total_events: number;
|
|
69
|
+
by_outcome: Record<Outcome, number>;
|
|
70
|
+
by_denial_reason: Record<string, number>;
|
|
71
|
+
cursor_continuity: "VERIFIED" | "GAPS_DETECTED" | "UNKNOWN";
|
|
72
|
+
}
|
|
73
|
+
interface EvidenceBundle {
|
|
74
|
+
metadata: EvidenceBundleMetadata;
|
|
75
|
+
events: AuditEvent[];
|
|
76
|
+
integrity_summary: IntegritySummary;
|
|
77
|
+
gateway_snapshot?: Partial<GatewayStatus>;
|
|
78
|
+
}
|
|
79
|
+
interface CursorGap {
|
|
80
|
+
from_cursor: string;
|
|
81
|
+
to_cursor: string;
|
|
82
|
+
expected_events?: number;
|
|
83
|
+
detected_at: number;
|
|
84
|
+
}
|
|
85
|
+
interface ContinuityCheckResult {
|
|
86
|
+
status: "CONTINUOUS" | "GAP_DETECTED" | "UNKNOWN";
|
|
87
|
+
gaps: CursorGap[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Derive a cursor from timestamp and event_id.
|
|
92
|
+
* cursor = base64url(utf8("{timestamp}:{event_id}"))
|
|
93
|
+
*/
|
|
94
|
+
declare function deriveCursor(timestamp: number, eventId: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* Decode a cursor to {timestamp, event_id}.
|
|
97
|
+
* Throws on invalid cursor format.
|
|
98
|
+
*/
|
|
99
|
+
declare function decodeCursor(cursor: string): DecodedCursor;
|
|
100
|
+
/**
|
|
101
|
+
* Compare two cursors.
|
|
102
|
+
* Returns -1 if a < b, 0 if equal, 1 if a > b.
|
|
103
|
+
* Compares by (timestamp, event_id) lexicographically.
|
|
104
|
+
*/
|
|
105
|
+
declare function compareCursor(a: string, b: string): -1 | 0 | 1;
|
|
106
|
+
/**
|
|
107
|
+
* Validate that an event's cursor matches the derived cursor.
|
|
108
|
+
* D4=B: Does not throw on invalid frames, returns { ok: false, reason: "INVALID_FRAME" }
|
|
109
|
+
*/
|
|
110
|
+
declare function assertCursorInvariant(event: {
|
|
111
|
+
timestamp: unknown;
|
|
112
|
+
event_id: unknown;
|
|
113
|
+
cursor: unknown;
|
|
114
|
+
}): CursorValidationResult;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check cursor continuity based on contract-defined rules.
|
|
118
|
+
* Gap detection requires cursor predecessor relation, not heuristics.
|
|
119
|
+
* For v1.1, we verify strict ordering.
|
|
120
|
+
*/
|
|
121
|
+
declare function checkCursorContinuity(events: Array<{
|
|
122
|
+
cursor: string;
|
|
123
|
+
timestamp: number;
|
|
124
|
+
}>, _expectedPredecessor?: (cursor: string) => string | null): ContinuityCheckResult;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Compare two events for ordering.
|
|
128
|
+
* Returns -1 if a should come before b (a is "greater" in DESC order)
|
|
129
|
+
* Returns 0 if equal
|
|
130
|
+
* Returns 1 if a should come after b
|
|
131
|
+
*
|
|
132
|
+
* Ordering: timestamp DESC, then event_id DESC
|
|
133
|
+
*/
|
|
134
|
+
declare function orderingCompare(a: {
|
|
135
|
+
timestamp: number;
|
|
136
|
+
event_id: string;
|
|
137
|
+
}, b: {
|
|
138
|
+
timestamp: number;
|
|
139
|
+
event_id: string;
|
|
140
|
+
}): -1 | 0 | 1;
|
|
141
|
+
|
|
142
|
+
declare function redactEvent(event: AuditEvent, level: RedactionLevel): AuditEvent;
|
|
143
|
+
declare function redactGatewaySnapshot(snapshot: GatewayStatus): Partial<GatewayStatus>;
|
|
144
|
+
|
|
145
|
+
interface CreateEvidenceBundleParams {
|
|
146
|
+
events: AuditEvent[];
|
|
147
|
+
redactionLevel?: RedactionLevel;
|
|
148
|
+
gatewaySnapshot?: GatewayStatus;
|
|
149
|
+
filters?: AuditFilters;
|
|
150
|
+
cursorRange?: {
|
|
151
|
+
start?: string;
|
|
152
|
+
end?: string;
|
|
153
|
+
};
|
|
154
|
+
dashboardVersion: string;
|
|
155
|
+
}
|
|
156
|
+
declare function createEvidenceBundle(params: CreateEvidenceBundleParams): EvidenceBundle;
|
|
157
|
+
|
|
158
|
+
export { type AuditEvent, type AuditFilters, Base64UrlError, type Comparator, type ContinuityCheckResult, type CursorGap, type CursorValidationReason, type CursorValidationResult, type DecodedCursor, type EvidenceBundle, type EvidenceBundleMetadata, type GatewayStatus, type IntegritySummary, type Outcome, type RedactionLevel, assertCursorInvariant, base64urlDecodeToBytes, base64urlDecodeToUtf8, base64urlEncodeBytes, base64urlEncodeUtf8, checkCursorContinuity, compareCursor, createEvidenceBundle, decodeCursor, deriveCursor, isCanonicalLowerUuid, isUuidV7, orderingCompare, redactEvent, redactGatewaySnapshot };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// src/infrastructure/base64url.ts
|
|
2
|
+
var Base64UrlError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "Base64UrlError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
9
|
+
var RE_VALID = /^[A-Za-z0-9\-_]*$/;
|
|
10
|
+
function toUtf8Bytes(s) {
|
|
11
|
+
return new TextEncoder().encode(s);
|
|
12
|
+
}
|
|
13
|
+
function fromUtf8Bytes(b) {
|
|
14
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(b);
|
|
15
|
+
}
|
|
16
|
+
function base64urlEncodeBytes(bytes) {
|
|
17
|
+
if (bytes.length === 0) return "";
|
|
18
|
+
let out = "";
|
|
19
|
+
let i = 0;
|
|
20
|
+
while (i + 3 <= bytes.length) {
|
|
21
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
22
|
+
out += ALPHABET[n >>> 18 & 63];
|
|
23
|
+
out += ALPHABET[n >>> 12 & 63];
|
|
24
|
+
out += ALPHABET[n >>> 6 & 63];
|
|
25
|
+
out += ALPHABET[n & 63];
|
|
26
|
+
i += 3;
|
|
27
|
+
}
|
|
28
|
+
const rem = bytes.length - i;
|
|
29
|
+
if (rem === 1) {
|
|
30
|
+
const n = bytes[i];
|
|
31
|
+
out += ALPHABET[n >>> 2 & 63];
|
|
32
|
+
out += ALPHABET[n << 4 & 63];
|
|
33
|
+
} else if (rem === 2) {
|
|
34
|
+
const n = bytes[i] << 8 | bytes[i + 1];
|
|
35
|
+
out += ALPHABET[n >>> 10 & 63];
|
|
36
|
+
out += ALPHABET[n >>> 4 & 63];
|
|
37
|
+
out += ALPHABET[n << 2 & 63];
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
function decodeChar(c) {
|
|
42
|
+
const idx = ALPHABET.indexOf(c);
|
|
43
|
+
if (idx === -1) throw new Base64UrlError(`Invalid base64url character: ${c}`);
|
|
44
|
+
return idx;
|
|
45
|
+
}
|
|
46
|
+
function base64urlDecodeToBytes(s) {
|
|
47
|
+
if (s.length === 0) return new Uint8Array(0);
|
|
48
|
+
if (s.includes("=")) throw new Base64UrlError("Padding is not allowed");
|
|
49
|
+
if (!RE_VALID.test(s))
|
|
50
|
+
throw new Base64UrlError("Non-base64url characters present");
|
|
51
|
+
if (s.length % 4 === 1) throw new Base64UrlError("Invalid base64url length");
|
|
52
|
+
const out = [];
|
|
53
|
+
let i = 0;
|
|
54
|
+
while (i < s.length) {
|
|
55
|
+
const remain = s.length - i;
|
|
56
|
+
if (remain >= 4) {
|
|
57
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]), c = decodeChar(s[i + 2]), d = decodeChar(s[i + 3]);
|
|
58
|
+
const n = a << 18 | b << 12 | c << 6 | d;
|
|
59
|
+
out.push(n >>> 16 & 255, n >>> 8 & 255, n & 255);
|
|
60
|
+
i += 4;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (remain === 2) {
|
|
64
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]);
|
|
65
|
+
const n = a << 18 | b << 12;
|
|
66
|
+
out.push(n >>> 16 & 255);
|
|
67
|
+
i += 2;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (remain === 3) {
|
|
71
|
+
const a = decodeChar(s[i]), b = decodeChar(s[i + 1]), c = decodeChar(s[i + 2]);
|
|
72
|
+
const n = a << 18 | b << 12 | c << 6;
|
|
73
|
+
out.push(n >>> 16 & 255, n >>> 8 & 255);
|
|
74
|
+
i += 3;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
throw new Base64UrlError("Invalid base64url length");
|
|
78
|
+
}
|
|
79
|
+
const decoded = new Uint8Array(out);
|
|
80
|
+
const recoded = base64urlEncodeBytes(decoded);
|
|
81
|
+
if (recoded !== s) throw new Base64UrlError("Non-canonical base64url form");
|
|
82
|
+
return decoded;
|
|
83
|
+
}
|
|
84
|
+
function base64urlEncodeUtf8(s) {
|
|
85
|
+
return base64urlEncodeBytes(toUtf8Bytes(s));
|
|
86
|
+
}
|
|
87
|
+
function base64urlDecodeToUtf8(s) {
|
|
88
|
+
return fromUtf8Bytes(base64urlDecodeToBytes(s));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/infrastructure/uuidv7.ts
|
|
92
|
+
var RE_UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
93
|
+
function isUuidV7(id) {
|
|
94
|
+
if (!RE_UUID.test(id)) return false;
|
|
95
|
+
const version = id[14];
|
|
96
|
+
if (version !== "7") return false;
|
|
97
|
+
const variant = id[19].toLowerCase();
|
|
98
|
+
return variant === "8" || variant === "9" || variant === "a" || variant === "b";
|
|
99
|
+
}
|
|
100
|
+
function isCanonicalLowerUuid(id) {
|
|
101
|
+
return id === id.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/domain/logic/cursor.ts
|
|
105
|
+
function isValidUnixSecondsInt(n) {
|
|
106
|
+
return typeof n === "number" && Number.isInteger(n) && n >= 0 && Number.isSafeInteger(n);
|
|
107
|
+
}
|
|
108
|
+
function isCanonicalTimestampString(s) {
|
|
109
|
+
if (!/^\d+$/.test(s)) return false;
|
|
110
|
+
if (s.length > 1 && s.startsWith("0")) return false;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
function deriveCursor(timestamp, eventId) {
|
|
114
|
+
if (!isValidUnixSecondsInt(timestamp)) {
|
|
115
|
+
throw new Error("timestamp must be unix seconds integer");
|
|
116
|
+
}
|
|
117
|
+
const plain = `${String(timestamp)}:${eventId}`;
|
|
118
|
+
return base64urlEncodeUtf8(plain);
|
|
119
|
+
}
|
|
120
|
+
function decodeCursor(cursor) {
|
|
121
|
+
const raw = base64urlDecodeToUtf8(cursor);
|
|
122
|
+
const colonIndex = raw.indexOf(":");
|
|
123
|
+
if (colonIndex === -1)
|
|
124
|
+
throw new Error("cursor frame must be '{timestamp}:{event_id}'");
|
|
125
|
+
const tsStr = raw.slice(0, colonIndex);
|
|
126
|
+
const eventId = raw.slice(colonIndex + 1);
|
|
127
|
+
if (!isCanonicalTimestampString(tsStr))
|
|
128
|
+
throw new Error("timestamp is not canonical base-10");
|
|
129
|
+
const tsNum = Number(tsStr);
|
|
130
|
+
if (!isValidUnixSecondsInt(tsNum))
|
|
131
|
+
throw new Error("timestamp is out of range");
|
|
132
|
+
if (!isUuidV7(eventId)) throw new Error("event_id is not uuidv7");
|
|
133
|
+
if (!isCanonicalLowerUuid(eventId))
|
|
134
|
+
throw new Error("event_id must be lowercase canonical uuid");
|
|
135
|
+
return { timestamp: tsNum, event_id: eventId };
|
|
136
|
+
}
|
|
137
|
+
function compareCursor(a, b) {
|
|
138
|
+
const da = decodeCursor(a);
|
|
139
|
+
const db = decodeCursor(b);
|
|
140
|
+
if (da.timestamp < db.timestamp) return -1;
|
|
141
|
+
if (da.timestamp > db.timestamp) return 1;
|
|
142
|
+
if (da.event_id < db.event_id) return -1;
|
|
143
|
+
if (da.event_id > db.event_id) return 1;
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
function assertCursorInvariant(event) {
|
|
147
|
+
if (!isValidUnixSecondsInt(event.timestamp) || typeof event.event_id !== "string" || typeof event.cursor !== "string") {
|
|
148
|
+
return { ok: false, derived: "", reason: "INVALID_FRAME" };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
decodeCursor(event.cursor);
|
|
152
|
+
} catch {
|
|
153
|
+
const derived2 = deriveCursor(event.timestamp, event.event_id);
|
|
154
|
+
return { ok: false, derived: derived2, reason: "INVALID_FRAME" };
|
|
155
|
+
}
|
|
156
|
+
const derived = deriveCursor(event.timestamp, event.event_id);
|
|
157
|
+
if (event.cursor !== derived)
|
|
158
|
+
return { ok: false, derived, reason: "CURSOR_MISMATCH" };
|
|
159
|
+
if (!isUuidV7(event.event_id) || !isCanonicalLowerUuid(event.event_id)) {
|
|
160
|
+
return { ok: false, derived, reason: "INVALID_FRAME" };
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, derived };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/domain/logic/continuity.ts
|
|
166
|
+
function checkCursorContinuity(events, _expectedPredecessor) {
|
|
167
|
+
if (events.length <= 1) {
|
|
168
|
+
return { status: "CONTINUOUS", gaps: [] };
|
|
169
|
+
}
|
|
170
|
+
const gaps = [];
|
|
171
|
+
for (let i = 0; i < events.length - 1; i++) {
|
|
172
|
+
const current = events[i];
|
|
173
|
+
const next = events[i + 1];
|
|
174
|
+
const cmp = compareCursor(current.cursor, next.cursor);
|
|
175
|
+
if (cmp === 1) {
|
|
176
|
+
gaps.push({
|
|
177
|
+
from_cursor: current.cursor,
|
|
178
|
+
to_cursor: next.cursor,
|
|
179
|
+
detected_at: Date.now()
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (gaps.length > 0) {
|
|
184
|
+
return { status: "GAP_DETECTED", gaps };
|
|
185
|
+
}
|
|
186
|
+
return { status: "CONTINUOUS", gaps: [] };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/domain/logic/ordering.ts
|
|
190
|
+
function orderingCompare(a, b) {
|
|
191
|
+
if (a.timestamp > b.timestamp) return -1;
|
|
192
|
+
if (a.timestamp < b.timestamp) return 1;
|
|
193
|
+
if (a.event_id > b.event_id) return -1;
|
|
194
|
+
if (a.event_id < b.event_id) return 1;
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/domain/logic/redaction.ts
|
|
199
|
+
function redactEvent(event, level) {
|
|
200
|
+
if (level === "none") return event;
|
|
201
|
+
const copy = structuredClone(event);
|
|
202
|
+
if (level === "safe_default" || level === "strict") {
|
|
203
|
+
if (copy.request && typeof copy.request === "object") {
|
|
204
|
+
const req = copy.request;
|
|
205
|
+
if (req.headers) {
|
|
206
|
+
delete req.headers["authorization"];
|
|
207
|
+
delete req.headers["cookie"];
|
|
208
|
+
delete req.headers["x-api-key"];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (level === "strict") {
|
|
213
|
+
if (copy.input) {
|
|
214
|
+
copy.input = "[REDACTED]";
|
|
215
|
+
}
|
|
216
|
+
if (copy.request && typeof copy.request === "object") {
|
|
217
|
+
const req = copy.request;
|
|
218
|
+
req.headers = void 0;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return copy;
|
|
222
|
+
}
|
|
223
|
+
function redactGatewaySnapshot(snapshot) {
|
|
224
|
+
const copy = { ...snapshot };
|
|
225
|
+
delete copy.internal_endpoints;
|
|
226
|
+
delete copy.keys;
|
|
227
|
+
delete copy.tokens;
|
|
228
|
+
return copy;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/domain/logic/evidence-bundle.ts
|
|
232
|
+
function createEvidenceBundle(params) {
|
|
233
|
+
const level = params.redactionLevel ?? "safe_default";
|
|
234
|
+
const sortedEvents = [...params.events].sort(
|
|
235
|
+
(a, b) => compareCursor(a.cursor, b.cursor)
|
|
236
|
+
);
|
|
237
|
+
const redactedEvents = sortedEvents.map((e) => redactEvent(e, level));
|
|
238
|
+
const by_outcome = { OK: 0, DENY: 0, ERROR: 0 };
|
|
239
|
+
const by_denial_reason = {};
|
|
240
|
+
redactedEvents.forEach((e) => {
|
|
241
|
+
const outcome = e.outcome || "OK";
|
|
242
|
+
by_outcome[outcome] = (by_outcome[outcome] || 0) + 1;
|
|
243
|
+
if (typeof e.reason === "string") {
|
|
244
|
+
by_denial_reason[e.reason] = (by_denial_reason[e.reason] || 0) + 1;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const summary = {
|
|
248
|
+
total_events: redactedEvents.length,
|
|
249
|
+
by_outcome,
|
|
250
|
+
by_denial_reason,
|
|
251
|
+
cursor_continuity: "UNKNOWN"
|
|
252
|
+
// Continuity check is computationally expensive for large bundles
|
|
253
|
+
};
|
|
254
|
+
const redactedSnapshot = params.gatewaySnapshot ? redactGatewaySnapshot(params.gatewaySnapshot) : void 0;
|
|
255
|
+
return {
|
|
256
|
+
metadata: {
|
|
257
|
+
schema_version: "1.1.0",
|
|
258
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259
|
+
source: {
|
|
260
|
+
dashboard_version: params.dashboardVersion,
|
|
261
|
+
gateway_version: params.gatewaySnapshot?.version
|
|
262
|
+
},
|
|
263
|
+
filters_applied: params.filters,
|
|
264
|
+
cursor_range: params.cursorRange,
|
|
265
|
+
redaction_level: level
|
|
266
|
+
},
|
|
267
|
+
events: redactedEvents,
|
|
268
|
+
integrity_summary: summary,
|
|
269
|
+
gateway_snapshot: redactedSnapshot
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export {
|
|
273
|
+
Base64UrlError,
|
|
274
|
+
assertCursorInvariant,
|
|
275
|
+
base64urlDecodeToBytes,
|
|
276
|
+
base64urlDecodeToUtf8,
|
|
277
|
+
base64urlEncodeBytes,
|
|
278
|
+
base64urlEncodeUtf8,
|
|
279
|
+
checkCursorContinuity,
|
|
280
|
+
compareCursor,
|
|
281
|
+
createEvidenceBundle,
|
|
282
|
+
decodeCursor,
|
|
283
|
+
deriveCursor,
|
|
284
|
+
isCanonicalLowerUuid,
|
|
285
|
+
isUuidV7,
|
|
286
|
+
orderingCompare,
|
|
287
|
+
redactEvent,
|
|
288
|
+
redactGatewaySnapshot
|
|
289
|
+
};
|
|
290
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infrastructure/base64url.ts","../src/infrastructure/uuidv7.ts","../src/domain/logic/cursor.ts","../src/domain/logic/continuity.ts","../src/domain/logic/ordering.ts","../src/domain/logic/redaction.ts","../src/domain/logic/evidence-bundle.ts"],"sourcesContent":["// src/infrastructure/base64url.ts\n// Strict base64url encoding/decoding - NO btoa/atob, NO padding\n// Uses TextEncoder/TextDecoder for universal runtime support (Node, Browser, Edge)\n\nexport class Base64UrlError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"Base64UrlError\";\n }\n}\n\nconst ALPHABET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\";\nconst RE_VALID = /^[A-Za-z0-9\\-_]*$/;\n\nfunction toUtf8Bytes(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\nfunction fromUtf8Bytes(b: Uint8Array): string {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(b);\n}\n\nexport function base64urlEncodeBytes(bytes: Uint8Array): string {\n if (bytes.length === 0) return \"\";\n\n let out = \"\";\n let i = 0;\n\n while (i + 3 <= bytes.length) {\n const n = (bytes[i]! << 16) | (bytes[i + 1]! << 8) | bytes[i + 2]!;\n out += ALPHABET[(n >>> 18) & 63]!;\n out += ALPHABET[(n >>> 12) & 63]!;\n out += ALPHABET[(n >>> 6) & 63]!;\n out += ALPHABET[n & 63]!;\n i += 3;\n }\n\n const rem = bytes.length - i;\n if (rem === 1) {\n const n = bytes[i]!;\n out += ALPHABET[(n >>> 2) & 63]!;\n out += ALPHABET[(n << 4) & 63]!;\n // no padding\n } else if (rem === 2) {\n const n = (bytes[i]! << 8) | bytes[i + 1]!;\n out += ALPHABET[(n >>> 10) & 63]!;\n out += ALPHABET[(n >>> 4) & 63]!;\n out += ALPHABET[(n << 2) & 63]!;\n // no padding\n }\n\n return out;\n}\n\nfunction decodeChar(c: string): number {\n const idx = ALPHABET.indexOf(c);\n if (idx === -1) throw new Base64UrlError(`Invalid base64url character: ${c}`);\n return idx;\n}\n\nexport function base64urlDecodeToBytes(s: string): Uint8Array {\n if (s.length === 0) return new Uint8Array(0);\n\n // Reject padding and non-url alphabet\n if (s.includes(\"=\")) throw new Base64UrlError(\"Padding is not allowed\");\n if (!RE_VALID.test(s))\n throw new Base64UrlError(\"Non-base64url characters present\");\n // length mod 4 == 1 is impossible for canonical base64url\n if (s.length % 4 === 1) throw new Base64UrlError(\"Invalid base64url length\");\n\n const out: number[] = [];\n let i = 0;\n\n while (i < s.length) {\n const remain = s.length - i;\n\n if (remain >= 4) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!),\n c = decodeChar(s[i + 2]!),\n d = decodeChar(s[i + 3]!);\n const n = (a << 18) | (b << 12) | (c << 6) | d;\n out.push((n >>> 16) & 255, (n >>> 8) & 255, n & 255);\n i += 4;\n continue;\n }\n\n if (remain === 2) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!);\n const n = (a << 18) | (b << 12);\n out.push((n >>> 16) & 255);\n i += 2;\n continue;\n }\n\n if (remain === 3) {\n const a = decodeChar(s[i]!),\n b = decodeChar(s[i + 1]!),\n c = decodeChar(s[i + 2]!);\n const n = (a << 18) | (b << 12) | (c << 6);\n out.push((n >>> 16) & 255, (n >>> 8) & 255);\n i += 3;\n continue;\n }\n\n throw new Base64UrlError(\"Invalid base64url length\");\n }\n\n const decoded = new Uint8Array(out);\n // Strict canonical check: re-encode must match exactly\n const recoded = base64urlEncodeBytes(decoded);\n if (recoded !== s) throw new Base64UrlError(\"Non-canonical base64url form\");\n return decoded;\n}\n\nexport function base64urlEncodeUtf8(s: string): string {\n return base64urlEncodeBytes(toUtf8Bytes(s));\n}\n\nexport function base64urlDecodeToUtf8(s: string): string {\n return fromUtf8Bytes(base64urlDecodeToBytes(s));\n}\n","// src/infrastructure/uuidv7.ts\n// Strict UUIDv7 validation - pure regex, no external dependencies\n\nconst RE_UUID =\n /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n\nexport function isUuidV7(id: string): boolean {\n if (!RE_UUID.test(id)) return false;\n // version nibble is first nibble of 3rd group (position 14)\n const version = id[14]!;\n if (version !== \"7\") return false;\n\n // variant is first nibble of 4th group (position 19): 8, 9, a, b\n const variant = id[19]!.toLowerCase();\n return (\n variant === \"8\" || variant === \"9\" || variant === \"a\" || variant === \"b\"\n );\n}\n\nexport function isCanonicalLowerUuid(id: string): boolean {\n return id === id.toLowerCase();\n}\n","// src/domain/logic/cursor.ts\n// Cursor derivation, validation, and comparison\n// D4=B: invalid frames do not throw from validation, return INVALID_FRAME\n\nimport {\n base64urlDecodeToUtf8,\n base64urlEncodeUtf8,\n} from \"../../infrastructure/base64url.js\";\nimport { isUuidV7, isCanonicalLowerUuid } from \"../../infrastructure/uuidv7.js\";\nimport type {\n CursorValidationResult,\n DecodedCursor,\n} from \"../types/cursor.types.js\";\n\nfunction isValidUnixSecondsInt(n: unknown): n is number {\n return (\n typeof n === \"number\" &&\n Number.isInteger(n) &&\n n >= 0 &&\n Number.isSafeInteger(n)\n );\n}\n\nfunction isCanonicalTimestampString(s: string): boolean {\n if (!/^\\d+$/.test(s)) return false;\n if (s.length > 1 && s.startsWith(\"0\")) return false;\n return true;\n}\n\n/**\n * Derive a cursor from timestamp and event_id.\n * cursor = base64url(utf8(\"{timestamp}:{event_id}\"))\n */\nexport function deriveCursor(timestamp: number, eventId: string): string {\n if (!isValidUnixSecondsInt(timestamp)) {\n throw new Error(\"timestamp must be unix seconds integer\");\n }\n const plain = `${String(timestamp)}:${eventId}`;\n return base64urlEncodeUtf8(plain);\n}\n\n/**\n * Decode a cursor to {timestamp, event_id}.\n * Throws on invalid cursor format.\n */\nexport function decodeCursor(cursor: string): DecodedCursor {\n const raw = base64urlDecodeToUtf8(cursor);\n const colonIndex = raw.indexOf(\":\");\n if (colonIndex === -1)\n throw new Error(\"cursor frame must be '{timestamp}:{event_id}'\");\n\n const tsStr = raw.slice(0, colonIndex);\n const eventId = raw.slice(colonIndex + 1);\n\n if (!isCanonicalTimestampString(tsStr))\n throw new Error(\"timestamp is not canonical base-10\");\n const tsNum = Number(tsStr);\n if (!isValidUnixSecondsInt(tsNum))\n throw new Error(\"timestamp is out of range\");\n\n if (!isUuidV7(eventId)) throw new Error(\"event_id is not uuidv7\");\n if (!isCanonicalLowerUuid(eventId))\n throw new Error(\"event_id must be lowercase canonical uuid\");\n\n return { timestamp: tsNum, event_id: eventId };\n}\n\n/**\n * Compare two cursors.\n * Returns -1 if a < b, 0 if equal, 1 if a > b.\n * Compares by (timestamp, event_id) lexicographically.\n */\nexport function compareCursor(a: string, b: string): -1 | 0 | 1 {\n const da = decodeCursor(a);\n const db = decodeCursor(b);\n\n if (da.timestamp < db.timestamp) return -1;\n if (da.timestamp > db.timestamp) return 1;\n\n if (da.event_id < db.event_id) return -1;\n if (da.event_id > db.event_id) return 1;\n return 0;\n}\n\n/**\n * Validate that an event's cursor matches the derived cursor.\n * D4=B: Does not throw on invalid frames, returns { ok: false, reason: \"INVALID_FRAME\" }\n */\nexport function assertCursorInvariant(event: {\n timestamp: unknown;\n event_id: unknown;\n cursor: unknown;\n}): CursorValidationResult {\n // Frame checks first (D4=B: do not throw)\n if (\n !isValidUnixSecondsInt(event.timestamp) ||\n typeof event.event_id !== \"string\" ||\n typeof event.cursor !== \"string\"\n ) {\n return { ok: false, derived: \"\", reason: \"INVALID_FRAME\" };\n }\n\n // Decode cursor must be strict base64url + canonical frame. If it fails, INVALID_FRAME.\n try {\n decodeCursor(event.cursor);\n } catch {\n const derived = deriveCursor(event.timestamp, event.event_id);\n return { ok: false, derived, reason: \"INVALID_FRAME\" };\n }\n\n // Derived cursor must match exactly (CURSOR_MISMATCH)\n const derived = deriveCursor(event.timestamp, event.event_id);\n if (event.cursor !== derived)\n return { ok: false, derived, reason: \"CURSOR_MISMATCH\" };\n\n // Also enforce event_id canonical uuidv7 for the event frame\n if (!isUuidV7(event.event_id) || !isCanonicalLowerUuid(event.event_id)) {\n return { ok: false, derived, reason: \"INVALID_FRAME\" };\n }\n\n return { ok: true, derived };\n}\n","// src/domain/logic/continuity.ts\n// Cursor continuity checking\n\nimport { compareCursor } from \"./cursor.js\";\nimport type {\n CursorGap,\n ContinuityCheckResult,\n} from \"../types/bundle.types.js\";\n\n/**\n * Check cursor continuity based on contract-defined rules.\n * Gap detection requires cursor predecessor relation, not heuristics.\n * For v1.1, we verify strict ordering.\n */\nexport function checkCursorContinuity(\n events: Array<{ cursor: string; timestamp: number }>,\n _expectedPredecessor?: (cursor: string) => string | null,\n): ContinuityCheckResult {\n if (events.length <= 1) {\n return { status: \"CONTINUOUS\", gaps: [] };\n }\n\n const gaps: CursorGap[] = [];\n\n // Verify ordering\n for (let i = 0; i < events.length - 1; i++) {\n const current = events[i];\n const next = events[i + 1];\n\n // If next cursor is Lexicographically smaller than current, strict ordering violation\n const cmp = compareCursor(current.cursor, next.cursor);\n if (cmp === 1) {\n // Order violation isn't exactly a \"gap\" but a \"discontinuity\"\n // For v1.1, we treat this as a Gap for the sake of the interface\n gaps.push({\n from_cursor: current.cursor,\n to_cursor: next.cursor,\n detected_at: Date.now(),\n });\n }\n }\n\n // FUTURE(v1.2): Implement actual rigorous predecessor check using Merkle links if available\n // For now, we only check monotonic ordering.\n\n if (gaps.length > 0) {\n return { status: \"GAP_DETECTED\", gaps };\n }\n\n return { status: \"CONTINUOUS\", gaps: [] };\n}\n","// src/domain/logic/ordering.ts\n// Event ordering comparison: (timestamp DESC, event_id DESC)\n\n/**\n * Compare two events for ordering.\n * Returns -1 if a should come before b (a is \"greater\" in DESC order)\n * Returns 0 if equal\n * Returns 1 if a should come after b\n *\n * Ordering: timestamp DESC, then event_id DESC\n */\nexport function orderingCompare(\n a: { timestamp: number; event_id: string },\n b: { timestamp: number; event_id: string },\n): -1 | 0 | 1 {\n // DESC timestamp: higher timestamp comes first\n if (a.timestamp > b.timestamp) return -1;\n if (a.timestamp < b.timestamp) return 1;\n\n // DESC event_id: higher event_id comes first (lexicographic)\n if (a.event_id > b.event_id) return -1;\n if (a.event_id < b.event_id) return 1;\n return 0;\n}\n","// src/domain/logic/redaction.ts\n// Event and gateway snapshot redaction\n\nimport type {\n AuditEvent,\n GatewayStatus,\n RedactionLevel,\n} from \"../types/event.types.js\";\n\nexport function redactEvent(\n event: AuditEvent,\n level: RedactionLevel,\n): AuditEvent {\n if (level === \"none\") return event;\n\n const copy = structuredClone(event); // Deep copy\n\n // Common redactions for safe_default\n if (level === \"safe_default\" || level === \"strict\") {\n if (copy.request && typeof copy.request === \"object\") {\n const req = copy.request as {\n headers?: Record<string, string>;\n [key: string]: unknown;\n };\n if (req.headers) {\n // Strip auth headers\n delete req.headers[\"authorization\"];\n delete req.headers[\"cookie\"];\n delete req.headers[\"x-api-key\"];\n }\n }\n }\n\n if (level === \"strict\") {\n // Strict redaction - strip inputs and all headers\n if (copy.input) {\n copy.input = \"[REDACTED]\";\n }\n if (copy.request && typeof copy.request === \"object\") {\n const req = copy.request as {\n headers?: Record<string, string>;\n [key: string]: unknown;\n };\n req.headers = undefined; // Remove all headers\n }\n }\n\n return copy;\n}\n\nexport function redactGatewaySnapshot(\n snapshot: GatewayStatus,\n): Partial<GatewayStatus> {\n const copy = { ...snapshot };\n\n // Always strip these\n delete copy.internal_endpoints;\n delete copy.keys;\n delete copy.tokens;\n\n return copy;\n}\n","// src/domain/logic/evidence-bundle.ts\n// Evidence bundle creation - pure deterministic transformation\n\nimport { compareCursor } from \"./cursor.js\";\nimport { redactEvent, redactGatewaySnapshot } from \"./redaction.js\";\nimport type {\n AuditEvent,\n GatewayStatus,\n RedactionLevel,\n AuditFilters,\n Outcome,\n} from \"../types/event.types.js\";\nimport type {\n EvidenceBundle,\n IntegritySummary,\n} from \"../types/bundle.types.js\";\n\nexport interface CreateEvidenceBundleParams {\n events: AuditEvent[];\n redactionLevel?: RedactionLevel;\n gatewaySnapshot?: GatewayStatus;\n filters?: AuditFilters;\n cursorRange?: { start?: string; end?: string };\n dashboardVersion: string;\n}\n\nexport function createEvidenceBundle(\n params: CreateEvidenceBundleParams,\n): EvidenceBundle {\n const level = params.redactionLevel ?? \"safe_default\";\n\n // 1. Sort events by cursor (canonical order)\n const sortedEvents = [...params.events].sort((a, b) =>\n compareCursor(a.cursor, b.cursor),\n );\n\n // 2. Apply redaction based on level\n const redactedEvents = sortedEvents.map((e) => redactEvent(e, level));\n\n // 3. Compute integrity summary\n const by_outcome: Record<Outcome, number> = { OK: 0, DENY: 0, ERROR: 0 };\n const by_denial_reason: Record<string, number> = {};\n\n redactedEvents.forEach((e) => {\n const outcome = (e.outcome as Outcome) || \"OK\";\n by_outcome[outcome] = (by_outcome[outcome] || 0) + 1;\n\n if (typeof e.reason === \"string\") {\n by_denial_reason[e.reason] = (by_denial_reason[e.reason] || 0) + 1;\n }\n });\n\n const summary: IntegritySummary = {\n total_events: redactedEvents.length,\n by_outcome,\n by_denial_reason,\n cursor_continuity: \"UNKNOWN\", // Continuity check is computationally expensive for large bundles\n };\n\n // 4. Strip secrets from gateway snapshot\n const redactedSnapshot = params.gatewaySnapshot\n ? redactGatewaySnapshot(params.gatewaySnapshot)\n : undefined;\n\n return {\n metadata: {\n schema_version: \"1.1.0\",\n generated_at: new Date().toISOString(),\n source: {\n dashboard_version: params.dashboardVersion,\n gateway_version: params.gatewaySnapshot?.version,\n },\n filters_applied: params.filters,\n cursor_range: params.cursorRange,\n redaction_level: level,\n },\n events: redactedEvents,\n integrity_summary: summary,\n gateway_snapshot: redactedSnapshot,\n };\n}\n"],"mappings":";AAIO,IAAM,iBAAN,cAA6B,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAM,WACJ;AACF,IAAM,WAAW;AAEjB,SAAS,YAAY,GAAuB;AAC1C,SAAO,IAAI,YAAY,EAAE,OAAO,CAAC;AACnC;AAEA,SAAS,cAAc,GAAuB;AAC5C,SAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC;AAC3D;AAEO,SAAS,qBAAqB,OAA2B;AAC9D,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,MAAI,MAAM;AACV,MAAI,IAAI;AAER,SAAO,IAAI,KAAK,MAAM,QAAQ;AAC5B,UAAM,IAAK,MAAM,CAAC,KAAM,KAAO,MAAM,IAAI,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AAChE,WAAO,SAAU,MAAM,KAAM,EAAE;AAC/B,WAAO,SAAU,MAAM,KAAM,EAAE;AAC/B,WAAO,SAAU,MAAM,IAAK,EAAE;AAC9B,WAAO,SAAS,IAAI,EAAE;AACtB,SAAK;AAAA,EACP;AAEA,QAAM,MAAM,MAAM,SAAS;AAC3B,MAAI,QAAQ,GAAG;AACb,UAAM,IAAI,MAAM,CAAC;AACjB,WAAO,SAAU,MAAM,IAAK,EAAE;AAC9B,WAAO,SAAU,KAAK,IAAK,EAAE;AAAA,EAE/B,WAAW,QAAQ,GAAG;AACpB,UAAM,IAAK,MAAM,CAAC,KAAM,IAAK,MAAM,IAAI,CAAC;AACxC,WAAO,SAAU,MAAM,KAAM,EAAE;AAC/B,WAAO,SAAU,MAAM,IAAK,EAAE;AAC9B,WAAO,SAAU,KAAK,IAAK,EAAE;AAAA,EAE/B;AAEA,SAAO;AACT;AAEA,SAAS,WAAW,GAAmB;AACrC,QAAM,MAAM,SAAS,QAAQ,CAAC;AAC9B,MAAI,QAAQ,GAAI,OAAM,IAAI,eAAe,gCAAgC,CAAC,EAAE;AAC5E,SAAO;AACT;AAEO,SAAS,uBAAuB,GAAuB;AAC5D,MAAI,EAAE,WAAW,EAAG,QAAO,IAAI,WAAW,CAAC;AAG3C,MAAI,EAAE,SAAS,GAAG,EAAG,OAAM,IAAI,eAAe,wBAAwB;AACtE,MAAI,CAAC,SAAS,KAAK,CAAC;AAClB,UAAM,IAAI,eAAe,kCAAkC;AAE7D,MAAI,EAAE,SAAS,MAAM,EAAG,OAAM,IAAI,eAAe,0BAA0B;AAE3E,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI;AAER,SAAO,IAAI,EAAE,QAAQ;AACnB,UAAM,SAAS,EAAE,SAAS;AAE1B,QAAI,UAAU,GAAG;AACf,YAAM,IAAI,WAAW,EAAE,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE;AAC1B,YAAM,IAAK,KAAK,KAAO,KAAK,KAAO,KAAK,IAAK;AAC7C,UAAI,KAAM,MAAM,KAAM,KAAM,MAAM,IAAK,KAAK,IAAI,GAAG;AACnD,WAAK;AACL;AAAA,IACF;AAEA,QAAI,WAAW,GAAG;AAChB,YAAM,IAAI,WAAW,EAAE,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE;AAC1B,YAAM,IAAK,KAAK,KAAO,KAAK;AAC5B,UAAI,KAAM,MAAM,KAAM,GAAG;AACzB,WAAK;AACL;AAAA,IACF;AAEA,QAAI,WAAW,GAAG;AAChB,YAAM,IAAI,WAAW,EAAE,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE,GACxB,IAAI,WAAW,EAAE,IAAI,CAAC,CAAE;AAC1B,YAAM,IAAK,KAAK,KAAO,KAAK,KAAO,KAAK;AACxC,UAAI,KAAM,MAAM,KAAM,KAAM,MAAM,IAAK,GAAG;AAC1C,WAAK;AACL;AAAA,IACF;AAEA,UAAM,IAAI,eAAe,0BAA0B;AAAA,EACrD;AAEA,QAAM,UAAU,IAAI,WAAW,GAAG;AAElC,QAAM,UAAU,qBAAqB,OAAO;AAC5C,MAAI,YAAY,EAAG,OAAM,IAAI,eAAe,8BAA8B;AAC1E,SAAO;AACT;AAEO,SAAS,oBAAoB,GAAmB;AACrD,SAAO,qBAAqB,YAAY,CAAC,CAAC;AAC5C;AAEO,SAAS,sBAAsB,GAAmB;AACvD,SAAO,cAAc,uBAAuB,CAAC,CAAC;AAChD;;;ACxHA,IAAM,UACJ;AAEK,SAAS,SAAS,IAAqB;AAC5C,MAAI,CAAC,QAAQ,KAAK,EAAE,EAAG,QAAO;AAE9B,QAAM,UAAU,GAAG,EAAE;AACrB,MAAI,YAAY,IAAK,QAAO;AAG5B,QAAM,UAAU,GAAG,EAAE,EAAG,YAAY;AACpC,SACE,YAAY,OAAO,YAAY,OAAO,YAAY,OAAO,YAAY;AAEzE;AAEO,SAAS,qBAAqB,IAAqB;AACxD,SAAO,OAAO,GAAG,YAAY;AAC/B;;;ACPA,SAAS,sBAAsB,GAAyB;AACtD,SACE,OAAO,MAAM,YACb,OAAO,UAAU,CAAC,KAClB,KAAK,KACL,OAAO,cAAc,CAAC;AAE1B;AAEA,SAAS,2BAA2B,GAAoB;AACtD,MAAI,CAAC,QAAQ,KAAK,CAAC,EAAG,QAAO;AAC7B,MAAI,EAAE,SAAS,KAAK,EAAE,WAAW,GAAG,EAAG,QAAO;AAC9C,SAAO;AACT;AAMO,SAAS,aAAa,WAAmB,SAAyB;AACvE,MAAI,CAAC,sBAAsB,SAAS,GAAG;AACrC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACA,QAAM,QAAQ,GAAG,OAAO,SAAS,CAAC,IAAI,OAAO;AAC7C,SAAO,oBAAoB,KAAK;AAClC;AAMO,SAAS,aAAa,QAA+B;AAC1D,QAAM,MAAM,sBAAsB,MAAM;AACxC,QAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,MAAI,eAAe;AACjB,UAAM,IAAI,MAAM,+CAA+C;AAEjE,QAAM,QAAQ,IAAI,MAAM,GAAG,UAAU;AACrC,QAAM,UAAU,IAAI,MAAM,aAAa,CAAC;AAExC,MAAI,CAAC,2BAA2B,KAAK;AACnC,UAAM,IAAI,MAAM,oCAAoC;AACtD,QAAM,QAAQ,OAAO,KAAK;AAC1B,MAAI,CAAC,sBAAsB,KAAK;AAC9B,UAAM,IAAI,MAAM,2BAA2B;AAE7C,MAAI,CAAC,SAAS,OAAO,EAAG,OAAM,IAAI,MAAM,wBAAwB;AAChE,MAAI,CAAC,qBAAqB,OAAO;AAC/B,UAAM,IAAI,MAAM,2CAA2C;AAE7D,SAAO,EAAE,WAAW,OAAO,UAAU,QAAQ;AAC/C;AAOO,SAAS,cAAc,GAAW,GAAuB;AAC9D,QAAM,KAAK,aAAa,CAAC;AACzB,QAAM,KAAK,aAAa,CAAC;AAEzB,MAAI,GAAG,YAAY,GAAG,UAAW,QAAO;AACxC,MAAI,GAAG,YAAY,GAAG,UAAW,QAAO;AAExC,MAAI,GAAG,WAAW,GAAG,SAAU,QAAO;AACtC,MAAI,GAAG,WAAW,GAAG,SAAU,QAAO;AACtC,SAAO;AACT;AAMO,SAAS,sBAAsB,OAIX;AAEzB,MACE,CAAC,sBAAsB,MAAM,SAAS,KACtC,OAAO,MAAM,aAAa,YAC1B,OAAO,MAAM,WAAW,UACxB;AACA,WAAO,EAAE,IAAI,OAAO,SAAS,IAAI,QAAQ,gBAAgB;AAAA,EAC3D;AAGA,MAAI;AACF,iBAAa,MAAM,MAAM;AAAA,EAC3B,QAAQ;AACN,UAAMA,WAAU,aAAa,MAAM,WAAW,MAAM,QAAQ;AAC5D,WAAO,EAAE,IAAI,OAAO,SAAAA,UAAS,QAAQ,gBAAgB;AAAA,EACvD;AAGA,QAAM,UAAU,aAAa,MAAM,WAAW,MAAM,QAAQ;AAC5D,MAAI,MAAM,WAAW;AACnB,WAAO,EAAE,IAAI,OAAO,SAAS,QAAQ,kBAAkB;AAGzD,MAAI,CAAC,SAAS,MAAM,QAAQ,KAAK,CAAC,qBAAqB,MAAM,QAAQ,GAAG;AACtE,WAAO,EAAE,IAAI,OAAO,SAAS,QAAQ,gBAAgB;AAAA,EACvD;AAEA,SAAO,EAAE,IAAI,MAAM,QAAQ;AAC7B;;;AC3GO,SAAS,sBACd,QACA,sBACuB;AACvB,MAAI,OAAO,UAAU,GAAG;AACtB,WAAO,EAAE,QAAQ,cAAc,MAAM,CAAC,EAAE;AAAA,EAC1C;AAEA,QAAM,OAAoB,CAAC;AAG3B,WAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,UAAM,UAAU,OAAO,CAAC;AACxB,UAAM,OAAO,OAAO,IAAI,CAAC;AAGzB,UAAM,MAAM,cAAc,QAAQ,QAAQ,KAAK,MAAM;AACrD,QAAI,QAAQ,GAAG;AAGb,WAAK,KAAK;AAAA,QACR,aAAa,QAAQ;AAAA,QACrB,WAAW,KAAK;AAAA,QAChB,aAAa,KAAK,IAAI;AAAA,MACxB,CAAC;AAAA,IACH;AAAA,EACF;AAKA,MAAI,KAAK,SAAS,GAAG;AACnB,WAAO,EAAE,QAAQ,gBAAgB,KAAK;AAAA,EACxC;AAEA,SAAO,EAAE,QAAQ,cAAc,MAAM,CAAC,EAAE;AAC1C;;;ACvCO,SAAS,gBACd,GACA,GACY;AAEZ,MAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AACtC,MAAI,EAAE,YAAY,EAAE,UAAW,QAAO;AAGtC,MAAI,EAAE,WAAW,EAAE,SAAU,QAAO;AACpC,MAAI,EAAE,WAAW,EAAE,SAAU,QAAO;AACpC,SAAO;AACT;;;ACdO,SAAS,YACd,OACA,OACY;AACZ,MAAI,UAAU,OAAQ,QAAO;AAE7B,QAAM,OAAO,gBAAgB,KAAK;AAGlC,MAAI,UAAU,kBAAkB,UAAU,UAAU;AAClD,QAAI,KAAK,WAAW,OAAO,KAAK,YAAY,UAAU;AACpD,YAAM,MAAM,KAAK;AAIjB,UAAI,IAAI,SAAS;AAEf,eAAO,IAAI,QAAQ,eAAe;AAClC,eAAO,IAAI,QAAQ,QAAQ;AAC3B,eAAO,IAAI,QAAQ,WAAW;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,UAAU;AAEtB,QAAI,KAAK,OAAO;AACd,WAAK,QAAQ;AAAA,IACf;AACA,QAAI,KAAK,WAAW,OAAO,KAAK,YAAY,UAAU;AACpD,YAAM,MAAM,KAAK;AAIjB,UAAI,UAAU;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,sBACd,UACwB;AACxB,QAAM,OAAO,EAAE,GAAG,SAAS;AAG3B,SAAO,KAAK;AACZ,SAAO,KAAK;AACZ,SAAO,KAAK;AAEZ,SAAO;AACT;;;ACnCO,SAAS,qBACd,QACgB;AAChB,QAAM,QAAQ,OAAO,kBAAkB;AAGvC,QAAM,eAAe,CAAC,GAAG,OAAO,MAAM,EAAE;AAAA,IAAK,CAAC,GAAG,MAC/C,cAAc,EAAE,QAAQ,EAAE,MAAM;AAAA,EAClC;AAGA,QAAM,iBAAiB,aAAa,IAAI,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC;AAGpE,QAAM,aAAsC,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,EAAE;AACvE,QAAM,mBAA2C,CAAC;AAElD,iBAAe,QAAQ,CAAC,MAAM;AAC5B,UAAM,UAAW,EAAE,WAAuB;AAC1C,eAAW,OAAO,KAAK,WAAW,OAAO,KAAK,KAAK;AAEnD,QAAI,OAAO,EAAE,WAAW,UAAU;AAChC,uBAAiB,EAAE,MAAM,KAAK,iBAAiB,EAAE,MAAM,KAAK,KAAK;AAAA,IACnE;AAAA,EACF,CAAC;AAED,QAAM,UAA4B;AAAA,IAChC,cAAc,eAAe;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA;AAAA,EACrB;AAGA,QAAM,mBAAmB,OAAO,kBAC5B,sBAAsB,OAAO,eAAe,IAC5C;AAEJ,SAAO;AAAA,IACL,UAAU;AAAA,MACR,gBAAgB;AAAA,MAChB,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC,QAAQ;AAAA,QACN,mBAAmB,OAAO;AAAA,QAC1B,iBAAiB,OAAO,iBAAiB;AAAA,MAC3C;AAAA,MACA,iBAAiB,OAAO;AAAA,MACxB,cAAc,OAAO;AAAA,MACrB,iBAAiB;AAAA,IACnB;AAAA,IACA,QAAQ;AAAA,IACR,mBAAmB;AAAA,IACnB,kBAAkB;AAAA,EACpB;AACF;","names":["derived"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@talosprotocol/contracts",
|
|
3
|
+
"version": "1.1.5",
|
|
4
|
+
"description": "Talos Protocol contracts - schemas, types, and helper functions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./*": null
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
|
|
22
|
+
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
|
|
23
|
+
"format": "prettier -w .",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"validate": "npm run lint && npm run typecheck && npm run build && npm run test",
|
|
26
|
+
"version:sync": "node ./scripts/version-sync.mjs",
|
|
27
|
+
"gates:contracts": "echo 'Skipping gates for contracts repo'",
|
|
28
|
+
"api:snapshot": "npm run build && cp dist/index.d.ts test/fixtures/api-baseline.d.ts",
|
|
29
|
+
"smoke:pack": "npm pack --dry-run && node scripts/smoke-test.mjs"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"talos",
|
|
38
|
+
"protocol",
|
|
39
|
+
"contracts",
|
|
40
|
+
"mcp",
|
|
41
|
+
"security"
|
|
42
|
+
],
|
|
43
|
+
"author": "Talos Protocol",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/talosprotocol/talos-contracts.git"
|
|
51
|
+
},
|
|
52
|
+
"lint-staged": {
|
|
53
|
+
"*.{ts,tsx,js,jsx}": [
|
|
54
|
+
"eslint --fix",
|
|
55
|
+
"prettier -w"
|
|
56
|
+
],
|
|
57
|
+
"*.{json,md,yml,yaml}": [
|
|
58
|
+
"prettier -w"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^20.19.27",
|
|
63
|
+
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
64
|
+
"@typescript-eslint/parser": "^8.51.0",
|
|
65
|
+
"eslint": "^8.57.1",
|
|
66
|
+
"eslint-config-prettier": "^10.1.8",
|
|
67
|
+
"husky": "^9.1.7",
|
|
68
|
+
"lint-staged": "^15.5.2",
|
|
69
|
+
"prettier": "^3.7.4",
|
|
70
|
+
"tsup": "^8.0.1",
|
|
71
|
+
"typescript": "^5.3.0",
|
|
72
|
+
"vitest": "^1.0.0"
|
|
73
|
+
}
|
|
74
|
+
}
|