@fabricorg/platform 1.6.0 → 1.8.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 +79 -0
- package/README.md +216 -3
- package/dist/actions/index.cjs +6 -6
- package/dist/actions/index.d.cts +85 -3
- package/dist/actions/index.d.ts +85 -3
- package/dist/actions/index.js +1 -1
- package/dist/adapters/index.cjs +33 -9
- package/dist/adapters/index.d.cts +388 -1
- package/dist/adapters/index.d.ts +388 -1
- package/dist/adapters/index.js +1 -1
- package/dist/certification/index.cjs +6 -6
- package/dist/certification/index.js +3 -3
- package/dist/{chunk-VZ6C4LXC.cjs → chunk-45OKKLNY.cjs} +17 -9
- package/dist/chunk-45OKKLNY.cjs.map +1 -0
- package/dist/chunk-5FOCPRTR.js +511 -0
- package/dist/chunk-5FOCPRTR.js.map +1 -0
- package/dist/{chunk-USDLP4E7.cjs → chunk-5IVFPWK6.cjs} +4 -4
- package/dist/{chunk-USDLP4E7.cjs.map → chunk-5IVFPWK6.cjs.map} +1 -1
- package/dist/chunk-6HY35TKB.cjs +491 -0
- package/dist/chunk-6HY35TKB.cjs.map +1 -0
- package/dist/chunk-6UGEIDLC.cjs +26 -0
- package/dist/chunk-6UGEIDLC.cjs.map +1 -0
- package/dist/chunk-BJGZE3GE.js +20 -0
- package/dist/chunk-BJGZE3GE.js.map +1 -0
- package/dist/{chunk-X3GHYMAJ.cjs → chunk-CRZXTZ36.cjs} +2 -22
- package/dist/chunk-CRZXTZ36.cjs.map +1 -0
- package/dist/{chunk-TMMP4U2D.cjs → chunk-DBTAUI6C.cjs} +5 -2
- package/dist/chunk-DBTAUI6C.cjs.map +1 -0
- package/dist/{chunk-UYCQV65U.js → chunk-EMSAFNPX.js} +3 -3
- package/dist/{chunk-UYCQV65U.js.map → chunk-EMSAFNPX.js.map} +1 -1
- package/dist/{chunk-DCC3UIUJ.js → chunk-GFU7BGEJ.js} +3 -3
- package/dist/{chunk-DCC3UIUJ.js.map → chunk-GFU7BGEJ.js.map} +1 -1
- package/dist/{chunk-JFVEPAED.js → chunk-GU3LZWJD.js} +17 -9
- package/dist/chunk-GU3LZWJD.js.map +1 -0
- package/dist/chunk-HEYMMVDP.js +480 -0
- package/dist/chunk-HEYMMVDP.js.map +1 -0
- package/dist/chunk-IF6NJORI.cjs +526 -0
- package/dist/chunk-IF6NJORI.cjs.map +1 -0
- package/dist/{chunk-XS7EUS4T.cjs → chunk-MCWSUGB3.cjs} +4 -4
- package/dist/{chunk-XS7EUS4T.cjs.map → chunk-MCWSUGB3.cjs.map} +1 -1
- package/dist/{chunk-QHV6LOS6.js → chunk-PDN5DOLQ.js} +3 -3
- package/dist/{chunk-QHV6LOS6.js.map → chunk-PDN5DOLQ.js.map} +1 -1
- package/dist/{chunk-OZWUNGJE.js → chunk-PIDZQAEL.js} +5 -2
- package/dist/chunk-PIDZQAEL.js.map +1 -0
- package/dist/{chunk-ATWWZTRC.js → chunk-TYNIFC7J.js} +3 -18
- package/dist/chunk-TYNIFC7J.js.map +1 -0
- package/dist/{chunk-DDX5FKOM.cjs → chunk-UGWGHTNC.cjs} +5 -2
- package/dist/chunk-UGWGHTNC.cjs.map +1 -0
- package/dist/{chunk-ZF5HDNSU.js → chunk-WNZP53GI.js} +5 -2
- package/dist/chunk-WNZP53GI.js.map +1 -0
- package/dist/{chunk-2URHPZXL.cjs → chunk-WQ3BYI7T.cjs} +6 -6
- package/dist/{chunk-2URHPZXL.cjs.map → chunk-WQ3BYI7T.cjs.map} +1 -1
- package/dist/evidence/index.cjs +45 -1
- package/dist/evidence/index.d.cts +187 -1
- package/dist/evidence/index.d.ts +187 -1
- package/dist/evidence/index.js +3 -1
- package/dist/governance/index.d.cts +1 -0
- package/dist/governance/index.d.ts +1 -0
- package/dist/index.cjs +153 -88
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +11 -10
- package/dist/manifest/index.cjs +8 -8
- package/dist/manifest/index.js +2 -2
- package/dist/modules/index.cjs +9 -9
- package/dist/modules/index.js +1 -1
- package/dist/policies/index.cjs +9 -9
- package/dist/policies/index.d.cts +14 -1
- package/dist/policies/index.d.ts +14 -1
- package/dist/policies/index.js +1 -1
- package/dist/privacy/index.cjs +19 -18
- package/dist/privacy/index.js +2 -1
- package/dist/testing/index.cjs +5 -5
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-ATWWZTRC.js.map +0 -1
- package/dist/chunk-DDX5FKOM.cjs.map +0 -1
- package/dist/chunk-FAG2RTPM.js +0 -120
- package/dist/chunk-FAG2RTPM.js.map +0 -1
- package/dist/chunk-JFVEPAED.js.map +0 -1
- package/dist/chunk-OZLZHISS.js +0 -3
- package/dist/chunk-OZLZHISS.js.map +0 -1
- package/dist/chunk-OZWUNGJE.js.map +0 -1
- package/dist/chunk-PG5LYTUX.cjs +0 -4
- package/dist/chunk-PG5LYTUX.cjs.map +0 -1
- package/dist/chunk-TMMP4U2D.cjs.map +0 -1
- package/dist/chunk-VZ6C4LXC.cjs.map +0 -1
- package/dist/chunk-X3GHYMAJ.cjs.map +0 -1
- package/dist/chunk-XZQZQ5SW.cjs +0 -129
- package/dist/chunk-XZQZQ5SW.cjs.map +0 -1
- package/dist/chunk-ZF5HDNSU.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkIF6NJORI_cjs = require('./chunk-IF6NJORI.cjs');
|
|
4
4
|
|
|
5
5
|
// testing/index.ts
|
|
6
6
|
function createFakeActionContext(options = {}) {
|
|
@@ -17,7 +17,7 @@ function createFakeActionContext(options = {}) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
function createInMemoryAdapterRegistry() {
|
|
20
|
-
return new
|
|
20
|
+
return new chunkIF6NJORI_cjs.AdapterRegistry();
|
|
21
21
|
}
|
|
22
22
|
function createDeterministicIdFactory(prefix = "test") {
|
|
23
23
|
let sequence = 0;
|
|
@@ -27,5 +27,5 @@ function createDeterministicIdFactory(prefix = "test") {
|
|
|
27
27
|
exports.createDeterministicIdFactory = createDeterministicIdFactory;
|
|
28
28
|
exports.createFakeActionContext = createFakeActionContext;
|
|
29
29
|
exports.createInMemoryAdapterRegistry = createInMemoryAdapterRegistry;
|
|
30
|
-
//# sourceMappingURL=chunk-
|
|
31
|
-
//# sourceMappingURL=chunk-
|
|
30
|
+
//# sourceMappingURL=chunk-5IVFPWK6.cjs.map
|
|
31
|
+
//# sourceMappingURL=chunk-5IVFPWK6.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../testing/index.ts"],"names":["AdapterRegistry"],"mappings":";;;;;AAeO,SAAS,uBAAA,CACf,OAAA,GAAyC,EAAC,EACrB;AACrB,EAAA,OAAO;AAAA,IACN,kBAAA,EAAoB,QAAQ,kBAAA,IAAsB,UAAA;AAAA,IAClD,QAAA,EAAU,QAAQ,QAAA,IAAY,aAAA;AAAA,IAC9B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,SAAA,EAAW,QAAQ,SAAA,IAAa,QAAA;AAAA,IAChC,aAAA,EAAe,QAAQ,aAAA,IAAiB,WAAA;AAAA,IACxC,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,EAAA,EAAI,OAAA,CAAQ,EAAA,IAAO,EAAC;AAAA,IACpB,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY;AAAC,GAChC;AACD;AAEO,SAAS,6BAAA,GAAiD;AAChE,EAAA,OAAO,IAAIA,iCAAA,EAAgB;AAC5B;AAEO,SAAS,4BAAA,CAA6B,SAAS,MAAA,EAAsB;AAC3E,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,OAAO,MAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC9D","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../testing/index.ts"],"names":["AdapterRegistry"],"mappings":";;;;;AAeO,SAAS,uBAAA,CACf,OAAA,GAAyC,EAAC,EACrB;AACrB,EAAA,OAAO;AAAA,IACN,kBAAA,EAAoB,QAAQ,kBAAA,IAAsB,UAAA;AAAA,IAClD,QAAA,EAAU,QAAQ,QAAA,IAAY,aAAA;AAAA,IAC9B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,SAAA,EAAW,QAAQ,SAAA,IAAa,QAAA;AAAA,IAChC,aAAA,EAAe,QAAQ,aAAA,IAAiB,WAAA;AAAA,IACxC,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,EAAA,EAAI,OAAA,CAAQ,EAAA,IAAO,EAAC;AAAA,IACpB,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY;AAAC,GAChC;AACD;AAEO,SAAS,6BAAA,GAAiD;AAChE,EAAA,OAAO,IAAIA,iCAAA,EAAgB;AAC5B;AAEO,SAAS,4BAAA,CAA6B,SAAS,MAAA,EAAsB;AAC3E,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,OAAO,MAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC9D","file":"chunk-5IVFPWK6.cjs","sourcesContent":["import { AdapterRegistry } from \"../adapters\";\nimport type { ActionContext, ActorType } from \"../actions\";\n\nexport interface FakeActionContextOptions<TDb = Record<string, never>> {\n\tactionInvocationId?: string;\n\ttenantId?: string;\n\tspaceId?: string;\n\tactorId?: string;\n\tactorType?: ActorType;\n\tcorrelationId?: string;\n\tcausationId?: string;\n\tdb?: TDb;\n\tservices?: Record<string, unknown>;\n}\n\nexport function createFakeActionContext<TDb = Record<string, never>>(\n\toptions: FakeActionContextOptions<TDb> = {},\n): ActionContext<TDb> {\n\treturn {\n\t\tactionInvocationId: options.actionInvocationId ?? \"act_test\",\n\t\ttenantId: options.tenantId ?? \"tenant-test\",\n\t\tspaceId: options.spaceId ?? \"space-test\",\n\t\tactorId: options.actorId ?? \"actor-test\",\n\t\tactorType: options.actorType ?? \"system\",\n\t\tcorrelationId: options.correlationId ?? \"corr-test\",\n\t\tcausationId: options.causationId,\n\t\tdb: options.db ?? ({} as TDb),\n\t\tservices: options.services ?? {},\n\t};\n}\n\nexport function createInMemoryAdapterRegistry(): AdapterRegistry {\n\treturn new AdapterRegistry();\n}\n\nexport function createDeterministicIdFactory(prefix = \"test\"): () => string {\n\tlet sequence = 0;\n\treturn () => `${prefix}_${String(++sequence).padStart(6, \"0\")}`;\n}\n"]}
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkLZSE6QGQ_cjs = require('./chunk-LZSE6QGQ.cjs');
|
|
4
|
+
var chunkCRZXTZ36_cjs = require('./chunk-CRZXTZ36.cjs');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
// evidence/canonical-json.ts
|
|
8
|
+
function canonicalJson(value) {
|
|
9
|
+
return JSON.stringify(sortKeys(value));
|
|
10
|
+
}
|
|
11
|
+
function sortKeys(value) {
|
|
12
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortKeys(child)]));
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
var EVIDENCE_PACKET_CHECKSUM_ALGORITHM = "fabric-canonical-json-sha256-v1";
|
|
19
|
+
var EVIDENCE_PACKET_SCHEMA_VERSION = 1;
|
|
20
|
+
var DEFAULT_EVIDENCE_PACKET_TYPE = "compliance_evidence";
|
|
21
|
+
var DEFAULT_EVIDENCE_REDACTION_REASON = "evidence_packet_generation";
|
|
22
|
+
var EvidencePacketGenerationError = class extends Error {
|
|
23
|
+
code;
|
|
24
|
+
constructor(code, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "EvidencePacketGenerationError";
|
|
27
|
+
this.code = code;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function compareStrings(left, right) {
|
|
31
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
32
|
+
}
|
|
33
|
+
function assertNonEmptyString(value, what) {
|
|
34
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
35
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${what} must be a non-empty string.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function isPlainObject(value) {
|
|
39
|
+
const prototype = Object.getPrototypeOf(value);
|
|
40
|
+
return prototype === Object.prototype || prototype === null;
|
|
41
|
+
}
|
|
42
|
+
function exhaustiveList() {
|
|
43
|
+
return (list) => list;
|
|
44
|
+
}
|
|
45
|
+
var REDACTION_METHODS = Object.freeze(
|
|
46
|
+
exhaustiveList()(["nullify", "mask", "hash", "tokenize", "generalize", "drop"])
|
|
47
|
+
);
|
|
48
|
+
var PLATFORM_DATA_CLASSES = Object.freeze(
|
|
49
|
+
exhaustiveList()([
|
|
50
|
+
"asset_event",
|
|
51
|
+
"read_model",
|
|
52
|
+
"raw_payload",
|
|
53
|
+
"evidence_packet",
|
|
54
|
+
"action_evidence",
|
|
55
|
+
"access_event"
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
59
|
+
function isIsoInstant(value) {
|
|
60
|
+
if (typeof value !== "string") return false;
|
|
61
|
+
const date = new Date(value);
|
|
62
|
+
return !Number.isNaN(date.getTime()) && date.toISOString() === value;
|
|
63
|
+
}
|
|
64
|
+
var METADATA_STRING_KEYS = ["policyVersion", "jobId", "redactedAt", "reason"];
|
|
65
|
+
var METADATA_KEYS = Object.freeze([
|
|
66
|
+
"redactionState",
|
|
67
|
+
"dataClass",
|
|
68
|
+
"redactedFields",
|
|
69
|
+
"preservedSemantics",
|
|
70
|
+
...METADATA_STRING_KEYS
|
|
71
|
+
]);
|
|
72
|
+
var FIELD_METADATA_KEYS = Object.freeze([
|
|
73
|
+
"fieldPath",
|
|
74
|
+
"method",
|
|
75
|
+
"policyVersion",
|
|
76
|
+
"jobId",
|
|
77
|
+
"redactedAt",
|
|
78
|
+
"reason",
|
|
79
|
+
"dataClass",
|
|
80
|
+
"beforeChecksum"
|
|
81
|
+
]);
|
|
82
|
+
function hasExactKeys(value, keys) {
|
|
83
|
+
const present = Object.keys(value);
|
|
84
|
+
return present.length === keys.length && present.every((key) => keys.includes(key));
|
|
85
|
+
}
|
|
86
|
+
function assertEnginePrivacyMetadata(value, path) {
|
|
87
|
+
const invalid = (detail) => new EvidencePacketGenerationError(
|
|
88
|
+
"EVIDENCE_INVALID_RECORD",
|
|
89
|
+
`${path} is not engine-written AnonymizationMetadata (${detail}); _privacy values bypass the anonymization policy, so other data is rejected.`
|
|
90
|
+
);
|
|
91
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid("not an object");
|
|
92
|
+
const metadata = value;
|
|
93
|
+
if (!hasExactKeys(metadata, METADATA_KEYS)) throw invalid("unexpected or missing keys");
|
|
94
|
+
if (metadata.redactionState !== "anonymized") throw invalid("redactionState");
|
|
95
|
+
if (!PLATFORM_DATA_CLASSES.includes(metadata.dataClass)) throw invalid("dataClass");
|
|
96
|
+
for (const key of METADATA_STRING_KEYS) {
|
|
97
|
+
if (typeof metadata[key] !== "string") throw invalid(key);
|
|
98
|
+
}
|
|
99
|
+
if (!isIsoInstant(metadata.redactedAt)) throw invalid("redactedAt");
|
|
100
|
+
if (!Array.isArray(metadata.preservedSemantics) || !metadata.preservedSemantics.every((item) => typeof item === "string")) {
|
|
101
|
+
throw invalid("preservedSemantics");
|
|
102
|
+
}
|
|
103
|
+
if (!Array.isArray(metadata.redactedFields)) throw invalid("redactedFields");
|
|
104
|
+
metadata.redactedFields.forEach((field, index) => {
|
|
105
|
+
if (!field || typeof field !== "object" || Array.isArray(field)) throw invalid(`redactedFields[${index}]`);
|
|
106
|
+
const entry = field;
|
|
107
|
+
if (!hasExactKeys(entry, FIELD_METADATA_KEYS) || !Object.values(entry).every((item) => typeof item === "string")) {
|
|
108
|
+
throw invalid(`redactedFields[${index}]`);
|
|
109
|
+
}
|
|
110
|
+
if (!REDACTION_METHODS.includes(entry.method)) throw invalid(`redactedFields[${index}].method`);
|
|
111
|
+
if (!PLATFORM_DATA_CLASSES.includes(entry.dataClass)) throw invalid(`redactedFields[${index}].dataClass`);
|
|
112
|
+
if (!isIsoInstant(entry.redactedAt)) throw invalid(`redactedFields[${index}].redactedAt`);
|
|
113
|
+
if (!SHA256_HEX.test(entry.beforeChecksum)) throw invalid(`redactedFields[${index}].beforeChecksum`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function toPlainJson(value, path) {
|
|
117
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
118
|
+
if (typeof value === "number") {
|
|
119
|
+
if (!Number.isFinite(value)) {
|
|
120
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is not a finite number.`);
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
if (value instanceof Date) {
|
|
125
|
+
if (Number.isNaN(value.getTime())) {
|
|
126
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is an invalid Date.`);
|
|
127
|
+
}
|
|
128
|
+
return value.toISOString();
|
|
129
|
+
}
|
|
130
|
+
if (Array.isArray(value)) {
|
|
131
|
+
return value.map((item, index) => item === void 0 ? null : toPlainJson(item, `${path}[${index}]`));
|
|
132
|
+
}
|
|
133
|
+
if (typeof value === "object") {
|
|
134
|
+
if (!isPlainObject(value)) {
|
|
135
|
+
const toJSON = value.toJSON;
|
|
136
|
+
if (typeof toJSON === "function") return toPlainJson(toJSON.call(value), path);
|
|
137
|
+
throw new EvidencePacketGenerationError(
|
|
138
|
+
"EVIDENCE_INVALID_RECORD",
|
|
139
|
+
`${path} is a non-plain object that cannot be represented as JSON.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
143
|
+
for (const [key, child] of Object.entries(value)) {
|
|
144
|
+
if (key === "__proto__") {
|
|
145
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} has an own "__proto__" key.`);
|
|
146
|
+
}
|
|
147
|
+
if (child === void 0) continue;
|
|
148
|
+
const normalized = toPlainJson(child, `${path}.${key}`);
|
|
149
|
+
if (key === "_privacy") assertEnginePrivacyMetadata(normalized, `${path}._privacy`);
|
|
150
|
+
result[key] = normalized;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} has unsupported type ${typeof value}.`);
|
|
155
|
+
}
|
|
156
|
+
var ZONED_TIMESTAMP = /^([+-]\d{4,6}|\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)? ?(?:(Z)|([+-])(\d{2})(?::?(\d{2}))?)$/;
|
|
157
|
+
function isLeapYear(year) {
|
|
158
|
+
return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
|
159
|
+
}
|
|
160
|
+
function daysInMonth(year, month) {
|
|
161
|
+
return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
162
|
+
}
|
|
163
|
+
function parseZonedTimestamp(value) {
|
|
164
|
+
const match = ZONED_TIMESTAMP.exec(value);
|
|
165
|
+
if (!match) return void 0;
|
|
166
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zulu, sign, offsetHourText, offsetMinuteText] = match;
|
|
167
|
+
const year = Number(yearText);
|
|
168
|
+
const month = Number(monthText);
|
|
169
|
+
const day = Number(dayText);
|
|
170
|
+
const hour = Number(hourText);
|
|
171
|
+
const minute = Number(minuteText);
|
|
172
|
+
const second = secondText === void 0 ? 0 : Number(secondText);
|
|
173
|
+
const millisecond = fraction === void 0 ? 0 : Number(fraction.padEnd(3, "0").slice(0, 3));
|
|
174
|
+
if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return void 0;
|
|
175
|
+
if (hour > 23 || minute > 59 || second > 59) return void 0;
|
|
176
|
+
let offsetMinutes = 0;
|
|
177
|
+
if (!zulu) {
|
|
178
|
+
const offsetHours = Number(offsetHourText);
|
|
179
|
+
const offsetMinutePart = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
|
|
180
|
+
if (offsetHours > 14 || offsetMinutePart > 59) return void 0;
|
|
181
|
+
offsetMinutes = (sign === "-" ? -1 : 1) * (offsetHours * 60 + offsetMinutePart);
|
|
182
|
+
}
|
|
183
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
184
|
+
date.setUTCFullYear(year, month - 1, day);
|
|
185
|
+
date.setUTCHours(hour, minute, second, millisecond);
|
|
186
|
+
const instant = date.getTime() - offsetMinutes * 6e4;
|
|
187
|
+
return Number.isNaN(new Date(instant).getTime()) ? void 0 : instant;
|
|
188
|
+
}
|
|
189
|
+
function toIsoTimestamp(value, path) {
|
|
190
|
+
const instant = value instanceof Date ? value.getTime() : typeof value === "string" ? parseZonedTimestamp(value) : void 0;
|
|
191
|
+
if (instant === void 0 || Number.isNaN(instant)) {
|
|
192
|
+
throw new EvidencePacketGenerationError(
|
|
193
|
+
"EVIDENCE_INVALID_RECORD",
|
|
194
|
+
`${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).`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return new Date(instant).toISOString();
|
|
198
|
+
}
|
|
199
|
+
function assertScope(record, scope, section) {
|
|
200
|
+
if (record.tenantId !== scope.tenantId || record.spaceId !== scope.spaceId) {
|
|
201
|
+
throw new EvidencePacketGenerationError(
|
|
202
|
+
"EVIDENCE_SCOPE_MISMATCH",
|
|
203
|
+
`Evidence read port returned a ${section} record (${String(record.id)}) outside tenant/space ${scope.tenantId}/${scope.spaceId}.`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function normalizeSection(records, scope, section, prepare) {
|
|
208
|
+
if (!Array.isArray(records)) {
|
|
209
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `Evidence read port returned a non-array for ${section}.`);
|
|
210
|
+
}
|
|
211
|
+
const byId2 = /* @__PURE__ */ new Map();
|
|
212
|
+
records.forEach((raw, index) => {
|
|
213
|
+
const path = `${section}[${index}]`;
|
|
214
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
215
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} must be an object.`);
|
|
216
|
+
}
|
|
217
|
+
assertScope(raw, scope, section);
|
|
218
|
+
const record = toPlainJson(raw, path);
|
|
219
|
+
assertScope(record, scope, section);
|
|
220
|
+
assertNonEmptyString(record.id, `${path}.id`);
|
|
221
|
+
prepare?.(record, path);
|
|
222
|
+
const canonical = canonicalJson(record);
|
|
223
|
+
const existing = byId2.get(record.id);
|
|
224
|
+
if (existing && existing.canonical !== canonical) {
|
|
225
|
+
throw new EvidencePacketGenerationError(
|
|
226
|
+
"EVIDENCE_INVALID_RECORD",
|
|
227
|
+
`Evidence read port returned conflicting ${section} records with id ${String(record.id)}.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
byId2.set(record.id, { record, canonical });
|
|
231
|
+
});
|
|
232
|
+
return [...byId2.values()].map((entry) => entry.record);
|
|
233
|
+
}
|
|
234
|
+
function byId(left, right) {
|
|
235
|
+
return compareStrings(left.id, right.id);
|
|
236
|
+
}
|
|
237
|
+
function byEventOrder(left, right) {
|
|
238
|
+
return Date.parse(left.occurredAt) - Date.parse(right.occurredAt) || Date.parse(left.recordedAt) - Date.parse(right.recordedAt) || left.sequence - right.sequence || byId(left, right);
|
|
239
|
+
}
|
|
240
|
+
function assertSubject(subject, path) {
|
|
241
|
+
if (!subject || typeof subject !== "object") {
|
|
242
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} must be an object.`);
|
|
243
|
+
}
|
|
244
|
+
assertNonEmptyString(subject.subjectType, `${path}.subjectType`);
|
|
245
|
+
assertNonEmptyString(subject.subjectId, `${path}.subjectId`);
|
|
246
|
+
return { subjectType: subject.subjectType, subjectId: subject.subjectId };
|
|
247
|
+
}
|
|
248
|
+
function compareSubjects(left, right) {
|
|
249
|
+
return compareStrings(left.subjectType, right.subjectType) || compareStrings(left.subjectId, right.subjectId);
|
|
250
|
+
}
|
|
251
|
+
async function resolveSubjects(primary, includeRelated, port, scope) {
|
|
252
|
+
if (!includeRelated || !port.resolveRelatedSubjects) return [primary];
|
|
253
|
+
const related = await port.resolveRelatedSubjects({ ...primary }, { ...scope });
|
|
254
|
+
if (!Array.isArray(related)) {
|
|
255
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", "resolveRelatedSubjects must return an array.");
|
|
256
|
+
}
|
|
257
|
+
const key = (subject) => `${subject.subjectType}\0${subject.subjectId}`;
|
|
258
|
+
const seen = /* @__PURE__ */ new Set([key(primary)]);
|
|
259
|
+
const unique = [];
|
|
260
|
+
related.forEach((candidate, index) => {
|
|
261
|
+
const subject = assertSubject(candidate, `relatedSubjects[${index}]`);
|
|
262
|
+
if (seen.has(key(subject))) return;
|
|
263
|
+
seen.add(key(subject));
|
|
264
|
+
unique.push(subject);
|
|
265
|
+
});
|
|
266
|
+
return [primary, ...unique.sort(compareSubjects)];
|
|
267
|
+
}
|
|
268
|
+
function assertRequestedInvocation(record, path, requested) {
|
|
269
|
+
const invocationId = record.actionInvocationId;
|
|
270
|
+
if (typeof invocationId !== "string" || !requested.has(invocationId)) {
|
|
271
|
+
throw new EvidencePacketGenerationError(
|
|
272
|
+
"EVIDENCE_UNREQUESTED_RECORD",
|
|
273
|
+
`${path}.actionInvocationId (${String(invocationId)}) is not one of the requested action invocations.`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
var REFERENCE_METADATA_FIELDS = {
|
|
278
|
+
actionInvocations: ["actionId", "status"],
|
|
279
|
+
events: ["eventType", "subjectType", "subjectId", "actionInvocationId"],
|
|
280
|
+
policyEvaluations: ["actionInvocationId", "policyId", "policyVersion", "result"],
|
|
281
|
+
adapterInvocations: ["actionInvocationId", "adapterType", "operation", "status"]
|
|
282
|
+
};
|
|
283
|
+
var REFERENCE_TYPES = {
|
|
284
|
+
actionInvocations: "action_invocation",
|
|
285
|
+
events: "asset_event",
|
|
286
|
+
policyEvaluations: "policy_evaluation",
|
|
287
|
+
adapterInvocations: "adapter_invocation"
|
|
288
|
+
};
|
|
289
|
+
function buildReferences(payload, unredacted) {
|
|
290
|
+
const sections = ["actionInvocations", "events", "policyEvaluations", "adapterInvocations"];
|
|
291
|
+
return sections.flatMap(
|
|
292
|
+
(section) => payload[section].map((record, index) => {
|
|
293
|
+
const originalId = unredacted[section][index]?.id;
|
|
294
|
+
if (!record || typeof record !== "object" || record.id !== originalId) {
|
|
295
|
+
throw new EvidencePacketGenerationError(
|
|
296
|
+
"EVIDENCE_IDENTIFIER_REDACTED",
|
|
297
|
+
`The anonymization policy removed or changed ${section}[${index}].id; record ids must pass through redaction unchanged.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const metadata = {};
|
|
301
|
+
for (const field of REFERENCE_METADATA_FIELDS[section]) {
|
|
302
|
+
const value = record[field];
|
|
303
|
+
if (typeof value === "string") metadata[field] = value;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
referenceType: REFERENCE_TYPES[section],
|
|
307
|
+
referenceId: record.id,
|
|
308
|
+
metadata
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
function redactedSubjects(value) {
|
|
314
|
+
if (!Array.isArray(value)) {
|
|
315
|
+
throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", "The anonymization policy removed payload.subjects.");
|
|
316
|
+
}
|
|
317
|
+
return value.map((subject, index) => {
|
|
318
|
+
try {
|
|
319
|
+
return assertSubject(subject, `subjects[${index}]`);
|
|
320
|
+
} catch {
|
|
321
|
+
throw new EvidencePacketGenerationError(
|
|
322
|
+
"EVIDENCE_IDENTIFIER_REDACTED",
|
|
323
|
+
`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.`
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
function computeEvidencePacketChecksum(payload) {
|
|
329
|
+
return crypto.createHash("sha256").update(canonicalJson(payload)).digest("hex");
|
|
330
|
+
}
|
|
331
|
+
async function generateEvidencePacket(input, context, options) {
|
|
332
|
+
const { port } = options;
|
|
333
|
+
if (!port || typeof port.listEvents !== "function") {
|
|
334
|
+
throw new TypeError("generateEvidencePacket requires a read port with listEvents().");
|
|
335
|
+
}
|
|
336
|
+
if (!options.anonymizationPolicy || !Array.isArray(options.anonymizationPolicy.fields)) {
|
|
337
|
+
throw new TypeError("generateEvidencePacket requires an anonymizationPolicy (use { fields: [] } to redact nothing).");
|
|
338
|
+
}
|
|
339
|
+
assertNonEmptyString(context.tenantId, "context.tenantId");
|
|
340
|
+
assertNonEmptyString(context.spaceId, "context.spaceId");
|
|
341
|
+
assertNonEmptyString(context.actorId, "context.actorId");
|
|
342
|
+
assertNonEmptyString(context.correlationId, "context.correlationId");
|
|
343
|
+
const scope = { tenantId: context.tenantId, spaceId: context.spaceId };
|
|
344
|
+
const primary = assertSubject(input.subject, "input.subject");
|
|
345
|
+
const includeRelated = input.includeRelated !== false && typeof port.resolveRelatedSubjects === "function";
|
|
346
|
+
const packetType = input.packetType ?? DEFAULT_EVIDENCE_PACKET_TYPE;
|
|
347
|
+
assertNonEmptyString(packetType, "input.packetType");
|
|
348
|
+
const subjects = await resolveSubjects(primary, includeRelated, port, scope);
|
|
349
|
+
const rawEvents = await port.listEvents({
|
|
350
|
+
...scope,
|
|
351
|
+
primarySubject: { ...primary },
|
|
352
|
+
subjects: subjects.map((subject) => ({ ...subject }))
|
|
353
|
+
});
|
|
354
|
+
const events = normalizeSection(rawEvents, scope, "events", (record, path) => {
|
|
355
|
+
for (const field of ["eventType", "subjectType", "subjectId", "correlationId"]) {
|
|
356
|
+
assertNonEmptyString(record[field], `${path}.${field}`);
|
|
357
|
+
}
|
|
358
|
+
if (typeof record.sequence !== "number") {
|
|
359
|
+
throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path}.sequence must be a number.`);
|
|
360
|
+
}
|
|
361
|
+
record.occurredAt = toIsoTimestamp(record.occurredAt, `${path}.occurredAt`);
|
|
362
|
+
record.recordedAt = toIsoTimestamp(record.recordedAt, `${path}.recordedAt`);
|
|
363
|
+
const invocationId = record.actionInvocationId;
|
|
364
|
+
if (invocationId !== void 0 && invocationId !== null) {
|
|
365
|
+
assertNonEmptyString(invocationId, `${path}.actionInvocationId`);
|
|
366
|
+
}
|
|
367
|
+
}).sort(byEventOrder);
|
|
368
|
+
const invocationIds = [
|
|
369
|
+
...new Set(
|
|
370
|
+
events.map((event) => event.actionInvocationId).filter((id2) => typeof id2 === "string")
|
|
371
|
+
)
|
|
372
|
+
].sort(compareStrings);
|
|
373
|
+
const requested = new Set(invocationIds);
|
|
374
|
+
const read = async (method) => {
|
|
375
|
+
if (!method || invocationIds.length === 0) return [];
|
|
376
|
+
return method.call(port, { ...scope }, [...invocationIds]);
|
|
377
|
+
};
|
|
378
|
+
const [rawActionInvocations, rawPolicyEvaluations, rawAdapterInvocations] = await Promise.all([
|
|
379
|
+
read(port.listActionInvocations),
|
|
380
|
+
read(port.listPolicyEvaluations),
|
|
381
|
+
read(port.listAdapterInvocations)
|
|
382
|
+
]);
|
|
383
|
+
const actionInvocations = normalizeSection(rawActionInvocations, scope, "actionInvocations", (record, path) => {
|
|
384
|
+
if (!requested.has(record.id)) {
|
|
385
|
+
throw new EvidencePacketGenerationError(
|
|
386
|
+
"EVIDENCE_UNREQUESTED_RECORD",
|
|
387
|
+
`${path}.id (${String(record.id)}) is not one of the requested action invocations.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}).sort(byId);
|
|
391
|
+
const policyEvaluations = normalizeSection(
|
|
392
|
+
rawPolicyEvaluations,
|
|
393
|
+
scope,
|
|
394
|
+
"policyEvaluations",
|
|
395
|
+
(record, path) => assertRequestedInvocation(record, path, requested)
|
|
396
|
+
).sort(byId);
|
|
397
|
+
const adapterInvocations = normalizeSection(
|
|
398
|
+
rawAdapterInvocations,
|
|
399
|
+
scope,
|
|
400
|
+
"adapterInvocations",
|
|
401
|
+
(record, path) => assertRequestedInvocation(record, path, requested)
|
|
402
|
+
).sort(byId);
|
|
403
|
+
const generatedAt = context.now ?? /* @__PURE__ */ new Date();
|
|
404
|
+
const id = options.idFactory ? options.idFactory() : chunkLZSE6QGQ_cjs.createFabricId(chunkLZSE6QGQ_cjs.FABRIC_ID_PREFIXES.complianceEvidencePacket);
|
|
405
|
+
assertNonEmptyString(id, "packet id");
|
|
406
|
+
const unredacted = {
|
|
407
|
+
schemaVersion: EVIDENCE_PACKET_SCHEMA_VERSION,
|
|
408
|
+
packetType,
|
|
409
|
+
tenantId: scope.tenantId,
|
|
410
|
+
spaceId: scope.spaceId,
|
|
411
|
+
primarySubject: { ...primary },
|
|
412
|
+
subjects: subjects.map((subject) => ({ ...subject })),
|
|
413
|
+
includeRelated,
|
|
414
|
+
...input.policyVersion !== void 0 ? { policyVersion: input.policyVersion } : {},
|
|
415
|
+
events,
|
|
416
|
+
actionInvocations,
|
|
417
|
+
policyEvaluations,
|
|
418
|
+
adapterInvocations
|
|
419
|
+
};
|
|
420
|
+
const anonymizationContext = {
|
|
421
|
+
tenantId: scope.tenantId,
|
|
422
|
+
spaceId: scope.spaceId,
|
|
423
|
+
jobId: options.anonymization?.jobId ?? id,
|
|
424
|
+
reason: options.anonymization?.reason ?? DEFAULT_EVIDENCE_REDACTION_REASON,
|
|
425
|
+
redactedAt: generatedAt,
|
|
426
|
+
...options.anonymization?.policyVersion !== void 0 ? { policyVersion: options.anonymization.policyVersion } : {}
|
|
427
|
+
};
|
|
428
|
+
const keySorted = JSON.parse(canonicalJson(unredacted));
|
|
429
|
+
const redacted = new chunkCRZXTZ36_cjs.AnonymizationEngine(options.anonymizationPolicy).anonymizeJsonValue(
|
|
430
|
+
keySorted,
|
|
431
|
+
anonymizationContext,
|
|
432
|
+
"evidence_packet"
|
|
433
|
+
);
|
|
434
|
+
const payload = { ...redacted.value, redaction: redacted.metadata };
|
|
435
|
+
for (const section of ["events", "actionInvocations", "policyEvaluations", "adapterInvocations"]) {
|
|
436
|
+
if (!Array.isArray(payload[section])) {
|
|
437
|
+
throw new EvidencePacketGenerationError(
|
|
438
|
+
"EVIDENCE_IDENTIFIER_REDACTED",
|
|
439
|
+
`The anonymization policy removed or replaced payload.${section}.`
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const envelopeSubjects = redactedSubjects(payload.subjects);
|
|
444
|
+
const references = buildReferences(payload, keySorted);
|
|
445
|
+
return {
|
|
446
|
+
id,
|
|
447
|
+
tenantId: scope.tenantId,
|
|
448
|
+
spaceId: scope.spaceId,
|
|
449
|
+
packetType,
|
|
450
|
+
status: "sealed",
|
|
451
|
+
subjects: envelopeSubjects,
|
|
452
|
+
references,
|
|
453
|
+
payload,
|
|
454
|
+
generatedAt: generatedAt.toISOString(),
|
|
455
|
+
generatedBy: context.actorId,
|
|
456
|
+
...input.policyVersion !== void 0 ? { policyVersion: input.policyVersion } : {},
|
|
457
|
+
checksum: computeEvidencePacketChecksum(payload),
|
|
458
|
+
checksumAlgorithm: EVIDENCE_PACKET_CHECKSUM_ALGORITHM,
|
|
459
|
+
correlationId: context.correlationId,
|
|
460
|
+
...context.causationId !== void 0 ? { causationId: context.causationId } : {}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function verifyEvidencePacketChecksum(envelope) {
|
|
464
|
+
try {
|
|
465
|
+
if (!envelope || typeof envelope.checksum !== "string" || envelope.checksum.length === 0) return false;
|
|
466
|
+
if (envelope.checksumAlgorithm !== EVIDENCE_PACKET_CHECKSUM_ALGORITHM) return false;
|
|
467
|
+
return computeEvidencePacketChecksum(envelope.payload) === envelope.checksum;
|
|
468
|
+
} catch {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function createEvidencePacketRenderer(options) {
|
|
473
|
+
const { packetType = DEFAULT_EVIDENCE_PACKET_TYPE, ...generatorOptions } = options;
|
|
474
|
+
return {
|
|
475
|
+
packetType,
|
|
476
|
+
render: (input, context) => generateEvidencePacket({ ...input, packetType }, context, generatorOptions)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
exports.DEFAULT_EVIDENCE_PACKET_TYPE = DEFAULT_EVIDENCE_PACKET_TYPE;
|
|
481
|
+
exports.DEFAULT_EVIDENCE_REDACTION_REASON = DEFAULT_EVIDENCE_REDACTION_REASON;
|
|
482
|
+
exports.EVIDENCE_PACKET_CHECKSUM_ALGORITHM = EVIDENCE_PACKET_CHECKSUM_ALGORITHM;
|
|
483
|
+
exports.EVIDENCE_PACKET_SCHEMA_VERSION = EVIDENCE_PACKET_SCHEMA_VERSION;
|
|
484
|
+
exports.EvidencePacketGenerationError = EvidencePacketGenerationError;
|
|
485
|
+
exports.canonicalJson = canonicalJson;
|
|
486
|
+
exports.computeEvidencePacketChecksum = computeEvidencePacketChecksum;
|
|
487
|
+
exports.createEvidencePacketRenderer = createEvidencePacketRenderer;
|
|
488
|
+
exports.generateEvidencePacket = generateEvidencePacket;
|
|
489
|
+
exports.verifyEvidencePacketChecksum = verifyEvidencePacketChecksum;
|
|
490
|
+
//# sourceMappingURL=chunk-6HY35TKB.cjs.map
|
|
491
|
+
//# sourceMappingURL=chunk-6HY35TKB.cjs.map
|