Skip to main content
Version: Current

Behavior-contract checklist

A documented runtime promise holds only while a test fails when it breaks. Each row below is one such promise: a one-line claim, the doc section that owns it, and the tests that pin it. The explanation lives in the linked section, not here.

Rules for rows:

  • Only documented runtime promises. A behavior a caller or adapter author relies on, stated in the docs or a public doc-comment. Not every sentence, and not internal mechanics.
  • Exact test names. Cite file.spec.ts โ†’ "the exact test name". CI (pnpm check:contracts) fails when a cited test does not exist in the cited file, so a rename has to update its row.
  • Short. One line of claim, no history or ticket numbers. When the promise needs explaining, explain it in the owning doc.

Store claims are pinned in the TCK (libs/act-tck/src/store-tck.ts), so they run on InMemory, Postgres and SQLite at once.

Cache and snapshotsโ€‹

ClaimSourceBacking test
seed() upgrades any released schema additively and losslessly, and is safe on every boot from every workerWriting a storeact-pg + act-sqlite seed-upgrade.spec.ts โ†’ "upgrades the oldest supported shape losslessly and idempotently", "serializes concurrent cold boots on an empty schema"
Postgres streams.retry is an integer, so an unbounded retry count never overflows claimWriting a storeact-pg retry-overflow.spec.ts โ†’ "claims a stream whose retry sits at the old smallint ceiling"; seed-upgrade.spec.ts โ†’ "upgrades the oldest supported shape losslessly and idempotently"
with_snaps resumes each stream from its latest snapshot, in both scan directions; after overrides itCache and snapshotsstore-tck.ts โ†’ "with_snaps resumes from the latest snapshot per stream", "with_snaps applies the resume floor on a backward scan too"
A cold start folds the snapshot into state and resets patches to 0Cache and snapshotsevent-sourcing.spec.ts โ†’ "should load from a snapshot event on cold start"
A failed snapshot write does not propagateCache and snapshotsevent-sourcing.spec.ts โ†’ "should not throw on snap error and warn with stream, reason, and hint"
A failed snapshot write never fails the action and warns with the stream, reason and replay hintCache and snapshotsevent-sourcing.spec.ts โ†’ "should complete the action and warn when the snapshot write fails"
A passing snap predicate commits a __snapshot__ eventCache and snapshotsevent-sourcing.spec.ts โ†’ "should persist snapshot event on snap success"
A cache miss populates the cacheCache and snapshotscache.spec.ts โ†’ "cache miss populates cache on load"
A warm load resumes with with_snaps, so a snapshot above a stale checkpoint is still foldedCache and snapshotsclose-windowed.spec.ts โ†’ "warm reload after a prune folds through the rebaselining snapshot"; optimizations.spec.ts โ†’ "a cache-warm load queries with after and replays zero rows"
The cache is invalidated only on ConcurrencyErrorCache and snapshotscache.spec.ts โ†’ "cache invalidated on ConcurrencyError"
A failing cache never changes which error the caller sees, or whether the action retriesCache and snapshotsaction-retry.spec.ts โ†’ "surfaces ConcurrencyError, not the cache error", "still retries a resolvable conflict through the action budget", "control โ€” a healthy cache behaves identically"; cache-frontier.spec.ts โ†’ "logs and swallows a failing invalidate on the gapped path"
Invariant and validation failures leave the warm cache untouchedCache and snapshotscache.spec.ts โ†’ "invariant failure leaves the warm cache untouched (no commit)", "validation failure leaves the warm cache untouched (no commit)"
A cache.set rejection warns and does not fail the actionCache and snapshotscache.spec.ts โ†’ "cache.set rejection warns but does not fail the action"
A time-travel (asOf) load skips the cache and ignores snapshots outside its cutoff, for every boundCache and snapshotstime-travel.spec.ts โ†’ "should not read from cache for time-travel loads", "should not write to cache for time-travel loads", "should not use snapshots for time-travel loads", "time-travel ignores snapshots outside the cutoff (RFC 1274)"
query_stats head and tail never carry piiStore.query_stats docstore-tck.ts โ†’ "query_stats head/tail never carry pii โ€” introspection surface is pii-safe"; act-sqlite pii-encryption.spec.ts โ†’ "never carries pii on query_stats head/tail โ€” the operator-introspection surface is pii-safe"

Reactions, drain, and errorsโ€‹

