@fabricorg/platform-host 8.3.0 → 8.5.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,45 @@
1
1
  # @fabricorg/platform-host
2
2
 
3
+ ## 8.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 08274e6: Add host-owned approval deadlines/expiry, bounded JSON decision evidence, and opt-in atomic approval lifecycle events. Evaluators can supply an absolute deadline, or Hosts can set a default TTL. Due decisions and bounded worker/operator sweeps settle `failed` with expiry evidence and no approval decision; store serialization prevents a concurrent decision or sweep from applying twice. No deadline preserves indefinite waits, and no new ActionStatus is introduced.
8
+
9
+ Decision evidence is limited to 16 KiB of plain JSON verification references, validated and copied before resume store access and authorization. Credentials and tokens must not be included. Approval requested/decided/expired events default off. Opting in requires transactional approval/outbox capabilities at Host creation; event/state write failures roll back together, parking failures surface for recovery, and event IDs remain stable on retry. Event payloads include evidence presence rather than the evidence object. PostgreSQL users must run ensureSchema to apply additive migration 12 for the nullable expiry columns and due-expiry index before deploying; migrations 1-11 retain their original SQL/checksums.
10
+
11
+ Built-in HITL checkpoints are first-write-wins, preventing a delayed evaluator from changing a parked deadline. Resume of an unavailable action fails an approval without a deadline, preserving the previous terminal behavior; an approval with a deadline stays waiting with an error so a sweep can record its expiry verdict. That `Unknown action` failure appends no approval lifecycle event. Host fails before parking if a custom store drops the evaluator-supplied deadline. Custom approval stores must atomically enforce deadline expiry in beginApprovalDecision and serialize decisions with sweeps.
12
+
13
+ - 6002c61: Add the finalizing-action `ActionResult.adapterPreparation` channel and optional third
14
+ `AdapterStep.getInput` argument. The finalizing-action helper refuses returned preparation
15
+ without the Host's `adapterPreparationProtocol: "v1"` capability; actions returning none
16
+ retain their existing capability behavior. Non-finalizing actions returning preparation fail
17
+ before adapter delivery.
18
+
19
+ Preparation must be a strict-JSON plain-object record and counts toward the 256 KiB checkpoint
20
+ limit. It is persisted separately for crash replay, excluded from public results, handler data,
21
+ finalize input and event extraction, and removed with terminal settlement while retaining other
22
+ checkpoint evidence. Running and reconciliation-required invocations retain preparation.
23
+ Older checkpoints pass undefined to getInput. Derived adapter inputs remain subject to the
24
+ existing redactAdapterInput and adapter evidence retention contracts.
25
+
26
+ Preparation checkpoints use `atomic-after-adapters-v2`; checkpoints without preparation keep
27
+ `atomic-after-adapters-v1`. Replay accepts only the matching protocol/preparation shapes.
28
+ Settlement clears preparation and restores v1, including approval rejection. Handler capability
29
+ and manifest finalization protocols remain v1. Upgrade all workers before handlers return
30
+ preparation: older Hosts refuse in-flight v2 replay through interruption handling, requiring
31
+ reconciliation after recorded effects or settling failed without recorded effects. An older
32
+ Host does not clear preparation, so a row it settles keeps the v2 checkpoint and its
33
+ preparation; in PostgreSQL, find such rows by a terminal status with
34
+ `finalization_checkpoint->>'protocol' = 'atomic-after-adapters-v2'`. Reconcile in-flight v2
35
+ work before rolling workers back.
36
+
37
+ ## 8.4.0
38
+
39
+ ### Minor Changes
40
+
41
+ - 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.
42
+
3
43
  ## 8.3.0
4
44
 
5
45
  ### Minor Changes
package/README.md CHANGED
@@ -647,12 +647,46 @@ If transaction entry or COMMIT fails with an unknown outcome, Host raises
647
647
  verdict and result remain intact and are returned on re-execution.
648
648
 
