@fabricorg/platform 1.10.1 → 1.11.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 +20 -0
- package/README.md +46 -6
- package/dist/{engine-B4csEOEc.js → engine-CG-OKfsX.js} +12 -8
- package/dist/engine-CG-OKfsX.js.map +1 -0
- package/dist/{engine-BqScuhrT.cjs → engine-T6yBlzOE.cjs} +11 -7
- package/dist/engine-T6yBlzOE.cjs.map +1 -0
- package/dist/evidence/index.cjs +1 -1
- package/dist/evidence/index.d.cts +1 -1
- package/dist/evidence/index.d.ts +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/{evidence-BuCICb0c.js → evidence-Bf8dz-BP.js} +21 -10
- package/dist/evidence-Bf8dz-BP.js.map +1 -0
- package/dist/{evidence-Cq48eDvA.cjs → evidence-DPOImHJB.cjs} +21 -10
- package/dist/evidence-DPOImHJB.cjs.map +1 -0
- package/dist/governance/index.d.cts +1 -1
- package/dist/governance/index.d.ts +1 -1
- package/dist/{index-BnbsKZsT.d.cts → index-BIZIxaxe.d.cts} +16 -5
- package/dist/{index-BXpeFdk2.d.cts → index-Bc7UEt3o.d.cts} +9 -6
- package/dist/{index-BXpeFdk2.d.ts → index-Bc7UEt3o.d.ts} +9 -6
- package/dist/{index-z9-CYOIo.d.ts → index-BdW9UuCw.d.ts} +16 -5
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/privacy/index.cjs +9 -9
- package/dist/privacy/index.cjs.map +1 -1
- package/dist/privacy/index.d.cts +2 -2
- package/dist/privacy/index.d.ts +2 -2
- package/dist/privacy/index.js +9 -9
- package/dist/privacy/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/engine-B4csEOEc.js.map +0 -1
- package/dist/engine-BqScuhrT.cjs.map +0 -1
- package/dist/evidence-BuCICb0c.js.map +0 -1
- package/dist/evidence-Cq48eDvA.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @fabricorg/platform
|
|
2
2
|
|
|
3
|
+
## 1.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a297fcd: Add optional `beforeChecksumKey` to AnonymizationEngine, the privacy wrappers, and evidence generation (issue #104). A supplied secret key produces HMAC-SHA256 field before-checksums; the default unkeyed SHA-256 output is unchanged.
|
|
8
|
+
|
|
9
|
+
Security note: an unkeyed checksum of a low-entropy value is reversible by enumeration, including after nullify or drop. Pass a secret key from application secret storage and retain it while checksum verification is needed. Keyed verification requires the key. Hash, tokenize, FieldRedactor and packet-level checksum algorithms are unchanged.
|
|
10
|
+
|
|
11
|
+
- 0a93df2: Add optional access-event reads to evidence packets (#101), so who-accessed records can live inside the redacted, checksummed payload. Without a reader the section is absent, leaving existing packets and checksums unchanged; an empty reader result includes an empty section. Schema version remains 1.
|
|
12
|
+
|
|
13
|
+
The new `access_event` reference type is an additive union member. Consumers with an exhaustive switch over `referenceType` must handle it. Reference metadata includes only string resource identifiers (`resourceType` and `resourceId`).
|
|
14
|
+
|
|
15
|
+
## 1.10.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- d69dd7a: README: recommend Node 22.23.4, 24.22.1, or 26.12.1 or newer in the same release line for the
|
|
20
|
+
Brotli decoder fix in CVE-2026-111497, and note that Node 20 is end-of-life with no fixed release.
|
|
21
|
+
The `engines` range is unchanged (`>=20`).
|
|
22
|
+
|
|
3
23
|
## 1.10.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -319,11 +319,12 @@ import {
|
|
|
319
319
|
|
|
320
320
|
const port: EvidencePacketReadPort = {
|
|
321
321
|
listEvents: ({ tenantId, spaceId, subjects }) => store.eventsFor(tenantId, spaceId, subjects),
|
|
322
|
-
// Optional
|
|
322
|
+
// Optional readers below; access events are omitted when their reader is absent.
|
|
323
323
|
resolveRelatedSubjects: (subject, scope) => store.relatedSubjects(subject, scope),
|
|
324
324
|
listActionInvocations: (scope, ids) => store.actionInvocations(scope, ids),
|
|
325
325
|
listPolicyEvaluations: (scope, invocationIds) => store.policyEvaluations(scope, invocationIds),
|
|
326
326
|
listAdapterInvocations: (scope, invocationIds) => store.adapterInvocations(scope, invocationIds),
|
|
327
|
+
listAccessEvents: (scope, subjects) => store.accessEvents(scope, subjects),
|
|
327
328
|
};
|
|
328
329
|
|
|
329
330
|
const packet = await generateEvidencePacket(
|
|
@@ -338,8 +339,20 @@ await store.savePacket(packet);
|
|
|
338
339
|
verifyEvidencePacketChecksum(packet); // true until the payload is altered
|
|
339
340
|
```
|
|
340
341
|
|
|
342
|
+
The optional `listAccessEvents(scope, subjects)` reader receives the resolved subjects (primary
|
|
343
|
+
first, then related), even when there are no events or invocation ids. The generator omits
|
|
344
|
+
`payload.accessEvents` when the reader is absent, preserving existing payloads and checksums;
|
|
345
|
+
a reader returning `[]` includes `accessEvents: []`. The port selects the relevant access records;
|
|
346
|
+
the generator does not filter them by subject. Records require `id`, `tenantId` and `spaceId`
|
|
347
|
+
and pass through the same scope checks, JSON normalization, de-duplication, id sorting and
|
|
348
|
+
redaction as other sections. References use `access_event` with only string `resourceType`
|
|
349
|
+
and `resourceId` metadata from the redacted record. Schema version remains 1.
|
|
350
|
+
|
|
341
351
|
What the tests in `evidence/generator.test.ts` cover:
|
|
342
352
|
|
|
353
|
+
- **Access events.** Tests cover absent versus empty sections, the pinned legacy checksum,
|
|
354
|
+
copied scope and subjects, scope rejection, sorting, duplicate conflicts, redaction and
|
|
355
|
+
identifier/section preservation.
|
|
343
356
|
- **Scope fails closed.** A record from the port whose `tenantId` or `spaceId` differs from the context
|
|
344
357
|
throws `EvidencePacketGenerationError` with code `EVIDENCE_SCOPE_MISMATCH`, in every section. The
|
|
345
358
|
check runs on the record as returned and again after normalization. A dependent record for an action
|
|
@@ -399,10 +412,15 @@ What the tests in `evidence/generator.test.ts` cover:
|
|
|
399
412
|
stored as a key (`{ byEmail: { "jane@example.com": 1 } }`) is not redacted.
|
|
400
413
|
- Envelope fields copied from the context (`generatedBy`, `correlationId`, `causationId`) and from
|
|
401
414
|
the input (`policyVersion`) are not redacted.
|
|
402
|
-
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
415
|
+
- Supply `anonymization.beforeChecksumKey` (a non-empty string or `Uint8Array`) to use
|
|
416
|
+
HMAC-SHA256 for field `beforeChecksum` values. Without a key, the default remains unkeyed
|
|
417
|
+
SHA-256 of `JSON.stringify(value)`: low-entropy values can be recovered by enumeration,
|
|
418
|
+
including with `nullify` or `drop`. Supply a secret key from the application's secret store
|
|
419
|
+
and keep it stable while any party needs to verify checksums; keyed verification requires
|
|
420
|
+
the key. The generator passes it only to the engine, outside the packet context.
|
|
421
|
+
Existing `hash` output remains unsalted SHA-256, and the tenant, space, field path and policy
|
|
422
|
+
version used by `tokenize` are in the packet. Those outputs still permit candidate guessing;
|
|
423
|
+
use opaque references where appropriate.
|
|
406
424
|
- **Checksum.** `checksum` is the hex SHA-256 of `canonicalJson(payload)`, recorded with
|
|
407
425
|
`checksumAlgorithm: EVIDENCE_PACKET_CHECKSUM_ALGORITHM`. `verifyEvidencePacketChecksum` returns `false`
|
|
408
426
|
for a changed payload, a missing checksum or an unknown algorithm. The checksum covers the payload,
|
|
@@ -458,7 +476,7 @@ This is what keeps the platform portable across verticals and consumers. If you
|
|
|
458
476
|
|
|
459
477
|
## Compatibility
|
|
460
478
|
|
|
461
|
-
- **Runtime:** Node 20
|
|
479
|
+
- **Runtime:** Node 20+. Run Node 22.23.4, 24.22.1, or 26.12.1 or newer in the same release line in production for the Brotli decoder fix in [CVE-2026-111497](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases); Node 20 is end-of-life and has no fixed release.
|
|
462
480
|
- **Module formats:** ESM and CommonJS (dual format from v0.2.0+)
|
|
463
481
|
- **TypeScript:** 5.0+, `moduleResolution: node` or `node16`/`nodenext`
|
|
464
482
|
|
|
@@ -494,6 +512,28 @@ retain internal contracts for trusted assembly and worker integration; none impl
|
|
|
494
512
|
catalog disclosure. A usage contract or generated schema must not
|
|
495
513
|
be served as an audience catalog without the internal-surface and audience filters above.
|
|
496
514
|
|
|
515
|
+
## Privacy engine
|
|
516
|
+
|
|
517
|
+
The engine requires an application-owned field policy. Configure field before-checksums with
|
|
518
|
+
the optional second constructor argument, exported as `AnonymizationEngineOptions`:
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
const engine = new AnonymizationEngine(policy, { beforeChecksumKey: secretFromStore });
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
`beforeChecksumKey` accepts a non-empty string or `Uint8Array`; invalid or empty keys throw
|
|
525
|
+
`TypeError`. The engine stores the key privately and copies byte keys at construction.
|
|
526
|
+
With a key, `beforeChecksum` is lowercase hex HMAC-SHA256 of `JSON.stringify(value)`.
|
|
527
|
+
Without a key it retains the existing unkeyed SHA-256, which reveals low-entropy values by
|
|
528
|
+
enumeration even when the field is nullified or dropped. Use a secret from the application's
|
|
529
|
+
secret store, keep it stable while verification is needed, and provide it to authorized verifiers.
|
|
530
|
+
Verification of a keyed checksum requires the key.
|
|
531
|
+
|
|
532
|
+
The four standalone privacy wrappers accept the same options as a trailing argument.
|
|
533
|
+
Lifecycle evidence copies the supplied metadata's checksums; its options do not re-key
|
|
534
|
+
previously generated metadata. This option does not change `FieldRedactor`, `hash`,
|
|
535
|
+
`tokenize`, `stableChecksum`, or packet-level checksum algorithms.
|
|
536
|
+
|
|
497
537
|
## License
|
|
498
538
|
|
|
499
539
|
MIT
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
1
|
+
import { createHash, createHmac } from "crypto";
|
|
2
2
|
//#region privacy/engine.ts
|
|
3
3
|
const ANONYMIZATION_POLICY_VERSION = "adr-026-platform-anonymization-v1";
|
|
4
4
|
function normalizeKey(key) {
|
|
@@ -44,7 +44,7 @@ function policyForPath(policies, path, key) {
|
|
|
44
44
|
function isPlainObject(value) {
|
|
45
45
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
46
46
|
}
|
|
47
|
-
function anonymizeRecursive(value, context, dataClass, path, redactedFields, policies, key) {
|
|
47
|
+
function anonymizeRecursive(value, context, dataClass, path, redactedFields, policies, beforeChecksum, key) {
|
|
48
48
|
const fieldPolicy = policyForPath(policies, path, key);
|
|
49
49
|
if (fieldPolicy && value !== null && value !== void 0) {
|
|
50
50
|
redactedFields.push({
|
|
@@ -55,11 +55,11 @@ function anonymizeRecursive(value, context, dataClass, path, redactedFields, pol
|
|
|
55
55
|
redactedAt: (context.redactedAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
56
56
|
reason: context.reason,
|
|
57
57
|
dataClass,
|
|
58
|
-
beforeChecksum:
|
|
58
|
+
beforeChecksum: beforeChecksum(value)
|
|
59
59
|
});
|
|
60
60
|
return applyMethod(value, fieldPolicy.method, context, path);
|
|
61
61
|
}
|
|
62
|
-
if (Array.isArray(value)) return value.map((item, index) => anonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies));
|
|
62
|
+
if (Array.isArray(value)) return value.map((item, index) => anonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies, beforeChecksum));
|
|
63
63
|
if (!isPlainObject(value)) return value;
|
|
64
64
|
const result = {};
|
|
65
65
|
for (const [childKey, childValue] of Object.entries(value)) {
|
|
@@ -67,7 +67,7 @@ function anonymizeRecursive(value, context, dataClass, path, redactedFields, pol
|
|
|
67
67
|
result[childKey] = childValue;
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
|
-
const anonymized = anonymizeRecursive(childValue, context, dataClass, `${path}.${childKey}`, redactedFields, policies, childKey);
|
|
70
|
+
const anonymized = anonymizeRecursive(childValue, context, dataClass, `${path}.${childKey}`, redactedFields, policies, beforeChecksum, childKey);
|
|
71
71
|
if (anonymized !== void 0) result[childKey] = anonymized;
|
|
72
72
|
}
|
|
73
73
|
return result;
|
|
@@ -103,7 +103,11 @@ var FieldRedactor = class {
|
|
|
103
103
|
};
|
|
104
104
|
var AnonymizationEngine = class {
|
|
105
105
|
#policy;
|
|
106
|
-
|
|
106
|
+
#beforeChecksumKey;
|
|
107
|
+
constructor(policy, options) {
|
|
108
|
+
const key = options?.beforeChecksumKey;
|
|
109
|
+
if (key !== void 0 && (typeof key !== "string" && !(key instanceof Uint8Array) || key.length === 0)) throw new TypeError("beforeChecksumKey must be a non-empty string or Uint8Array.");
|
|
110
|
+
this.#beforeChecksumKey = key instanceof Uint8Array ? new Uint8Array(key) : key;
|
|
107
111
|
this.#policy = {
|
|
108
112
|
fields: policy.fields.map((field) => ({ ...field })),
|
|
109
113
|
preservedSemantics: [...policy.preservedSemantics ?? []]
|
|
@@ -115,7 +119,7 @@ var AnonymizationEngine = class {
|
|
|
115
119
|
anonymizeJsonValueAtPath(value, context, dataClass, basePath) {
|
|
116
120
|
const redactedFields = [];
|
|
117
121
|
return {
|
|
118
|
-
value: anonymizeRecursive(value, context, dataClass, basePath, redactedFields, this.#policy.fields),
|
|
122
|
+
value: anonymizeRecursive(value, context, dataClass, basePath, redactedFields, this.#policy.fields, (value) => this.#beforeChecksumKey === void 0 ? stableChecksum(value) : createHmac("sha256", this.#beforeChecksumKey).update(JSON.stringify(value)).digest("hex")),
|
|
119
123
|
metadata: buildMetadata(context, dataClass, redactedFields, this.#policy.preservedSemantics ?? [])
|
|
120
124
|
};
|
|
121
125
|
}
|
|
@@ -220,4 +224,4 @@ var AnonymizationEngine = class {
|
|
|
220
224
|
//#endregion
|
|
221
225
|
export { stableChecksum as i, AnonymizationEngine as n, FieldRedactor as r, ANONYMIZATION_POLICY_VERSION as t };
|
|
222
226
|
|
|
223
|
-
//# sourceMappingURL=engine-
|
|
227
|
+
//# sourceMappingURL=engine-CG-OKfsX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-CG-OKfsX.js","names":[],"sources":["../privacy/engine.ts"],"sourcesContent":["import { createHash, createHmac } from \"node:crypto\";\n\nimport type {\n\tAnonymizationContext,\n\tAnonymizationFieldMetadata,\n\tAnonymizationMetadata,\n\tAnonymizationResult,\n\tPlatformDataClass,\n\tRedactionMethod,\n} from \"./index\";\n\nexport const ANONYMIZATION_POLICY_VERSION = \"adr-026-platform-anonymization-v1\";\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface AnonymizationFieldPolicy {\n\tmethod: RedactionMethod;\n\tmatch: \"key\" | \"path\";\n\tvalue: string;\n}\n\nexport interface AnonymizationEngineOptions {\n\tbeforeChecksumKey?: string | Uint8Array;\n}\n\nexport interface AnonymizationPolicy {\n\tfields: readonly AnonymizationFieldPolicy[];\n\tpreservedSemantics?: readonly string[];\n}\n\nexport interface AssetEventLike {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\tsubjectType: string;\n\tsubjectId: string;\n\tactionInvocationId: string;\n\tpayload: unknown;\n\tsequence: number;\n\toccurredAt: Date;\n\trecordedAt: Date;\n\tcorrelationId: string;\n\tcausationId?: string | null;\n}\n\nexport interface EvidencePacketLike {\n\tid: string;\n\tpacket: unknown;\n\tredactionMetadata: unknown;\n\tchecksum: string;\n}\n\nexport interface AnonymizationLifecycleEvidence {\n\teventType: \"AnonymizationApplied\";\n\tpolicyVersion: string;\n\tjobId: string;\n\treason: string;\n\tappliedAt: string;\n\ttenantId: string;\n\tspaceId: string | null;\n\tcounts: {\n\t\tassetEvents: number;\n\t\treadModels: number;\n\t\tevidencePackets: number;\n\t\tredactedFields: number;\n\t};\n\trecords: Array<{\n\t\trecordClass: PlatformDataClass;\n\t\trecordId: string;\n\t\tsubjectType?: string;\n\t\tsubjectId?: string;\n\t\tfieldManifest: Array<\n\t\t\tPick<AnonymizationFieldMetadata, \"fieldPath\" | \"method\" | \"beforeChecksum\">\n\t\t>;\n\t}>;\n\tpreservedSemantics: string[];\n}\n\nfunction normalizeKey(key: string): string {\n\treturn key.replace(/[^a-zA-Z0-9]/g, \"\").toLowerCase();\n}\n\nexport function stableChecksum(value: unknown): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(value)).digest(\"hex\");\n}\n\nfunction stableScopedToken(\n\tvalue: unknown,\n\tcontext: AnonymizationContext,\n\tfieldPath: string,\n): string {\n\tconst scope = `${context.tenantId}:${context.spaceId ?? \"global\"}:${fieldPath}`;\n\treturn `tok_${createHash(\"sha256\")\n\t\t.update(`${context.policyVersion ?? ANONYMIZATION_POLICY_VERSION}:${scope}:${String(value)}`)\n\t\t.digest(\"hex\")\n\t\t.slice(0, 24)}`;\n}\n\nfunction maskValue(value: unknown): string | null {\n\tif (value === null || value === undefined) {\n\t\treturn null;\n\t}\n\n\tconst stringValue = String(value);\n\tif (stringValue.length <= 4) {\n\t\treturn \"****\";\n\t}\n\n\treturn `${\"*\".repeat(Math.max(stringValue.length - 4, 4))}${stringValue.slice(-4)}`;\n}\n\nfunction generalizeValue(value: unknown): unknown {\n\tif (typeof value === \"number\") {\n\t\treturn Math.round(value / 1000) * 1000;\n\t}\n\n\tif (typeof value === \"string\" && /^\\d{5}(?:-\\d{4})?$/.test(value)) {\n\t\treturn `${value.slice(0, 3)}**`;\n\t}\n\n\treturn null;\n}\n\nfunction applyMethod(\n\tvalue: unknown,\n\tmethod: RedactionMethod,\n\tcontext: AnonymizationContext,\n\tpath: string,\n): unknown {\n\tswitch (method) {\n\t\tcase \"drop\":\n\t\t\treturn undefined;\n\t\tcase \"nullify\":\n\t\t\treturn null;\n\t\tcase \"mask\":\n\t\t\treturn maskValue(value);\n\t\tcase \"hash\":\n\t\t\treturn `sha256_${stableChecksum(value)}`;\n\t\tcase \"tokenize\":\n\t\t\treturn stableScopedToken(value, context, path);\n\t\tcase \"generalize\":\n\t\t\treturn generalizeValue(value);\n\t}\n}\n\nfunction policyForPath(policies: readonly AnonymizationFieldPolicy[], path: string, key?: string): AnonymizationFieldPolicy | undefined {\n\tconst normalizedPath = normalizeKey(path);\n\tconst normalizedKey = key ? normalizeKey(key) : undefined;\n\n\treturn policies.find((policy) => {\n\t\tconst normalizedValue = normalizeKey(policy.value);\n\t\tif (policy.match === \"key\") {\n\t\t\treturn normalizedValue === normalizedKey;\n\t\t}\n\n\t\treturn normalizedValue === normalizedPath;\n\t});\n}\n\nfunction isPlainObject(value: unknown): value is JsonObject {\n\treturn (\n\t\tBoolean(value) && typeof value === \"object\" && !Array.isArray(value) && !(value instanceof Date)\n\t);\n}\n\nfunction anonymizeRecursive(\n\tvalue: unknown,\n\tcontext: AnonymizationContext,\n\tdataClass: PlatformDataClass,\n\tpath: string,\n\tredactedFields: AnonymizationFieldMetadata[],\n\tpolicies: readonly AnonymizationFieldPolicy[],\n\tbeforeChecksum: (value: unknown) => string,\n\tkey?: string,\n): unknown {\n\tconst fieldPolicy = policyForPath(policies, path, key);\n\tif (fieldPolicy && value !== null && value !== undefined) {\n\t\tredactedFields.push({\n\t\t\tfieldPath: path,\n\t\t\tmethod: fieldPolicy.method,\n\t\t\tpolicyVersion: context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\t\tjobId: context.jobId,\n\t\t\tredactedAt: (context.redactedAt ?? new Date()).toISOString(),\n\t\t\treason: context.reason,\n\t\t\tdataClass,\n\t\t\tbeforeChecksum: beforeChecksum(value),\n\t\t});\n\n\t\treturn applyMethod(value, fieldPolicy.method, context, path);\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item, index) =>\n\t\t\tanonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies, beforeChecksum),\n\t\t);\n\t}\n\n\tif (!isPlainObject(value)) {\n\t\treturn value;\n\t}\n\n\tconst result: JsonObject = {};\n\tfor (const [childKey, childValue] of Object.entries(value)) {\n\t\tif (childKey === \"_privacy\") {\n\t\t\tresult[childKey] = childValue;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst anonymized = anonymizeRecursive(\n\t\t\tchildValue,\n\t\t\tcontext,\n\t\t\tdataClass,\n\t\t\t`${path}.${childKey}`,\n\t\t\tredactedFields,\n\t\t\tpolicies,\n\t\t\tbeforeChecksum,\n\t\t\tchildKey,\n\t\t);\n\n\t\tif (anonymized !== undefined) {\n\t\t\tresult[childKey] = anonymized;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction buildMetadata(\n\tcontext: AnonymizationContext,\n\tdataClass: PlatformDataClass,\n\tredactedFields: AnonymizationFieldMetadata[],\n\tpreservedSemantics: readonly string[],\n): AnonymizationMetadata {\n\treturn {\n\t\tredactionState: \"anonymized\",\n\t\tpolicyVersion: context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\tjobId: context.jobId,\n\t\tredactedAt: (context.redactedAt ?? new Date()).toISOString(),\n\t\treason: context.reason,\n\t\tdataClass,\n\t\tredactedFields,\n\t\tpreservedSemantics: [...preservedSemantics],\n\t};\n}\n\nexport class FieldRedactor {\n\tredactField(value: unknown, method: RedactionMethod, context: AnonymizationContext, path: string): unknown {\n\t\treturn applyMethod(value, method, context, path);\n\t}\n\n\tmask(value: unknown): string | null {\n\t\treturn maskValue(value);\n\t}\n\n\thash(value: unknown): string {\n\t\treturn `sha256_${stableChecksum(value)}`;\n\t}\n\n\ttokenize(value: unknown, context: AnonymizationContext, path: string): string {\n\t\treturn stableScopedToken(value, context, path);\n\t}\n\n\tgeneralize(value: unknown): unknown {\n\t\treturn generalizeValue(value);\n\t}\n}\n\nexport class AnonymizationEngine {\n\treadonly #policy: AnonymizationPolicy;\n\n\treadonly #beforeChecksumKey: string | Uint8Array | undefined;\n\n\tconstructor(policy: AnonymizationPolicy, options?: AnonymizationEngineOptions) {\n\t\tconst key = options?.beforeChecksumKey;\n\t\tif (key !== undefined && (\n\t\t\t(typeof key !== \"string\" && !(key instanceof Uint8Array)) || key.length === 0\n\t\t)) {\n\t\t\tthrow new TypeError(\"beforeChecksumKey must be a non-empty string or Uint8Array.\");\n\t\t}\n\t\tthis.#beforeChecksumKey = key instanceof Uint8Array ? new Uint8Array(key) : key;\n\t\tthis.#policy = {\n\t\t\tfields: policy.fields.map((field) => ({ ...field })),\n\t\t\tpreservedSemantics: [...(policy.preservedSemantics ?? [])],\n\t\t};\n\t}\n\n\tanonymizeJsonValue<TValue>(\n\t\tvalue: TValue,\n\t\tcontext: AnonymizationContext,\n\t\tdataClass: PlatformDataClass,\n\t): AnonymizationResult<TValue> {\n\t\treturn this.anonymizeJsonValueAtPath(value, context, dataClass, \"$\");\n\t}\n\n\tanonymizeJsonValueAtPath<TValue>(\n\t\tvalue: TValue,\n\t\tcontext: AnonymizationContext,\n\t\tdataClass: PlatformDataClass,\n\t\tbasePath: string,\n\t): AnonymizationResult<TValue> {\n\t\tconst redactedFields: AnonymizationFieldMetadata[] = [];\n\t\tconst anonymizedValue = anonymizeRecursive(\n\t\t\tvalue,\n\t\t\tcontext,\n\t\t\tdataClass,\n\t\t\tbasePath,\n\t\t\tredactedFields,\n\t\t\tthis.#policy.fields,\n\t\t\t(value) => this.#beforeChecksumKey === undefined\n\t\t\t\t? stableChecksum(value)\n\t\t\t\t: createHmac(\"sha256\", this.#beforeChecksumKey).update(JSON.stringify(value)).digest(\"hex\"),\n\t\t) as TValue;\n\n\t\treturn {\n\t\t\tvalue: anonymizedValue,\n\t\t\tmetadata: buildMetadata(context, dataClass, redactedFields, this.#policy.preservedSemantics ?? []),\n\t\t};\n\t}\n\n\tanonymizeAssetEventPayload(\n\t\tevent: AssetEventLike,\n\t\tcontext: AnonymizationContext,\n\t): AnonymizationResult<unknown> {\n\t\tconst result = this.anonymizeJsonValue(event.payload, context, \"asset_event\");\n\t\tconst payload = isPlainObject(result.value)\n\t\t\t? { ...result.value, _privacy: result.metadata }\n\t\t\t: { value: result.value, _privacy: result.metadata };\n\n\t\treturn { value: payload, metadata: result.metadata };\n\t}\n\n\tanonymizeEvidencePacket(\n\t\tpacket: EvidencePacketLike,\n\t\tcontext: AnonymizationContext,\n\t): AnonymizationResult<Pick<EvidencePacketLike, \"packet\" | \"redactionMetadata\" | \"checksum\">> {\n\t\tconst result = this.anonymizeJsonValue(packet.packet, context, \"evidence_packet\");\n\t\tconst redactionMetadata = {\n\t\t\t...(isPlainObject(packet.redactionMetadata) ? packet.redactionMetadata : {}),\n\t\t\tlifecycleAnonymization: result.metadata,\n\t\t};\n\t\tconst anonymizedPacket = isPlainObject(result.value)\n\t\t\t? { ...result.value, _privacy: result.metadata }\n\t\t\t: { value: result.value, _privacy: result.metadata };\n\n\t\treturn {\n\t\t\tvalue: {\n\t\t\t\tpacket: anonymizedPacket,\n\t\t\t\tredactionMetadata,\n\t\t\t\tchecksum: stableChecksum({ packet: anonymizedPacket, redactionMetadata }),\n\t\t\t},\n\t\t\tmetadata: result.metadata,\n\t\t};\n\t}\n\n\tbuildAnonymizationLifecycleEvidence(input: {\n\t\tcontext: AnonymizationContext;\n\t\tassetEvents?: Array<{ event: AssetEventLike; metadata: AnonymizationMetadata }>;\n\t\treadModels?: Array<{\n\t\t\tmodelKind: string;\n\t\t\trecord: JsonObject & { id: string };\n\t\t\tmetadata: AnonymizationMetadata;\n\t\t}>;\n\t\tevidencePackets?: Array<{ packet: EvidencePacketLike; metadata: AnonymizationMetadata }>;\n\t}): AnonymizationLifecycleEvidence {\n\t\tconst assetEvents = input.assetEvents ?? [];\n\t\tconst readModels = input.readModels ?? [];\n\t\tconst evidencePackets = input.evidencePackets ?? [];\n\t\tconst redactedFields = [\n\t\t\t...assetEvents.flatMap((item) => item.metadata.redactedFields),\n\t\t\t...readModels.flatMap((item) => item.metadata.redactedFields),\n\t\t\t...evidencePackets.flatMap((item) => item.metadata.redactedFields),\n\t\t];\n\n\t\treturn {\n\t\t\teventType: \"AnonymizationApplied\",\n\t\t\tpolicyVersion: input.context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\t\tjobId: input.context.jobId,\n\t\t\treason: input.context.reason,\n\t\t\tappliedAt: (input.context.redactedAt ?? new Date()).toISOString(),\n\t\t\ttenantId: input.context.tenantId,\n\t\t\tspaceId: input.context.spaceId ?? null,\n\t\t\tcounts: {\n\t\t\t\tassetEvents: assetEvents.length,\n\t\t\t\treadModels: readModels.length,\n\t\t\t\tevidencePackets: evidencePackets.length,\n\t\t\t\tredactedFields: redactedFields.length,\n\t\t\t},\n\t\t\trecords: [\n\t\t\t\t...assetEvents.map((item) => ({\n\t\t\t\t\trecordClass: \"asset_event\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.event.id,\n\t\t\t\t\tsubjectType: item.event.subjectType,\n\t\t\t\t\tsubjectId: item.event.subjectId,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t\t...readModels.map((item) => ({\n\t\t\t\t\trecordClass: \"read_model\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.record.id,\n\t\t\t\t\tsubjectType: item.modelKind,\n\t\t\t\t\tsubjectId: item.record.id,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t\t...evidencePackets.map((item) => ({\n\t\t\t\t\trecordClass: \"evidence_packet\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.packet.id,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t],\n\t\t\tpreservedSemantics: [...(this.#policy.preservedSemantics ?? [])],\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -44,7 +44,7 @@ function policyForPath(policies, path, key) {
|
|
|
44
44
|
function isPlainObject(value) {
|
|
45
45
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
46
46
|
}
|
|
47
|
-
function anonymizeRecursive(value, context, dataClass, path, redactedFields, policies, key) {
|
|
47
|
+
function anonymizeRecursive(value, context, dataClass, path, redactedFields, policies, beforeChecksum, key) {
|
|
48
48
|
const fieldPolicy = policyForPath(policies, path, key);
|
|
49
49
|
if (fieldPolicy && value !== null && value !== void 0) {
|
|
50
50
|
redactedFields.push({
|
|
@@ -55,11 +55,11 @@ function anonymizeRecursive(value, context, dataClass, path, redactedFields, pol
|
|
|
55
55
|
redactedAt: (context.redactedAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
56
56
|
reason: context.reason,
|
|
57
57
|
dataClass,
|
|
58
|
-
beforeChecksum:
|
|
58
|
+
beforeChecksum: beforeChecksum(value)
|
|
59
59
|
});
|
|
60
60
|
return applyMethod(value, fieldPolicy.method, context, path);
|
|
61
61
|
}
|
|
62
|
-
if (Array.isArray(value)) return value.map((item, index) => anonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies));
|
|
62
|
+
if (Array.isArray(value)) return value.map((item, index) => anonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies, beforeChecksum));
|
|
63
63
|
if (!isPlainObject(value)) return value;
|
|
64
64
|
const result = {};
|
|
65
65
|
for (const [childKey, childValue] of Object.entries(value)) {
|
|
@@ -67,7 +67,7 @@ function anonymizeRecursive(value, context, dataClass, path, redactedFields, pol
|
|
|
67
67
|
result[childKey] = childValue;
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
|
-
const anonymized = anonymizeRecursive(childValue, context, dataClass, `${path}.${childKey}`, redactedFields, policies, childKey);
|
|
70
|
+
const anonymized = anonymizeRecursive(childValue, context, dataClass, `${path}.${childKey}`, redactedFields, policies, beforeChecksum, childKey);
|
|
71
71
|
if (anonymized !== void 0) result[childKey] = anonymized;
|
|
72
72
|
}
|
|
73
73
|
return result;
|
|
@@ -103,7 +103,11 @@ var FieldRedactor = class {
|
|
|
103
103
|
};
|
|
104
104
|
var AnonymizationEngine = class {
|
|
105
105
|
#policy;
|
|
106
|
-
|
|
106
|
+
#beforeChecksumKey;
|
|
107
|
+
constructor(policy, options) {
|
|
108
|
+
const key = options?.beforeChecksumKey;
|
|
109
|
+
if (key !== void 0 && (typeof key !== "string" && !(key instanceof Uint8Array) || key.length === 0)) throw new TypeError("beforeChecksumKey must be a non-empty string or Uint8Array.");
|
|
110
|
+
this.#beforeChecksumKey = key instanceof Uint8Array ? new Uint8Array(key) : key;
|
|
107
111
|
this.#policy = {
|
|
108
112
|
fields: policy.fields.map((field) => ({ ...field })),
|
|
109
113
|
preservedSemantics: [...policy.preservedSemantics ?? []]
|
|
@@ -115,7 +119,7 @@ var AnonymizationEngine = class {
|
|
|
115
119
|
anonymizeJsonValueAtPath(value, context, dataClass, basePath) {
|
|
116
120
|
const redactedFields = [];
|
|
117
121
|
return {
|
|
118
|
-
value: anonymizeRecursive(value, context, dataClass, basePath, redactedFields, this.#policy.fields),
|
|
122
|
+
value: anonymizeRecursive(value, context, dataClass, basePath, redactedFields, this.#policy.fields, (value) => this.#beforeChecksumKey === void 0 ? stableChecksum(value) : (0, crypto.createHmac)("sha256", this.#beforeChecksumKey).update(JSON.stringify(value)).digest("hex")),
|
|
119
123
|
metadata: buildMetadata(context, dataClass, redactedFields, this.#policy.preservedSemantics ?? [])
|
|
120
124
|
};
|
|
121
125
|
}
|
|
@@ -243,4 +247,4 @@ Object.defineProperty(exports, "stableChecksum", {
|
|
|
243
247
|
}
|
|
244
248
|
});
|
|
245
249
|
|
|
246
|
-
//# sourceMappingURL=engine-
|
|
250
|
+
//# sourceMappingURL=engine-T6yBlzOE.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-T6yBlzOE.cjs","names":["createHash","createHmac"],"sources":["../privacy/engine.ts"],"sourcesContent":["import { createHash, createHmac } from \"node:crypto\";\n\nimport type {\n\tAnonymizationContext,\n\tAnonymizationFieldMetadata,\n\tAnonymizationMetadata,\n\tAnonymizationResult,\n\tPlatformDataClass,\n\tRedactionMethod,\n} from \"./index\";\n\nexport const ANONYMIZATION_POLICY_VERSION = \"adr-026-platform-anonymization-v1\";\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface AnonymizationFieldPolicy {\n\tmethod: RedactionMethod;\n\tmatch: \"key\" | \"path\";\n\tvalue: string;\n}\n\nexport interface AnonymizationEngineOptions {\n\tbeforeChecksumKey?: string | Uint8Array;\n}\n\nexport interface AnonymizationPolicy {\n\tfields: readonly AnonymizationFieldPolicy[];\n\tpreservedSemantics?: readonly string[];\n}\n\nexport interface AssetEventLike {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\tsubjectType: string;\n\tsubjectId: string;\n\tactionInvocationId: string;\n\tpayload: unknown;\n\tsequence: number;\n\toccurredAt: Date;\n\trecordedAt: Date;\n\tcorrelationId: string;\n\tcausationId?: string | null;\n}\n\nexport interface EvidencePacketLike {\n\tid: string;\n\tpacket: unknown;\n\tredactionMetadata: unknown;\n\tchecksum: string;\n}\n\nexport interface AnonymizationLifecycleEvidence {\n\teventType: \"AnonymizationApplied\";\n\tpolicyVersion: string;\n\tjobId: string;\n\treason: string;\n\tappliedAt: string;\n\ttenantId: string;\n\tspaceId: string | null;\n\tcounts: {\n\t\tassetEvents: number;\n\t\treadModels: number;\n\t\tevidencePackets: number;\n\t\tredactedFields: number;\n\t};\n\trecords: Array<{\n\t\trecordClass: PlatformDataClass;\n\t\trecordId: string;\n\t\tsubjectType?: string;\n\t\tsubjectId?: string;\n\t\tfieldManifest: Array<\n\t\t\tPick<AnonymizationFieldMetadata, \"fieldPath\" | \"method\" | \"beforeChecksum\">\n\t\t>;\n\t}>;\n\tpreservedSemantics: string[];\n}\n\nfunction normalizeKey(key: string): string {\n\treturn key.replace(/[^a-zA-Z0-9]/g, \"\").toLowerCase();\n}\n\nexport function stableChecksum(value: unknown): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(value)).digest(\"hex\");\n}\n\nfunction stableScopedToken(\n\tvalue: unknown,\n\tcontext: AnonymizationContext,\n\tfieldPath: string,\n): string {\n\tconst scope = `${context.tenantId}:${context.spaceId ?? \"global\"}:${fieldPath}`;\n\treturn `tok_${createHash(\"sha256\")\n\t\t.update(`${context.policyVersion ?? ANONYMIZATION_POLICY_VERSION}:${scope}:${String(value)}`)\n\t\t.digest(\"hex\")\n\t\t.slice(0, 24)}`;\n}\n\nfunction maskValue(value: unknown): string | null {\n\tif (value === null || value === undefined) {\n\t\treturn null;\n\t}\n\n\tconst stringValue = String(value);\n\tif (stringValue.length <= 4) {\n\t\treturn \"****\";\n\t}\n\n\treturn `${\"*\".repeat(Math.max(stringValue.length - 4, 4))}${stringValue.slice(-4)}`;\n}\n\nfunction generalizeValue(value: unknown): unknown {\n\tif (typeof value === \"number\") {\n\t\treturn Math.round(value / 1000) * 1000;\n\t}\n\n\tif (typeof value === \"string\" && /^\\d{5}(?:-\\d{4})?$/.test(value)) {\n\t\treturn `${value.slice(0, 3)}**`;\n\t}\n\n\treturn null;\n}\n\nfunction applyMethod(\n\tvalue: unknown,\n\tmethod: RedactionMethod,\n\tcontext: AnonymizationContext,\n\tpath: string,\n): unknown {\n\tswitch (method) {\n\t\tcase \"drop\":\n\t\t\treturn undefined;\n\t\tcase \"nullify\":\n\t\t\treturn null;\n\t\tcase \"mask\":\n\t\t\treturn maskValue(value);\n\t\tcase \"hash\":\n\t\t\treturn `sha256_${stableChecksum(value)}`;\n\t\tcase \"tokenize\":\n\t\t\treturn stableScopedToken(value, context, path);\n\t\tcase \"generalize\":\n\t\t\treturn generalizeValue(value);\n\t}\n}\n\nfunction policyForPath(policies: readonly AnonymizationFieldPolicy[], path: string, key?: string): AnonymizationFieldPolicy | undefined {\n\tconst normalizedPath = normalizeKey(path);\n\tconst normalizedKey = key ? normalizeKey(key) : undefined;\n\n\treturn policies.find((policy) => {\n\t\tconst normalizedValue = normalizeKey(policy.value);\n\t\tif (policy.match === \"key\") {\n\t\t\treturn normalizedValue === normalizedKey;\n\t\t}\n\n\t\treturn normalizedValue === normalizedPath;\n\t});\n}\n\nfunction isPlainObject(value: unknown): value is JsonObject {\n\treturn (\n\t\tBoolean(value) && typeof value === \"object\" && !Array.isArray(value) && !(value instanceof Date)\n\t);\n}\n\nfunction anonymizeRecursive(\n\tvalue: unknown,\n\tcontext: AnonymizationContext,\n\tdataClass: PlatformDataClass,\n\tpath: string,\n\tredactedFields: AnonymizationFieldMetadata[],\n\tpolicies: readonly AnonymizationFieldPolicy[],\n\tbeforeChecksum: (value: unknown) => string,\n\tkey?: string,\n): unknown {\n\tconst fieldPolicy = policyForPath(policies, path, key);\n\tif (fieldPolicy && value !== null && value !== undefined) {\n\t\tredactedFields.push({\n\t\t\tfieldPath: path,\n\t\t\tmethod: fieldPolicy.method,\n\t\t\tpolicyVersion: context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\t\tjobId: context.jobId,\n\t\t\tredactedAt: (context.redactedAt ?? new Date()).toISOString(),\n\t\t\treason: context.reason,\n\t\t\tdataClass,\n\t\t\tbeforeChecksum: beforeChecksum(value),\n\t\t});\n\n\t\treturn applyMethod(value, fieldPolicy.method, context, path);\n\t}\n\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item, index) =>\n\t\t\tanonymizeRecursive(item, context, dataClass, `${path}[${index}]`, redactedFields, policies, beforeChecksum),\n\t\t);\n\t}\n\n\tif (!isPlainObject(value)) {\n\t\treturn value;\n\t}\n\n\tconst result: JsonObject = {};\n\tfor (const [childKey, childValue] of Object.entries(value)) {\n\t\tif (childKey === \"_privacy\") {\n\t\t\tresult[childKey] = childValue;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst anonymized = anonymizeRecursive(\n\t\t\tchildValue,\n\t\t\tcontext,\n\t\t\tdataClass,\n\t\t\t`${path}.${childKey}`,\n\t\t\tredactedFields,\n\t\t\tpolicies,\n\t\t\tbeforeChecksum,\n\t\t\tchildKey,\n\t\t);\n\n\t\tif (anonymized !== undefined) {\n\t\t\tresult[childKey] = anonymized;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction buildMetadata(\n\tcontext: AnonymizationContext,\n\tdataClass: PlatformDataClass,\n\tredactedFields: AnonymizationFieldMetadata[],\n\tpreservedSemantics: readonly string[],\n): AnonymizationMetadata {\n\treturn {\n\t\tredactionState: \"anonymized\",\n\t\tpolicyVersion: context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\tjobId: context.jobId,\n\t\tredactedAt: (context.redactedAt ?? new Date()).toISOString(),\n\t\treason: context.reason,\n\t\tdataClass,\n\t\tredactedFields,\n\t\tpreservedSemantics: [...preservedSemantics],\n\t};\n}\n\nexport class FieldRedactor {\n\tredactField(value: unknown, method: RedactionMethod, context: AnonymizationContext, path: string): unknown {\n\t\treturn applyMethod(value, method, context, path);\n\t}\n\n\tmask(value: unknown): string | null {\n\t\treturn maskValue(value);\n\t}\n\n\thash(value: unknown): string {\n\t\treturn `sha256_${stableChecksum(value)}`;\n\t}\n\n\ttokenize(value: unknown, context: AnonymizationContext, path: string): string {\n\t\treturn stableScopedToken(value, context, path);\n\t}\n\n\tgeneralize(value: unknown): unknown {\n\t\treturn generalizeValue(value);\n\t}\n}\n\nexport class AnonymizationEngine {\n\treadonly #policy: AnonymizationPolicy;\n\n\treadonly #beforeChecksumKey: string | Uint8Array | undefined;\n\n\tconstructor(policy: AnonymizationPolicy, options?: AnonymizationEngineOptions) {\n\t\tconst key = options?.beforeChecksumKey;\n\t\tif (key !== undefined && (\n\t\t\t(typeof key !== \"string\" && !(key instanceof Uint8Array)) || key.length === 0\n\t\t)) {\n\t\t\tthrow new TypeError(\"beforeChecksumKey must be a non-empty string or Uint8Array.\");\n\t\t}\n\t\tthis.#beforeChecksumKey = key instanceof Uint8Array ? new Uint8Array(key) : key;\n\t\tthis.#policy = {\n\t\t\tfields: policy.fields.map((field) => ({ ...field })),\n\t\t\tpreservedSemantics: [...(policy.preservedSemantics ?? [])],\n\t\t};\n\t}\n\n\tanonymizeJsonValue<TValue>(\n\t\tvalue: TValue,\n\t\tcontext: AnonymizationContext,\n\t\tdataClass: PlatformDataClass,\n\t): AnonymizationResult<TValue> {\n\t\treturn this.anonymizeJsonValueAtPath(value, context, dataClass, \"$\");\n\t}\n\n\tanonymizeJsonValueAtPath<TValue>(\n\t\tvalue: TValue,\n\t\tcontext: AnonymizationContext,\n\t\tdataClass: PlatformDataClass,\n\t\tbasePath: string,\n\t): AnonymizationResult<TValue> {\n\t\tconst redactedFields: AnonymizationFieldMetadata[] = [];\n\t\tconst anonymizedValue = anonymizeRecursive(\n\t\t\tvalue,\n\t\t\tcontext,\n\t\t\tdataClass,\n\t\t\tbasePath,\n\t\t\tredactedFields,\n\t\t\tthis.#policy.fields,\n\t\t\t(value) => this.#beforeChecksumKey === undefined\n\t\t\t\t? stableChecksum(value)\n\t\t\t\t: createHmac(\"sha256\", this.#beforeChecksumKey).update(JSON.stringify(value)).digest(\"hex\"),\n\t\t) as TValue;\n\n\t\treturn {\n\t\t\tvalue: anonymizedValue,\n\t\t\tmetadata: buildMetadata(context, dataClass, redactedFields, this.#policy.preservedSemantics ?? []),\n\t\t};\n\t}\n\n\tanonymizeAssetEventPayload(\n\t\tevent: AssetEventLike,\n\t\tcontext: AnonymizationContext,\n\t): AnonymizationResult<unknown> {\n\t\tconst result = this.anonymizeJsonValue(event.payload, context, \"asset_event\");\n\t\tconst payload = isPlainObject(result.value)\n\t\t\t? { ...result.value, _privacy: result.metadata }\n\t\t\t: { value: result.value, _privacy: result.metadata };\n\n\t\treturn { value: payload, metadata: result.metadata };\n\t}\n\n\tanonymizeEvidencePacket(\n\t\tpacket: EvidencePacketLike,\n\t\tcontext: AnonymizationContext,\n\t): AnonymizationResult<Pick<EvidencePacketLike, \"packet\" | \"redactionMetadata\" | \"checksum\">> {\n\t\tconst result = this.anonymizeJsonValue(packet.packet, context, \"evidence_packet\");\n\t\tconst redactionMetadata = {\n\t\t\t...(isPlainObject(packet.redactionMetadata) ? packet.redactionMetadata : {}),\n\t\t\tlifecycleAnonymization: result.metadata,\n\t\t};\n\t\tconst anonymizedPacket = isPlainObject(result.value)\n\t\t\t? { ...result.value, _privacy: result.metadata }\n\t\t\t: { value: result.value, _privacy: result.metadata };\n\n\t\treturn {\n\t\t\tvalue: {\n\t\t\t\tpacket: anonymizedPacket,\n\t\t\t\tredactionMetadata,\n\t\t\t\tchecksum: stableChecksum({ packet: anonymizedPacket, redactionMetadata }),\n\t\t\t},\n\t\t\tmetadata: result.metadata,\n\t\t};\n\t}\n\n\tbuildAnonymizationLifecycleEvidence(input: {\n\t\tcontext: AnonymizationContext;\n\t\tassetEvents?: Array<{ event: AssetEventLike; metadata: AnonymizationMetadata }>;\n\t\treadModels?: Array<{\n\t\t\tmodelKind: string;\n\t\t\trecord: JsonObject & { id: string };\n\t\t\tmetadata: AnonymizationMetadata;\n\t\t}>;\n\t\tevidencePackets?: Array<{ packet: EvidencePacketLike; metadata: AnonymizationMetadata }>;\n\t}): AnonymizationLifecycleEvidence {\n\t\tconst assetEvents = input.assetEvents ?? [];\n\t\tconst readModels = input.readModels ?? [];\n\t\tconst evidencePackets = input.evidencePackets ?? [];\n\t\tconst redactedFields = [\n\t\t\t...assetEvents.flatMap((item) => item.metadata.redactedFields),\n\t\t\t...readModels.flatMap((item) => item.metadata.redactedFields),\n\t\t\t...evidencePackets.flatMap((item) => item.metadata.redactedFields),\n\t\t];\n\n\t\treturn {\n\t\t\teventType: \"AnonymizationApplied\",\n\t\t\tpolicyVersion: input.context.policyVersion ?? ANONYMIZATION_POLICY_VERSION,\n\t\t\tjobId: input.context.jobId,\n\t\t\treason: input.context.reason,\n\t\t\tappliedAt: (input.context.redactedAt ?? new Date()).toISOString(),\n\t\t\ttenantId: input.context.tenantId,\n\t\t\tspaceId: input.context.spaceId ?? null,\n\t\t\tcounts: {\n\t\t\t\tassetEvents: assetEvents.length,\n\t\t\t\treadModels: readModels.length,\n\t\t\t\tevidencePackets: evidencePackets.length,\n\t\t\t\tredactedFields: redactedFields.length,\n\t\t\t},\n\t\t\trecords: [\n\t\t\t\t...assetEvents.map((item) => ({\n\t\t\t\t\trecordClass: \"asset_event\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.event.id,\n\t\t\t\t\tsubjectType: item.event.subjectType,\n\t\t\t\t\tsubjectId: item.event.subjectId,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t\t...readModels.map((item) => ({\n\t\t\t\t\trecordClass: \"read_model\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.record.id,\n\t\t\t\t\tsubjectType: item.modelKind,\n\t\t\t\t\tsubjectId: item.record.id,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t\t...evidencePackets.map((item) => ({\n\t\t\t\t\trecordClass: \"evidence_packet\" as PlatformDataClass,\n\t\t\t\t\trecordId: item.packet.id,\n\t\t\t\t\tfieldManifest: item.metadata.redactedFields.map(\n\t\t\t\t\t\t({ fieldPath, method, beforeChecksum }) => ({\n\t\t\t\t\t\t\tfieldPath,\n\t\t\t\t\t\t\tmethod,\n\t\t\t\t\t\t\tbeforeChecksum,\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t})),\n\t\t\t],\n\t\t\tpreservedSemantics: [...(this.#policy.preservedSemantics ?? [])],\n\t\t};\n\t}\n}\n"],"mappings":"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"}
|
package/dist/evidence/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_evidence = require("../evidence-
|
|
2
|
+
const require_evidence = require("../evidence-DPOImHJB.cjs");
|
|
3
3
|
exports.DEFAULT_EVIDENCE_PACKET_TYPE = require_evidence.DEFAULT_EVIDENCE_PACKET_TYPE;
|
|
4
4
|
exports.DEFAULT_EVIDENCE_REDACTION_REASON = require_evidence.DEFAULT_EVIDENCE_REDACTION_REASON;
|
|
5
5
|
exports.EVIDENCE_PACKET_CHECKSUM_ALGORITHM = require_evidence.EVIDENCE_PACKET_CHECKSUM_ALGORITHM;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as canonicalJson, C as EvidenceTimestamp, D as createEvidencePacketRenderer, E as computeEvidencePacketChecksum, O as generateEvidencePacket, S as EvidenceScope, T as GenerateEvidencePacketOptions, _ as EvidencePacketPayload, a as EvidencePacketSubject, b as EvidencePacketRendererOptions, c as DEFAULT_EVIDENCE_REDACTION_REASON, d as EvidenceEventQuery, f as EvidenceEventRecord, g as EvidencePacketGenerationErrorCode, h as EvidencePacketGenerationError, i as EvidencePacketStatus, k as verifyEvidencePacketChecksum, l as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, m as EvidenceJsonRecord, n as EvidencePacketEnvelope, o as EvidenceRenderer, p as EvidenceInvocationRecord, r as EvidencePacketReference, s as DEFAULT_EVIDENCE_PACKET_TYPE, t as EvidenceGenerationContext, u as EVIDENCE_PACKET_SCHEMA_VERSION, v as EvidencePacketReadPort, w as GenerateEvidencePacketInput, x as EvidenceRecord, y as EvidencePacketRendererInput } from "../index-
|
|
1
|
+
import { A as canonicalJson, C as EvidenceTimestamp, D as createEvidencePacketRenderer, E as computeEvidencePacketChecksum, O as generateEvidencePacket, S as EvidenceScope, T as GenerateEvidencePacketOptions, _ as EvidencePacketPayload, a as EvidencePacketSubject, b as EvidencePacketRendererOptions, c as DEFAULT_EVIDENCE_REDACTION_REASON, d as EvidenceEventQuery, f as EvidenceEventRecord, g as EvidencePacketGenerationErrorCode, h as EvidencePacketGenerationError, i as EvidencePacketStatus, k as verifyEvidencePacketChecksum, l as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, m as EvidenceJsonRecord, n as EvidencePacketEnvelope, o as EvidenceRenderer, p as EvidenceInvocationRecord, r as EvidencePacketReference, s as DEFAULT_EVIDENCE_PACKET_TYPE, t as EvidenceGenerationContext, u as EVIDENCE_PACKET_SCHEMA_VERSION, v as EvidencePacketReadPort, w as GenerateEvidencePacketInput, x as EvidenceRecord, y as EvidencePacketRendererInput } from "../index-BIZIxaxe.cjs";
|
|
2
2
|
export { DEFAULT_EVIDENCE_PACKET_TYPE, DEFAULT_EVIDENCE_REDACTION_REASON, EVIDENCE_PACKET_CHECKSUM_ALGORITHM, EVIDENCE_PACKET_SCHEMA_VERSION, type EvidenceEventQuery, type EvidenceEventRecord, EvidenceGenerationContext, type EvidenceInvocationRecord, type EvidenceJsonRecord, EvidencePacketEnvelope, EvidencePacketGenerationError, type EvidencePacketGenerationErrorCode, type EvidencePacketPayload, type EvidencePacketReadPort, EvidencePacketReference, type EvidencePacketRendererInput, type EvidencePacketRendererOptions, EvidencePacketStatus, EvidencePacketSubject, type EvidenceRecord, EvidenceRenderer, type EvidenceScope, type EvidenceTimestamp, type GenerateEvidencePacketInput, type GenerateEvidencePacketOptions, canonicalJson, computeEvidencePacketChecksum, createEvidencePacketRenderer, generateEvidencePacket, verifyEvidencePacketChecksum };
|
package/dist/evidence/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as canonicalJson, C as EvidenceTimestamp, D as createEvidencePacketRenderer, E as computeEvidencePacketChecksum, O as generateEvidencePacket, S as EvidenceScope, T as GenerateEvidencePacketOptions, _ as EvidencePacketPayload, a as EvidencePacketSubject, b as EvidencePacketRendererOptions, c as DEFAULT_EVIDENCE_REDACTION_REASON, d as EvidenceEventQuery, f as EvidenceEventRecord, g as EvidencePacketGenerationErrorCode, h as EvidencePacketGenerationError, i as EvidencePacketStatus, k as verifyEvidencePacketChecksum, l as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, m as EvidenceJsonRecord, n as EvidencePacketEnvelope, o as EvidenceRenderer, p as EvidenceInvocationRecord, r as EvidencePacketReference, s as DEFAULT_EVIDENCE_PACKET_TYPE, t as EvidenceGenerationContext, u as EVIDENCE_PACKET_SCHEMA_VERSION, v as EvidencePacketReadPort, w as GenerateEvidencePacketInput, x as EvidenceRecord, y as EvidencePacketRendererInput } from "../index-
|
|
1
|
+
import { A as canonicalJson, C as EvidenceTimestamp, D as createEvidencePacketRenderer, E as computeEvidencePacketChecksum, O as generateEvidencePacket, S as EvidenceScope, T as GenerateEvidencePacketOptions, _ as EvidencePacketPayload, a as EvidencePacketSubject, b as EvidencePacketRendererOptions, c as DEFAULT_EVIDENCE_REDACTION_REASON, d as EvidenceEventQuery, f as EvidenceEventRecord, g as EvidencePacketGenerationErrorCode, h as EvidencePacketGenerationError, i as EvidencePacketStatus, k as verifyEvidencePacketChecksum, l as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, m as EvidenceJsonRecord, n as EvidencePacketEnvelope, o as EvidenceRenderer, p as EvidenceInvocationRecord, r as EvidencePacketReference, s as DEFAULT_EVIDENCE_PACKET_TYPE, t as EvidenceGenerationContext, u as EVIDENCE_PACKET_SCHEMA_VERSION, v as EvidencePacketReadPort, w as GenerateEvidencePacketInput, x as EvidenceRecord, y as EvidencePacketRendererInput } from "../index-BdW9UuCw.js";
|
|
2
2
|
export { DEFAULT_EVIDENCE_PACKET_TYPE, DEFAULT_EVIDENCE_REDACTION_REASON, EVIDENCE_PACKET_CHECKSUM_ALGORITHM, EVIDENCE_PACKET_SCHEMA_VERSION, type EvidenceEventQuery, type EvidenceEventRecord, EvidenceGenerationContext, type EvidenceInvocationRecord, type EvidenceJsonRecord, EvidencePacketEnvelope, EvidencePacketGenerationError, type EvidencePacketGenerationErrorCode, type EvidencePacketPayload, type EvidencePacketReadPort, EvidencePacketReference, type EvidencePacketRendererInput, type EvidencePacketRendererOptions, EvidencePacketStatus, EvidencePacketSubject, type EvidenceRecord, EvidenceRenderer, type EvidenceScope, type EvidenceTimestamp, type GenerateEvidencePacketInput, type GenerateEvidencePacketOptions, canonicalJson, computeEvidencePacketChecksum, createEvidencePacketRenderer, generateEvidencePacket, verifyEvidencePacketChecksum };
|
package/dist/evidence/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as DEFAULT_EVIDENCE_PACKET_TYPE, n as DEFAULT_EVIDENCE_REDACTION_REASON, r as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, i as EVIDENCE_PACKET_SCHEMA_VERSION, a as EvidencePacketGenerationError, u as canonicalJson, o as computeEvidencePacketChecksum, s as createEvidencePacketRenderer, c as generateEvidencePacket, l as verifyEvidencePacketChecksum } from "../evidence-
|
|
1
|
+
export { t as DEFAULT_EVIDENCE_PACKET_TYPE, n as DEFAULT_EVIDENCE_REDACTION_REASON, r as EVIDENCE_PACKET_CHECKSUM_ALGORITHM, i as EVIDENCE_PACKET_SCHEMA_VERSION, a as EvidencePacketGenerationError, u as canonicalJson, o as computeEvidencePacketChecksum, s as createEvidencePacketRenderer, c as generateEvidencePacket, l as verifyEvidencePacketChecksum } from "../evidence-Bf8dz-BP.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FABRIC_ID_PREFIXES, createFabricId } from "./ids/index.js";
|
|
2
|
-
import { n as AnonymizationEngine } from "./engine-
|
|
2
|
+
import { n as AnonymizationEngine } from "./engine-CG-OKfsX.js";
|
|
3
3
|
import { createHash } from "crypto";
|
|
4
4
|
//#region evidence/canonical-json.ts
|
|
5
5
|
/**
|
|
@@ -300,6 +300,7 @@ function assertRequestedInvocation(record, path, requested) {
|
|
|
300
300
|
if (typeof invocationId !== "string" || !requested.has(invocationId)) throw new EvidencePacketGenerationError("EVIDENCE_UNREQUESTED_RECORD", `${path}.actionInvocationId (${String(invocationId)}) is not one of the requested action invocations.`);
|
|
301
301
|
}
|
|
302
302
|
const REFERENCE_METADATA_FIELDS = {
|
|
303
|
+
accessEvents: ["resourceType", "resourceId"],
|
|
303
304
|
actionInvocations: ["actionId", "status"],
|
|
304
305
|
events: [
|
|
305
306
|
"eventType",
|
|
@@ -321,6 +322,7 @@ const REFERENCE_METADATA_FIELDS = {
|
|
|
321
322
|
]
|
|
322
323
|
};
|
|
323
324
|
const REFERENCE_TYPES = {
|
|
325
|
+
accessEvents: "access_event",
|
|
324
326
|
actionInvocations: "action_invocation",
|
|
325
327
|
events: "asset_event",
|
|
326
328
|
policyEvaluations: "policy_evaluation",
|
|
@@ -336,9 +338,10 @@ function buildReferences(payload, unredacted) {
|
|
|
336
338
|
"actionInvocations",
|
|
337
339
|
"events",
|
|
338
340
|
"policyEvaluations",
|
|
339
|
-
"adapterInvocations"
|
|
340
|
-
|
|
341
|
-
|
|
341
|
+
"adapterInvocations",
|
|
342
|
+
"accessEvents"
|
|
343
|
+
].flatMap((section) => (payload[section] ?? []).map((record, index) => {
|
|
344
|
+
const originalId = unredacted[section]?.[index]?.id;
|
|
342
345
|
if (!record || typeof record !== "object" || record.id !== originalId) throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", `The anonymization policy removed or changed ${section}[${index}].id; record ids must pass through redaction unchanged.`);
|
|
343
346
|
const metadata = {};
|
|
344
347
|
for (const field of REFERENCE_METADATA_FIELDS[section]) {
|
|
@@ -375,6 +378,10 @@ function computeEvidencePacketChecksum(payload) {
|
|
|
375
378
|
* deterministically; redact the payload with `AnonymizationEngine` (data class
|
|
376
379
|
* `"evidence_packet"`); checksum the redacted payload; and build references from it.
|
|
377
380
|
*
|
|
381
|
+
* The optional access-event reader runs with resolved subjects even without invocation ids. Its
|
|
382
|
+
* section is omitted when the reader is absent, preserving existing payloads and checksums; an
|
|
383
|
+
* empty reader result includes `accessEvents: []`. normalizeSection validates its scope and shape.
|
|
384
|
+
*
|
|
378
385
|
* Nothing is persisted. Storing the returned envelope is the application's job.
|
|
379
386
|
*/
|
|
380
387
|
async function generateEvidencePacket(input, context, options) {
|
|
@@ -417,16 +424,18 @@ async function generateEvidencePacket(input, context, options) {
|
|
|
417
424
|
if (!method || invocationIds.length === 0) return [];
|
|
418
425
|
return method.call(port, { ...scope }, [...invocationIds]);
|
|
419
426
|
};
|
|
420
|
-
const [rawActionInvocations, rawPolicyEvaluations, rawAdapterInvocations] = await Promise.all([
|
|
427
|
+
const [rawActionInvocations, rawPolicyEvaluations, rawAdapterInvocations, rawAccessEvents] = await Promise.all([
|
|
421
428
|
read(port.listActionInvocations),
|
|
422
429
|
read(port.listPolicyEvaluations),
|
|
423
|
-
read(port.listAdapterInvocations)
|
|
430
|
+
read(port.listAdapterInvocations),
|
|
431
|
+
(async () => port.listAccessEvents?.({ ...scope }, subjects.map((subject) => ({ ...subject }))))()
|
|
424
432
|
]);
|
|
425
433
|
const actionInvocations = normalizeSection(rawActionInvocations, scope, "actionInvocations", (record, path) => {
|
|
426
434
|
if (!requested.has(record.id)) throw new EvidencePacketGenerationError("EVIDENCE_UNREQUESTED_RECORD", `${path}.id (${String(record.id)}) is not one of the requested action invocations.`);
|
|
427
435
|
}).sort(byId);
|
|
428
436
|
const policyEvaluations = normalizeSection(rawPolicyEvaluations, scope, "policyEvaluations", (record, path) => assertRequestedInvocation(record, path, requested)).sort(byId);
|
|
429
437
|
const adapterInvocations = normalizeSection(rawAdapterInvocations, scope, "adapterInvocations", (record, path) => assertRequestedInvocation(record, path, requested)).sort(byId);
|
|
438
|
+
const accessEvents = port.listAccessEvents ? normalizeSection(rawAccessEvents, scope, "accessEvents").sort(byId) : void 0;
|
|
430
439
|
const generatedAt = context.now ?? /* @__PURE__ */ new Date();
|
|
431
440
|
const id = options.idFactory ? options.idFactory() : createFabricId(FABRIC_ID_PREFIXES.complianceEvidencePacket);
|
|
432
441
|
assertNonEmptyString(id, "packet id");
|
|
@@ -442,7 +451,8 @@ async function generateEvidencePacket(input, context, options) {
|
|
|
442
451
|
events,
|
|
443
452
|
actionInvocations,
|
|
444
453
|
policyEvaluations,
|
|
445
|
-
adapterInvocations
|
|
454
|
+
adapterInvocations,
|
|
455
|
+
...accessEvents !== void 0 ? { accessEvents } : {}
|
|
446
456
|
};
|
|
447
457
|
const anonymizationContext = {
|
|
448
458
|
tenantId: scope.tenantId,
|
|
@@ -453,7 +463,7 @@ async function generateEvidencePacket(input, context, options) {
|
|
|
453
463
|
...options.anonymization?.policyVersion !== void 0 ? { policyVersion: options.anonymization.policyVersion } : {}
|
|
454
464
|
};
|
|
455
465
|
const keySorted = JSON.parse(canonicalJson(unredacted));
|
|
456
|
-
const redacted = new AnonymizationEngine(options.anonymizationPolicy).anonymizeJsonValue(keySorted, anonymizationContext, "evidence_packet");
|
|
466
|
+
const redacted = new AnonymizationEngine(options.anonymizationPolicy, { beforeChecksumKey: options.anonymization?.beforeChecksumKey }).anonymizeJsonValue(keySorted, anonymizationContext, "evidence_packet");
|
|
457
467
|
const payload = {
|
|
458
468
|
...redacted.value,
|
|
459
469
|
redaction: redacted.metadata
|
|
@@ -462,7 +472,8 @@ async function generateEvidencePacket(input, context, options) {
|
|
|
462
472
|
"events",
|
|
463
473
|
"actionInvocations",
|
|
464
474
|
"policyEvaluations",
|
|
465
|
-
"adapterInvocations"
|
|
475
|
+
"adapterInvocations",
|
|
476
|
+
...accessEvents !== void 0 ? ["accessEvents"] : []
|
|
466
477
|
]) if (!Array.isArray(payload[section])) throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", `The anonymization policy removed or replaced payload.${section}.`);
|
|
467
478
|
const envelopeSubjects = redactedSubjects(payload.subjects);
|
|
468
479
|
const references = buildReferences(payload, keySorted);
|
|
@@ -513,4 +524,4 @@ function createEvidencePacketRenderer(options) {
|
|
|
513
524
|
//#endregion
|
|
514
525
|
export { EvidencePacketGenerationError as a, generateEvidencePacket as c, EVIDENCE_PACKET_SCHEMA_VERSION as i, verifyEvidencePacketChecksum as l, DEFAULT_EVIDENCE_REDACTION_REASON as n, computeEvidencePacketChecksum as o, EVIDENCE_PACKET_CHECKSUM_ALGORITHM as r, createEvidencePacketRenderer as s, DEFAULT_EVIDENCE_PACKET_TYPE as t, canonicalJson as u };
|
|
515
526
|
|
|
516
|
-
//# sourceMappingURL=evidence-
|
|
527
|
+
//# sourceMappingURL=evidence-Bf8dz-BP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-Bf8dz-BP.js","names":[],"sources":["../evidence/canonical-json.ts","../evidence/generator.ts"],"sourcesContent":["/**\n * Serializes a value as JSON with every object's keys sorted, recursively, by UTF-16 code-unit\n * order (locale-independent). Array order is preserved.\n *\n * This is the serialization behind the `fabric-canonical-json-sha256-v1` digests: Host's\n * parameter digests (`PARAMETER_DIGEST_ALGORITHM` in `@fabricorg/platform-host`) and evidence\n * packet checksums (`EVIDENCE_PACKET_CHECKSUM_ALGORITHM`). Its output is a durable contract, so it\n * must not change for any input. That includes values that are not plain JSON: any non-array\n * object is rebuilt from its own enumerable entries before serialization, so a `Date` (which has\n * none) serializes as `{}`, not as an ISO string. Convert dates to strings before calling this\n * when they must survive into the output.\n */\nexport function canonicalJson(value: unknown): string {\n\treturn JSON.stringify(sortKeys(value));\n}\n\nfunction sortKeys(value: unknown): unknown {\n\tif (Array.isArray(value)) return value.map(sortKeys);\n\tif (value && typeof value === \"object\") {\n\t\treturn Object.fromEntries(Object.entries(value as Record<string, unknown>)\n\t\t\t.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)\n\t\t\t.map(([key, child]) => [key, sortKeys(child)]));\n\t}\n\treturn value;\n}\n","import { createHash } from \"node:crypto\";\n\nimport { createFabricId, FABRIC_ID_PREFIXES } from \"../ids\";\nimport type { AnonymizationContext, AnonymizationMetadata, PlatformDataClass, RedactionMethod } from \"../privacy\";\nimport { AnonymizationEngine, type AnonymizationPolicy } from \"../privacy/engine\";\nimport { canonicalJson } from \"./canonical-json\";\nimport type {\n\tEvidenceGenerationContext,\n\tEvidencePacketEnvelope,\n\tEvidencePacketReference,\n\tEvidencePacketSubject,\n\tEvidenceRenderer,\n} from \"./index\";\n\n/**\n * Identifies how `EvidencePacketEnvelope.checksum` is computed: the lowercase hex SHA-256 of\n * `canonicalJson(payload)`. It is the same identifier as Host's `PARAMETER_DIGEST_ALGORITHM`\n * because the algorithm is the same (SHA-256 over `canonicalJson`).\n */\nexport const EVIDENCE_PACKET_CHECKSUM_ALGORITHM = \"fabric-canonical-json-sha256-v1\" as const;\n\n/** Version of the `EvidencePacketPayload` shape produced by `generateEvidencePacket`. */\nexport const EVIDENCE_PACKET_SCHEMA_VERSION = 1 as const;\n\n/** Packet type used when the input does not name one. */\nexport const DEFAULT_EVIDENCE_PACKET_TYPE = \"compliance_evidence\";\n\n/** Anonymization reason recorded when the caller does not supply one. */\nexport const DEFAULT_EVIDENCE_REDACTION_REASON = \"evidence_packet_generation\";\n\n/**\n * A `Date`, or an ISO 8601 / PostgreSQL ISO date-time string with an explicit zone (`Z`, `+HH`,\n * `+HH:MM` or `+HHMM`). Zone-less, non-ISO and out-of-range strings are rejected.\n */\nexport type EvidenceTimestamp = Date | string;\n\n/** Tenant and space every record read through the port must belong to. */\nexport interface EvidenceScope {\n\ttenantId: string;\n\tspaceId: string;\n}\n\n/**\n * An asset event as the read port returns it. `AssetEventEnvelope` (events) and `AssetEventLike`\n * (privacy) both satisfy this shape. Fields beyond these are kept in the packet payload.\n */\nexport interface EvidenceEventRecord {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\tsubjectType: string;\n\tsubjectId: string;\n\tactionInvocationId?: string | null;\n\tsequence: number;\n\toccurredAt: EvidenceTimestamp;\n\trecordedAt: EvidenceTimestamp;\n\tcorrelationId: string;\n\tcausationId?: string | null;\n\tpayload: unknown;\n}\n\n/** An action invocation, policy evaluation or adapter invocation record. Further fields are kept. */\nexport interface EvidenceRecord {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\tactionInvocationId?: string | null;\n\t[field: string]: unknown;\n}\n\n/** A record that belongs to one action invocation (policy evaluations, adapter invocations). */\nexport interface EvidenceInvocationRecord extends EvidenceRecord {\n\tactionInvocationId: string;\n}\n\nexport interface EvidenceEventQuery extends EvidenceScope {\n\t/** The subject the packet is about. Always `subjects[0]`. */\n\tprimarySubject: EvidencePacketSubject;\n\t/** Primary subject first, then related subjects, de-duplicated and sorted. */\n\tsubjects: readonly EvidencePacketSubject[];\n}\n\ntype MaybePromise<T> = T | Promise<T>;\n\n/**\n * Read-only access to the application's evidence store. The generator filters nothing the port\n * returns; it rejects, rather than drops, a record that fails one of its checks:\n *\n * - every record must have the generation context's `tenantId` and `spaceId`;\n * - action invocations must have one of the requested ids, and policy evaluations and adapter\n * invocations one of the requested `actionInvocationId`s;\n * - records must have the documented shape (non-empty `id`, event fields, parseable timestamps) and\n * be representable as plain JSON, and any `_privacy` value must be engine-written\n * `AnonymizationMetadata`.\n *\n * Which events and access events belong to the packet is the port's decision: the generator does\n * not check their subjects against `subjects`, because a port may legitimately return events\n * recorded against the primary subject's own action invocations under another subject. Access events\n * use the resolved subjects independently of invocation ids. Without their reader the section is\n * omitted; a reader returning an empty array produces `accessEvents: []`.\n */\nexport interface EvidencePacketReadPort {\n\t/** Asset events for the given subjects within the tenant and space. Required. */\n\tlistEvents(query: EvidenceEventQuery): MaybePromise<readonly EvidenceEventRecord[]>;\n\t/** The application's related-subject rules. When absent, only the primary subject is used. */\n\tresolveRelatedSubjects?(\n\t\tsubject: EvidencePacketSubject,\n\t\tscope: EvidenceScope,\n\t): MaybePromise<readonly EvidencePacketSubject[]>;\n\t/** Action invocations by id. When absent, the section is empty. */\n\tlistActionInvocations?(scope: EvidenceScope, ids: readonly string[]): MaybePromise<readonly EvidenceRecord[]>;\n\t/** Policy evaluations for the given action invocations. When absent, the section is empty. */\n\tlistPolicyEvaluations?(\n\t\tscope: EvidenceScope,\n\t\tactionInvocationIds: readonly string[],\n\t): MaybePromise<readonly EvidenceInvocationRecord[]>;\n\t/** Adapter invocations for the given action invocations. When absent, the section is empty. */\n\tlistAdapterInvocations?(\n\t\tscope: EvidenceScope,\n\t\tactionInvocationIds: readonly string[],\n\t): MaybePromise<readonly EvidenceInvocationRecord[]>;\n\t/** Access records selected by the port for these subjects. Absent reader omits the section. */\n\tlistAccessEvents?(scope: EvidenceScope, subjects: readonly EvidencePacketSubject[]): MaybePromise<readonly EvidenceRecord[]>;\n}\n\n/** A record after normalization: plain JSON, every `Date` replaced by its ISO string. */\nexport type EvidenceJsonRecord = Record<string, unknown>;\n\nexport interface EvidencePacketPayload {\n\tschemaVersion: typeof EVIDENCE_PACKET_SCHEMA_VERSION;\n\tpacketType: string;\n\ttenantId: string;\n\tspaceId: string;\n\tprimarySubject: EvidencePacketSubject;\n\tsubjects: EvidencePacketSubject[];\n\tincludeRelated: boolean;\n\tpolicyVersion?: string;\n\tevents: EvidenceJsonRecord[];\n\tactionInvocations: EvidenceJsonRecord[];\n\tpolicyEvaluations: EvidenceJsonRecord[];\n\tadapterInvocations: EvidenceJsonRecord[];\n\taccessEvents?: EvidenceJsonRecord[];\n\t/** Metadata from the anonymization pass over the rest of the payload (validated engine-written `_privacy` values pass through it unchanged). */\n\tredaction: AnonymizationMetadata;\n}\n\nexport interface GenerateEvidencePacketInput {\n\tsubject: EvidencePacketSubject;\n\t/** Set to `false` to skip `resolveRelatedSubjects`. Defaults to `true`. */\n\tincludeRelated?: boolean;\n\t/** Defaults to `DEFAULT_EVIDENCE_PACKET_TYPE`. */\n\tpacketType?: string;\n\tpolicyVersion?: string;\n}\n\nexport interface GenerateEvidencePacketOptions {\n\tport: EvidencePacketReadPort;\n\t/**\n\t * Redaction applied to the whole payload with data class `\"evidence_packet\"`. Required so that\n\t * redaction cannot be skipped by omission; pass `{ fields: [] }` to redact nothing.\n\t */\n\tanonymizationPolicy: AnonymizationPolicy;\n\tanonymization?: {\n\t\t/** Secret key for HMAC-SHA256 field before-checksums. */\n\t\tbeforeChecksumKey?: string | Uint8Array;\n\t\t/** Defaults to the packet id. */\n\t\tjobId?: string;\n\t\t/** Defaults to `DEFAULT_EVIDENCE_REDACTION_REASON`. */\n\t\treason?: string;\n\t\t/** Defaults to the engine's `ANONYMIZATION_POLICY_VERSION`. */\n\t\tpolicyVersion?: string;\n\t};\n\t/** Defaults to `createFabricId(FABRIC_ID_PREFIXES.complianceEvidencePacket)`. */\n\tidFactory?: () => string;\n}\n\nexport type EvidencePacketGenerationErrorCode =\n\t| \"EVIDENCE_SCOPE_MISMATCH\"\n\t| \"EVIDENCE_INVALID_RECORD\"\n\t| \"EVIDENCE_UNREQUESTED_RECORD\"\n\t| \"EVIDENCE_IDENTIFIER_REDACTED\";\n\nexport class EvidencePacketGenerationError extends Error {\n\treadonly code: EvidencePacketGenerationErrorCode;\n\n\tconstructor(code: EvidencePacketGenerationErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"EvidencePacketGenerationError\";\n\t\tthis.code = code;\n\t}\n}\n\ntype Section = \"events\" | \"actionInvocations\" | \"policyEvaluations\" | \"adapterInvocations\" | \"accessEvents\";\n\nfunction compareStrings(left: string, right: string): number {\n\treturn left < right ? -1 : left > right ? 1 : 0;\n}\n\nfunction assertNonEmptyString(value: unknown, what: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${what} must be a non-empty string.`);\n\t}\n}\n\nfunction isPlainObject(value: object): boolean {\n\tconst prototype = Object.getPrototypeOf(value);\n\treturn prototype === Object.prototype || prototype === null;\n}\n\ntype MissingMembers<TUnion, TList extends readonly unknown[]> = Exclude<TUnion, TList[number]>;\n\n/**\n * Returns the list unchanged, but fails type-checking unless it names every member of `TUnion`\n * (and nothing else), so these runtime lists cannot drift from the privacy types.\n */\nfunction exhaustiveList<TUnion>() {\n\treturn <const TList extends readonly TUnion[]>(\n\t\tlist: TList & ([MissingMembers<TUnion, TList>] extends [never] ? unknown : { missingMembers: MissingMembers<TUnion, TList> }),\n\t): TList => list;\n}\n\n// Plain frozen arrays, not Sets: the boundary guard forbids module-scope Map/Set state in core.\nconst REDACTION_METHODS: readonly RedactionMethod[] = Object.freeze(\n\texhaustiveList<RedactionMethod>()([\"nullify\", \"mask\", \"hash\", \"tokenize\", \"generalize\", \"drop\"]),\n);\nconst PLATFORM_DATA_CLASSES: readonly PlatformDataClass[] = Object.freeze(\n\texhaustiveList<PlatformDataClass>()([\n\t\t\"asset_event\",\n\t\t\"read_model\",\n\t\t\"raw_payload\",\n\t\t\"evidence_packet\",\n\t\t\"action_evidence\",\n\t\t\"access_event\",\n\t]),\n);\nconst SHA256_HEX = /^[0-9a-f]{64}$/;\n\n/** True for a string `Date.prototype.toISOString` produced: it parses and serializes back unchanged. */\nfunction isIsoInstant(value: unknown): boolean {\n\tif (typeof value !== \"string\") return false;\n\tconst date = new Date(value);\n\treturn !Number.isNaN(date.getTime()) && date.toISOString() === value;\n}\nconst METADATA_STRING_KEYS = [\"policyVersion\", \"jobId\", \"redactedAt\", \"reason\"] as const;\nconst METADATA_KEYS: readonly string[] = Object.freeze([\n\t\"redactionState\",\n\t\"dataClass\",\n\t\"redactedFields\",\n\t\"preservedSemantics\",\n\t...METADATA_STRING_KEYS,\n]);\nconst FIELD_METADATA_KEYS: readonly string[] = Object.freeze([\n\t\"fieldPath\",\n\t\"method\",\n\t\"policyVersion\",\n\t\"jobId\",\n\t\"redactedAt\",\n\t\"reason\",\n\t\"dataClass\",\n\t\"beforeChecksum\",\n]);\n\nfunction hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {\n\tconst present = Object.keys(value);\n\treturn present.length === keys.length && present.every((key) => keys.includes(key));\n}\n\n/**\n * `AnonymizationEngine` copies any `_privacy` key through unredacted, because that is where it\n * stores its own metadata. A `_privacy` value from the port is therefore accepted only when it has\n * exactly the shape the engine writes; anything else could carry raw data past the policy.\n */\nfunction assertEnginePrivacyMetadata(value: unknown, path: string): void {\n\tconst invalid = (detail: string) =>\n\t\tnew EvidencePacketGenerationError(\n\t\t\t\"EVIDENCE_INVALID_RECORD\",\n\t\t\t`${path} is not engine-written AnonymizationMetadata (${detail}); _privacy values bypass the anonymization policy, so other data is rejected.`,\n\t\t);\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalid(\"not an object\");\n\tconst metadata = value as Record<string, unknown>;\n\tif (!hasExactKeys(metadata, METADATA_KEYS)) throw invalid(\"unexpected or missing keys\");\n\tif (metadata.redactionState !== \"anonymized\") throw invalid(\"redactionState\");\n\tif (!PLATFORM_DATA_CLASSES.includes(metadata.dataClass as PlatformDataClass)) throw invalid(\"dataClass\");\n\tfor (const key of METADATA_STRING_KEYS) {\n\t\tif (typeof metadata[key] !== \"string\") throw invalid(key);\n\t}\n\t// The engine writes redactedAt with toISOString() (privacy/engine.ts buildMetadata).\n\tif (!isIsoInstant(metadata.redactedAt)) throw invalid(\"redactedAt\");\n\tif (!Array.isArray(metadata.preservedSemantics) || !metadata.preservedSemantics.every((item) => typeof item === \"string\")) {\n\t\tthrow invalid(\"preservedSemantics\");\n\t}\n\tif (!Array.isArray(metadata.redactedFields)) throw invalid(\"redactedFields\");\n\tmetadata.redactedFields.forEach((field, index) => {\n\t\tif (!field || typeof field !== \"object\" || Array.isArray(field)) throw invalid(`redactedFields[${index}]`);\n\t\tconst entry = field as Record<string, unknown>;\n\t\tif (!hasExactKeys(entry, FIELD_METADATA_KEYS) || !Object.values(entry).every((item) => typeof item === \"string\")) {\n\t\t\tthrow invalid(`redactedFields[${index}]`);\n\t\t}\n\t\tif (!REDACTION_METHODS.includes(entry.method as RedactionMethod)) throw invalid(`redactedFields[${index}].method`);\n\t\tif (!PLATFORM_DATA_CLASSES.includes(entry.dataClass as PlatformDataClass)) throw invalid(`redactedFields[${index}].dataClass`);\n\t\t// The engine writes these with toISOString() and a SHA-256 hex digest (privacy/engine.ts\n\t\t// anonymizeRecursive and stableChecksum), so free text in them is not engine output.\n\t\tif (!isIsoInstant(entry.redactedAt)) throw invalid(`redactedFields[${index}].redactedAt`);\n\t\tif (!SHA256_HEX.test(entry.beforeChecksum as string)) throw invalid(`redactedFields[${index}].beforeChecksum`);\n\t});\n}\n\n/**\n * Converts a value to plain JSON. Dates become ISO strings; objects with a `toJSON` method (for\n * example decimal types) are replaced by its result. Values JSON cannot represent faithfully\n * (bigint, functions, symbols, non-finite numbers, invalid dates, other non-plain objects, an own\n * `__proto__` key) throw rather than being silently changed or dropped. Objects are rebuilt with a\n * null prototype, so later reads cannot pick up inherited fields.\n */\nfunction toPlainJson(value: unknown, path: string): unknown {\n\tif (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n\tif (typeof value === \"number\") {\n\t\tif (!Number.isFinite(value)) {\n\t\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} is not a finite number.`);\n\t\t}\n\t\treturn value;\n\t}\n\tif (value instanceof Date) {\n\t\tif (Number.isNaN(value.getTime())) {\n\t\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} is an invalid Date.`);\n\t\t}\n\t\treturn value.toISOString();\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn value.map((item, index) => (item === undefined ? null : toPlainJson(item, `${path}[${index}]`)));\n\t}\n\tif (typeof value === \"object\") {\n\t\tif (!isPlainObject(value)) {\n\t\t\tconst toJSON = (value as { toJSON?: unknown }).toJSON;\n\t\t\tif (typeof toJSON === \"function\") return toPlainJson(toJSON.call(value), path);\n\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\"EVIDENCE_INVALID_RECORD\",\n\t\t\t\t`${path} is a non-plain object that cannot be represented as JSON.`,\n\t\t\t);\n\t\t}\n\t\tconst result: Record<string, unknown> = Object.create(null);\n\t\tfor (const [key, child] of Object.entries(value)) {\n\t\t\tif (key === \"__proto__\") {\n\t\t\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} has an own \"__proto__\" key.`);\n\t\t\t}\n\t\t\tif (child === undefined) continue;\n\t\t\tconst normalized = toPlainJson(child, `${path}.${key}`);\n\t\t\tif (key === \"_privacy\") assertEnginePrivacyMetadata(normalized, `${path}._privacy`);\n\t\t\tresult[key] = normalized;\n\t\t}\n\t\treturn result;\n\t}\n\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} has unsupported type ${typeof value}.`);\n}\n\n/**\n * The timestamp strings accepted for event `occurredAt`/`recordedAt`: an ISO 8601 date-time, or\n * PostgreSQL's ISO output, with an explicit zone. Groups: year (4 digits, or a sign and 4 to 6\n * digits), month, day, `T` or one space, hour, minute, optional second and fraction, an optional\n * single space, then `Z` or an offset written `+HH`, `+HH:MM` or `+HHMM`.\n */\nconst ZONED_TIMESTAMP =\n\t/^([+-]\\d{4,6}|\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.(\\d+))?)? ?(?:(Z)|([+-])(\\d{2})(?::?(\\d{2}))?)$/;\n\nfunction isLeapYear(year: number): boolean {\n\treturn (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n}\n\nfunction daysInMonth(year: number, month: number): number {\n\treturn [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]!;\n}\n\n/**\n * Parses a zoned timestamp string without `new Date(string)`, whose fallback parser accepts\n * non-ISO text and rolls invalid dates over. Every field is range-checked, so the result is exactly\n * the instant written, truncated to milliseconds. Returns `undefined` when the string is not\n * accepted. A zone-less string is rejected because reading it would depend on the host's time zone.\n */\nfunction parseZonedTimestamp(value: string): number | undefined {\n\tconst match = ZONED_TIMESTAMP.exec(value);\n\tif (!match) return undefined;\n\tconst [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zulu, sign, offsetHourText, offsetMinuteText] = match;\n\tconst year = Number(yearText);\n\tconst month = Number(monthText);\n\tconst day = Number(dayText);\n\tconst hour = Number(hourText);\n\tconst minute = Number(minuteText);\n\tconst second = secondText === undefined ? 0 : Number(secondText);\n\tconst millisecond = fraction === undefined ? 0 : Number(fraction.padEnd(3, \"0\").slice(0, 3));\n\tif (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return undefined;\n\tif (hour > 23 || minute > 59 || second > 59) return undefined;\n\tlet offsetMinutes = 0;\n\tif (!zulu) {\n\t\tconst offsetHours = Number(offsetHourText);\n\t\tconst offsetMinutePart = offsetMinuteText === undefined ? 0 : Number(offsetMinuteText);\n\t\tif (offsetHours > 14 || offsetMinutePart > 59) return undefined;\n\t\toffsetMinutes = (sign === \"-\" ? -1 : 1) * (offsetHours * 60 + offsetMinutePart);\n\t}\n\t// setUTCFullYear, not Date.UTC: Date.UTC maps years 0-99 to 1900-1999.\n\tconst date = new Date(0);\n\tdate.setUTCFullYear(year, month - 1, day);\n\tdate.setUTCHours(hour, minute, second, millisecond);\n\tconst instant = date.getTime() - offsetMinutes * 60_000;\n\treturn Number.isNaN(new Date(instant).getTime()) ? undefined : instant;\n}\n\n/** Normalizes a Date or zoned timestamp string to an ISO string, at millisecond precision. */\nfunction toIsoTimestamp(value: unknown, path: string): string {\n\tconst instant =\n\t\tvalue instanceof Date ? value.getTime() : typeof value === \"string\" ? parseZonedTimestamp(value) : undefined;\n\tif (instant === undefined || Number.isNaN(instant)) {\n\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\"EVIDENCE_INVALID_RECORD\",\n\t\t\t`${path} must be a Date or an ISO 8601 / PostgreSQL ISO date-time with a valid date, time and explicit zone (Z, +HH, +HH:MM or +HHMM).`,\n\t\t);\n\t}\n\treturn new Date(instant).toISOString();\n}\n\nfunction assertScope(record: { id?: unknown; tenantId?: unknown; spaceId?: unknown }, scope: EvidenceScope, section: Section): void {\n\tif (record.tenantId !== scope.tenantId || record.spaceId !== scope.spaceId) {\n\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\"EVIDENCE_SCOPE_MISMATCH\",\n\t\t\t`Evidence read port returned a ${section} record (${String(record.id)}) outside tenant/space ${scope.tenantId}/${scope.spaceId}.`,\n\t\t);\n\t}\n}\n\n/** Normalizes, scope-checks and de-duplicates a section. Conflicting records with one id throw. */\nfunction normalizeSection(\n\trecords: readonly object[],\n\tscope: EvidenceScope,\n\tsection: Section,\n\tprepare?: (record: EvidenceJsonRecord, path: string) => void,\n): EvidenceJsonRecord[] {\n\tif (!Array.isArray(records)) {\n\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `Evidence read port returned a non-array for ${section}.`);\n\t}\n\tconst byId = new Map<string, { record: EvidenceJsonRecord; canonical: string }>();\n\trecords.forEach((raw, index) => {\n\t\tconst path = `${section}[${index}]`;\n\t\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} must be an object.`);\n\t\t}\n\t\t// Scope is checked on the record as returned, before anything else is done with it, and again\n\t\t// on the normalized record, which is authoritative: it has a null prototype (so inherited\n\t\t// fields cannot satisfy the check) and reflects any toJSON conversion.\n\t\tassertScope(raw as EvidenceRecord, scope, section);\n\t\tconst record = toPlainJson(raw, path) as EvidenceJsonRecord;\n\t\tassertScope(record, scope, section);\n\t\tassertNonEmptyString(record.id, `${path}.id`);\n\t\tprepare?.(record, path);\n\t\tconst canonical = canonicalJson(record);\n\t\tconst existing = byId.get(record.id as string);\n\t\tif (existing && existing.canonical !== canonical) {\n\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\"EVIDENCE_INVALID_RECORD\",\n\t\t\t\t`Evidence read port returned conflicting ${section} records with id ${String(record.id)}.`,\n\t\t\t);\n\t\t}\n\t\tbyId.set(record.id as string, { record, canonical });\n\t});\n\treturn [...byId.values()].map((entry) => entry.record);\n}\n\nfunction byId(left: EvidenceJsonRecord, right: EvidenceJsonRecord): number {\n\treturn compareStrings(left.id as string, right.id as string);\n}\n\nfunction byEventOrder(left: EvidenceJsonRecord, right: EvidenceJsonRecord): number {\n\t// Compare instants, not ISO strings: extended years (+010000-...) do not sort as strings.\n\treturn (\n\t\tDate.parse(left.occurredAt as string) - Date.parse(right.occurredAt as string) ||\n\t\tDate.parse(left.recordedAt as string) - Date.parse(right.recordedAt as string) ||\n\t\t(left.sequence as number) - (right.sequence as number) ||\n\t\tbyId(left, right)\n\t);\n}\n\nfunction assertSubject(subject: EvidencePacketSubject, path: string): EvidencePacketSubject {\n\tif (!subject || typeof subject !== \"object\") {\n\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path} must be an object.`);\n\t}\n\tassertNonEmptyString(subject.subjectType, `${path}.subjectType`);\n\tassertNonEmptyString(subject.subjectId, `${path}.subjectId`);\n\treturn { subjectType: subject.subjectType, subjectId: subject.subjectId };\n}\n\nfunction compareSubjects(left: EvidencePacketSubject, right: EvidencePacketSubject): number {\n\treturn compareStrings(left.subjectType, right.subjectType) || compareStrings(left.subjectId, right.subjectId);\n}\n\nasync function resolveSubjects(\n\tprimary: EvidencePacketSubject,\n\tincludeRelated: boolean,\n\tport: EvidencePacketReadPort,\n\tscope: EvidenceScope,\n): Promise<EvidencePacketSubject[]> {\n\tif (!includeRelated || !port.resolveRelatedSubjects) return [primary];\n\tconst related = await port.resolveRelatedSubjects({ ...primary }, { ...scope });\n\tif (!Array.isArray(related)) {\n\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", \"resolveRelatedSubjects must return an array.\");\n\t}\n\tconst key = (subject: EvidencePacketSubject) => `${subject.subjectType}\\u0000${subject.subjectId}`;\n\tconst seen = new Set([key(primary)]);\n\tconst unique: EvidencePacketSubject[] = [];\n\trelated.forEach((candidate, index) => {\n\t\tconst subject = assertSubject(candidate, `relatedSubjects[${index}]`);\n\t\tif (seen.has(key(subject))) return;\n\t\tseen.add(key(subject));\n\t\tunique.push(subject);\n\t});\n\treturn [primary, ...unique.sort(compareSubjects)];\n}\n\nfunction assertRequestedInvocation(record: EvidenceJsonRecord, path: string, requested: ReadonlySet<string>): void {\n\tconst invocationId = record.actionInvocationId;\n\tif (typeof invocationId !== \"string\" || !requested.has(invocationId)) {\n\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\"EVIDENCE_UNREQUESTED_RECORD\",\n\t\t\t`${path}.actionInvocationId (${String(invocationId)}) is not one of the requested action invocations.`,\n\t\t);\n\t}\n}\n\nconst REFERENCE_METADATA_FIELDS: Record<Section, readonly string[]> = {\n\taccessEvents: [\"resourceType\", \"resourceId\"],\n\tactionInvocations: [\"actionId\", \"status\"],\n\tevents: [\"eventType\", \"subjectType\", \"subjectId\", \"actionInvocationId\"],\n\tpolicyEvaluations: [\"actionInvocationId\", \"policyId\", \"policyVersion\", \"result\"],\n\tadapterInvocations: [\"actionInvocationId\", \"adapterType\", \"operation\", \"status\"],\n};\n\nconst REFERENCE_TYPES: Record<Section, EvidencePacketReference[\"referenceType\"]> = {\n\taccessEvents: \"access_event\",\n\tactionInvocations: \"action_invocation\",\n\tevents: \"asset_event\",\n\tpolicyEvaluations: \"policy_evaluation\",\n\tadapterInvocations: \"adapter_invocation\",\n};\n\n/**\n * References are built from the redacted payload so nothing outside it can carry a raw value.\n * Each record's `id` must come through redaction unchanged, because references and verifiers\n * identify records by it.\n */\nfunction buildReferences(\n\tpayload: EvidencePacketPayload,\n\tunredacted: Pick<EvidencePacketPayload, Section>,\n): EvidencePacketReference[] {\n\tconst sections: Section[] = [\"actionInvocations\", \"events\", \"policyEvaluations\", \"adapterInvocations\", \"accessEvents\"];\n\treturn sections.flatMap((section) =>\n\t\t((payload[section] ?? []) as unknown[]).map((record, index) => {\n\t\t\tconst originalId = unredacted[section]?.[index]?.id;\n\t\t\tif (!record || typeof record !== \"object\" || (record as EvidenceJsonRecord).id !== originalId) {\n\t\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\t\"EVIDENCE_IDENTIFIER_REDACTED\",\n\t\t\t\t\t`The anonymization policy removed or changed ${section}[${index}].id; record ids must pass through redaction unchanged.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst metadata: Record<string, string> = {};\n\t\t\tfor (const field of REFERENCE_METADATA_FIELDS[section]) {\n\t\t\t\tconst value = (record as EvidenceJsonRecord)[field];\n\t\t\t\tif (typeof value === \"string\") metadata[field] = value;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\treferenceType: REFERENCE_TYPES[section],\n\t\t\t\treferenceId: (record as EvidenceJsonRecord).id as string,\n\t\t\t\tmetadata,\n\t\t\t};\n\t\t}),\n\t);\n}\n\nfunction redactedSubjects(value: unknown): EvidencePacketSubject[] {\n\tif (!Array.isArray(value)) {\n\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_IDENTIFIER_REDACTED\", \"The anonymization policy removed payload.subjects.\");\n\t}\n\treturn value.map((subject, index) => {\n\t\ttry {\n\t\t\treturn assertSubject(subject as EvidencePacketSubject, `subjects[${index}]`);\n\t\t} catch {\n\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\"EVIDENCE_IDENTIFIER_REDACTED\",\n\t\t\t\t`The anonymization policy removed subjects[${index}] or made its subjectType/subjectId a non-string; subject identifiers may be transformed (for example tokenized) but must remain non-empty strings.`,\n\t\t\t);\n\t\t}\n\t});\n}\n\n/** Lowercase hex SHA-256 of `canonicalJson(payload)`. */\nexport function computeEvidencePacketChecksum(payload: unknown): string {\n\treturn createHash(\"sha256\").update(canonicalJson(payload)).digest(\"hex\");\n}\n\n/**\n * Builds a sealed evidence packet for one subject from the application's read port.\n *\n * Steps: resolve subjects (primary first, then related, de-duplicated and sorted); read events,\n * then the action invocations, policy evaluations and adapter invocations those events reference;\n * reject any record outside `context.tenantId`/`context.spaceId`; normalize to plain JSON and sort\n * deterministically; redact the payload with `AnonymizationEngine` (data class\n * `\"evidence_packet\"`); checksum the redacted payload; and build references from it.\n *\n * The optional access-event reader runs with resolved subjects even without invocation ids. Its\n * section is omitted when the reader is absent, preserving existing payloads and checksums; an\n * empty reader result includes `accessEvents: []`. normalizeSection validates its scope and shape.\n *\n * Nothing is persisted. Storing the returned envelope is the application's job.\n */\nexport async function generateEvidencePacket<TDb = unknown>(\n\tinput: GenerateEvidencePacketInput,\n\tcontext: EvidenceGenerationContext<TDb>,\n\toptions: GenerateEvidencePacketOptions,\n): Promise<EvidencePacketEnvelope<EvidencePacketPayload>> {\n\tconst { port } = options;\n\tif (!port || typeof port.listEvents !== \"function\") {\n\t\tthrow new TypeError(\"generateEvidencePacket requires a read port with listEvents().\");\n\t}\n\tif (!options.anonymizationPolicy || !Array.isArray(options.anonymizationPolicy.fields)) {\n\t\tthrow new TypeError(\"generateEvidencePacket requires an anonymizationPolicy (use { fields: [] } to redact nothing).\");\n\t}\n\tassertNonEmptyString(context.tenantId, \"context.tenantId\");\n\tassertNonEmptyString(context.spaceId, \"context.spaceId\");\n\tassertNonEmptyString(context.actorId, \"context.actorId\");\n\tassertNonEmptyString(context.correlationId, \"context.correlationId\");\n\n\tconst scope: EvidenceScope = { tenantId: context.tenantId, spaceId: context.spaceId };\n\tconst primary = assertSubject(input.subject, \"input.subject\");\n\t// Records whether a related-subject lookup actually ran, not only whether it was requested.\n\tconst includeRelated = input.includeRelated !== false && typeof port.resolveRelatedSubjects === \"function\";\n\tconst packetType = input.packetType ?? DEFAULT_EVIDENCE_PACKET_TYPE;\n\tassertNonEmptyString(packetType, \"input.packetType\");\n\n\tconst subjects = await resolveSubjects(primary, includeRelated, port, scope);\n\n\tconst rawEvents = await port.listEvents({\n\t\t...scope,\n\t\tprimarySubject: { ...primary },\n\t\tsubjects: subjects.map((subject) => ({ ...subject })),\n\t});\n\tconst events = normalizeSection(rawEvents, scope, \"events\", (record, path) => {\n\t\tfor (const field of [\"eventType\", \"subjectType\", \"subjectId\", \"correlationId\"]) {\n\t\t\tassertNonEmptyString(record[field], `${path}.${field}`);\n\t\t}\n\t\tif (typeof record.sequence !== \"number\") {\n\t\t\tthrow new EvidencePacketGenerationError(\"EVIDENCE_INVALID_RECORD\", `${path}.sequence must be a number.`);\n\t\t}\n\t\trecord.occurredAt = toIsoTimestamp(record.occurredAt, `${path}.occurredAt`);\n\t\trecord.recordedAt = toIsoTimestamp(record.recordedAt, `${path}.recordedAt`);\n\t\tconst invocationId = record.actionInvocationId;\n\t\tif (invocationId !== undefined && invocationId !== null) {\n\t\t\tassertNonEmptyString(invocationId, `${path}.actionInvocationId`);\n\t\t}\n\t}).sort(byEventOrder);\n\n\tconst invocationIds = [\n\t\t...new Set(\n\t\t\tevents\n\t\t\t\t.map((event) => event.actionInvocationId)\n\t\t\t\t.filter((id): id is string => typeof id === \"string\"),\n\t\t),\n\t].sort(compareStrings);\n\tconst requested = new Set(invocationIds);\n\n\tconst read = async (\n\t\tmethod: ((scope: EvidenceScope, ids: readonly string[]) => MaybePromise<readonly object[]>) | undefined,\n\t): Promise<readonly object[]> => {\n\t\tif (!method || invocationIds.length === 0) return [];\n\t\treturn method.call(port, { ...scope }, [...invocationIds]);\n\t};\n\tconst [rawActionInvocations, rawPolicyEvaluations, rawAdapterInvocations, rawAccessEvents] = await Promise.all([\n\t\tread(port.listActionInvocations),\n\t\tread(port.listPolicyEvaluations),\n\t\tread(port.listAdapterInvocations),\n\t\t(async () => port.listAccessEvents?.({ ...scope }, subjects.map((subject) => ({ ...subject }))))(),\n\t]);\n\n\tconst actionInvocations = normalizeSection(rawActionInvocations, scope, \"actionInvocations\", (record, path) => {\n\t\tif (!requested.has(record.id as string)) {\n\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\"EVIDENCE_UNREQUESTED_RECORD\",\n\t\t\t\t`${path}.id (${String(record.id)}) is not one of the requested action invocations.`,\n\t\t\t);\n\t\t}\n\t}).sort(byId);\n\tconst policyEvaluations = normalizeSection(rawPolicyEvaluations, scope, \"policyEvaluations\", (record, path) =>\n\t\tassertRequestedInvocation(record, path, requested),\n\t).sort(byId);\n\tconst adapterInvocations = normalizeSection(rawAdapterInvocations, scope, \"adapterInvocations\", (record, path) =>\n\t\tassertRequestedInvocation(record, path, requested),\n\t).sort(byId);\n\n\tconst accessEvents = port.listAccessEvents ? normalizeSection(rawAccessEvents!, scope, \"accessEvents\").sort(byId) : undefined;\n\n\tconst generatedAt = context.now ?? new Date();\n\tconst id = options.idFactory ? options.idFactory() : createFabricId(FABRIC_ID_PREFIXES.complianceEvidencePacket);\n\tassertNonEmptyString(id, \"packet id\");\n\n\tconst unredacted: Omit<EvidencePacketPayload, \"redaction\"> = {\n\t\tschemaVersion: EVIDENCE_PACKET_SCHEMA_VERSION,\n\t\tpacketType,\n\t\ttenantId: scope.tenantId,\n\t\tspaceId: scope.spaceId,\n\t\tprimarySubject: { ...primary },\n\t\tsubjects: subjects.map((subject) => ({ ...subject })),\n\t\tincludeRelated,\n\t\t...(input.policyVersion !== undefined ? { policyVersion: input.policyVersion } : {}),\n\t\tevents,\n\t\tactionInvocations,\n\t\tpolicyEvaluations,\n\t\tadapterInvocations,\n\t\t...(accessEvents !== undefined ? { accessEvents } : {}),\n\t};\n\n\tconst anonymizationContext: AnonymizationContext = {\n\t\ttenantId: scope.tenantId,\n\t\tspaceId: scope.spaceId,\n\t\tjobId: options.anonymization?.jobId ?? id,\n\t\treason: options.anonymization?.reason ?? DEFAULT_EVIDENCE_REDACTION_REASON,\n\t\tredactedAt: generatedAt,\n\t\t...(options.anonymization?.policyVersion !== undefined\n\t\t\t? { policyVersion: options.anonymization.policyVersion }\n\t\t\t: {}),\n\t};\n\t// Rebuild the payload with sorted keys first: the engine walks keys in insertion order and lists\n\t// redacted fields in that order, so without this the record key order chosen by the store\n\t// would change the redaction metadata and therefore the checksum.\n\tconst keySorted = JSON.parse(canonicalJson(unredacted)) as typeof unredacted;\n\tconst redacted = new AnonymizationEngine(options.anonymizationPolicy, {\n\t\tbeforeChecksumKey: options.anonymization?.beforeChecksumKey,\n\t}).anonymizeJsonValue(\n\t\tkeySorted,\n\t\tanonymizationContext,\n\t\t\"evidence_packet\",\n\t);\n\t// The engine can replace any field, including the payload's own structure, so the result is\n\t// re-read defensively below rather than trusted to keep the declared shape.\n\tconst payload = { ...(redacted.value as object), redaction: redacted.metadata } as EvidencePacketPayload;\n\tfor (const section of [\n\t\t\"events\",\n\t\t\"actionInvocations\",\n\t\t\"policyEvaluations\",\n\t\t\"adapterInvocations\",\n\t\t...(accessEvents !== undefined ? [\"accessEvents\" as const] : []),\n\t] as const) {\n\t\tif (!Array.isArray(payload[section])) {\n\t\t\tthrow new EvidencePacketGenerationError(\n\t\t\t\t\"EVIDENCE_IDENTIFIER_REDACTED\",\n\t\t\t\t`The anonymization policy removed or replaced payload.${section}.`,\n\t\t\t);\n\t\t}\n\t}\n\tconst envelopeSubjects = redactedSubjects(payload.subjects);\n\tconst references = buildReferences(payload, keySorted);\n\n\treturn {\n\t\tid,\n\t\ttenantId: scope.tenantId,\n\t\tspaceId: scope.spaceId,\n\t\tpacketType,\n\t\tstatus: \"sealed\",\n\t\tsubjects: envelopeSubjects,\n\t\treferences,\n\t\tpayload,\n\t\tgeneratedAt: generatedAt.toISOString(),\n\t\tgeneratedBy: context.actorId,\n\t\t...(input.policyVersion !== undefined ? { policyVersion: input.policyVersion } : {}),\n\t\tchecksum: computeEvidencePacketChecksum(payload),\n\t\tchecksumAlgorithm: EVIDENCE_PACKET_CHECKSUM_ALGORITHM,\n\t\tcorrelationId: context.correlationId,\n\t\t...(context.causationId !== undefined ? { causationId: context.causationId } : {}),\n\t};\n}\n\n/**\n * Recomputes an envelope's checksum. Returns `false`, never throws, when the checksum is missing,\n * the algorithm is missing or unknown, the payload cannot be serialized, or the values differ.\n * It covers `payload` only, which includes the subjects and the redaction metadata; the other\n * envelope fields are not covered.\n */\nexport function verifyEvidencePacketChecksum(envelope: Pick<EvidencePacketEnvelope<unknown>, \"payload\" | \"checksum\" | \"checksumAlgorithm\">): boolean {\n\ttry {\n\t\tif (!envelope || typeof envelope.checksum !== \"string\" || envelope.checksum.length === 0) return false;\n\t\tif (envelope.checksumAlgorithm !== EVIDENCE_PACKET_CHECKSUM_ALGORITHM) return false;\n\t\treturn computeEvidencePacketChecksum(envelope.payload) === envelope.checksum;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport type EvidencePacketRendererInput = Omit<GenerateEvidencePacketInput, \"packetType\">;\n\nexport interface EvidencePacketRendererOptions extends GenerateEvidencePacketOptions {\n\tpacketType?: string;\n}\n\n/** Wraps `generateEvidencePacket` as an `EvidenceRenderer` for one packet type. */\nexport function createEvidencePacketRenderer<TDb = unknown>(\n\toptions: EvidencePacketRendererOptions,\n): EvidenceRenderer<EvidencePacketRendererInput, EvidencePacketPayload, TDb> {\n\tconst { packetType = DEFAULT_EVIDENCE_PACKET_TYPE, ...generatorOptions } = options;\n\treturn {\n\t\tpacketType,\n\t\trender: (input, context) => generateEvidencePacket({ ...input, packetType }, context, generatorOptions),\n\t};\n}\n"],"mappings":"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"}
|