ClaimSourceBacking test
On Postgres, event ids become visible in id order, so no commit surfaces below an acked watermarkConcurrency modelact-pg commit-visibility.spec.ts โ†’ "a commit waits for an in-flight append transaction to finish", "truncate seeds take the same visibility lock"
A reaction's app.do gets reactingTo injected unless the handler passes oneState managementreacting-to.spec.ts โ†’ "should auto-inject reactingTo when handler omits it", "should respect explicit reactingTo when provided"
The injection follows the reaction through a captured app, and not outside oneState managementreacting-to.spec.ts โ†’ "should auto-inject when the handler dispatches through a captured app", "should mint a fresh correlation for dispatches outside any reaction"; reacting-to-singleton.spec.ts
A live reaction skips the inferred concurrency guard but honors an explicit expectedVersionConcurrency modelreacting-to.spec.ts โ†’ "should honor an explicit expectedVersion inside a reaction", "should restore the inferred guard for work that outlived the handler", "should NOT treat work the handler did not await as a reaction"
A z.date() field reads back as a Date; an ISO-shaped z.string() stays a stringExtension pointslibs/act/test/schema-dates.spec.ts; libs/act-pg/test/schema-dates.spec.ts
NonRetryableError blocks on the first attempt when blockOnError is trueError handlingnon-retryable.spec.ts โ†’ "blocks on first attempt when blockOnError is true (default)"
NonRetryableError does not override blockOnError: falseError handlingnon-retryable.spec.ts โ†’ "ignores NonRetryableError when blockOnError is false"
A worker whose lease was taken mid-handler reports the discarded roundConcurrency modelnon-retryable.spec.ts โ†’ "reports the round of work the stolen lease discarded"; store-tck.ts โ†’ "drops the ack of a holder whose lease was taken, and accrues retry per claim"
A stream reaching claim with retry > maxRetries blocks before dispatch; retry === maxRetries still runsConcurrency modelnon-retryable.spec.ts โ†’ "blocks the stream once the budget is spent with no error raised", "control โ€” a claim at exactly maxRetries still gets its attempt", "control โ€” blockOnError:false still means retry forever"
Backoff holds the retry until its window passesError handlingbackoff.spec.ts โ†’ "defers retry until backoff window elapses (persisted schedule)"
A successful ack clears the backoffError handlingbackoff.spec.ts โ†’ "clears backoff entry on successful ack"
Backoff still blocks once retries run outError handlingbackoff.spec.ts โ†’ "preserves blocking behavior when retries are exhausted"
A failing stream runs maxRetries + 1 times before it blocksError handlingbackoff.spec.ts โ†’ "spends the whole retry budget and blocks", "preserves blocking behavior when retries are exhausted"
compute_backoff_delay applies strategy, clamp and jitterError handlingbackoff.spec.ts โ†’ compute_backoff_delay unit block
The backoff window is honored precisely and does not depend on leaseMillisError handlingbackoff.spec.ts โ†’ "backoff window is honored precisely via persisted deferred_at, not the lease duration"
A backoff keeps the retry count through the window; an explicit defer does not count as a failureError handlingstore-tck.ts โ†’ "persists the lease's retry on a backoff-style due lease" / "holds the watermark and resets retry on an explicit-defer due lease (retry: -1)"
A partial-progress defer keeps the handled prefix and holds only the failing tailError handlingstore-tck.ts โ†’ "advances the watermark to at while persisting the schedule on a partial-progress due lease"; backoff.spec.ts โ†’ "advances the watermark past the succeeded prefix AND persists the window on partial progress"
A batch that makes progress and then blocks both advances the watermark and blocks, in one cycleError handlingnon-retryable.spec.ts โ†’ "blocks in the SAME cycle after partial progress โ€” ack must not drop the block"
The settle loop closes an OPEN breaker only when its correlate actually read the storeError handlingsettle-wakeup.spec.ts โ†’ "leaves the breaker OPEN when correlate was disarmed and read nothing", "closes the breaker when the scan returns (control)"
A failure with no backoff is retried after a fixed 10-second default, whatever the drain's leaseError handlingbackoff.spec.ts โ†’ "retries after the default backoff, whatever the drain's lease", "spends the whole retry budget and blocks"
app.unblock resumes from the watermark; app.reset replays everythingError handlingnon-retryable.spec.ts โ†’ "recovers via app.unblock without replaying history"; rebuild.spec.ts โ†’ "should enable replay of projection after reset"
correlate arms the lane that a newly subscribed stream lands onCorrelation and draincorrelate.spec.ts โ†’ "revives a lane that disarmed before the subscription landed"
restore and scan carry __snapshot__ events, and drop_snapshots drops themEvent sourcingrestore.spec.ts โ†’ "carries a snapshot through a store-to-store restore" / "does not lose a stream whose history was pruned by a windowed close" / "drop_snapshots actually drops them from a Store source"
Correlate stops scanning only on a short page, not on a full page that resolved nothingCorrelation and draincorrelate.spec.ts โ†’ "keeps scanning past a FULL window that resolved no target" / "a full inert window does not make settle report a false catch-up" / "still parks when the window came back short" / "parks after an UNBOUNDED scan that resolved no target"
Each start_correlations tick correlates and drains, so polling delivers another process's commitsCross-process reactionscorrelate.spec.ts โ†’ "polling discovers and drains a commit this process never saw"
Correlate's cold-start checkpoint re-scans events committed but not correlated before a crashCorrelation and draincorrelate.spec.ts โ†’ "still discovers a dynamic target committed-but-not-correlated before restart"
settle() never drops a wake-up requested while a cycle runsCorrelation and drainsettle-wakeup.spec.ts โ†’ "runs a cycle for a schedule() that fires while a cycle is in flight"
A state projection flushes before it acks, so a failed flush holds the watermarkProjections to a databasestate-projection.spec.ts โ†’ "holds the watermark when flush fails and converges on retry"
A state projection flushes an evicted stream before dropping itProjections to a databasestate-projection.spec.ts โ†’ "flushes the evictee before dropping it under maxCachedStates pressure"
A state projection rebuild writes one row per stream per flushProjections to a databasestate-projection.spec.ts โ†’ "rebuilds in O(streams) upserts, not O(events)"
A state projection's first load resumes from the latest snapshotProjections to a databasestate-projection.spec.ts โ†’ "resumes the first-sight load from the latest snapshot"
A cache entry's state is always the fold of events up to its event_idCache and snapshotscache-frontier.spec.ts โ†’ "never leaves a stale-at-head cache entry after guardless commits"
A state projection's first fold pairs state with the head id from the same load()Projections to a databasefold-frontier-toctou.spec.ts โ†’ "does not skip events when the cache frontier moves during first-sight load"
A failing cache.set inside load() is logged, not thrownCache and snapshotsload-cache-error.spec.ts โ†’ "returns the correct state and swallows a failing cache.set"
A caller-supplied expectedVersion conflict is rethrown at once, without retryError handlingaction-retry.spec.ts โ†’ "rethrows immediately without consuming the retry budget or sleeping"
The concurrency guard holds on a warm cache hitConcurrency modelcache-frontier.spec.ts โ†’ "keeps the optimistic guard on warm cache hits"
A load between a close's tombstone and its truncate does not cache, so the next action sees the tombstoneClose cycleevent-sourcing.spec.ts โ†’ "a cold load during the guard window keeps the tombstone check live"
A snapshot is written only from a contiguous fold and is cached before the action returnsCache and snapshotscache.spec.ts โ†’ "has the snapshot in the log by the time the action returns"
Lanes drain in parallel, so a stalled slow lane does not block a fast oneConfigurationlanes.spec.ts โ†’ "drains lanes in parallel โ€” a stalled slow handler does not block the fast lane"
When onlyLanes leaves a declared lane undrained, the build logs one advisory naming itConfigurationlanes.spec.ts โ†’ "logs an orphaned-lane advisory when onlyLanes excludes a declared lane", "does not log the orphaned-lane advisory when onlyLanes covers every declared lane", "does not log the orphaned-lane advisory when onlyLanes is unset"
Each drain controller claims only its own laneConfigurationlanes.spec.ts โ†’ "a default-only worker never claims the slow lane's streams", "a slow-only worker never claims the default lane's streams", "a lane's streams are only claimed by the controller carrying its lease budget"
Workers on different onlyLanes shards each correlate; workers on the same shard share one leaseConfigurationlanes.spec.ts โ†’ "lets a worker on another shard correlate while the first holds its lease", "keeps one lease for workers on the same shard"
A drain that claims nothing still keeps the wake for every parked streamState managementdefer-outcome.spec.ts โ†’ "keeps the wake for other parked streams when a woken drain claims nothing"
A defer redelivers its whole reaction group, unless isolated with .to(...)State managementdefer-outcome.spec.ts โ†’ "re-runs a sibling that shares the deferred reaction's target, but not an isolated one"
A deferred stream is due by the claiming worker's clock, on every adapterWriting a storestore-tck.ts โ†’ "defer", "judges deferred_at by the claiming worker's clock"; act-pg defer-skew.spec.ts
After restore, restored events and new commits reach their reactionsWriting a storestore-tck.ts โ†’ "resets every correlate checkpoint, since the restored log is renumbered"; restore.spec.ts โ†’ "delivers restored events and new commits to their reactions"
A batch projection handler sees each event once, in orderProjection.batchHandler docprojection.spec.ts โ†’ "hands the batch handler each event once, however many reactions it has"
A cold start re-arms each still-future deferred_atClose cycledefer-durability.spec.ts โ†’ "re-seeds the DeferTimer from persisted deferred_at at cold start", "runs the drain at the persisted due-time with no intervening commit"
A deferred or backed-off reaction fires at its due-time on an idle app, with no further callsClose cycledefer-outcome.spec.ts โ†’ "on the default lane", "control โ€” on a lane that polls with cycleMs", "keeps the wake for other parked streams when a woken drain claims nothing", "keeps the wake for parked streams when it fires while the breaker is open", "a wake that lands while another drain is running", "still runs the stream that came due"; backoff.spec.ts โ†’ "clears backoff entry on successful ack"
A defer holds the event pending without bumping retry, then redelivers when dueClose cycledefer-outcome.spec.ts โ†’ "holds pending until the due-time, then redelivers and acks"
Registering the same projection object twice registers it onceState managementtarget-ownership.spec.ts โ†’ "runs its handler once per event, not twice", "control โ€” two DIFFERENT handlers sharing a name still dedupe via _p"
A projection target has one batch handler or one state projection, never bothState managementtarget-ownership.spec.ts โ†’ "rejects batch + batch", "rejects batch + fold", "rejects fold + batch โ€” the reverse order too", "rejects fold + fold", "refuses the cross-state hijack at build instead of corrupting the read model", "control โ€” distinct targets coexist, and both run"
The registry is complete and frozen at build()Close cycleregistry-freeze.spec.ts โ†’ "freezes the registry containers at build", "synthesizes the autoclose reaction at build, not construction"
.autocloses closes on a live-head match, skips a reopened stream, waits out after, and defers outside its windowClose policiesautoclose-reaction.spec.ts โ†’ "closes immediately on the terminal event for an is policy", "evaluates the live head โ€” a reopened stream is not closed", "parks on the cooldown instead of closing while after has not elapsed", "closes on the threshold event for a reaches policy", "respects the off-hours window โ€” outside it, defers instead of closing"
An unmet .autocloses predicate never defers to a past due-timeClose policiesautoclose-reaction.spec.ts โ†’ "does not defer to a PAST due-time when after has elapsed but the predicate is unmet"
.autocloses decides on the domain head, so a trailing snapshot does not hide the terminal eventClose cycleautoclose-reaction.spec.ts โ†’ "closes on the terminal event even when a snapshot trails it", "counts only domain events toward reaches when snapshots trail"
On a DST spring-forward day, the autoclose window still opens at the skipped hour's replacementClose policiesautoclose-builder.spec.ts โ†’ "opens ~1h out on a DST spring-forward day whose start hour is in the gap", "in_autoclose_window is true at the DST gap replacement instant"
.defer(when) holds a reaction until due, then runs it onceState managementdeclarative-defer.spec.ts โ†’ "literal { after } holds the reaction (handler not run, not acked)", "runs immediately once the schedule is already due", "is available on the slice() builder too"
The .defer function form can read the event to pick the scheduleState managementdeclarative-defer.spec.ts โ†’ "function form reads the payload to choose the schedule, then runs"
Drain finalization is atomic: schedules ride the same ack as the watermarksClose cycledefer-durability.spec.ts โ†’ "a failed finalize lands nothing โ€” close requests are never lost", "persists the defer schedule in the same store call as the acks"; TCK store-tck.ts โ†’ "ack finalize (due-marked leases)"
A failed finalize leaves the drain armed, so the defer is persisted on the retryClose cycledefer-durability.spec.ts โ†’ "keeps the drain armed while finalization is unhealed"
A malformed literal .defer(...) throws ZodError at buildState managementdeclarative-defer.spec.ts โ†’ "rejects a bad literal schedule at build time"
throw new DeferSignal(when) holds the stream until due, then redeliversState managementpublic-defer.spec.ts โ†’ "{ at: Date } holds until the due-time, then acts", "{ after } is measured from the event's created time (parks, not acked)"

