@fabricorg/platform 1.9.0 → 1.10.1

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.
Files changed (209) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +48 -2
  3. package/dist/actions/index.cjs +45 -26
  4. package/dist/actions/index.cjs.map +1 -1
  5. package/dist/actions/index.d.cts +225 -226
  6. package/dist/actions/index.d.ts +225 -226
  7. package/dist/actions/index.js +41 -2
  8. package/dist/actions/index.js.map +1 -1
  9. package/dist/adapters/index.cjs +696 -66
  10. package/dist/adapters/index.cjs.map +1 -1
  11. package/dist/adapters/index.d.cts +367 -365
  12. package/dist/adapters/index.d.ts +367 -365
  13. package/dist/adapters/index.js +682 -2
  14. package/dist/adapters/index.js.map +1 -1
  15. package/dist/agent-hitl/index.cjs +21 -10
  16. package/dist/agent-hitl/index.cjs.map +1 -1
  17. package/dist/agent-hitl/index.d.cts +30 -30
  18. package/dist/agent-hitl/index.d.ts +30 -30
  19. package/dist/agent-hitl/index.js +21 -2
  20. package/dist/agent-hitl/index.js.map +1 -1
  21. package/dist/certification/index.cjs +109 -24
  22. package/dist/certification/index.cjs.map +1 -1
  23. package/dist/certification/index.d.cts +33 -39
  24. package/dist/certification/index.d.ts +33 -39
  25. package/dist/certification/index.js +107 -8
  26. package/dist/certification/index.js.map +1 -1
  27. package/dist/discovery/index.cjs +12 -10
  28. package/dist/discovery/index.cjs.map +1 -1
  29. package/dist/discovery/index.d.cts +2 -10
  30. package/dist/discovery/index.d.ts +2 -10
  31. package/dist/discovery/index.js +12 -2
  32. package/dist/discovery/index.js.map +1 -1
  33. package/dist/engine-B4csEOEc.js +223 -0
  34. package/dist/engine-B4csEOEc.js.map +1 -0
  35. package/dist/engine-BqScuhrT.cjs +246 -0
  36. package/dist/engine-BqScuhrT.cjs.map +1 -0
  37. package/dist/events/index.cjs +39 -10
  38. package/dist/events/index.cjs.map +1 -1
  39. package/dist/events/index.d.cts +43 -43
  40. package/dist/events/index.d.ts +43 -43
  41. package/dist/events/index.js +39 -2
  42. package/dist/events/index.js.map +1 -1
  43. package/dist/evidence/index.cjs +12 -50
  44. package/dist/evidence/index.d.cts +2 -229
  45. package/dist/evidence/index.d.ts +2 -229
  46. package/dist/evidence/index.js +1 -5
  47. package/dist/evidence-BuCICb0c.js +516 -0
  48. package/dist/evidence-BuCICb0c.js.map +1 -0
  49. package/dist/evidence-Cq48eDvA.cjs +575 -0
  50. package/dist/evidence-Cq48eDvA.cjs.map +1 -0
  51. package/dist/governance/index.cjs +24 -18
  52. package/dist/governance/index.cjs.map +1 -1
  53. package/dist/governance/index.d.cts +80 -83
  54. package/dist/governance/index.d.ts +80 -83
  55. package/dist/governance/index.js +22 -2
  56. package/dist/governance/index.js.map +1 -1
  57. package/dist/ids/index.cjs +52 -22
  58. package/dist/ids/index.cjs.map +1 -1
  59. package/dist/ids/index.d.cts +17 -17
  60. package/dist/ids/index.d.ts +17 -17
  61. package/dist/ids/index.js +49 -2
  62. package/dist/ids/index.js.map +1 -1
  63. package/dist/index-9jasmw76.d.cts +124 -0
  64. package/dist/index-BXpeFdk2.d.cts +190 -0
  65. package/dist/index-BXpeFdk2.d.ts +190 -0
  66. package/dist/index-BnbsKZsT.d.cts +231 -0
  67. package/dist/index-CIl1rYRv.d.cts +11 -0
  68. package/dist/index-CIl1rYRv.d.ts +11 -0
  69. package/dist/index-CVHA1ypT.d.cts +262 -0
  70. package/dist/index-DZ4mnNb7.d.cts +78 -0
  71. package/dist/index-DZ4mnNb7.d.ts +78 -0
  72. package/dist/index-IqcDvl69.d.ts +124 -0
  73. package/dist/index-fmUiRBXu.d.cts +57 -0
  74. package/dist/index-fmUiRBXu.d.ts +57 -0
  75. package/dist/index-sPUPs8pr.d.ts +262 -0
  76. package/dist/index-z9-CYOIo.d.ts +231 -0
  77. package/dist/index.cjs +111 -385
  78. package/dist/index.d.cts +18 -17
  79. package/dist/index.d.ts +18 -17
  80. package/dist/index.js +19 -20
  81. package/dist/manifest/index.cjs +224 -35
  82. package/dist/manifest/index.cjs.map +1 -1
  83. package/dist/manifest/index.d.cts +130 -131
  84. package/dist/manifest/index.d.ts +130 -131
  85. package/dist/manifest/index.js +219 -7
  86. package/dist/manifest/index.js.map +1 -1
  87. package/dist/modules/index.cjs +281 -42
  88. package/dist/modules/index.cjs.map +1 -1
  89. package/dist/modules/index.d.cts +157 -159
  90. package/dist/modules/index.d.ts +157 -159
  91. package/dist/modules/index.js +274 -6
  92. package/dist/modules/index.js.map +1 -1
  93. package/dist/objects/index.cjs +355 -22
  94. package/dist/objects/index.cjs.map +1 -1
  95. package/dist/objects/index.d.cts +2 -77
  96. package/dist/objects/index.d.ts +2 -77
  97. package/dist/objects/index.js +352 -2
  98. package/dist/objects/index.js.map +1 -1
  99. package/dist/policies/index.cjs +14 -48
  100. package/dist/policies/index.d.cts +2 -247
  101. package/dist/policies/index.d.ts +2 -247
  102. package/dist/policies/index.js +1 -3
  103. package/dist/policies-DH_GCXQ-.js +627 -0
  104. package/dist/policies-DH_GCXQ-.js.map +1 -0
  105. package/dist/policies-Nxbp1CH8.cjs +698 -0
  106. package/dist/policies-Nxbp1CH8.cjs.map +1 -0
  107. package/dist/privacy/index.cjs +26 -43
  108. package/dist/privacy/index.cjs.map +1 -1
  109. package/dist/privacy/index.d.cts +2 -189
  110. package/dist/privacy/index.d.ts +2 -189
  111. package/dist/privacy/index.js +18 -3
  112. package/dist/privacy/index.js.map +1 -1
  113. package/dist/projections/index.cjs +241 -23
  114. package/dist/projections/index.cjs.map +1 -1
  115. package/dist/projections/index.d.cts +2 -125
  116. package/dist/projections/index.d.ts +2 -125
  117. package/dist/projections/index.js +238 -3
  118. package/dist/projections/index.js.map +1 -1
  119. package/dist/state-machines/index.cjs +76 -18
  120. package/dist/state-machines/index.cjs.map +1 -1
  121. package/dist/state-machines/index.d.cts +2 -55
  122. package/dist/state-machines/index.d.ts +2 -55
  123. package/dist/state-machines/index.js +74 -2
  124. package/dist/state-machines/index.js.map +1 -1
  125. package/dist/testing/index.cjs +27 -19
  126. package/dist/testing/index.cjs.map +1 -1
  127. package/dist/testing/index.d.cts +17 -19
  128. package/dist/testing/index.d.ts +17 -19
  129. package/dist/testing/index.js +25 -3
  130. package/dist/testing/index.js.map +1 -1
  131. package/package.json +6 -5
  132. package/dist/chunk-2AE6TMAX.cjs +0 -44
  133. package/dist/chunk-2AE6TMAX.cjs.map +0 -1
  134. package/dist/chunk-6HY35TKB.cjs +0 -491
  135. package/dist/chunk-6HY35TKB.cjs.map +0 -1
  136. package/dist/chunk-6UGEIDLC.cjs +0 -26
  137. package/dist/chunk-6UGEIDLC.cjs.map +0 -1
  138. package/dist/chunk-7GAWSUOG.js +0 -64
  139. package/dist/chunk-7GAWSUOG.js.map +0 -1
  140. package/dist/chunk-AFXFAEWA.js +0 -673
  141. package/dist/chunk-AFXFAEWA.js.map +0 -1
  142. package/dist/chunk-AFXKSIVI.cjs +0 -540
  143. package/dist/chunk-AFXKSIVI.cjs.map +0 -1
  144. package/dist/chunk-B5DOGDPG.js +0 -42
  145. package/dist/chunk-B5DOGDPG.js.map +0 -1
  146. package/dist/chunk-BJGZE3GE.js +0 -20
  147. package/dist/chunk-BJGZE3GE.js.map +0 -1
  148. package/dist/chunk-CRZXTZ36.cjs +0 -258
  149. package/dist/chunk-CRZXTZ36.cjs.map +0 -1
  150. package/dist/chunk-D4SWRBUH.cjs +0 -22
  151. package/dist/chunk-D4SWRBUH.cjs.map +0 -1
  152. package/dist/chunk-DBTAUI6C.cjs +0 -395
  153. package/dist/chunk-DBTAUI6C.cjs.map +0 -1
  154. package/dist/chunk-EMSAFNPX.js +0 -98
  155. package/dist/chunk-EMSAFNPX.js.map +0 -1
  156. package/dist/chunk-F3E5SXPN.cjs +0 -28
  157. package/dist/chunk-F3E5SXPN.cjs.map +0 -1
  158. package/dist/chunk-HEYMMVDP.js +0 -480
  159. package/dist/chunk-HEYMMVDP.js.map +0 -1
  160. package/dist/chunk-IFW43ELT.cjs +0 -331
  161. package/dist/chunk-IFW43ELT.cjs.map +0 -1
  162. package/dist/chunk-LFE74IJX.js +0 -20
  163. package/dist/chunk-LFE74IJX.js.map +0 -1
  164. package/dist/chunk-LZSE6QGQ.cjs +0 -60
  165. package/dist/chunk-LZSE6QGQ.cjs.map +0 -1
  166. package/dist/chunk-MCWSUGB3.cjs +0 -102
  167. package/dist/chunk-MCWSUGB3.cjs.map +0 -1
  168. package/dist/chunk-MLBALEUR.js +0 -290
  169. package/dist/chunk-MLBALEUR.js.map +0 -1
  170. package/dist/chunk-NNXQZPB3.cjs +0 -31
  171. package/dist/chunk-NNXQZPB3.cjs.map +0 -1
  172. package/dist/chunk-OFCCZ27Z.js +0 -26
  173. package/dist/chunk-OFCCZ27Z.js.map +0 -1
  174. package/dist/chunk-PDN5DOLQ.js +0 -242
  175. package/dist/chunk-PDN5DOLQ.js.map +0 -1
  176. package/dist/chunk-PIDZQAEL.js +0 -50
  177. package/dist/chunk-PIDZQAEL.js.map +0 -1
  178. package/dist/chunk-Q4Q4JPR3.js +0 -524
  179. package/dist/chunk-Q4Q4JPR3.js.map +0 -1
  180. package/dist/chunk-RALMUSOX.js +0 -55
  181. package/dist/chunk-RALMUSOX.js.map +0 -1
  182. package/dist/chunk-SNTV45LH.cjs +0 -295
  183. package/dist/chunk-SNTV45LH.cjs.map +0 -1
  184. package/dist/chunk-TBS2S5AX.cjs +0 -40
  185. package/dist/chunk-TBS2S5AX.cjs.map +0 -1
  186. package/dist/chunk-TYNIFC7J.js +0 -253
  187. package/dist/chunk-TYNIFC7J.js.map +0 -1
  188. package/dist/chunk-UGWGHTNC.cjs +0 -56
  189. package/dist/chunk-UGWGHTNC.cjs.map +0 -1
  190. package/dist/chunk-WI7CWNYI.cjs +0 -68
  191. package/dist/chunk-WI7CWNYI.cjs.map +0 -1
  192. package/dist/chunk-WNZP53GI.js +0 -386
  193. package/dist/chunk-WNZP53GI.js.map +0 -1
  194. package/dist/chunk-WQ3BYI7T.cjs +0 -249
  195. package/dist/chunk-WQ3BYI7T.cjs.map +0 -1
  196. package/dist/chunk-Y2PJLRIY.js +0 -36
  197. package/dist/chunk-Y2PJLRIY.js.map +0 -1
  198. package/dist/chunk-YGIPN3TU.cjs +0 -684
  199. package/dist/chunk-YGIPN3TU.cjs.map +0 -1
  200. package/dist/chunk-ZAEVQSB5.js +0 -326
  201. package/dist/chunk-ZAEVQSB5.js.map +0 -1
  202. package/dist/chunk-ZMQTV5OJ.js +0 -27
  203. package/dist/chunk-ZMQTV5OJ.js.map +0 -1
  204. package/dist/evidence/index.cjs.map +0 -1
  205. package/dist/evidence/index.js.map +0 -1
  206. package/dist/index.cjs.map +0 -1
  207. package/dist/index.js.map +0 -1
  208. package/dist/policies/index.cjs.map +0 -1
  209. package/dist/policies/index.js.map +0 -1
