import type { Store } from "@rotorsoft/act/types"; /** * One {@link Store} implementation to feed into * {@link runStoreDifferentialTck}. The harness drops + seeds each store, * replays the identical generated workload against all of them, then * compares their normalized outputs. */ export type DifferentialStore = { /** Display name used in assertion messages and the describe block. */ readonly name: string; /** * Produces the store under test. Called once in `beforeAll`; the * harness owns its lifecycle (`drop` + `seed`, then `dispose`). */ readonly factory: () => Store | Promise; }; /** * Known cross-adapter divergences the caller wants the differential to * skip until the backing fix lands. Each flag suppresses exactly the * assertions that a currently-filed bug makes red, so the differential * stays green on master and the gate un-gates itself (delete the flag) * when the fix merges. Every flag names its issue so the reason is * auditable at the call site. */ export type DifferentialSkips = { /** * #1197 — SQLite `LIKE` is ASCII-case-insensitive, so a **mixed-case * regex pattern** filter overmatches vs PG (`~`) / InMemory (`RegExp`), * which are case-sensitive. Skips only the pattern-driven mixed-case * assertions; exact-match (`stream_exact`) mixed-case still runs, since * that path is case-exact on every adapter. Un-gate when #1197 lands. */ readonly caseInsensitivePatterns?: boolean; /** * #1199 — `names: []` and falsy-zero `before`/`after: 0` guards differ * across adapters (`names: []` → PG returns all, InMemory/SQLite return * none; `before: 0` / `after: 0` are dropped by some adapters' truthy * guards). Skips only those specific edge-input query assertions. * Un-gate when #1199 lands. */ readonly queryEdgeInputs?: boolean; }; /** * Options for {@link runStoreDifferentialTck}. */ export type StoreDifferentialTckOptions = { /** Display name for the differential suite. */ readonly name: string; /** * Two or more stores to drive in lockstep and compare. The first * entry is the reference; every other store's normalized output must * match it exactly. Pass `InMemoryStore` as the reference and the * durable adapter(s) as the comparands. */ readonly stores: ReadonlyArray; /** * Base PRNG seed. Workload `r` of {@link StoreDifferentialTckOptions.runs} * is built from `seed + r`, so the whole fuzz campaign is reproducible: * the same `seed` ⇒ the same family of workloads ⇒ any divergence is * deterministically replayable. Default `0xac7`. */ readonly seed?: number; /** * Number of distinct event-bearing streams per workload. * Default `4`. */ readonly streams?: number; /** * How many independent randomized workloads to generate and compare, * each from a distinct seed (`seed`, `seed + 1`, …, `seed + runs - 1`). * More runs widen the slice of the input space the differential * explores; fewer keep durable-adapter suites fast. Default `8`. */ readonly runs?: number; /** * Whether the comparand adapters support the optional {@link Store.forget_pii} * surface (and the `pii` field on commit). When `true`, the workload * commits PII-bearing events and the differential asserts that * `forget_pii` wipes them identically across adapters. All in-tree * adapters implement it, so it defaults to `true`; a third-party store * that opts out sets it `false`. Default `true`. */ readonly piiIsolation?: boolean; /** * Known-divergence gates — see {@link DifferentialSkips}. Each flag * suppresses the assertions a currently-filed bug makes red so the * differential stays green until the fix merges. Default: nothing * skipped. */ readonly skip?: DifferentialSkips; }; /** * Cross-adapter differential contract (#1030, fuzz workloads #1057, * lease/truncate/pii/query fuzz #1200). * * Adapters can drift in ways per-adapter cases don't catch — event * ordering, the `with_snaps` snapshot floor, the exact shape of * `query_stats` / `query_streams` output, the lease lifecycle's effect on * a stream's `retry`/`blocked`/`error`, windowed-truncate boundaries, * `forget_pii` erasure, and query-option edge cases. This harness drives a * **family of randomized, seeded workloads** — commits (some PII-bearing), * inline + windowed truncates, subscriptions, and the full lease lifecycle * (`claim` / `ack` / `defer` / `block` / `unblock` / `reset` / * `prioritize`) — against every store in `options.stores`, then asserts * their **normalized** outputs are byte-for-byte identical for every * workload. * * Each workload is its own seeded plan (`seed`, `seed + 1`, …): the * operation sequence — and even its length — varies by seed, so divergence * is hunted across a slice of the input space rather than one fixed script. * The seeds are deterministic, so a failing workload is always replayable. * * Normalization drops only the fields that legitimately differ between * stores (absolute event ids, `created` timestamps, correlation/causation * uuids, lease holder UUIDs, and wall-clock lease expiries) and keeps * everything that defines correctness (stream, version, name, data, * emission order, and the durable subscription state a lease op leaves * behind: watermark, retry, blocked, error, priority, lane). Stream names * carry mixed case so a case-insensitive pattern filter (#1197) surfaces as * a diff. * * Wire it with the in-memory store as the reference and one or more * durable adapters as comparands: * * @example * ```ts * import { runStoreDifferentialTck } from "@rotorsoft/act-tck"; * import { InMemoryStore } from "@rotorsoft/act"; * import { PostgresStore } from "../src/index.js"; * * runStoreDifferentialTck({ * name: "InMemory vs Postgres", * runs: 6, // durable adapter: fewer workloads keep the suite fast * stores: [ * { name: "InMemoryStore", factory: () => new InMemoryStore() }, * { name: "PostgresStore", factory: () => new PostgresStore({ ... }) }, * ], * }); * ``` */ export declare const runStoreDifferentialTck: (options: StoreDifferentialTckOptions) => void; //# sourceMappingURL=store-differential-tck.d.ts.map