649
649
  **Durable preparation and recovery.** The handler transaction commits a JSON
650
- `finalizationCheckpoint` with protocol `atomic-after-adapters-v1`, action version, ordered
650
+ `finalizationCheckpoint` with protocol `atomic-after-adapters-v1` without preparation, or
651
+ `atomic-after-adapters-v2` with preparation, plus action version, ordered
651
652
  adapter type/operation identities, normalized parameters and public handler data, handler
652
653
  events, public result, optional pending transition event and skipped indexes. Adapter inputs
653
654
  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
655
+ Finalizing `getInput(params, handlerResult, adapterPreparation?)` callbacks receive copies of
656
+ normalized parameters and public handler data on both paths. A handler can return a private
657
+ strict-JSON plain-object `ActionResult.adapterPreparation` alongside `data`. The optional third
658
+ argument receives a copy of that record (or `undefined`, including for older checkpoints).
659
+ It is separate from public handler data, public results, `finalize` input, event payloads and
660
+ `extractEvents` input. Only values the action explicitly derives into adapter inputs enter the
661
+ existing adapter-row and success-event input evidence, governed by `redactAdapterInput`.
662
+
663
+ Host validates the channel immediately after a successful handler returns, before staging,
664
+ event extraction, or adapter preparation. Non-finalizing actions returning it fail before
665
+ adapter delivery. Arrays, non-object records and lossy JSON values are rejected. Unlike public
666
+ handler data, preparation does not normalize Dates or drop undefined values. It is saved under
667
+ its own optional `finalizationCheckpoint.adapterPreparation` key and counts toward the 256 KiB
668
+ checkpoint limit. Settlement rewrites the checkpoint without that key and restores protocol
669
+ `atomic-after-adapters-v1`, preserving the remaining evidence:
670
+ completion clears it in the success transaction, and terminal failure, policy/validation refusal
671
+ or cancellation clears it with the status write. Running and `reconciliation_required`
672
+ invocations retain it for recovery or reconciliation. This retention applies to the private
673
+ checkpoint channel; derived adapter input evidence follows the existing retention contract.
674
+
675
+ The Host advertises `ctx.adapterPreparationProtocol === "v1"` for finalizing handlers.
676
+ `defineFinalizingAction` refuses a returned preparation record when that capability is absent,
677
+ after the handler returns; its existing finalization capability check still runs before the
678
+ handler. Returning no preparation preserves the earlier capability behavior.
679
+
680
+ Replay accepts v1 only without the preparation key and v2 only with it. Other combinations
681
+ use the existing interruption refusal path before adapter delivery. Older Hosts refuse v2
682
+ checkpoints through that path: recorded effects require reconciliation; no recorded effect
683
+ settles failed. An older Host does not clear preparation, so a row it settles keeps the v2
684
+ checkpoint and its preparation; in PostgreSQL, find such rows by a terminal status with
685
+ `finalization_checkpoint->>'protocol' = 'atomic-after-adapters-v2'`. Upgrade all workers before enabling handlers that return preparation, and
686
+ reconcile in-flight v2 work if workers are rolled back. The handler capability and manifest
687
+ finalization protocol remain `atomic-after-adapters-v1`; checkpoint versioning is separate.
688
+
689
+ Other actions retain raw two-argument inputs. Skip decisions are computed before
656
690
  preparation commits. Replay does not call `getInput` for recorded skips or succeeded rows.
657
691
  For an unstarted step, it recomputes input from the saved normalized values; callbacks must
658
692
  be deterministic for those values.
