@fabricorg/platform-host 4.0.0 → 5.0.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 +27 -0
- package/dist/index.cjs +41 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +41 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -213,6 +213,8 @@ interface EnterpriseEventEnvelope {
|
|
|
213
213
|
source: InvocationSource;
|
|
214
214
|
auditAttributes?: NamespacedAttributes;
|
|
215
215
|
};
|
|
216
|
+
/** Present only for events an action declaring provisional consistency emitted. */
|
|
217
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
216
218
|
payload: unknown;
|
|
217
219
|
}
|
|
218
220
|
interface OutboxRecord {
|
|
@@ -393,6 +395,8 @@ interface SubmitActionResult {
|
|
|
393
395
|
result?: Record<string, unknown>;
|
|
394
396
|
error?: string;
|
|
395
397
|
reconciliation?: AuthorizationReconciliationOutcome;
|
|
398
|
+
/** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
|
|
399
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
396
400
|
hitlRoute?: AgentActionRoute;
|
|
397
401
|
hitlRiskTier?: AgentActionRiskTier;
|
|
398
402
|
}
|
|
@@ -402,6 +406,8 @@ interface ExecuteActionResult {
|
|
|
402
406
|
result?: Record<string, unknown>;
|
|
403
407
|
error?: string;
|
|
404
408
|
reconciliation?: AuthorizationReconciliationOutcome;
|
|
409
|
+
/** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
|
|
410
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
405
411
|
hitlRoute?: AgentActionRoute;
|
|
406
412
|
hitlRiskTier?: AgentActionRiskTier;
|
|
407
413
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -213,6 +213,8 @@ interface EnterpriseEventEnvelope {
|
|
|
213
213
|
source: InvocationSource;
|
|
214
214
|
auditAttributes?: NamespacedAttributes;
|
|
215
215
|
};
|
|
216
|
+
/** Present only for events an action declaring provisional consistency emitted. */
|
|
217
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
216
218
|
payload: unknown;
|
|
217
219
|
}
|
|
218
220
|
interface OutboxRecord {
|
|
@@ -393,6 +395,8 @@ interface SubmitActionResult {
|
|
|
393
395
|
result?: Record<string, unknown>;
|
|
394
396
|
error?: string;
|
|
395
397
|
reconciliation?: AuthorizationReconciliationOutcome;
|
|
398
|
+
/** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
|
|
399
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
396
400
|
hitlRoute?: AgentActionRoute;
|
|
397
401
|
hitlRiskTier?: AgentActionRiskTier;
|
|
398
402
|
}
|
|
@@ -402,6 +406,8 @@ interface ExecuteActionResult {
|
|
|
402
406
|
result?: Record<string, unknown>;
|
|
403
407
|
error?: string;
|
|
404
408
|
reconciliation?: AuthorizationReconciliationOutcome;
|
|
409
|
+
/** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
|
|
410
|
+
consistency?: "authoritative-now" | "provisional-until-reconciled";
|
|
405
411
|
hitlRoute?: AgentActionRoute;
|
|
406
412
|
hitlRiskTier?: AgentActionRiskTier;
|
|
407
413
|
}
|
package/dist/index.js
CHANGED
|
@@ -92,6 +92,12 @@ function createGovernedActionHost(options) {
|
|
|
92
92
|
}
|
|
93
93
|
const action = actionResolver(input.actionId);
|
|
94
94
|
if (!action) throw new Error(`Unknown action: ${input.actionId}`);
|
|
95
|
+
if (action.execution?.connectivity === "online-required" && input.executionReason === "offline_replay") {
|
|
96
|
+
throw new Error(`Action ${input.actionId} is online-required and cannot be submitted as an offline replay.`);
|
|
97
|
+
}
|
|
98
|
+
if (action.execution?.sensitivity === "restricted" && Object.keys(input.provenance?.auditAttributes ?? {}).length > 0) {
|
|
99
|
+
throw new Error(`Action ${input.actionId} declares restricted sensitivity; provenance audit attributes must not be durably recorded.`);
|
|
100
|
+
}
|
|
95
101
|
const authorizationInput = toAuthorizationInput(action, input);
|
|
96
102
|
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
97
103
|
throw new Error(`Module "${action.namespace}" is not enabled for tenant ${input.tenantId}`);
|
|
@@ -164,7 +170,7 @@ function createGovernedActionHost(options) {
|
|
|
164
170
|
}
|
|
165
171
|
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
166
172
|
if (durableInvocation.id !== actionInvocationId) {
|
|
167
|
-
return {
|
|
173
|
+
return withConsistency({
|
|
168
174
|
actionInvocationId: durableInvocation.id,
|
|
169
175
|
status: durableInvocation.status,
|
|
170
176
|
workflowId: durableWorkflowId,
|
|
@@ -173,7 +179,7 @@ function createGovernedActionHost(options) {
|
|
|
173
179
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
174
180
|
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {},
|
|
175
181
|
...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {}
|
|
176
|
-
};
|
|
182
|
+
}, input.actionId);
|
|
177
183
|
}
|
|
178
184
|
if (options.dispatcher) {
|
|
179
185
|
try {
|
|
@@ -183,14 +189,14 @@ function createGovernedActionHost(options) {
|
|
|
183
189
|
spaceId: input.spaceId,
|
|
184
190
|
workflowId: durableWorkflowId
|
|
185
191
|
});
|
|
186
|
-
return {
|
|
192
|
+
return withConsistency({
|
|
187
193
|
actionInvocationId: durableInvocation.id,
|
|
188
194
|
status: durableInvocation.status,
|
|
189
195
|
workflowId: dispatched.workflowId,
|
|
190
196
|
...dispatched.runId ? { runId: dispatched.runId } : {},
|
|
191
197
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
192
198
|
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
|
|
193
|
-
};
|
|
199
|
+
}, input.actionId);
|
|
194
200
|
} catch (error) {
|
|
195
201
|
const message = errorMessage(error);
|
|
196
202
|
await options.store.updateActionInvocation(
|
|
@@ -207,7 +213,7 @@ function createGovernedActionHost(options) {
|
|
|
207
213
|
input.tenantId,
|
|
208
214
|
input.spaceId
|
|
209
215
|
);
|
|
210
|
-
return { ...executed, workflowId: durableWorkflowId };
|
|
216
|
+
return { ...withConsistency(executed, input.actionId), workflowId: durableWorkflowId };
|
|
211
217
|
}
|
|
212
218
|
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
|
|
213
219
|
const loadedInvocation = await options.store.getActionInvocation(
|
|
@@ -221,7 +227,7 @@ function createGovernedActionHost(options) {
|
|
|
221
227
|
const resumingRunningInvocation = loadedInvocation.status === "running";
|
|
222
228
|
let invocation = loadedInvocation;
|
|
223
229
|
if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
|
|
224
|
-
return actionResult(invocation);
|
|
230
|
+
return withConsistency(actionResult(invocation), invocation.actionId);
|
|
225
231
|
}
|
|
226
232
|
if (invocation.status === "running" && invocation.leaseOwner && executionOptions.leaseOwner !== invocation.leaseOwner) {
|
|
227
233
|
return {
|
|
@@ -338,7 +344,7 @@ function createGovernedActionHost(options) {
|
|
|
338
344
|
message: `Action ${action.actionId} requires durable capture-time authorization evidence`
|
|
339
345
|
};
|
|
340
346
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
341
|
-
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
347
|
+
return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
|
|
342
348
|
}
|
|
343
349
|
const bindingExpired = authorityMoment !== "capture" && invocation.authorizationBinding?.expiresAt !== void 0 && Date.parse(invocation.authorizationBinding.expiresAt) <= now().getTime();
|
|
344
350
|
if (bindingExpired) {
|
|
@@ -351,7 +357,7 @@ function createGovernedActionHost(options) {
|
|
|
351
357
|
message: `Authorization binding for action ${action.actionId} expired before execution`
|
|
352
358
|
};
|
|
353
359
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
354
|
-
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
360
|
+
return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
|
|
355
361
|
}
|
|
356
362
|
const executionAuthorized = authorityMoment === "capture" ? invocation.authorizationBinding !== void 0 : options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
|
|
357
363
|
...authorizationInput,
|
|
@@ -373,7 +379,7 @@ function createGovernedActionHost(options) {
|
|
|
373
379
|
message: `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
|
|
374
380
|
};
|
|
375
381
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
376
|
-
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
382
|
+
return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
|
|
377
383
|
}
|
|
378
384
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
379
385
|
...authorizationInput,
|
|
@@ -715,7 +721,7 @@ function createGovernedActionHost(options) {
|
|
|
715
721
|
spaceId
|
|
716
722
|
);
|
|
717
723
|
if (!invocation) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
718
|
-
if (isTerminal(invocation.status)) return actionResult(invocation);
|
|
724
|
+
if (isTerminal(invocation.status)) return withConsistency(actionResult(invocation), invocation.actionId);
|
|
719
725
|
if (invocation.status !== "waiting_for_approval") {
|
|
720
726
|
return {
|
|
721
727
|
...actionResult(invocation),
|
|
@@ -765,9 +771,9 @@ function createGovernedActionHost(options) {
|
|
|
765
771
|
const transitioned = transition.invocation ?? await options.store.getActionInvocation(actionInvocationId, tenantId, spaceId);
|
|
766
772
|
if (!transition.applied || !transitioned) {
|
|
767
773
|
if (!transitioned) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
768
|
-
return actionResult(transitioned);
|
|
774
|
+
return withConsistency(actionResult(transitioned), transitioned.actionId);
|
|
769
775
|
}
|
|
770
|
-
if (!decision.approved) return actionResult(transitioned);
|
|
776
|
+
if (!decision.approved) return withConsistency(actionResult(transitioned), transitioned.actionId);
|
|
771
777
|
return executeInvocation(
|
|
772
778
|
actionInvocationId,
|
|
773
779
|
tenantId,
|
|
@@ -808,6 +814,13 @@ function createGovernedActionHost(options) {
|
|
|
808
814
|
spaceId
|
|
809
815
|
});
|
|
810
816
|
}
|
|
817
|
+
function declaredConsistency(actionId) {
|
|
818
|
+
return actionResolver(actionId)?.execution?.consistency;
|
|
819
|
+
}
|
|
820
|
+
function withConsistency(result, actionId) {
|
|
821
|
+
const consistency = declaredConsistency(actionId);
|
|
822
|
+
return consistency ? { ...result, consistency } : result;
|
|
823
|
+
}
|
|
811
824
|
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
|
|
812
825
|
const timestamp = now();
|
|
813
826
|
const envelope = {
|
|
@@ -830,7 +843,8 @@ function createGovernedActionHost(options) {
|
|
|
830
843
|
recordedAt: timestamp,
|
|
831
844
|
correlationId: invocation.correlationId,
|
|
832
845
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
833
|
-
...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {}
|
|
846
|
+
...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {},
|
|
847
|
+
...declaredConsistency(invocation.actionId) === "provisional-until-reconciled" ? { consistency: "provisional-until-reconciled" } : {}
|
|
834
848
|
};
|
|
835
849
|
if (options.outbox) {
|
|
836
850
|
const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
|
|
@@ -998,6 +1012,8 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
998
1012
|
payloadClassification: metadata.payloadClassification,
|
|
999
1013
|
...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
|
|
1000
1014
|
...metadata.includeProvenance && event.provenance ? { provenance: event.provenance } : {},
|
|
1015
|
+
// A provisional fact must stay marked as provisional once it leaves the platform.
|
|
1016
|
+
...event.consistency ? { consistency: event.consistency } : {},
|
|
1001
1017
|
payload: event.payload
|
|
1002
1018
|
};
|
|
1003
1019
|
}
|
|
@@ -1429,10 +1445,11 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1429
1445
|
actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
|
|
1430
1446
|
payload jsonb NOT NULL, sequence bigint NOT NULL,
|
|
1431
1447
|
occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
|
|
1432
|
-
correlation_id text NOT NULL, causation_id text, provenance jsonb,
|
|
1448
|
+
correlation_id text NOT NULL, causation_id text, provenance jsonb, consistency text,
|
|
1433
1449
|
UNIQUE (tenant_id, space_id, sequence)
|
|
1434
1450
|
);
|
|
1435
1451
|
ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS provenance jsonb;
|
|
1452
|
+
ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS consistency text;
|
|
1436
1453
|
CREATE INDEX IF NOT EXISTS asset_events_subject_idx
|
|
1437
1454
|
ON fabric_platform.asset_events
|
|
1438
1455
|
(tenant_id, space_id, subject_type, subject_id, sequence);
|
|
@@ -1716,8 +1733,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1716
1733
|
`INSERT INTO fabric_platform.asset_events
|
|
1717
1734
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1718
1735
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1719
|
-
correlation_id,causation_id,provenance)
|
|
1720
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1736
|
+
correlation_id,causation_id,provenance,consistency)
|
|
1737
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb,$18)
|
|
1721
1738
|
ON CONFLICT (id) DO NOTHING`,
|
|
1722
1739
|
[
|
|
1723
1740
|
event.id,
|
|
@@ -1736,7 +1753,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1736
1753
|
event.recordedAt,
|
|
1737
1754
|
event.correlationId,
|
|
1738
1755
|
event.causationId ?? null,
|
|
1739
|
-
event.provenance ? JSON.stringify(event.provenance) : null
|
|
1756
|
+
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
1757
|
+
event.consistency ?? null
|
|
1740
1758
|
]
|
|
1741
1759
|
);
|
|
1742
1760
|
}
|
|
@@ -1747,8 +1765,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1747
1765
|
INSERT INTO fabric_platform.asset_events
|
|
1748
1766
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1749
1767
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1750
|
-
correlation_id,causation_id,provenance)
|
|
1751
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1768
|
+
correlation_id,causation_id,provenance,consistency)
|
|
1769
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb,$19)
|
|
1752
1770
|
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
1753
1771
|
)
|
|
1754
1772
|
INSERT INTO fabric_platform.event_outbox
|
|
@@ -1773,7 +1791,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1773
1791
|
event.correlationId,
|
|
1774
1792
|
event.causationId ?? null,
|
|
1775
1793
|
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
1776
|
-
JSON.stringify(envelope)
|
|
1794
|
+
JSON.stringify(envelope),
|
|
1795
|
+
event.consistency ?? null
|
|
1777
1796
|
]
|
|
1778
1797
|
);
|
|
1779
1798
|
}
|
|
@@ -2015,7 +2034,8 @@ function toEventRecord(row) {
|
|
|
2015
2034
|
recordedAt: new Date(row.recorded_at),
|
|
2016
2035
|
correlationId: String(row.correlation_id),
|
|
2017
2036
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
2018
|
-
...row.provenance ? { provenance: row.provenance } : {}
|
|
2037
|
+
...row.provenance ? { provenance: row.provenance } : {},
|
|
2038
|
+
...row.consistency ? { consistency: row.consistency } : {}
|
|
2019
2039
|
};
|
|
2020
2040
|
}
|
|
2021
2041
|
function toOutboxRecord(row) {
|