Store contractโ€‹

Pinned in the TCK unless the citation names an adapter.

ClaimSourceBacking test
subscribe is idempotentStore.subscribe docstore-tck.ts โ†’ "subscribes new streams and is idempotent on repeat"
A subscribe batch carries at most one entry per streamStore.subscribe docbuild-classify.spec.ts โ†’ "collapses same-target reactions from different sources, keeping the max priority" / "keeps the max priority regardless of declaration order"; act-pg priority.spec.ts โ†’ "restores the declared maximum priority on boot"
subscribe keeps the maximum priority, so a boot restores a prioritize() downgradeStore.subscribe docstore-tck.ts โ†’ "keeps the maximum priority when a stream is re-subscribed", "keeps the maximum for negative and zero priorities too", "restores the declared priority on the next subscribe after a prioritize() downgrade"
subscribe writes a lane only at or above the stored priorityConfigurationstore-tck.ts โ†’ "lanes", "keeps the lane when a lower-priority subscribe re-registers", "keeps the lane when a mark-only subscribe re-registers", "re-lanes when the subscribe outranks the stored priority", "re-lanes at equal priority, which is what keeps re-laning restart-driven"; correlate.spec.ts โ†’ "dynamic targets survive a forgotten record"
A dynamic target's priority rises when a later scan resolves higher, and never dropsPriority lanescorrelate.spec.ts โ†’ "cross-scan priority upgrade", "a later higher-priority scan raises the target's priority and lane", "a later lower-priority scan does NOT lower the target (max holds)"
A dynamic resolution naming an undeclared lane goes to default and is reported onceConfigurationdynamic-lane-guard.spec.ts โ†’ "still runs the reaction instead of stranding the stream", "says so โ€” a rerouted lane is not silent", "reports once, not once per matching event"
Dynamic lane reports fire once per declaration, not per targetConfigurationdynamic-lane-guard.spec.ts โ†’ "reporting once per declaration, not once per aggregate", "still one report when the same declaration reroutes 25 targets", "still one report when the same pair disagrees on 25 targets", "CONTROL โ€” two declarations naming the same bad lane report twice", "CONTROL โ€” two distinct bad pairs report twice"
Two dynamic lanes for one target at equal priority keep the first and report the conflictConfigurationdynamic-lane-guard.spec.ts โ†’ "CONTROL โ€” the static form is still rejected at build", "says so โ€” first-discovery-wins is not silent"
A dynamic target keeps its lane at equal priority after correlate forgets it, and the conflict is loggedConfigurationcorrelate.spec.ts โ†’ "CONTROL โ€” no eviction: the target keeps its first lane and the conflict is logged", "a target's lane survives correlate forgetting it", "a fresh process keeps the lane the row holds", "a forgotten target still moves to a higher-priority resolution"
The dynamic lane conflict compares resolved names and fires only on a real tieConfigurationdynamic-lane-guard.spec.ts โ†’ "an omitted lane disagreeing with a declared one", "reports the same disagreement on the dynamic path", "reports it in the other discovery order too", "reports it across scans, where the held lane comes from the row", "reports a rerouted undeclared lane against a stream already laned", "CONTROL โ€” a first resolution onto a never-seen target is not a conflict", "CONTROL โ€” a higher-priority resolution outranks, it does not conflict"
An omitted lane and lane: "default" agree at buildConfigurationlanes.spec.ts โ†’ "accepts an explicit 'default' followed by an omitted lane", "accepts an omitted lane followed by an explicit 'default'", "builds the lane-conflict failure scenario with no cast anywhere", "still rejects a declared lane against an omitted one"
A zero-length lease is granted and its ack still landsStore.claim docstore-tck.ts โ†’ "records the lease holder for a zero-length lease, so its ack lands"
limit: 0 on query_streams and query_stats returns nothingQueryStreams.limit docstore-tck.ts โ†’ "limit: 0 returns no rows"
prioritize sets priority directly, overriding subscribe's maximumPriority lanesstore-tck.ts โ†’ "sets priority directly, overriding subscribe's max() rule"
unblock and reset return 0 for nothing to changeStore.unblock / Store.reset docsstore-tck.ts โ†’ "returns 0 when the stream is not blocked", "returns 0 for unknown streams and empty input"
unblock keeps the watermark; reset rewinds it to -1Error handlingstore-tck.ts โ†’ "clears blocked flag and preserves the watermark", "rewinds a stream watermark to -1", "clears blocked status when resetting"
A full truncate deletes the events and seeds a snapshot or tombstoneWriting a storestore-tck.ts โ†’ truncate describe block
A keep window advances on commits; a quiet stream is pruned with app.close([{ stream, before }])Close policiesautoclose-reaction.spec.ts โ†’ "does not prune again while the stream stays idle", "an on-demand close prunes the idle stream the reaction left alone"
A manual app.close archives only through its target's archive callbackClose policiesautoclose-reaction.spec.ts โ†’ "an on-demand close archives only through the target's own archive, never the state's .archives"
PostgresStore.dispose() resolves during a LISTEN reconnect and leaks nothingPostgresStore.dispose docnotify.resilience.spec.ts โ†’ "PostgresStore disposal racing an in-flight re-LISTEN", "CONTROL โ€” with no open in flight, disposal clears the listen client", "releases the client an in-flight open was holding, instead of stranding it"; notify.spec.ts โ†’ "resolves while a reconnect sits in the pool checkout"
autocloseWindow is per Act and validated on every build()Close policiesautoclose-reaction.spec.ts โ†’ "the off-hours window is per-Act, not per-builder", "a second Act built with no window closes immediately", "a second Act built WITH a window still defers, whoever built first", "validates a window on every build, not only the first", "CONTROL โ€” the same invalid window throws on a first build too"
A windowed truncate deletes only below the closest safe snapshot, and the stream stays liveWriting a storestore-tck.ts โ†’ "windowed (before boundary)", "deletes the prefix below the closest safe snapshot and keeps the snapshot + tail", "leaves subscriptions untouched, unlike a full truncate", "keeps the stream writable and readable after a prune", "mixes full and windowed targets in one call"
A windowed truncate never prunes past its max_id capClose cyclestore-tck.ts โ†’ "honors the max_id cap โ€” boundary never rises past a lagging consumer"; close-windowed.spec.ts โ†’ "caps the boundary at a lagging consumer's watermark"
A windowed target with no qualifying snapshot is skippedClose cyclestore-tck.ts โ†’ "no-ops when no snapshot qualifies โ€” stream absent from the result, events untouched"; close-windowed.spec.ts โ†’ "skips streams with no qualifying snapshot"
forget_pii wipes every event's PII and is idempotentExtension pointsstore-tck.ts โ†’ "wipes pii for every event on the stream via forget_pii", "is idempotent โ€” second forget_pii returns 0, no error", "forget_pii on a stream with no pii events returns 0"
A generated action route parses its input onceAuto-generated APIact-http hono/index.spec.ts, trpc/index.spec.ts โ†’ "action input is parsed once"
withBroker checks each notification's shape and contains each handleract-notify READMEact-notify notify.spec.ts โ†’ "delivery to notify handlers"
A stalled notify broker never holds up commit or shutdown()act-notify READMEact-notify notify.spec.ts โ†’ "a broker that stalls or fails never holds the orchestrator up"
Every generated subscription runs the actor extractor before openingAuto-generated APIact-http trpc/sse.spec.ts โ†’ "CONTROL โ€” an accepting extractor still yields the cached state", "a denying extractor runs, and no state reaches the caller", "the denial is UNAUTHORIZED, not a framework 500", "the extractor runs before a connection slot is taken"
Gated reads drop the pii sidecar, and reads with no actor default to redactedSensitive dataread-gate.spec.ts โ†’ "the gated load event drops the pii sidecar โ€” plaintext never rides in event.pii" + the "query / query_array default-deny PII gate", "drain() default-deny PII gate" blocks
A projection fold strips PII the same way warm or coldSensitive datafold-pii-path.spec.ts โ†’ "cold-loaded and warm-folded rows of an identical history match"
query_streams reports maxEventId as the highest committed idStore.query_streams docstore-tck.ts โ†’ "maxEventId tracks the highest committed id"
query_streams reports a future deferred_atStreamPosition.deferred_at docstore-tck.ts โ†’ "defer", โ†’ surfaces a future deferred_at on query_streams
claim skips a stream until deferred_at; a defer does not bump retry; reset clears itWriting a storestore-tck.ts โ†’ defer describe block: "hides a stream from claim until its deferred_at passes", "makes a stream claimable once the deferred_at is in the past", "does not bump retry while a stream is deferred", "reset clears a pending defer", "defers streams matching a filter and counts matches"
Stream filters share one portable grammar; anything richer matches exactly or throwsExtension pointsstore-tck.ts โ†’ "stream filter grammar" describe block: "portable subset: anchors, ., and .* match identically", "literal _ and % in patterns are not wildcards", "portable subset applies to stream-position filters", "non-portable patterns match with full regex semantics or throw", "bulk stream ops reject non-portable filters instead of mis-matching"
Stream and source filters are case-sensitive on every adapterWriting a storestore-tck.ts โ†’ "stream filter grammar" describe block: "stream filters match case-sensitively (query)"
names: [] matches no events; an omitted names matches allWriting a storestore-tck.ts โ†’ "query" describe block: "names:[] matches no events"
before: 0 and after: 0 are honored as boundsWriting a storestore-tck.ts โ†’ "query" describe block: "after:0 and before:0 are honored as id bounds"
A Date in data is stored and read back as its ISO stringWriting a storestore-tck.ts โ†’ "round-trips a Date losslessly in pii, like data/meta"; csv.spec.ts โ†’ "round-trips a Date in data through a store, not just CsvFile"
A Date in pii round-trips the same way, encrypted or notWriting a storestore-tck.ts โ†’ pii_isolation: "round-trips a Date losslessly in pii, like data/meta"
A (stream, version) collision maps to ConcurrencyError; other driver errors to StoreErrorWriting a storeact-sqlite store.error.spec.ts โ†’ "commit: maps a (stream,version) unique collision to ConcurrencyError"
claim serves only streams with work past their watermarkWriting a storestore-tck.ts โ†’ "does not claim a fresh subscription with no matching events", "claims a fresh subscription as soon as its first event is marked"; in-memory-store.spec.ts โ†’ "does not lose event id 0 when a commit lands during a fresh stream's cycle"
A literal source selects events by exact stream nameStore.subscribe doccorrelate.spec.ts โ†’ "skips an event outside a literal source"; store-tck.ts โ†’ "receives exactly its source stream's events", "fetches only the exact source stream's events, never a sibling prefix"
A pattern source matches as a regex, or is rejected at subscribeStore.subscribe doccorrelate.spec.ts โ†’ "skips an event outside a pattern source, and marks one inside it"; store-tck.ts โ†’ "throws at subscribe for a non-portable (alternation) claim source"
A lease timeout counts against the retry budgetConcurrency modelstore-tck.ts โ†’ "counts a timed-out lease reclaimed by another worker against the retry budget; ack resets it"
A stream is not blocked for a retry count the store ran upConcurrency modelstore-failure-budget.spec.ts โ†’ "keeps the stream running while acks keep failing, and recovers", "still quarantines a stream that loses its lease every round"
An unexpired lease is invisible to other claimers; an expired one goes to exactly oneConcurrency modelstore-tck.ts โ†’ concurrency : "does not hand an unexpired lease to a competing claimer", "hands an expired lease to exactly one competing claimer, with retry accounting shared across workers"
A claim locks only its own candidates, so disjoint workers both get their sliceConcurrency modelact-pg contention.spec.ts โ†’ "locks only the claimed candidates, not the whole eligible frontier"
notify never delivers an instance's own commitsCross-process reactionsstore-tck.ts โ†’ notify : "does not deliver an instance's own commits (self-filtering)"
notify delivers one notification per commit, with the whole batchStore.notify docstore-tck.ts โ†’ notify : "delivers one notification per commit transaction carrying the full event batch"
An adapter built from its defaults either refuses to construct or keeps what it commitsStoreTckOptions.default_factory docstore-tck.ts โ†’ "either refuses to construct or round-trips a commit"
SqliteStore needs a url and maps every in-memory spelling to a shared cacheact-sqlite READMEact-sqlite store.error.spec.ts โ†’ "rejects a blank or non-string url: %o", "normalizes the in-memory url %s to shared cache"

