@fabricorg/platform-host 8.0.0 → 8.1.1
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 +73 -0
- package/README.md +95 -0
- package/dist/index.cjs +576 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +575 -35
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 8.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d69dd7a: Republish against the patched `@fabricorg/platform` minimum. No code or behavior changes.
|
|
8
|
+
|
|
9
|
+
## 8.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- e81defa: Add `eventAppendChecks()`, `runEventAppendChecks(subject)`, and their public subject, check,
|
|
14
|
+
and result types for custom store authors. Six checks exercise first-write-wins event appends
|
|
15
|
+
through plain, outbox and transaction paths, including buffered and committed duplicates and
|
|
16
|
+
plain-to-outbox behavior. Each check uses earlier and later duplicates with changed envelope and
|
|
17
|
+
metadata fields, including added map keys. Whole-record comparisons normalize key order, dates
|
|
18
|
+
and nulls; numeric strings are normalised only for `sequence`, `attemptCount` and `leaseToken`.
|
|
19
|
+
The first returned records supply snapshot references, allowing custom envelope fields and fields
|
|
20
|
+
the store omits or returns as null, including required fields. Transaction checks verify after commit
|
|
21
|
+
without requiring read-your-writes. Each committed-duplicate transaction also appends a fresh plain
|
|
22
|
+
control event after the duplicate and verifies its presence after commit to detect a silently
|
|
23
|
+
rolled-back unit of work.
|
|
24
|
+
Outbox snapshots cover pending, probe-leased, retry-pending and published rows, including lease
|
|
25
|
+
fields. Retry-pending accepts cleared or expired leases or the probe owner retained without an
|
|
26
|
+
expiry; a probe lease still live more than five seconds past the probe's clock reports a failure
|
|
27
|
+
to release it, and an expiry inside that margin is waited out as clock skew. Attempt counts and error formatting are
|
|
28
|
+
not prescribed. Retry and retirement availability use whole seconds a few seconds in the past,
|
|
29
|
+
tolerating coarse timestamp precision and small clock skew. Lease tokens come from claims, not
|
|
30
|
+
optional list views.
|
|
31
|
+
Absence checks require a fresh control outbox row.
|
|
32
|
+
|
|
33
|
+
These checks detect common first-write-wins violations: throwing, replacing or merging events,
|
|
34
|
+
keeping both events, selecting the earlier-ordered or later-ordered duplicate, adding outbox rows,
|
|
35
|
+
and resetting or rewriting an existing outbox row in the probed states. They are not exhaustive.
|
|
36
|
+
Known limits are rules comparing ordering fields in mixed directions, store-wide uniqueness across
|
|
37
|
+
spaces or tenants, the dead-letter state, and claims leasing foreign rows without returning them.
|
|
38
|
+
The checks also cannot detect a buffering transaction that merges fields the store never returns
|
|
39
|
+
(such as `includeProvenance`) from a buffered duplicate, or duplicate handling backed by an
|
|
40
|
+
in-process cache that works within one run but not across restarts. Stores requiring gapless
|
|
41
|
+
sequences, or unique keys over fields the control events share with the probe event, can fail
|
|
42
|
+
falsely.
|
|
43
|
+
|
|
44
|
+
Missing append capabilities skip; missing outbox verification or cleanup methods (including
|
|
45
|
+
`markOutboxPublished`) fail. Run in test or staging with outbox relays paused for the probe tenant.
|
|
46
|
+
Event timestamps use whole seconds slightly in the past; probes leave append-only events in dedicated
|
|
47
|
+
`event-append-probe` spaces. Successful outbox-creating checks mark the row published without
|
|
48
|
+
sending anything; final cleanup attempts to dead-letter visible pending control/failure rows.
|
|
49
|
+
Published exercises duplicate appends against that delivery state. Absence-control rows remain
|
|
50
|
+
dead-lettered; operator requeue tooling could publish them later, so exclude probe spaces from real
|
|
51
|
+
publication and requeue workflows.
|
|
52
|
+
|
|
53
|
+
Claims use real current time, a pending-probe-row limit and a 30-second lease. A filter-ignoring
|
|
54
|
+
claim can still lease foreign rows and make its own writes, such as incrementing attempts. The
|
|
55
|
+
checks write nothing to foreign rows and stop claiming for that run; later outbox checks fail as
|
|
56
|
+
skipped because of the earlier filter violation. Standalone checks stop claiming for that check.
|
|
57
|
+
After a filter violation, cleanup dead-letters only probe rows returned by that claim, using the
|
|
58
|
+
probe worker and claim tokens without further claims. The failure lists visible probe rows still
|
|
59
|
+
pending and instructs manual dead-lettering, including unreturned rows or failed retirements.
|
|
60
|
+
Leases lapse within the requested duration if the store honours it. Visible relay interference
|
|
61
|
+
fails with instructions to pause relays. These are additive exports; Host and shipped-store runtime behavior is unchanged.
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- 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.
|
|
66
|
+
- 5af8a02: Lifecycle record ids no longer collide when their key is cut to length (issue #90).
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
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.
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
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 `.`.
|
|
75
|
+
|
|
3
76
|
## 8.0.0
|
|
4
77
|
|
|
5
78
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -255,6 +255,19 @@ the adapter loop. Saga compensation is unchanged. Withheld-output and refused-ha
|
|
|
255
255
|
reconciliation retain the degraded pattern. Adapter-row fencing and overdue-sweep/late-callback
|
|
256
256
|
rules are described below.
|
|
257
257
|
|
|
258
|
+
Event appends are first-write-wins by event ID. Host re-appends lifecycle events under stable IDs
|
|
259
|
+
on retry and recovery, and each later attempt carries a fresh `sequence` and timestamps and can carry
|
|
260
|
+
a different payload. A custom store's `appendEvent` and `appendEventWithOutbox` must treat an ID it
|
|
261
|
+
already holds as a no-op that keeps the first event: do not throw, replace, or merge it. A store that
|
|
262
|
+
refuses these appends can leave recovery unable to converge, so the invocation stays `running` until
|
|
263
|
+
the recovery budget settles it, even when the adapter effect succeeded. Custom stores that currently
|
|
264
|
+
throw on a conflicting append need to change. The shipped stores enforce ID uniqueness across the
|
|
265
|
+
whole store, not per tenant or space. The rule covers the outbox too: `appendEventWithOutbox` for an
|
|
266
|
+
ID already stored adds no outbox row, including when the first append went through plain
|
|
267
|
+
`appendEvent`, so that event is never published later. Keep `outbox.shouldPublish` deterministic for
|
|
268
|
+
a given event ID. Custom store authors can verify these paths with the
|
|
269
|
+
[event append conformance checks](#event-append-conformance).
|
|
270
|
+
|
|
258
271
|
### Conditional adapter-row writes (8.0.0, issue #79)
|
|
259
272
|
|
|
260
273
|
`PlatformHostStore.writeAdapterInvocation(input)` is optional. Memory and PostgreSQL implement it;
|
|
@@ -1069,6 +1082,88 @@ refusals and an admitted update in our scope. The two adapter isolation checks
|
|
|
1069
1082
|
fail for stores that ignore scope. Deployments running `runTenantIsolationChecks` against a custom
|
|
1070
1083
|
store will see new failures until that store honours the argument.
|
|
1071
1084
|
|
|
1085
|
+
## Event append conformance
|
|
1086
|
+
|
|
1087
|
+
`eventAppendChecks()` and `runEventAppendChecks(subject)` run six checks for first-write-wins
|
|
1088
|
+
event IDs: plain appends, outbox appends, outbox-after-plain, transactional appends,
|
|
1089
|
+
transactional outbox appends, and transactional outbox-after-plain. Each sends earlier and later
|
|
1090
|
+
duplicates with lower and higher sequences, earlier and later occurrence/recording times, and
|
|
1091
|
+
different remaining envelope and outbox metadata fields. Transaction checks cover buffered
|
|
1092
|
+
duplicates and duplicates against a committed event; they do not require transaction reads to
|
|
1093
|
+
expose buffered writes. Each committed-duplicate transaction appends a fresh plain control event
|
|
1094
|
+
after the duplicate and verifies its presence after commit, detecting a silently rolled-back unit
|
|
1095
|
+
of work. Event-count checks count only the original probe ID; controls use distinct sequences.
|
|
1096
|
+
|
|
1097
|
+
The checks detect common first-write-wins violations: throwing, replacing or merging events,
|
|
1098
|
+
keeping both events, selecting the earlier-ordered or later-ordered duplicate, adding outbox
|
|
1099
|
+
rows, and resetting or rewriting an existing outbox row in the probed states. They are not
|
|
1100
|
+
exhaustive. Known limits include rules that compare ordering fields in mixed directions,
|
|
1101
|
+
store-wide uniqueness across spaces or tenants, the dead-letter state, and claims that lease
|
|
1102
|
+
foreign rows without returning them. They also cannot detect a buffering transaction that merges
|
|
1103
|
+
fields the store never returns (such as `includeProvenance`) from a buffered duplicate, or
|
|
1104
|
+
duplicate handling backed by an in-process cache that works during one run but fails across
|
|
1105
|
+
restarts. Stores requiring gapless sequences can fail falsely: the first event uses sequence 50
|
|
1106
|
+
in a fresh space. So can stores with a unique key over fields the control events share with the
|
|
1107
|
+
probe event, such as `actionInvocationId` with the subject.
|
|
1108
|
+
|
|
1109
|
+
Outbox-creating checks verify the first event and metadata, then compare whole-row snapshots
|
|
1110
|
+
while pending, leased by the probe worker, retry-pending, and settled as published. Retry-pending
|
|
1111
|
+
accepts a cleared lease, an expired lease with no owner or the probe worker, or the probe owner
|
|
1112
|
+
retained as a record without an expiry. A probe lease that still expires more than five seconds
|
|
1113
|
+
after the probe's clock fails with `markOutboxFailed did not release the lease`; an expiry inside
|
|
1114
|
+
that margin is treated as clock skew and waited out before the row is claimed again. A lease held
|
|
1115
|
+
by another worker fails with the request to pause relays. The checks prescribe neither a positive attempt
|
|
1116
|
+
count nor error formatting; they snapshot the values the store returns. Retry availability uses
|
|
1117
|
+
whole seconds a few seconds in the past, tolerating coarse timestamp precision and small clock
|
|
1118
|
+
skew, then the probe reclaims the row before publication. Snapshots include the full envelope,
|
|
1119
|
+
lease and publication fields. Comparisons sort object keys, normalize dates and date strings to milliseconds, treat null like undefined, and
|
|
1120
|
+
compare numeric strings in `sequence`, `attemptCount` and `leaseToken` as numbers. An absent
|
|
1121
|
+
actor disclosure is accepted; included disclosure or actor identity on the first outbox envelope
|
|
1122
|
+
fails because the first metadata does not opt in. A store's first returned records supply the
|
|
1123
|
+
snapshot references; extra envelope fields and fields the store omits or returns as null are
|
|
1124
|
+
accepted, including required fields. This tolerance does not validate envelope completeness. For
|
|
1125
|
+
buffered duplicates in the first transaction, exposed fields that differ between the first input
|
|
1126
|
+
and a duplicate must retain the first value. Outbox-after-plain checks write a fresh control
|
|
1127
|
+
event through the matching outbox seam and require its row to be the only outbox row visible in
|
|
1128
|
+
the probe space, so absence verification cannot pass solely because `listOutbox` returns
|
|
1129
|
+
nothing.
|
|
1130
|
+
|
|
1131
|
+
`EventAppendSubject<TDb>` supplies `store`, `tenantId`, and optional `reset()`. Only
|
|
1132
|
+
`appendEvent` and `listEvents(tenantId, spaceId)` are required; the subject does not need a full
|
|
1133
|
+
recoverable store. The result has `passed` and per-check `id`, `status`, `evidence`, and
|
|
1134
|
+
optional `error`, following the tenant-isolation suite. Absent outbox or transaction append
|
|
1135
|
+
capabilities produce `skipped: ` evidence; `passed` means no failures, so inspect skips for
|
|
1136
|
+
capability coverage. An available outbox append without `listOutbox`, `claimOutbox`,
|
|
1137
|
+
`markOutboxPublished` or `markOutboxFailed` fails because the check cannot verify or clean up.
|
|
1138
|
+
Append errors and probe retirement failures also fail.
|
|
1139
|
+
|
|
1140
|
+
Run against test or staging stores, preferably with a dedicated probe tenant. **Pause outbox
|
|
1141
|
+
relays for the probe tenant.** Event timestamps and written availability values (including
|
|
1142
|
+
retirement) use whole seconds a few seconds in the past. Claims and publication markers use
|
|
1143
|
+
real current time. Probe rows are expected to be due when availability follows `recordedAt`;
|
|
1144
|
+
stores assigning availability by their own clock may need additional setup. Visible foreign-worker
|
|
1145
|
+
leases or status changes before the probe acts fail with a request to pause relays. This detects visible interference; it does not prevent a relay race.
|
|
1146
|
+
Append-only events remain in fresh `event-append-probe-${uuid}` spaces. Successful
|
|
1147
|
+
outbox-creating checks leave their row **published although nothing was sent**; the space name
|
|
1148
|
+
identifies probe rows. Published is needed to exercise duplicate appends against that delivery
|
|
1149
|
+
state. Absence-control rows and failure rows instead go through final cleanup, which attempts
|
|
1150
|
+
to dead-letter visible probe rows still pending with `event append probe; never published`.
|
|
1151
|
+
Operator tooling can requeue those dead-lettered rows for real publication: exclude probe
|
|
1152
|
+
spaces from real publication and requeue workflows.
|
|
1153
|
+
|
|
1154
|
+
Claims use the real current time, a 30-second lease, and a limit equal to the pending probe row
|
|
1155
|
+
count. Cleanup filters listed rows locally by tenant and space and writes only to probe rows. If
|
|
1156
|
+
a claim returns foreign rows, the failure names their IDs, the checks write nothing to those
|
|
1157
|
+
rows, and no further claims occur in that run. Later outbox checks fail with an explanation that
|
|
1158
|
+
they were skipped because of the earlier filter violation; a standalone `check.run(subject)`
|
|
1159
|
+
stops claims for that check. Cleanup after a filter violation can dead-letter only probe rows
|
|
1160
|
+
returned by the violating claim, using the probe worker and that claim's tokens without another
|
|
1161
|
+
claim. The failure lists visible probe rows still pending, including unreturned rows or failed
|
|
1162
|
+
retirements, and instructs the operator to dead-letter them by hand. A filter-ignoring claim can
|
|
1163
|
+
still impose a short lease and make whatever writes the claim itself makes, such as incrementing attempt counts. Those leases lapse
|
|
1164
|
+
within 30 seconds of the claim if the store honours the requested duration; the checks do not
|
|
1165
|
+
release foreign leases or change foreign statuses, availability or errors.
|
|
1166
|
+
|
|
1072
1167
|
## Runtime observability and health
|
|
1073
1168
|
|
|
1074
1169
|
Platform Host exposes vendor-neutral lifecycle telemetry through the optional `telemetry` sink on
|