@fabricorg/platform-host 8.6.0 → 8.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 +6 -0
- package/README.md +51 -12
- package/dist/index.cjs +96 -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 +97 -46
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 8.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
3
9
|
## 8.6.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1478,6 +1478,17 @@ child schema before the parent transaction commits. `redactActionParameters` run
|
|
|
1478
1478
|
the redacted, schema-valid parameters are the only parameters written to the stage record and later
|
|
1479
1479
|
used to create the child invocation.
|
|
1480
1480
|
|
|
1481
|
+
A parent action may select a Host-owned dispatch identity by declaring
|
|
1482
|
+
`stagedInvocationPrincipals`, which maps an allowlisted child action ID to a principal name. Host
|
|
1483
|
+
composition registers those names through `PlatformHostOptions.stagedInvocationPrincipals` with an
|
|
1484
|
+
`actorId` and Platform `ActorType`. A registry-backed Host refuses construction when a referenced
|
|
1485
|
+
name is absent; a resolve-only Host refuses the stage when it encounters an absent registration.
|
|
1486
|
+
The request allowlist remains exactly `key`, `actionId`, and `parameters`, so a handler or finalizer
|
|
1487
|
+
cannot supply `principal`, `actorId`, `actorType`, or an authorization binding. Host snapshots its
|
|
1488
|
+
registration at construction, records the principal name in the staged row, and resolves that
|
|
1489
|
+
durable name at dispatch. If a later Host no longer registers it, dispatch definitively rejects the
|
|
1490
|
+
stage instead of using the parent actor.
|
|
1491
|
+
|
|
1481
1492
|
Staging requires `transactionWithEvents`, a transaction-scoped `stageInvocations`, and a store whose
|
|
1482
1493
|
`transactionalStagedInvocations` flag is true. Host construction checks the advertised staged-store
|
|
1483
1494
|
operations and atomic-store capability: scoped origin lookup, lease-fenced child creation, claim,
|
|
@@ -1487,8 +1498,9 @@ execution preflight and interrupts that invocation. For `before_adapters`, the h
|
|
|
1487
1498
|
commits with the handler mutation. For `after_adapters`, handler and finalizer requests commit with
|
|
1488
1499
|
events and the completed parent verdict. A finalizing action retains its handler requests in the
|
|
1489
1500
|
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.
|
|
1501
|
+
action, version evidence, parameter digest, parameter value, principal name, batch size, or batch
|
|
1502
|
+
digest moves the parent to reconciliation without changing stored requests. Principal-less batches
|
|
1503
|
+
retain their earlier canonical identity and digest.
|
|
1492
1504
|
|
|
1493
1505
|
A pending stage is claimable only while its immutable parent scope matches and the parent is
|
|
1494
1506
|
`completed`. Parents ending `failed`, `blocked_by_policy`, or `validation_failed` have pending stages
|
|
@@ -1500,29 +1512,56 @@ contains an action with a non-empty `stagesInvocations` allowlist. `dispatchStag
|
|
|
1500
1512
|
forces a sweep and `dispatchStagedInvocations: false` disables it. A deployment that has no staging
|
|
1501
1513
|
declarations therefore does not query the migration-11 tables by default.
|
|
1502
1514
|
|
|
1503
|
-
The child has a Host-only `stagedOrigin` of parent invocation ID plus stage key,
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1515
|
+
The child has a Host-only `stagedOrigin` of parent invocation ID plus stage key and, when selected,
|
|
1516
|
+
the durable principal name. `authorizeExecution` can inspect `invocation.stagedOrigin.principal`
|
|
1517
|
+
alongside the child's durable parameters and actor. Public `SubmitActionInput` has no
|
|
1518
|
+
`stagedOrigin`; Host adds it through the internal staged-dispatch path.
|
|
1519
|
+
The lookup and unique index intentionally remain keyed by tenant, space, parent invocation ID, and
|
|
1520
|
+
stage key: one parent stage key identifies one child, while the principal is evidence for that
|
|
1521
|
+
durable intent rather than a second child identity. The child carries no idempotency key, so a public
|
|
1522
|
+
caller cannot pre-claim its identity with a staged-looking string. A crash after parent commit is
|
|
1523
|
+
recovered by a later sweep. A crash after child creation but before stage settlement resolves the
|
|
1524
|
+
same child, including its principal evidence, by `stagedOrigin`. PostgreSQL migration 11 creates the
|
|
1525
|
+
staged tables and origin index; migration 14 adds nullable `principal` and
|
|
1526
|
+
`staged_origin_principal` columns for the staged record and child invocation.
|
|
1527
|
+
|
|
1528
|
+
Migration 14 is a deployment prerequisite for this Host version wherever staged invocations are
|
|
1529
|
+
used, including stages that do not select a principal: the staging and fenced child-insert SQL name
|
|
1530
|
+
the new nullable columns in both cases. Use this rollout order for a shared PostgreSQL database:
|
|
1531
|
+
|
|
1532
|
+
1. Run `ensureSchema()` and confirm migration 14 (`staged_invocation_principals`) is recorded.
|
|
1533
|
+
2. Deploy `@fabricorg/platform-host` 8.7.0 or later to every process that runs this Host: processes
|
|
1534
|
+
that execute staging parent actions (including inline execution inside `submitAction`), run
|
|
1535
|
+
`dispatchStagedInvocations`, or run the Platform worker. Keep module principal declarations
|
|
1536
|
+
disabled during this step. An older Host that executes a parent ignores the declaration and
|
|
1537
|
+
stages the child without a principal, so every later dispatch uses the parent's actor.
|
|
1538
|
+
3. After those processes are upgraded, ship modules that declare `stagedInvocationPrincipals`.
|
|
1539
|
+
|
|
1540
|
+
An older worker can claim a principal-bearing row because its claim query does not filter on the
|
|
1541
|
+
new column. It then reads no principal evidence and can submit the child as the parent's actor; its
|
|
1542
|
+
older fenced insert also does not compare the principal. Depending on which version handles the stage, it can be
|
|
1543
|
+
rejected after its intent is lost or the child can complete under the wrong actor. Running migration
|
|
1544
|
+
14 before step 2 also matters for principal-less stages because this Host version names the columns
|
|
1545
|
+
when staging and dispatching them.
|
|
1509
1546
|
|
|
1510
1547
|
Each claimed row goes through one settlement sequence (`dispatchStagedRow` in `src/host.ts`):
|
|
1511
1548
|
|
|
1512
1549
|
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.
|
|
1550
|
+
`stagedOrigin` in the parent's tenant and space. Each claim replaces the lease token, so a
|
|
1514
1551
|
creation still in flight from an earlier attempt holds an older token and fails the creation
|
|
1515
1552
|
fence; this lookup therefore settles what earlier attempts did. A child found here goes to step 4
|
|
1516
1553
|
without the pre-submission checks or any entitlement, authorization, or admission call.
|
|
1517
1554
|
2. With no child, Host checks that the parent is still `completed` and that the child action is
|
|
1518
1555
|
still registered, does not require capture-time authority, and accepts the stored parameters. It
|
|
1519
|
-
|
|
1520
|
-
`submissionTimeoutMs`.
|
|
1556
|
+
resolves any recorded principal against the current Host registration, then submits through the
|
|
1557
|
+
shared submission pipeline under the claim's lease token, bounded by `submissionTimeoutMs`.
|
|
1558
|
+
Entitlement, submission authorization, admission, and execution authorization all evaluate the
|
|
1559
|
+
registered principal just as they do for any other submission.
|
|
1521
1560
|
3. If that submission's own fenced create returned the child, Host settles with its ID: `dispatched`
|
|
1522
1561
|
when the handoff succeeded or no dispatcher is configured, otherwise a retry that records the ID
|
|
1523
1562
|
(`dead_lettered` once the attempt budget is spent). In every other case (a failed pre-submission
|
|
1524
1563
|
check, a refusal, an error, or a timeout) Host first rotates the lease token with
|
|
1525
|
-
`rotateStagedInvocationLease`. The
|
|
1564
|
+
`rotateStagedInvocationLease`. The remaining possible creator is this attempt's own timed-out
|
|
1526
1565
|
submission, which still holds the old token, so after the rotation it can no longer create the
|
|
1527
1566
|
child and the lookup that follows is authoritative. A child found there goes to step 4. With none,
|
|
1528
1567
|
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
|
});
|
|
@@ -3574,6 +3599,7 @@ function createGovernedActionHost(options) {
|
|
|
3574
3599
|
parentAction: action,
|
|
3575
3600
|
requests: stagedRequests,
|
|
3576
3601
|
resolveAction: actionResolver,
|
|
3602
|
+
stagedInvocationPrincipals,
|
|
3577
3603
|
redactParameters: options.redactActionParameters,
|
|
3578
3604
|
redactedKeys: handlerStagedInvocations.map((request) => request.key),
|
|
3579
3605
|
now: now()
|
|
@@ -4144,6 +4170,8 @@ function createGovernedActionHost(options) {
|
|
|
4144
4170
|
if (!parent || parent.status !== "completed") throw new Error("Staged parent is not completed.");
|
|
4145
4171
|
const child = actionResolver(record.actionId);
|
|
4146
4172
|
if (!child) throw new StagedDispatchRejectionError("Staged action is no longer registered.");
|
|
4173
|
+
const principal = record.principal !== void 0 && Object.hasOwn(stagedInvocationPrincipals, record.principal) ? stagedInvocationPrincipals[record.principal] : void 0;
|
|
4174
|
+
if (record.principal !== void 0 && !principal) throw new StagedDispatchRejectionError("Staged invocation principal is no longer registered.");
|
|
4147
4175
|
if (child.execution?.authorityMoment === "capture") throw new StagedDispatchRejectionError("Staged action now requires capture-time authority.");
|
|
4148
4176
|
if (!child.schema.safeParse(structuredClone(record.parameters)).success) throw new StagedDispatchRejectionError("Staged parameters no longer match the registered action.");
|
|
4149
4177
|
return submitActionInternal({
|
|
@@ -4151,8 +4179,8 @@ function createGovernedActionHost(options) {
|
|
|
4151
4179
|
parameters: structuredClone(record.parameters),
|
|
4152
4180
|
tenantId: parent.tenantId,
|
|
4153
4181
|
spaceId: parent.spaceId,
|
|
4154
|
-
actorId: parent.actorId,
|
|
4155
|
-
actorType: parent.actorType,
|
|
4182
|
+
actorId: principal?.actorId ?? parent.actorId,
|
|
4183
|
+
actorType: principal?.actorType ?? parent.actorType,
|
|
4156
4184
|
correlationId: parent.correlationId,
|
|
4157
4185
|
causationId: parent.id,
|
|
4158
4186
|
provenance: {
|
|
@@ -4166,7 +4194,8 @@ function createGovernedActionHost(options) {
|
|
|
4166
4194
|
id: record.id,
|
|
4167
4195
|
origin: {
|
|
4168
4196
|
parentInvocationId: parent.id,
|
|
4169
|
-
stageKey: record.key
|
|
4197
|
+
stageKey: record.key,
|
|
4198
|
+
...record.principal !== void 0 ? { principal: record.principal } : {}
|
|
4170
4199
|
},
|
|
4171
4200
|
workerId: lease.workerId,
|
|
4172
4201
|
leaseToken: lease.leaseToken,
|
|
@@ -4836,7 +4865,7 @@ var MemoryPlatformHostStore = class {
|
|
|
4836
4865
|
kind: "existing",
|
|
4837
4866
|
invocation: existing
|
|
4838
4867
|
};
|
|
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" };
|
|
4868
|
+
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
4869
|
const createdAt = /* @__PURE__ */ new Date();
|
|
4841
4870
|
const record = {
|
|
4842
4871
|
...invocation,
|
|
@@ -5684,6 +5713,16 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5684
5713
|
version: 13,
|
|
5685
5714
|
name: "agent_proposal",
|
|
5686
5715
|
sql: `ALTER TABLE fabric_platform.action_invocations ADD COLUMN IF NOT EXISTS agent_proposal jsonb;`
|
|
5716
|
+
},
|
|
5717
|
+
{
|
|
5718
|
+
version: 14,
|
|
5719
|
+
name: "staged_invocation_principals",
|
|
5720
|
+
sql: `
|
|
5721
|
+
ALTER TABLE fabric_platform.staged_invocations
|
|
5722
|
+
ADD COLUMN IF NOT EXISTS principal text;
|
|
5723
|
+
ALTER TABLE fabric_platform.action_invocations
|
|
5724
|
+
ADD COLUMN IF NOT EXISTS staged_origin_principal text;
|
|
5725
|
+
`
|
|
5687
5726
|
}
|
|
5688
5727
|
]);
|
|
5689
5728
|
}
|
|
@@ -5748,9 +5787,9 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5748
5787
|
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
5749
5788
|
const admitted = invocation.admittedAt !== void 0;
|
|
5750
5789
|
const admittedColumn = admitted ? ",admitted_at" : "";
|
|
5751
|
-
const admittedValue = admitted ? ",$
|
|
5790
|
+
const admittedValue = admitted ? ",$31" : "";
|
|
5752
5791
|
const proposalColumn = invocation.agentProposal !== void 0 ? ",agent_proposal" : "";
|
|
5753
|
-
const proposalValue = invocation.agentProposal !== void 0 ? `,$${admitted ?
|
|
5792
|
+
const proposalValue = invocation.agentProposal !== void 0 ? `,$${admitted ? 32 : 31}::jsonb` : "";
|
|
5754
5793
|
const values = [
|
|
5755
5794
|
invocation.id,
|
|
5756
5795
|
invocation.tenantId,
|
|
@@ -5777,6 +5816,7 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5777
5816
|
invocation.runtimeEvidence ? JSON.stringify(invocation.runtimeEvidence) : null,
|
|
5778
5817
|
invocation.stagedOrigin.parentInvocationId,
|
|
5779
5818
|
invocation.stagedOrigin.stageKey,
|
|
5819
|
+
invocation.stagedOrigin.principal ?? null,
|
|
5780
5820
|
input.stageId,
|
|
5781
5821
|
input.workerId,
|
|
5782
5822
|
input.leaseToken,
|
|
@@ -5789,10 +5829,11 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5789
5829
|
result = await this.sql.query(`WITH fence AS (
|
|
5790
5830
|
SELECT stage.id
|
|
5791
5831
|
FROM fabric_platform.staged_invocations AS stage
|
|
5792
|
-
WHERE stage.id=$
|
|
5832
|
+
WHERE stage.id=$27 AND stage.tenant_id=$2 AND stage.space_id=$3
|
|
5793
5833
|
AND stage.parent_invocation_id=$24 AND stage.stage_key=$25 AND stage.action_id=$4
|
|
5794
|
-
AND stage.
|
|
5795
|
-
AND stage.
|
|
5834
|
+
AND stage.principal IS NOT DISTINCT FROM $26
|
|
5835
|
+
AND stage.status='pending' AND stage.lease_owner=$28 AND stage.lease_token=$29
|
|
5836
|
+
AND stage.lease_expires_at IS NOT NULL AND stage.lease_expires_at > $30
|
|
5796
5837
|
FOR SHARE
|
|
5797
5838
|
)
|
|
5798
5839
|
INSERT INTO fabric_platform.action_invocations
|
|
@@ -5800,17 +5841,19 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5800
5841
|
parameters,result,correlation_id,causation_id,idempotency_key,parameter_digest,
|
|
5801
5842
|
parameter_digest_algorithm,idempotency_actor_id,idempotency_authorization_binding_id,
|
|
5802
5843
|
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)
|
|
5844
|
+
runtime_evidence,staged_parent_invocation_id,staged_stage_key,staged_origin_principal${admittedColumn}${proposalColumn},created_at,updated_at)
|
|
5804
5845
|
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},$
|
|
5846
|
+
$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$25,$26${admittedValue}${proposalValue},$30,$30
|
|
5806
5847
|
FROM fence
|
|
5807
5848
|
ON CONFLICT (tenant_id,space_id,staged_parent_invocation_id,staged_stage_key)
|
|
5808
5849
|
WHERE staged_parent_invocation_id IS NOT NULL AND staged_stage_key IS NOT NULL
|
|
5809
5850
|
DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
5810
5851
|
RETURNING *`, values);
|
|
5811
5852
|
} catch (error) {
|
|
5812
|
-
|
|
5813
|
-
if (
|
|
5853
|
+
const databaseError = error;
|
|
5854
|
+
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 });
|
|
5855
|
+
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 });
|
|
5856
|
+
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
5857
|
throw error;
|
|
5815
5858
|
}
|
|
5816
5859
|
if (result.rows[0]) {
|
|
@@ -5852,33 +5895,39 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5852
5895
|
}
|
|
5853
5896
|
for (const record of records) {
|
|
5854
5897
|
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
|
-
|
|
5898
|
+
try {
|
|
5899
|
+
await this.sql.query(`INSERT INTO fabric_platform.staged_invocations
|
|
5900
|
+
(id,tenant_id,space_id,parent_invocation_id,parent_action_id,stage_key,action_id,principal,
|
|
5857
5901
|
expected_action_version,parameters,parameter_digest,batch_digest,status,child_invocation_id,
|
|
5858
5902
|
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
|
-
|
|
5903
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22)`, [
|
|
5904
|
+
record.id,
|
|
5905
|
+
record.tenantId,
|
|
5906
|
+
record.spaceId,
|
|
5907
|
+
record.parentInvocationId,
|
|
5908
|
+
record.parentActionId,
|
|
5909
|
+
record.key,
|
|
5910
|
+
record.actionId,
|
|
5911
|
+
record.principal ?? null,
|
|
5912
|
+
record.expectedActionVersion,
|
|
5913
|
+
JSON.stringify(record.parameters),
|
|
5914
|
+
record.parameterDigest,
|
|
5915
|
+
record.batchDigest,
|
|
5916
|
+
record.status,
|
|
5917
|
+
record.childInvocationId ?? null,
|
|
5918
|
+
record.attemptCount,
|
|
5919
|
+
record.availableAt,
|
|
5920
|
+
record.leaseOwner ?? null,
|
|
5921
|
+
record.leaseExpiresAt ?? null,
|
|
5922
|
+
record.leaseToken,
|
|
5923
|
+
record.lastError ?? null,
|
|
5924
|
+
record.createdAt,
|
|
5925
|
+
record.updatedAt
|
|
5926
|
+
]);
|
|
5927
|
+
} catch (error) {
|
|
5928
|
+
if (error?.code === "42703") throw new Error("Staging an invocation needs PostgreSQL migration 14 (staged_invocation_principals); run ensureSchema() before staging invocations.", { cause: error });
|
|
5929
|
+
throw error;
|
|
5930
|
+
}
|
|
5882
5931
|
}
|
|
5883
5932
|
}
|
|
5884
5933
|
async getActionInvocation(id, tenantId, spaceId) {
|
|
@@ -6899,7 +6948,8 @@ function toActionRecord(row) {
|
|
|
6899
6948
|
...row.idempotency_authorization_binding_id ? { idempotencyAuthorizationBindingId: String(row.idempotency_authorization_binding_id) } : {},
|
|
6900
6949
|
...row.staged_parent_invocation_id && row.staged_stage_key ? { stagedOrigin: {
|
|
6901
6950
|
parentInvocationId: String(row.staged_parent_invocation_id),
|
|
6902
|
-
stageKey: String(row.staged_stage_key)
|
|
6951
|
+
stageKey: String(row.staged_stage_key),
|
|
6952
|
+
...row.staged_origin_principal ? { principal: String(row.staged_origin_principal) } : {}
|
|
6903
6953
|
} } : {},
|
|
6904
6954
|
...row.invocation_provenance ? { provenance: row.invocation_provenance } : {},
|
|
6905
6955
|
...row.authorization_binding ? { authorizationBinding: row.authorization_binding } : {},
|
|
@@ -6945,6 +6995,7 @@ function toStagedInvocationRecord(row) {
|
|
|
6945
6995
|
parentActionId: String(row.parent_action_id),
|
|
6946
6996
|
key: String(row.stage_key),
|
|
6947
6997
|
actionId: String(row.action_id),
|
|
6998
|
+
...row.principal ? { principal: String(row.principal) } : {},
|
|
6948
6999
|
expectedActionVersion: Number(row.expected_action_version),
|
|
6949
7000
|
parameters: row.parameters,
|
|
6950
7001
|
parameterDigest: String(row.parameter_digest),
|
|
@@ -8444,6 +8495,7 @@ exports.PLATFORM_HOST_HEALTH_CONTRACT_VERSION = PLATFORM_HOST_HEALTH_CONTRACT_VE
|
|
|
8444
8495
|
exports.PLATFORM_HOST_METRIC_NAMES = PLATFORM_HOST_METRIC_NAMES;
|
|
8445
8496
|
exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
|
|
8446
8497
|
exports.STAGED_INVOCATION_KEY_PATTERN = STAGED_INVOCATION_KEY_PATTERN;
|
|
8498
|
+
exports.STAGED_INVOCATION_PRINCIPAL_PATTERN = STAGED_INVOCATION_PRINCIPAL_PATTERN;
|
|
8447
8499
|
exports.SubmissionRefusalError = SubmissionRefusalError;
|
|
8448
8500
|
exports.applyPostgresMigrations = applyPostgresMigrations;
|
|
8449
8501
|
exports.asStagedInvocationStore = asStagedInvocationStore;
|