PostgresStore notifyโ€‹

The portable notify contract is in the TCK's notify suite. These rows are the PostgreSQL plumbing it can't express: the LISTEN reconnect and the 8000-byte payload cap.

ClaimSourceBacking test
A commit wakes other instances' listeners, never its ownCross-process reactionsstore-tck.ts notify capability โ†’ "does not deliver an instance's own commits (self-filtering)"
An oversize NOTIFY payload is skipped and the commit still succeedsCross-process reactionsnotify.contract.spec.ts โ†’ "oversize notify payload skips the NOTIFY โ€” the commit succeeds and events stay discoverable via the poll path"
A LISTEN connection loss cannot crash the process, and the listener reconnectsCross-process reactionsact-pg notify.resilience.spec.ts โ†’ "attaches an error listener to the LISTEN client so a connection blip cannot crash the process"
Disposal cancels a pending reconnectCross-process reactionsnotify.resilience.spec.ts โ†’ "dispose during a pending reconnect cancels it โ€” no client is checked out after teardown"
A second socket error during reconnect is swallowed, and reconnects never overlapCross-process reactionsnotify.resilience.spec.ts โ†’ "a SECOND error on the dead client during the backoff window does not crash the process", "repeated reconnect cycles return the checked-out and listener counts to baseline (no leak, no double-subscribe)", "a re-entrant reconnect cancels the pending timer instead of leaking a second one (no double-LISTEN)"

