@fabricorg/platform-host 8.6.0 → 8.8.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 +12 -0
- package/README.md +63 -12
- package/dist/index.cjs +97 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +98 -46
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 8.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 385a989: Expose the optional execution lease owner and token in adapter execution context, using the same frozen identity as Host write fencing and omitting the key for unleased execution. Platform Host 8.8.0 is the first Host that supplies it, so with an older Host the key is also absent: an adapter that requires fencing must treat a missing lease as "cannot fence", not as "safe". Every new claim of the invocation, including a takeover by another worker, rotates the token. Adapters can check this evidence in the transaction that claims their domain row immediately before a provider call; Host does not check the lease during the adapter call.
|
|
8
|
+
|
|
9
|
+
## 8.7.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- a28154f: Register Host-owned staged invocation principals, persist their names through recovery, and dispatch children through entitlement, admission, and authorization as the registered actor. PostgreSQL migration 14 adds nullable principal evidence columns and is required before deploying 8.7.0 anywhere staged invocations are used, including principal-less stages. Run `ensureSchema()`, deploy 8.7.0 or later to every process that runs the Host (executes staging parent actions, dispatches stages, or runs the Platform worker) without principal declarations, and only then ship modules that declare principals. An older worker can claim a principal-bearing stage and dispatch it as the parent's actor because it does not read or fence the new principal column.
|
|
14
|
+
|
|
3
15
|
## 8.6.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -496,6 +496,18 @@ Expiry before an attempt prevents that attempt; an aborted call that throws foll
|
|
|
496
496
|
the existing ambiguous-outcome reconciliation path. With no step duration and an
|
|
497
497
|
unset or zero Host duration, neither context field is supplied.
|
|
498
498
|
|
|
499
|
+
When Host executes under a lease, `AdapterExecutionContext.lease` contains the
|
|
500
|
+
`leaseOwner` and `leaseToken` used to fence Host's own writes (Host 8.8.0 and
|
|
501
|
+
later). The key is absent for unleased execution and on older Hosts, so an adapter
|
|
502
|
+
that requires fencing must treat a missing lease as "cannot fence", not as "safe".
|
|
503
|
+
Every new claim of the invocation, such as a takeover by another worker, changes
|
|
504
|
+
`leaseToken`.
|
|
505
|
+
An adapter guarding a provider call should check this identity immediately before
|
|
506
|
+
that call, in the same transaction as its own domain claim (for example, by locking
|
|
507
|
+
the Host lease row and comparing owner and token). Host does not check the lease
|
|
508
|
+
during the adapter call; this context is evidence for the adapter's transaction,
|
|
509
|
+
not a guarantee that a provider operation remains protected after that transaction.
|
|
510
|
+
|
|
499
511
|
## Execution authorization
|
|
500
512
|
|
|
501
513
|
Submission authorization proves that a caller may create an invocation. Applications that delegate
|
|
@@ -1478,6 +1490,17 @@ child schema before the parent transaction commits. `redactActionParameters` run
|
|
|
1478
1490
|
the redacted, schema-valid parameters are the only parameters written to the stage record and later
|
|
1479
1491
|
used to create the child invocation.
|
|
1480
1492
|
|
|
1493
|
+
A parent action may select a Host-owned dispatch identity by declaring
|
|
1494
|
+
`stagedInvocationPrincipals`, which maps an allowlisted child action ID to a principal name. Host
|
|
1495
|
+
composition registers those names through `PlatformHostOptions.stagedInvocationPrincipals` with an
|
|
1496
|
+
`actorId` and Platform `ActorType`. A registry-backed Host refuses construction when a referenced
|
|
1497
|
+
name is absent; a resolve-only Host refuses the stage when it encounters an absent registration.
|
|
1498
|
+
The request allowlist remains exactly `key`, `actionId`, and `parameters`, so a handler or finalizer
|
|
1499
|
+
cannot supply `principal`, `actorId`, `actorType`, or an authorization binding. Host snapshots its
|
|
1500
|
+
registration at construction, records the principal name in the staged row, and resolves that
|
|
1501
|
+
durable name at dispatch. If a later Host no longer registers it, dispatch definitively rejects the
|
|
1502
|
+
stage instead of using the parent actor.
|
|
1503
|
+
|
|
1481
1504
|
Staging requires `transactionWithEvents`, a transaction-scoped `stageInvocations`, and a store whose
|
|
1482
1505
|
`transactionalStagedInvocations` flag is true. Host construction checks the advertised staged-store
|
|
1483
1506
|
operations and atomic-store capability: scoped origin lookup, lease-fenced child creation, claim,
|
|
@@ -1487,8 +1510,9 @@ execution preflight and interrupts that invocation. For `before_adapters`, the h
|
|
|
1487
1510
|
commits with the handler mutation. For `after_adapters`, handler and finalizer requests commit with
|
|
1488
1511
|
events and the completed parent verdict. A finalizing action retains its handler requests in the
|
|
1489
1512
|
durable finalization checkpoint. Replaying an identical parent batch is a no-op; any changed key,
|
|
1490
|
-
action, version evidence, parameter digest, parameter value, batch size, or batch
|
|
1491
|
-
parent to reconciliation without changing stored requests.
|
|
1513
|
+
action, version evidence, parameter digest, parameter value, principal name, batch size, or batch
|
|
1514
|
+
digest moves the parent to reconciliation without changing stored requests. Principal-less batches
|
|
1515
|
+
retain their earlier canonical identity and digest.
|
|
1492
1516
|
|
|
1493
1517
|
A pending stage is claimable only while its immutable parent scope matches and the parent is
|
|
1494
1518
|
`completed`. Parents ending `failed`, `blocked_by_policy`, or `validation_failed` have pending stages
|
|
@@ -1500,29 +1524,56 @@ contains an action with a non-empty `stagesInvocations` allowlist. `dispatchStag
|
|
|
1500
1524
|
forces a sweep and `dispatchStagedInvocations: false` disables it. A deployment that has no staging
|
|
1501
1525
|
declarations therefore does not query the migration-11 tables by default.
|
|
1502
1526
|
|
|
1503
|
-
The child has a Host-only `stagedOrigin` of parent invocation ID plus stage key,
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1527
|
+
The child has a Host-only `stagedOrigin` of parent invocation ID plus stage key and, when selected,
|
|
1528
|
+
the durable principal name. `authorizeExecution` can inspect `invocation.stagedOrigin.principal`
|
|
1529
|
+
alongside the child's durable parameters and actor. Public `SubmitActionInput` has no
|
|
1530
|
+
`stagedOrigin`; Host adds it through the internal staged-dispatch path.
|
|
1531
|
+
The lookup and unique index intentionally remain keyed by tenant, space, parent invocation ID, and
|
|
1532
|
+
stage key: one parent stage key identifies one child, while the principal is evidence for that
|
|
1533
|
+
durable intent rather than a second child identity. The child carries no idempotency key, so a public
|
|
1534
|
+
caller cannot pre-claim its identity with a staged-looking string. A crash after parent commit is
|
|
1535
|
+
recovered by a later sweep. A crash after child creation but before stage settlement resolves the
|
|
1536
|
+
same child, including its principal evidence, by `stagedOrigin`. PostgreSQL migration 11 creates the
|
|
1537
|
+
staged tables and origin index; migration 14 adds nullable `principal` and
|
|
1538
|
+
`staged_origin_principal` columns for the staged record and child invocation.
|
|
1539
|
+
|
|
1540
|
+
Migration 14 is a deployment prerequisite for this Host version wherever staged invocations are
|
|
1541
|
+
used, including stages that do not select a principal: the staging and fenced child-insert SQL name
|
|
1542
|
+
the new nullable columns in both cases. Use this rollout order for a shared PostgreSQL database:
|
|
1543
|
+
|
|
1544
|
+
1. Run `ensureSchema()` and confirm migration 14 (`staged_invocation_principals`) is recorded.
|
|
1545
|
+
2. Deploy `@fabricorg/platform-host` 8.7.0 or later to every process that runs this Host: processes
|
|
1546
|
+
that execute staging parent actions (including inline execution inside `submitAction`), run
|
|
1547
|
+
`dispatchStagedInvocations`, or run the Platform worker. Keep module principal declarations
|
|
1548
|
+
disabled during this step. An older Host that executes a parent ignores the declaration and
|
|
1549
|
+
stages the child without a principal, so every later dispatch uses the parent's actor.
|
|
1550
|
+
3. After those processes are upgraded, ship modules that declare `stagedInvocationPrincipals`.
|
|
1551
|
+
|
|
1552
|
+
An older worker can claim a principal-bearing row because its claim query does not filter on the
|
|
1553
|
+
new column. It then reads no principal evidence and can submit the child as the parent's actor; its
|
|
1554
|
+
older fenced insert also does not compare the principal. Depending on which version handles the stage, it can be
|
|
1555
|
+
rejected after its intent is lost or the child can complete under the wrong actor. Running migration
|
|
1556
|
+
14 before step 2 also matters for principal-less stages because this Host version names the columns
|
|
1557
|
+
when staging and dispatching them.
|
|
1509
1558
|
|
|
1510
1559
|
Each claimed row goes through one settlement sequence (`dispatchStagedRow` in `src/host.ts`):
|
|
1511
1560
|
|
|
1512
1561
|
1. Host renews the claim's lease, skipping the row if the lease is gone, and looks the child up by
|
|
1513
|
-
`stagedOrigin` in the parent's tenant and space.
|
|
1562
|
+
`stagedOrigin` in the parent's tenant and space. Each claim replaces the lease token, so a
|
|
1514
1563
|
creation still in flight from an earlier attempt holds an older token and fails the creation
|
|
1515
1564
|
fence; this lookup therefore settles what earlier attempts did. A child found here goes to step 4
|
|
1516
1565
|
without the pre-submission checks or any entitlement, authorization, or admission call.
|
|
1517
1566
|
2. With no child, Host checks that the parent is still `completed` and that the child action is
|
|
1518
1567
|
still registered, does not require capture-time authority, and accepts the stored parameters. It
|
|
1519
|
-
|
|
1520
|
-
`submissionTimeoutMs`.
|
|
1568
|
+
resolves any recorded principal against the current Host registration, then submits through the
|
|
1569
|
+
shared submission pipeline under the claim's lease token, bounded by `submissionTimeoutMs`.
|
|
1570
|
+
Entitlement, submission authorization, admission, and execution authorization all evaluate the
|
|
1571
|
+
registered principal just as they do for any other submission.
|
|
1521
1572
|
3. If that submission's own fenced create returned the child, Host settles with its ID: `dispatched`
|
|
1522
1573
|
when the handoff succeeded or no dispatcher is configured, otherwise a retry that records the ID
|
|
1523
1574
|
(`dead_lettered` once the attempt budget is spent). In every other case (a failed pre-submission
|
|
1524
1575
|
check, a refusal, an error, or a timeout) Host first rotates the lease token with
|
|
1525
|
-
`rotateStagedInvocationLease`. The
|
|
1576
|
+
`rotateStagedInvocationLease`. The remaining possible creator is this attempt's own timed-out
|
|
1526
1577
|
submission, which still holds the old token, so after the rotation it can no longer create the
|
|
1527
1578
|
child and the lookup that follows is authoritative. A child found there goes to step 4. With none,
|
|
1528
1579
|
a definitive refusal settles `rejected`, and anything else settles for retry or, at the attempt
|
package/dist/index.cjs
CHANGED
|
@@ -1375,6 +1375,7 @@ const MAX_STAGED_INVOCATION_KEY_LENGTH = 128;
|
|
|
1375
1375
|
const MAX_STAGED_INVOCATION_BYTES = 65536;
|
|
1376
1376
|
const MAX_STAGED_INVOCATION_BATCH_BYTES = 262144;
|
|
1377
1377
|
const STAGED_INVOCATION_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
1378
|
+
const STAGED_INVOCATION_PRINCIPAL_PATTERN = /^[a-z][a-z0-9._:-]{0,127}$/;
|
|
1378
1379
|
/** Validate and normalize one immutable batch before its parent transaction commits. */
|
|
1379
1380
|
function prepareStagedInvocationBatch(input) {
|
|
1380
1381
|
if (!Array.isArray(input.requests)) throw new Error("stagedInvocations must be an array.");
|
|
@@ -1394,6 +1395,8 @@ function prepareStagedInvocationBatch(input) {
|
|
|
1394
1395
|
if (!(input.parentAction.stagesInvocations ?? []).includes(request.actionId)) throw new Error(`Action "${input.parentAction.actionId}" may not stage "${String(request.actionId)}".`);
|
|
1395
1396
|
const child = input.resolveAction(request.actionId);
|
|
1396
1397
|
if (!child) throw new Error(`Staged action "${String(request.actionId)}" is not registered.`);
|
|
1398
|
+
const principal = input.parentAction.stagedInvocationPrincipals?.[child.actionId];
|
|
1399
|
+
if (principal !== void 0 && !Object.hasOwn(input.stagedInvocationPrincipals ?? {}, principal)) throw new Error(`Staged invocation principal "${principal}" is not registered with this Platform Host.`);
|
|
1397
1400
|
const parameters = finalizationJson(request.parameters);
|
|
1398
1401
|
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) throw new Error(`stagedInvocations[${index}] parameters must be a JSON object.`);
|
|
1399
1402
|
if (!child.schema.safeParse(parameters).success) throw new Error(`stagedInvocations[${index}] parameters do not match action "${child.actionId}".`);
|
|
@@ -1408,24 +1411,27 @@ function prepareStagedInvocationBatch(input) {
|
|
|
1408
1411
|
if (byteLength((0, _fabricorg_platform.canonicalJson)({
|
|
1409
1412
|
key: request.key,
|
|
1410
1413
|
actionId: child.actionId,
|
|
1411
|
-
parameters: durableParameters
|
|
1414
|
+
parameters: durableParameters,
|
|
1415
|
+
...principal !== void 0 ? { principal } : {}
|
|
1412
1416
|
})) > 65536) throw new Error(`stagedInvocations[${index}] exceeds ${MAX_STAGED_INVOCATION_BYTES} bytes.`);
|
|
1413
1417
|
return {
|
|
1414
1418
|
key: request.key,
|
|
1415
1419
|
actionId: child.actionId,
|
|
1416
1420
|
expectedActionVersion: child.version,
|
|
1417
1421
|
parameters: durableParameters,
|
|
1418
|
-
parameterDigest: digestParameters(durableParameters)
|
|
1422
|
+
parameterDigest: digestParameters(durableParameters),
|
|
1423
|
+
...principal !== void 0 ? { principal } : {}
|
|
1419
1424
|
};
|
|
1420
1425
|
});
|
|
1421
1426
|
for (const [index, request] of normalized.entries()) if (normalized.findIndex((candidate) => candidate.key === request.key) !== index) throw new Error(`stagedInvocations contains duplicate key "${request.key}".`);
|
|
1422
1427
|
normalized.sort((left, right) => compareText(left.key, right.key) || compareText(left.actionId, right.actionId));
|
|
1423
|
-
const batchIdentity = normalized.map(({ key, actionId, expectedActionVersion, parameters, parameterDigest }) => ({
|
|
1428
|
+
const batchIdentity = normalized.map(({ key, actionId, expectedActionVersion, parameters, parameterDigest, principal }) => ({
|
|
1424
1429
|
key,
|
|
1425
1430
|
actionId,
|
|
1426
1431
|
expectedActionVersion,
|
|
1427
1432
|
parameters,
|
|
1428
|
-
parameterDigest
|
|
1433
|
+
parameterDigest,
|
|
1434
|
+
...principal !== void 0 ? { principal } : {}
|
|
1429
1435
|
}));
|
|
1430
1436
|
if (byteLength((0, _fabricorg_platform.canonicalJson)(batchIdentity)) > 262144) throw new Error(`stagedInvocations batch exceeds ${MAX_STAGED_INVOCATION_BATCH_BYTES} bytes.`);
|
|
1431
1437
|
const batchDigest = digestParameters({
|
|
@@ -1460,7 +1466,8 @@ function stagedInvocationRequestIdentity(record) {
|
|
|
1460
1466
|
expectedActionVersion: record.expectedActionVersion,
|
|
1461
1467
|
parameters: record.parameters,
|
|
1462
1468
|
parameterDigest: record.parameterDigest,
|
|
1463
|
-
batchDigest: record.batchDigest
|
|
1469
|
+
batchDigest: record.batchDigest,
|
|
1470
|
+
...record.principal !== void 0 ? { principal: record.principal } : {}
|
|
1464
1471
|
});
|
|
1465
1472
|
}
|
|
1466
1473
|
function cloneStagedInvocationRecord(record) {
|
|
@@ -1527,6 +1534,20 @@ const STAGED_ERROR = Object.freeze({
|
|
|
1527
1534
|
lookup: "Staged child lookup failed."
|
|
1528
1535
|
});
|
|
1529
1536
|
function createGovernedActionHost(options) {
|
|
1537
|
+
const configuredStagedPrincipals = options.stagedInvocationPrincipals;
|
|
1538
|
+
if (configuredStagedPrincipals !== void 0 && (!configuredStagedPrincipals || typeof configuredStagedPrincipals !== "object" || Array.isArray(configuredStagedPrincipals))) throw new Error("stagedInvocationPrincipals must be an object.");
|
|
1539
|
+
const stagedInvocationPrincipals = Object.freeze(Object.fromEntries(Object.entries(configuredStagedPrincipals ?? {}).map(([name, candidate]) => {
|
|
1540
|
+
if (!STAGED_INVOCATION_PRINCIPAL_PATTERN.test(name)) throw new Error(`Invalid staged invocation principal name: ${name}`);
|
|
1541
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) throw new Error(`Staged invocation principal "${name}" must be an object.`);
|
|
1542
|
+
const actorId = candidate.actorId;
|
|
1543
|
+
const actorType = candidate.actorType;
|
|
1544
|
+
if (typeof actorId !== "string" || actorId.length === 0) throw new Error(`Staged invocation principal "${name}" actorId must be a non-empty string.`);
|
|
1545
|
+
if (typeof actorType !== "string" || !(0, _fabricorg_platform.isActorType)(actorType)) throw new Error(`Staged invocation principal "${name}" has invalid actorType.`);
|
|
1546
|
+
return [name, Object.freeze({
|
|
1547
|
+
actorId,
|
|
1548
|
+
actorType
|
|
1549
|
+
})];
|
|
1550
|
+
})));
|
|
1530
1551
|
if (options.approvalDefaultTtlMs !== void 0 && (!Number.isSafeInteger(options.approvalDefaultTtlMs) || options.approvalDefaultTtlMs <= 0)) throw new Error("approvalDefaultTtlMs must be a positive safe integer in milliseconds.");
|
|
1531
1552
|
if (options.approvalLifecycleEvents) {
|
|
1532
1553
|
const approvalStore = asApprovalStore(options.store);
|
|
@@ -1560,6 +1581,9 @@ function createGovernedActionHost(options) {
|
|
|
1560
1581
|
const configuredActionResolver = options.resolveAction ?? options.registry?.resolveAction;
|
|
1561
1582
|
if (!configuredActionResolver) throw new Error("Platform Host requires an explicit module registry or resolveAction function.");
|
|
1562
1583
|
const actionResolver = configuredActionResolver;
|
|
1584
|
+
if (options.registry?.orderedModules) {
|
|
1585
|
+
for (const module of options.registry.orderedModules) for (const action of module.actions ?? []) for (const principal of Object.values(action.stagedInvocationPrincipals ?? {})) if (!Object.hasOwn(stagedInvocationPrincipals, principal)) throw new Error(`Staged invocation principal "${principal}" referenced by action "${action.actionId}" is not registered with this Platform Host.`);
|
|
1586
|
+
}
|
|
1563
1587
|
const stagedStore = asStagedInvocationStore(options.store);
|
|
1564
1588
|
const declaresStaging = options.registry?.orderedModules?.some((module) => (module.actions ?? []).some((action) => (action.stagesInvocations?.length ?? 0) > 0)) ?? false;
|
|
1565
1589
|
if (declaresStaging && (!stagedStore || !asAtomicMutationStore(options.store))) throw new Error("Staged descendant invocations require a store with transactionalStagedInvocations and transactionWithEvents capabilities.");
|
|
@@ -2703,6 +2727,7 @@ function createGovernedActionHost(options) {
|
|
|
2703
2727
|
parentAction: action,
|
|
2704
2728
|
requests: rawStagedInvocations,
|
|
2705
2729
|
resolveAction: actionResolver,
|
|
2730
|
+
stagedInvocationPrincipals,
|
|
2706
2731
|
redactParameters: options.redactActionParameters,
|
|
2707
2732
|
now: now()
|
|
2708
2733
|
});
|
|
@@ -3123,6 +3148,7 @@ function createGovernedActionHost(options) {
|
|
|
3123
3148
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
3124
3149
|
attempt,
|
|
3125
3150
|
maxAttempts,
|
|
3151
|
+
...executionLease ? { lease: executionLease } : {},
|
|
3126
3152
|
...deadlineMs ? { deadlineMs } : {},
|
|
3127
3153
|
...adapterController ? { signal: adapterController.signal } : {}
|
|
3128
3154
|
});
|
|
@@ -3574,6 +3600,7 @@ function createGovernedActionHost(options) {
|
|
|
3574
3600
|
parentAction: action,
|
|
3575
3601
|
requests: stagedRequests,
|
|
3576
3602
|
resolveAction: actionResolver,
|
|
3603
|
+
stagedInvocationPrincipals,
|
|
3577
3604
|
redactParameters: options.redactActionParameters,
|
|
3578
3605
|
redactedKeys: handlerStagedInvocations.map((request) => request.key),
|
|
3579
3606
|
now: now()
|
|
@@ -4144,6 +4171,8 @@ function createGovernedActionHost(options) {
|
|
|
4144
4171
|
if (!parent || parent.status !== "completed") throw new Error("Staged parent is not completed.");
|
|
4145
4172
|
const child = actionResolver(record.actionId);
|
|
4146
4173
|
if (!child) throw new StagedDispatchRejectionError("Staged action is no longer registered.");
|
|
4174
|
+
const principal = record.principal !== void 0 && Object.hasOwn(stagedInvocationPrincipals, record.principal) ? stagedInvocationPrincipals[record.principal] : void 0;
|
|
4175
|
+
if (record.principal !== void 0 && !principal) throw new StagedDispatchRejectionError("Staged invocation principal is no longer registered.");
|
|
4147
4176
|
if (child.execution?.authorityMoment === "capture") throw new StagedDispatchRejectionError("Staged action now requires capture-time authority.");
|
|
4148
4177
|
if (!child.schema.safeParse(structuredClone(record.parameters)).success) throw new StagedDispatchRejectionError("Staged parameters no longer match the registered action.");
|
|
4149
4178
|
return submitActionInternal({
|
|
@@ -4151,8 +4180,8 @@ function createGovernedActionHost(options) {
|
|
|
4151
4180
|
parameters: structuredClone(record.parameters),
|
|
4152
4181
|
tenantId: parent.tenantId,
|
|
4153
4182
|
spaceId: parent.spaceId,
|
|
4154
|
-
actorId: parent.actorId,
|
|
4155
|
-
actorType: parent.actorType,
|
|
4183
|
+
actorId: principal?.actorId ?? parent.actorId,
|
|
4184
|
+
actorType: principal?.actorType ?? parent.actorType,
|
|
4156
4185
|
correlationId: parent.correlationId,
|
|
4157
4186
|
causationId: parent.id,
|
|
4158
4187
|
provenance: {
|
|
@@ -4166,7 +4195,8 @@ function createGovernedActionHost(options) {
|
|
|
4166
4195
|
id: record.id,
|
|
4167
4196
|
origin: {
|
|
4168
4197
|
parentInvocationId: parent.id,
|
|
4169
|
-
stageKey: record.key
|
|
4198
|
+
stageKey: record.key,
|
|
4199
|
+
...record.principal !== void 0 ? { principal: record.principal } : {}
|
|
4170
4200
|
},
|
|
4171
4201
|
workerId: lease.workerId,
|
|
4172
4202
|
leaseToken: lease.leaseToken,
|
|
@@ -4836,7 +4866,7 @@ var MemoryPlatformHostStore = class {
|
|
|
4836
4866
|
kind: "existing",
|
|
4837
4867
|
invocation: existing
|
|
4838
4868
|
};
|
|
4839
|
-
if (!this.stagedInvocations.find((record) => record.id === input.stageId && record.tenantId === invocation.tenantId && record.spaceId === invocation.spaceId && record.parentInvocationId === invocation.stagedOrigin.parentInvocationId && record.key === invocation.stagedOrigin.stageKey && record.actionId === invocation.actionId && record.status === "pending" && record.leaseOwner === input.workerId && record.leaseToken === input.leaseToken && record.leaseExpiresAt !== void 0 && record.leaseExpiresAt > now)) return { kind: "lease_lost" };
|
|
4869
|
+
if (!this.stagedInvocations.find((record) => record.id === input.stageId && record.tenantId === invocation.tenantId && record.spaceId === invocation.spaceId && record.parentInvocationId === invocation.stagedOrigin.parentInvocationId && record.key === invocation.stagedOrigin.stageKey && record.actionId === invocation.actionId && record.principal === invocation.stagedOrigin.principal && record.status === "pending" && record.leaseOwner === input.workerId && record.leaseToken === input.leaseToken && record.leaseExpiresAt !== void 0 && record.leaseExpiresAt > now)) return { kind: "lease_lost" };
|
|
4840
4870
|
const createdAt = /* @__PURE__ */ new Date();
|
|
4841
4871
|
const record = {
|
|
4842
4872
|
...invocation,
|
|
@@ -5684,6 +5714,16 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5684
5714
|
version: 13,
|
|
5685
5715
|
name: "agent_proposal",
|
|
5686
5716
|
sql: `ALTER TABLE fabric_platform.action_invocations ADD COLUMN IF NOT EXISTS agent_proposal jsonb;`
|
|
5717
|
+
},
|
|
5718
|
+
{
|
|
5719
|
+
version: 14,
|
|
5720
|
+
name: "staged_invocation_principals",
|
|
5721
|
+
sql: `
|
|
5722
|
+
ALTER TABLE fabric_platform.staged_invocations
|
|
5723
|
+
ADD COLUMN IF NOT EXISTS principal text;
|
|
5724
|
+
ALTER TABLE fabric_platform.action_invocations
|
|
5725
|
+
ADD COLUMN IF NOT EXISTS staged_origin_principal text;
|
|
5726
|
+
`
|
|
5687
5727
|
}
|
|
5688
5728
|
]);
|
|
5689
5729
|
}
|
|
@@ -5748,9 +5788,9 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5748
5788
|
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
5749
5789
|
const admitted = invocation.admittedAt !== void 0;
|
|
5750
5790
|
const admittedColumn = admitted ? ",admitted_at" : "";
|
|
5751
|
-
const admittedValue = admitted ? ",$
|
|
5791
|
+
const admittedValue = admitted ? ",$31" : "";
|
|
5752
5792
|
const proposalColumn = invocation.agentProposal !== void 0 ? ",agent_proposal" : "";
|
|
5753
|
-
const proposalValue = invocation.agentProposal !== void 0 ? `,$${admitted ?
|
|
5793
|
+
const proposalValue = invocation.agentProposal !== void 0 ? `,$${admitted ? 32 : 31}::jsonb` : "";
|
|
5754
5794
|
const values = [
|
|
5755
5795
|
invocation.id,
|
|
5756
5796
|
invocation.tenantId,
|
|
@@ -5777,6 +5817,7 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5777
5817
|
invocation.runtimeEvidence ? JSON.stringify(invocation.runtimeEvidence) : null,
|
|
5778
5818
|
invocation.stagedOrigin.parentInvocationId,
|
|
5779
5819
|
invocation.stagedOrigin.stageKey,
|
|
5820
|
+
invocation.stagedOrigin.principal ?? null,
|
|
5780
5821
|
input.stageId,
|
|
5781
5822
|
input.workerId,
|
|
5782
5823
|
input.leaseToken,
|
|
@@ -5789,10 +5830,11 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5789
5830
|
result = await this.sql.query(`WITH fence AS (
|
|
5790
5831
|
SELECT stage.id
|
|
5791
5832
|
FROM fabric_platform.staged_invocations AS stage
|
|
5792
|
-
WHERE stage.id=$
|
|
5833
|
+
WHERE stage.id=$27 AND stage.tenant_id=$2 AND stage.space_id=$3
|
|
5793
5834
|
AND stage.parent_invocation_id=$24 AND stage.stage_key=$25 AND stage.action_id=$4
|
|
5794
|
-
AND stage.
|
|
5795
|
-
AND stage.
|
|
5835
|
+
AND stage.principal IS NOT DISTINCT FROM $26
|
|
5836
|
+
AND stage.status='pending' AND stage.lease_owner=$28 AND stage.lease_token=$29
|
|
5837
|
+
AND stage.lease_expires_at IS NOT NULL AND stage.lease_expires_at > $30
|
|
5796
5838
|
FOR SHARE
|
|
5797
5839
|
)
|
|
5798
5840
|
INSERT INTO fabric_platform.action_invocations
|
|
@@ -5800,17 +5842,19 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5800
5842
|
parameters,result,correlation_id,causation_id,idempotency_key,parameter_digest,
|
|
5801
5843
|
parameter_digest_algorithm,idempotency_actor_id,idempotency_authorization_binding_id,
|
|
5802
5844
|
invocation_provenance,authorization_binding,execution_reason,authorization_binding_id,error,
|
|
5803
|
-
runtime_evidence,staged_parent_invocation_id,staged_stage_key${admittedColumn}${proposalColumn},created_at,updated_at)
|
|
5845
|
+
runtime_evidence,staged_parent_invocation_id,staged_stage_key,staged_origin_principal${admittedColumn}${proposalColumn},created_at,updated_at)
|
|
5804
5846
|
SELECT $1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,
|
|
5805
|
-
$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$25${admittedValue}${proposalValue},$
|
|
5847
|
+
$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$25,$26${admittedValue}${proposalValue},$30,$30
|
|
5806
5848
|
FROM fence
|
|
5807
5849
|
ON CONFLICT (tenant_id,space_id,staged_parent_invocation_id,staged_stage_key)
|
|
5808
5850
|
WHERE staged_parent_invocation_id IS NOT NULL AND staged_stage_key IS NOT NULL
|
|
5809
5851
|
DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
5810
5852
|
RETURNING *`, values);
|
|
5811
5853
|
} catch (error) {
|
|
5812
|
-
|
|
5813
|
-
if (
|
|
5854
|
+
const databaseError = error;
|
|
5855
|
+
if (invocation.agentProposal !== void 0 && databaseError?.code === "42703" && typeof databaseError.message === "string" && /\bagent_proposal\b/.test(databaseError.message)) throw new Error("Agent proposals require PostgreSQL migration 13 (agent_proposal); run ensureSchema().", { cause: error });
|
|
5856
|
+
if (databaseError?.code === "42703") throw new Error("A staged child needs PostgreSQL migration 14 (staged_invocation_principals); run ensureSchema() before dispatching staged invocations.", { cause: error });
|
|
5857
|
+
if (databaseError?.code === "42P01") throw new Error("A staged child needs PostgreSQL migration 11 (staged_descendant_invocations); run ensureSchema() before dispatching staged invocations.", { cause: error });
|
|
5814
5858
|
throw error;
|
|
5815
5859
|
}
|
|
5816
5860
|
if (result.rows[0]) {
|
|
@@ -5852,33 +5896,39 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5852
5896
|
}
|
|
5853
5897
|
for (const record of records) {
|
|
5854
5898
|
if (record.tenantId !== first.tenantId || record.spaceId !== first.spaceId || record.parentInvocationId !== first.parentInvocationId || record.batchDigest !== first.batchDigest) throw new Error(`Invalid staged invocation batch for parent ${first.parentInvocationId}.`);
|
|
5855
|
-
|
|
5856
|
-
|
|
5899
|
+
try {
|
|
5900
|
+
await this.sql.query(`INSERT INTO fabric_platform.staged_invocations
|
|
5901
|
+
(id,tenant_id,space_id,parent_invocation_id,parent_action_id,stage_key,action_id,principal,
|
|
5857
5902
|
expected_action_version,parameters,parameter_digest,batch_digest,status,child_invocation_id,
|
|
5858
5903
|
attempt_count,available_at,lease_owner,lease_expires_at,lease_token,last_error,created_at,updated_at)
|
|
5859
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5904
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)`, [
|
|
5905
|
+
record.id,
|
|
5906
|
+
record.tenantId,
|
|
5907
|
+
record.spaceId,
|
|
5908
|
+
record.parentInvocationId,
|
|
5909
|
+
record.parentActionId,
|
|
5910
|
+
record.key,
|
|
5911
|
+
record.actionId,
|
|
5912
|
+
record.principal ?? null,
|
|
5913
|
+
record.expectedActionVersion,
|
|
5914
|
+
JSON.stringify(record.parameters),
|
|
5915
|
+
record.parameterDigest,
|
|
5916
|
+
record.batchDigest,
|
|
5917
|
+
record.status,
|
|
5918
|
+
record.childInvocationId ?? null,
|
|
5919
|
+
record.attemptCount,
|
|
5920
|
+
record.availableAt,
|
|
5921
|
+
record.leaseOwner ?? null,
|
|
5922
|
+
record.leaseExpiresAt ?? null,
|
|
5923
|
+
record.leaseToken,
|
|
5924
|
+
record.lastError ?? null,
|
|
5925
|
+
record.createdAt,
|
|
5926
|
+
record.updatedAt
|
|
5927
|
+
]);
|
|
5928
|
+
} catch (error) {
|
|
5929
|
+
if (error?.code === "42703") throw new Error("Staging an invocation needs PostgreSQL migration 14 (staged_invocation_principals); run ensureSchema() before staging invocations.", { cause: error });
|
|
5930
|
+
throw error;
|
|
5931
|
+
}
|
|
5882
5932
|
}
|
|
5883
5933
|
}
|
|
5884
5934
|
async getActionInvocation(id, tenantId, spaceId) {
|
|
@@ -6899,7 +6949,8 @@ function toActionRecord(row) {
|
|
|
6899
6949
|
...row.idempotency_authorization_binding_id ? { idempotencyAuthorizationBindingId: String(row.idempotency_authorization_binding_id) } : {},
|
|
6900
6950
|
...row.staged_parent_invocation_id && row.staged_stage_key ? { stagedOrigin: {
|
|
6901
6951
|
parentInvocationId: String(row.staged_parent_invocation_id),
|
|
6902
|
-
stageKey: String(row.staged_stage_key)
|
|
6952
|
+
stageKey: String(row.staged_stage_key),
|
|
6953
|
+
...row.staged_origin_principal ? { principal: String(row.staged_origin_principal) } : {}
|
|
6903
6954
|
} } : {},
|
|
6904
6955
|
...row.invocation_provenance ? { provenance: row.invocation_provenance } : {},
|
|
6905
6956
|
...row.authorization_binding ? { authorizationBinding: row.authorization_binding } : {},
|
|
@@ -6945,6 +6996,7 @@ function toStagedInvocationRecord(row) {
|
|
|
6945
6996
|
parentActionId: String(row.parent_action_id),
|
|
6946
6997
|
key: String(row.stage_key),
|
|
6947
6998
|
actionId: String(row.action_id),
|
|
6999
|
+
...row.principal ? { principal: String(row.principal) } : {},
|
|
6948
7000
|
expectedActionVersion: Number(row.expected_action_version),
|
|
6949
7001
|
parameters: row.parameters,
|
|
6950
7002
|
parameterDigest: String(row.parameter_digest),
|
|
@@ -8444,6 +8496,7 @@ exports.PLATFORM_HOST_HEALTH_CONTRACT_VERSION = PLATFORM_HOST_HEALTH_CONTRACT_VE
|
|
|
8444
8496
|
exports.PLATFORM_HOST_METRIC_NAMES = PLATFORM_HOST_METRIC_NAMES;
|
|
8445
8497
|
exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
|
|
8446
8498
|
exports.STAGED_INVOCATION_KEY_PATTERN = STAGED_INVOCATION_KEY_PATTERN;
|
|
8499
|
+
exports.STAGED_INVOCATION_PRINCIPAL_PATTERN = STAGED_INVOCATION_PRINCIPAL_PATTERN;
|
|
8447
8500
|
exports.SubmissionRefusalError = SubmissionRefusalError;
|
|
8448
8501
|
exports.applyPostgresMigrations = applyPostgresMigrations;
|
|
8449
8502
|
exports.asStagedInvocationStore = asStagedInvocationStore;
|