Skip to content
Tracon

IStatePreflightReader

Namespace Tracon · Assembly Tracon.Abstractions.dll

Reads the stored state generations WITHOUT writing anything, so an operator can ask “can this build still read my data” BEFORE upgrading.

public interface IStatePreflightReader

This surface is deliberately separate from ISessionStore and IWorkflowCheckpointStore. Those two are application surfaces scoped to one tenant: they filter every read by the tenant in scope, page their results, and pull the full state payload of every row. A preflight asks a different question — how many rows exist per generation, across every tenant at once — and answering it by paging through the application surface would drag the entire state column across the wire.

Tenant mode — tenant-independent. Nothing here takes or applies a tenant identifier, and that is the point rather than an omission: an upgrade replaces the process for every tenant at the same moment, so a count that saw only one tenant would under-report the very risk this surface exists to measure. Never expose its results through a per-tenant API: a row count is information about other tenants.

Delivery — no delivery guarantee applies. Every method is a synchronous read that returns its own result; nothing is queued, retried, or handed to another party. A failed call throws and has changed nothing, so the caller’s only recovery is to call again.

Nothing here writes. Every implementation runs read-only queries and takes no lock; it is safe to run against a live database while the application is serving traffic.

The values returned are raw: this reader reports what is stored, never whether the running build understands it. That comparison belongs to StatePreflight in Tracon.Core, the only layer that knows which generation the current code writes.

DI lifetime — singleton. Registered with Replace by whichever SQL provider is active. No implementation is registered when persistence is in memory; resolve it as optional.

Gets the provider name, for example PostgreSQL.

string ProviderName { get; }

string

SampleAsync(StatePreflightTarget, int, CancellationToken)

Section titled “ SampleAsync(StatePreflightTarget, int, CancellationToken)”

Reads at most perGeneration rows of each generation, newest first.

ValueTask<IReadOnlyList<StateSample>> SampleAsync(StatePreflightTarget target, int perGeneration, CancellationToken cancellationToken = default)

target StatePreflightTarget

The table to sample.

perGeneration int

The maximum number of rows to read per generation. 0 reads nothing.

cancellationToken CancellationToken

The cancellation token.

ValueTask<IReadOnlyList<StateSample>>

The sampled rows, state payload included.

The sample is taken PER GENERATION rather than “the newest N rows overall”: the newest rows are the ones the running build just wrote, so they prove nothing. The risk concentrates in the older generations.

TallyAsync(StatePreflightTarget, CancellationToken)

Section titled “ TallyAsync(StatePreflightTarget, CancellationToken)”

Counts the rows of target per stored schema generation.

ValueTask<IReadOnlyList<StateGenerationTally>> TallyAsync(StatePreflightTarget target, CancellationToken cancellationToken = default)

target StatePreflightTarget

The table to count.

cancellationToken CancellationToken

The cancellation token.

ValueTask<IReadOnlyList<StateGenerationTally>>

One entry per distinct generation. An empty table returns an empty list, never an error.

A single aggregate query over the whole table; the state payload is never read. Rows of every tenant are counted, because an upgrade is not a per-tenant event.