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"
act-pg seed() creates its schema even on a connection that looked the schema up before another worker created itWriting a storeact-pg seed-upgrade.spec.ts → "creates the schema on a connection that looked it up before it existed"
Postgres streams.retry is an integer, so an unbounded retry count never overflows claimWriting a storeact-pg seed-upgrade.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"
query leaves snapshot rows out unless with_snaps is set, even when called with no query objectQuery.with_snaps docstore-tck.ts → "excludes snapshots when called with no query object"
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 when a reaction loses the race"
Precondition and validation failures leave the warm cache untouchedCache and snapshotscache.spec.ts → "precondition 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 fails neither the action nor a load"
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", "replays an unseeded stream up to the point, not from its latest snapshot", "time-travel ignores snapshots outside the cutoff"
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"

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", "should NOT treat work the handler did not await as a reaction"; reacting-to-singleton.spec.ts
Every action commits with the version it loaded or an explicit expectedVersion, a reaction's dispatch and a new stream's -1 included: a writer whose load went stale gets ConcurrencyError, so its preconditions and state check never pass on old state, and a retry re-checks themConcurrency modelversion-guard.spec.ts → "lets only one of two callers create a new stream", "re-checks the precondition when the creator that lost retries", "refuses a reaction whose load went stale", "re-checks a reaction's precondition on the state it retries with", "never lets a reaction store a state the schema rejects"; reacting-to.spec.ts → "should honor an explicit expectedVersion inside a reaction"; store-tck.ts → "treats expectedVersion -1 as an empty stream"
A z.date() field reads back as a Date; an ISO-shaped z.string() stays a stringExtension pointslibs/act/test/schema-dates.spec.ts; store-tck.ts → "schema-driven dates on read", "types z.date() and leaves an ISO-shaped z.string() alone, warm and cold"
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"
A resolver that throws during correlate stops the scan before that event, is logged once, never emits error or opens the breaker, and resumes from that event once it stops throwingError handlingapp-code-errors.spec.ts → "stops the scan before that event, logs once, and resumes without losing it"
A resolver that throws during the drain fails only its own stream, retrying on the reaction's backoff and blocking on its budgetError handlingapp-code-errors.spec.ts → "fails only its own stream, which blocks on the reaction's budget", "retries on the reaction's backoff while budget remains"
A reaction-requested close whose archive callback throws is logged and never counts on the store breaker; a StoreError inside it still doesError handlingapp-code-errors.spec.ts → "is logged and never counts on the store breaker"; autoclose-reaction.spec.ts → "surfaces a store failure inside the close as an error event"; act-pg store.error.spec.ts → "surfaces a DELETE error as a StoreError that keeps the cause"; act-sqlite store.error.spec.ts → "truncate: rolls back and surfaces a DELETE failure as a StoreError"
A handler that throws a DeferSignal with a schedule the schema rejects fails only its own reaction: it never voids the drain pass or counts on the store breakerError handlingdefer.spec.ts → "an invalid schedule fails its own reaction, never the drain pass"
The circuit breaker shares one failure count across loops, so a partly broken store can leave it closed while every failure still emits errorError handlingstore-error-circuit.spec.ts → "stays closed while another loop's store calls keep succeeding", "control — opens when nothing else touches the store"
A local commit, app.reset(), app.unblock(), app.restore() or a notification from another process that frees reaction work settles it with no wiring unless the app sets autoSettle: false; a scheduled pass is never put off, the framework's own wake-ups never replace a caller's settle options, and nothing settles after shutdown()Event sourcingsettle-wakeup.spec.ts → "runs the reaction with no settle wiring", "runs nothing on its own with autoSettle: false, until the caller walks the pipeline", "control — schedules nothing for an event no reaction handles", "replays a reset target with no settle call", "resumes an unblocked stream with no settle call", "control — a reset that resets nothing schedules nothing", "runs no reaction for a commit after shutdown", "keeps a caller's options when an internal wake-up lands mid-cycle", "doesn't put off a scheduled pass when more calls arrive", "control — stop() drops the caller's options with the pending pass", "brings a scheduled pass forward for a shorter delay", "keeps a caller's settle options while start_correlations polls", "scans with the poll's limit only while the poll runs", "a poll with limit 0 scans at the background default, not the whole log", "a pass queued mid-cycle keeps the caller's delay"; notify.spec.ts → "wakes settle when notification carries a reactive event", "does not wake settle with autoSettle: false"; restore.spec.ts → "settles the restored events' reactions with no settle call"
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)"; store-error-circuit.spec.ts → "leaves the breaker OPEN"
After any store failure, open or not, the breaker re-drives settle cooldownMs later, so an idle app recovers on its ownError handlingstore-error-circuit.spec.ts → "re-drives settle cooldownMs after a store failure, before the breaker opens", "control — runs the reaction when settle's read doesn't fail"; circuit-breaker.spec.ts → "fires the on_retry hook cooldownMs after a failure that leaves it closed"
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"
Without notify, an idle process never sees another process's commits, or a lease a dead worker let expire; each start_correlations tick correlates and wakes every lane, so polling delivers bothCross-process reactionscorrelate.spec.ts → "polling discovers and drains a commit this process never saw", "polling reclaims a lease a dead worker let expire"
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"
A correlate scan reads only limit from its query (app.correlate reads 10 when there is none, 0 reads everything), so a filter or after loses no reactionCorrelation and draincorrelate.spec.ts → "runs every reaction after an unfiltered scan", "ignores %s in app.correlate(), losing no reaction", "ignores a filter in settle's correlate option, losing no reaction", "reads 10 with no query, an empty one, or a leftover filter", "reads the given limit, and everything with limit 0"
An action checks its preconditions in declaration order; the first that doesn't hold refuses it with PreconditionError ("failed precondition") and nothing commitsState managementpreconditions.spec.ts → "runs the action when every precondition holds", "refuses at the first precondition that doesn't hold, and commits nothing", "passes the validated payload, the state and the actor to holds"
One precondition serves every action whose payload has its fields, and merged partial states keep each partial's preconditionsState managementpreconditions.spec.ts → "shares one precondition between actions", "keeps each partial's preconditions when partial states merge"
settle() never drops a wake-up requested while a cycle runs, even one that lands while its scan or claim is still outCorrelation and drainsettle-wakeup.spec.ts → "runs a cycle for a schedule() that fires while a cycle is in flight", "drains a stream unblocked while an empty claim is in flight", "control — drains a stream unblocked after the pass", "reacts to a commit that lands while an empty scan is in flight", "control — reacts to commits that land between passes"
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 when a reaction loses the race"
A state projection's first fold pairs state with the head id from the same load()Projections to a databasestate-projection.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 snapshotscache.spec.ts → "cache.set rejection warns but fails neither the action nor a load"
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"
A lane name declares one config: the same config declared again on the Act or a slice, in any order, is one lane; a different config throws at compositionConfiguration § Laneslanes.spec.ts → "merges a lane the slice and the Act both declare, in either order", "accepts a lane declared again with the same config", "rejects a lane declared twice with different configs", "rejects a slice lane declared twice with different configs", "rejects slice/Act lane configs that disagree on timing"
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.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.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", "delivers restored events when the restore lands during a correlate scan", "delivers restored events when the scan's last write lands after the restore starts", "a correlate pass that starts during the restore reads nothing, and loses nothing", "delivers restored events when the restore lands during correlate's startup"; act-pg restore-live.spec.ts → "delivers restored events when a scan's write was sent during the restore"
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.spec.ts → "runs the drain at the persisted due-time with no intervening commit", "re-seeds a deferred stream sorting past the first stream page", "seeds a non-default lane's controller from persisted deferred_at"
A deferred or backed-off reaction fires at its due-time on an idle app, with no further callsClose cycledefer.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", "runs every stream of a burst whose first pass only deferred", "re-fires after the finalize that would persist its schedule fails"; backoff.spec.ts → "clears backoff entry on successful ack"
A defer holds the event pending without bumping retry, then redelivers when dueClose cycledefer.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"
.autocloses with keep and a time-gated terminate closes a stream that went quiet after a prune, and keeps the event the terminate readsClose policiesautoclose-reaction.spec.ts → "closes a stream that went quiet right after a prune", "keeps the terminal head when the only snapshot is above it"
An adapter passes the TCK only if a commit keeps every append's meta and pii, a guard sees a trailing marker, a truncate resets the head, and a failed restore changes nothingWriting a storestore-tck.ts → "attaches meta to every append, not just the last", "a guard below a trailing tombstone is stale (what stops a writer during a close)", "a commit after a full truncate continues from the seed at version 0", "changes nothing when it fails"
A restart close brings an SSE-only client back with the listener in the docsReal-timeresync-restart.spec.ts → "brings an SSE-only client back with the listener in the docs"
A parked .autocloses reaction doesn't stop app.closeClose cycleautoclose-reaction.spec.ts → "doesn't let a parked autoclose refuse app.close"
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 managementdefer.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 managementdefer.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.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.spec.ts → "keeps the drain armed while finalization is unhealed"
A malformed literal .defer(...) throws ZodError at buildState managementdefer.spec.ts → "rejects a bad literal schedule at build time"
throw new DeferSignal(when) holds the stream until due, then redeliversState managementdefer.spec.ts → "{ at: Date } holds until the due-time, then acts", "{ after } is measured from the event's created time (parks, not acked)"
A sensitive field is stored in pii, never in data, behind .optional(), .nullable(), .default(), .readonly(), .catch(), either side of a pipe (.transform(), .pipe(), z.preprocess()), z.lazy(), and any refinement chained after sensitive()Sensitive datacommit-pii-split.spec.ts → "survives a refinement chained after sensitive()", "survives .transform(), .pipe(), z.preprocess() and z.lazy() around sensitive()"; zod-layers.spec.ts → "seeing through Zod wrappers"
The first build warns once, naming each sensitive event field and same-named state field whose type isn't a string (it would read as a redaction marker)Sensitive datasensitive-build.spec.ts → "the sensitive-type advisory"
A state whose events declare a sensitive(...) field and that declares .snap() throws at build, naming the state and those eventsSensitive datasensitive-build.spec.ts → "throws at build, naming only the offending state and every sensitive event", "builds a sensitive state without .snap(), and a plain state with it"
A committed listener scoped to a state (by State or name) hears only commits by that state's actions, with snapshots typed from it; it is contained like every listener, and a state the app doesn't have throws at registrationEvent sourcingcommitted-by-state.spec.ts → "a committed listener scoped to one state"
An action whose handler returns no events commits nothing and emits no committed, on a new, warm or cold stream, and the trace logs no commitState managementact.spec.ts → "commits nothing and emits no committed, on a new, warm or cold stream", "control: an action that emits announces its commit"; tracing.spec.ts → "withActionTrace skips commit log when nothing emitted on a stream with history"
A per-call correlator set to undefined falls back to the app's correlatorEvent sourcingcorrelator.spec.ts → "keeps the app's correlator when a call passes correlator: undefined"

