@fabricorg/platform-host 0.6.0 → 1.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 +33 -0
- package/README.md +66 -2
- package/dist/index.cjs +138 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -3
- package/dist/index.d.ts +65 -3
- package/dist/index.js +138 -43
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, GovernanceRuntimeEvidence, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
|
|
2
2
|
|
|
3
3
|
/** Durable Host lifecycle generation, independent from the npm package version. */
|
|
4
|
-
declare const PLATFORM_HOST_CONTRACT_VERSION:
|
|
4
|
+
declare const PLATFORM_HOST_CONTRACT_VERSION: 2;
|
|
5
5
|
interface PendingAssetEvent {
|
|
6
6
|
eventType: string;
|
|
7
7
|
subjectType: string;
|
|
@@ -23,6 +23,8 @@ interface ActionInvocationRecord {
|
|
|
23
23
|
correlationId: string;
|
|
24
24
|
causationId?: string;
|
|
25
25
|
idempotencyKey?: string;
|
|
26
|
+
/** Opaque, non-secret reference used to revalidate delegated execution authority. */
|
|
27
|
+
authorizationBindingId?: string;
|
|
26
28
|
attemptCount: number;
|
|
27
29
|
leaseOwner?: string;
|
|
28
30
|
leaseExpiresAt?: Date;
|
|
@@ -118,6 +120,31 @@ interface PlatformHostStore<TDb> {
|
|
|
118
120
|
nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
|
|
119
121
|
getEntityState(tenantId: string, spaceId: string, entityType: string, entityId: string): Promise<string | undefined>;
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Transaction-scoped Host operations used to commit domain writes and their
|
|
125
|
+
* canonical events as one unit of work.
|
|
126
|
+
*
|
|
127
|
+
* The supplied `db` must be bound to the same database transaction as every
|
|
128
|
+
* lifecycle method on this object. Implementations must roll the whole unit
|
|
129
|
+
* back when `run` throws.
|
|
130
|
+
*/
|
|
131
|
+
interface PlatformHostMutationTransaction<TDb> {
|
|
132
|
+
readonly db: TDb;
|
|
133
|
+
appendEvent(event: AssetEventEnvelope): Promise<void>;
|
|
134
|
+
nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
|
|
135
|
+
listEvents(tenantId: string, spaceId: string): Promise<AssetEventEnvelope[]>;
|
|
136
|
+
updateActionInvocation(id: string, tenantId: string, spaceId: string, patch: Partial<Pick<ActionInvocationRecord, "status" | "result" | "error">>): Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Optional production capability for atomic domain/event persistence.
|
|
140
|
+
*
|
|
141
|
+
* A mutating Host action uses this capability when available. Stores that do
|
|
142
|
+
* not implement it retain the legacy transaction boundary for compatibility,
|
|
143
|
+
* but cannot claim atomic domain-write/event persistence.
|
|
144
|
+
*/
|
|
145
|
+
interface AtomicMutationPlatformHostStore<TDb> extends PlatformHostStore<TDb> {
|
|
146
|
+
transactionWithEvents<TResult>(run: (transaction: PlatformHostMutationTransaction<TDb>) => Promise<TResult>): Promise<TResult>;
|
|
147
|
+
}
|
|
121
148
|
interface BeginApprovalDecisionInput {
|
|
122
149
|
actionInvocationId: string;
|
|
123
150
|
tenantId: string;
|
|
@@ -180,9 +207,27 @@ interface ActionAuthorizationInput {
|
|
|
180
207
|
actorId: string;
|
|
181
208
|
actorType: ActorType;
|
|
182
209
|
}
|
|
210
|
+
type ActionExecutionReason = "initial" | "approval_resume" | "recovery";
|
|
211
|
+
/**
|
|
212
|
+
* Trusted execution-boundary authorization input.
|
|
213
|
+
*
|
|
214
|
+
* Parameters are the schema-parsed durable parameters, never caller-supplied
|
|
215
|
+
* transient values. `invocation` is the canonical record that will execute.
|
|
216
|
+
*/
|
|
217
|
+
interface ActionExecutionAuthorizationInput extends ActionAuthorizationInput {
|
|
218
|
+
actionInvocationId: string;
|
|
219
|
+
parameters: unknown;
|
|
220
|
+
invocation: Readonly<ActionInvocationRecord>;
|
|
221
|
+
executionReason: ActionExecutionReason;
|
|
222
|
+
}
|
|
183
223
|
interface PlatformHostAuthorization {
|
|
184
224
|
checkEntitlement(input: ActionAuthorizationInput): Promise<boolean>;
|
|
185
225
|
authorize(input: ActionAuthorizationInput): Promise<boolean>;
|
|
226
|
+
/**
|
|
227
|
+
* Optional final authorization immediately before policies and mutation
|
|
228
|
+
* execution. When absent, the Host reuses `authorize` at this boundary.
|
|
229
|
+
*/
|
|
230
|
+
authorizeExecution?(input: ActionExecutionAuthorizationInput): Promise<boolean>;
|
|
186
231
|
/** Optional distinct authorization boundary for human/system approval decisions. */
|
|
187
232
|
authorizeApproval?(input: ActionAuthorizationInput & {
|
|
188
233
|
decision: ActionApprovalDecision;
|
|
@@ -212,6 +257,8 @@ interface SubmitActionInput {
|
|
|
212
257
|
causationId?: string;
|
|
213
258
|
/** Stable logical command key. Reusing it returns/re-dispatches the original invocation. */
|
|
214
259
|
idempotencyKey?: string;
|
|
260
|
+
/** Opaque, non-secret authorization/admission reference persisted with the invocation. */
|
|
261
|
+
authorizationBindingId?: string;
|
|
215
262
|
}
|
|
216
263
|
interface SubmitActionResult {
|
|
217
264
|
actionInvocationId: string;
|
|
@@ -357,11 +404,26 @@ interface PlatformHostSqlResult<Row = Record<string, unknown>> {
|
|
|
357
404
|
interface PlatformHostSqlClient {
|
|
358
405
|
query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<PlatformHostSqlResult<Row>>;
|
|
359
406
|
}
|
|
407
|
+
interface PostgresPlatformHostTransactionContext<TDb> {
|
|
408
|
+
db: TDb;
|
|
409
|
+
sql: PlatformHostSqlClient;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Application-owned transaction binder.
|
|
413
|
+
*
|
|
414
|
+
* Platform Host cannot infer how an application's `TDb` is rebound to a
|
|
415
|
+
* `pg.PoolClient`. Production applications provide this adapter so domain
|
|
416
|
+
* methods and Host ledger writes share one PostgreSQL transaction.
|
|
417
|
+
*/
|
|
418
|
+
interface PostgresPlatformHostTransactionProvider<TDb> {
|
|
419
|
+
run<TResult>(run: (context: PostgresPlatformHostTransactionContext<TDb>) => Promise<TResult>): Promise<TResult>;
|
|
420
|
+
}
|
|
360
421
|
/** Durable mutation-pipeline ledger for Databricks Lakebase or standard Postgres. */
|
|
361
422
|
declare class PostgresPlatformHostStore<TDb> implements RecoverablePlatformHostStore<TDb>, ApprovalPlatformHostStore<TDb>, GovernancePlatformHostStore<TDb> {
|
|
362
423
|
readonly db: TDb;
|
|
363
424
|
private readonly sql;
|
|
364
|
-
|
|
425
|
+
readonly transactionWithEvents?: AtomicMutationPlatformHostStore<TDb>["transactionWithEvents"];
|
|
426
|
+
constructor(db: TDb, sql: PlatformHostSqlClient, transactionProvider?: PostgresPlatformHostTransactionProvider<TDb>);
|
|
365
427
|
ensureSchema(): Promise<void>;
|
|
366
428
|
transaction<TResult>(run: (db: TDb) => Promise<TResult>): Promise<TResult>;
|
|
367
429
|
createActionInvocation(input: CreateActionInvocationInput): Promise<ActionInvocationRecord>;
|
|
@@ -401,4 +463,4 @@ declare function runPlatformActionWorkerCycle<TDb>(options: PlatformActionWorker
|
|
|
401
463
|
/** Poll until aborted. Each iteration is bounded and waits without busy-spinning. */
|
|
402
464
|
declare function runPlatformActionWorker<TDb>(options: PlatformActionWorkerOptions<TDb>): Promise<void>;
|
|
403
465
|
|
|
404
|
-
export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
|
466
|
+
export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, GovernanceRuntimeEvidence, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
|
|
2
2
|
|
|
3
3
|
/** Durable Host lifecycle generation, independent from the npm package version. */
|
|
4
|
-
declare const PLATFORM_HOST_CONTRACT_VERSION:
|
|
4
|
+
declare const PLATFORM_HOST_CONTRACT_VERSION: 2;
|
|
5
5
|
interface PendingAssetEvent {
|
|
6
6
|
eventType: string;
|
|
7
7
|
subjectType: string;
|
|
@@ -23,6 +23,8 @@ interface ActionInvocationRecord {
|
|
|
23
23
|
correlationId: string;
|
|
24
24
|
causationId?: string;
|
|
25
25
|
idempotencyKey?: string;
|
|
26
|
+
/** Opaque, non-secret reference used to revalidate delegated execution authority. */
|
|
27
|
+
authorizationBindingId?: string;
|
|
26
28
|
attemptCount: number;
|
|
27
29
|
leaseOwner?: string;
|
|
28
30
|
leaseExpiresAt?: Date;
|
|
@@ -118,6 +120,31 @@ interface PlatformHostStore<TDb> {
|
|
|
118
120
|
nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
|
|
119
121
|
getEntityState(tenantId: string, spaceId: string, entityType: string, entityId: string): Promise<string | undefined>;
|
|
120
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Transaction-scoped Host operations used to commit domain writes and their
|
|
125
|
+
* canonical events as one unit of work.
|
|
126
|
+
*
|
|
127
|
+
* The supplied `db` must be bound to the same database transaction as every
|
|
128
|
+
* lifecycle method on this object. Implementations must roll the whole unit
|
|
129
|
+
* back when `run` throws.
|
|
130
|
+
*/
|
|
131
|
+
interface PlatformHostMutationTransaction<TDb> {
|
|
132
|
+
readonly db: TDb;
|
|
133
|
+
appendEvent(event: AssetEventEnvelope): Promise<void>;
|
|
134
|
+
nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
|
|
135
|
+
listEvents(tenantId: string, spaceId: string): Promise<AssetEventEnvelope[]>;
|
|
136
|
+
updateActionInvocation(id: string, tenantId: string, spaceId: string, patch: Partial<Pick<ActionInvocationRecord, "status" | "result" | "error">>): Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Optional production capability for atomic domain/event persistence.
|
|
140
|
+
*
|
|
141
|
+
* A mutating Host action uses this capability when available. Stores that do
|
|
142
|
+
* not implement it retain the legacy transaction boundary for compatibility,
|
|
143
|
+
* but cannot claim atomic domain-write/event persistence.
|
|
144
|
+
*/
|
|
145
|
+
interface AtomicMutationPlatformHostStore<TDb> extends PlatformHostStore<TDb> {
|
|
146
|
+
transactionWithEvents<TResult>(run: (transaction: PlatformHostMutationTransaction<TDb>) => Promise<TResult>): Promise<TResult>;
|
|
147
|
+
}
|
|
121
148
|
interface BeginApprovalDecisionInput {
|
|
122
149
|
actionInvocationId: string;
|
|
123
150
|
tenantId: string;
|
|
@@ -180,9 +207,27 @@ interface ActionAuthorizationInput {
|
|
|
180
207
|
actorId: string;
|
|
181
208
|
actorType: ActorType;
|
|
182
209
|
}
|
|
210
|
+
type ActionExecutionReason = "initial" | "approval_resume" | "recovery";
|
|
211
|
+
/**
|
|
212
|
+
* Trusted execution-boundary authorization input.
|
|
213
|
+
*
|
|
214
|
+
* Parameters are the schema-parsed durable parameters, never caller-supplied
|
|
215
|
+
* transient values. `invocation` is the canonical record that will execute.
|
|
216
|
+
*/
|
|
217
|
+
interface ActionExecutionAuthorizationInput extends ActionAuthorizationInput {
|
|
218
|
+
actionInvocationId: string;
|
|
219
|
+
parameters: unknown;
|
|
220
|
+
invocation: Readonly<ActionInvocationRecord>;
|
|
221
|
+
executionReason: ActionExecutionReason;
|
|
222
|
+
}
|
|
183
223
|
interface PlatformHostAuthorization {
|
|
184
224
|
checkEntitlement(input: ActionAuthorizationInput): Promise<boolean>;
|
|
185
225
|
authorize(input: ActionAuthorizationInput): Promise<boolean>;
|
|
226
|
+
/**
|
|
227
|
+
* Optional final authorization immediately before policies and mutation
|
|
228
|
+
* execution. When absent, the Host reuses `authorize` at this boundary.
|
|
229
|
+
*/
|
|
230
|
+
authorizeExecution?(input: ActionExecutionAuthorizationInput): Promise<boolean>;
|
|
186
231
|
/** Optional distinct authorization boundary for human/system approval decisions. */
|
|
187
232
|
authorizeApproval?(input: ActionAuthorizationInput & {
|
|
188
233
|
decision: ActionApprovalDecision;
|
|
@@ -212,6 +257,8 @@ interface SubmitActionInput {
|
|
|
212
257
|
causationId?: string;
|
|
213
258
|
/** Stable logical command key. Reusing it returns/re-dispatches the original invocation. */
|
|
214
259
|
idempotencyKey?: string;
|
|
260
|
+
/** Opaque, non-secret authorization/admission reference persisted with the invocation. */
|
|
261
|
+
authorizationBindingId?: string;
|
|
215
262
|
}
|
|
216
263
|
interface SubmitActionResult {
|
|
217
264
|
actionInvocationId: string;
|
|
@@ -357,11 +404,26 @@ interface PlatformHostSqlResult<Row = Record<string, unknown>> {
|
|
|
357
404
|
interface PlatformHostSqlClient {
|
|
358
405
|
query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<PlatformHostSqlResult<Row>>;
|
|
359
406
|
}
|
|
407
|
+
interface PostgresPlatformHostTransactionContext<TDb> {
|
|
408
|
+
db: TDb;
|
|
409
|
+
sql: PlatformHostSqlClient;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Application-owned transaction binder.
|
|
413
|
+
*
|
|
414
|
+
* Platform Host cannot infer how an application's `TDb` is rebound to a
|
|
415
|
+
* `pg.PoolClient`. Production applications provide this adapter so domain
|
|
416
|
+
* methods and Host ledger writes share one PostgreSQL transaction.
|
|
417
|
+
*/
|
|
418
|
+
interface PostgresPlatformHostTransactionProvider<TDb> {
|
|
419
|
+
run<TResult>(run: (context: PostgresPlatformHostTransactionContext<TDb>) => Promise<TResult>): Promise<TResult>;
|
|
420
|
+
}
|
|
360
421
|
/** Durable mutation-pipeline ledger for Databricks Lakebase or standard Postgres. */
|
|
361
422
|
declare class PostgresPlatformHostStore<TDb> implements RecoverablePlatformHostStore<TDb>, ApprovalPlatformHostStore<TDb>, GovernancePlatformHostStore<TDb> {
|
|
362
423
|
readonly db: TDb;
|
|
363
424
|
private readonly sql;
|
|
364
|
-
|
|
425
|
+
readonly transactionWithEvents?: AtomicMutationPlatformHostStore<TDb>["transactionWithEvents"];
|
|
426
|
+
constructor(db: TDb, sql: PlatformHostSqlClient, transactionProvider?: PostgresPlatformHostTransactionProvider<TDb>);
|
|
365
427
|
ensureSchema(): Promise<void>;
|
|
366
428
|
transaction<TResult>(run: (db: TDb) => Promise<TResult>): Promise<TResult>;
|
|
367
429
|
createActionInvocation(input: CreateActionInvocationInput): Promise<ActionInvocationRecord>;
|
|
@@ -401,4 +463,4 @@ declare function runPlatformActionWorkerCycle<TDb>(options: PlatformActionWorker
|
|
|
401
463
|
/** Poll until aborted. Each iteration is bounded and waits without busy-spinning. */
|
|
402
464
|
declare function runPlatformActionWorker<TDb>(options: PlatformActionWorkerOptions<TDb>): Promise<void>;
|
|
403
465
|
|
|
404
|
-
export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
|
466
|
+
export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
package/dist/index.js
CHANGED
|
@@ -3,13 +3,16 @@ import { AdapterRegistry, resolveAction, createFabricId, assertGovernanceRuntime
|
|
|
3
3
|
// src/host.ts
|
|
4
4
|
|
|
5
5
|
// src/types.ts
|
|
6
|
-
var PLATFORM_HOST_CONTRACT_VERSION =
|
|
6
|
+
var PLATFORM_HOST_CONTRACT_VERSION = 2;
|
|
7
7
|
|
|
8
8
|
// src/host.ts
|
|
9
9
|
var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
10
10
|
const value = data._events;
|
|
11
11
|
return Array.isArray(value) ? value : [];
|
|
12
12
|
};
|
|
13
|
+
var RecoverableFinalizationError = class extends Error {
|
|
14
|
+
name = "RecoverableFinalizationError";
|
|
15
|
+
};
|
|
13
16
|
function createGovernedActionHost(options) {
|
|
14
17
|
const adapters = new AdapterRegistry();
|
|
15
18
|
for (const adapter of options.adapters ?? []) adapters.register(adapter);
|
|
@@ -50,7 +53,8 @@ function createGovernedActionHost(options) {
|
|
|
50
53
|
runtimeEvidence,
|
|
51
54
|
correlationId,
|
|
52
55
|
...input.causationId ? { causationId: input.causationId } : {},
|
|
53
|
-
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
|
|
56
|
+
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
57
|
+
...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
|
|
54
58
|
});
|
|
55
59
|
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
56
60
|
if (durableInvocation.id !== actionInvocationId && isTerminal(durableInvocation.status)) {
|
|
@@ -98,7 +102,7 @@ function createGovernedActionHost(options) {
|
|
|
98
102
|
);
|
|
99
103
|
return { ...executed, workflowId: durableWorkflowId };
|
|
100
104
|
}
|
|
101
|
-
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}) {
|
|
105
|
+
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
|
|
102
106
|
const loadedInvocation = await options.store.getActionInvocation(
|
|
103
107
|
actionInvocationId,
|
|
104
108
|
tenantId,
|
|
@@ -107,6 +111,7 @@ function createGovernedActionHost(options) {
|
|
|
107
111
|
if (!loadedInvocation) {
|
|
108
112
|
throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
109
113
|
}
|
|
114
|
+
const resumingRunningInvocation = loadedInvocation.status === "running";
|
|
110
115
|
let invocation = loadedInvocation;
|
|
111
116
|
if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
|
|
112
117
|
return actionResult(invocation);
|
|
@@ -208,6 +213,28 @@ function createGovernedActionHost(options) {
|
|
|
208
213
|
}
|
|
209
214
|
}
|
|
210
215
|
const authorizationInput = toAuthorizationInput(action, invocation);
|
|
216
|
+
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
|
|
217
|
+
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
218
|
+
return fail(
|
|
219
|
+
invocation,
|
|
220
|
+
"failed",
|
|
221
|
+
`Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const executionAuthorized = options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
|
|
225
|
+
...authorizationInput,
|
|
226
|
+
actionInvocationId,
|
|
227
|
+
parameters: parsed.data,
|
|
228
|
+
invocation,
|
|
229
|
+
executionReason
|
|
230
|
+
}) : await options.authorization.authorize(authorizationInput);
|
|
231
|
+
if (!executionAuthorized) {
|
|
232
|
+
return fail(
|
|
233
|
+
invocation,
|
|
234
|
+
"failed",
|
|
235
|
+
`Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
211
238
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
212
239
|
...authorizationInput,
|
|
213
240
|
declaredPolicyIds: action.policies ?? []
|
|
@@ -292,8 +319,8 @@ function createGovernedActionHost(options) {
|
|
|
292
319
|
let data;
|
|
293
320
|
let domainEvents = [];
|
|
294
321
|
try {
|
|
295
|
-
const
|
|
296
|
-
|
|
322
|
+
const runHandler = async (db, transaction) => {
|
|
323
|
+
const handlerResult = action.handler ? await action.handler(
|
|
297
324
|
{
|
|
298
325
|
actionInvocationId,
|
|
299
326
|
tenantId,
|
|
@@ -306,28 +333,39 @@ function createGovernedActionHost(options) {
|
|
|
306
333
|
services: options.services
|
|
307
334
|
},
|
|
308
335
|
parsed.data
|
|
309
|
-
) :
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
(event) => !action.emitsEvents.includes(event.eventType)
|
|
318
|
-
);
|
|
319
|
-
if (undeclaredEvent) {
|
|
320
|
-
return fail(
|
|
321
|
-
invocation,
|
|
322
|
-
"failed",
|
|
323
|
-
`${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
|
|
336
|
+
) : { success: true, data: {} };
|
|
337
|
+
if (!handlerResult.success) {
|
|
338
|
+
throw new Error(handlerResult.error ?? "Action handler failed");
|
|
339
|
+
}
|
|
340
|
+
const handlerData = handlerResult.data ?? {};
|
|
341
|
+
const events = extractEvents(handlerData);
|
|
342
|
+
const undeclaredEvent = events.find(
|
|
343
|
+
(event) => !action.emitsEvents.includes(event.eventType)
|
|
324
344
|
);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
if (undeclaredEvent) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
|
|
348
|
+
);
|
|
329
349
|
}
|
|
330
|
-
|
|
350
|
+
if (action.eventPhase !== "after_adapters") {
|
|
351
|
+
for (const [index, event] of events.entries()) {
|
|
352
|
+
await appendEvent(
|
|
353
|
+
invocation,
|
|
354
|
+
event,
|
|
355
|
+
`domain:${index}`,
|
|
356
|
+
action.version,
|
|
357
|
+
transaction
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { data: handlerData, domainEvents: events };
|
|
362
|
+
};
|
|
363
|
+
const atomicStore = asAtomicMutationStore(options.store);
|
|
364
|
+
const executed = atomicStore ? await atomicStore.transactionWithEvents(
|
|
365
|
+
(transaction) => runHandler(transaction.db, transaction)
|
|
366
|
+
) : await options.store.transaction((db) => runHandler(db));
|
|
367
|
+
data = executed.data;
|
|
368
|
+
domainEvents = executed.domainEvents;
|
|
331
369
|
} catch (error) {
|
|
332
370
|
return fail(invocation, "failed", errorMessage(error));
|
|
333
371
|
}
|
|
@@ -458,11 +496,6 @@ function createGovernedActionHost(options) {
|
|
|
458
496
|
return fail(invocation, "failed", message);
|
|
459
497
|
}
|
|
460
498
|
}
|
|
461
|
-
if (action.eventPhase === "after_adapters") {
|
|
462
|
-
for (const [index, event] of domainEvents.entries()) {
|
|
463
|
-
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
499
|
const governanceStore = asGovernanceStore(options.store);
|
|
467
500
|
if (governanceStore && options.resolvePolicyObligations) {
|
|
468
501
|
const obligations = await governanceStore.listPolicyObligations(actionInvocationId, tenantId, spaceId);
|
|
@@ -472,10 +505,43 @@ function createGovernedActionHost(options) {
|
|
|
472
505
|
}
|
|
473
506
|
}
|
|
474
507
|
const result = withoutPrivateHostFields(data, eventResultFields);
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
508
|
+
try {
|
|
509
|
+
const atomicStore = asAtomicMutationStore(options.store);
|
|
510
|
+
if (action.eventPhase === "after_adapters" && atomicStore) {
|
|
511
|
+
await atomicStore.transactionWithEvents(async (transaction) => {
|
|
512
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
513
|
+
await appendEvent(
|
|
514
|
+
invocation,
|
|
515
|
+
event,
|
|
516
|
+
`domain:${index}`,
|
|
517
|
+
action.version,
|
|
518
|
+
transaction
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
await transaction.updateActionInvocation(
|
|
522
|
+
actionInvocationId,
|
|
523
|
+
tenantId,
|
|
524
|
+
spaceId,
|
|
525
|
+
{ status: "completed", result }
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
if (action.eventPhase === "after_adapters") {
|
|
530
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
531
|
+
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
535
|
+
status: "completed",
|
|
536
|
+
result
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
} catch (error) {
|
|
540
|
+
if (action.eventPhase === "after_adapters") {
|
|
541
|
+
throw new RecoverableFinalizationError(errorMessage(error));
|
|
542
|
+
}
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
479
545
|
return {
|
|
480
546
|
actionInvocationId,
|
|
481
547
|
status: "completed",
|
|
@@ -484,6 +550,7 @@ function createGovernedActionHost(options) {
|
|
|
484
550
|
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
485
551
|
};
|
|
486
552
|
} catch (error) {
|
|
553
|
+
if (error instanceof RecoverableFinalizationError) throw error;
|
|
487
554
|
return fail(invocation, "failed", errorMessage(error));
|
|
488
555
|
}
|
|
489
556
|
}
|
|
@@ -548,7 +615,13 @@ function createGovernedActionHost(options) {
|
|
|
548
615
|
return actionResult(transitioned);
|
|
549
616
|
}
|
|
550
617
|
if (!decision.approved) return actionResult(transitioned);
|
|
551
|
-
return executeInvocation(
|
|
618
|
+
return executeInvocation(
|
|
619
|
+
actionInvocationId,
|
|
620
|
+
tenantId,
|
|
621
|
+
spaceId,
|
|
622
|
+
{ leaseOwner },
|
|
623
|
+
"approval_resume"
|
|
624
|
+
);
|
|
552
625
|
}
|
|
553
626
|
async function recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId) {
|
|
554
627
|
const governanceStore = asGovernanceStore(options.store);
|
|
@@ -582,7 +655,7 @@ function createGovernedActionHost(options) {
|
|
|
582
655
|
spaceId
|
|
583
656
|
});
|
|
584
657
|
}
|
|
585
|
-
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
|
|
658
|
+
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
|
|
586
659
|
const timestamp = now();
|
|
587
660
|
const envelope = {
|
|
588
661
|
id: lifecycleId("evt", invocation.id, deduplicationKey),
|
|
@@ -596,7 +669,7 @@ function createGovernedActionHost(options) {
|
|
|
596
669
|
actorType: invocation.actorType,
|
|
597
670
|
actionInvocationId: invocation.id,
|
|
598
671
|
payload: event.payload,
|
|
599
|
-
sequence: await options.store.nextEventSequence(
|
|
672
|
+
sequence: await (transaction ?? options.store).nextEventSequence(
|
|
600
673
|
invocation.tenantId,
|
|
601
674
|
invocation.spaceId
|
|
602
675
|
),
|
|
@@ -605,7 +678,7 @@ function createGovernedActionHost(options) {
|
|
|
605
678
|
correlationId: invocation.correlationId,
|
|
606
679
|
...invocation.causationId ? { causationId: invocation.causationId } : {}
|
|
607
680
|
};
|
|
608
|
-
await options.store.appendEvent(envelope);
|
|
681
|
+
await (transaction ?? options.store).appendEvent(envelope);
|
|
609
682
|
}
|
|
610
683
|
async function fail(invocation, status, error) {
|
|
611
684
|
await options.store.updateActionInvocation(
|
|
@@ -638,6 +711,10 @@ function asApprovalStore(store) {
|
|
|
638
711
|
const candidate = store;
|
|
639
712
|
return typeof candidate.recordHitlDecision === "function" && typeof candidate.beginApprovalDecision === "function" ? store : void 0;
|
|
640
713
|
}
|
|
714
|
+
function asAtomicMutationStore(store) {
|
|
715
|
+
const candidate = store;
|
|
716
|
+
return typeof candidate.transactionWithEvents === "function" ? store : void 0;
|
|
717
|
+
}
|
|
641
718
|
function asGovernanceStore(store) {
|
|
642
719
|
const candidate = store;
|
|
643
720
|
return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
|
|
@@ -864,13 +941,26 @@ var MemoryPlatformHostStore = class {
|
|
|
864
941
|
};
|
|
865
942
|
|
|
866
943
|
// src/postgres-store.ts
|
|
867
|
-
var PostgresPlatformHostStore = class {
|
|
868
|
-
constructor(db, sql) {
|
|
944
|
+
var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
945
|
+
constructor(db, sql, transactionProvider) {
|
|
869
946
|
this.db = db;
|
|
870
947
|
this.sql = sql;
|
|
948
|
+
if (transactionProvider) {
|
|
949
|
+
this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
|
|
950
|
+
const scoped = new _PostgresPlatformHostStore(db2, sql2);
|
|
951
|
+
return run({
|
|
952
|
+
db: db2,
|
|
953
|
+
appendEvent: (event) => scoped.appendEvent(event),
|
|
954
|
+
nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
|
|
955
|
+
listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
|
|
956
|
+
updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
|
|
957
|
+
});
|
|
958
|
+
});
|
|
959
|
+
}
|
|
871
960
|
}
|
|
872
961
|
db;
|
|
873
962
|
sql;
|
|
963
|
+
transactionWithEvents;
|
|
874
964
|
async ensureSchema() {
|
|
875
965
|
await this.sql.query(`
|
|
876
966
|
CREATE SCHEMA IF NOT EXISTS fabric_platform;
|
|
@@ -879,7 +969,8 @@ var PostgresPlatformHostStore = class {
|
|
|
879
969
|
action_id text NOT NULL, action_version integer NOT NULL,
|
|
880
970
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
881
971
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
882
|
-
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
972
|
+
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
973
|
+
authorization_binding_id text, error text,
|
|
883
974
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
884
975
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
885
976
|
hitl_reason text, hitl_policy_version text, approval_decision jsonb,
|
|
@@ -888,6 +979,7 @@ var PostgresPlatformHostStore = class {
|
|
|
888
979
|
);
|
|
889
980
|
ALTER TABLE fabric_platform.action_invocations
|
|
890
981
|
ADD COLUMN IF NOT EXISTS idempotency_key text,
|
|
982
|
+
ADD COLUMN IF NOT EXISTS authorization_binding_id text,
|
|
891
983
|
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
892
984
|
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
893
985
|
ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz,
|
|
@@ -962,8 +1054,9 @@ var PostgresPlatformHostStore = class {
|
|
|
962
1054
|
const result = await this.sql.query(
|
|
963
1055
|
`INSERT INTO fabric_platform.action_invocations
|
|
964
1056
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
965
|
-
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
966
|
-
|
|
1057
|
+
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
1058
|
+
authorization_binding_id,error,runtime_evidence,created_at,updated_at)
|
|
1059
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$17,$17)
|
|
967
1060
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
968
1061
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
969
1062
|
RETURNING *`,
|
|
@@ -981,6 +1074,7 @@ var PostgresPlatformHostStore = class {
|
|
|
981
1074
|
input.correlationId,
|
|
982
1075
|
input.causationId ?? null,
|
|
983
1076
|
input.idempotencyKey ?? null,
|
|
1077
|
+
input.authorizationBindingId ?? null,
|
|
984
1078
|
input.error ?? null,
|
|
985
1079
|
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
986
1080
|
now
|
|
@@ -1344,6 +1438,7 @@ function toActionRecord(row) {
|
|
|
1344
1438
|
correlationId: String(row.correlation_id),
|
|
1345
1439
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
1346
1440
|
...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
|
|
1441
|
+
...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
|
|
1347
1442
|
attemptCount: Number(row.attempt_count ?? 0),
|
|
1348
1443
|
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
1349
1444
|
...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
|