@@ -0,0 +1,575 @@
1
+ const require_ids_index = require("./ids/index.cjs");
2
+ const require_engine = require("./engine-BqScuhrT.cjs");
3
+ let crypto = require("crypto");
4
+ //#region evidence/canonical-json.ts
5
+ /**
6
+ * Serializes a value as JSON with every object's keys sorted, recursively, by UTF-16 code-unit
7
+ * order (locale-independent). Array order is preserved.
8
+ *
9
+ * This is the serialization behind the `fabric-canonical-json-sha256-v1` digests: Host's
10
+ * parameter digests (`PARAMETER_DIGEST_ALGORITHM` in `@fabricorg/platform-host`) and evidence
11
+ * packet checksums (`EVIDENCE_PACKET_CHECKSUM_ALGORITHM`). Its output is a durable contract, so it
12
+ * must not change for any input. That includes values that are not plain JSON: any non-array
13
+ * object is rebuilt from its own enumerable entries before serialization, so a `Date` (which has
14
+ * none) serializes as `{}`, not as an ISO string. Convert dates to strings before calling this
15
+ * when they must survive into the output.
16
+ */
17
+ function canonicalJson(value) {
18
+ return JSON.stringify(sortKeys(value));
19
+ }
20
+ function sortKeys(value) {
21
+ if (Array.isArray(value)) return value.map(sortKeys);
22
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortKeys(child)]));
23
+ return value;
24
+ }
25
+ //#endregion
26
+ //#region evidence/generator.ts
27
+ /**
28
+ * Identifies how `EvidencePacketEnvelope.checksum` is computed: the lowercase hex SHA-256 of
29
+ * `canonicalJson(payload)`. It is the same identifier as Host's `PARAMETER_DIGEST_ALGORITHM`
30
+ * because the algorithm is the same (SHA-256 over `canonicalJson`).
31
+ */
32
+ const EVIDENCE_PACKET_CHECKSUM_ALGORITHM = "fabric-canonical-json-sha256-v1";
33
+ /** Version of the `EvidencePacketPayload` shape produced by `generateEvidencePacket`. */
34
+ const EVIDENCE_PACKET_SCHEMA_VERSION = 1;
35
+ /** Packet type used when the input does not name one. */
36
+ const DEFAULT_EVIDENCE_PACKET_TYPE = "compliance_evidence";
37
+ /** Anonymization reason recorded when the caller does not supply one. */
38
+ const DEFAULT_EVIDENCE_REDACTION_REASON = "evidence_packet_generation";
39
+ var EvidencePacketGenerationError = class extends Error {
40
+ code;
41
+ constructor(code, message) {
42
+ super(message);
43
+ this.name = "EvidencePacketGenerationError";
44
+ this.code = code;
45
+ }
46
+ };
47
+ function compareStrings(left, right) {
48
+ return left < right ? -1 : left > right ? 1 : 0;
49
+ }
50
+ function assertNonEmptyString(value, what) {
51
+ if (typeof value !== "string" || value.length === 0) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${what} must be a non-empty string.`);
52
+ }
53
+ function isPlainObject(value) {
54
+ const prototype = Object.getPrototypeOf(value);
55
+ return prototype === Object.prototype || prototype === null;
56
+ }
57
+ /**
58
+ * Returns the list unchanged, but fails type-checking unless it names every member of `TUnion`
59
+ * (and nothing else), so these runtime lists cannot drift from the privacy types.
60
+ */
61
+ function exhaustiveList() {
62
+ return (list) => list;
63
+ }
64
+ const REDACTION_METHODS = Object.freeze(exhaustiveList()([
65
+ "nullify",
66
+ "mask",
67
+ "hash",
68
+ "tokenize",
69
+ "generalize",
70
+ "drop"
71
+ ]));
72
+ const PLATFORM_DATA_CLASSES = Object.freeze(exhaustiveList()([
73
+ "asset_event",
74
+ "read_model",
75
+ "raw_payload",
76
+ "evidence_packet",
77
+ "action_evidence",
78
+ "access_event"
79
+ ]));
80
+ const SHA256_HEX = /^[0-9a-f]{64}$/;
81
+ /** True for a string `Date.prototype.toISOString` produced: it parses and serializes back unchanged. */
82
+ function isIsoInstant(value) {
83
+ if (typeof value !== "string") return false;
84
+ const date = new Date(value);
85
+ return !Number.isNaN(date.getTime()) && date.toISOString() === value;
86
+ }
87
+ const METADATA_STRING_KEYS = [
88
+ "policyVersion",
89
+ "jobId",
90
+ "redactedAt",
91
+ "reason"
92
+ ];
93
+ const METADATA_KEYS = Object.freeze([
94
+ "redactionState",
95
+ "dataClass",
96
+ "redactedFields",
97
+ "preservedSemantics",
98
+ ...METADATA_STRING_KEYS
99
+ ]);
100
+ const FIELD_METADATA_KEYS = Object.freeze([
101
+ "fieldPath",
102
+ "method",
103
+ "policyVersion",
104
+ "jobId",
105
+ "redactedAt",
106
+ "reason",
107
+ "dataClass",
108
+ "beforeChecksum"
109
+ ]);
110
+ function hasExactKeys(value, keys) {
111
+ const present = Object.keys(value);
112
+ return present.length === keys.length && present.every((key) => keys.includes(key));
113
+ }
114
+ /**
115
+ * `AnonymizationEngine` copies any `_privacy` key through unredacted, because that is where it
116
+ * stores its own metadata. A `_privacy` value from the port is therefore accepted only when it has
117
+ * exactly the shape the engine writes; anything else could carry raw data past the policy.
118
+ */
119
+ function assertEnginePrivacyMetadata(value, path) {
120
+ const invalid = (detail) => new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is not engine-written AnonymizationMetadata (${detail}); _privacy values bypass the anonymization policy, so other data is rejected.`);
121
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid("not an object");
122
+ const metadata = value;
123
+ if (!hasExactKeys(metadata, METADATA_KEYS)) throw invalid("unexpected or missing keys");
124
+ if (metadata.redactionState !== "anonymized") throw invalid("redactionState");
125
+ if (!PLATFORM_DATA_CLASSES.includes(metadata.dataClass)) throw invalid("dataClass");
126
+ for (const key of METADATA_STRING_KEYS) if (typeof metadata[key] !== "string") throw invalid(key);
127
+ if (!isIsoInstant(metadata.redactedAt)) throw invalid("redactedAt");
128
+ if (!Array.isArray(metadata.preservedSemantics) || !metadata.preservedSemantics.every((item) => typeof item === "string")) throw invalid("preservedSemantics");
129
+ if (!Array.isArray(metadata.redactedFields)) throw invalid("redactedFields");
130
+ metadata.redactedFields.forEach((field, index) => {
131
+ if (!field || typeof field !== "object" || Array.isArray(field)) throw invalid(`redactedFields[${index}]`);
132
+ const entry = field;
133
+ if (!hasExactKeys(entry, FIELD_METADATA_KEYS) || !Object.values(entry).every((item) => typeof item === "string")) throw invalid(`redactedFields[${index}]`);
134
+ if (!REDACTION_METHODS.includes(entry.method)) throw invalid(`redactedFields[${index}].method`);
135
+ if (!PLATFORM_DATA_CLASSES.includes(entry.dataClass)) throw invalid(`redactedFields[${index}].dataClass`);
136
+ if (!isIsoInstant(entry.redactedAt)) throw invalid(`redactedFields[${index}].redactedAt`);
137
+ if (!SHA256_HEX.test(entry.beforeChecksum)) throw invalid(`redactedFields[${index}].beforeChecksum`);
138
+ });
139
+ }
140
+ /**
141
+ * Converts a value to plain JSON. Dates become ISO strings; objects with a `toJSON` method (for
142
+ * example decimal types) are replaced by its result. Values JSON cannot represent faithfully
143
+ * (bigint, functions, symbols, non-finite numbers, invalid dates, other non-plain objects, an own
144
+ * `__proto__` key) throw rather than being silently changed or dropped. Objects are rebuilt with a
145
+ * null prototype, so later reads cannot pick up inherited fields.
146
+ */
147
+ function toPlainJson(value, path) {
148
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
149
+ if (typeof value === "number") {
150
+ if (!Number.isFinite(value)) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is not a finite number.`);
151
+ return value;
152
+ }
153
+ if (value instanceof Date) {
154
+ if (Number.isNaN(value.getTime())) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is an invalid Date.`);
155
+ return value.toISOString();
156
+ }
157
+ if (Array.isArray(value)) return value.map((item, index) => item === void 0 ? null : toPlainJson(item, `${path}[${index}]`));
158
+ if (typeof value === "object") {
159
+ if (!isPlainObject(value)) {
160
+ const toJSON = value.toJSON;
161
+ if (typeof toJSON === "function") return toPlainJson(toJSON.call(value), path);
162
+ throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} is a non-plain object that cannot be represented as JSON.`);
163
+ }
164
+ const result = Object.create(null);
165
+ for (const [key, child] of Object.entries(value)) {
166
+ if (key === "__proto__") throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} has an own "__proto__" key.`);
167
+ if (child === void 0) continue;
168
+ const normalized = toPlainJson(child, `${path}.${key}`);
169
+ if (key === "_privacy") assertEnginePrivacyMetadata(normalized, `${path}._privacy`);
170
+ result[key] = normalized;
171
+ }
172
+ return result;
173
+ }
174
+ throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} has unsupported type ${typeof value}.`);
175
+ }
176
+ /**
177
+ * The timestamp strings accepted for event `occurredAt`/`recordedAt`: an ISO 8601 date-time, or
178
+ * PostgreSQL's ISO output, with an explicit zone. Groups: year (4 digits, or a sign and 4 to 6
179
+ * digits), month, day, `T` or one space, hour, minute, optional second and fraction, an optional
180
+ * single space, then `Z` or an offset written `+HH`, `+HH:MM` or `+HHMM`.
181
+ */
182
+ const ZONED_TIMESTAMP = /^([+-]\d{4,6}|\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)? ?(?:(Z)|([+-])(\d{2})(?::?(\d{2}))?)$/;
183
+ function isLeapYear(year) {
184
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
185
+ }
186
+ function daysInMonth(year, month) {
187
+ return [
188
+ 31,
189
+ isLeapYear(year) ? 29 : 28,
190
+ 31,
191
+ 30,
192
+ 31,
193
+ 30,
194
+ 31,
195
+ 31,
196
+ 30,
197
+ 31,
198
+ 30,
199
+ 31
200
+ ][month - 1];
201
+ }
202
+ /**
203
+ * Parses a zoned timestamp string without `new Date(string)`, whose fallback parser accepts
204
+ * non-ISO text and rolls invalid dates over. Every field is range-checked, so the result is exactly
205
+ * the instant written, truncated to milliseconds. Returns `undefined` when the string is not
206
+ * accepted. A zone-less string is rejected because reading it would depend on the host's time zone.
207
+ */
208
+ function parseZonedTimestamp(value) {
209
+ const match = ZONED_TIMESTAMP.exec(value);
210
+ if (!match) return void 0;
211
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zulu, sign, offsetHourText, offsetMinuteText] = match;
212
+ const year = Number(yearText);
213
+ const month = Number(monthText);
214
+ const day = Number(dayText);
215
+ const hour = Number(hourText);
216
+ const minute = Number(minuteText);
217
+ const second = secondText === void 0 ? 0 : Number(secondText);
218
+ const millisecond = fraction === void 0 ? 0 : Number(fraction.padEnd(3, "0").slice(0, 3));
219
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month)) return void 0;
220
+ if (hour > 23 || minute > 59 || second > 59) return void 0;
221
+ let offsetMinutes = 0;
222
+ if (!zulu) {
223
+ const offsetHours = Number(offsetHourText);
224
+ const offsetMinutePart = offsetMinuteText === void 0 ? 0 : Number(offsetMinuteText);
225
+ if (offsetHours > 14 || offsetMinutePart > 59) return void 0;
226
+ offsetMinutes = (sign === "-" ? -1 : 1) * (offsetHours * 60 + offsetMinutePart);
227
+ }
228
+ const date = /* @__PURE__ */ new Date(0);
229
+ date.setUTCFullYear(year, month - 1, day);
230
+ date.setUTCHours(hour, minute, second, millisecond);
231
+ const instant = date.getTime() - offsetMinutes * 6e4;
232
+ return Number.isNaN(new Date(instant).getTime()) ? void 0 : instant;
233
+ }
234
+ /** Normalizes a Date or zoned timestamp string to an ISO string, at millisecond precision. */
235
+ function toIsoTimestamp(value, path) {
236
+ const instant = value instanceof Date ? value.getTime() : typeof value === "string" ? parseZonedTimestamp(value) : void 0;
237
+ if (instant === void 0 || Number.isNaN(instant)) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${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).`);
238
+ return new Date(instant).toISOString();
239
+ }
240
+ function assertScope(record, scope, section) {
241
+ if (record.tenantId !== scope.tenantId || record.spaceId !== scope.spaceId) throw new EvidencePacketGenerationError("EVIDENCE_SCOPE_MISMATCH", `Evidence read port returned a ${section} record (${String(record.id)}) outside tenant/space ${scope.tenantId}/${scope.spaceId}.`);
242
+ }
243
+ /** Normalizes, scope-checks and de-duplicates a section. Conflicting records with one id throw. */
244
+ function normalizeSection(records, scope, section, prepare) {
245
+ if (!Array.isArray(records)) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `Evidence read port returned a non-array for ${section}.`);
246
+ const byId = /* @__PURE__ */ new Map();
247
+ records.forEach((raw, index) => {
248
+ const path = `${section}[${index}]`;
249
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} must be an object.`);
250
+ assertScope(raw, scope, section);
251
+ const record = toPlainJson(raw, path);
252
+ assertScope(record, scope, section);
253
+ assertNonEmptyString(record.id, `${path}.id`);
254
+ prepare?.(record, path);
255
+ const canonical = canonicalJson(record);
256
+ const existing = byId.get(record.id);
257
+ if (existing && existing.canonical !== canonical) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `Evidence read port returned conflicting ${section} records with id ${String(record.id)}.`);
258
+ byId.set(record.id, {
259
+ record,
260
+ canonical
261
+ });
262
+ });
263
+ return [...byId.values()].map((entry) => entry.record);
264
+ }
265
+ function byId(left, right) {
266
+ return compareStrings(left.id, right.id);
267
+ }
268
+ function byEventOrder(left, right) {
269
+ return Date.parse(left.occurredAt) - Date.parse(right.occurredAt) || Date.parse(left.recordedAt) - Date.parse(right.recordedAt) || left.sequence - right.sequence || byId(left, right);
270
+ }
271
+ function assertSubject(subject, path) {
272
+ if (!subject || typeof subject !== "object") throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path} must be an object.`);
273
+ assertNonEmptyString(subject.subjectType, `${path}.subjectType`);
274
+ assertNonEmptyString(subject.subjectId, `${path}.subjectId`);
275
+ return {
276
+ subjectType: subject.subjectType,
277
+ subjectId: subject.subjectId
278
+ };
279
+ }
280
+ function compareSubjects(left, right) {
281
+ return compareStrings(left.subjectType, right.subjectType) || compareStrings(left.subjectId, right.subjectId);
282
+ }
283
+ async function resolveSubjects(primary, includeRelated, port, scope) {
284
+ if (!includeRelated || !port.resolveRelatedSubjects) return [primary];
285
+ const related = await port.resolveRelatedSubjects({ ...primary }, { ...scope });
286
+ if (!Array.isArray(related)) throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", "resolveRelatedSubjects must return an array.");
287
+ const key = (subject) => `${subject.subjectType}\u0000${subject.subjectId}`;
288
+ const seen = /* @__PURE__ */ new Set([key(primary)]);
289
+ const unique = [];
290
+ related.forEach((candidate, index) => {
291
+ const subject = assertSubject(candidate, `relatedSubjects[${index}]`);
292
+ if (seen.has(key(subject))) return;
293
+ seen.add(key(subject));
294
+ unique.push(subject);
295
+ });
296
+ return [primary, ...unique.sort(compareSubjects)];
297
+ }
298
+ function assertRequestedInvocation(record, path, requested) {
299
+ const invocationId = record.actionInvocationId;
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
+ }
302
+ const REFERENCE_METADATA_FIELDS = {
303
+ actionInvocations: ["actionId", "status"],
304
+ events: [
305
+ "eventType",
306
+ "subjectType",
307
+ "subjectId",
308
+ "actionInvocationId"
309
+ ],
310
+ policyEvaluations: [
311
+ "actionInvocationId",
312
+ "policyId",
313
+ "policyVersion",
314
+ "result"
315
+ ],
316
+ adapterInvocations: [
317
+ "actionInvocationId",
318
+ "adapterType",
319
+ "operation",
320
+ "status"
321
+ ]
322
+ };
323
+ const REFERENCE_TYPES = {
324
+ actionInvocations: "action_invocation",
325
+ events: "asset_event",
326
+ policyEvaluations: "policy_evaluation",
327
+ adapterInvocations: "adapter_invocation"
328
+ };
329
+ /**
330
+ * References are built from the redacted payload so nothing outside it can carry a raw value.
331
+ * Each record's `id` must come through redaction unchanged, because references and verifiers
332
+ * identify records by it.
333
+ */
334
+ function buildReferences(payload, unredacted) {
335
+ return [
336
+ "actionInvocations",
337
+ "events",
338
+ "policyEvaluations",
339
+ "adapterInvocations"
340
+ ].flatMap((section) => payload[section].map((record, index) => {
341
+ const originalId = unredacted[section][index]?.id;
342
+ 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
+ const metadata = {};
344
+ for (const field of REFERENCE_METADATA_FIELDS[section]) {
345
+ const value = record[field];
346
+ if (typeof value === "string") metadata[field] = value;
347
+ }
348
+ return {
349
+ referenceType: REFERENCE_TYPES[section],
350
+ referenceId: record.id,
351
+ metadata
352
+ };
353
+ }));
354
+ }
355
+ function redactedSubjects(value) {
356
+ if (!Array.isArray(value)) throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", "The anonymization policy removed payload.subjects.");
357
+ return value.map((subject, index) => {
358
+ try {
359
+ return assertSubject(subject, `subjects[${index}]`);
360
+ } catch {
361
+ throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", `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.`);
362
+ }
363
+ });
364
+ }
365
+ /** Lowercase hex SHA-256 of `canonicalJson(payload)`. */
366
+ function computeEvidencePacketChecksum(payload) {
367
+ return (0, crypto.createHash)("sha256").update(canonicalJson(payload)).digest("hex");
368
+ }
369
+ /**
370
+ * Builds a sealed evidence packet for one subject from the application's read port.
371
+ *
372
+ * Steps: resolve subjects (primary first, then related, de-duplicated and sorted); read events,
373
+ * then the action invocations, policy evaluations and adapter invocations those events reference;
374
+ * reject any record outside `context.tenantId`/`context.spaceId`; normalize to plain JSON and sort
375
+ * deterministically; redact the payload with `AnonymizationEngine` (data class
376
+ * `"evidence_packet"`); checksum the redacted payload; and build references from it.
377
+ *
378
+ * Nothing is persisted. Storing the returned envelope is the application's job.
379
+ */
380
+ async function generateEvidencePacket(input, context, options) {
381
+ const { port } = options;
382
+ if (!port || typeof port.listEvents !== "function") throw new TypeError("generateEvidencePacket requires a read port with listEvents().");
383
+ if (!options.anonymizationPolicy || !Array.isArray(options.anonymizationPolicy.fields)) throw new TypeError("generateEvidencePacket requires an anonymizationPolicy (use { fields: [] } to redact nothing).");
384
+ assertNonEmptyString(context.tenantId, "context.tenantId");
385
+ assertNonEmptyString(context.spaceId, "context.spaceId");
386
+ assertNonEmptyString(context.actorId, "context.actorId");
387
+ assertNonEmptyString(context.correlationId, "context.correlationId");
388
+ const scope = {
389
+ tenantId: context.tenantId,
390
+ spaceId: context.spaceId
391
+ };
392
+ const primary = assertSubject(input.subject, "input.subject");
393
+ const includeRelated = input.includeRelated !== false && typeof port.resolveRelatedSubjects === "function";
394
+ const packetType = input.packetType ?? "compliance_evidence";
395
+ assertNonEmptyString(packetType, "input.packetType");
396
+ const subjects = await resolveSubjects(primary, includeRelated, port, scope);
397
+ const events = normalizeSection(await port.listEvents({
398
+ ...scope,
399
+ primarySubject: { ...primary },
400
+ subjects: subjects.map((subject) => ({ ...subject }))
401
+ }), scope, "events", (record, path) => {
402
+ for (const field of [
403
+ "eventType",
404
+ "subjectType",
405
+ "subjectId",
406
+ "correlationId"
407
+ ]) assertNonEmptyString(record[field], `${path}.${field}`);
408
+ if (typeof record.sequence !== "number") throw new EvidencePacketGenerationError("EVIDENCE_INVALID_RECORD", `${path}.sequence must be a number.`);
409
+ record.occurredAt = toIsoTimestamp(record.occurredAt, `${path}.occurredAt`);
410
+ record.recordedAt = toIsoTimestamp(record.recordedAt, `${path}.recordedAt`);
411
+ const invocationId = record.actionInvocationId;
412
+ if (invocationId !== void 0 && invocationId !== null) assertNonEmptyString(invocationId, `${path}.actionInvocationId`);
413
+ }).sort(byEventOrder);
414
+ const invocationIds = [...new Set(events.map((event) => event.actionInvocationId).filter((id) => typeof id === "string"))].sort(compareStrings);
415
+ const requested = new Set(invocationIds);
416
+ const read = async (method) => {
417
+ if (!method || invocationIds.length === 0) return [];
418
+ return method.call(port, { ...scope }, [...invocationIds]);
419
+ };
420
+ const [rawActionInvocations, rawPolicyEvaluations, rawAdapterInvocations] = await Promise.all([
421
+ read(port.listActionInvocations),
422
+ read(port.listPolicyEvaluations),
423
+ read(port.listAdapterInvocations)
424
+ ]);
425
+ const actionInvocations = normalizeSection(rawActionInvocations, scope, "actionInvocations", (record, path) => {
426
+ 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
+ }).sort(byId);
428
+ const policyEvaluations = normalizeSection(rawPolicyEvaluations, scope, "policyEvaluations", (record, path) => assertRequestedInvocation(record, path, requested)).sort(byId);
429
+ const adapterInvocations = normalizeSection(rawAdapterInvocations, scope, "adapterInvocations", (record, path) => assertRequestedInvocation(record, path, requested)).sort(byId);
430
+ const generatedAt = context.now ?? /* @__PURE__ */ new Date();
431
+ const id = options.idFactory ? options.idFactory() : require_ids_index.createFabricId(require_ids_index.FABRIC_ID_PREFIXES.complianceEvidencePacket);
432
+ assertNonEmptyString(id, "packet id");
433
+ const unredacted = {
434
+ schemaVersion: 1,
435
+ packetType,
436
+ tenantId: scope.tenantId,
437
+ spaceId: scope.spaceId,
438
+ primarySubject: { ...primary },
439
+ subjects: subjects.map((subject) => ({ ...subject })),
440
+ includeRelated,
441
+ ...input.policyVersion !== void 0 ? { policyVersion: input.policyVersion } : {},
442
+ events,
443
+ actionInvocations,
444
+ policyEvaluations,
445
+ adapterInvocations
446
+ };
447
+ const anonymizationContext = {
448
+ tenantId: scope.tenantId,
449
+ spaceId: scope.spaceId,
450
+ jobId: options.anonymization?.jobId ?? id,
451
+ reason: options.anonymization?.reason ?? "evidence_packet_generation",
452
+ redactedAt: generatedAt,
453
+ ...options.anonymization?.policyVersion !== void 0 ? { policyVersion: options.anonymization.policyVersion } : {}
454
+ };
455
+ const keySorted = JSON.parse(canonicalJson(unredacted));
456
+ const redacted = new require_engine.AnonymizationEngine(options.anonymizationPolicy).anonymizeJsonValue(keySorted, anonymizationContext, "evidence_packet");
457
+ const payload = {
458
+ ...redacted.value,
459
+ redaction: redacted.metadata
460
+ };
461
+ for (const section of [
462
+ "events",
463
+ "actionInvocations",
464
+ "policyEvaluations",
465
+ "adapterInvocations"
466
+ ]) if (!Array.isArray(payload[section])) throw new EvidencePacketGenerationError("EVIDENCE_IDENTIFIER_REDACTED", `The anonymization policy removed or replaced payload.${section}.`);
467
+ const envelopeSubjects = redactedSubjects(payload.subjects);
468
+ const references = buildReferences(payload, keySorted);
469
+ return {
470
+ id,
471
+ tenantId: scope.tenantId,
472
+ spaceId: scope.spaceId,
473
+ packetType,
474
+ status: "sealed",
475
+ subjects: envelopeSubjects,
476
+ references,
477
+ payload,
478
+ generatedAt: generatedAt.toISOString(),
479
+ generatedBy: context.actorId,
480
+ ...input.policyVersion !== void 0 ? { policyVersion: input.policyVersion } : {},
481
+ checksum: computeEvidencePacketChecksum(payload),
482
+ checksumAlgorithm: EVIDENCE_PACKET_CHECKSUM_ALGORITHM,
483
+ correlationId: context.correlationId,
484
+ ...context.causationId !== void 0 ? { causationId: context.causationId } : {}
485
+ };
486
+ }
487
+ /**
488
+ * Recomputes an envelope's checksum. Returns `false`, never throws, when the checksum is missing,
489
+ * the algorithm is missing or unknown, the payload cannot be serialized, or the values differ.
490
+ * It covers `payload` only, which includes the subjects and the redaction metadata; the other
491
+ * envelope fields are not covered.
492
+ */
493
+ function verifyEvidencePacketChecksum(envelope) {
494
+ try {
495
+ if (!envelope || typeof envelope.checksum !== "string" || envelope.checksum.length === 0) return false;
496
+ if (envelope.checksumAlgorithm !== "fabric-canonical-json-sha256-v1") return false;
497
+ return computeEvidencePacketChecksum(envelope.payload) === envelope.checksum;
498
+ } catch {
499
+ return false;
500
+ }
501
+ }
502
+ /** Wraps `generateEvidencePacket` as an `EvidenceRenderer` for one packet type. */
503
+ function createEvidencePacketRenderer(options) {
504
+ const { packetType = DEFAULT_EVIDENCE_PACKET_TYPE, ...generatorOptions } = options;
505
+ return {
506
+ packetType,
507
+ render: (input, context) => generateEvidencePacket({
508
+ ...input,
509
+ packetType
510
+ }, context, generatorOptions)
511
+ };
512
+ }
513
+ //#endregion
514
+ Object.defineProperty(exports, "DEFAULT_EVIDENCE_PACKET_TYPE", {
515
+ enumerable: true,
516
+ get: function() {
517
+ return DEFAULT_EVIDENCE_PACKET_TYPE;
518
+ }
519
+ });
520
+ Object.defineProperty(exports, "DEFAULT_EVIDENCE_REDACTION_REASON", {
521
+ enumerable: true,
522
+ get: function() {
523
+ return DEFAULT_EVIDENCE_REDACTION_REASON;
524
+ }
525
+ });
526
+ Object.defineProperty(exports, "EVIDENCE_PACKET_CHECKSUM_ALGORITHM", {
527
+ enumerable: true,
528
+ get: function() {
529
+ return EVIDENCE_PACKET_CHECKSUM_ALGORITHM;
530
+ }
531
+ });
532
+ Object.defineProperty(exports, "EVIDENCE_PACKET_SCHEMA_VERSION", {
533
+ enumerable: true,
534
+ get: function() {
535
+ return EVIDENCE_PACKET_SCHEMA_VERSION;
536
+ }
537
+ });
538
+ Object.defineProperty(exports, "EvidencePacketGenerationError", {
539
+ enumerable: true,
540
+ get: function() {
541
+ return EvidencePacketGenerationError;
542
+ }
543
+ });
544
+ Object.defineProperty(exports, "canonicalJson", {
545
+ enumerable: true,
546
+ get: function() {
547
+ return canonicalJson;
548
+ }
549
+ });
550
+ Object.defineProperty(exports, "computeEvidencePacketChecksum", {
551
+ enumerable: true,
552
+ get: function() {
553
+ return computeEvidencePacketChecksum;
554
+ }
555
+ });
556
+ Object.defineProperty(exports, "createEvidencePacketRenderer", {
557
+ enumerable: true,
558
+ get: function() {
559
+ return createEvidencePacketRenderer;
560
+ }
561
+ });
562
+ Object.defineProperty(exports, "generateEvidencePacket", {
563
+ enumerable: true,
564
+ get: function() {
565
+ return generateEvidencePacket;
566
+ }
567
+ });
568
+ Object.defineProperty(exports, "verifyEvidencePacketChecksum", {
569
+ enumerable: true,
570
+ get: function() {
571
+ return verifyEvidencePacketChecksum;
572
+ }
573
+ });
574
+
575
+ //# sourceMappingURL=evidence-Cq48eDvA.cjs.map