Store contract​

Pinned in the TCK unless the citation names an adapter.

ClaimSourceBacking test
One store instance takes concurrent commits, reads and writes from one process, and a race on one stream has one winner (ConcurrencyError for the other)act-sqlite README § Concurrency modelstore-tck.ts → "takes concurrent commits, reads and writes on one instance"
A commit across streams lands every append or none: a stale append (first, middle or last) stores nothing and its ConcurrencyError names that stream; a stream named twice and an append with no events are refused; ids follow append order, then event order; of two commits racing on a shared stream one winsStore.commit doc, Writing a storestore-tck.ts commit across streams → "lands every append, each from its own head, with ids in append order", "stores nothing when the first, a middle or the last append is stale, and names its stream", "refuses a stream named twice and an append with no events, storing nothing", "lands an unguarded append beside guarded ones", "lets one of two racing commits on a shared stream win, and both on disjoint streams"; an append that fails part-way, after its head probe: libs/act-pg/test/commit-across-streams.spec.ts, libs/act-sqlite/test/store.error.spec.ts → "commit across streams, failing part-way"
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 docclassify.spec.ts → "collapses same-target reactions, keeping the max priority" / "keeps the max priority regardless of declaration order"
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 → "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 refused action maps to 409: PreconditionError to PRECONDITIONAuto-generated APIact-http api/errors.spec.ts → "maps PreconditionError to 409 + PRECONDITION"
The OpenAPI document types each action's success response from its state: the state, each event's data by name, the patch; a sensitive event field, and every field of a state with sensitive events, may read as a redaction markerAuto-generated APIact-http openapi/index.spec.ts → "openapi — typed action responses"
The generated tRPC and Hono APIs are typed from the app: each action's payload in, its snapshots out (ApiError on a Hono error status), and with sse each state's subscription or routeAuto-generated APIact-http typed-api.spec.ts → "trpc(app) types", "hono(app) types" (checked by pnpm typecheck)
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"
trpc(app, { sse }) and hono(app, { sse }) refuse at build a state whose events declare sensitive fieldsAuto-generated APIact-http trpc/sse.spec.ts → "trpc sse — a state with sensitive events"; hono/sse.spec.ts → "hono sse — a state with sensitive events"
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 datastate-projection.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"
The SQL adapters' recovery writes (reset, unblock, prioritize, defer) throw a driver failure as a StoreError naming the operation, with the driver error as causeWriting a storeact-pg store.error.spec.ts → "recovery writes"; act-sqlite store.error.spec.ts → "defer: rolls back and wraps UPDATE failure in StoreError"
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 reaction's source is a regex only when anchored, so a default or dynamic reaction delivers on any stream name, and a close waits for it, on every storeExtension pointsstore-tck.ts → "takes a target reading itself, whatever its name holds"; stream-names.spec.ts → "a close waits for a default reaction still behind on such a name", "reads a written source as a name unless it's anchored", "delivers a dynamic reaction whose source is any stream's name"
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 stream a commit appends to, with that stream's eventsStore.notify docstore-tck.ts → notify : "delivers one notification carrying a stream's full batch from one commit", "delivers one notification per stream a commit appends to, with that stream's events"
A store still shaped for the commit before 2.0 is refused when it's injected, on the port and in a scoped ActMigrating to 2.xstore-shape.spec.ts → "is refused when injected", "is refused in a scoped Act"
A static target with reactions on two sources refuses to buildCorrelation and drainclassify.spec.ts → "refuses static reactions on two sources"
A result's version is the stream's head, after a snapshot too, so it pins the next actionAuto-generated APIversion-guard.spec.ts → "is the stream's head after a snapshot, so it pins the next action"
BroadcastChannel.resync drops a stream's cached state and tells its clients to refetchReal-timeact-http sse/broadcast.spec.ts → "resync drops the cached state and tells clients to refetch"
Every $ref in a generated OpenAPI document resolves, for recursive and .meta({ id }) schemasAuto-generated APIact-http openapi/index.spec.ts → "resolves every ref"
Time travel keeps what a prune or a restart moved into a snapshotCache and snapshotstime-travel.spec.ts → "keeps the state a windowed close moved into a snapshot", "keeps the state a restart seeded"
A stream name that reads as a pattern (starts with ^ or ends with $) has no autoclose, and doesn't stop other reactionsClose policiesautoclose-reaction.spec.ts → "leaves a stream whose name reads as a pattern to app.close"
A webhook doesn't follow a redirect, so a 3xx blocks instead of losing the eventact-http READMEact-http webhook/webhook.spec.ts → "blocks on a real redirect, with one POST"
Audit doesn't list a healthy closed stream as a correlation gap, and counts every snapshot of a restart candidateAuditing a storeaudit.spec.ts → "doesn't list a healthy closed stream as a correlation gap", "counts every snapshot of a restart candidate"
An or.after backstop still fires on an idle stream after an earlier after has passedClose policiesautoclose-reaction.spec.ts → "keeps a later or.after backstop once the earlier after has passed"
Subscribing to the same streams in different orders doesn't deadlock on PostgresWriting a storeact-pg commit-across-streams.spec.ts → "subscribing in different orders doesn't deadlock"
A failed BEGIN on SQLite is a StoreError, on a commit, a write and a restoreWriting a storeact-sqlite store.error.spec.ts → "wraps a BEGIN that fails, on a commit, a write and a restore"
The loser of a commit race leaves nothing on any of its streams, and every event of every append is stored wholeTCK conformancestore-tck.ts → commit across streams : "leaves nothing of the commit that lost a race, on any of its streams", "stores every event of every append, whole"
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 Hono route answers a body that fails the schema, or JSON that doesn't parse, with 422 and the ApiError envelopeAuto-generated APIact-http hono/index.spec.ts → "body that fails Zod validation returns 422 with the ApiError envelope (matches the in-do ValidationError path)", "a JSON body that doesn't parse gets the same 422 ApiError envelope"
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"
A snapshot doesn't make a live client resync: the listener in the docs publishes the snapshot's version as an empty patchReal-timeact-http sse/resync-restart.spec.ts → "keeps a live client caught up across a snapshot"
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
An action with .loads appends to its own stream and every loaded stream it emits to in one commit, returns only its own snapshots, and committed fires once per stream appended to (its own first), state-scoped listeners hear their own state's streams, and reactions see every stream appended toState managementloads.spec.ts → "commits to both streams at once and returns its own snapshots", "a state's committed listener hears each of its streams in its own call"
A precondition over a loaded state refuses with PreconditionError, and a loaded state its schema rejects with ValidationError; nothing commits. An owner's own event and one from an outside action both fold, told apart by meta.causation.actionState managementloads.spec.ts → "refuses with PreconditionError over the loaded state, storing nothing", "refuses a loaded state its schema rejects, storing nothing", "the owner's own action and an outside emitter both fold, told apart by causation"
Every stream an action appends to is guarded by its version at load: a race is a ConcurrencyError naming that stream, retried by maxRetries with fresh loads (also when the caller pins its own stream, unless that stream moved), and the moved stream's cache entry is droppedState managementloads.spec.ts → "is retried with fresh loads, and the retry re-checks the rule", "surfaces ConcurrencyError, naming the stream, past maxRetries", "is retried even when the caller pins its own stream's version", "on the pinned own stream is never retried", "a lost race drops the moved stream's cache entry"
On Postgres and SQLite, an action with .loads commits every stream in one step, its own first; a refusal stores nothing on any stream; a rival on a loaded stream refuses the whole commit, naming that stream; and racing actions never break a rule over a loaded stateState managementstore-tck.ts → "commits both streams at once and fires committed once per stream", "stores nothing on either stream when a rule or a schema refuses", "a rival on the loaded stream refuses the whole commit, naming that stream", "racing enrollments fill the seats exactly: the rest are refused by the rule"
Over an action with .loads, a generated route returns only that action's snapshots and is typed by its own state; a rule over a loaded state, or two loads naming one stream, answers 409 naming the action and the rule, and a rival on a loaded stream answers 412 naming that stream; OpenAPI describes bothAuto-generated APIlibs/act-http/test/api/loads.spec.ts → "returns only the action's own snapshots", "a rule over the loaded state answers 409, naming the action and the rule", "a rival on the loaded stream answers 412, naming that stream", "maps a refusal and a lost race to their codes", "a transfer to the same account answers 409", "describes the refusal and the lost race on the route"; typed-api.spec.ts → "types a mutation by its own state, not the loaded one", "types a route by its own state, not the loaded one"
A mistake in an .emit handler fails to compile, marked on the entry: a misspelled event, an event no load lists, a wrong or missing field, an extra field at any depth, also in a list of unknown length; nothing for the action's own stream, no return value, and .emit("Event") for an event the payload can't satisfy fail to compile tooState managementemit-types.spec.ts → "a misspelled event", "an event no load lists", "a wrong field type", "a missing field", "an extra field", "an extra field in a nested object", "an extra field in an array", "nothing for the action's own stream", "refuses a handler with no return value", "checks a list of unknown length", "offers the name form only for an event the payload can satisfy"
A generated API opens an SSE subscription for a state reached only through .loads, and refuses sse when that state's events declare sensitive fieldsAuto-generated APIsse-wiring.spec.ts → "gets its own subscription: its streams change too", "throws when its events declare sensitive fields"
A value's stream outlives its holder: an action frees the value, then the holder's stream can close, and closing without that action keeps the value taken; a limit lowered below the slots taken keeps those holding them and refuses new onesState managementrecipes/rules-across-streams/examples/unique-values.spec.ts → "freeing on close: the closing action frees the address, then the stream closes", "closing without the action keeps the address taken"; recipes/rules-across-streams/examples/count-limits.spec.ts → "a lowered limit: those enrolled keep their seats, and new ones are refused"
A load without events is a read: nothing is appended to its stream, so the commit lands when that stream movedState managementloads.spec.ts → "on a read-only load lands anyway: reads aren't guarded"
A target returning undefined skips its load: loaded holds undefined, events listed on it are dropped, and the action still commits to its own streamState managementloads.spec.ts → "a target returning undefined skips the load, and its events are dropped"
A target naming a stream with no events loads the initial state; a target with an empty name, the action's own stream or another load's stream refuses with PreconditionError, so a client gets a 409, not a 500; a load of a closed stream, a throwing target, an emitted event no load lists and an action that appends only to loaded streams refuse the action at the call; a load of an unregistered state, a state loaded twice without names, an event the loaded state doesn't emit, a deprecated event, an event listed on two loads and a static .emit of a listed event throw at buildState managementloads.spec.ts → "a target with an empty name refuses", "a load of its own stream refuses", "two loads naming one stream refuse", "a load of a closed stream refuses the whole commit", "a target that throws commits nothing", "a target naming a new stream loads the initial state", "an emitted event no load lists throws at the call", "an action that appends only to loaded streams throws, storing nothing", "a load of a state not registered in this Act throws at build", "a state loaded twice without names throws", "an event the loaded state doesn't emit throws", "an event listed on two loads throws", "a deprecated event listed on a load throws at build", "a static emit of an event its load lists fails to compile and throws at build"
Each Act resolves a load to its own merge of the loaded state, whatever other Acts register; a loaded stream loads with the caller's actor, so its sensitive fields are gated and the state check ignores their markers; a reaction's action carries correlation and causation onto every stream it appends toState managementloads.spec.ts → "a loaded stream loads with the caller's actor, so its sensitive fields are gated", "a reaction's action carries correlation and causation onto every stream", "each Act loads its own merge of the loaded state"
A fold projection reloads on a version gap, and reset clears every fold cacheProjections to a databasestate-projection.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"
Reaction and projection handlers are named functions: an arrow function throws at build, and two handlers for one event can't share a nameState managementbuilder.spec.ts → "should throw for anonymous handlers in act builder"; projection.spec.ts → "should throw for anonymous handlers"; slice.spec.ts → "should throw for anonymous handlers"; target-ownership.spec.ts → "throws on duplicate reaction names within one slice", "throws on duplicate projection handler names"
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() schedules the replay, so a settled app still replaysEvent sourcingsettle-wakeup.spec.ts → "replays a reset target with no settle call"; rebuild.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"
Nothing starts work after shutdown() resolves: not the settle loop, not a pass still scanning when the grace budget runs out, and not a pass scheduled while the notify subscription comes downProduction checklistshutdown.spec.ts → "starts no settle pass after shutdown resolves", "control — a settle that needs one pass also runs none", "a pass still scanning when the grace runs out starts nothing after shutdown", "control — a grace the scan fits in also starts nothing after", "a slow notify disposal can't let a scheduled pass run", "control — an instant disposal cancels it too"
shutdown() hands back the correlation lease after the grace waitProduction checklistshutdown.spec.ts → "hands the correlation lease back last, so nothing re-takes it", "a pass still scanning when the grace runs out starts nothing after shutdown", "a renewal reply landing after shutdown leaves the lease handed back", "control — a renewal reply landing within the grace"
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
A deferred reaction's wake starts no drain pass after shutdown() resolvesProduction checklistshutdown.spec.ts → "runs no handler after shutdown resolves", "control — a wake that needs one pass also runs none"
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", "should throw when one partial declares a value and another a list", "should throw when two partials list different elements", "partials whose state schema is not a plain z.object"; builder.spec.ts → "should refuse to merge partials whose state schemas are not ZodObjects"
An action belongs to one partial: declaring it in two partials throws at build, even when they share its schema; the same partial registered through several slices is not a duplicateState managementpartial-state.spec.ts → "should throw on duplicate action across partials", "should throw on a duplicate action even when the partials share its schema"
Overlapping full closes of one stream truncate once and never delete a later commitClose cycleclose.spec.ts → "overlapping full closes"
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"
Every action checks the state it leaves against the state schema once, before the commit: a state the schema rejects refuses the action, nothing is stored and no reaction runs; the check never rewrites the state; loads and replays don't checkState managementinvariants.spec.ts → "refuses before the commit: nothing is stored, no reaction runs, the stream keeps working", "names the state and the action in the error", "checks the state the action leaves, not the states between its events", "checks without replacing the state with the parsed value", "loads and replays don't check"
A state schema refinement that throws refuses the action with ValidationError and stores nothing, as a payload's doesState managementinvariants.spec.ts → "refuses with ValidationError when a refinement throws, as a payload's does"
On a state with sensitive events, the state check ignores failures whose value is [REDACTED] or [SHREDDED], so another actor's action and every action after forget commit; any other failure, a field derived from a hidden value, and a marker string on a state without sensitive events still refuseSensitive datainvariants.spec.ts → "ignores a value another actor reads as [REDACTED]", "ignores a forgotten value, so the stream keeps working after forget", "still refuses a real value that breaks the schema", "still refuses a field derived from a hidden value", "checks a marker string in full on a state without sensitive events"
A state field cleared with null in a patch loses its key, so a .nullable() field (which requires the key) refuses the clearing action and stores nothing, while .nullish() accepts itState managementinvariants.spec.ts → "refuses a .nullable() field cleared with null, because the patch deletes the key"
An action folds each event once, before the commit; reducers see the next version and placeholders for the store-assigned id (-1) and created, and the returned snapshots carry the stored rowsState managementinvariants.spec.ts → "folds each event once, with the next version and a placeholder id"
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"