@@ -731,13 +765,16 @@ The evaluator returns `auto-execute`, `needs-approval`, `escalate`, or `rejected
731
765
 
732
766
  - `auto-execute` continues through policies, state validation, the handler, events, and adapters.
733
767
  - `needs-approval` and `escalate` persist the route and risk evidence, clear any worker lease, and park
734
- the invocation as `waiting_for_approval`. Polling workers never claim that status.
768
+ the invocation as `waiting_for_approval`. The built-in polling stores exclude that status
769
+ from execution claims.
735
770
  - `rejected` terminally fails before policies and mutation code run.
736
771
 
737
772
  An approval workflow calls `resumeApprovedInvocation()` instead of invoking the handler directly. The
738
773
  host authorizes the approver (using `authorizeApproval` when provided), then atomically persists the
739
774
  decision and changes `waiting_for_approval` to either leased `running` or terminal `failed`. This keeps
740
775
  the decision in the mutation ledger and prevents an approval/worker race.
776
+ `authorizeApproval` receives the parked `actionInvocationId` and durable, post-redaction `invocation`
777
+ record so the application can verify the approver's authority over that specific invocation.
741
778
 
742
779
  ```ts
743
780
  await host.resumeApprovedInvocation(actionInvocationId, tenantId, spaceId, {
@@ -753,6 +790,84 @@ Custom stores remain source-compatible when HITL is unused. To enable HITL they
753
790
  additive `ApprovalPlatformHostStore` capability. Both built-in stores implement it; Postgres users must
754
791
  call `ensureSchema()` so the HITL evidence and approval-decision columns are added.
755
792
 
793
+ ### Approval deadlines, evidence, and lifecycle events
794
+
795
+ `HostHitlDecision.approvalExpiresAt` accepts an absolute, valid `Date`. Alternatively,
796
+ `approvalDefaultTtlMs` supplies a positive safe integer of milliseconds from the successful
797
+ park attempt. The evaluator's deadline takes precedence. With neither option, the wait has
798
+ no deadline. An invalid deadline fails execution before parking; an invalid default TTL
799
+ throws when constructing the Host. A deadline already due is permitted and expires on a
800
+ sweep or decision attempt.
801
+
802
+ `host.expireDueApprovals({ tenantId?, spaceId?, now?, limit? })` settles due approval waits.
803
+ The default limit is 100, with valid limits from 1 to 1000. The worker calls it before claiming
804
+ work and reports a positive count as `expiredApprovals`, separately from execution failures.
805
+ The store compares the deadline to the transition's trusted `now`, including after approval
806
+ authorization: `decidedAt` cannot backdate acceptance. At or after the deadline, a decision
807
+ attempt settles expiry instead of recording that decision. Expiry uses `status: "failed"`,
808
+ retains `approvalExpiresAt`, records `approvalExpiry: { expiresAt, expiredAt }`, and leaves
809
+ `approvalDecision` absent. Rejections retain `approvalDecision.approved: false`. Repeated
810
+ sweeps and racing decisions return only the transition each caller applied. Expiry emits
811
+ ordinary failed-status telemetry. Saga coordination reads the child's terminal result and
812
+ uses its existing parent failure/compensation path, as it does for rejection; the Host does
813
+ not dispatch a saga signal itself.
814
+
815
+ A custom approval store needs `parkApproval` and `expireDueApprovals` to enable deadlines;
816
+ a Host requiring these capabilities fails the invocation before parking if they are missing.
817
+ A store implementing `parkApproval`/`expireDueApprovals` must also enforce the stored deadline
818
+ inside `beginApprovalDecision`: at or after it (`input.now >= approvalExpiresAt`), expire the row
819
+ instead of applying the decision. Decisions and sweeps must serialize so each wait settles once;
820
+ parking must check the lease and absence of a prior decision. Supplied lifecycle events/outbox
821
+ writes must commit atomically with their state changes. Host refuses to park if
822
+ `recordHitlDecision` drops a freshly evaluated deadline.
823
+ Existing stores without these optional capabilities retain indefinite approval behavior.
824
+ If an action definition is unavailable during resume, a wait without a deadline fails with
825
+ `Unknown action`; a wait with a deadline stays waiting with that error so the sweep can expire it.
826
+ That `Unknown action` failure is not an approval transition, so it appends no approval
827
+ lifecycle event even when `approvalLifecycleEvents` is enabled: consumers tracking open approvals
828
+ from these events should also close one when the invocation reaches a terminal status.
829
+ HITL checkpoints are first-write-wins in the built-in stores, so a late evaluator cannot revise a
830
+ parked deadline. Apply `PostgresPlatformHostStore.ensureSchema()` before deploying this feature:
831
+ migration 12 (`approval_lifecycle_expiry`) adds nullable `approval_expires_at` and `approval_expiry`
832
+ columns and a partial due-expiry index, preserving the SQL/checksums of migrations 1-11.
833
+
834
+ An approval decision may include `evidence`, a plain JSON object containing verification
835
+ references such as a binding ID, signature/verification reference, or receipt ID. Credentials
836
+ and tokens must not be included. `MAX_APPROVAL_EVIDENCE_BYTES` exports the 16 KiB serialized
837
+ UTF-8 limit. Host rejects non-JSON data, accessors, proxies, cycles, sparse arrays, non-finite
838
+ numbers, nesting beyond 64 levels, and oversized objects with a thrown error at resume entry,
839
+ before store reads or writes. It snapshots the decision before awaiting authorization and
840
+ passes independent decision and invocation copies to `authorizeApproval`. Both stores retain
841
+ evidence on `approvalDecision`; applications own the safety of its verification references.
842
+
843
+ Set `approvalLifecycleEvents: true` to enable `APPROVAL_REQUESTED_EVENT` (`ApprovalRequested`),
844
+ `APPROVAL_DECIDED_EVENT` (`ApprovalDecided`), and `APPROVAL_EXPIRED_EVENT` (`ApprovalExpired`).
845
+ Their typed payloads are `ApprovalRequestedPayload`, `ApprovalDecidedPayload`, and
846
+ `ApprovalExpiredPayload`. Request events contain route, risk tier, reason, and optional
847
+ `expiresAt`; decision events contain approval outcome, approver identity/type, optional reason,
848
+ `decidedAt`, and `evidencePresent`; expiry events contain `expiresAt` and `expiredAt`.
849
+ Payload dates are ISO strings. Decision events do not copy the evidence object.
850
+
851
+ The option defaults off. When enabled, Host construction requires `transactionalOutbox`,
852
+ `transactionWithEvents`, and `transactionalApprovalLifecycle` plus the approval methods.
853
+ Both built-in stores provide event atomicity when constructed with a transaction provider.
854
+ A state transition and its lifecycle event/outbox row commit together; append or update
855
+ failures roll them back. An expiry sweep commits its bounded batch as one unit, preventing
856
+ a later append failure from hiding earlier transitions from the result or status telemetry.
857
+ A parking transaction failure is surfaced for recovery without a
858
+ terminal failure overwrite. Earlier execution-start and HITL checkpoints remain durable.
859
+ Stable invocation-and-kind event IDs make transition retries first-write-wins. Event envelopes
860
+ reuse Host's domain/lifecycle builder, including tenant, actor, correlation, causation,
861
+ provenance, and declared consistency. The store allocates sequence inside the transaction.
862
+
863
+ Approval lifecycle events explicitly enabled by this option go to the outbox even when
864
+ `outbox.shouldPublish` excludes other Host lifecycle events. Existing classification,
865
+ producer-version, trace, actor-disclosure, and provenance hooks apply when configured.
866
+ Without `outbox` configuration, approval metadata uses `internal` classification, the action
867
+ version string as producer version, provenance included, and actor disclosure excluded from
868
+ the enterprise envelope. Configure those hooks before publishing approval reasons or approver
869
+ identifiers outside the mutation ledger.
870
+
756
871
  Sensitive values must not be passed as action parameters. Stage them in tenant-bound encrypted storage
757
872
  and pass an opaque identifier instead; action parameters are intentionally durable audit evidence.
758
873
  Hosts should additionally configure `redactActionParameters` as a fail-safe allowlist for actions