@fabricorg/platform-host 0.4.0 → 0.4.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.
- package/CHANGELOG.md +8 -0
- package/README.md +7 -1
- package/dist/index.cjs +27 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +27 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 0.4.1 — 2026-07-20
|
|
4
|
+
|
|
5
|
+
- Add runtime-local action resolution without process-wide registry mutation.
|
|
6
|
+
- Pass the injected host clock to policy evaluators.
|
|
7
|
+
- Treat empty state-machine targets as no-op transitions.
|
|
8
|
+
- Default domain events to the action schema version while preserving explicit event versions.
|
|
9
|
+
- Omit custom extracted-event carrier fields from durable invocation results.
|
|
10
|
+
|
|
3
11
|
## 0.4.0 — 2026-07-19
|
|
4
12
|
|
|
5
13
|
- Add an optional agent-only HITL evaluator between schema validation and ordinary policies.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The canonical host for the `@fabricorg/platform` mutation pipeline.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
pnpm add @fabricorg/platform@^0.7.0 @fabricorg/platform-host@^0.4.
|
|
6
|
+
pnpm add @fabricorg/platform@^0.7.0 @fabricorg/platform-host@^0.4.1
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Vertical packages register `FabricModule` definitions. Host applications provide tenant authorization,
|
|
@@ -18,6 +18,12 @@ Actor → ActionInvocation → Schema → Agent HITL → PolicyEvaluation → St
|
|
|
18
18
|
worker entry point. With no dispatcher the host executes inline, which is intended for tests and local
|
|
19
19
|
development only.
|
|
20
20
|
|
|
21
|
+
Applications that assemble action catalogs per runtime can provide `resolveAction` instead of
|
|
22
|
+
mutating the process-wide platform registry. A custom `extractEvents` implementation can pair with
|
|
23
|
+
`eventResultFields` so its event carrier is removed from the durable invocation result. Domain events
|
|
24
|
+
without an explicit `eventSchemaVersion` inherit the action version, while host lifecycle events stay
|
|
25
|
+
at version 1. The injected `now` clock is also passed to ordinary policy evaluation.
|
|
26
|
+
|
|
21
27
|
Production polling workers can use `createStoreBackedActionDispatcher()` plus
|
|
22
28
|
`runPlatformActionWorker()`. The pending invocation row is the durable queue item; workers claim
|
|
23
29
|
bounded batches with an atomic lease and `FOR UPDATE SKIP LOCKED`, and an expired `running` lease is
|
package/dist/index.cjs
CHANGED
|
@@ -12,8 +12,10 @@ function createGovernedActionHost(options) {
|
|
|
12
12
|
for (const adapter of options.adapters ?? []) adapters.register(adapter);
|
|
13
13
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
14
14
|
const extractEvents = options.extractEvents ?? DEFAULT_EXTRACT_EVENTS;
|
|
15
|
+
const actionResolver = options.resolveAction ?? platform.resolveAction;
|
|
16
|
+
const eventResultFields = options.eventResultFields ?? ["_events"];
|
|
15
17
|
async function submitAction(input) {
|
|
16
|
-
const action =
|
|
18
|
+
const action = actionResolver(input.actionId);
|
|
17
19
|
if (!action) throw new Error(`Unknown action: ${input.actionId}`);
|
|
18
20
|
const authorizationInput = toAuthorizationInput(action, input);
|
|
19
21
|
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
@@ -105,7 +107,7 @@ function createGovernedActionHost(options) {
|
|
|
105
107
|
error: `Invocation is leased by ${invocation.leaseOwner}`
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
|
-
const action =
|
|
110
|
+
const action = actionResolver(invocation.actionId);
|
|
109
111
|
if (!action) {
|
|
110
112
|
return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
|
|
111
113
|
}
|
|
@@ -193,7 +195,8 @@ function createGovernedActionHost(options) {
|
|
|
193
195
|
parameters: parsed.data,
|
|
194
196
|
db: options.store.db,
|
|
195
197
|
services: options.services,
|
|
196
|
-
mode: "execute"
|
|
198
|
+
mode: "execute",
|
|
199
|
+
now: now()
|
|
197
200
|
});
|
|
198
201
|
for (const outcome of outcomes) {
|
|
199
202
|
await options.store.appendPolicyEvaluation({
|
|
@@ -226,15 +229,17 @@ function createGovernedActionHost(options) {
|
|
|
226
229
|
entityId
|
|
227
230
|
) ?? initialState(binding.entityType) : initialState(binding.entityType);
|
|
228
231
|
const targetState = typeof binding.targetState === "function" ? binding.targetState(parsed.data) : binding.targetState;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
if (targetState !== "") {
|
|
233
|
+
const transition = platform.validateTransition(
|
|
234
|
+
binding.entityType,
|
|
235
|
+
currentState,
|
|
236
|
+
targetState,
|
|
237
|
+
action.actionId
|
|
238
|
+
);
|
|
239
|
+
const replayingAppliedTransition = action.idempotent && currentState === targetState;
|
|
240
|
+
if (!transition.valid && !replayingAppliedTransition) {
|
|
241
|
+
return fail(invocation, "failed", transition.error ?? "Invalid state transition");
|
|
242
|
+
}
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
245
|
let data;
|
|
@@ -263,7 +268,7 @@ function createGovernedActionHost(options) {
|
|
|
263
268
|
domainEvents = extractEvents(data);
|
|
264
269
|
if (action.eventPhase !== "after_adapters") {
|
|
265
270
|
for (const [index, event] of domainEvents.entries()) {
|
|
266
|
-
await appendEvent(invocation, event, `domain:${index}
|
|
271
|
+
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
267
272
|
}
|
|
268
273
|
}
|
|
269
274
|
} catch (error) {
|
|
@@ -382,10 +387,10 @@ function createGovernedActionHost(options) {
|
|
|
382
387
|
}
|
|
383
388
|
if (action.eventPhase === "after_adapters") {
|
|
384
389
|
for (const [index, event] of domainEvents.entries()) {
|
|
385
|
-
await appendEvent(invocation, event, `domain:${index}
|
|
390
|
+
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
386
391
|
}
|
|
387
392
|
}
|
|
388
|
-
const result = withoutPrivateHostFields(data);
|
|
393
|
+
const result = withoutPrivateHostFields(data, eventResultFields);
|
|
389
394
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
390
395
|
status: "completed",
|
|
391
396
|
result
|
|
@@ -416,7 +421,7 @@ function createGovernedActionHost(options) {
|
|
|
416
421
|
error: `Invocation is not waiting for approval (status: ${invocation.status})`
|
|
417
422
|
};
|
|
418
423
|
}
|
|
419
|
-
const action =
|
|
424
|
+
const action = actionResolver(invocation.actionId);
|
|
420
425
|
if (!action) return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
|
|
421
426
|
const approvalAuthorizationInput = toAuthorizationInput(action, {
|
|
422
427
|
tenantId,
|
|
@@ -464,14 +469,14 @@ function createGovernedActionHost(options) {
|
|
|
464
469
|
if (!decision.approved) return actionResult(transitioned);
|
|
465
470
|
return executeInvocation(actionInvocationId, tenantId, spaceId, { leaseOwner });
|
|
466
471
|
}
|
|
467
|
-
async function appendEvent(invocation, event, deduplicationKey) {
|
|
472
|
+
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
|
|
468
473
|
const timestamp = now();
|
|
469
474
|
const envelope = {
|
|
470
475
|
id: lifecycleId("evt", invocation.id, deduplicationKey),
|
|
471
476
|
tenantId: invocation.tenantId,
|
|
472
477
|
spaceId: invocation.spaceId,
|
|
473
478
|
eventType: event.eventType,
|
|
474
|
-
eventSchemaVersion: event.eventSchemaVersion ??
|
|
479
|
+
eventSchemaVersion: event.eventSchemaVersion ?? defaultEventSchemaVersion,
|
|
475
480
|
subjectType: event.subjectType,
|
|
476
481
|
subjectId: event.subjectId,
|
|
477
482
|
actorId: invocation.actorId,
|
|
@@ -556,9 +561,10 @@ function initialState(entityType) {
|
|
|
556
561
|
function isTerminal(status) {
|
|
557
562
|
return ["completed", "failed", "blocked_by_policy", "validation_failed"].includes(status);
|
|
558
563
|
}
|
|
559
|
-
function withoutPrivateHostFields(data) {
|
|
560
|
-
|
|
561
|
-
|
|
564
|
+
function withoutPrivateHostFields(data, eventResultFields) {
|
|
565
|
+
return Object.fromEntries(
|
|
566
|
+
Object.entries(data).filter(([key]) => !eventResultFields.includes(key))
|
|
567
|
+
);
|
|
562
568
|
}
|
|
563
569
|
function errorMessage(error) {
|
|
564
570
|
return error instanceof Error ? error.message : String(error);
|