@fabricorg/platform-host 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +62 -1
- package/dist/index.cjs +155 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -3
- package/dist/index.d.ts +70 -3
- package/dist/index.js +156 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 0.7.0 — 2026-07-27
|
|
4
|
+
|
|
5
|
+
- Add parameter-aware execution authorization immediately before policies and mutation execution,
|
|
6
|
+
including approval resume and interrupted-work recovery.
|
|
7
|
+
- Persist an opaque authorization binding with each invocation so applications can revalidate the
|
|
8
|
+
exact admission without retaining credentials or caller proofs.
|
|
9
|
+
- Add an optional transaction-scoped mutation unit of work that commits domain writes and
|
|
10
|
+
before-adapter events atomically.
|
|
11
|
+
- Atomically append `after_adapters` completion events with invocation completion when the store
|
|
12
|
+
provides the unit-of-work capability; finalization failures remain recoverable and do not repeat
|
|
13
|
+
succeeded adapter checkpoints.
|
|
14
|
+
- Advance the durable Host lifecycle contract to version 2.
|
|
15
|
+
|
|
16
|
+
## 0.6.0
|
|
17
|
+
|
|
18
|
+
- Persist governance, Host, provider-bridge, package, and policy-ruleset generations with every new invocation.
|
|
19
|
+
- Add PostgreSQL `runtime_evidence` migration and in-memory/PostgreSQL conformance coverage.
|
|
20
|
+
- Run the shared external-mutation fixture used by the .NET parity suite.
|
|
21
|
+
|
|
3
22
|
## 0.5.1 — 2026-07-20
|
|
4
23
|
|
|
5
24
|
- Include the audit-safe, redacted adapter input in execution-attestation extraction so integrations can identify external resources even when provider responses are minimal.
|
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.
|
|
6
|
+
pnpm add @fabricorg/platform@^0.9.0 @fabricorg/platform-host@^0.7.0
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Vertical packages register `FabricModule` definitions. Host applications provide tenant authorization,
|
|
@@ -36,6 +36,51 @@ adapter that already reached `succeeded`, and event, policy, and adapter writes
|
|
|
36
36
|
stale action that was not declared idempotent fails terminally for manual reconciliation instead of
|
|
37
37
|
silently rerunning unknown side effects.
|
|
38
38
|
|
|
39
|
+
## Execution authorization
|
|
40
|
+
|
|
41
|
+
Submission authorization proves that a caller may create an invocation. Applications that delegate
|
|
42
|
+
resource-scoped authority should also configure `authorizeExecution`. The Host calls it with the
|
|
43
|
+
schema-parsed durable parameters, canonical invocation, opaque `authorizationBindingId`, and an
|
|
44
|
+
`executionReason` of `initial`, `approval_resume`, or `recovery` immediately before policies and
|
|
45
|
+
mutation code run.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const host = createGovernedActionHost({
|
|
49
|
+
store,
|
|
50
|
+
authorization: {
|
|
51
|
+
checkEntitlement,
|
|
52
|
+
authorize: authorizeSubmission,
|
|
53
|
+
authorizeExecution: async ({ invocation, parameters, executionReason }) =>
|
|
54
|
+
admissionStore.authorize({
|
|
55
|
+
admissionId: invocation.authorizationBindingId,
|
|
56
|
+
parameters,
|
|
57
|
+
executionReason,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
When `authorizeExecution` is absent, the Host reuses `authorize` at the execution boundary. A
|
|
64
|
+
completed idempotent replay returns its existing result without creating or executing a new
|
|
65
|
+
mutation.
|
|
66
|
+
|
|
67
|
+
## Atomic mutation unit of work
|
|
68
|
+
|
|
69
|
+
Production stores can implement `AtomicMutationPlatformHostStore.transactionWithEvents`. The
|
|
70
|
+
transaction-scoped `db`, event sequence, event append, event listing, and invocation update methods
|
|
71
|
+
must all use the same database transaction.
|
|
72
|
+
|
|
73
|
+
`PostgresPlatformHostStore` enables this capability when constructed with a
|
|
74
|
+
`PostgresPlatformHostTransactionProvider`. The application owns that narrow binder because only the
|
|
75
|
+
application knows how its `TDb` is rebound to the transaction's SQL client.
|
|
76
|
+
|
|
77
|
+
- `before_adapters`: handler domain writes and declared domain events commit or roll back together.
|
|
78
|
+
- `after_adapters`: completion events and invocation completion commit together. A finalization
|
|
79
|
+
failure leaves the invocation recoverable; succeeded adapter checkpoints are not repeated.
|
|
80
|
+
|
|
81
|
+
Stores without this additive capability retain the legacy boundary for compatibility and must not
|
|
82
|
+
claim atomic domain-write/event persistence.
|
|
83
|
+
|
|
39
84
|
## Agent HITL
|
|
40
85
|
|
|
41
86
|
Hosts can inject a vertical-owned `hitlEvaluator`. It runs only for `actorType: "agent"`, after schema
|
|
@@ -98,6 +143,22 @@ The built-in stores persist footprints, delegation, obligations, attestations, a
|
|
|
98
143
|
observations. Provider-specific authentication and clients do not belong here. For Databricks, use
|
|
99
144
|
the optional Platform integration exported by `@fabric-harness/databricks`.
|
|
100
145
|
|
|
146
|
+
Every newly submitted invocation also records `runtimeEvidence`. The host always supplies the
|
|
147
|
+
portable governance and host contract generations; applications should add the deployed host
|
|
148
|
+
package version, policy ruleset version, and provider bridge identity. This makes an audit record
|
|
149
|
+
explain which contract and provider adapter governed a mutation after dependencies have moved on.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const host = createGovernedActionHost({
|
|
153
|
+
// ...
|
|
154
|
+
runtimeEvidence: {
|
|
155
|
+
hostPackageVersion: "0.7.0",
|
|
156
|
+
policyRulesetVersion: "gtm-rules.v8",
|
|
157
|
+
providerBridge: { name: "@fabric-harness/databricks", version: "1" },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
101
162
|
`MemoryPlatformHostStore` is for tests and local demos. Production control planes use
|
|
102
163
|
`PostgresPlatformHostStore` with Databricks Lakebase (or standard Postgres), call
|
|
103
164
|
`ensureSchema()` at startup, and hydrate projections from `listEvents()`.
|
package/dist/index.cjs
CHANGED
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
var platform = require('@fabricorg/platform');
|
|
4
4
|
|
|
5
|
+
// src/host.ts
|
|
6
|
+
|
|
7
|
+
// src/types.ts
|
|
8
|
+
var PLATFORM_HOST_CONTRACT_VERSION = 2;
|
|
9
|
+
|
|
5
10
|
// src/host.ts
|
|
6
11
|
var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
7
12
|
const value = data._events;
|
|
8
13
|
return Array.isArray(value) ? value : [];
|
|
9
14
|
};
|
|
15
|
+
var RecoverableFinalizationError = class extends Error {
|
|
16
|
+
name = "RecoverableFinalizationError";
|
|
17
|
+
};
|
|
10
18
|
function createGovernedActionHost(options) {
|
|
11
19
|
const adapters = new platform.AdapterRegistry();
|
|
12
20
|
for (const adapter of options.adapters ?? []) adapters.register(adapter);
|
|
@@ -27,6 +35,12 @@ function createGovernedActionHost(options) {
|
|
|
27
35
|
const actionInvocationId = platform.createFabricId("act");
|
|
28
36
|
const correlationId = input.correlationId ?? platform.createFabricId("corr");
|
|
29
37
|
const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
|
|
38
|
+
const runtimeEvidence = {
|
|
39
|
+
governanceContractVersion: platform.FABRIC_GOVERNANCE_CONTRACT_VERSION,
|
|
40
|
+
hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
|
|
41
|
+
...options.runtimeEvidence
|
|
42
|
+
};
|
|
43
|
+
platform.assertGovernanceRuntimeEvidence(runtimeEvidence);
|
|
30
44
|
const durableInvocation = await options.store.createActionInvocation({
|
|
31
45
|
id: actionInvocationId,
|
|
32
46
|
tenantId: input.tenantId,
|
|
@@ -38,9 +52,11 @@ function createGovernedActionHost(options) {
|
|
|
38
52
|
status: "pending",
|
|
39
53
|
parameters: durableParameters,
|
|
40
54
|
result: {},
|
|
55
|
+
runtimeEvidence,
|
|
41
56
|
correlationId,
|
|
42
57
|
...input.causationId ? { causationId: input.causationId } : {},
|
|
43
|
-
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
|
|
58
|
+
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
59
|
+
...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
|
|
44
60
|
});
|
|
45
61
|
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
46
62
|
if (durableInvocation.id !== actionInvocationId && isTerminal(durableInvocation.status)) {
|
|
@@ -88,7 +104,7 @@ function createGovernedActionHost(options) {
|
|
|
88
104
|
);
|
|
89
105
|
return { ...executed, workflowId: durableWorkflowId };
|
|
90
106
|
}
|
|
91
|
-
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}) {
|
|
107
|
+
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
|
|
92
108
|
const loadedInvocation = await options.store.getActionInvocation(
|
|
93
109
|
actionInvocationId,
|
|
94
110
|
tenantId,
|
|
@@ -97,6 +113,7 @@ function createGovernedActionHost(options) {
|
|
|
97
113
|
if (!loadedInvocation) {
|
|
98
114
|
throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
99
115
|
}
|
|
116
|
+
const resumingRunningInvocation = loadedInvocation.status === "running";
|
|
100
117
|
let invocation = loadedInvocation;
|
|
101
118
|
if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
|
|
102
119
|
return actionResult(invocation);
|
|
@@ -198,6 +215,28 @@ function createGovernedActionHost(options) {
|
|
|
198
215
|
}
|
|
199
216
|
}
|
|
200
217
|
const authorizationInput = toAuthorizationInput(action, invocation);
|
|
218
|
+
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
|
|
219
|
+
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
220
|
+
return fail(
|
|
221
|
+
invocation,
|
|
222
|
+
"failed",
|
|
223
|
+
`Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const executionAuthorized = options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
|
|
227
|
+
...authorizationInput,
|
|
228
|
+
actionInvocationId,
|
|
229
|
+
parameters: parsed.data,
|
|
230
|
+
invocation,
|
|
231
|
+
executionReason
|
|
232
|
+
}) : await options.authorization.authorize(authorizationInput);
|
|
233
|
+
if (!executionAuthorized) {
|
|
234
|
+
return fail(
|
|
235
|
+
invocation,
|
|
236
|
+
"failed",
|
|
237
|
+
`Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
201
240
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
202
241
|
...authorizationInput,
|
|
203
242
|
declaredPolicyIds: action.policies ?? []
|
|
@@ -282,8 +321,8 @@ function createGovernedActionHost(options) {
|
|
|
282
321
|
let data;
|
|
283
322
|
let domainEvents = [];
|
|
284
323
|
try {
|
|
285
|
-
const
|
|
286
|
-
|
|
324
|
+
const runHandler = async (db, transaction) => {
|
|
325
|
+
const handlerResult = action.handler ? await action.handler(
|
|
287
326
|
{
|
|
288
327
|
actionInvocationId,
|
|
289
328
|
tenantId,
|
|
@@ -296,28 +335,39 @@ function createGovernedActionHost(options) {
|
|
|
296
335
|
services: options.services
|
|
297
336
|
},
|
|
298
337
|
parsed.data
|
|
299
|
-
) :
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
(event) => !action.emitsEvents.includes(event.eventType)
|
|
308
|
-
);
|
|
309
|
-
if (undeclaredEvent) {
|
|
310
|
-
return fail(
|
|
311
|
-
invocation,
|
|
312
|
-
"failed",
|
|
313
|
-
`${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
|
|
338
|
+
) : { success: true, data: {} };
|
|
339
|
+
if (!handlerResult.success) {
|
|
340
|
+
throw new Error(handlerResult.error ?? "Action handler failed");
|
|
341
|
+
}
|
|
342
|
+
const handlerData = handlerResult.data ?? {};
|
|
343
|
+
const events = extractEvents(handlerData);
|
|
344
|
+
const undeclaredEvent = events.find(
|
|
345
|
+
(event) => !action.emitsEvents.includes(event.eventType)
|
|
314
346
|
);
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
347
|
+
if (undeclaredEvent) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
|
|
350
|
+
);
|
|
319
351
|
}
|
|
320
|
-
|
|
352
|
+
if (action.eventPhase !== "after_adapters") {
|
|
353
|
+
for (const [index, event] of events.entries()) {
|
|
354
|
+
await appendEvent(
|
|
355
|
+
invocation,
|
|
356
|
+
event,
|
|
357
|
+
`domain:${index}`,
|
|
358
|
+
action.version,
|
|
359
|
+
transaction
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return { data: handlerData, domainEvents: events };
|
|
364
|
+
};
|
|
365
|
+
const atomicStore = asAtomicMutationStore(options.store);
|
|
366
|
+
const executed = atomicStore ? await atomicStore.transactionWithEvents(
|
|
367
|
+
(transaction) => runHandler(transaction.db, transaction)
|
|
368
|
+
) : await options.store.transaction((db) => runHandler(db));
|
|
369
|
+
data = executed.data;
|
|
370
|
+
domainEvents = executed.domainEvents;
|
|
321
371
|
} catch (error) {
|
|
322
372
|
return fail(invocation, "failed", errorMessage(error));
|
|
323
373
|
}
|
|
@@ -448,11 +498,6 @@ function createGovernedActionHost(options) {
|
|
|
448
498
|
return fail(invocation, "failed", message);
|
|
449
499
|
}
|
|
450
500
|
}
|
|
451
|
-
if (action.eventPhase === "after_adapters") {
|
|
452
|
-
for (const [index, event] of domainEvents.entries()) {
|
|
453
|
-
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
501
|
const governanceStore = asGovernanceStore(options.store);
|
|
457
502
|
if (governanceStore && options.resolvePolicyObligations) {
|
|
458
503
|
const obligations = await governanceStore.listPolicyObligations(actionInvocationId, tenantId, spaceId);
|
|
@@ -462,10 +507,43 @@ function createGovernedActionHost(options) {
|
|
|
462
507
|
}
|
|
463
508
|
}
|
|
464
509
|
const result = withoutPrivateHostFields(data, eventResultFields);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
510
|
+
try {
|
|
511
|
+
const atomicStore = asAtomicMutationStore(options.store);
|
|
512
|
+
if (action.eventPhase === "after_adapters" && atomicStore) {
|
|
513
|
+
await atomicStore.transactionWithEvents(async (transaction) => {
|
|
514
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
515
|
+
await appendEvent(
|
|
516
|
+
invocation,
|
|
517
|
+
event,
|
|
518
|
+
`domain:${index}`,
|
|
519
|
+
action.version,
|
|
520
|
+
transaction
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
await transaction.updateActionInvocation(
|
|
524
|
+
actionInvocationId,
|
|
525
|
+
tenantId,
|
|
526
|
+
spaceId,
|
|
527
|
+
{ status: "completed", result }
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
if (action.eventPhase === "after_adapters") {
|
|
532
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
533
|
+
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
537
|
+
status: "completed",
|
|
538
|
+
result
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (action.eventPhase === "after_adapters") {
|
|
543
|
+
throw new RecoverableFinalizationError(errorMessage(error));
|
|
544
|
+
}
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
469
547
|
return {
|
|
470
548
|
actionInvocationId,
|
|
471
549
|
status: "completed",
|
|
@@ -474,6 +552,7 @@ function createGovernedActionHost(options) {
|
|
|
474
552
|
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
475
553
|
};
|
|
476
554
|
} catch (error) {
|
|
555
|
+
if (error instanceof RecoverableFinalizationError) throw error;
|
|
477
556
|
return fail(invocation, "failed", errorMessage(error));
|
|
478
557
|
}
|
|
479
558
|
}
|
|
@@ -538,7 +617,13 @@ function createGovernedActionHost(options) {
|
|
|
538
617
|
return actionResult(transitioned);
|
|
539
618
|
}
|
|
540
619
|
if (!decision.approved) return actionResult(transitioned);
|
|
541
|
-
return executeInvocation(
|
|
620
|
+
return executeInvocation(
|
|
621
|
+
actionInvocationId,
|
|
622
|
+
tenantId,
|
|
623
|
+
spaceId,
|
|
624
|
+
{ leaseOwner },
|
|
625
|
+
"approval_resume"
|
|
626
|
+
);
|
|
542
627
|
}
|
|
543
628
|
async function recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId) {
|
|
544
629
|
const governanceStore = asGovernanceStore(options.store);
|
|
@@ -572,7 +657,7 @@ function createGovernedActionHost(options) {
|
|
|
572
657
|
spaceId
|
|
573
658
|
});
|
|
574
659
|
}
|
|
575
|
-
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
|
|
660
|
+
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
|
|
576
661
|
const timestamp = now();
|
|
577
662
|
const envelope = {
|
|
578
663
|
id: lifecycleId("evt", invocation.id, deduplicationKey),
|
|
@@ -586,7 +671,7 @@ function createGovernedActionHost(options) {
|
|
|
586
671
|
actorType: invocation.actorType,
|
|
587
672
|
actionInvocationId: invocation.id,
|
|
588
673
|
payload: event.payload,
|
|
589
|
-
sequence: await options.store.nextEventSequence(
|
|
674
|
+
sequence: await (transaction ?? options.store).nextEventSequence(
|
|
590
675
|
invocation.tenantId,
|
|
591
676
|
invocation.spaceId
|
|
592
677
|
),
|
|
@@ -595,7 +680,7 @@ function createGovernedActionHost(options) {
|
|
|
595
680
|
correlationId: invocation.correlationId,
|
|
596
681
|
...invocation.causationId ? { causationId: invocation.causationId } : {}
|
|
597
682
|
};
|
|
598
|
-
await options.store.appendEvent(envelope);
|
|
683
|
+
await (transaction ?? options.store).appendEvent(envelope);
|
|
599
684
|
}
|
|
600
685
|
async function fail(invocation, status, error) {
|
|
601
686
|
await options.store.updateActionInvocation(
|
|
@@ -628,6 +713,10 @@ function asApprovalStore(store) {
|
|
|
628
713
|
const candidate = store;
|
|
629
714
|
return typeof candidate.recordHitlDecision === "function" && typeof candidate.beginApprovalDecision === "function" ? store : void 0;
|
|
630
715
|
}
|
|
716
|
+
function asAtomicMutationStore(store) {
|
|
717
|
+
const candidate = store;
|
|
718
|
+
return typeof candidate.transactionWithEvents === "function" ? store : void 0;
|
|
719
|
+
}
|
|
631
720
|
function asGovernanceStore(store) {
|
|
632
721
|
const candidate = store;
|
|
633
722
|
return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
|
|
@@ -854,13 +943,26 @@ var MemoryPlatformHostStore = class {
|
|
|
854
943
|
};
|
|
855
944
|
|
|
856
945
|
// src/postgres-store.ts
|
|
857
|
-
var PostgresPlatformHostStore = class {
|
|
858
|
-
constructor(db, sql) {
|
|
946
|
+
var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
947
|
+
constructor(db, sql, transactionProvider) {
|
|
859
948
|
this.db = db;
|
|
860
949
|
this.sql = sql;
|
|
950
|
+
if (transactionProvider) {
|
|
951
|
+
this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
|
|
952
|
+
const scoped = new _PostgresPlatformHostStore(db2, sql2);
|
|
953
|
+
return run({
|
|
954
|
+
db: db2,
|
|
955
|
+
appendEvent: (event) => scoped.appendEvent(event),
|
|
956
|
+
nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
|
|
957
|
+
listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
|
|
958
|
+
updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
}
|
|
861
962
|
}
|
|
862
963
|
db;
|
|
863
964
|
sql;
|
|
965
|
+
transactionWithEvents;
|
|
864
966
|
async ensureSchema() {
|
|
865
967
|
await this.sql.query(`
|
|
866
968
|
CREATE SCHEMA IF NOT EXISTS fabric_platform;
|
|
@@ -869,15 +971,17 @@ var PostgresPlatformHostStore = class {
|
|
|
869
971
|
action_id text NOT NULL, action_version integer NOT NULL,
|
|
870
972
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
871
973
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
872
|
-
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
974
|
+
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
975
|
+
authorization_binding_id text, error text,
|
|
873
976
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
874
977
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
875
978
|
hitl_reason text, hitl_policy_version text, approval_decision jsonb,
|
|
876
|
-
mutation_footprint jsonb, execution_principal jsonb,
|
|
979
|
+
mutation_footprint jsonb, execution_principal jsonb, runtime_evidence jsonb,
|
|
877
980
|
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
|
|
878
981
|
);
|
|
879
982
|
ALTER TABLE fabric_platform.action_invocations
|
|
880
983
|
ADD COLUMN IF NOT EXISTS idempotency_key text,
|
|
984
|
+
ADD COLUMN IF NOT EXISTS authorization_binding_id text,
|
|
881
985
|
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
882
986
|
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
883
987
|
ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz,
|
|
@@ -887,7 +991,8 @@ var PostgresPlatformHostStore = class {
|
|
|
887
991
|
ADD COLUMN IF NOT EXISTS hitl_policy_version text,
|
|
888
992
|
ADD COLUMN IF NOT EXISTS approval_decision jsonb,
|
|
889
993
|
ADD COLUMN IF NOT EXISTS mutation_footprint jsonb,
|
|
890
|
-
ADD COLUMN IF NOT EXISTS execution_principal jsonb
|
|
994
|
+
ADD COLUMN IF NOT EXISTS execution_principal jsonb,
|
|
995
|
+
ADD COLUMN IF NOT EXISTS runtime_evidence jsonb;
|
|
891
996
|
CREATE UNIQUE INDEX IF NOT EXISTS action_invocations_idempotency_idx
|
|
892
997
|
ON fabric_platform.action_invocations
|
|
893
998
|
(tenant_id, space_id, action_id, idempotency_key)
|
|
@@ -951,8 +1056,9 @@ var PostgresPlatformHostStore = class {
|
|
|
951
1056
|
const result = await this.sql.query(
|
|
952
1057
|
`INSERT INTO fabric_platform.action_invocations
|
|
953
1058
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
954
|
-
|
|
955
|
-
|
|
1059
|
+
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
1060
|
+
authorization_binding_id,error,runtime_evidence,created_at,updated_at)
|
|
1061
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$17,$17)
|
|
956
1062
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
957
1063
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
958
1064
|
RETURNING *`,
|
|
@@ -970,7 +1076,9 @@ var PostgresPlatformHostStore = class {
|
|
|
970
1076
|
input.correlationId,
|
|
971
1077
|
input.causationId ?? null,
|
|
972
1078
|
input.idempotencyKey ?? null,
|
|
1079
|
+
input.authorizationBindingId ?? null,
|
|
973
1080
|
input.error ?? null,
|
|
1081
|
+
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
974
1082
|
now
|
|
975
1083
|
]
|
|
976
1084
|
);
|
|
@@ -1332,6 +1440,7 @@ function toActionRecord(row) {
|
|
|
1332
1440
|
correlationId: String(row.correlation_id),
|
|
1333
1441
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
1334
1442
|
...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
|
|
1443
|
+
...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
|
|
1335
1444
|
attemptCount: Number(row.attempt_count ?? 0),
|
|
1336
1445
|
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
1337
1446
|
...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
|
|
@@ -1342,6 +1451,7 @@ function toActionRecord(row) {
|
|
|
1342
1451
|
...row.approval_decision ? { approvalDecision: toApprovalDecision(row.approval_decision) } : {},
|
|
1343
1452
|
...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
|
|
1344
1453
|
...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
|
|
1454
|
+
...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
|
|
1345
1455
|
...row.error ? { error: String(row.error) } : {},
|
|
1346
1456
|
createdAt: new Date(row.created_at),
|
|
1347
1457
|
updatedAt: new Date(row.updated_at)
|
|
@@ -1476,6 +1586,7 @@ async function abortableDelay(milliseconds, signal) {
|
|
|
1476
1586
|
}
|
|
1477
1587
|
|
|
1478
1588
|
exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
|
|
1589
|
+
exports.PLATFORM_HOST_CONTRACT_VERSION = PLATFORM_HOST_CONTRACT_VERSION;
|
|
1479
1590
|
exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
|
|
1480
1591
|
exports.createGovernedActionHost = createGovernedActionHost;
|
|
1481
1592
|
exports.createStoreBackedActionDispatcher = createStoreBackedActionDispatcher;
|