Receiver idempotencyโ€‹

The receiver-side IdempotencyStore is two-phase: claim reserves a key, then commit keeps it or release drops it, so a failed handler never turns the sender's retry into a silent success.

ClaimSourceBacking test
claim is tentative; commit keeps the key, release drops it, and committed keys expireExternal integrationact-ops in-memory.spec.ts โ†’ "two-phase commit / release", "release drops an uncommitted claim so a retry re-processes", "commit makes a claim survive so a retry dedups", "release after commit is a no-op โ€” a committed claim stays claimed", "committed entries still expire after ttlMs"
receiver releases on a throw, commits on success, and answers an in-flight duplicate 503External integrationact-http receiver/start.spec.ts โ†’ "re-runs the handler on retry after a transient handler failure (no lost delivery)", "dedups a retry after a successful delivery (handler runs exactly once)", "answers a duplicate that arrives while the handler is in flight with a retryable 503", "does not lose a delivery when the original fails after a duplicate arrived mid-flight", "tracks in-flight keys through a store whose claim is async"
Hono and tRPC adapters commit or release from the outcome; Express and Fastify let the handlerExternal integrationact-http receiver/{hono,trpc,express,fastify}/index.spec.ts; receiver/finalize.spec.ts
The generated API answers an in-flight duplicate with a retryable errorAuto-generated APIact-http hono/index.spec.ts โ†’ "a duplicate of a still-running request gets a retryable 503, and is not lost when the original fails"; receiver/start.spec.ts โ†’ "answers 503 even when an async store replies to the duplicate before the original"; api/in-flight.spec.ts
withIdempotency commits on success and releases on failureAuto-generated APIact-http api/idempotency.spec.ts โ†’ "commits the key after the handler succeeds", "releases the key and propagates handler rejections after a fresh claim"
minSafeTtl throws for a sender that retries foreverExternal integrationact-ops min-safe-ttl.spec.ts โ†’ "unbounded sender (maxRetries: Infinity)"; in-memory.spec.ts โ†’ "refuses an unbounded retryProfile with no explicit ttlMs", "accepts an unbounded retryProfile when ttlMs sizes the window"

