@fabricorg/platform-host 8.1.1 → 8.2.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 CHANGED
@@ -1,5 +1,58 @@
1
1
  # @fabricorg/platform-host
2
2
 
3
+ ## 8.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 71d306f: Add optional action-level finalization for atomic, immediate after_adapters actions, including non-idempotent actions. Export JSON finalization types, registration validation, a manifest protocol descriptor and defineFinalizingAction to guard handlers against older hosts.
8
+
9
+ Host requires atomic transactions, invocation locking and transaction-scoped lease fencing before application execution. On normal success the hook consumes stored redacted outputs and commits transaction-local domain writes, declared events, outbox and the merged result together. Hook or validation failure rolls back finalization and settles reconciliation_required when durable provider effects exist, or failed when there are none, with redacted error text. Lease loss escapes without a stale-worker verdict. Handler preparation writes remain separately committed.
10
+
11
+ If entry to or COMMIT of the final success transaction (after adapters) fails with an unknown outcome, Host raises RecoverableFinalizationError and records no verdict. An uncommitted invocation stays running for recovery; a committed completed invocation retains its stored verdict and result for re-execution.
12
+
13
+ Explicit undefined on the optional hook return fields events, resultPatch and eventSchemaVersion is treated as omitted. Payloads and result-patch values retain strict JSON validation and reject undefined.
14
+
15
+ Failed, ambiguous, degraded and accepted/handoff outcomes do not invoke the hook. Existing actions retain their lifecycle.
16
+
17
+ Before adapters, normalize hook parameters and public handler data through a JSON round-trip, stripping private handler carriers. Dates become ISO strings and undefined object properties drop; serialization failures such as cycles or BigInt fail before provider execution. Handler events are normalized separately. Hook event schema versions default to the action version when omitted.
18
+
19
+ Add optional Host eventContracts name validation for events returned by the finalize hook. It checks eventType and subjectType membership only; handler events, payload schemas and schema-version compatibility are outside this option's scope. Recovery refusal also preserves ambiguity evidence for in-flight adapter rows alongside succeeded effects.
20
+
21
+ - d5e7caa: Add optional adapter-step deadlineMs durations overriding the Host-wide duration,
22
+ including when the Host duration is unset or zero. Action and module registration, manifest export,
23
+ and manifest normalization reject step durations that are not positive finite integers.
24
+ Host validates resolved steps before handler or adapter execution. Invalid
25
+ durations fail a first execution without invoking adapters; retries with recorded
26
+ applied effects or unknown adapter outcomes require reconciliation instead.
27
+ Manifests preserve valid step durations.
28
+
29
+ Correct adapterDeadlineMs documentation: it is a duration starting just before
30
+ step retry execution, not an epoch timestamp. Host-wide behavior is unchanged.
31
+ Budgets span retries; expiry prevents a subsequent attempt and signals cooperative
32
+ cancellation of an in-flight call. An aborted call that throws retains the existing
33
+ ambiguous-outcome reconciliation semantics.
34
+
35
+ - f32aae6: Commit versioned finalization preparation with handler writes, bounded to 256 KiB of canonical UTF-8 JSON. Recovery skips the handler, reuses succeeded adapter outputs and durable skips, and finalizes through the locked success transaction, including non-idempotent actions. Finalizing getInput callbacks receive normalized public data; legacy callbacks retain raw inputs.
36
+
37
+ Unknown adapter outcomes, incompatible protocol/action versions/ordered step identities, handoffs and governance refusal after effects retain reconciliation semantics. Missing checkpoints retain prior recovery behavior. Preparation validation/write failures roll back before adapters; unknown commit outcomes and lease loss remain recoverable without a stale verdict.
38
+
39
+ Apply migration 10 with ensureSchema before deploying checkpoint-writing Hosts. Updates omitting the field do not require its column.
40
+
41
+ A checkpoint bypasses non-idempotent interruption gates only when recorded adapter rows are succeeded. Failed rows preserve interruption settlement without repeating the provider call: failed without recorded success, reconciliation_required after another step succeeded. Leased non-idempotent replay additionally requires store-level and transaction-level writeAdapterInvocation fencing. Idempotent finalizing actions retain failed-row retry under existing adapter retry rules, using saved preparation.
42
+
43
+ On the live path, a finalizing action with an earlier succeeded adapter and a later permanent adapter failure now settles reconciliation_required instead of failed, without running the hook. Subsequent execution failures after a recorded provider effect likewise preserve reconciliation of partial progress.
44
+
45
+ - 3d24330: Permit bounded leased retries of non-idempotent actions after retryable pre-effect governance errors. The additive deferredBeforeEffectAttempt record field proves only the immediately preceding claim deferred before handler or adapter effects. Missing or stale proof retains interruption failure. Marker-write failure retains terminal failure where persistence succeeds; lease loss preserves fencing errors. Approval resume, inline execution, exhausted budgets, and errors after the governance window retain their existing refusal semantics. Workspace dependents receive compatible patch releases.
46
+
47
+ PostgreSQL migration 9 (`pre_effect_governance_deferral`) adds the nullable marker column. Run `ensureSchema()` before deploying Hosts that may defer non-idempotent actions. Invocation updates that do not carry the marker omit its column, preserving writes on schemas without migration 9.
48
+
49
+ ## 8.1.2
50
+
51
+ ### Patch Changes
52
+
53
+ - a297fcd: Republish workspace dependents of `@fabricorg/platform`, including transitive dependents, alongside the optional keyed privacy before-checksums (issue #104). Workspace ranges resolve to the released version when packed. Runtime source is unchanged.
54
+ - 0a93df2: Republish workspace dependents of `@fabricorg/platform`, including transitive dependents, alongside the evidence-packet access-events section (issue #101). Workspace ranges resolve to the released version when packed. Runtime source is unchanged.
55
+
3
56
  ## 8.1.1
4
57
 
5
58
  ### Patch Changes
package/README.md CHANGED
@@ -55,7 +55,8 @@ immediately before the first policy-evaluation record is written. Host rethrows
55
55
  without a failure/status write when:
56
56
 
57
57
  - the invocation has a lease owner and a defined lease token;
58
- - the action declares `idempotent: true`;
58
+ - the action declares `idempotent: true`, or Host successfully records the current claim in
59
+ `deferredBeforeEffectAttempt` through a lease-fenced write;
59
60
  - `attemptCount` is below `retryableExecutionMaxAttempts`, a positive integer option defaulting to 5; and
60
61
  - execution is not a `resumeApprovedInvocation()` call.
61
62
 
@@ -65,12 +66,24 @@ deferrals do not increment `failed`. The worker counts by error type: a `Retryab
65
66
  that a store wrapper throws from Host's own reads or status writes also lands in `deferred`. In that
66
67
  case the row likewise stays leased for a later claim, but outside this cap. Retry becomes eligible when the current lease expires, so the
67
68
  delay is the remaining lease duration, followed by the worker's next claim; there is no immediate
68
- retry or separate backoff. The next claim increments `attemptCount`. At the cap, or for an unleased,
69
- non-idempotent, or approval-resume invocation, Host records `failed` with the reason retry was refused,
69
+ retry or separate backoff. The next claim increments `attemptCount`. At the cap, or for an unleased
70
+ or approval-resume invocation, Host records `failed` with the reason retry was refused,
70
71
  through the error-text redactor. Approval resume is excluded because its temporary lease does not
71
72
  establish that a recovery worker is configured. Ordinary application governance errors retain terminal failure behavior. Host store errors after
72
73
  a recorded provider effect follow the recovery rules below.
73
74
 
75
+ Non-idempotent governance retry requires a durable `deferredBeforeEffectAttempt` equal to the
76
+ preceding claim count. Each retry must record its own pre-effect deferral to permit another claim.
77
+ A missing or stale marker retains interruption failure and manual reconciliation. Marker-write
78
+ failure ends terminally when the verdict can be persisted; lease loss retains the recoverable
79
+ fencing error. Handler and adapter errors are outside this deferral window.
80
+
81
+ PostgreSQL migration 9 (`pre_effect_governance_deferral`) adds the nullable marker column.
82
+ Run `ensureSchema()` before deploying Hosts that may defer non-idempotent actions. Invocation
83
+ updates without the marker omit the column and continue to work on schemas without migration 9.
84
+ If a marker write fails, Host refuses retry with `pre-effect marker could not be recorded` when
85
+ it can persist the terminal verdict; lease loss preserves its fencing error.
86
+
74
87
  The retry region includes Host's single-record store writes for mutation-governance and HITL
75
88
  checkpoints. Later attempts reuse these persisted decisions through the `mutationFootprint` and
76
89
  `hitlRoute` guards. Callbacks should be safe to repeat and return stable invocation-scoped evidence.
@@ -133,7 +146,7 @@ Lifecycle records use deterministic checkpoint ids. Recovered idempotent actions
133
146
  adapter that already reached `succeeded`. Built-in stores deduplicate event and policy inserts by
134
147
  checkpoint ID. Adapter rows are mutable; stale-worker protection requires the optional
135
148
  `writeAdapterInvocation` capability described below. A
136
- reclaimed non-idempotent action inspects its adapter checkpoints: running or succeeded rows route
149
+ reclaimed non-idempotent action without compatible finalization preparation inspects its adapter checkpoints: running or succeeded rows route
137
150
  to reconciliation; no rows or only failed rows retain terminal failure for manual review.
138
151
 
139
152
  Returned `ambiguous` or `timeout` outcomes, timeouts thrown from `adapter.execute` (including an
@@ -201,8 +214,8 @@ On a store without atomic support, if the verdict cannot be written at all, no d
201
214
  call succeeds: neither verdict nor checkpoint is saved, as after a crash mid-call. A leftover `running`
202
215
  step can then re-run on recovery for an idempotent action under the same `adapterInvocationId`
203
216
  idempotency key, within the worker recovery budget. A reclaimed non-idempotent action with a
204
- running row without a matching durable handoff settles that step as unknown. A succeeded row or
205
- durable handoff instead ends `reconciliation_required` as an applied effect. Adapters should be
217
+ running row without a matching durable handoff settles that step as unknown. Without compatible
218
+ finalization preparation, a succeeded row or durable handoff instead ends `reconciliation_required` as an applied effect. Adapters should be
206
219
  idempotent or carry that stable key. The same exposure applies if two atomic attempts roll back
207
220
  and the fallback verdict cannot be written.
208
221
 
@@ -212,7 +225,7 @@ This includes later adapter checkpoints, obligation reads, and the completion wr
212
225
  `before_adapters` and `after_adapters`. Recovery also protects Host store operations before
213
226
  and around the handler when an earlier provider effect is recorded. Authorization, policy and handler callbacks
214
227
  retain their own failure semantics. Idempotent recovery repairs recorded success/handoff evidence
215
- without calling that step's provider. Non-idempotent interruption ends `reconciliation_required`
228
+ without calling that step's provider. Without compatible finalization preparation, non-idempotent interruption ends `reconciliation_required`
216
229
  instead of repairing the invocation. Lease loss retains its fencing error.
217
230
 
218
231
  If neither record is durable, Host retries the failed handoff or marker write once. After a
@@ -229,10 +242,12 @@ Recovery may replay that unrecorded outcome for an idempotent action under its s
229
242
  `adapterInvocationId`, within the worker budget. A reclaimed non-idempotent action instead
230
243
  ends `reconciliation_required`. A durable handoff takes precedence over its running row and is
231
244
  recorded as an applied effect with its external reference. A running row without such a handoff
232
- is settled as unknown. A succeeded row also ends `reconciliation_required`. With no rows or only failed rows, the existing
245
+ is settled as unknown. Without compatible finalization preparation, a succeeded row also ends `reconciliation_required`. With no rows or only failed rows, the existing
233
246
  non-idempotent interruption gates still fail the invocation.
234
247
 
235
- For idempotent recovery, the handler still runs before the adapter loop. The loop reads each
248
+ For idempotent recovery without an action finalizer, the handler still runs before the adapter loop.
249
+ Finalizing actions with compatible durable preparation replay without rerunning the handler;
250
+ missing or incompatible preparation retains effect-aware refusal. See [action-level finalization](#action-level-finalization). The loop reads each
236
251
  checkpoint before `getInput()` or adapter lookup. Succeeded rows and durable handoffs use stored
237
252
  adapter type, operation, vendor, input and output; a falsy new input or removed adapter does not
238
253
  skip evidence repair. Stable event IDs deduplicate replayed accepted and success events; newly
@@ -245,7 +260,7 @@ Host calls `extractExecutionAttestation` with stored input/output and checkpoint
245
260
  it must return deterministic evidence for the same input. A handoff checkpoint with no output,
246
261
  or withheld output, skips extraction. Repair errors remain recoverable within the budget.
247
262
 
248
- Known limits: a later step's definite provider failure or pre-call refusal can still end the
263
+ Known limits for actions without checkpointed finalization: a later step's definite provider failure or pre-call refusal can still end the
249
264
  invocation `failed` after an earlier step succeeded. Authorization denials, ordinary handler
250
265
  failures, validation/state-guard rejection, and unsatisfied policy obligations on recovery retain
251
266
  their existing terminal outcomes. These are decisions or application failures, not Host store
@@ -398,7 +413,6 @@ still treats a failure inside the handler callback as a handler outcome.
398
413
  failing, the row stays `running` although the provider was not called. For an action not declared
399
414
  idempotent, the next claim settles that step as an unknown provider outcome.
400
415
 
401
-
402
416
  A deadline sweep retains the handoff and sets `pendingCompletion.overdueAt` with its
403
417
  `reconciliation_required` verdict. A late callback settles that verdict only when the handoff also
404
418
  has `parkedAt`, proving Host reached parking after its remaining steps and finalization. An unparked
@@ -466,6 +480,22 @@ in the `before_adapters` phase stay appended. If the breaker opens between retry
466
480
  retrying and records the refusal. See the `@fabricorg/platform` README for thresholds and outcome
467
481
  mapping.
468
482
 
483
+ ## Adapter execution deadlines
484
+
485
+ `PlatformHostOptions.adapterDeadlineMs` is a duration in milliseconds, starting
486
+ after step input preparation, just before the step enters retry execution.
487
+ An action's `AdapterStep.deadlineMs` overrides that duration for the step, including
488
+ when the Host option is unset or zero. Step durations must be positive finite integers;
489
+ action and module registration, manifest normalization and export reject invalid
490
+ values. Host also validates resolved steps before handler or adapter execution.
491
+
492
+ The budget spans retry attempts and delays; it is not renewed per attempt.
493
+ Host supplies an epoch-millisecond `AdapterExecutionContext.deadlineMs` and an
494
+ `AbortSignal`. Adapters must observe the signal to abandon an in-flight call.
495
+ Expiry before an attempt prevents that attempt; an aborted call that throws follows
496
+ the existing ambiguous-outcome reconciliation path. With no step duration and an
497
+ unset or zero Host duration, neither context field is supplied.
498
+
469
499
  ## Execution authorization
470
500
 
471
501
  Submission authorization proves that a caller may create an invocation. Applications that delegate
@@ -562,7 +592,116 @@ evidence, so concurrent transitions serialize. Retrying the same identity and ev
562
592
  reusing it with contradictory evidence fails closed, and a distinct transition cannot rewrite a
563
593
  terminal parent. `createSagaParentActivities()` supplies deterministic identities for Temporal callers.
564
594
 
565
- Actions can optionally declare `resultSchema`. The Host validates public handler result data after
595
+ ### Action-level finalization
596
+
597
+ Define provider-dependent domain work with `defineFinalizingAction({ ...action, finalize })`.
598
+ This helper guards the handler against older hosts by requiring
599
+ `ctx.finalizationProtocol === "atomic-after-adapters-v1"`, including when no handler was
600
+ provided. An optional callback alone cannot make an older host refuse execution.
601
+
602
+ Finalizing actions declare `eventPhase: "after_adapters"`, atomic kind, and immediate
603
+ completion (or omit kind/completion). Non-idempotent actions are supported. Host refuses
604
+ execution before handler or adapters unless the store provides atomic mutation transactions,
605
+ invocation locking, and transaction-scoped lease fencing. There is no fallback.
606
+
607
+ `finalize(ctx, { params, handlerData, adapterOutputs })` receives transaction-bound `ctx.db`
608
+ and identity/provenance, without services. Each ordered step is either skipped or succeeded
609
+ with its index, adapter type, operation, invocation ID, and stored redacted JSON output.
610
+ Before adapters, Host strips `eventResultFields` from handler data and normalizes the hook's
611
+ parameters and public handler data using `JSON.parse(JSON.stringify(value))`, then validates
612
+ the normalized JSON. Dates become ISO strings, undefined object properties drop, and class
613
+ instances use their JSON representation; non-finite numbers become null. Cycles, BigInt or
614
+ other serialization failures fail the handler phase before adapter calls or adapter rows.
615
+ Handler events are normalized in that phase too. The hook receives copies of the saved
616
+ normalized inputs, so adapter input preparation cannot change them.
617
+ Perform transaction-local domain writes here; provider calls belong in adapters. Earlier
618
+ handler writes commit separately and are outside this atomic completion.
619
+
620
+ Return only `{ events?, resultPatch? }` with JSON-compatible values. Explicit `undefined` on
621
+ `events`, `resultPatch`, or an event's `eventSchemaVersion` is treated as omitted; undefined
622
+ payloads or nested payload/result-patch values are rejected. Events require non-empty
623
+ event/subject identifiers, a payload, and an optional positive integer schema version;
624
+ event types must be declared in `emitsEvents`. Host checks event and subject names against
625
+ `eventContracts` when supplied for hook-returned events only; handler events are outside
626
+ that option's scope. It checks name membership, without validating payload schemas or schema
627
+ version compatibility. Host owns envelope identity, scope, actor and timestamps.
628
+ Hook and handler events default `eventSchemaVersion` to `action.version` when omitted;
629
+ an explicit event schema version is preserved.
630
+ Handler events use `domain:<index>`, hook events use `finalize:<index>`, followed by
631
+ `state:transition`. The result patch merges shallowly over the public handler data; keys
632
+ in `eventResultFields` are rejected. For these actions `resultSchema` validates the merged
633
+ result inside the final transaction.
634
+
635
+ The final transaction locks and checks the running invocation before calling the hook.
636
+ Hook, validation, event-append or completion-write failure rolls back finalizer domain
637
+ writes, events, outbox and completion, then settles `reconciliation_required` through
638
+ applied-effect settlement when durable provider effects exist. With zero steps or all steps
639
+ skipped, it settles `failed` without claiming a provider effect. Error text uses Host error
640
+ redaction. Lease loss rolls back and
641
+ escapes without a stale-worker verdict. Failed, ambiguous, degraded and accepted/handoff
642
+ outcomes do not call the hook.
643
+
644
+ If transaction entry or COMMIT fails with an unknown outcome, Host raises
645
+ `RecoverableFinalizationError` and records no verdict. An uncommitted invocation stays
646
+ `running` for recovery; if COMMIT succeeded despite the error, its stored `completed`
647
+ verdict and result remain intact and are returned on re-execution.
648
+
649
+ **Durable preparation and recovery.** The handler transaction commits a JSON
650
+ `finalizationCheckpoint` with protocol `atomic-after-adapters-v1`, action version, ordered
651
+ adapter type/operation identities, normalized parameters and public handler data, handler
652
+ events, public result, optional pending transition event and skipped indexes. Adapter inputs
653
+ are excluded. Parameters derive from the already-redacted durable invocation input.
654
+ Finalizing `getInput` callbacks receive copies of normalized parameters and public handler
655
+ data on both paths; other actions retain raw inputs. Skip decisions are computed before
656
+ preparation commits. Replay does not call `getInput` for recorded skips or succeeded rows.
657
+ For an unstarted step, it recomputes input from the saved normalized values; callbacks must
658
+ be deterministic for those values.
659
+
660
+ On resumed running invocations or later attempts, a compatible checkpoint bypasses the
661
+ handler, including for non-idempotent actions. Succeeded rows supply their stored redacted
662
+ outputs; steps without rows can run because Host creates a running row before calling the
663
+ provider. Recovery retains entitlement, authorization and policy checks. A refusal after
664
+ an applied effect requires reconciliation. Finalization uses the same locked, lease-fenced
665
+ success transaction as live execution. A completed invocation returns its stored result.
666
+
667
+ Running or ambiguous adapter rows require reconciliation, before checkpoint compatibility
668
+ checks. Protocol, action version or step count/order/type/operation changes refuse replay;
669
+ retain compatible definitions while work is recoverable. Handoffs cannot replay a finalizer.
670
+ Refusals reconcile when an effect exists or may exist, and fail when no effect is recorded.
671
+ Missing checkpoints retain the previous recovery rules, including reconciliation after
672
+ succeeded adapter evidence. Recovery-attempt budgets and deadline/capability refusals remain
673
+ in force. A checkpoint bypasses non-idempotent interruption gates only when each recorded
674
+ adapter row is succeeded; missing rows may start, but failed rows retain interruption
675
+ settlement without another provider call. Failed rows do not prove the provider had no effect.
676
+ Settlement fails without recorded success and reconciles when another step succeeded.
677
+ For leased non-idempotent replay, both the store and mutation transaction must provide
678
+ `writeAdapterInvocation` for lease-fenced adapter creation and updates. Without either
679
+ capability the existing interruption gates remain in force. Idempotent finalizing actions
680
+ retain failed-row retry through the adapter loop under existing retry rules, using saved
681
+ preparation rather than rerunning the handler.
682
+
683
+ On the live path, a finalizing action whose earlier adapter succeeded and whose later adapter
684
+ returns a permanent failure settles `reconciliation_required`, rather than `failed`. The hook
685
+ does not run. This also applies to subsequent execution failures after a recorded provider
686
+ effect, so the terminal status preserves the need to reconcile partial progress.
687
+
688
+ Canonical checkpoint JSON is limited to **256 KiB in UTF-8**, a conservative fixed bound on
689
+ durable evidence and transaction size. Oversize or unserializable preparation fails before
690
+ adapters and rolls back handler writes. Store entry/commit errors with unknown outcomes raise
691
+ `RecoverableFinalizationError` without a terminal verdict. Checkpoint write failures roll
692
+ back preparation, so adapters do not run.
693
+
694
+ **Rollout:** apply PostgreSQL migration **10**, `finalization_preparation_checkpoint`, using
695
+ `ensureSchema()` before deploying Hosts that write checkpoints. It adds nullable
696
+ `action_invocations.finalization_checkpoint jsonb`. Updates without the checkpoint field do
697
+ not name the column and remain usable on older schemas. Custom stores must persist the field
698
+ atomically with handler writes and honor explicit `undefined` clearing. Memory storage follows
699
+ the same patch semantics. The saved transition event preserves the original emission decision;
700
+ `pendingStateTransition` alone cannot encode suppression by a handler-emitted transition.
701
+
702
+ ### Result validation
703
+
704
+ Actions without a finalizer can optionally declare `resultSchema`. The Host validates public handler result data after
566
705
  the handler and before canonical event append or adapter execution. Invalid results fail the invocation,
567
706
  emit no domain/completion event, invoke no adapter, and persist only a sanitized validation failure.
568
707
  Domain writes roll back only when the configured store transaction provider includes those writes.
@@ -891,7 +1030,7 @@ must match the current state and invocation. Missing provenance leaves normal va
891
1030
  custom stores can continue compiling without the optional method.
892
1031
 
893
1032
  After a valid transition for a binding with a non-empty entity ID and target, Host appends
894
- `StateTransitioned` immediately after the handler's domain events, following the action's
1033
+ `StateTransitioned` after the handler's domain events and any action-finalizer events, following the action's
895
1034
  `eventPhase`. Its subject is the bound entity and its version-1 payload is
896
1035
  `{ entityType, entityId, fromState, toState, causedByAction }`. `fromState` is the trusted state
897
1036
  read before the handler, and `causedByAction` is the action ID. The event uses a stable
@@ -900,7 +1039,8 @@ can filter it. It does not need an `emitsEvents` declaration from the action.
900
1039
 
901
1040
  For `before_adapters`, the transition is appended with the handler's domain events. For
902
1041
  `after_adapters`, it is deferred to finalization after required adapter steps succeed or
903
- accept a parked external handoff, immediately after domain events. Adapter failure withholds
1042
+ accept a parked external handoff, after domain events and any action-finalizer events.
1043
+ Actions with `finalize` reconcile accepted handoffs without calling the hook. Adapter failure withholds
904
1044
  both domain events and the transition, leaving the built-in stores' ledger-derived entity state
905
1045
  unchanged by that invocation even though handler writes may have committed. Custom stores that
906
1046
  read domain rows can already observe the target state at this point. With `transactionWithEvents`, the