@fabricorg/platform-host 7.6.0 → 8.1.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 +263 -0
- package/MIGRATION-8.md +247 -0
- package/README.md +471 -47
- package/dist/index.cjs +5943 -4393
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1619 -1405
- package/dist/index.d.ts +1619 -1405
- package/dist/index.js +5934 -4388
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,268 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 8.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e81defa: Add `eventAppendChecks()`, `runEventAppendChecks(subject)`, and their public subject, check,
|
|
8
|
+
and result types for custom store authors. Six checks exercise first-write-wins event appends
|
|
9
|
+
through plain, outbox and transaction paths, including buffered and committed duplicates and
|
|
10
|
+
plain-to-outbox behavior. Each check uses earlier and later duplicates with changed envelope and
|
|
11
|
+
metadata fields, including added map keys. Whole-record comparisons normalize key order, dates
|
|
12
|
+
and nulls; numeric strings are normalised only for `sequence`, `attemptCount` and `leaseToken`.
|
|
13
|
+
The first returned records supply snapshot references, allowing custom envelope fields and fields
|
|
14
|
+
the store omits or returns as null, including required fields. Transaction checks verify after commit
|
|
15
|
+
without requiring read-your-writes. Each committed-duplicate transaction also appends a fresh plain
|
|
16
|
+
control event after the duplicate and verifies its presence after commit to detect a silently
|
|
17
|
+
rolled-back unit of work.
|
|
18
|
+
Outbox snapshots cover pending, probe-leased, retry-pending and published rows, including lease
|
|
19
|
+
fields. Retry-pending accepts cleared or expired leases or the probe owner retained without an
|
|
20
|
+
expiry; a probe lease still live more than five seconds past the probe's clock reports a failure
|
|
21
|
+
to release it, and an expiry inside that margin is waited out as clock skew. Attempt counts and error formatting are
|
|
22
|
+
not prescribed. Retry and retirement availability use whole seconds a few seconds in the past,
|
|
23
|
+
tolerating coarse timestamp precision and small clock skew. Lease tokens come from claims, not
|
|
24
|
+
optional list views.
|
|
25
|
+
Absence checks require a fresh control outbox row.
|
|
26
|
+
|
|
27
|
+
These checks detect common first-write-wins violations: throwing, replacing or merging events,
|
|
28
|
+
keeping both events, selecting the earlier-ordered or later-ordered duplicate, adding outbox rows,
|
|
29
|
+
and resetting or rewriting an existing outbox row in the probed states. They are not exhaustive.
|
|
30
|
+
Known limits are rules comparing ordering fields in mixed directions, store-wide uniqueness across
|
|
31
|
+
spaces or tenants, the dead-letter state, and claims leasing foreign rows without returning them.
|
|
32
|
+
The checks also cannot detect a buffering transaction that merges fields the store never returns
|
|
33
|
+
(such as `includeProvenance`) from a buffered duplicate, or duplicate handling backed by an
|
|
34
|
+
in-process cache that works within one run but not across restarts. Stores requiring gapless
|
|
35
|
+
sequences, or unique keys over fields the control events share with the probe event, can fail
|
|
36
|
+
falsely.
|
|
37
|
+
|
|
38
|
+
Missing append capabilities skip; missing outbox verification or cleanup methods (including
|
|
39
|
+
`markOutboxPublished`) fail. Run in test or staging with outbox relays paused for the probe tenant.
|
|
40
|
+
Event timestamps use whole seconds slightly in the past; probes leave append-only events in dedicated
|
|
41
|
+
`event-append-probe` spaces. Successful outbox-creating checks mark the row published without
|
|
42
|
+
sending anything; final cleanup attempts to dead-letter visible pending control/failure rows.
|
|
43
|
+
Published exercises duplicate appends against that delivery state. Absence-control rows remain
|
|
44
|
+
dead-lettered; operator requeue tooling could publish them later, so exclude probe spaces from real
|
|
45
|
+
publication and requeue workflows.
|
|
46
|
+
|
|
47
|
+
Claims use real current time, a pending-probe-row limit and a 30-second lease. A filter-ignoring
|
|
48
|
+
claim can still lease foreign rows and make its own writes, such as incrementing attempts. The
|
|
49
|
+
checks write nothing to foreign rows and stop claiming for that run; later outbox checks fail as
|
|
50
|
+
skipped because of the earlier filter violation. Standalone checks stop claiming for that check.
|
|
51
|
+
After a filter violation, cleanup dead-letters only probe rows returned by that claim, using the
|
|
52
|
+
probe worker and claim tokens without further claims. The failure lists visible probe rows still
|
|
53
|
+
pending and instructs manual dead-lettering, including unreturned rows or failed retirements.
|
|
54
|
+
Leases lapse within the requested duration if the store honours it. Visible relay interference
|
|
55
|
+
fails with instructions to pause relays. These are additive exports; Host and shipped-store runtime behavior is unchanged.
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- 1effb28: Document first-write-wins event appends for custom store authors. Host re-appends lifecycle events under stable IDs on retry and recovery, with a fresh sequence and timestamps and sometimes a different payload, so `appendEvent` and `appendEventWithOutbox` must treat an event ID the store already holds as a no-op that keeps the first event, without throwing, replacing or merging it. A store that refuses these appends can leave recovery unable to converge; custom stores that throw on a conflicting append need to change. The shipped memory and PostgreSQL stores already conform and enforce ID uniqueness across the whole store, not per tenant or space. `appendEventWithOutbox` for an ID already stored adds no outbox row, even when the first append had none, so `outbox.shouldPublish` should be deterministic for a given event ID. No runtime behavior changes.
|
|
60
|
+
- 5af8a02: Lifecycle record ids no longer collide when their key is cut to length (issue #90).
|
|
61
|
+
|
|
62
|
+
Host derives event, attestation, reconciliation and policy-evaluation ids from a key that it sanitises and used to cut to 96 characters. Two keys that differed only after the cut used to get the same id, and since stores keep the first write for an existing id, the later record was dropped silently. The realistic case was a vendor `externalReference` long enough to cut off the outcome, which merged a second `ExternalOperationContradicted` event into the first.
|
|
63
|
+
|
|
64
|
+
A key longer than 96 characters after sanitising is now cut to 64 characters followed by `_` and a digest of the whole raw key, 97 characters in all. Being one character over the cap, it cannot equal the id of any key that fits. Ids whose key fits are byte-identical to earlier releases.
|
|
65
|
+
|
|
66
|
+
Upgrade note: only ids whose composed key exceeds 96 characters change. Getting there takes a long vendor-supplied value, such as an `externalReference` or external operation id, or an unusually long policy id, vendor, provider, operation name or custom invocation id. An invocation that is in flight across the upgrade and re-appends such a record during retry or recovery writes it under the new id, next to the one written before the upgrade.
|
|
67
|
+
|
|
68
|
+
Not changed: keys within the limit that differ only in characters outside `[A-Za-z0-9_-]` (for example policy ids `a.b` and `a/b`) still share an id. Changing that would alter almost every existing id, because host's own keys use `:` and policy ids conventionally use `.`.
|
|
69
|
+
|
|
70
|
+
## 8.0.0
|
|
71
|
+
|
|
72
|
+
### Major Changes
|
|
73
|
+
|
|
74
|
+
- 8d87a36: Release note: failure semantics (issue #79)
|
|
75
|
+
|
|
76
|
+
Once any adapter step has a durable succeeded row or accepted handoff, subsequent Host store
|
|
77
|
+
read/write errors raise RecoverableFinalizationError instead of failing the invocation. This
|
|
78
|
+
includes later checkpoints, recovery-time store operations, obligation reads and final completion
|
|
79
|
+
in either event phase. Idempotent recovery repairs recorded evidence without repeating that
|
|
80
|
+
provider call. A later step's definite failure or pre-call refusal can still fail the invocation.
|
|
81
|
+
Authorization/handler failures, validation/state rejection and unsatisfied obligations on recovery
|
|
82
|
+
retain their existing outcomes. The handler still re-runs and saga compensation is unchanged.
|
|
83
|
+
|
|
84
|
+
Recovery reads checkpoints before input generation or adapter lookup, replays accepted/success
|
|
85
|
+
events by stable ID (`recovered: true` on newly repaired success events), reuses existing stable-ID
|
|
86
|
+
attestations, and reapplies their obligation updates. Missing attestations can be extracted again
|
|
87
|
+
from stored safe input/output and checkpoint timestamps. Extractors must be deterministic for the
|
|
88
|
+
same input. No stored output means no extraction, including lost-marker handoffs and withheld output.
|
|
89
|
+
|
|
90
|
+
With neither success marker nor handoff durable, Host retries the failed checkpoint write once.
|
|
91
|
+
A second failure selects existing degraded atomic finalization or a verdict-first fallback to
|
|
92
|
+
reconciliation_required. Fallback checkpoint/events are best effort; terminal verdicts can retain
|
|
93
|
+
evidence gaps and automatic recovery excludes them. If even the verdict cannot be written, Host
|
|
94
|
+
raises RecoverableFinalizationError and a running row remains eligible for idempotent replay under
|
|
95
|
+
its stable adapterInvocationId within the worker budget. Lease loss retains its fencing error.
|
|
96
|
+
|
|
97
|
+
The optional recoveryMaxAttempts is a positive integer. Its effective default is
|
|
98
|
+
max(5, retryableExecutionMaxAttempts); Infinity is rejected. An explicit recovery budget below the
|
|
99
|
+
governance retry cap is rejected. Both caps count the same worker claims. With default settings,
|
|
100
|
+
pre-policy deferrals exhaust on claim 5; recovery exhaustion settles on claim 6 before mutation work.
|
|
101
|
+
Adopters who raised the governance cap get a matching implicit recovery budget.
|
|
102
|
+
|
|
103
|
+
The plain budget verdict commits before evidence reads. Running rows without a matching handoff
|
|
104
|
+
settle ambiguous; older ambiguous rows enrich the verdict from recorded evidence. A durable handoff
|
|
105
|
+
takes precedence over its running row and is recorded as an applied effect with its external
|
|
106
|
+
reference in the message and supported reconciliation record. Failed resolution/reads leave the
|
|
107
|
+
plain verdict intact. Failed enrichment can retain gaps. Transition telemetry emits once when
|
|
108
|
+
status changes. Unwritable verdicts leave claims continuing without executing handlers or providers.
|
|
109
|
+
|
|
110
|
+
Inline/direct execution does not increment the counter. These callers can now receive an error
|
|
111
|
+
whose name is RecoverableFinalizationError where 7.x returned failed; the class is not exported.
|
|
112
|
+
An unleased invocation remains running and is not claimed by workers. Callers must re-run
|
|
113
|
+
executeInvocation themselves and cap their own retries. Approval acceptance sets the counter to
|
|
114
|
+
at least 1 without incrementing it on resume. Approval's temporary lease uses the existing budget:
|
|
115
|
+
equal is admitted, above settles reconciliation. Approval does not reset the budget. A worker dying
|
|
116
|
+
before the check consumes a claim without calling a provider there. Parked handoffs are unleased
|
|
117
|
+
and are not claimed. ADR 0003 records the bounded exception to recoverable finalization.
|
|
118
|
+
|
|
119
|
+
An empty-reference handoff refusal retains its specific error if its verdict cannot be saved and
|
|
120
|
+
does not mark the step succeeded through generic degraded handling.
|
|
121
|
+
|
|
122
|
+
Both inline and leased non-idempotent interruption gates inspect adapter checkpoints: running rows
|
|
123
|
+
without a matching handoff settle unknown. Succeeded rows and durable handoffs end reconciliation
|
|
124
|
+
because a provider effect was applied. No rows or only failed rows retain failure. Idempotent
|
|
125
|
+
recovery still replays definite failed rows when invocation failure persistence was interrupted.
|
|
126
|
+
Known limit: if a later step's attempt-preparation write keeps failing, its row stays `running`
|
|
127
|
+
although the provider was not called, and a non-idempotent action's next claim settles that step
|
|
128
|
+
as an unknown outcome. Public options only grow; no store method is added. Adapter-row lease
|
|
129
|
+
fencing and overdue-sweep/late-callback behavior are unchanged.
|
|
130
|
+
|
|
131
|
+
- 4f56809: Fence adapter checkpoint creation and updates with the optional `writeAdapterInvocation` store
|
|
132
|
+
capability. Built-in stores atomically check the scoped parent lease or settled verdict and expected
|
|
133
|
+
adapter status; conditional writes cannot reset succeeded or ambiguous rows to running or failed.
|
|
134
|
+
Host captures execution credentials before reading live records. Lease/status refusal raises
|
|
135
|
+
`RecoverableFinalizationError`; changed verdict evidence is refused and reported through
|
|
136
|
+
`invocation.adapter_evidence_refused` telemetry. Memory transactions recheck at commit and
|
|
137
|
+
PostgreSQL locks the parent before updating or creating the child. Atomic finalizers require the
|
|
138
|
+
transaction-scoped capability when the store advertises it; callback writes use their transaction's
|
|
139
|
+
adapter-update seam. Parent enrichment joins the post-verdict evidence collector: a refusal is
|
|
140
|
+
reported through telemetry, a missing capability skips only enrichment, and a thrown error is raised
|
|
141
|
+
after the remaining evidence writes are attempted. Successful enrichment saves the parent error
|
|
142
|
+
and reconciliation outcome for replay. Verdict conditions carry `completionAdapterInvocationId`
|
|
143
|
+
and check the external-completion snapshot for that handoff row, preserving real callback output
|
|
144
|
+
even when its parent status/error
|
|
145
|
+
stays the same. Unrelated rows retain status/error and expected-row-status fencing. Isolation probes
|
|
146
|
+
retire their parent rows, and prepare-attempt lease refusals bypass provider retry/backoff.
|
|
147
|
+
|
|
148
|
+
This is part of the declared 8.0.0 major. No schema migration is added. Custom stores without the
|
|
149
|
+
capability remain compatible with unchecked adapter writes and a weaker stale-worker guarantee.
|
|
150
|
+
Inline guard reads and transactions apply only when the execution snapshot or patch carries a
|
|
151
|
+
handoff. Handoffs imported into running invocations by other writers are unsupported.
|
|
152
|
+
See MIGRATION-8.md.
|
|
153
|
+
|
|
154
|
+
Public API additions: `AdapterInvocationWriteCondition`, `AdapterInvocationPatch`,
|
|
155
|
+
`AdapterInvocationWriteInput`, and `UpdateSettledActionInvocationInput`;
|
|
156
|
+
optional `PlatformHostStore.writeAdapterInvocation` and `updateSettledActionInvocation`;
|
|
157
|
+
and optional `PlatformHostMutationTransaction.writeAdapterInvocation`.
|
|
158
|
+
`PlatformHostTelemetryEventName` gains `invocation.adapter_evidence_refused`, which requires
|
|
159
|
+
updates to exhaustive switches over that union. Subclasses customizing unchecked adapter writes
|
|
160
|
+
must also customize the checked write seam, or disable it and accept the weaker guarantee.
|
|
161
|
+
|
|
162
|
+
- 438be3f: Release note: late completion semantics (issue #79).
|
|
163
|
+
|
|
164
|
+
Overdue sweeps retain the handoff and set optional
|
|
165
|
+
`pendingCompletion.overdueAt` with the reconciliation verdict. Callbacks for
|
|
166
|
+
parked running invocations can settle even past their deadline before the
|
|
167
|
+
sweep. From reconciliation, settlement requires parking and the marker or the
|
|
168
|
+
fallback below. Callbacks for unparked invocations already
|
|
169
|
+
`reconciliation_required` retain the verdict and record redacted provider
|
|
170
|
+
evidence without result-schema validation. Unparked `running` invocations
|
|
171
|
+
still refuse callbacks with `still_executing`. Any parked row without the
|
|
172
|
+
marker, including a 7.x row, retains the weaker `"did not complete by"`
|
|
173
|
+
error-substring compatibility path. PostgreSQL hydrates the marker as a Date
|
|
174
|
+
from existing JSONB; no migration changes are required.
|
|
175
|
+
|
|
176
|
+
Sweeps re-read eligibility under the transaction row lock where supported and
|
|
177
|
+
use transaction-scoped reconciliation evidence when available. Inline
|
|
178
|
+
post-handoff writes preserve the sweep verdict while committing independent
|
|
179
|
+
adapter evidence. Reaching parking records the handler result and `parkedAt`
|
|
180
|
+
while keeping status, error, and `overdueAt`. Successful `after_adapters`
|
|
181
|
+
finalization commits its deferred domain events; without a local handoff it
|
|
182
|
+
records the result/events without fabricating parking. Inline callers receive
|
|
183
|
+
the latest reconciliation record observed by the preserving writer, merged
|
|
184
|
+
with its evidence update, without another read or a stale attempt fallback.
|
|
185
|
+
Leased paths retain `RecoverableFinalizationError` on fencing failure.
|
|
186
|
+
Verdict-first fallbacks attempt evidence writes independently, including
|
|
187
|
+
accepted/succeeded/ambiguous events, governance reconciliation, the outer
|
|
188
|
+
refused-handoff reconciliation append, and adapter checkpoints. A failed write
|
|
189
|
+
does not skip later evidence; the first failure raises
|
|
190
|
+
`RecoverableFinalizationError` after the remaining attempts. This also replaces
|
|
191
|
+
post-success fallback error suppression. The verdict remains durable and failed
|
|
192
|
+
writes can leave terminal evidence gaps. Atomic finalization and its outer
|
|
193
|
+
logging behavior retain their existing semantics; sweep errors are preserved.
|
|
194
|
+
Stores without the transaction lock retain a read/write race; without transaction-scoped reconciliation appends,
|
|
195
|
+
evidence can survive a rolled-back verdict. Neither check cancels an adapter
|
|
196
|
+
already in flight or undoes previously committed domain writes. Custom stores
|
|
197
|
+
must preserve and reload the optional marker.
|
|
198
|
+
|
|
199
|
+
Refused handoffs now retain their provider reference in the step's succeeded
|
|
200
|
+
event, including when explicit output omits it. Capable transactions append
|
|
201
|
+
the refusal reconciliation record with that evidence; fallback writes attempt
|
|
202
|
+
it independently of the other evidence writes. The outer append uses the same
|
|
203
|
+
stable id. An event failure does not skip reconciliation or the adapter marker.
|
|
204
|
+
|
|
205
|
+
Inline callers should retry only before a handoff is durable: re-running
|
|
206
|
+
`executeInvocation()` on an unleased running handoff returns without resuming
|
|
207
|
+
(it first settles any step already recorded `ambiguous`).
|
|
208
|
+
If execution stopped before parking, callbacks refuse with `still_executing`
|
|
209
|
+
until a sweep. Declare `completionDeadlineMs` for actions that hand off, then
|
|
210
|
+
reconcile surfaced rows per the external-completion runbook.
|
|
211
|
+
|
|
212
|
+
Handoff-free inline writes skip the guard's extra read and transaction. The
|
|
213
|
+
sweep accepts null lease owners from custom stores. Custom transactions should
|
|
214
|
+
implement both invocation locking and reconciliation appends to avoid blocking
|
|
215
|
+
an outer append on the invocation lock. Upgrade hosts and workers together:
|
|
216
|
+
7.x sweeps omit the typed marker, and 7.x inline writers lack the new guard.
|
|
217
|
+
|
|
218
|
+
- 8d87a36: Version 8 changes the meaning of some invocation statuses: outcomes Host cannot know, and Host's own write failures after a provider call, settle `reconciliation_required` where 7.x reported `failed`. The public API only grows. `MIGRATION-8.md` in the package lists what to check before upgrading.
|
|
219
|
+
|
|
220
|
+
### Minor Changes
|
|
221
|
+
|
|
222
|
+
- a205c48: Reconcile unknown adapter outcomes (issue #76). Platform's README describes the Host behavior; Platform runtime code is unchanged.
|
|
223
|
+
|
|
224
|
+
**Release note: failure semantics (issue #76).** Returned `timeout` outcomes, timeouts thrown from `adapter.execute` (including an aborted adapter deadline), contradictory results in either direction (`success: true` with a non-success outcome, or `success: false` with `outcome: "success"`), uninterpretable replies (non-objects, `null`, or arrays), getters that throw before Host interprets the reply, and unrecognised outcome strings such as `"timed_out"` now end `reconciliation_required` instead of `failed`, using the existing `adapter_outcome_ambiguous` kind and `AdapterInvocationAmbiguous` event payload and stable ID. Returned `ambiguous` outcomes use the same settlement path. These unknown outcomes are not retried by the retry helper. Governance stores receive `ExternalReconciliation` evidence even without provider `outcomeEvidence`, because a timed-out call may have applied its effect without returning an external reference.
|
|
225
|
+
|
|
226
|
+
Host detects a timeout thrown from `adapter.execute` from the adapter deadline's aborted signal or an error message matching `/timeout|deadline|timed?\s*out/i`. A definite rejection whose message matches therefore also routes to reconciliation; the matcher does not prove a provider timeout. Reverse contradictions are classified as ambiguous for retry and circuit-breaker accounting, rather than as success.
|
|
227
|
+
|
|
228
|
+
Host copies the reply's contract fields and other own enumerable fields into a plain object before interpretation, reading each top-level field once. A getter that throws during the copy takes the unreadable-reply path. Object replies retain Host's previous truthiness reading of `success`, including truthy non-boolean values from JavaScript adapters.
|
|
229
|
+
|
|
230
|
+
Unknown-outcome settlement uses `transactionWithEvents` when the transaction has `updateAdapterInvocation`, governance `appendExternalReconciliation` where applicable, and leased `updateLeasedActionInvocation` where required. Host commits the ambiguous checkpoint, stable ambiguity event, reconciliation record, and fenced terminal verdict together, in that write order. It retries the transaction once on a non-lease error; a second non-lease failure selects the verdict-first fallback. Lease loss raises `RecoverableFinalizationError` without retry or fallback.
|
|
231
|
+
|
|
232
|
+
The fallback writes `reconciliation_required` first through the existing verdict helper (one retry except on lease loss), then writes checkpoint, event, and reconciliation evidence. After the verdict commits, Host attempts each remaining evidence write (checkpoint, event, record), then surfaces the first error as `RecoverableFinalizationError`. This includes attempting the reconciliation record after an event-write failure and attempting both event and record when both checkpoint shapes fail. Evidence failure can leave a terminal evidence gap that recovery does not repair. If both verdict attempts fail, Host raises `RecoverableFinalizationError` without attempting an unfenced checkpoint. With the original action definition and step order available, recovery re-settles a saved ambiguous row before parked-handoff shortcuts, stale-action gates, authorization, or handler execution, for idempotent and non-idempotent actions. The pending-handoff shortcut retains its base condition (running with a pending handoff and no lease owner, without requiring `parkedAt`); its position after the new recorded-ambiguity check is the change. For a running invocation whose action resolves, `executeInvocation` reads each adapter step's row before the shortcut, stopping on recorded ambiguity or a failed read. A failed read surfaces `RecoverableFinalizationError`, where base 939bcad returned the parked result. If the action no longer resolves, the shortcut still returns the handoff before the unknown-action failure.
|
|
233
|
+
|
|
234
|
+
The optional `AdapterInvocationRecord.reconciliation` checkpoint preserves redacted reason, external reference, and the original event subject; the subject is excluded from the returned `AdapterReconciliationOutcome`. Recovery does not scan the space's event log. Legacy rows use the recorded error as message and reason, and default to the `AdapterInvocation` subject keyed by `adapterInvocationId`. Stable event and reconciliation IDs retain deduplication.
|
|
235
|
+
|
|
236
|
+
Ordinary adapter writes omit the migration 8 column. On checkpoint-write failure, fallback retries once without `reconciliation`, retaining legacy `status: "ambiguous"` plus `error`. Schemas without migration 8 still settle unknown outcomes, without the checkpoint metadata. Atomic PostgreSQL failures roll back before this legacy write runs outside the transaction. Apply migration 8 (`adapter_reconciliation_checkpoint`) through controlled `ensureSchema()` to retain metadata. Existing migration checksums are unchanged. Custom stores should persist the optional checkpoint through adapter create/update/read methods.
|
|
237
|
+
|
|
238
|
+
On a store without atomic support, if the verdict cannot be written at all, no durable write after the call succeeds (neither verdict nor checkpoint), as after a crash mid-call. Idempotent recovery may re-run a leftover running step under the same `adapterInvocationId` idempotency key. The same exposure applies if two atomic attempts roll back and the fallback verdict cannot be written. Non-idempotent recovery retains its stale-action gate without an ambiguous checkpoint. Before another provider attempt, Host resets a prior failed row to `running` and clears its old error.
|
|
239
|
+
|
|
240
|
+
Post-success Host write failures still end `failed`, as in 7.6.0, even though the provider succeeded. Accepted-handoff persistence and its `ExternalOperationAccepted` event, the succeeded marker, attestation extraction and recording, and `AdapterInvocationSucceeded` failures again write the adapter row `failed` and fail the invocation. Later execution returns that terminal verdict; lease loss retains its fencing error. If fencing refuses the failure verdict after the adapter row was saved as `failed`, idempotent recovery can re-run the step; this pre-existing 7.6.0 case remains for follow-up. A follow-up issue will cover these post-success failures. The existing 7.6.0 degraded-success paths for withheld output and refused handoffs retain their reconciliation semantics and documented re-call exposure when the verdict cannot be persisted. Final `after_adapters` domain-event/invocation completion retains its recovery path.
|
|
241
|
+
|
|
242
|
+
A deadline sweep followed by a late completion callback can settle `completed` when the `ambiguous` evidence of a later step was never written.
|
|
243
|
+
|
|
244
|
+
Circuit-open refusals, deadline expiry before execution (including between retry attempts), `prepareAttempt` store errors, returned `permanent_failure`, exhausted `transient_failure`, and exhausted non-timeout adapter throws retain `failed` and their existing event shapes. Adapters already express definite non-effects through `permanent_failure` and `transient_failure`; there is no new adapter-side definite-failure flag. `after_adapters` completion events remain withheld while reconciliation is required.
|
|
245
|
+
|
|
246
|
+
### Patch Changes
|
|
247
|
+
|
|
248
|
+
- 939bcad: Build packages with tsdown in place of tsup. Preserve the published ESM/CommonJS
|
|
249
|
+
entry paths, ES2022 target, declaration filenames, and raw external module
|
|
250
|
+
specifier spellings, including the existing Node built-in spellings.
|
|
251
|
+
|
|
252
|
+
Generated JavaScript, source-map contents, declaration formatting, and internal
|
|
253
|
+
chunk layout change. Comparison with the saved tsup build found unchanged named
|
|
254
|
+
runtime exports and public declaration signatures. Declaration maps remain
|
|
255
|
+
unpublished. Facade maps are no longer published for Platform's root, evidence,
|
|
256
|
+
and policies entries, or experience-runtime's contracts entry (both `.js.map`
|
|
257
|
+
and `.cjs.map`). Their new JavaScript output has no source-map URL comment.
|
|
258
|
+
|
|
259
|
+
CommonJS interop: 29 of 72 baseline CJS files expose an `__esModule` marker;
|
|
260
|
+
0 of 57 tsdown CJS files do. The compared package entries have no default runtime
|
|
261
|
+
export, and the emitted `.d.cts` files declare none. Typed consumers of the
|
|
262
|
+
unchanged declared exports are unaffected. For entries whose marker was removed,
|
|
263
|
+
an untyped CJS `import X from` compiled with a Babel-style default-interop helper
|
|
264
|
+
now yields the namespace instead of `undefined`.
|
|
265
|
+
|
|
3
266
|
## 7.6.0
|
|
4
267
|
|
|
5
268
|
### Minor Changes
|
package/MIGRATION-8.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Migrating to @fabricorg/platform-host 8
|
|
2
|
+
|
|
3
|
+
**Version 8 changes what some invocation statuses mean.** The public API only
|
|
4
|
+
grows; no export is removed or renamed. But an invocation that 7.x recorded as
|
|
5
|
+
`failed` may now end `reconciliation_required`, and code that reads `failed` as
|
|
6
|
+
"the provider did nothing" has to handle the new outcome. That change in meaning
|
|
7
|
+
is why this is a major release.
|
|
8
|
+
|
|
9
|
+
## Unknown adapter outcomes settle `reconciliation_required` (issue #76)
|
|
10
|
+
|
|
11
|
+
In 7.x, an adapter call whose outcome Host could not know ended `failed`. That
|
|
12
|
+
covered a thrown timeout, a returned `timeout`, and a reply Host could not read.
|
|
13
|
+
The provider may have acted, so a caller that retried on `failed` could repeat
|
|
14
|
+
an SMS or a submission.
|
|
15
|
+
|
|
16
|
+
From 8.0.0 these outcomes settle like a returned `ambiguous` always has:
|
|
17
|
+
|
|
18
|
+
- the adapter row ends `ambiguous`;
|
|
19
|
+
- Host appends an `AdapterInvocationAmbiguous` event and, on governance stores,
|
|
20
|
+
an `ExternalReconciliation` record;
|
|
21
|
+
- the invocation ends `reconciliation_required` with `adapterReconciliation`.
|
|
22
|
+
|
|
23
|
+
Refusals that made no call (circuit open, an expired deadline) and definite
|
|
24
|
+
provider failures still end `failed`.
|
|
25
|
+
|
|
26
|
+
### What to check
|
|
27
|
+
|
|
28
|
+
- **Callers and retry logic.** Treat `reconciliation_required` as "a person or
|
|
29
|
+
a reconciliation job must establish what happened". Do not retry it the way
|
|
30
|
+
you retry `failed`.
|
|
31
|
+
- **Status fences in your own store or domain code.** A fence that admits only
|
|
32
|
+
`completed` or `failed` as terminal statuses will refuse the new verdict, and
|
|
33
|
+
the invocation will stay `running` and be recovered again. Admit
|
|
34
|
+
`reconciliation_required`, at least when the invocation has an `ambiguous`
|
|
35
|
+
adapter row.
|
|
36
|
+
- **PostgreSQL.** Run `ensureSchema()` to apply migration 8, which adds
|
|
37
|
+
`adapter_invocations.reconciliation`. Host still settles on a schema without
|
|
38
|
+
it, but stores less evidence.
|
|
39
|
+
- **Custom stores.** Persist the optional
|
|
40
|
+
`AdapterInvocationRecord.reconciliation` field and accept it in
|
|
41
|
+
`updateAdapterInvocation` patches.
|
|
42
|
+
|
|
43
|
+
## Truthful, bounded post-provider recovery (issue #79 part 1)
|
|
44
|
+
|
|
45
|
+
In 7.x, a Host write could fail after the provider reported success and leave
|
|
46
|
+
the invocation `failed`. Host 8 makes those errors recoverable once a success
|
|
47
|
+
marker or handoff is durable. Host store reads and writes stay recoverable
|
|
48
|
+
after that point in either event phase. This includes later checkpoints,
|
|
49
|
+
obligation reads, and the final completion write. Idempotent recovery repairs
|
|
50
|
+
the recorded step without calling its provider again. The handler still
|
|
51
|
+
re-runs before the adapter loop.
|
|
52
|
+
|
|
53
|
+
Non-idempotent recovery ends `reconciliation_required` instead of repairing
|
|
54
|
+
the invocation. A durable handoff takes precedence over its running row. Host
|
|
55
|
+
records it as an applied effect and keeps the external reference in the
|
|
56
|
+
message. A governance store also receives that reference in its reconciliation
|
|
57
|
+
record. A running row without a durable handoff is settled as unknown. A
|
|
58
|
+
succeeded row ends `reconciliation_required` as an applied effect. No rows or
|
|
59
|
+
only failed rows keep the existing interruption failure.
|
|
60
|
+
|
|
61
|
+
If neither checkpoint is durable, Host retries the failed write once. A
|
|
62
|
+
second failure selects degraded atomic finalization or a verdict-first
|
|
63
|
+
fallback. The fallback attempts the succeeded marker and safe events
|
|
64
|
+
independently, raising the first evidence error as
|
|
65
|
+
`RecoverableFinalizationError` after the remaining writes are attempted. A
|
|
66
|
+
terminal verdict can still have missing evidence. If the verdict cannot be
|
|
67
|
+
saved, Host raises `RecoverableFinalizationError`. The row can remain
|
|
68
|
+
running. An idempotent action can call the provider a second time for that
|
|
69
|
+
unrecorded outcome. It can also call the provider again for a definite
|
|
70
|
+
failed row whose invocation verdict was lost. An empty-reference handoff
|
|
71
|
+
refusal keeps its specific message if its verdict write fails. That failure
|
|
72
|
+
does not create a succeeded marker.
|
|
73
|
+
|
|
74
|
+
The optional `recoveryMaxAttempts` must be a positive integer. Its effective
|
|
75
|
+
default is `max(5, retryableExecutionMaxAttempts)`. Infinity is rejected. An
|
|
76
|
+
explicit recovery budget below the governance retry cap is rejected too. Both
|
|
77
|
+
settings count worker claims. The first execution claim counts as 1. With the
|
|
78
|
+
default of 5, the first run and four recoveries can execute. The sixth claim
|
|
79
|
+
settles `reconciliation_required` before the handler or adapters run.
|
|
80
|
+
Transient governance deferrals reach their own default cap on claim 5 first.
|
|
81
|
+
|
|
82
|
+
On exhaustion, Host writes a plain verdict before reading evidence. A readable
|
|
83
|
+
running step without a handoff is settled as unknown. A recorded ambiguous row
|
|
84
|
+
conditionally enriches the parent error and `adapterReconciliation` with its
|
|
85
|
+
saved evidence; successful enrichment makes response and replay agree. A handoff is recorded as an
|
|
86
|
+
applied effect instead. Failed reads leave the plain verdict already written
|
|
87
|
+
standing. Enrichment refusal or a missing capability skips only that parent write;
|
|
88
|
+
checkpoint, event and reconciliation evidence are still attempted. Store errors
|
|
89
|
+
are raised after the remaining independent writes. If the verdict itself
|
|
90
|
+
cannot be saved, later claims continue without running mutation code.
|
|
91
|
+
|
|
92
|
+
Inline and direct execution do not increment the claim counter. Approval
|
|
93
|
+
acceptance sets it to at least 1 without adding a claim on resume. Approval
|
|
94
|
+
resume uses its temporary lease and the existing count. A count equal to the
|
|
95
|
+
budget is admitted. A count above it settles reconciliation before the handler
|
|
96
|
+
runs. Approval does not reset the budget. A worker dying before the check
|
|
97
|
+
consumes a claim without calling a provider there. Parked handoffs have no
|
|
98
|
+
lease and are not claimed by workers.
|
|
99
|
+
|
|
100
|
+
Known limits remain. A later step's definite failure or pre-call refusal can
|
|
101
|
+
still end `failed` after an earlier success. Authorization and ordinary
|
|
102
|
+
handler failures on recovery keep their existing outcomes. Validation, state
|
|
103
|
+
guards, and unsatisfied obligations also keep their terminal outcomes. These
|
|
104
|
+
are not Host store errors. Saga compensation is unchanged.
|
|
105
|
+
|
|
106
|
+
### What to check
|
|
107
|
+
|
|
108
|
+
- **Worker budgets.** If you raised `retryableExecutionMaxAttempts`, the
|
|
109
|
+
implicit recovery budget now rises with it. An explicit
|
|
110
|
+
`recoveryMaxAttempts` must be at least that retry cap. Both settings use the
|
|
111
|
+
same claim counter. Recovery also bounds evidence and completion failures.
|
|
112
|
+
- **Evidence extractors.** Return deterministic evidence for the same stored
|
|
113
|
+
input and output. Extraction can run again if the attestation was not saved.
|
|
114
|
+
Host reuses an existing stable-ID attestation and reapplies its obligation
|
|
115
|
+
updates. Missing output means extraction is skipped.
|
|
116
|
+
- **Store and action definitions.** Retain adapter rows, stable IDs, safe
|
|
117
|
+
input/output, and the original step order. No new store method is required.
|
|
118
|
+
Terminal verdicts can need manual evidence repair. Recovery reads
|
|
119
|
+
checkpoints before input generation and adapter lookup.
|
|
120
|
+
- **Callers.** Inline `submitAction()` without a dispatcher and direct
|
|
121
|
+
`executeInvocation()` can now throw where 7.x returned `failed`. Recognise
|
|
122
|
+
`name === "RecoverableFinalizationError"`. The class is not exported.
|
|
123
|
+
Without a lease, the invocation stays `running` and workers do not claim it.
|
|
124
|
+
Before a handoff is durable, re-run `executeInvocation()` yourself and cap
|
|
125
|
+
your own retries. Once a handoff is durable, re-running `executeInvocation()`
|
|
126
|
+
on the unleased running row returns without resuming execution (it first
|
|
127
|
+
settles any step already recorded `ambiguous`). If execution
|
|
128
|
+
stopped before parking, callbacks are refused with `still_executing` until a
|
|
129
|
+
sweep. Declare `completionDeadlineMs` for actions that hand off so the sweep
|
|
130
|
+
surfaces the row, then reconcile it per the
|
|
131
|
+
[external-completion runbook](../../docs/operations/runbooks.md#overdue-handoffs).
|
|
132
|
+
The worker budget does not bound unleased calls. A previously leased call can be
|
|
133
|
+
reclaimed after lease expiry. Handle terminal `reconciliation_required`
|
|
134
|
+
separately. Do not blindly resubmit an applied effect under a new key.
|
|
135
|
+
|
|
136
|
+
## Late completion requires a parked, swept handoff (issue #79 part 3)
|
|
137
|
+
|
|
138
|
+
In 7.x, any reconciliation error containing `"did not complete by"` could let a
|
|
139
|
+
late callback settle an invocation, even if execution had stopped before parking.
|
|
140
|
+
From 8.0.0, the sweep writes optional `pendingCompletion.overdueAt` alongside its
|
|
141
|
+
verdict. Settlement from reconciliation requires `parkedAt` and that marker,
|
|
142
|
+
subject to the markerless fallback below. Callbacks for unparked invocations
|
|
143
|
+
already `reconciliation_required` record their redacted result/outcome as
|
|
144
|
+
evidence without result-schema validation and retain the existing verdict.
|
|
145
|
+
Callbacks for unparked `running` invocations are still refused with
|
|
146
|
+
`still_executing`.
|
|
147
|
+
|
|
148
|
+
Any parked row without the marker, including a 7.x row, still matches the old
|
|
149
|
+
substring. This path is weaker because unrelated error text on such a row can
|
|
150
|
+
also match.
|
|
151
|
+
|
|
152
|
+
### What to check
|
|
153
|
+
|
|
154
|
+
- **Operators and callbacks.** A provider's completion does not establish that
|
|
155
|
+
an unparked invocation finished its remaining steps. Inspect its callback
|
|
156
|
+
evidence and reconcile that work explicitly.
|
|
157
|
+
- **Custom stores.** Preserve and hydrate optional `overdueAt` as a Date. No
|
|
158
|
+
PostgreSQL migration is needed for this JSONB member. Transaction-scoped
|
|
159
|
+
`getActionInvocationForUpdate` serializes the sweep's eligibility check with
|
|
160
|
+
the verdict; `appendExternalReconciliation` puts its evidence in the same
|
|
161
|
+
transaction. Without those capabilities, a read/write race or an evidence gap
|
|
162
|
+
remains; see the README.
|
|
163
|
+
- **Inline callers.** Independent adapter evidence commits without replacing
|
|
164
|
+
the sweep verdict. Reaching parking records the handler result and `parkedAt`
|
|
165
|
+
while retaining the sweep's status, error and `overdueAt`. Successful
|
|
166
|
+
`after_adapters` finalization commits its deferred domain events.
|
|
167
|
+
Inline entry points return the latest record observed by the preserving
|
|
168
|
+
writer, merged with its evidence update, after these writes succeed. No
|
|
169
|
+
post-attempt read or stale attempt fallback is used. Leased paths retain
|
|
170
|
+
`RecoverableFinalizationError` on fencing failure. In-flight adapters and
|
|
171
|
+
earlier domain writes are not undone; stores without a transaction lock
|
|
172
|
+
retain a read/write race. Evidence writes retain their existing retry and
|
|
173
|
+
error handling.
|
|
174
|
+
- **Mixed-version deployments.** Upgrade all inline hosts and workers together.
|
|
175
|
+
A 7.x sweep writes no `overdueAt`. An 8.x inline execution that was still
|
|
176
|
+
unparked under that verdict records its result but does not add `parkedAt`;
|
|
177
|
+
its callbacks stay evidence-only. A 7.x inline host can still overwrite an
|
|
178
|
+
8.x sweep verdict because it lacks the new write guard.
|
|
179
|
+
|
|
180
|
+
## Lease-checked adapter rows (issue #79 part 2)
|
|
181
|
+
|
|
182
|
+
Implement the optional `writeAdapterInvocation` on custom stores to
|
|
183
|
+
atomically authorize adapter creates and updates against their scoped
|
|
184
|
+
parent. Support both running owner/token lease conditions and exact settled
|
|
185
|
+
status/error conditions, plus expected row statuses. Host captures the handoff
|
|
186
|
+
row ID in `completionAdapterInvocationId`; only that row also checks
|
|
187
|
+
`externalCompletionDigest` (absence requires no recorded completion; a supplied
|
|
188
|
+
digest requires that same completion). A callback can retain status/error while
|
|
189
|
+
changing completion and the handoff row's output. Unrelated adapter rows still
|
|
190
|
+
use status/error and expected-row-status fencing, so their ambiguity evidence
|
|
191
|
+
can be recorded after that callback. Omitting the captured row ID retains the
|
|
192
|
+
conservative completion check on adapter writes. Reject conditional
|
|
193
|
+
`succeeded`/`ambiguous` to `running`/`failed` transitions even without an
|
|
194
|
+
expected-status list. Lock the parent before the adapter in PostgreSQL; an
|
|
195
|
+
unlocked `EXISTS` is insufficient against concurrent claims under READ
|
|
196
|
+
COMMITTED. Buffered memory implementations must recheck at commit. Expose
|
|
197
|
+
the same method on mutation transactions to retain atomic leased
|
|
198
|
+
finalization; otherwise Host chooses the verdict-first fallback before
|
|
199
|
+
evidence writes.
|
|
200
|
+
|
|
201
|
+
Built-in stores implement this without a new migration. Host captures
|
|
202
|
+
execution credentials and uses conditional writes for leased checkpoints and
|
|
203
|
+
post-verdict evidence. Lease/status refusal raises
|
|
204
|
+
`RecoverableFinalizationError`; post-verdict refusal leaves the adapter row
|
|
205
|
+
untouched and emits `invocation.adapter_evidence_refused` telemetry. Host
|
|
206
|
+
still attempts ambiguity and reconciliation evidence, and
|
|
207
|
+
withheld-output/refused-handoff success evidence may already have been
|
|
208
|
+
appended. Callback adapter writes share the callback's transaction when its
|
|
209
|
+
adapter-update member exists. A post-verdict refusal is not an
|
|
210
|
+
evidence-collector error. Thrown store errors are raised after every
|
|
211
|
+
independent fallback evidence write has been attempted, preserving the first
|
|
212
|
+
error.
|
|
213
|
+
|
|
214
|
+
A preserving inline write never adopts the sweep verdict as this execution's
|
|
215
|
+
own. Its adapter evidence keeps the unchecked inline path, and
|
|
216
|
+
settled-parent enrichment applies only to a verdict the execution wrote. The
|
|
217
|
+
inline guard reads the current row only when the execution snapshot or its patch
|
|
218
|
+
carries `pendingCompletion`. Handoffs written into running invocations by anything
|
|
219
|
+
other than their executing step are not supported.
|
|
220
|
+
|
|
221
|
+
Stores omitting the capability remain compatible and keep unchecked adapter
|
|
222
|
+
writes, including the risk that a stale worker overwrites a newer
|
|
223
|
+
checkpoint. After its own verdict, an inline execution uses verdict-conditioned
|
|
224
|
+
adapter writes too; earlier unleased writes retain their existing path. Implement
|
|
225
|
+
optional `updateSettledActionInvocation` to enrich a settled parent under the
|
|
226
|
+
full status/error/completion snapshot condition, regardless of adapter row ID.
|
|
227
|
+
The two optional methods are expected together. Without the parent-update method,
|
|
228
|
+
only parent enrichment is skipped; adapter checkpoints, events and reconciliation
|
|
229
|
+
evidence are still attempted. Refused enrichment emits
|
|
230
|
+
`invocation.adapter_evidence_refused` with `evidence: "parent_enrichment"` and is not
|
|
231
|
+
an error. A thrown enrichment error joins the evidence collector, which raises
|
|
232
|
+
the first error after the remaining writes are attempted. Successful enrichment
|
|
233
|
+
stores the budget error and `adapterReconciliation` before returning them;
|
|
234
|
+
skipped or refused enrichment can leave replay with only the plain verdict.
|
|
235
|
+
|
|
236
|
+
### Built-in store subclasses
|
|
237
|
+
|
|
238
|
+
A subclass overriding `createAdapterInvocation` or `updateAdapterInvocation`
|
|
239
|
+
to encrypt or audit adapter data must also override
|
|
240
|
+
`writeAdapterInvocation`: checked writes can bypass the unchecked methods.
|
|
241
|
+
Disabling the capability preserves those overrides and accepts weaker
|
|
242
|
+
stale-worker protection. Parent annotations use
|
|
243
|
+
`updateSettledActionInvocation`; override that seam too when customizing
|
|
244
|
+
parent evidence persistence.
|
|
245
|
+
|
|
246
|
+
`PlatformHostTelemetryEventName` adds `invocation.adapter_evidence_refused`.
|
|
247
|
+
Update exhaustive switches over this union when upgrading.
|