SSE framesโ€‹

ClaimSourceBacking test
Overlay keys survive a commit, so a reconnecting client matches a live oneReal-timesse/wire-safe.spec.ts โ†’ "gives a reconnecting client what a live one holds after a publish", "lets a later domain state overwrite an overlay key", "keeps overlay keys across several commits", "does not resurrect an overlay key the overlay itself cleared", "carries nothing when no overlay ever ran", "keeps the marker off the wire"
A Set reaches the client as an array, live and on reconnectReal-timeact-http sse/wire-safe.spec.ts โ†’ "delivers a Set-valued overlay as an array to a live client", "gives a reconnecting client the same value as a live one", "gives a reconnecting client the same Set-valued field a live one holds", "normalizes a Set nested inside the published state"
Every out-of-range SSE option throws ZodErrorAuto-generated APIapi/sse-wiring.spec.ts โ†’ "applies the maxPendingPerConnection default", "preserves a caller-supplied maxPendingPerConnection", "rejects maxPendingPerConnection below the floor", "rejects maxPendingPerConnection above the ceiling", "rejects a non-finite or fractional maxPendingPerConnection", "throws ZodError, the documented type, for every out-of-range knob"
An empty frame to a client with no baseline reports behindReal-timesse/apply-patch.spec.ts โ†’ "reports behind, not stale, for an empty frame with no baseline", "still reports stale for an empty frame when a baseline exists"
A cleared field reaches the client as a deleteReal-timesse/wire-safe.spec.ts โ†’ "delivers an undefined-valued delete to a live client", "treats a null-valued delete identically", "clears a nested field without disturbing its siblings", "delivers an undefined-valued delete through overlay() too", "does not rewrite the internals of a non-plain object", "keeps the server-side cached state untouched by normalization"

SSE subscriptionsโ€‹

ClaimSourceBacking test
A connection's pending backlog keeps at most maxPendingPerConnection framesAuto-generated APIact-http api/sse-wiring.spec.ts โ†’ "bounds the per-connection pending buffer, dropping the oldest when a slow consumer stalls"
A subscription that never got a slot releases noneAuto-generated APIact-http api/sse-wiring.spec.ts โ†’ "does not release a slot it never acquired when the cap is full and no on_cap_exceeded is supplied"
An _overlay frame at the current version merges onto a caught-up clientReal-timeact-http sse/apply-patch.spec.ts โ†’ "merges a marked overlay at the current version, keeping _v" / "still treats a same-version patch WITHOUT the marker as stale"
openapi() emits an open schema for a field with no JSON Schema formAuto-generated APIact-http openapi/index.spec.ts

Orchestrator and buildersโ€‹

