@fabricorg/platform-host 8.2.0 → 8.4.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 +39 -0
- package/README.md +136 -4
- package/dist/index.cjs +1196 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +357 -14
- package/dist/index.d.ts +357 -14
- package/dist/index.js +1188 -25
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 8.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f1b6def: Add the parked action invocation ID and durable invocation record to `authorizeApproval` input so applications can authorize decisions for the specific proposal being resumed.
|
|
8
|
+
|
|
9
|
+
## 8.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 749c507: Add host-owned staged descendant invocations. Actions declare an allowlist and return typed child
|
|
14
|
+
requests; Platform Host validates and redacts them atomically, releases them only after parent
|
|
15
|
+
completion, and dispatches them through fresh entitlement, authorization, admission, runtime
|
|
16
|
+
evidence, policy, and HITL decisions.
|
|
17
|
+
|
|
18
|
+
PostgreSQL migration 11 adds the staged-invocation ledger and caller-unforgeable child-origin unique
|
|
19
|
+
index. Child identity is a Host-only parent-invocation/stage-key origin. Child creation is
|
|
20
|
+
atomically fenced on the stage's pending status, lease owner, lease token, and unexpired lease;
|
|
21
|
+
PostgreSQL locks that stage row `FOR SHARE` while the fenced insert is in flight.
|
|
22
|
+
|
|
23
|
+
Each attempt settles a stage through one sequence. It first looks the child up by origin: a child
|
|
24
|
+
created by an earlier attempt is handed to the dispatcher again with its stable workflow ID while it
|
|
25
|
+
remains pending, without pre-submission checks or repeated governance, even after a deploy changes
|
|
26
|
+
the child's schema. Otherwise Host submits the child. When that submission did not itself return
|
|
27
|
+
the child (a failed check, a refusal, an error, or a timeout), Host rotates the lease token through
|
|
28
|
+
the store operation `rotateStagedInvocationLease` before an authoritative lookup, so the attempt's
|
|
29
|
+
own timed-out submission can no longer create the child. A stage is rejected only when that lookup
|
|
30
|
+
finds no child, and a dead-lettered stage records the child ID whenever the attempt found or created
|
|
31
|
+
a child. A stage becomes dispatched only after confirmed handoff, when no dispatcher is configured,
|
|
32
|
+
or when the child has already left pending. A failed child lookup settles the stage for retry and
|
|
33
|
+
does not dead-letter it. Settlements are compare-and-set on the lease owner and current token and
|
|
34
|
+
keep a child ID recorded earlier. A store error on one stage is reported through the sweep's
|
|
35
|
+
`onError` option (the Platform worker passes its own) and does not stop the rest of the batch.
|
|
36
|
+
Handoff failures retry after a constant one-second delay by default; callers provide `retryDelayMs`
|
|
37
|
+
for backoff. Public entitlement and authorization refusals now expose `SubmissionRefusalError` with
|
|
38
|
+
stable `code: "ACTION_SUBMISSION_REFUSED"`. The child inherits the parent's actor type: a
|
|
39
|
+
natural-person parent therefore does not enter agent HITL, while an agent parent follows the normal
|
|
40
|
+
agent approval route.
|
|
41
|
+
|
|
3
42
|
## 8.2.0
|
|
4
43
|
|
|
5
44
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -738,6 +738,8 @@ An approval workflow calls `resumeApprovedInvocation()` instead of invoking the
|
|
|
738
738
|
host authorizes the approver (using `authorizeApproval` when provided), then atomically persists the
|
|
739
739
|
decision and changes `waiting_for_approval` to either leased `running` or terminal `failed`. This keeps
|
|
740
740
|
the decision in the mutation ledger and prevents an approval/worker race.
|
|
741
|
+
`authorizeApproval` receives the parked `actionInvocationId` and durable, post-redaction `invocation`
|
|
742
|
+
record so the application can verify the approver's authority over that specific invocation.
|
|
741
743
|
|
|
742
744
|
```ts
|
|
743
745
|
await host.resumeApprovedInvocation(actionInvocationId, tenantId, spaceId, {
|
|
@@ -917,6 +919,11 @@ Only JSON-data parameters can be admitted. Anything else is refused as `invalid_
|
|
|
917
919
|
directly after `authorize`, before the composition callback and the check run. A SharedArrayBuffer or a view over one is refused this way,
|
|
918
920
|
since the caller could still write to its memory (test "a parameter backed by a SharedArrayBuffer …").
|
|
919
921
|
|
|
922
|
+
Entitlement and submission-authorization refusals throw `SubmissionRefusalError`. It remains an
|
|
923
|
+
`Error` with the existing message and adds stable `code: "ACTION_SUBMISSION_REFUSED"`, `actionId`,
|
|
924
|
+
and `refusal: "entitlement" | "authorization"` fields so internal dispatchers can distinguish a
|
|
925
|
+
definitive refusal from a retryable failure.
|
|
926
|
+
|
|
920
927
|
Only `{ admitted: true }`, read as an own data property, admits. Each of these makes
|
|
921
928
|
`submitAction` throw `ActionAdmissionError` (`code: "ACTION_ADMISSION_REFUSED"`, `refusal`, and
|
|
922
929
|
`refusalCode` for `refused`):
|
|
@@ -1206,11 +1213,13 @@ cast in an index expression is not immutable and PostgreSQL refuses it.
|
|
|
1206
1213
|
|
|
1207
1214
|
## Tenant isolation
|
|
1208
1215
|
|
|
1209
|
-
`tenantIsolationChecks()` and `runTenantIsolationChecks(subject)` run
|
|
1216
|
+
`tenantIsolationChecks()` and `runTenantIsolationChecks(subject)` run nine checks against a host
|
|
1210
1217
|
and its store. They prove scope on the invocation reads and writes, idempotency keys, event streams,
|
|
1211
|
-
worker claims, external completion, an optional governed read,
|
|
1212
|
-
writes they probe. The subject supplies a host, a recoverable
|
|
1213
|
-
scopes in different tenants. Adapter
|
|
1218
|
+
worker claims, staged-invocation lists and claims, external completion, an optional governed read,
|
|
1219
|
+
and adapter checkpoint reads and writes they probe. The subject supplies a host, a recoverable
|
|
1220
|
+
store, a registered action, and two scopes in different tenants. Adapter and staged checks create
|
|
1221
|
+
scoped probes and retire their parent invocations afterwards. The staged check is skipped for a
|
|
1222
|
+
store that does not advertise transactional staged invocations.
|
|
1214
1223
|
|
|
1215
1224
|
Host passes `AdapterInvocationScope` from the store-loaded action invocation to
|
|
1216
1225
|
`getAdapterInvocation(id, scope)` and `updateAdapterInvocation(id, patch, scope)`. Custom stores
|
|
@@ -1345,6 +1354,129 @@ CI certifies concurrent migrations, command idempotency, and schema behavior aga
|
|
|
1345
1354
|
Lakebase failover, backup, and restore remain adopter certification gates and are not implied by the
|
|
1346
1355
|
PostgreSQL CI result.
|
|
1347
1356
|
|
|
1357
|
+
## Staged descendant invocations
|
|
1358
|
+
|
|
1359
|
+
An action declaring `stagesInvocations` may return up to 32 child requests from its handler or
|
|
1360
|
+
`after_adapters` finalizer. A request key is 1–128 characters and matches
|
|
1361
|
+
`[A-Za-z0-9][A-Za-z0-9._:-]*`. The Host accepts at most 64 KiB of canonical JSON per request and
|
|
1362
|
+
256 KiB for the canonical batch. Keys are unique within the combined handler/finalizer batch.
|
|
1363
|
+
Every action ID must be allowlisted and registered, and each parameter object must pass the current
|
|
1364
|
+
child schema before the parent transaction commits. `redactActionParameters` runs during staging;
|
|
1365
|
+
the redacted, schema-valid parameters are the only parameters written to the stage record and later
|
|
1366
|
+
used to create the child invocation.
|
|
1367
|
+
|
|
1368
|
+
Staging requires `transactionWithEvents`, a transaction-scoped `stageInvocations`, and a store whose
|
|
1369
|
+
`transactionalStagedInvocations` flag is true. Host construction checks the advertised staged-store
|
|
1370
|
+
operations and atomic-store capability: scoped origin lookup, lease-fenced child creation, claim,
|
|
1371
|
+
renewal, lease-token rotation, settlement, cancellation, listing, and `transactionWithEvents`. The transaction object itself is available only when execution begins, so
|
|
1372
|
+
a missing transaction-scoped `stageInvocations` method is caught by the first staging action's
|
|
1373
|
+
execution preflight and interrupts that invocation. For `before_adapters`, the handler's batch
|
|
1374
|
+
commits with the handler mutation. For `after_adapters`, handler and finalizer requests commit with
|
|
1375
|
+
events and the completed parent verdict. A finalizing action retains its handler requests in the
|
|
1376
|
+
durable finalization checkpoint. Replaying an identical parent batch is a no-op; any changed key,
|
|
1377
|
+
action, version evidence, parameter digest, parameter value, batch size, or batch digest moves the
|
|
1378
|
+
parent to reconciliation without changing stored requests.
|
|
1379
|
+
|
|
1380
|
+
A pending stage is claimable only while its immutable parent scope matches and the parent is
|
|
1381
|
+
`completed`. Parents ending `failed`, `blocked_by_policy`, or `validation_failed` have pending stages
|
|
1382
|
+
cancelled by the sweep. `reconciliation_required` and nonterminal parents leave stages pending and
|
|
1383
|
+
unclaimed. `host.dispatchStagedInvocations` uses renewable leases, a monotonic lease token,
|
|
1384
|
+
per-submission timeout, retry availability, a default ten-row claim batch, and a default ten-attempt
|
|
1385
|
+
dead-letter budget. The normal Platform worker runs this sweep by default only when its Host registry
|
|
1386
|
+
contains an action with a non-empty `stagesInvocations` allowlist. `dispatchStagedInvocations: true`
|
|
1387
|
+
forces a sweep and `dispatchStagedInvocations: false` disables it. A deployment that has no staging
|
|
1388
|
+
declarations therefore does not query the migration-11 tables by default.
|
|
1389
|
+
|
|
1390
|
+
The child has a Host-only `stagedOrigin` of parent invocation ID plus stage key, backed by a separate
|
|
1391
|
+
unique index. It carries no idempotency key, so a public caller cannot pre-claim its identity with a
|
|
1392
|
+
staged-looking string. A crash after parent commit is recovered by a later sweep. A crash after child
|
|
1393
|
+
creation but before the stage checkpoint resolves the same child by `stagedOrigin`. PostgreSQL
|
|
1394
|
+
migration 11 creates `staged_invocations`, adds the nullable origin columns and partial unique index,
|
|
1395
|
+
and adds the claim and parent indexes.
|
|
1396
|
+
|
|
1397
|
+
Each claimed row goes through one settlement sequence (`dispatchStagedRow` in `src/host.ts`):
|
|
1398
|
+
|
|
1399
|
+
1. Host renews the claim's lease, skipping the row if the lease is gone, and looks the child up by
|
|
1400
|
+
`stagedOrigin` in the parent's tenant and space. Every claim replaces the lease token, so a
|
|
1401
|
+
creation still in flight from an earlier attempt holds an older token and fails the creation
|
|
1402
|
+
fence; this lookup therefore settles what earlier attempts did. A child found here goes to step 4
|
|
1403
|
+
without the pre-submission checks or any entitlement, authorization, or admission call.
|
|
1404
|
+
2. With no child, Host checks that the parent is still `completed` and that the child action is
|
|
1405
|
+
still registered, does not require capture-time authority, and accepts the stored parameters. It
|
|
1406
|
+
then submits through the shared submission pipeline under the claim's lease token, bounded by
|
|
1407
|
+
`submissionTimeoutMs`.
|
|
1408
|
+
3. If that submission's own fenced create returned the child, Host settles with its ID: `dispatched`
|
|
1409
|
+
when the handoff succeeded or no dispatcher is configured, otherwise a retry that records the ID
|
|
1410
|
+
(`dead_lettered` once the attempt budget is spent). In every other case (a failed pre-submission
|
|
1411
|
+
check, a refusal, an error, or a timeout) Host first rotates the lease token with
|
|
1412
|
+
`rotateStagedInvocationLease`. The only other possible creator is this attempt's own timed-out
|
|
1413
|
+
submission, which still holds the old token, so after the rotation it can no longer create the
|
|
1414
|
+
child and the lookup that follows is authoritative. A child found there goes to step 4. With none,
|
|
1415
|
+
a definitive refusal settles `rejected`, and anything else settles for retry or, at the attempt
|
|
1416
|
+
limit, `dead_lettered`.
|
|
1417
|
+
4. For a known child that is still `pending`, Host calls the configured dispatcher again with the
|
|
1418
|
+
stable workflow ID `action-invocation-<id>`, bounded by `submissionTimeoutMs`. A confirmed
|
|
1419
|
+
handoff, no dispatcher, or a child that has left `pending` settles `dispatched`; a failed or
|
|
1420
|
+
unconfirmed handoff settles for retry or `dead_lettered`. Both record the child ID.
|
|
1421
|
+
|
|
1422
|
+
Each settlement is one compare-and-set on the row's lease owner and current token, and a settlement
|
|
1423
|
+
that carries no child ID keeps one recorded earlier (both stores; shared contract test "keeps a
|
|
1424
|
+
recorded child id when a settlement carries none"). A settlement that finds the lease gone writes
|
|
1425
|
+
nothing, and the row's next claim starts again at step 1. A child lookup that fails settles the row
|
|
1426
|
+
for retry with the error `Staged child lookup failed.`, not as `rejected` or `dead_lettered`, even
|
|
1427
|
+
past the attempt limit. That attempt still counts, so the next attempt that can read the lookup and
|
|
1428
|
+
fails is dead-lettered if the budget is spent. A store
|
|
1429
|
+
error from renewal, rotation, or settlement ends only that row: the sweep counts it as failed,
|
|
1430
|
+
passes the error to `onError`, and continues with the next row. The Platform worker passes its own
|
|
1431
|
+
`onError`. Without a dispatcher, the submission that creates a child also executes it inline, as
|
|
1432
|
+
`submitAction` does; the stage settles `dispatched` once the child exists, even when that execution
|
|
1433
|
+
outlasts `submissionTimeoutMs`.
|
|
1434
|
+
|
|
1435
|
+
Child creation is fenced in the store operation that inserts the invocation. The stage must still be
|
|
1436
|
+
`pending`, owned by the submitting worker at the matching lease token, and unexpired. PostgreSQL
|
|
1437
|
+
locks the qualifying stage row `FOR SHARE` in a CTE before `INSERT ... SELECT`. A token rotation of
|
|
1438
|
+
that row therefore waits for an in-flight insert to commit, and an insert under the old token that
|
|
1439
|
+
reaches the row while a rotation is uncommitted waits for it and then inserts nothing; the live
|
|
1440
|
+
PostgreSQL contract tests observe both orders. The memory store's fence, insert, rotation, and
|
|
1441
|
+
settlements run without an intervening await. An existing child is returned regardless of the fence,
|
|
1442
|
+
but a stale attempt with no child reports lease loss and writes nothing. While a submission or a
|
|
1443
|
+
handoff runs, a heartbeat renews the row's current lease token.
|
|
1444
|
+
|
|
1445
|
+
Dispatch inherits the immutable parent's tenant, space, actor ID/type, and correlation ID; causation
|
|
1446
|
+
is the parent invocation ID. It does not copy the parent's authorization binding or approval decision.
|
|
1447
|
+
The child receives current entitlement, submission authorization, `admitSubmission`, runtime
|
|
1448
|
+
evidence, dispatcher, execution authorization, policy, and HITL handling as a fresh governed
|
|
1449
|
+
decision. Consequently, a natural-person parent produces a natural-person child and does not trigger
|
|
1450
|
+
agent HITL; an agent parent produces an agent child and follows the normal agent route. A definitive
|
|
1451
|
+
admission refusal, a submission authorization or entitlement refusal, or a failed pre-submission
|
|
1452
|
+
check settles `rejected` only after the token rotation and an authoritative lookup that finds no
|
|
1453
|
+
child. When that lookup finds one, as after a deploy that changes the schema of a child whose earlier
|
|
1454
|
+
handoff failed, the stage takes the existing-child path instead, so a `rejected` stage has no child.
|
|
1455
|
+
Admission failures/timeouts, store failures, and submission timeouts before child creation retry and
|
|
1456
|
+
eventually become `dead_lettered`. The default retry delay is a constant one second; callers provide
|
|
1457
|
+
`retryDelayMs(attemptCount)` when they need linear, exponential, jittered, or another backoff policy.
|
|
1458
|
+
A stage becomes `dispatched` only when the child exists and the current dispatcher handoff succeeds,
|
|
1459
|
+
no dispatcher is configured, or the child is no longer `pending`. A dispatcher failure after creation
|
|
1460
|
+
leaves the stage pending with the child ID and consumes the attempt. Exhausting the attempt budget
|
|
1461
|
+
moves it to `dead_lettered` while retaining that child ID: this terminal state means Host could not
|
|
1462
|
+
confirm dispatcher handoff, not that no child was created. A `dead_lettered` stage without a child ID
|
|
1463
|
+
was settled after an authoritative lookup found none. Later child policy blocks, approval waits,
|
|
1464
|
+
validation failures, or execution failures do not rewrite a dispatched stage. Stored stage errors
|
|
1465
|
+
are fixed messages and do not include request parameters or caught secret text:
|
|
1466
|
+
|
|
1467
|
+
- `Staged invocation dispatch failed.`
|
|
1468
|
+
- `Staged invocation submission was definitively refused.`
|
|
1469
|
+
- `Staged child dispatcher handoff was not confirmed.`
|
|
1470
|
+
- `Staged child lookup failed.`
|
|
1471
|
+
|
|
1472
|
+
`dispatchStagedInvocations` returns counters for the sweep. Each claimed row is counted once, as
|
|
1473
|
+
`dispatched`, `rejected`, or `failed`; `failed` also covers rows whose lease was lost before
|
|
1474
|
+
settlement and rows whose processing threw, and `deadLettered` is the part of `failed` settled as
|
|
1475
|
+
`dead_lettered`.
|
|
1476
|
+
|
|
1477
|
+
This release does not provide operator requeue of dead-lettered stages, lifecycle events for stage
|
|
1478
|
+
transitions, a parent-result field listing children, or per-stage release options.
|
|
1479
|
+
|
|
1348
1480
|
## Durable outbox egress
|
|
1349
1481
|
|
|
1350
1482
|
Configure `outbox` on `createGovernedActionHost()` only with an `OutboxPlatformHostStore`.
|