@fabricorg/platform-host 8.5.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 +16 -0
- package/README.md +88 -12
- package/dist/index.cjs +178 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +179 -54
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -297,6 +297,52 @@ async function appendLifecycleEvent(transaction, factory, invocation, kind) {
|
|
|
297
297
|
}, metadata);
|
|
298
298
|
}
|
|
299
299
|
//#endregion
|
|
300
|
+
//#region src/agent-proposal.ts
|
|
301
|
+
/** Validate without invoking getters or retaining caller-owned evidence. */
|
|
302
|
+
function snapshotAgentProposal(value) {
|
|
303
|
+
const invalid = () => {
|
|
304
|
+
throw new Error("Invalid agent proposal.");
|
|
305
|
+
};
|
|
306
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || util.types.isProxy(value)) return invalid();
|
|
307
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return invalid();
|
|
308
|
+
const allowed = [
|
|
309
|
+
"confidence",
|
|
310
|
+
"riskTier",
|
|
311
|
+
"agentSessionId",
|
|
312
|
+
"agentRunId",
|
|
313
|
+
"reason",
|
|
314
|
+
"evidence"
|
|
315
|
+
];
|
|
316
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
317
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
318
|
+
if (typeof key !== "string" || !allowed.includes(key) || !descriptor.enumerable || !Object.hasOwn(descriptor, "value")) return invalid();
|
|
319
|
+
}
|
|
320
|
+
if (value.confidence !== void 0 && (typeof value.confidence !== "number" || !Number.isFinite(value.confidence))) return invalid();
|
|
321
|
+
if (value.riskTier !== void 0 && ![
|
|
322
|
+
"low",
|
|
323
|
+
"medium",
|
|
324
|
+
"high"
|
|
325
|
+
].includes(value.riskTier)) return invalid();
|
|
326
|
+
for (const key of [
|
|
327
|
+
"agentSessionId",
|
|
328
|
+
"agentRunId",
|
|
329
|
+
"reason"
|
|
330
|
+
]) {
|
|
331
|
+
const field = value[key];
|
|
332
|
+
if (field !== void 0 && (typeof field !== "string" || field.length > (key === "reason" ? 4096 : 256) || key !== "reason" && field.trim().length === 0)) return invalid();
|
|
333
|
+
}
|
|
334
|
+
const evidence = snapshotApprovalDecision({
|
|
335
|
+
approved: true,
|
|
336
|
+
approverId: "proposal",
|
|
337
|
+
approverType: "agent",
|
|
338
|
+
...value.evidence !== void 0 ? { evidence: value.evidence } : {}
|
|
339
|
+
}).evidence;
|
|
340
|
+
return {
|
|
341
|
+
...value,
|
|
342
|
+
...evidence !== void 0 ? { evidence } : {}
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
300
346
|
//#region src/command-identity.ts
|
|
301
347
|
const PARAMETER_DIGEST_ALGORITHM = "fabric-canonical-json-sha256-v1";
|
|
302
348
|
function digestParameters(parameters) {
|
|
@@ -1002,7 +1048,11 @@ function snapshotSubmission(input) {
|
|
|
1002
1048
|
const executionReason = scalar("executionReason");
|
|
1003
1049
|
const idempotencyKey = scalar("idempotencyKey");
|
|
1004
1050
|
const authorizationBindingId = scalar("authorizationBindingId");
|
|
1051
|
+
const proposal = input.agentProposal;
|
|
1052
|
+
if (proposal !== void 0 && actorType !== "agent") throw new Error("Agent proposal requires an agent actor.");
|
|
1053
|
+
const agentProposal = proposal === void 0 ? void 0 : snapshotAgentProposal(proposal);
|
|
1005
1054
|
const live = {
|
|
1055
|
+
...agentProposal !== void 0 ? { agentProposal } : {},
|
|
1006
1056
|
actionId,
|
|
1007
1057
|
tenantId,
|
|
1008
1058
|
spaceId,
|
|
@@ -1071,6 +1121,7 @@ function privateSubmission(snapshot) {
|
|
|
1071
1121
|
const provenance = provenanceRead ? copyProvenance(provenanceRead) : void 0;
|
|
1072
1122
|
const authorizationBinding = bindingRead ? copyBinding(bindingRead) : void 0;
|
|
1073
1123
|
return Object.freeze({
|
|
1124
|
+
...snapshot.agentProposal !== void 0 ? { agentProposal: snapshot.agentProposal } : {},
|
|
1074
1125
|
actionId: snapshot.actionId,
|
|
1075
1126
|
tenantId: snapshot.tenantId,
|
|
1076
1127
|
spaceId: snapshot.spaceId,
|
|
@@ -1167,6 +1218,7 @@ function parametersCopy(snapshot) {
|
|
|
1167
1218
|
*/
|
|
1168
1219
|
function liveSubmissionView(snapshot, input) {
|
|
1169
1220
|
const view = {
|
|
1221
|
+
...snapshot.agentProposal !== void 0 ? { agentProposal: snapshotAgentProposal(snapshot.agentProposal) } : {},
|
|
1170
1222
|
actionId: snapshot.actionId,
|
|
1171
1223
|
tenantId: snapshot.tenantId,
|
|
1172
1224
|
spaceId: snapshot.spaceId,
|
|
@@ -1208,6 +1260,7 @@ function submissionView(snapshot) {
|
|
|
1208
1260
|
const provenance = fresh(snapshot.provenance);
|
|
1209
1261
|
const authorizationBinding = fresh(snapshot.authorizationBinding);
|
|
1210
1262
|
return {
|
|
1263
|
+
...snapshot.agentProposal !== void 0 ? { agentProposal: snapshotAgentProposal(snapshot.agentProposal) } : {},
|
|
1211
1264
|
actionId: snapshot.actionId,
|
|
1212
1265
|
parameters: parametersCopy(snapshot),
|
|
1213
1266
|
tenantId: snapshot.tenantId,
|
|
@@ -1322,6 +1375,7 @@ const MAX_STAGED_INVOCATION_KEY_LENGTH = 128;
|
|
|
1322
1375
|
const MAX_STAGED_INVOCATION_BYTES = 65536;
|
|
1323
1376
|
const MAX_STAGED_INVOCATION_BATCH_BYTES = 262144;
|
|
1324
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}$/;
|
|
1325
1379
|
/** Validate and normalize one immutable batch before its parent transaction commits. */
|
|
1326
1380
|
function prepareStagedInvocationBatch(input) {
|
|
1327
1381
|
if (!Array.isArray(input.requests)) throw new Error("stagedInvocations must be an array.");
|
|
@@ -1341,6 +1395,8 @@ function prepareStagedInvocationBatch(input) {
|
|
|
1341
1395
|
if (!(input.parentAction.stagesInvocations ?? []).includes(request.actionId)) throw new Error(`Action "${input.parentAction.actionId}" may not stage "${String(request.actionId)}".`);
|
|
1342
1396
|
const child = input.resolveAction(request.actionId);
|
|
1343
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.`);
|
|
1344
1400
|
const parameters = finalizationJson(request.parameters);
|
|
1345
1401
|
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) throw new Error(`stagedInvocations[${index}] parameters must be a JSON object.`);
|
|
1346
1402
|
if (!child.schema.safeParse(parameters).success) throw new Error(`stagedInvocations[${index}] parameters do not match action "${child.actionId}".`);
|
|
@@ -1355,24 +1411,27 @@ function prepareStagedInvocationBatch(input) {
|
|
|
1355
1411
|
if (byteLength((0, _fabricorg_platform.canonicalJson)({
|
|
1356
1412
|
key: request.key,
|
|
1357
1413
|
actionId: child.actionId,
|
|
1358
|
-
parameters: durableParameters
|
|
1414
|
+
parameters: durableParameters,
|
|
1415
|
+
...principal !== void 0 ? { principal } : {}
|
|
1359
1416
|
})) > 65536) throw new Error(`stagedInvocations[${index}] exceeds ${MAX_STAGED_INVOCATION_BYTES} bytes.`);
|
|
1360
1417
|
return {
|
|
1361
1418
|
key: request.key,
|
|
1362
1419
|
actionId: child.actionId,
|
|
1363
1420
|
expectedActionVersion: child.version,
|
|
1364
1421
|
parameters: durableParameters,
|
|
1365
|
-
parameterDigest: digestParameters(durableParameters)
|
|
1422
|
+
parameterDigest: digestParameters(durableParameters),
|
|
1423
|
+
...principal !== void 0 ? { principal } : {}
|
|
1366
1424
|
};
|
|
1367
1425
|
});
|
|
1368
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}".`);
|
|
1369
1427
|
normalized.sort((left, right) => compareText(left.key, right.key) || compareText(left.actionId, right.actionId));
|
|
1370
|
-
const batchIdentity = normalized.map(({ key, actionId, expectedActionVersion, parameters, parameterDigest }) => ({
|
|
1428
|
+
const batchIdentity = normalized.map(({ key, actionId, expectedActionVersion, parameters, parameterDigest, principal }) => ({
|
|
1371
1429
|
key,
|
|
1372
1430
|
actionId,
|
|
1373
1431
|
expectedActionVersion,
|
|
1374
1432
|
parameters,
|
|
1375
|
-
parameterDigest
|
|
1433
|
+
parameterDigest,
|
|
1434
|
+
...principal !== void 0 ? { principal } : {}
|
|
1376
1435
|
}));
|
|
1377
1436
|
if (byteLength((0, _fabricorg_platform.canonicalJson)(batchIdentity)) > 262144) throw new Error(`stagedInvocations batch exceeds ${MAX_STAGED_INVOCATION_BATCH_BYTES} bytes.`);
|
|
1378
1437
|
const batchDigest = digestParameters({
|
|
@@ -1407,7 +1466,8 @@ function stagedInvocationRequestIdentity(record) {
|
|
|
1407
1466
|
expectedActionVersion: record.expectedActionVersion,
|
|
1408
1467
|
parameters: record.parameters,
|
|
1409
1468
|
parameterDigest: record.parameterDigest,
|
|
1410
|
-
batchDigest: record.batchDigest
|
|
1469
|
+
batchDigest: record.batchDigest,
|
|
1470
|
+
...record.principal !== void 0 ? { principal: record.principal } : {}
|
|
1411
1471
|
});
|
|
1412
1472
|
}
|
|
1413
1473
|
function cloneStagedInvocationRecord(record) {
|
|
@@ -1474,6 +1534,20 @@ const STAGED_ERROR = Object.freeze({
|
|
|
1474
1534
|
lookup: "Staged child lookup failed."
|
|
1475
1535
|
});
|
|
1476
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
|
+
})));
|
|
1477
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.");
|
|
1478
1552
|
if (options.approvalLifecycleEvents) {
|
|
1479
1553
|
const approvalStore = asApprovalStore(options.store);
|
|
@@ -1507,6 +1581,9 @@ function createGovernedActionHost(options) {
|
|
|
1507
1581
|
const configuredActionResolver = options.resolveAction ?? options.registry?.resolveAction;
|
|
1508
1582
|
if (!configuredActionResolver) throw new Error("Platform Host requires an explicit module registry or resolveAction function.");
|
|
1509
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
|
+
}
|
|
1510
1587
|
const stagedStore = asStagedInvocationStore(options.store);
|
|
1511
1588
|
const declaresStaging = options.registry?.orderedModules?.some((module) => (module.actions ?? []).some((action) => (action.stagesInvocations?.length ?? 0) > 0)) ?? false;
|
|
1512
1589
|
if (declaresStaging && (!stagedStore || !asAtomicMutationStore(options.store))) throw new Error("Staged descendant invocations require a store with transactionalStagedInvocations and transactionWithEvents capabilities.");
|
|
@@ -1544,7 +1621,8 @@ function createGovernedActionHost(options) {
|
|
|
1544
1621
|
if (submission.authorizationBinding) validateAuthorizationBinding(submission, parameterDigest, action.execution?.authorityMoment, now());
|
|
1545
1622
|
return {
|
|
1546
1623
|
durableParameters,
|
|
1547
|
-
parameterDigest
|
|
1624
|
+
parameterDigest,
|
|
1625
|
+
agentProposal: submission.agentProposal === void 0 ? void 0 : snapshotAgentProposal(options.redactAgentProposal ? options.redactAgentProposal(submission.actionId, submission.agentProposal) : submission.agentProposal)
|
|
1548
1626
|
};
|
|
1549
1627
|
};
|
|
1550
1628
|
const early = declaresAdmission ? void 0 : prepare();
|
|
@@ -1559,9 +1637,10 @@ function createGovernedActionHost(options) {
|
|
|
1559
1637
|
};
|
|
1560
1638
|
(0, _fabricorg_platform.assertGovernanceRuntimeEvidence)(runtimeEvidence);
|
|
1561
1639
|
const admission = declaresAdmission ? await admitSubmission(action, submission) : void 0;
|
|
1562
|
-
const { durableParameters, parameterDigest } = early ?? prepare();
|
|
1640
|
+
const { durableParameters, parameterDigest, agentProposal } = early ?? prepare();
|
|
1563
1641
|
const invocationInput = {
|
|
1564
1642
|
id: actionInvocationId,
|
|
1643
|
+
...agentProposal !== void 0 ? { agentProposal } : {},
|
|
1565
1644
|
tenantId: submission.tenantId,
|
|
1566
1645
|
spaceId: submission.spaceId,
|
|
1567
1646
|
actionId: submission.actionId,
|
|
@@ -1615,6 +1694,7 @@ function createGovernedActionHost(options) {
|
|
|
1615
1694
|
});
|
|
1616
1695
|
if (durableInvocation.id !== actionInvocationId && submission.idempotencyKey) {
|
|
1617
1696
|
const conflict = idempotencyConflict(durableInvocation, {
|
|
1697
|
+
agentProposal,
|
|
1618
1698
|
actorId: submission.actorId,
|
|
1619
1699
|
authorizationBindingId: submission.authorizationBindingId,
|
|
1620
1700
|
actionVersion: action.version,
|
|
@@ -2336,21 +2416,24 @@ function createGovernedActionHost(options) {
|
|
|
2336
2416
|
}
|
|
2337
2417
|
if (invocation.actorType === "agent" && options.hitlEvaluator) {
|
|
2338
2418
|
const hitlParameters = isRecord(parsed.data) ? parsed.data : invocation.parameters;
|
|
2419
|
+
const proposal = invocation.agentProposal;
|
|
2339
2420
|
const approvalStore = asApprovalStore(options.store);
|
|
2340
2421
|
if (!approvalStore) return hostOperation(() => failExecution(invocation, "failed", "The configured HITL evaluator requires a store with approval persistence support."));
|
|
2341
2422
|
let evaluatedApprovalExpiresAt;
|
|
2342
2423
|
if (!invocation.hitlRoute) {
|
|
2343
2424
|
const decision = await options.hitlEvaluator({
|
|
2425
|
+
actionInvocationId: invocation.id,
|
|
2426
|
+
invocation,
|
|
2344
2427
|
actionId: action.actionId,
|
|
2345
2428
|
actorId: invocation.actorId,
|
|
2346
2429
|
actorType: invocation.actorType,
|
|
2347
2430
|
tenantId,
|
|
2348
2431
|
spaceId,
|
|
2349
2432
|
parameters: hitlParameters,
|
|
2350
|
-
...numberValue(hitlParameters.confidence) !== void 0 ? { confidence: numberValue(hitlParameters.confidence) } : {},
|
|
2351
|
-
...isRiskTier(hitlParameters.riskTier) ? { riskTier: hitlParameters.riskTier } : {},
|
|
2352
|
-
...stringValue(hitlParameters.agentSessionId) ? { agentSessionId: stringValue(hitlParameters.agentSessionId) } : {},
|
|
2353
|
-
...stringValue(hitlParameters.agentRunId) ? { agentRunId: stringValue(hitlParameters.agentRunId) } : {}
|
|
2433
|
+
...(proposal?.confidence ?? numberValue(hitlParameters.confidence)) !== void 0 ? { confidence: proposal?.confidence ?? numberValue(hitlParameters.confidence) } : {},
|
|
2434
|
+
...isRiskTier(proposal?.riskTier ?? hitlParameters.riskTier) ? { riskTier: proposal?.riskTier ?? hitlParameters.riskTier } : {},
|
|
2435
|
+
...proposal?.agentSessionId ?? stringValue(hitlParameters.agentSessionId) ? { agentSessionId: proposal?.agentSessionId ?? stringValue(hitlParameters.agentSessionId) } : {},
|
|
2436
|
+
...proposal?.agentRunId ?? stringValue(hitlParameters.agentRunId) ? { agentRunId: proposal?.agentRunId ?? stringValue(hitlParameters.agentRunId) } : {}
|
|
2354
2437
|
});
|
|
2355
2438
|
if (decision.approvalExpiresAt !== void 0 && !validApprovalDate(decision.approvalExpiresAt)) return hostOperation(() => failExecution(invocation, "failed", "approvalExpiresAt must be a valid Date."));
|
|
2356
2439
|
evaluatedApprovalExpiresAt = decision.approvalExpiresAt === void 0 ? void 0 : new Date(decision.approvalExpiresAt);
|
|
@@ -2644,6 +2727,7 @@ function createGovernedActionHost(options) {
|
|
|
2644
2727
|
parentAction: action,
|
|
2645
2728
|
requests: rawStagedInvocations,
|
|
2646
2729
|
resolveAction: actionResolver,
|
|
2730
|
+
stagedInvocationPrincipals,
|
|
2647
2731
|
redactParameters: options.redactActionParameters,
|
|
2648
2732
|
now: now()
|
|
2649
2733
|
});
|
|
@@ -3515,6 +3599,7 @@ function createGovernedActionHost(options) {
|
|
|
3515
3599
|
parentAction: action,
|
|
3516
3600
|
requests: stagedRequests,
|
|
3517
3601
|
resolveAction: actionResolver,
|
|
3602
|
+
stagedInvocationPrincipals,
|
|
3518
3603
|
redactParameters: options.redactActionParameters,
|
|
3519
3604
|
redactedKeys: handlerStagedInvocations.map((request) => request.key),
|
|
3520
3605
|
now: now()
|
|
@@ -4085,6 +4170,8 @@ function createGovernedActionHost(options) {
|
|
|
4085
4170
|
if (!parent || parent.status !== "completed") throw new Error("Staged parent is not completed.");
|
|
4086
4171
|
const child = actionResolver(record.actionId);
|
|
4087
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.");
|
|
4088
4175
|
if (child.execution?.authorityMoment === "capture") throw new StagedDispatchRejectionError("Staged action now requires capture-time authority.");
|
|
4089
4176
|
if (!child.schema.safeParse(structuredClone(record.parameters)).success) throw new StagedDispatchRejectionError("Staged parameters no longer match the registered action.");
|
|
4090
4177
|
return submitActionInternal({
|
|
@@ -4092,8 +4179,8 @@ function createGovernedActionHost(options) {
|
|
|
4092
4179
|
parameters: structuredClone(record.parameters),
|
|
4093
4180
|
tenantId: parent.tenantId,
|
|
4094
4181
|
spaceId: parent.spaceId,
|
|
4095
|
-
actorId: parent.actorId,
|
|
4096
|
-
actorType: parent.actorType,
|
|
4182
|
+
actorId: principal?.actorId ?? parent.actorId,
|
|
4183
|
+
actorType: principal?.actorType ?? parent.actorType,
|
|
4097
4184
|
correlationId: parent.correlationId,
|
|
4098
4185
|
causationId: parent.id,
|
|
4099
4186
|
provenance: {
|
|
@@ -4107,7 +4194,8 @@ function createGovernedActionHost(options) {
|
|
|
4107
4194
|
id: record.id,
|
|
4108
4195
|
origin: {
|
|
4109
4196
|
parentInvocationId: parent.id,
|
|
4110
|
-
stageKey: record.key
|
|
4197
|
+
stageKey: record.key,
|
|
4198
|
+
...record.principal !== void 0 ? { principal: record.principal } : {}
|
|
4111
4199
|
},
|
|
4112
4200
|
workerId: lease.workerId,
|
|
4113
4201
|
leaseToken: lease.leaseToken,
|
|
@@ -4314,6 +4402,7 @@ function validateAuthorizationBinding(input, parameterDigest, authorityMoment, c
|
|
|
4314
4402
|
}
|
|
4315
4403
|
function idempotencyConflict(existing, incoming) {
|
|
4316
4404
|
const reasons = [];
|
|
4405
|
+
if ((0, _fabricorg_platform.canonicalJson)(existing.agentProposal ?? null) !== (0, _fabricorg_platform.canonicalJson)(incoming.agentProposal ?? null)) reasons.push("agent_proposal");
|
|
4317
4406
|
if ((existing.idempotencyActorId ?? existing.actorId) !== incoming.actorId) reasons.push("actor");
|
|
4318
4407
|
if ((existing.idempotencyAuthorizationBindingId ?? existing.authorizationBindingId) !== incoming.authorizationBindingId) reasons.push("authority_binding");
|
|
4319
4408
|
if (existing.actionVersion !== incoming.actionVersion) reasons.push("action_version");
|
|
@@ -4776,7 +4865,7 @@ var MemoryPlatformHostStore = class {
|
|
|
4776
4865
|
kind: "existing",
|
|
4777
4866
|
invocation: existing
|
|
4778
4867
|
};
|
|
4779
|
-
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" };
|
|
4780
4869
|
const createdAt = /* @__PURE__ */ new Date();
|
|
4781
4870
|
const record = {
|
|
4782
4871
|
...invocation,
|
|
@@ -5619,6 +5708,21 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5619
5708
|
ON fabric_platform.action_invocations (approval_expires_at, id)
|
|
5620
5709
|
WHERE status='waiting_for_approval' AND approval_expires_at IS NOT NULL;
|
|
5621
5710
|
`
|
|
5711
|
+
},
|
|
5712
|
+
{
|
|
5713
|
+
version: 13,
|
|
5714
|
+
name: "agent_proposal",
|
|
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
|
+
`
|
|
5622
5726
|
}
|
|
5623
5727
|
]);
|
|
5624
5728
|
}
|
|
@@ -5630,6 +5734,8 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5630
5734
|
const admitted = input.admittedAt !== void 0;
|
|
5631
5735
|
const admittedColumn = admitted ? "admitted_at," : "";
|
|
5632
5736
|
const admittedValue = admitted ? "$25," : "";
|
|
5737
|
+
const proposalColumn = input.agentProposal !== void 0 ? "agent_proposal," : "";
|
|
5738
|
+
const proposalValue = input.agentProposal !== void 0 ? `$${admitted ? 26 : 25}::jsonb,` : "";
|
|
5633
5739
|
let result;
|
|
5634
5740
|
try {
|
|
5635
5741
|
result = await this.sql.query(`INSERT INTO fabric_platform.action_invocations
|
|
@@ -5637,8 +5743,8 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5637
5743
|
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
5638
5744
|
parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
|
|
5639
5745
|
idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
|
|
5640
|
-
authorization_binding_id,error,runtime_evidence,${admittedColumn}created_at,updated_at)
|
|
5641
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,${admittedValue}$24,$24)
|
|
5746
|
+
authorization_binding_id,error,runtime_evidence,${admittedColumn}${proposalColumn}created_at,updated_at)
|
|
5747
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,${admittedValue}${proposalValue}$24,$24)
|
|
5642
5748
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
5643
5749
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
5644
5750
|
RETURNING *`, [
|
|
@@ -5666,9 +5772,11 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5666
5772
|
input.error ?? null,
|
|
5667
5773
|
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
5668
5774
|
now,
|
|
5669
|
-
...admitted ? [input.admittedAt] : []
|
|
5775
|
+
...admitted ? [input.admittedAt] : [],
|
|
5776
|
+
...input.agentProposal !== void 0 ? [JSON.stringify(input.agentProposal)] : []
|
|
5670
5777
|
]);
|
|
5671
5778
|
} catch (error) {
|
|
5779
|
+
if (input.agentProposal !== void 0 && error?.code === "42703") throw new Error("Agent proposals require PostgreSQL migration 13 (agent_proposal); run ensureSchema().", { cause: error });
|
|
5672
5780
|
if (admitted && error?.code === "42703") throw new Error("An admitted invocation needs the action_invocations.admitted_at column; run ensureSchema() to apply PostgreSQL migration 6 (invocation_admission) before submitting actions that declare admitSubmission.", { cause: error });
|
|
5673
5781
|
throw error;
|
|
5674
5782
|
}
|
|
@@ -5679,7 +5787,9 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5679
5787
|
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
5680
5788
|
const admitted = invocation.admittedAt !== void 0;
|
|
5681
5789
|
const admittedColumn = admitted ? ",admitted_at" : "";
|
|
5682
|
-
const admittedValue = admitted ? ",$
|
|
5790
|
+
const admittedValue = admitted ? ",$31" : "";
|
|
5791
|
+
const proposalColumn = invocation.agentProposal !== void 0 ? ",agent_proposal" : "";
|
|
5792
|
+
const proposalValue = invocation.agentProposal !== void 0 ? `,$${admitted ? 32 : 31}::jsonb` : "";
|
|
5683
5793
|
const values = [
|
|
5684
5794
|
invocation.id,
|
|
5685
5795
|
invocation.tenantId,
|
|
@@ -5706,21 +5816,24 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5706
5816
|
invocation.runtimeEvidence ? JSON.stringify(invocation.runtimeEvidence) : null,
|
|
5707
5817
|
invocation.stagedOrigin.parentInvocationId,
|
|
5708
5818
|
invocation.stagedOrigin.stageKey,
|
|
5819
|
+
invocation.stagedOrigin.principal ?? null,
|
|
5709
5820
|
input.stageId,
|
|
5710
5821
|
input.workerId,
|
|
5711
5822
|
input.leaseToken,
|
|
5712
5823
|
current,
|
|
5713
|
-
...admitted ? [invocation.admittedAt] : []
|
|
5824
|
+
...admitted ? [invocation.admittedAt] : [],
|
|
5825
|
+
...invocation.agentProposal !== void 0 ? [JSON.stringify(invocation.agentProposal)] : []
|
|
5714
5826
|
];
|
|
5715
5827
|
let result;
|
|
5716
5828
|
try {
|
|
5717
5829
|
result = await this.sql.query(`WITH fence AS (
|
|
5718
5830
|
SELECT stage.id
|
|
5719
5831
|
FROM fabric_platform.staged_invocations AS stage
|
|
5720
|
-
WHERE stage.id=$
|
|
5832
|
+
WHERE stage.id=$27 AND stage.tenant_id=$2 AND stage.space_id=$3
|
|
5721
5833
|
AND stage.parent_invocation_id=$24 AND stage.stage_key=$25 AND stage.action_id=$4
|
|
5722
|
-
AND stage.
|
|
5723
|
-
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
|
|
5724
5837
|
FOR SHARE
|
|
5725
5838
|
)
|
|
5726
5839
|
INSERT INTO fabric_platform.action_invocations
|
|
@@ -5728,16 +5841,19 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5728
5841
|
parameters,result,correlation_id,causation_id,idempotency_key,parameter_digest,
|
|
5729
5842
|
parameter_digest_algorithm,idempotency_actor_id,idempotency_authorization_binding_id,
|
|
5730
5843
|
invocation_provenance,authorization_binding,execution_reason,authorization_binding_id,error,
|
|
5731
|
-
runtime_evidence,staged_parent_invocation_id,staged_stage_key${admittedColumn},created_at,updated_at)
|
|
5844
|
+
runtime_evidence,staged_parent_invocation_id,staged_stage_key,staged_origin_principal${admittedColumn}${proposalColumn},created_at,updated_at)
|
|
5732
5845
|
SELECT $1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,
|
|
5733
|
-
$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$25${admittedValue},$
|
|
5846
|
+
$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$25,$26${admittedValue}${proposalValue},$30,$30
|
|
5734
5847
|
FROM fence
|
|
5735
5848
|
ON CONFLICT (tenant_id,space_id,staged_parent_invocation_id,staged_stage_key)
|
|
5736
5849
|
WHERE staged_parent_invocation_id IS NOT NULL AND staged_stage_key IS NOT NULL
|
|
5737
5850
|
DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
5738
5851
|
RETURNING *`, values);
|
|
5739
5852
|
} catch (error) {
|
|
5740
|
-
|
|
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 });
|
|
5741
5857
|
throw error;
|
|
5742
5858
|
}
|
|
5743
5859
|
if (result.rows[0]) {
|
|
@@ -5779,33 +5895,39 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
|
|
|
5779
5895
|
}
|
|
5780
5896
|
for (const record of records) {
|
|
5781
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}.`);
|
|
5782
|
-
|
|
5783
|
-
|
|
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,
|
|
5784
5901
|
expected_action_version,parameters,parameter_digest,batch_digest,status,child_invocation_id,
|
|
5785
5902
|
attempt_count,available_at,lease_owner,lease_expires_at,lease_token,last_error,created_at,updated_at)
|
|
5786
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
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
|
+
}
|
|
5809
5931
|
}
|
|
5810
5932
|
}
|
|
5811
5933
|
async getActionInvocation(id, tenantId, spaceId) {
|
|
@@ -6826,7 +6948,8 @@ function toActionRecord(row) {
|
|
|
6826
6948
|
...row.idempotency_authorization_binding_id ? { idempotencyAuthorizationBindingId: String(row.idempotency_authorization_binding_id) } : {},
|
|
6827
6949
|
...row.staged_parent_invocation_id && row.staged_stage_key ? { stagedOrigin: {
|
|
6828
6950
|
parentInvocationId: String(row.staged_parent_invocation_id),
|
|
6829
|
-
stageKey: String(row.staged_stage_key)
|
|
6951
|
+
stageKey: String(row.staged_stage_key),
|
|
6952
|
+
...row.staged_origin_principal ? { principal: String(row.staged_origin_principal) } : {}
|
|
6830
6953
|
} } : {},
|
|
6831
6954
|
...row.invocation_provenance ? { provenance: row.invocation_provenance } : {},
|
|
6832
6955
|
...row.authorization_binding ? { authorizationBinding: row.authorization_binding } : {},
|
|
@@ -6856,6 +6979,7 @@ function toActionRecord(row) {
|
|
|
6856
6979
|
...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
|
|
6857
6980
|
...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
|
|
6858
6981
|
...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
|
|
6982
|
+
...row.agent_proposal != null ? { agentProposal: row.agent_proposal } : {},
|
|
6859
6983
|
...row.admitted_at ? { admittedAt: new Date(row.admitted_at) } : {},
|
|
6860
6984
|
...row.error ? { error: String(row.error) } : {},
|
|
6861
6985
|
createdAt: new Date(row.created_at),
|
|
@@ -6871,6 +6995,7 @@ function toStagedInvocationRecord(row) {
|
|
|
6871
6995
|
parentActionId: String(row.parent_action_id),
|
|
6872
6996
|
key: String(row.stage_key),
|
|
6873
6997
|
actionId: String(row.action_id),
|
|
6998
|
+
...row.principal ? { principal: String(row.principal) } : {},
|
|
6874
6999
|
expectedActionVersion: Number(row.expected_action_version),
|
|
6875
7000
|
parameters: row.parameters,
|
|
6876
7001
|
parameterDigest: String(row.parameter_digest),
|
|
@@ -8370,6 +8495,7 @@ exports.PLATFORM_HOST_HEALTH_CONTRACT_VERSION = PLATFORM_HOST_HEALTH_CONTRACT_VE
|
|
|
8370
8495
|
exports.PLATFORM_HOST_METRIC_NAMES = PLATFORM_HOST_METRIC_NAMES;
|
|
8371
8496
|
exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
|
|
8372
8497
|
exports.STAGED_INVOCATION_KEY_PATTERN = STAGED_INVOCATION_KEY_PATTERN;
|
|
8498
|
+
exports.STAGED_INVOCATION_PRINCIPAL_PATTERN = STAGED_INVOCATION_PRINCIPAL_PATTERN;
|
|
8373
8499
|
exports.SubmissionRefusalError = SubmissionRefusalError;
|
|
8374
8500
|
exports.applyPostgresMigrations = applyPostgresMigrations;
|
|
8375
8501
|
exports.asStagedInvocationStore = asStagedInvocationStore;
|