ClaimSourceBacking test
A fold projection reloads on a version gap, and reset clears every fold cacheProjections to a databasefold-contiguity.spec.ts โ†’ "does not fold onto stale state when a sibling worker drained the gap", "still folds incrementally for a single worker โ€” no reload per event", "keeps each stream's frontier independent", "re-derives the row from the store instead of re-flushing the cache", "reset clears the cache for every fold projection, not just one"
A target has exactly one claimant, and re-registering a projection is a no-opState managementtarget-ownership.spec.ts โ†’ "a reaction may not target a projection's target", "a per-event projection may not share a served target", "re-registering one projection is idempotent"
claim trusts a row's work mark (correlated_at), which never moves backWriting a storestore-tck.ts โ†’ "subscription work set", "claims a marked stream without probing the event log", "never regresses the mark, for every value on the number line", "retires a stream when ack catches the watermark up to the mark", "keeps the mark across reset, so a rebuild is claimable", "keeps the mark across unblock, defer, and prioritize", "never claims an unmarked stream, however much work the log holds"
The correlate checkpoint is durable and only moves forwardWriting a storestore-tck.ts โ†’ "correlate checkpoint", "round-trips an advance through subscribe", "persists only when greater than the stored value", "is left untouched when subscribe omits it", "advances in the same call that registers discovered targets", "is invisible to every stream-scoped surface"; correlate.spec.ts โ†’ "resumes where the previous process stopped, re-reading nothing", "picks up events committed after the checkpoint, and only those", "advances as soon as a scan registers what it found", "never regresses on a later, lower value", "persists how far the scan read, because it scans now"; act-pg scope-isolation.spec.ts
Correlate marks the work for every target, static and dynamicCorrelation and draincorrelate.spec.ts โ†’ "marks the target with the id of the event that resolved to it", "raises the mark as later events arrive", "skips an event outside a pattern source, and marks one inside it", "skips an event outside a literal source", "marks nothing for an event its resolver declines", "a later lower-priority scan neither lowers the priority nor re-lanes", "a dynamic resolution never re-opens a static target's priority", "static targets survive LRU eviction"
The close safety probe reads pending work over events correlate has readClose cyclecorrelate.spec.ts โ†’ "closes a stream whose head has no reaction to consume it", "holds back a stream whose tail correlate has not read yet"; store-tck.ts โ†’ "surfaces the mark on query_streams positions"; recurring-reminder.spec.ts โ†’ "autocloses (reaps) the timer stream once the loop ends"
The sse subpath imports in a browserReal-timescripts/check-browser-safe.mjs โ€” walks the built subpath's static import graph, following @rotorsoft/* into their builds, and fails on any Node builtin
A windowed close caps its prune at pending work, not watermark lagClose cycleclose-windowed.spec.ts โ†’ "prune cap reads pending work, not watermark lag", "prunes a stream whose trailing events resolve to no target", "still caps at a consumer that genuinely has unconsumed work"
app.reset() arms the drain, so a settled app still replaysEvent sourcingrebuild.spec.ts โ†’ "should replay events when drain runs after reset on a settled app"
An unreachable Act is collected even without shutdown()dispose docdisposers.spec.ts โ†’ "skips a weak disposer whose target is gone", "prunes dead weak entries when anything else registers", "does not keep a shut-down Act reachable through the registry", "releases an Act once nothing else references it (needs --expose-gc)"
shutdown() waits for an in-flight settle cycle tooProduction checklistshutdown.spec.ts โ†’ "issues no store operations after teardown resolves", "does not return while a settle is parked mid-correlate", "proceeds when the grace budget expires on a stuck settle", "graceMs: 0 returns immediately", "the DEFAULT budget waits for a settle that is the only thing in flight", "treats a lane's leaseMillis: 0 as no pinned lease, not a zero budget", "does not abandon a parked handler on a zero-lease lane"
shutdown() hands back the correlation lease after the grace waitProduction checklistshutdown.spec.ts โ†’ "hands the correlation lease back last, so nothing re-takes it"
shutdown() waits for in-flight drains, up to a grace budgetProduction checklistshutdown.spec.ts โ†’ "awaits an in-flight cycle so the handler finishes and the lease is released", "still observes acked listeners for work that completes during the window", "graceMs: 0 keeps the old behavior โ€” returns without waiting", "proceeds when the budget is exhausted โ€” one stuck handler cannot hang teardown", and the derivation trio
reset rewinds watermarks to -1 and unblocksStore.reset docrebuild.spec.ts โ†’ "should reset subscribed stream watermarks to -1", "should unblock blocked streams after reset"
A static .emit() of a deprecated event version throws at buildEvent schema evolutiondeprecation.spec.ts โ†’ "throws when an action statically emits a deprecated event"
Two event names with the same version number throw at buildEvent schema evolutiondeprecation.spec.ts โ†’ "rejects a leading-zero version colliding with its canonical form regardless of order"
.on and state(...) throw on zero or several keysState managementstate-builder.spec.ts โ†’ "should throw when .on() receives multiple keys", "should throw when record has more than one key", "should throw when record has zero keys"
Same-name partials merge z.object state schemas, or the build throwsState managementpartial-state.spec.ts โ†’ "should throw on overlapping keys with different base types", "partials whose state schema is not a plain z.object"; builder.spec.ts โ†’ "should refuse to merge partials whose state schemas are not ZodObjects"
Overlapping full closes of one stream truncate once and never delete a later commitClose cycleclose.spec.ts โ†’ "overlapping full closes"; act-pg close-race.spec.ts
Partials sharing an event must use the same Zod schema instanceState managementslice.spec.ts โ†’ "throws when same-name state partials use different schema references for the same event"
State policies survive a partial merge, whichever partial declared themState managementpartial-state.spec.ts โ†’ "partial-state โ€” policies declared on a later partial", "two conflicting declarations are a conflict, not first-wins"; merge.spec.ts โ†’ "register_state โ€” single-declaration policies"
Scoped Acts keep their stores and caches apartExtension pointsscope.spec.ts โ†’ "two Acts with their own scoped ports โ€” no cross-talk", "scoped cache keeps per-Act snapshots isolated"
A scoped Act's background work uses its scoped portsExtension pointsscope.spec.ts โ†’ "settle() subscribes static targets on the scoped store, not the singleton", "start_correlations polls against the scoped store, not the singleton", "lane worker ticks drain against the scoped store, not the singleton"
The Act wires Store.notify at construction when the store has it and reactions existCross-process reactionsnotify.spec.ts โ†’ "subscribes when store has notify and reactions exist", "does not subscribe when store lacks notify", "does not subscribe when there are no reactive events"
A windowed close leaves the cache alone and the stream liveClose cycleclose-windowed.spec.ts โ†’ "prunes the prefix behind the boundary snapshot and keeps the stream live", "leaves the cache untouched โ€” no invalidation on prune"
Two windowed closes of one stream archive each range at most onceClose cycleclose-windowed.spec.ts โ†’ "fires the archive callback at most once when two windowed closes race the same stream"
.autocloses({ keep }) needs .snap(...), and rejects windows under a day and keep inside orClose policiesautoclose-builder.spec.ts โ†’ ".autocloses({ keep }) โ€” rolling window",: "requires .snap earlier in the chain โ€” the runtime guard for untyped callers", "gates keep behind .snap at the type level", "rejects windows below one day โ€” close is low-cadence housekeeping", "rejects keep inside the or block"
A stream is never acked past an event any reaction has not handledError handlingintra-event-ack.spec.ts โ†’ "holds the watermark when a later reaction on the same event fails", "acks the group once every reaction on the event succeeds", "keeps cross-event partial progress โ€” completed events ack, the failing group holds"
A keep prune waits for the oldest event to age out, and a terminate match winsClose policiesautoclose-reaction.spec.ts โ†’ "autoclose rolling window (keep)", "defers while the window holds, prunes once the tail ages out", "terminate and prune stay independent โ€” is full-closes even with keep declared", "passes the cutoff to the archiver on a windowed close", "skips the prune when no snapshot qualifies, retrying next trigger"
validateFoldedState throws ValidationError at the event that broke the schemaConfigurationvalidate-folded-state.spec.ts โ†’ "throws at the triggering event when a reducer produces schema-violating state", "names the state and the triggering event in the error", "does NOT throw and produces the bad state when the flag is off (default)", "catches the bad state on cold replay, not only on the committing action", "validates folded projection state (make_fold_handler) on replay"
A throwing lifecycle listener is contained; the operation and other listeners still runObservabilityemit-containment.spec.ts โ†’ "a throwing forgotten listener does not fail forget() after the PII is wiped", "a throwing closed listener does not fail close() after the truncate lands"
A throwing settled listener does not reach the circuit breakerObservabilitysettle-wakeup.spec.ts โ†’ "does not record the listener's throw as a store failure", "does not open the breaker, so the pipeline keeps draining", "control โ€” a non-throwing settled listener still fires and stays clean", "control โ€” a real store failure still reaches the breaker"
The settled payload covers every pass of the settleObservabilitysettle-wakeup.spec.ts โ†’ "reports the work the settle did, not its final empty pass"
Store.block returns the stored row, with the durable watermarkStore.block docstore-tck.ts โ†’ "returns the post-block row, not the caller's lease"
A blocked stream emits blocked once, even if the following ack failsStore.block docnon-retryable.spec.ts โ†’ "emits blocked even when the following ack fails"
A failed correlate init() is retried by the next correlateCorrelateCycle.init doccorrelate.spec.ts โ†’ "subscribes static targets on a retry after subscribe throws", "shares one init across concurrent callers (single-flight)"
A full SSE backlog never silently drops a version-neutral frameSseOptions.maxPendingPerConnection docact-http api/sse-wiring.spec.ts โ†’ "evicts a version-keyed frame rather than the overlay beside it", "collapses an all-neutral backlog into a single resync", "still drops the oldest frame when every frame is version-keyed"
An _overlay frame is classified by its version against the client'sReal-timeapply-patch.spec.ts โ†’ "frame kind ร— version position" matrix
A _resync frame reports behind, and an evicted overlay baseline sends oneReal-timebroadcast.spec.ts โ†’ "a resync frame makes the client refetch rather than go stale", "reports an overlay whose baseline was evicted", and "fires the overlay-miss hook when the evicted entry carried overlay keys", "tells live subscribers of the evicted stream to refetch", "says nothing when the evicted entry carried no overlay state"
A throwing onOverlayMiss costs no frameReal-timebroadcast.spec.ts โ†’ "a throwing hook costs neither stream its frame", "a throwing hook on the overlay path loses neither the resync nor the call", "control โ€” a non-throwing hook reports the miss and both frames land"
snapshot.snaps counts snapshots in the loaded window, not a lifetime totalCache and snapshotscache.spec.ts โ†’ "reports the window cold and the carried total warm"
with_pii: false never decrypts and returns pii: nullWriting a storestore-tck.ts โ†’ the with_pii block, run against InMemory / act-pg / act-sqlite; act-pg pii-encryption.spec.ts โ†’ "a declined payload survives a row the default read cannot decrypt"
An unreadable row stops neither reactions nor app.audit()Auditing a storeact audit.spec.ts โ†’ "names the poison row and audits every row behind it", "honours the caller's limit across a resume", "rethrows when the locating read finds no row to blame", "rethrows a backward scan rather than answering it partially", "control โ€” an intact store reports no unreadable rows"
One stream's failed fetch does not stall the others, and blocks once its budget runs outFetch[].error docact store-failure-budget.spec.ts โ†’ "delivers to the healthy stream, and never skips the poison one", "contains a non-Error throw too"; "a read that keeps failing spends the retry budget"
webhook timeoutMs covers the whole exchange, body includedWebhookConfig.timeoutMs docact-http webhook/webhook.spec.ts โ†’ "times out a 5xx whose body never ends", "times out a 4xx whose body never ends", "reports the status when the body read fails without a timeout", "still captures a 5xx body that DOES end", "does not arm anything on the success path"
truncate applies windowed targets before full ones, with one seed per full targetWriting a storestore-tck.ts โ†’ "truncate", โ€” "leaves exactly one seed when a stream is listed twice", "applies windowed targets before full ones", run against InMemory / act-pg / act-sqlite
act_streams_blocked keeps its last value when no provider answeredinstrument docact-otel instrument.spec.ts โ†’ "a degraded read keeps the gauge's last value instead of publishing 0", "a registry with no providers still publishes 0", "a blocked_streams rejection does not poison the whole scrape"