@fabricorg/platform 0.1.1 → 0.2.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/dist/actions/index.cjs +48 -0
- package/dist/actions/index.cjs.map +1 -0
- package/dist/actions/index.d.cts +116 -0
- package/dist/actions/index.js +1 -57
- package/dist/actions/index.js.map +1 -1
- package/dist/adapters/index.cjs +28 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +83 -0
- package/dist/adapters/index.js +1 -99
- package/dist/adapters/index.js.map +1 -1
- package/dist/chunk-3A6FU3KV.js +67 -0
- package/dist/chunk-3A6FU3KV.js.map +1 -0
- package/dist/chunk-3F6FKQZA.js +89 -0
- package/dist/chunk-3F6FKQZA.js.map +1 -0
- package/dist/chunk-3ZW5USXF.js +101 -0
- package/dist/chunk-3ZW5USXF.js.map +1 -0
- package/dist/chunk-4YQOGV7H.js +55 -0
- package/dist/chunk-4YQOGV7H.js.map +1 -0
- package/dist/chunk-6FMW4NF6.js +59 -0
- package/dist/chunk-6FMW4NF6.js.map +1 -0
- package/dist/chunk-7KJHGAZY.cjs +99 -0
- package/dist/chunk-7KJHGAZY.cjs.map +1 -0
- package/dist/chunk-GB2PSZ33.cjs +74 -0
- package/dist/chunk-GB2PSZ33.cjs.map +1 -0
- package/dist/chunk-I35B576X.js +30 -0
- package/dist/chunk-I35B576X.js.map +1 -0
- package/dist/chunk-IFMZPLM4.js +27 -0
- package/dist/chunk-IFMZPLM4.js.map +1 -0
- package/dist/chunk-JAXDEK5Z.cjs +63 -0
- package/dist/chunk-JAXDEK5Z.cjs.map +1 -0
- package/dist/chunk-JSTGOT6P.js +181 -0
- package/dist/chunk-JSTGOT6P.js.map +1 -0
- package/dist/chunk-KOC4A7A4.js +544 -0
- package/dist/chunk-KOC4A7A4.js.map +1 -0
- package/dist/chunk-KQEBNUZI.cjs +70 -0
- package/dist/chunk-KQEBNUZI.cjs.map +1 -0
- package/dist/chunk-LHCSVEFT.cjs +8 -0
- package/dist/chunk-LHCSVEFT.cjs.map +1 -0
- package/dist/chunk-LYX73U3P.cjs +195 -0
- package/dist/chunk-LYX73U3P.cjs.map +1 -0
- package/dist/chunk-LZSE6QGQ.cjs +60 -0
- package/dist/chunk-LZSE6QGQ.cjs.map +1 -0
- package/dist/chunk-N24NLCZ6.js +6 -0
- package/dist/chunk-N24NLCZ6.js.map +1 -0
- package/dist/chunk-NPVIFZQV.cjs +107 -0
- package/dist/chunk-NPVIFZQV.cjs.map +1 -0
- package/dist/chunk-OZLZHISS.js +3 -0
- package/dist/chunk-OZLZHISS.js.map +1 -0
- package/dist/chunk-PG5LYTUX.cjs +4 -0
- package/dist/chunk-PG5LYTUX.cjs.map +1 -0
- package/dist/chunk-PXMKCAQI.js +187 -0
- package/dist/chunk-PXMKCAQI.js.map +1 -0
- package/dist/chunk-Q3WYIWCN.cjs +183 -0
- package/dist/chunk-Q3WYIWCN.cjs.map +1 -0
- package/dist/chunk-QETQGAXJ.cjs +35 -0
- package/dist/chunk-QETQGAXJ.cjs.map +1 -0
- package/dist/chunk-RALMUSOX.js +55 -0
- package/dist/chunk-RALMUSOX.js.map +1 -0
- package/dist/chunk-SDXDHVTV.cjs +555 -0
- package/dist/chunk-SDXDHVTV.cjs.map +1 -0
- package/dist/chunk-YZKUEC2J.cjs +31 -0
- package/dist/chunk-YZKUEC2J.cjs.map +1 -0
- package/dist/displays/index.cjs +24 -0
- package/dist/displays/index.cjs.map +1 -0
- package/dist/displays/index.d.cts +34 -0
- package/dist/displays/index.js +1 -87
- package/dist/displays/index.js.map +1 -1
- package/dist/events/index.cjs +36 -0
- package/dist/events/index.cjs.map +1 -0
- package/dist/events/index.d.cts +35 -0
- package/dist/events/index.js +1 -53
- package/dist/events/index.js.map +1 -1
- package/dist/evidence/index.cjs +6 -0
- package/dist/evidence/index.cjs.map +1 -0
- package/dist/evidence/index.d.cts +43 -0
- package/dist/evidence/index.js +1 -1
- package/dist/ids/index.cjs +24 -0
- package/dist/ids/index.cjs.map +1 -0
- package/dist/ids/index.d.cts +19 -0
- package/dist/ids/index.js +1 -53
- package/dist/ids/index.js.map +1 -1
- package/dist/index.cjs +268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.js +13 -1355
- package/dist/index.js.map +1 -1
- package/dist/modules/index.cjs +42 -0
- package/dist/modules/index.cjs.map +1 -0
- package/dist/modules/index.d.cts +73 -0
- package/dist/modules/index.js +7 -373
- package/dist/modules/index.js.map +1 -1
- package/dist/objects/index.cjs +24 -0
- package/dist/objects/index.cjs.map +1 -0
- package/dist/objects/index.d.cts +7 -0
- package/dist/objects/index.js +1 -28
- package/dist/objects/index.js.map +1 -1
- package/dist/policies/index.cjs +48 -0
- package/dist/policies/index.cjs.map +1 -0
- package/dist/policies/index.d.cts +143 -0
- package/dist/policies/index.js +1 -542
- package/dist/policies/index.js.map +1 -1
- package/dist/privacy/index.cjs +12 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +77 -0
- package/dist/privacy/index.js +1 -4
- package/dist/privacy/index.js.map +1 -1
- package/dist/projections/index.cjs +12 -0
- package/dist/projections/index.cjs.map +1 -0
- package/dist/projections/index.d.cts +52 -0
- package/dist/projections/index.js +1 -179
- package/dist/projections/index.js.map +1 -1
- package/dist/state-machines/index.cjs +32 -0
- package/dist/state-machines/index.cjs.map +1 -0
- package/dist/state-machines/index.d.cts +60 -0
- package/dist/state-machines/index.js +1 -65
- package/dist/state-machines/index.js.map +1 -1
- package/dist/testing/index.cjs +21 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +19 -0
- package/dist/testing/index.js +2 -56
- package/dist/testing/index.js.map +1 -1
- package/package.json +196 -111
package/dist/index.js
CHANGED
|
@@ -1,1357 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
function assertActorType(value) {
|
|
16
|
-
if (!isActorType(value)) {
|
|
17
|
-
throw new Error(`Invalid actorType: ${value}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
var registry = /* @__PURE__ */ new Map();
|
|
21
|
-
function assertActionId(actionId) {
|
|
22
|
-
if (!/^[a-z][a-z0-9-]*\.[a-z][a-z0-9_]*$/.test(actionId)) {
|
|
23
|
-
throw new Error(`Invalid action ID: ${actionId}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function registerAction(definition) {
|
|
27
|
-
assertActionId(definition.actionId);
|
|
28
|
-
if (registry.has(definition.actionId)) {
|
|
29
|
-
throw new Error(`Action "${definition.actionId}" is already registered`);
|
|
30
|
-
}
|
|
31
|
-
if (definition.mutatesDomain && definition.emitsEvents.length === 0) {
|
|
32
|
-
throw new Error(
|
|
33
|
-
`Action "${definition.actionId}" mutates domain but emits no events. Every mutating action must emit at least one AssetEvent.`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
registry.set(definition.actionId, definition);
|
|
37
|
-
}
|
|
38
|
-
function resolveAction(actionId) {
|
|
39
|
-
return registry.get(actionId);
|
|
40
|
-
}
|
|
41
|
-
function isRegisteredActionId(actionId) {
|
|
42
|
-
return registry.has(actionId);
|
|
43
|
-
}
|
|
44
|
-
function resolveRegisteredAction(actionId) {
|
|
45
|
-
if (!isRegisteredActionId(actionId)) {
|
|
46
|
-
return void 0;
|
|
47
|
-
}
|
|
48
|
-
return resolveAction(actionId);
|
|
49
|
-
}
|
|
50
|
-
function getRegisteredActionIds() {
|
|
51
|
-
return Array.from(registry.keys());
|
|
52
|
-
}
|
|
53
|
-
function registerActionIfMissing(definition) {
|
|
54
|
-
if (!registry.has(definition.actionId)) {
|
|
55
|
-
registerAction(definition);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// adapters/index.ts
|
|
60
|
-
function adapterKey(adapterType, operation) {
|
|
61
|
-
return `${adapterType}:${operation}`;
|
|
62
|
-
}
|
|
63
|
-
var MissingAdapterRegistrationError = class extends Error {
|
|
64
|
-
constructor(adapterType, operation) {
|
|
65
|
-
super(`No adapter registered for adapterType "${adapterType}" and operation "${operation}"`);
|
|
66
|
-
this.name = "MissingAdapterRegistrationError";
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
var AdapterRegistry = class {
|
|
70
|
-
adapters = /* @__PURE__ */ new Map();
|
|
71
|
-
register(adapter) {
|
|
72
|
-
const key = adapterKey(adapter.adapterType, adapter.operation);
|
|
73
|
-
if (this.adapters.has(key)) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Adapter already registered for adapterType "${adapter.adapterType}" and operation "${adapter.operation}"`
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
this.adapters.set(key, adapter);
|
|
79
|
-
}
|
|
80
|
-
resolve(adapterType, operation) {
|
|
81
|
-
return this.adapters.get(adapterKey(adapterType, operation));
|
|
82
|
-
}
|
|
83
|
-
require(adapterType, operation) {
|
|
84
|
-
const adapter = this.resolve(adapterType, operation);
|
|
85
|
-
if (!adapter) {
|
|
86
|
-
throw new MissingAdapterRegistrationError(adapterType, operation);
|
|
87
|
-
}
|
|
88
|
-
return adapter;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
var DEFAULT_RETRY_POLICY = {
|
|
92
|
-
maxAttempts: 3,
|
|
93
|
-
initialDelayMs: 100,
|
|
94
|
-
backoffMultiplier: 2,
|
|
95
|
-
maxDelayMs: 1e3
|
|
96
|
-
};
|
|
97
|
-
var NO_RETRY_POLICY = {
|
|
98
|
-
idempotent: false,
|
|
99
|
-
maxAttempts: 1,
|
|
100
|
-
initialDelayMs: 0,
|
|
101
|
-
backoffMultiplier: 1,
|
|
102
|
-
maxDelayMs: 0
|
|
103
|
-
};
|
|
104
|
-
function resolveAdapterRetryPolicy(policy, defaultIdempotent) {
|
|
105
|
-
const idempotent = policy?.idempotent ?? defaultIdempotent;
|
|
106
|
-
if (!idempotent) {
|
|
107
|
-
return NO_RETRY_POLICY;
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
idempotent: true,
|
|
111
|
-
maxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),
|
|
112
|
-
initialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),
|
|
113
|
-
backoffMultiplier: Math.max(
|
|
114
|
-
1,
|
|
115
|
-
policy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier
|
|
116
|
-
),
|
|
117
|
-
maxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs)
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function getRetryDelayMs(attempt, policy) {
|
|
121
|
-
const exponentialDelay = policy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);
|
|
122
|
-
return Math.min(exponentialDelay, policy.maxDelayMs);
|
|
123
|
-
}
|
|
124
|
-
async function executeWithAdapterRetry(options) {
|
|
125
|
-
const policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);
|
|
126
|
-
const sleep = options.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
127
|
-
let lastError;
|
|
128
|
-
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
129
|
-
try {
|
|
130
|
-
const result = await options.execute(attempt, policy.maxAttempts);
|
|
131
|
-
if (options.isSuccessful(result)) {
|
|
132
|
-
await options.onAttempt?.({ attempt, result, willRetry: false });
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
lastError = options.getError(result) ?? "Adapter returned unsuccessful result";
|
|
136
|
-
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
137
|
-
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
138
|
-
await options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });
|
|
139
|
-
if (!willRetry) {
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
await sleep(nextDelayMs ?? 0);
|
|
143
|
-
} catch (error) {
|
|
144
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
145
|
-
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
146
|
-
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
147
|
-
await options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });
|
|
148
|
-
if (!willRetry) {
|
|
149
|
-
throw error;
|
|
150
|
-
}
|
|
151
|
-
await sleep(nextDelayMs ?? 0);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
throw new Error(lastError ?? "Adapter retry exhausted");
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// events/index.ts
|
|
158
|
-
var subjectTypeRegistry = /* @__PURE__ */ new Set([
|
|
159
|
-
"ActionInvocation",
|
|
160
|
-
"PolicyEvaluation",
|
|
161
|
-
"AdapterInvocation"
|
|
162
|
-
]);
|
|
163
|
-
var eventTypeRegistry = /* @__PURE__ */ new Set([
|
|
164
|
-
"ComplianceBlocked",
|
|
165
|
-
"StateTransitioned",
|
|
166
|
-
"AdapterInvocationStarted",
|
|
167
|
-
"AdapterInvocationSucceeded",
|
|
168
|
-
"AdapterInvocationFailed",
|
|
169
|
-
"WebhookReceived"
|
|
170
|
-
]);
|
|
171
|
-
var dynamicSubjectTypes = /* @__PURE__ */ new Set();
|
|
172
|
-
var dynamicEventTypes = /* @__PURE__ */ new Set();
|
|
173
|
-
function registerSubjectType(type) {
|
|
174
|
-
dynamicSubjectTypes.add(type);
|
|
175
|
-
}
|
|
176
|
-
function isValidSubjectType(type) {
|
|
177
|
-
return subjectTypeRegistry.has(type) || dynamicSubjectTypes.has(type);
|
|
178
|
-
}
|
|
179
|
-
function getRegisteredSubjectTypes() {
|
|
180
|
-
return Array.from(/* @__PURE__ */ new Set([...subjectTypeRegistry, ...dynamicSubjectTypes]));
|
|
181
|
-
}
|
|
182
|
-
function registerEventType(type) {
|
|
183
|
-
dynamicEventTypes.add(type);
|
|
184
|
-
}
|
|
185
|
-
function isValidEventType(type) {
|
|
186
|
-
return eventTypeRegistry.has(type) || dynamicEventTypes.has(type);
|
|
187
|
-
}
|
|
188
|
-
function getRegisteredEventTypes() {
|
|
189
|
-
return Array.from(/* @__PURE__ */ new Set([...eventTypeRegistry, ...dynamicEventTypes]));
|
|
190
|
-
}
|
|
191
|
-
function createEventTypeRegistry(initialEventTypes = []) {
|
|
192
|
-
const eventTypes = new Set(initialEventTypes);
|
|
193
|
-
return {
|
|
194
|
-
register(eventType) {
|
|
195
|
-
if (!eventType) {
|
|
196
|
-
throw new Error("Event type cannot be empty");
|
|
197
|
-
}
|
|
198
|
-
eventTypes.add(eventType);
|
|
199
|
-
},
|
|
200
|
-
has(eventType) {
|
|
201
|
-
return eventTypes.has(eventType);
|
|
202
|
-
},
|
|
203
|
-
list() {
|
|
204
|
-
return Array.from(eventTypes).sort();
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ids/index.ts
|
|
210
|
-
var CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
211
|
-
var FABRIC_ID_PREFIXES = {
|
|
212
|
-
assetEvent: "evt",
|
|
213
|
-
actionInvocation: "act",
|
|
214
|
-
policyEvaluation: "pol",
|
|
215
|
-
adapterInvocation: "adp",
|
|
216
|
-
adapterDeadLetterQueue: "dlq",
|
|
217
|
-
accessEvent: "acc",
|
|
218
|
-
projectionSnapshot: "psn",
|
|
219
|
-
complianceEvidencePacket: "cep"
|
|
220
|
-
};
|
|
221
|
-
var ID_REGEX = /^([a-z][a-z0-9]{1,7})_([0-9A-Z]{26})$/;
|
|
222
|
-
function randomBase32(length) {
|
|
223
|
-
let output = "";
|
|
224
|
-
const randomValues = new Uint8Array(length);
|
|
225
|
-
globalThis.crypto.getRandomValues(randomValues);
|
|
226
|
-
for (const value of randomValues) {
|
|
227
|
-
output += CROCKFORD_BASE32[value % CROCKFORD_BASE32.length];
|
|
228
|
-
}
|
|
229
|
-
return output;
|
|
230
|
-
}
|
|
231
|
-
function createSortableRandomId() {
|
|
232
|
-
let timestamp = Date.now();
|
|
233
|
-
let encodedTime = "";
|
|
234
|
-
for (let index = 0; index < 10; index++) {
|
|
235
|
-
encodedTime = CROCKFORD_BASE32[timestamp % 32] + encodedTime;
|
|
236
|
-
timestamp = Math.floor(timestamp / 32);
|
|
237
|
-
}
|
|
238
|
-
return `${encodedTime}${randomBase32(16)}`;
|
|
239
|
-
}
|
|
240
|
-
function createFabricId(prefix) {
|
|
241
|
-
if (!/^[a-z][a-z0-9]{1,7}$/.test(prefix)) {
|
|
242
|
-
throw new Error(`Invalid Fabric ID prefix: ${prefix}`);
|
|
243
|
-
}
|
|
244
|
-
return `${prefix}_${createSortableRandomId()}`;
|
|
245
|
-
}
|
|
246
|
-
function isFabricId(id, prefix) {
|
|
247
|
-
const match = ID_REGEX.exec(id);
|
|
248
|
-
if (!match) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
return prefix ? match[1] === prefix : true;
|
|
252
|
-
}
|
|
253
|
-
function parseFabricId(id) {
|
|
254
|
-
const match = ID_REGEX.exec(id);
|
|
255
|
-
if (!match) {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
return { prefix: match[1], ulid: match[2] };
|
|
259
|
-
}
|
|
260
|
-
var registry2 = /* @__PURE__ */ new Map();
|
|
261
|
-
function registerDisplayRenderer(eventType, renderer) {
|
|
262
|
-
registry2.set(eventType, renderer);
|
|
263
|
-
}
|
|
264
|
-
function resolveDisplayRenderer(eventType) {
|
|
265
|
-
return registry2.get(eventType);
|
|
266
|
-
}
|
|
267
|
-
function denormalizeDisplaySnapshot(payload) {
|
|
268
|
-
if (payload !== null && typeof payload === "object" && "_displaySnapshot" in payload && payload._displaySnapshot !== null && typeof payload._displaySnapshot === "object" && "title" in payload._displaySnapshot) {
|
|
269
|
-
return payload._displaySnapshot;
|
|
270
|
-
}
|
|
271
|
-
if (payload !== null && typeof payload === "object" && "_projectionSnapshot" in payload && payload._projectionSnapshot !== null && typeof payload._projectionSnapshot === "object" && "display" in payload._projectionSnapshot && payload._projectionSnapshot.display !== null && typeof payload._projectionSnapshot.display === "object" && "title" in payload._projectionSnapshot.display) {
|
|
272
|
-
const snapshot = payload._projectionSnapshot;
|
|
273
|
-
return {
|
|
274
|
-
...snapshot.display,
|
|
275
|
-
_cachedDisplay: {
|
|
276
|
-
source: "projection_snapshot",
|
|
277
|
-
cursor: snapshot.eventCursor,
|
|
278
|
-
checksum: snapshot.checksum,
|
|
279
|
-
generatedAt: snapshot.updatedAt
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
return void 0;
|
|
284
|
-
}
|
|
285
|
-
async function renderEventDisplay(eventType, payload, ctx) {
|
|
286
|
-
const snapshot = denormalizeDisplaySnapshot(payload);
|
|
287
|
-
if (snapshot) {
|
|
288
|
-
return snapshot;
|
|
289
|
-
}
|
|
290
|
-
const renderer = resolveDisplayRenderer(eventType);
|
|
291
|
-
if (!renderer) {
|
|
292
|
-
return {
|
|
293
|
-
title: eventType,
|
|
294
|
-
subtitle: "No display renderer registered for this event type."
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
return await renderer(payload, ctx);
|
|
298
|
-
}
|
|
299
|
-
async function _runInlineTests() {
|
|
300
|
-
const testEventType = "__TestEvent__";
|
|
301
|
-
registerDisplayRenderer(testEventType, (payload) => ({
|
|
302
|
-
title: `Test: ${payload.name}`,
|
|
303
|
-
subtitle: "registered renderer"
|
|
304
|
-
}));
|
|
305
|
-
const renderer = resolveDisplayRenderer(testEventType);
|
|
306
|
-
assert.ok(renderer, "resolveDisplayRenderer should return the registered renderer");
|
|
307
|
-
const result = await renderEventDisplay(testEventType, { name: "Alice" });
|
|
308
|
-
assert.strictEqual(result.title, "Test: Alice");
|
|
309
|
-
assert.strictEqual(result.subtitle, "registered renderer");
|
|
310
|
-
const snapshot = {
|
|
311
|
-
title: "Snapshot Title",
|
|
312
|
-
subtitle: "snapshot subtitle",
|
|
313
|
-
stateLabel: "archived",
|
|
314
|
-
primaryFacts: [{ label: "ID", value: "123" }]
|
|
315
|
-
};
|
|
316
|
-
const snapshotResult = await renderEventDisplay(testEventType, {
|
|
317
|
-
_displaySnapshot: snapshot
|
|
318
|
-
});
|
|
319
|
-
assert.deepStrictEqual(snapshotResult, snapshot);
|
|
320
|
-
const fallback = await renderEventDisplay("__UnknownEvent__", {});
|
|
321
|
-
assert.strictEqual(fallback.title, "__UnknownEvent__");
|
|
322
|
-
assert.strictEqual(fallback.subtitle, "No display renderer registered for this event type.");
|
|
323
|
-
registerDisplayRenderer("__AsyncEvent__", async (_payload, ctx) => {
|
|
324
|
-
return {
|
|
325
|
-
title: `Async: ${ctx?.tenantId ?? "no-ctx"}`
|
|
326
|
-
};
|
|
327
|
-
});
|
|
328
|
-
const asyncResult = await renderEventDisplay(
|
|
329
|
-
"__AsyncEvent__",
|
|
330
|
-
{},
|
|
331
|
-
{ tenantId: "t-1", spaceId: "s-1", db: {} }
|
|
332
|
-
);
|
|
333
|
-
assert.strictEqual(asyncResult.title, "Async: t-1");
|
|
334
|
-
console.log("\u2705 display engine inline tests passed");
|
|
335
|
-
}
|
|
336
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
337
|
-
_runInlineTests().catch((err) => {
|
|
338
|
-
console.error("\u274C display engine inline tests failed", err);
|
|
339
|
-
process.exit(1);
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// objects/index.ts
|
|
344
|
-
var objectTypeRegistry = /* @__PURE__ */ new Set([
|
|
345
|
-
"AssetEvent",
|
|
346
|
-
"ActionInvocation",
|
|
347
|
-
"PolicyEvaluation",
|
|
348
|
-
"ApprovalRequest",
|
|
349
|
-
"ConsentRecord",
|
|
350
|
-
"AgentRun"
|
|
351
|
-
]);
|
|
352
|
-
function assertObjectType(type) {
|
|
353
|
-
if (!/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
|
|
354
|
-
throw new Error(
|
|
355
|
-
`Invalid object type: "${type}". Object types must be PascalCase alphanumeric.`
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
function registerObjectType(type) {
|
|
360
|
-
assertObjectType(type);
|
|
361
|
-
objectTypeRegistry.add(type);
|
|
362
|
-
}
|
|
363
|
-
function isValidObjectType(type) {
|
|
364
|
-
return objectTypeRegistry.has(type);
|
|
365
|
-
}
|
|
366
|
-
function getRegisteredObjectTypes() {
|
|
367
|
-
return Array.from(objectTypeRegistry);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// policies/index.ts
|
|
371
|
-
function assertPolicyId(policyId) {
|
|
372
|
-
if (!/^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*\.v[0-9]+$/.test(policyId)) {
|
|
373
|
-
throw new Error(`Invalid policy ID: ${policyId}`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
var DATA_POLICY_MAX_DEPTH = 5;
|
|
377
|
-
var DATA_POLICY_MAX_CONDITIONS_PER_NODE = 20;
|
|
378
|
-
var DATA_POLICY_MAX_TOTAL_CONDITIONS = 100;
|
|
379
|
-
var DATA_POLICY_MAX_PATH_SEGMENTS = 12;
|
|
380
|
-
var DATA_POLICY_MAX_STRING_LENGTH = 500;
|
|
381
|
-
var DATA_POLICY_CONTEXT_PATHS = /* @__PURE__ */ new Set([
|
|
382
|
-
"tenantId",
|
|
383
|
-
"spaceId",
|
|
384
|
-
"actionInvocationId",
|
|
385
|
-
"actionId",
|
|
386
|
-
"mode"
|
|
387
|
-
]);
|
|
388
|
-
var DATA_POLICY_COMPARISON_OPERATORS = /* @__PURE__ */ new Set([
|
|
389
|
-
"exists",
|
|
390
|
-
"equals",
|
|
391
|
-
"notEquals",
|
|
392
|
-
"gt",
|
|
393
|
-
"gte",
|
|
394
|
-
"lt",
|
|
395
|
-
"lte"
|
|
396
|
-
]);
|
|
397
|
-
var POLICY_RESULTS = /* @__PURE__ */ new Set(["pass", "warn", "block"]);
|
|
398
|
-
var registry3 = /* @__PURE__ */ new Map();
|
|
399
|
-
function registerPolicy(evaluator) {
|
|
400
|
-
assertPolicyId(evaluator.policyId);
|
|
401
|
-
const existing = registry3.get(evaluator.policyId);
|
|
402
|
-
if (existing && existing !== evaluator) {
|
|
403
|
-
throw new Error(
|
|
404
|
-
`Conflicting policy registration for "${evaluator.policyId}". A different evaluator is already registered.`
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
registry3.set(evaluator.policyId, evaluator);
|
|
408
|
-
}
|
|
409
|
-
function registerPolicyIfMissing(evaluator) {
|
|
410
|
-
if (!registry3.has(evaluator.policyId)) {
|
|
411
|
-
registerPolicy(evaluator);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const existing = registry3.get(evaluator.policyId);
|
|
415
|
-
if (existing && existing !== evaluator) {
|
|
416
|
-
throw new Error(
|
|
417
|
-
`Conflicting policy registration for "${evaluator.policyId}". A different evaluator is already registered.`
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
function resolvePolicy(policyId) {
|
|
422
|
-
return registry3.get(policyId);
|
|
423
|
-
}
|
|
424
|
-
function getRegisteredPolicyIds() {
|
|
425
|
-
return Array.from(registry3.keys());
|
|
426
|
-
}
|
|
427
|
-
async function evaluateRegisteredPolicies(policyIds, ctx) {
|
|
428
|
-
return evaluatePolicyDefinitions(
|
|
429
|
-
policyIds.map((policyId) => ({
|
|
430
|
-
policyId,
|
|
431
|
-
policyVersion: parsePolicyVersion(policyId),
|
|
432
|
-
kind: "code",
|
|
433
|
-
codeEvaluatorPolicyId: policyId
|
|
434
|
-
})),
|
|
435
|
-
ctx
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
async function evaluatePolicyDefinitions(policies, ctx) {
|
|
439
|
-
const outcomes = [];
|
|
440
|
-
for (const policy of policies) {
|
|
441
|
-
assertPolicyId(policy.policyId);
|
|
442
|
-
outcomes.push(await evaluatePolicyDefinition(policy, ctx));
|
|
443
|
-
}
|
|
444
|
-
return outcomes;
|
|
445
|
-
}
|
|
446
|
-
async function evaluatePolicyDefinition(policy, ctx) {
|
|
447
|
-
if (policy.kind === "data") {
|
|
448
|
-
return evaluateDataPolicy(policy, ctx);
|
|
449
|
-
}
|
|
450
|
-
if (policy.kind === "code") {
|
|
451
|
-
return evaluateCodePolicy(policy, ctx);
|
|
452
|
-
}
|
|
453
|
-
return evaluateHybridPolicy(policy, ctx);
|
|
454
|
-
}
|
|
455
|
-
async function evaluateCodePolicy(policy, ctx) {
|
|
456
|
-
const codeEvaluatorPolicyId = policy.codeEvaluatorPolicyId ?? policy.policyId;
|
|
457
|
-
const evaluator = registry3.get(codeEvaluatorPolicyId);
|
|
458
|
-
if (!evaluator) {
|
|
459
|
-
return {
|
|
460
|
-
policyId: policy.policyId,
|
|
461
|
-
policyVersion: policy.policyVersion,
|
|
462
|
-
policyKind: policy.kind,
|
|
463
|
-
result: "block",
|
|
464
|
-
reason: `No evaluator registered for policy ${codeEvaluatorPolicyId}`,
|
|
465
|
-
metadata: {
|
|
466
|
-
missingCodeEvaluatorPolicyId: codeEvaluatorPolicyId
|
|
467
|
-
},
|
|
468
|
-
dispatchEvidence: {
|
|
469
|
-
policyKind: policy.kind,
|
|
470
|
-
policyId: policy.policyId,
|
|
471
|
-
policyVersion: policy.policyVersion,
|
|
472
|
-
dispatchPath: ["code"],
|
|
473
|
-
code: {
|
|
474
|
-
requestedPolicyId: codeEvaluatorPolicyId,
|
|
475
|
-
policyId: codeEvaluatorPolicyId,
|
|
476
|
-
registered: false
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
const outcome = await evaluator.evaluate(ctx);
|
|
482
|
-
return normalizeOutcome(policy, outcome, {
|
|
483
|
-
policyKind: policy.kind,
|
|
484
|
-
policyId: policy.policyId,
|
|
485
|
-
policyVersion: policy.policyVersion,
|
|
486
|
-
dispatchPath: ["code"],
|
|
487
|
-
code: {
|
|
488
|
-
requestedPolicyId: codeEvaluatorPolicyId,
|
|
489
|
-
policyId: evaluator.policyId,
|
|
490
|
-
version: evaluator.version,
|
|
491
|
-
registered: true
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
async function evaluateHybridPolicy(policy, ctx) {
|
|
496
|
-
const dataOutcome = evaluateDataPolicy(policy, ctx);
|
|
497
|
-
const fallback = policy.fallback;
|
|
498
|
-
const fallbackDecision = fallback ? resolveFallbackDecision(policy, dataOutcome, fallback) : { shouldFallback: false, trigger: "not_triggered" };
|
|
499
|
-
if (!fallback || !fallbackDecision.shouldFallback) {
|
|
500
|
-
if (fallback && dataOutcome.dispatchEvidence) {
|
|
501
|
-
return {
|
|
502
|
-
...dataOutcome,
|
|
503
|
-
dispatchEvidence: {
|
|
504
|
-
...dataOutcome.dispatchEvidence,
|
|
505
|
-
fallback: {
|
|
506
|
-
used: false,
|
|
507
|
-
trigger: "not_triggered",
|
|
508
|
-
reason: "Data policy result did not trigger fallback",
|
|
509
|
-
fromResult: dataOutcome.result,
|
|
510
|
-
definitionVersion: policy.policyVersion,
|
|
511
|
-
codeEvaluatorPolicyId: fallback.codeEvaluatorPolicyId
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
return dataOutcome;
|
|
517
|
-
}
|
|
518
|
-
const codePolicy = {
|
|
519
|
-
policyId: policy.policyId,
|
|
520
|
-
policyVersion: policy.policyVersion,
|
|
521
|
-
kind: "hybrid",
|
|
522
|
-
codeEvaluatorPolicyId: fallback.codeEvaluatorPolicyId
|
|
523
|
-
};
|
|
524
|
-
const codeOutcome = await evaluateCodePolicy(codePolicy, ctx);
|
|
525
|
-
return normalizeOutcome(policy, codeOutcome, {
|
|
526
|
-
...codeOutcome.dispatchEvidence ?? {
|
|
527
|
-
policyKind: "hybrid",
|
|
528
|
-
policyId: policy.policyId,
|
|
529
|
-
policyVersion: policy.policyVersion,
|
|
530
|
-
dispatchPath: ["code"]
|
|
531
|
-
},
|
|
532
|
-
policyKind: "hybrid",
|
|
533
|
-
policyId: policy.policyId,
|
|
534
|
-
policyVersion: policy.policyVersion,
|
|
535
|
-
dispatchPath: ["data", "fallback", "code"],
|
|
536
|
-
data: dataOutcome.dispatchEvidence?.data,
|
|
537
|
-
fallback: {
|
|
538
|
-
used: true,
|
|
539
|
-
trigger: fallbackDecision.trigger,
|
|
540
|
-
reason: fallback.reason ?? "Data policy result triggered code fallback",
|
|
541
|
-
fromResult: dataOutcome.result,
|
|
542
|
-
definitionVersion: policy.policyVersion,
|
|
543
|
-
codeEvaluatorPolicyId: fallback.codeEvaluatorPolicyId
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
function resolveFallbackDecision(policy, dataOutcome, fallback) {
|
|
548
|
-
const allowedTriggers = fallback.triggers ?? [
|
|
549
|
-
"data_result",
|
|
550
|
-
"missing_data_definition",
|
|
551
|
-
"invalid_data_definition"
|
|
552
|
-
];
|
|
553
|
-
const definitionStatus = dataOutcome.dispatchEvidence?.data?.definitionStatus;
|
|
554
|
-
if (definitionStatus === "missing") {
|
|
555
|
-
return {
|
|
556
|
-
shouldFallback: allowedTriggers.includes("missing_data_definition"),
|
|
557
|
-
trigger: "missing_data_definition"
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
if (definitionStatus === "invalid") {
|
|
561
|
-
return {
|
|
562
|
-
shouldFallback: allowedTriggers.includes("invalid_data_definition"),
|
|
563
|
-
trigger: "invalid_data_definition"
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
const fallbackResults = fallback.onResults ?? ["warn", "block"];
|
|
567
|
-
return {
|
|
568
|
-
shouldFallback: policy.kind === "hybrid" && allowedTriggers.includes("data_result") && fallbackResults.includes(dataOutcome.result),
|
|
569
|
-
trigger: "data_result"
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function evaluateDataPolicy(policy, ctx) {
|
|
573
|
-
const definition = policy.dataDefinition;
|
|
574
|
-
if (!definition) {
|
|
575
|
-
return {
|
|
576
|
-
policyId: policy.policyId,
|
|
577
|
-
policyVersion: policy.policyVersion,
|
|
578
|
-
policyKind: policy.kind,
|
|
579
|
-
result: "block",
|
|
580
|
-
reason: "Data policy definition is missing",
|
|
581
|
-
dispatchEvidence: {
|
|
582
|
-
policyKind: policy.kind,
|
|
583
|
-
policyId: policy.policyId,
|
|
584
|
-
policyVersion: policy.policyVersion,
|
|
585
|
-
dispatchPath: ["data"],
|
|
586
|
-
data: {
|
|
587
|
-
definitionVersion: policy.policyVersion,
|
|
588
|
-
definitionStatus: "missing",
|
|
589
|
-
conditionResults: []
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
const validation = validateDataPolicyDefinition(definition);
|
|
595
|
-
if (!validation.valid) {
|
|
596
|
-
return {
|
|
597
|
-
policyId: policy.policyId,
|
|
598
|
-
policyVersion: policy.policyVersion,
|
|
599
|
-
policyKind: policy.kind,
|
|
600
|
-
result: "block",
|
|
601
|
-
reason: `Invalid data policy definition: ${validation.errors.join("; ")}`,
|
|
602
|
-
metadata: {
|
|
603
|
-
validationErrors: validation.errors
|
|
604
|
-
},
|
|
605
|
-
dispatchEvidence: {
|
|
606
|
-
policyKind: policy.kind,
|
|
607
|
-
policyId: policy.policyId,
|
|
608
|
-
policyVersion: policy.policyVersion,
|
|
609
|
-
dispatchPath: ["data"],
|
|
610
|
-
data: {
|
|
611
|
-
definitionVersion: policy.policyVersion,
|
|
612
|
-
definitionStatus: "invalid",
|
|
613
|
-
conditionResults: [],
|
|
614
|
-
validationErrors: validation.errors
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
const evaluations = definition.conditions.map((condition) => evaluateDataPolicyCondition(condition, ctx));
|
|
620
|
-
const conditionResults = evaluations.flatMap((evaluation) => evaluation.evidence);
|
|
621
|
-
const topLevelResults = evaluations.map((evaluation) => evaluation.result);
|
|
622
|
-
const aggregate = topLevelResults.find((result2) => result2.result === "block") ?? topLevelResults.find((result2) => result2.result === "warn");
|
|
623
|
-
const result = aggregate?.result ?? definition.defaultResult ?? "pass";
|
|
624
|
-
return {
|
|
625
|
-
policyId: policy.policyId,
|
|
626
|
-
policyVersion: policy.policyVersion,
|
|
627
|
-
policyKind: policy.kind,
|
|
628
|
-
result,
|
|
629
|
-
reason: aggregate?.reason ?? definition.reason,
|
|
630
|
-
metadata: {
|
|
631
|
-
conditionCount: definition.conditions.length,
|
|
632
|
-
failedConditionId: aggregate?.conditionId
|
|
633
|
-
},
|
|
634
|
-
dispatchEvidence: {
|
|
635
|
-
policyKind: policy.kind,
|
|
636
|
-
policyId: policy.policyId,
|
|
637
|
-
policyVersion: policy.policyVersion,
|
|
638
|
-
dispatchPath: ["data"],
|
|
639
|
-
data: {
|
|
640
|
-
definitionVersion: policy.policyVersion,
|
|
641
|
-
definitionStatus: "evaluated",
|
|
642
|
-
conditionResults
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
function validateDataPolicyDefinition(definition) {
|
|
648
|
-
const errors = [];
|
|
649
|
-
if (!Array.isArray(definition.conditions) || definition.conditions.length === 0) {
|
|
650
|
-
errors.push("conditions must contain at least one condition");
|
|
651
|
-
} else if (definition.conditions.length > DATA_POLICY_MAX_CONDITIONS_PER_NODE) {
|
|
652
|
-
errors.push(
|
|
653
|
-
`conditions must contain at most ${DATA_POLICY_MAX_CONDITIONS_PER_NODE} top-level conditions`
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
if (definition.defaultResult && !POLICY_RESULTS.has(definition.defaultResult)) {
|
|
657
|
-
errors.push("defaultResult must be pass, warn, or block");
|
|
658
|
-
}
|
|
659
|
-
let totalConditions = 0;
|
|
660
|
-
for (const condition of definition.conditions ?? []) {
|
|
661
|
-
validateDataPolicyCondition(condition, errors, 1, () => {
|
|
662
|
-
totalConditions += 1;
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
if (totalConditions > DATA_POLICY_MAX_TOTAL_CONDITIONS) {
|
|
666
|
-
errors.push(`policy must contain at most ${DATA_POLICY_MAX_TOTAL_CONDITIONS} total conditions`);
|
|
667
|
-
}
|
|
668
|
-
return { valid: errors.length === 0, errors };
|
|
669
|
-
}
|
|
670
|
-
function validateDataPolicyCondition(condition, errors, depth, count) {
|
|
671
|
-
count();
|
|
672
|
-
if (!condition || typeof condition !== "object") {
|
|
673
|
-
errors.push("condition must be an object");
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (depth > DATA_POLICY_MAX_DEPTH) {
|
|
677
|
-
errors.push(`condition depth must not exceed ${DATA_POLICY_MAX_DEPTH}`);
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
if (!isNonEmptyBoundedString(condition.conditionId)) {
|
|
681
|
-
errors.push("conditionId must be a non-empty bounded string");
|
|
682
|
-
}
|
|
683
|
-
validateOptionalPolicyResult("onPass" in condition ? condition.onPass : void 0, "onPass", errors);
|
|
684
|
-
validateOptionalPolicyResult("onFail" in condition ? condition.onFail : void 0, "onFail", errors);
|
|
685
|
-
if (condition.reason !== void 0 && !isNonEmptyBoundedString(condition.reason)) {
|
|
686
|
-
errors.push(`condition ${condition.conditionId} reason must be a bounded string`);
|
|
687
|
-
}
|
|
688
|
-
if (condition.type === "always") {
|
|
689
|
-
if (!POLICY_RESULTS.has(condition.result)) {
|
|
690
|
-
errors.push(`condition ${condition.conditionId} result must be pass, warn, or block`);
|
|
691
|
-
}
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
if (condition.type === "parameter") {
|
|
695
|
-
validatePath(condition.path, `condition ${condition.conditionId} path`, errors);
|
|
696
|
-
validateComparison(condition.operator, condition.value, condition.conditionId, errors);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
if (condition.type === "comparison") {
|
|
700
|
-
validateSourceReference(condition.left, condition.conditionId, errors);
|
|
701
|
-
validateComparison(condition.operator, condition.value, condition.conditionId, errors);
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
if (condition.type === "all" || condition.type === "any") {
|
|
705
|
-
if (!Array.isArray(condition.conditions) || condition.conditions.length === 0) {
|
|
706
|
-
errors.push(`condition ${condition.conditionId} must contain child conditions`);
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
if (condition.conditions.length > DATA_POLICY_MAX_CONDITIONS_PER_NODE) {
|
|
710
|
-
errors.push(
|
|
711
|
-
`condition ${condition.conditionId} must contain at most ${DATA_POLICY_MAX_CONDITIONS_PER_NODE} child conditions`
|
|
712
|
-
);
|
|
713
|
-
}
|
|
714
|
-
for (const child of condition.conditions) {
|
|
715
|
-
validateDataPolicyCondition(child, errors, depth + 1, count);
|
|
716
|
-
}
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
if (condition.type === "not") {
|
|
720
|
-
validateDataPolicyCondition(condition.condition, errors, depth + 1, count);
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
errors.push(`condition ${condition.conditionId} has unsupported type`);
|
|
724
|
-
}
|
|
725
|
-
function validateOptionalPolicyResult(result, fieldName, errors) {
|
|
726
|
-
if (result !== void 0 && !POLICY_RESULTS.has(result)) {
|
|
727
|
-
errors.push(`${fieldName} must be pass, warn, or block`);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function validateSourceReference(reference, conditionId, errors) {
|
|
731
|
-
if (!reference || typeof reference !== "object") {
|
|
732
|
-
errors.push(`condition ${conditionId} left must be a source reference`);
|
|
733
|
-
return;
|
|
734
|
-
}
|
|
735
|
-
if (reference.source !== "parameters" && reference.source !== "context") {
|
|
736
|
-
errors.push(`condition ${conditionId} source must be parameters or context`);
|
|
737
|
-
}
|
|
738
|
-
validatePath(reference.path, `condition ${conditionId} left.path`, errors);
|
|
739
|
-
if (reference.source === "context" && !DATA_POLICY_CONTEXT_PATHS.has(reference.path)) {
|
|
740
|
-
errors.push(`condition ${conditionId} uses an unsupported context path`);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
function validateComparison(operator, value, conditionId, errors) {
|
|
744
|
-
if (!DATA_POLICY_COMPARISON_OPERATORS.has(operator)) {
|
|
745
|
-
errors.push(`condition ${conditionId} has unsupported operator`);
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
if (operator === "exists") {
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
if (!isDataPolicyLiteral(value)) {
|
|
752
|
-
errors.push(`condition ${conditionId} comparison value must be a bounded literal`);
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
if (["gt", "gte", "lt", "lte"].includes(operator) && typeof value !== "number") {
|
|
756
|
-
errors.push(`condition ${conditionId} numeric comparison requires a number value`);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
function validatePath(path, fieldName, errors) {
|
|
760
|
-
if (!isNonEmptyBoundedString(path)) {
|
|
761
|
-
errors.push(`${fieldName} must be a non-empty bounded string`);
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
764
|
-
const segments = path.split(".");
|
|
765
|
-
if (segments.length > DATA_POLICY_MAX_PATH_SEGMENTS) {
|
|
766
|
-
errors.push(`${fieldName} must contain at most ${DATA_POLICY_MAX_PATH_SEGMENTS} segments`);
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
for (const segment of segments) {
|
|
770
|
-
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(segment)) {
|
|
771
|
-
errors.push(`${fieldName} contains an invalid path segment`);
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
if (["__proto__", "constructor", "prototype"].includes(segment)) {
|
|
775
|
-
errors.push(`${fieldName} contains a forbidden path segment`);
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
function isDataPolicyLiteral(value) {
|
|
781
|
-
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
782
|
-
return true;
|
|
783
|
-
}
|
|
784
|
-
return typeof value === "string" && value.length <= DATA_POLICY_MAX_STRING_LENGTH;
|
|
785
|
-
}
|
|
786
|
-
function isNonEmptyBoundedString(value) {
|
|
787
|
-
return typeof value === "string" && value.length > 0 && value.length <= DATA_POLICY_MAX_STRING_LENGTH;
|
|
788
|
-
}
|
|
789
|
-
function evaluateDataPolicyCondition(condition, ctx) {
|
|
790
|
-
if (condition.type === "always") {
|
|
791
|
-
const result2 = {
|
|
792
|
-
conditionId: condition.conditionId,
|
|
793
|
-
passed: condition.result === "pass",
|
|
794
|
-
result: condition.result,
|
|
795
|
-
reason: condition.reason
|
|
796
|
-
};
|
|
797
|
-
return { result: result2, evidence: [result2] };
|
|
798
|
-
}
|
|
799
|
-
if (condition.type === "all" || condition.type === "any") {
|
|
800
|
-
const childEvaluations = condition.conditions.map((child) => evaluateDataPolicyCondition(child, ctx));
|
|
801
|
-
const passed2 = condition.type === "all" ? childEvaluations.every((evaluation) => evaluation.result.passed) : childEvaluations.some((evaluation) => evaluation.result.passed);
|
|
802
|
-
const result2 = {
|
|
803
|
-
conditionId: condition.conditionId,
|
|
804
|
-
passed: passed2,
|
|
805
|
-
result: passed2 ? condition.onPass ?? "pass" : condition.onFail ?? "block",
|
|
806
|
-
reason: passed2 ? void 0 : condition.reason
|
|
807
|
-
};
|
|
808
|
-
return {
|
|
809
|
-
result: result2,
|
|
810
|
-
evidence: [result2, ...childEvaluations.flatMap((evaluation) => evaluation.evidence)]
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
if (condition.type === "not") {
|
|
814
|
-
const childEvaluation = evaluateDataPolicyCondition(condition.condition, ctx);
|
|
815
|
-
const passed2 = !childEvaluation.result.passed;
|
|
816
|
-
const result2 = {
|
|
817
|
-
conditionId: condition.conditionId,
|
|
818
|
-
passed: passed2,
|
|
819
|
-
result: passed2 ? condition.onPass ?? "pass" : condition.onFail ?? "block",
|
|
820
|
-
reason: passed2 ? void 0 : condition.reason
|
|
821
|
-
};
|
|
822
|
-
return { result: result2, evidence: [result2, ...childEvaluation.evidence] };
|
|
823
|
-
}
|
|
824
|
-
if (condition.type !== "parameter" && condition.type !== "comparison") {
|
|
825
|
-
const result2 = {
|
|
826
|
-
conditionId: condition.conditionId,
|
|
827
|
-
passed: false,
|
|
828
|
-
result: "block",
|
|
829
|
-
reason: "Unsupported data policy condition type"
|
|
830
|
-
};
|
|
831
|
-
return { result: result2, evidence: [result2] };
|
|
832
|
-
}
|
|
833
|
-
const value = condition.type === "parameter" ? readPath(ctx.parameters, condition.path) : readDataPolicySource(ctx, condition.left);
|
|
834
|
-
const operator = condition.operator;
|
|
835
|
-
const expected = condition.value;
|
|
836
|
-
const passed = evaluateOperator(value, operator, expected);
|
|
837
|
-
const result = {
|
|
838
|
-
conditionId: condition.conditionId,
|
|
839
|
-
passed,
|
|
840
|
-
result: passed ? condition.onPass ?? "pass" : condition.onFail ?? "block",
|
|
841
|
-
reason: passed ? void 0 : condition.reason
|
|
842
|
-
};
|
|
843
|
-
return { result, evidence: [result] };
|
|
844
|
-
}
|
|
845
|
-
function evaluateOperator(actual, operator, expected) {
|
|
846
|
-
switch (operator) {
|
|
847
|
-
case "exists":
|
|
848
|
-
return actual !== void 0 && actual !== null && actual !== "";
|
|
849
|
-
case "equals":
|
|
850
|
-
return actual === expected;
|
|
851
|
-
case "notEquals":
|
|
852
|
-
return actual !== expected;
|
|
853
|
-
case "gt":
|
|
854
|
-
return typeof actual === "number" && typeof expected === "number" && actual > expected;
|
|
855
|
-
case "gte":
|
|
856
|
-
return typeof actual === "number" && typeof expected === "number" && actual >= expected;
|
|
857
|
-
case "lt":
|
|
858
|
-
return typeof actual === "number" && typeof expected === "number" && actual < expected;
|
|
859
|
-
case "lte":
|
|
860
|
-
return typeof actual === "number" && typeof expected === "number" && actual <= expected;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
function readDataPolicySource(ctx, reference) {
|
|
864
|
-
if (reference.source === "parameters") {
|
|
865
|
-
return readPath(ctx.parameters, reference.path);
|
|
866
|
-
}
|
|
867
|
-
return readPath(
|
|
868
|
-
{
|
|
869
|
-
tenantId: ctx.tenantId,
|
|
870
|
-
spaceId: ctx.spaceId,
|
|
871
|
-
actionInvocationId: ctx.actionInvocationId,
|
|
872
|
-
actionId: ctx.actionId,
|
|
873
|
-
mode: ctx.mode
|
|
874
|
-
},
|
|
875
|
-
reference.path
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
function readPath(source, path) {
|
|
879
|
-
if (!source || typeof source !== "object") {
|
|
880
|
-
return void 0;
|
|
881
|
-
}
|
|
882
|
-
return path.split(".").reduce((current, segment) => {
|
|
883
|
-
if (!current || typeof current !== "object") {
|
|
884
|
-
return void 0;
|
|
885
|
-
}
|
|
886
|
-
return current[segment];
|
|
887
|
-
}, source);
|
|
888
|
-
}
|
|
889
|
-
function normalizeOutcome(policy, outcome, dispatchEvidence) {
|
|
890
|
-
return {
|
|
891
|
-
...outcome,
|
|
892
|
-
policyId: policy.policyId,
|
|
893
|
-
policyVersion: policy.policyVersion,
|
|
894
|
-
policyKind: policy.kind,
|
|
895
|
-
metadata: {
|
|
896
|
-
...outcome.metadata ?? {},
|
|
897
|
-
codePolicyId: dispatchEvidence.code?.policyId ?? outcome.policyId,
|
|
898
|
-
codePolicyVersion: dispatchEvidence.code?.version ?? outcome.policyVersion
|
|
899
|
-
},
|
|
900
|
-
dispatchEvidence
|
|
901
|
-
};
|
|
902
|
-
}
|
|
903
|
-
function parsePolicyVersion(policyId) {
|
|
904
|
-
const segments = policyId.split(".v");
|
|
905
|
-
return Number(segments[segments.length - 1] ?? 1);
|
|
906
|
-
}
|
|
907
|
-
function aggregatePolicyOutcomes(outcomes) {
|
|
908
|
-
return outcomes.find((outcome) => outcome.result === "block") ?? outcomes.find((outcome) => outcome.result === "warn");
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
// state-machines/engine.ts
|
|
912
|
-
var registry4 = /* @__PURE__ */ new Map();
|
|
913
|
-
function registerStateMachine(def) {
|
|
914
|
-
if (registry4.has(def.entityType)) {
|
|
915
|
-
throw new Error(`State machine for entity type "${def.entityType}" is already registered.`);
|
|
916
|
-
}
|
|
917
|
-
registry4.set(def.entityType, def);
|
|
918
|
-
}
|
|
919
|
-
function registerStateMachineIfMissing(def) {
|
|
920
|
-
if (!registry4.has(def.entityType)) {
|
|
921
|
-
registerStateMachine(def);
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
function resolveStateMachine(entityType) {
|
|
925
|
-
return registry4.get(entityType);
|
|
926
|
-
}
|
|
927
|
-
function validateTransition(entityType, from, to, actionId) {
|
|
928
|
-
const sm = registry4.get(entityType);
|
|
929
|
-
if (!sm) {
|
|
930
|
-
return {
|
|
931
|
-
valid: false,
|
|
932
|
-
error: `No state machine registered for entity type "${entityType}".`
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
const fromDef = sm.states[from];
|
|
936
|
-
if (!fromDef) {
|
|
937
|
-
return {
|
|
938
|
-
valid: false,
|
|
939
|
-
error: `State "${from}" does not exist in state machine for entity type "${entityType}".`
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
const toDef = sm.states[to];
|
|
943
|
-
if (!toDef) {
|
|
944
|
-
return {
|
|
945
|
-
valid: false,
|
|
946
|
-
error: `State "${to}" does not exist in state machine for entity type "${entityType}".`
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
const transitionExists = sm.transitions.some(
|
|
950
|
-
(t) => t.from === from && t.to === to && t.causedByAction === actionId
|
|
951
|
-
);
|
|
952
|
-
if (!transitionExists) {
|
|
953
|
-
return {
|
|
954
|
-
valid: false,
|
|
955
|
-
error: `Transition from "${from}" to "${to}" via action "${actionId}" is not defined for entity type "${entityType}".`
|
|
956
|
-
};
|
|
957
|
-
}
|
|
958
|
-
return { valid: true };
|
|
959
|
-
}
|
|
960
|
-
function getAllowedTransitions(entityType, from) {
|
|
961
|
-
const sm = registry4.get(entityType);
|
|
962
|
-
if (!sm) {
|
|
963
|
-
return [];
|
|
964
|
-
}
|
|
965
|
-
return sm.transitions.filter((t) => t.from === from).map((t) => ({ to: t.to, causedByAction: t.causedByAction }));
|
|
966
|
-
}
|
|
967
|
-
function getEntityStateClass(entityType, state) {
|
|
968
|
-
const sm = registry4.get(entityType);
|
|
969
|
-
if (!sm) {
|
|
970
|
-
return void 0;
|
|
971
|
-
}
|
|
972
|
-
return sm.states[state]?.stateClass;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// modules/index.ts
|
|
976
|
-
var registeredModuleNamespaces = /* @__PURE__ */ new Set();
|
|
977
|
-
function getModuleDependencyNamespace(dependency) {
|
|
978
|
-
return typeof dependency === "string" ? dependency : dependency.namespace;
|
|
979
|
-
}
|
|
980
|
-
function getObjectTypeName(objectType) {
|
|
981
|
-
return typeof objectType === "string" ? objectType : objectType.type;
|
|
982
|
-
}
|
|
983
|
-
function getEventTypeName(eventType) {
|
|
984
|
-
return typeof eventType === "string" ? eventType : eventType.eventType;
|
|
985
|
-
}
|
|
986
|
-
function validateModuleNamespace(namespace) {
|
|
987
|
-
if (!namespace) {
|
|
988
|
-
throw new Error("Module namespace cannot be empty");
|
|
989
|
-
}
|
|
990
|
-
if (!/^[a-z][a-z0-9-]*$/.test(namespace)) {
|
|
991
|
-
throw new Error(
|
|
992
|
-
`Invalid module namespace: "${namespace}". Must be lowercase letters, numbers, and hyphens only.`
|
|
993
|
-
);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
function createModuleRegistry(modules = []) {
|
|
997
|
-
const registered = /* @__PURE__ */ new Map();
|
|
998
|
-
for (const module of modules) {
|
|
999
|
-
validateModuleNamespace(module.namespace);
|
|
1000
|
-
if (registered.has(module.namespace)) {
|
|
1001
|
-
throw new Error(`Duplicate FabricModule namespace "${module.namespace}".`);
|
|
1002
|
-
}
|
|
1003
|
-
registered.set(module.namespace, module);
|
|
1004
|
-
}
|
|
1005
|
-
return { modules: registered };
|
|
1006
|
-
}
|
|
1007
|
-
function validateIdentifierList(values, label, namespace) {
|
|
1008
|
-
for (const value of values ?? []) {
|
|
1009
|
-
if (!value) {
|
|
1010
|
-
throw new Error(`Module "${namespace}" has an empty ${label} entry.`);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
function validateManifest(module, context = {}) {
|
|
1015
|
-
validateModuleNamespace(module.namespace);
|
|
1016
|
-
const dependencies = (module.dependencies ?? []).map(getModuleDependencyNamespace);
|
|
1017
|
-
const objectTypes = (module.objectTypes ?? []).map(getObjectTypeName);
|
|
1018
|
-
const eventTypes = (module.eventTypes ?? []).map(getEventTypeName);
|
|
1019
|
-
for (const dependency of dependencies) {
|
|
1020
|
-
validateModuleNamespace(dependency);
|
|
1021
|
-
if (dependency === module.namespace) {
|
|
1022
|
-
throw new Error(`Module "${module.namespace}" cannot depend on itself.`);
|
|
1023
|
-
}
|
|
1024
|
-
if (context.availableNamespaces && !context.availableNamespaces.has(dependency)) {
|
|
1025
|
-
throw new Error(`Module "${module.namespace}" depends on missing module "${dependency}".`);
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
validateIdentifierList(objectTypes, "objectTypes", module.namespace);
|
|
1029
|
-
validateIdentifierList(module.subjectTypes, "subjectTypes", module.namespace);
|
|
1030
|
-
validateIdentifierList(eventTypes, "eventTypes", module.namespace);
|
|
1031
|
-
for (const objectType of objectTypes) {
|
|
1032
|
-
if (!/^[A-Z][a-zA-Z0-9]*$/.test(objectType)) {
|
|
1033
|
-
throw new Error(
|
|
1034
|
-
`Invalid object type: "${objectType}". Object types must be PascalCase alphanumeric.`
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
for (const action of module.actions ?? []) {
|
|
1039
|
-
if (!/^[a-z][a-z0-9-]*\.[a-z][a-z0-9_]*$/.test(action.actionId)) {
|
|
1040
|
-
throw new Error(`Invalid action ID: ${action.actionId}`);
|
|
1041
|
-
}
|
|
1042
|
-
if (action.namespace !== module.namespace) {
|
|
1043
|
-
throw new Error(
|
|
1044
|
-
`Action "${action.actionId}" declares namespace "${action.namespace}" but is registered by module "${module.namespace}".`
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
if (!action.actionId.startsWith(`${module.namespace}.`)) {
|
|
1048
|
-
throw new Error(
|
|
1049
|
-
`Action "${action.actionId}" must start with module namespace "${module.namespace}."`
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
if (action.mutatesDomain && action.emitsEvents.length === 0) {
|
|
1053
|
-
throw new Error(
|
|
1054
|
-
`Action "${action.actionId}" mutates domain but emits no events. Every mutating action must emit at least one AssetEvent.`
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
for (const descriptor of module.displayRenderers ?? []) {
|
|
1059
|
-
const isPlannedEventType = context.plannedEventTypes?.has(descriptor.eventType) ?? false;
|
|
1060
|
-
const isModuleEventType = eventTypes.includes(descriptor.eventType);
|
|
1061
|
-
if (!isValidEventType(descriptor.eventType) && !isModuleEventType && !isPlannedEventType) {
|
|
1062
|
-
throw new Error(
|
|
1063
|
-
`Display renderer registered for unknown event type "${descriptor.eventType}". Register the event type before adding a display renderer.`
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
function sortModulesByDependency(modules, registeredNamespaces = registeredModuleNamespaces) {
|
|
1069
|
-
const modulesByNamespace = /* @__PURE__ */ new Map();
|
|
1070
|
-
for (const module of modules) {
|
|
1071
|
-
validateModuleNamespace(module.namespace);
|
|
1072
|
-
if (modulesByNamespace.has(module.namespace)) {
|
|
1073
|
-
throw new Error(`Duplicate Fabric module namespace "${module.namespace}".`);
|
|
1074
|
-
}
|
|
1075
|
-
modulesByNamespace.set(module.namespace, module);
|
|
1076
|
-
}
|
|
1077
|
-
const sorted = [];
|
|
1078
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
1079
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1080
|
-
function visit(module, path) {
|
|
1081
|
-
if (visited.has(module.namespace)) {
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
if (visiting.has(module.namespace)) {
|
|
1085
|
-
throw new Error(
|
|
1086
|
-
`Cyclic Fabric module dependency detected: ${[...path, module.namespace].join(" -> ")}`
|
|
1087
|
-
);
|
|
1088
|
-
}
|
|
1089
|
-
visiting.add(module.namespace);
|
|
1090
|
-
for (const dependency of (module.dependencies ?? []).map(getModuleDependencyNamespace)) {
|
|
1091
|
-
const dependencyModule = modulesByNamespace.get(dependency);
|
|
1092
|
-
if (!dependencyModule) {
|
|
1093
|
-
if (registeredNamespaces.has(dependency)) {
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1096
|
-
throw new Error(`Module "${module.namespace}" depends on missing module "${dependency}".`);
|
|
1097
|
-
}
|
|
1098
|
-
visit(dependencyModule, [...path, module.namespace]);
|
|
1099
|
-
}
|
|
1100
|
-
visiting.delete(module.namespace);
|
|
1101
|
-
visited.add(module.namespace);
|
|
1102
|
-
sorted.push(module);
|
|
1103
|
-
}
|
|
1104
|
-
for (const module of modules) {
|
|
1105
|
-
visit(module, []);
|
|
1106
|
-
}
|
|
1107
|
-
return sorted;
|
|
1108
|
-
}
|
|
1109
|
-
function registerFabricModule(module) {
|
|
1110
|
-
validateManifest(module, { availableNamespaces: registeredModuleNamespaces });
|
|
1111
|
-
for (const objectType of module.objectTypes ?? []) {
|
|
1112
|
-
registerObjectType(getObjectTypeName(objectType));
|
|
1113
|
-
}
|
|
1114
|
-
for (const subjectType of module.subjectTypes ?? []) {
|
|
1115
|
-
registerSubjectType(subjectType);
|
|
1116
|
-
}
|
|
1117
|
-
for (const eventType of module.eventTypes ?? []) {
|
|
1118
|
-
registerEventType(getEventTypeName(eventType));
|
|
1119
|
-
}
|
|
1120
|
-
for (const policy of module.policies ?? []) {
|
|
1121
|
-
registerPolicyIfMissing(policy);
|
|
1122
|
-
}
|
|
1123
|
-
for (const machine of module.stateMachines ?? []) {
|
|
1124
|
-
registerStateMachineIfMissing(machine);
|
|
1125
|
-
}
|
|
1126
|
-
for (const action of module.actions ?? []) {
|
|
1127
|
-
registerActionIfMissing(action);
|
|
1128
|
-
}
|
|
1129
|
-
for (const descriptor of module.displayRenderers ?? []) {
|
|
1130
|
-
registerDisplayRenderer(descriptor.eventType, descriptor.renderer);
|
|
1131
|
-
}
|
|
1132
|
-
registeredModuleNamespaces.add(module.namespace);
|
|
1133
|
-
}
|
|
1134
|
-
function registerFabricModules(modules) {
|
|
1135
|
-
const availableNamespaces = /* @__PURE__ */ new Set([
|
|
1136
|
-
...registeredModuleNamespaces,
|
|
1137
|
-
...modules.map((module) => module.namespace)
|
|
1138
|
-
]);
|
|
1139
|
-
const plannedEventTypes = new Set(
|
|
1140
|
-
modules.flatMap((module) => (module.eventTypes ?? []).map(getEventTypeName))
|
|
1141
|
-
);
|
|
1142
|
-
for (const module of modules) {
|
|
1143
|
-
validateManifest(module, { availableNamespaces, plannedEventTypes });
|
|
1144
|
-
}
|
|
1145
|
-
const sortedModules = sortModulesByDependency(modules);
|
|
1146
|
-
for (const module of sortedModules) {
|
|
1147
|
-
registerFabricModule(module);
|
|
1148
|
-
}
|
|
1149
|
-
return sortedModules;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
// projections/index.ts
|
|
1153
|
-
async function replayEvents({
|
|
1154
|
-
events,
|
|
1155
|
-
scope,
|
|
1156
|
-
initialState,
|
|
1157
|
-
snapshot,
|
|
1158
|
-
applyEvent
|
|
1159
|
-
}) {
|
|
1160
|
-
const warnings = [];
|
|
1161
|
-
const scopedEvents = filterEventsByScope(await collectEvents(events), scope, warnings);
|
|
1162
|
-
const orderedEvents = sortEvents(scopedEvents, isSubjectScoped(scope));
|
|
1163
|
-
const uniqueEvents = removeDuplicateEvents(orderedEvents, warnings);
|
|
1164
|
-
const resumableEvents = applyCursor(uniqueEvents, snapshot, isSubjectScoped(scope), warnings);
|
|
1165
|
-
let state = snapshot?.snapshotData ?? initialState;
|
|
1166
|
-
const appliedEvents = [];
|
|
1167
|
-
let eventCursor = snapshot?.eventCursor ?? null;
|
|
1168
|
-
let eventSequence = snapshot?.eventSequence ?? 0;
|
|
1169
|
-
for (const event of resumableEvents) {
|
|
1170
|
-
state = await applyEvent(state, event);
|
|
1171
|
-
appliedEvents.push(event);
|
|
1172
|
-
eventCursor = event.id;
|
|
1173
|
-
eventSequence = event.sequence;
|
|
1174
|
-
}
|
|
1175
|
-
return {
|
|
1176
|
-
state,
|
|
1177
|
-
appliedEvents,
|
|
1178
|
-
warnings,
|
|
1179
|
-
eventCursor,
|
|
1180
|
-
eventSequence
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
async function collectEvents(events) {
|
|
1184
|
-
const collected = [];
|
|
1185
|
-
if (Symbol.asyncIterator in events) {
|
|
1186
|
-
for await (const event of events) {
|
|
1187
|
-
collected.push(event);
|
|
1188
|
-
}
|
|
1189
|
-
return collected;
|
|
1190
|
-
}
|
|
1191
|
-
for (const event of events) {
|
|
1192
|
-
collected.push(event);
|
|
1193
|
-
}
|
|
1194
|
-
return collected;
|
|
1195
|
-
}
|
|
1196
|
-
function filterEventsByScope(events, scope, warnings) {
|
|
1197
|
-
if (scope.subjectType && !scope.subjectId || !scope.subjectType && scope.subjectId) {
|
|
1198
|
-
warnings.push({
|
|
1199
|
-
code: "subject_scope_incomplete",
|
|
1200
|
-
message: "Both subjectType and subjectId are required for subject-scoped replay.",
|
|
1201
|
-
subjectType: scope.subjectType,
|
|
1202
|
-
subjectId: scope.subjectId
|
|
1203
|
-
});
|
|
1204
|
-
}
|
|
1205
|
-
return events.filter((event) => {
|
|
1206
|
-
if (event.tenantId !== scope.tenantId || event.spaceId !== scope.spaceId) {
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
if (scope.subjectType && event.subjectType !== scope.subjectType) {
|
|
1210
|
-
return false;
|
|
1211
|
-
}
|
|
1212
|
-
if (scope.subjectId && event.subjectId !== scope.subjectId) {
|
|
1213
|
-
return false;
|
|
1214
|
-
}
|
|
1215
|
-
return true;
|
|
1216
|
-
});
|
|
1217
|
-
}
|
|
1218
|
-
function removeDuplicateEvents(events, warnings) {
|
|
1219
|
-
const seenEventIds = /* @__PURE__ */ new Set();
|
|
1220
|
-
const uniqueEvents = [];
|
|
1221
|
-
for (const event of events) {
|
|
1222
|
-
if (seenEventIds.has(event.id)) {
|
|
1223
|
-
warnings.push({
|
|
1224
|
-
code: "duplicate_event",
|
|
1225
|
-
message: `Duplicate event ${event.id} ignored during replay.`,
|
|
1226
|
-
eventId: event.id,
|
|
1227
|
-
subjectType: event.subjectType,
|
|
1228
|
-
subjectId: event.subjectId
|
|
1229
|
-
});
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
seenEventIds.add(event.id);
|
|
1233
|
-
uniqueEvents.push(event);
|
|
1234
|
-
}
|
|
1235
|
-
recordMissingSequenceWarnings(uniqueEvents, warnings);
|
|
1236
|
-
return uniqueEvents;
|
|
1237
|
-
}
|
|
1238
|
-
function recordMissingSequenceWarnings(events, warnings) {
|
|
1239
|
-
const lastSequenceBySubject = /* @__PURE__ */ new Map();
|
|
1240
|
-
for (const event of events) {
|
|
1241
|
-
const subjectKey = `${event.subjectType}:${event.subjectId}`;
|
|
1242
|
-
const previousSequence = lastSequenceBySubject.get(subjectKey);
|
|
1243
|
-
const expectedSequence = previousSequence === void 0 ? event.sequence : previousSequence + 1;
|
|
1244
|
-
if (previousSequence !== void 0 && event.sequence > expectedSequence) {
|
|
1245
|
-
warnings.push({
|
|
1246
|
-
code: "missing_sequence",
|
|
1247
|
-
message: `Missing event sequence for ${subjectKey}: expected ${expectedSequence}, received ${event.sequence}.`,
|
|
1248
|
-
eventId: event.id,
|
|
1249
|
-
subjectType: event.subjectType,
|
|
1250
|
-
subjectId: event.subjectId,
|
|
1251
|
-
expectedSequence,
|
|
1252
|
-
actualSequence: event.sequence
|
|
1253
|
-
});
|
|
1254
|
-
}
|
|
1255
|
-
lastSequenceBySubject.set(subjectKey, Math.max(previousSequence ?? event.sequence, event.sequence));
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
function applyCursor(events, snapshot, subjectScoped, warnings) {
|
|
1259
|
-
const eventCursor = snapshot?.eventCursor;
|
|
1260
|
-
if (eventCursor) {
|
|
1261
|
-
const cursorIndex = events.findIndex((event) => event.id === eventCursor);
|
|
1262
|
-
if (cursorIndex >= 0) {
|
|
1263
|
-
return events.slice(cursorIndex + 1);
|
|
1264
|
-
}
|
|
1265
|
-
warnings.push({
|
|
1266
|
-
code: "cursor_not_found",
|
|
1267
|
-
message: `Snapshot cursor ${eventCursor} was not found in the scoped event stream.`,
|
|
1268
|
-
eventId: eventCursor
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
if (subjectScoped && snapshot?.eventSequence !== void 0 && snapshot.eventSequence !== null) {
|
|
1272
|
-
return events.filter((event) => event.sequence > snapshot.eventSequence);
|
|
1273
|
-
}
|
|
1274
|
-
return events;
|
|
1275
|
-
}
|
|
1276
|
-
function sortEvents(events, subjectScoped) {
|
|
1277
|
-
return [...events].sort(
|
|
1278
|
-
(first, second) => subjectScoped ? compareSubjectEvents(first, second) : compareGlobalEvents(first, second)
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
|
-
function compareSubjectEvents(first, second) {
|
|
1282
|
-
return compareValues(
|
|
1283
|
-
[first.sequence, toTimestamp(first.recordedAt), toTimestamp(first.occurredAt), first.id],
|
|
1284
|
-
[second.sequence, toTimestamp(second.recordedAt), toTimestamp(second.occurredAt), second.id]
|
|
1285
|
-
);
|
|
1286
|
-
}
|
|
1287
|
-
function compareGlobalEvents(first, second) {
|
|
1288
|
-
return compareValues(
|
|
1289
|
-
[
|
|
1290
|
-
toTimestamp(first.recordedAt),
|
|
1291
|
-
toTimestamp(first.occurredAt),
|
|
1292
|
-
first.actionInvocationId,
|
|
1293
|
-
first.correlationId,
|
|
1294
|
-
first.subjectType,
|
|
1295
|
-
first.subjectId,
|
|
1296
|
-
first.sequence,
|
|
1297
|
-
first.id
|
|
1298
|
-
],
|
|
1299
|
-
[
|
|
1300
|
-
toTimestamp(second.recordedAt),
|
|
1301
|
-
toTimestamp(second.occurredAt),
|
|
1302
|
-
second.actionInvocationId,
|
|
1303
|
-
second.correlationId,
|
|
1304
|
-
second.subjectType,
|
|
1305
|
-
second.subjectId,
|
|
1306
|
-
second.sequence,
|
|
1307
|
-
second.id
|
|
1308
|
-
]
|
|
1309
|
-
);
|
|
1310
|
-
}
|
|
1311
|
-
function compareValues(firstValues, secondValues) {
|
|
1312
|
-
for (const [index, firstValue] of firstValues.entries()) {
|
|
1313
|
-
const secondValue = secondValues[index];
|
|
1314
|
-
if (firstValue < secondValue) {
|
|
1315
|
-
return -1;
|
|
1316
|
-
}
|
|
1317
|
-
if (firstValue > secondValue) {
|
|
1318
|
-
return 1;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
return 0;
|
|
1322
|
-
}
|
|
1323
|
-
function isSubjectScoped(scope) {
|
|
1324
|
-
return Boolean(scope.subjectType && scope.subjectId);
|
|
1325
|
-
}
|
|
1326
|
-
function toTimestamp(value) {
|
|
1327
|
-
return value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
// privacy/index.ts
|
|
1331
|
-
var PLATFORM_PRIVACY_POLICY_VERSION = "fabric-platform-privacy-v1";
|
|
1332
|
-
|
|
1333
|
-
// testing/index.ts
|
|
1334
|
-
function createFakeActionContext(options = {}) {
|
|
1335
|
-
return {
|
|
1336
|
-
actionInvocationId: options.actionInvocationId ?? "act_test",
|
|
1337
|
-
tenantId: options.tenantId ?? "tenant-test",
|
|
1338
|
-
spaceId: options.spaceId ?? "space-test",
|
|
1339
|
-
actorId: options.actorId ?? "actor-test",
|
|
1340
|
-
actorType: options.actorType ?? "system",
|
|
1341
|
-
correlationId: options.correlationId ?? "corr-test",
|
|
1342
|
-
causationId: options.causationId,
|
|
1343
|
-
db: options.db ?? {},
|
|
1344
|
-
services: options.services ?? {}
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
function createInMemoryAdapterRegistry() {
|
|
1348
|
-
return new AdapterRegistry();
|
|
1349
|
-
}
|
|
1350
|
-
function createDeterministicIdFactory(prefix = "test") {
|
|
1351
|
-
let sequence = 0;
|
|
1352
|
-
return () => `${prefix}_${String(++sequence).padStart(6, "0")}`;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
export { ACTOR_TYPES, AdapterRegistry, FABRIC_ID_PREFIXES, MissingAdapterRegistrationError, PLATFORM_PRIVACY_POLICY_VERSION, aggregatePolicyOutcomes, assertActionId, assertActorType, assertObjectType, assertPolicyId, createDeterministicIdFactory, createEventTypeRegistry, createFabricId, createFakeActionContext, createInMemoryAdapterRegistry, createModuleRegistry, denormalizeDisplaySnapshot, evaluatePolicyDefinition, evaluatePolicyDefinitions, evaluateRegisteredPolicies, executeWithAdapterRetry, getAllowedTransitions, getEntityStateClass, getEventTypeName, getModuleDependencyNamespace, getObjectTypeName, getRegisteredActionIds, getRegisteredEventTypes, getRegisteredObjectTypes, getRegisteredPolicyIds, getRegisteredSubjectTypes, getRetryDelayMs, isActorType, isFabricId, isRegisteredActionId, isValidEventType, isValidObjectType, isValidSubjectType, parseFabricId, registerAction, registerActionIfMissing, registerDisplayRenderer, registerEventType, registerFabricModule, registerFabricModules, registerObjectType, registerPolicy, registerPolicyIfMissing, registerStateMachine, registerStateMachineIfMissing, registerSubjectType, renderEventDisplay, replayEvents, resolveAction, resolveAdapterRetryPolicy, resolveDisplayRenderer, resolvePolicy, resolveRegisteredAction, resolveStateMachine, validateDataPolicyDefinition, validateModuleNamespace, validateTransition };
|
|
1
|
+
export { PLATFORM_PRIVACY_POLICY_VERSION } from './chunk-N24NLCZ6.js';
|
|
2
|
+
export { replayEvents } from './chunk-JSTGOT6P.js';
|
|
3
|
+
export { createDeterministicIdFactory, createFakeActionContext, createInMemoryAdapterRegistry } from './chunk-IFMZPLM4.js';
|
|
4
|
+
export { AdapterRegistry, MissingAdapterRegistrationError, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy } from './chunk-3ZW5USXF.js';
|
|
5
|
+
import './chunk-OZLZHISS.js';
|
|
6
|
+
export { FABRIC_ID_PREFIXES, createFabricId, isFabricId, parseFabricId } from './chunk-RALMUSOX.js';
|
|
7
|
+
export { createModuleRegistry, getEventTypeName, getModuleDependencyNamespace, getObjectTypeName, registerFabricModule, registerFabricModules, validateModuleNamespace } from './chunk-PXMKCAQI.js';
|
|
8
|
+
export { assertObjectType, getRegisteredObjectTypes, isValidObjectType, registerObjectType } from './chunk-I35B576X.js';
|
|
9
|
+
export { aggregatePolicyOutcomes, assertPolicyId, evaluatePolicyDefinition, evaluatePolicyDefinitions, evaluateRegisteredPolicies, getRegisteredPolicyIds, registerPolicy, registerPolicyIfMissing, resolvePolicy, validateDataPolicyDefinition } from './chunk-KOC4A7A4.js';
|
|
10
|
+
export { getAllowedTransitions, getEntityStateClass, registerStateMachine, registerStateMachineIfMissing, resolveStateMachine, validateTransition } from './chunk-3A6FU3KV.js';
|
|
11
|
+
export { ACTOR_TYPES, assertActionId, assertActorType, getRegisteredActionIds, isActorType, isRegisteredActionId, registerAction, registerActionIfMissing, resolveAction, resolveRegisteredAction } from './chunk-6FMW4NF6.js';
|
|
12
|
+
export { denormalizeDisplaySnapshot, registerDisplayRenderer, renderEventDisplay, resolveDisplayRenderer } from './chunk-3F6FKQZA.js';
|
|
13
|
+
export { createEventTypeRegistry, getRegisteredEventTypes, getRegisteredSubjectTypes, isValidEventType, isValidSubjectType, registerEventType, registerSubjectType } from './chunk-4YQOGV7H.js';
|
|
1356
14
|
//# sourceMappingURL=index.js.map
|
|
1357
15
|
//# sourceMappingURL=index.js.map
|