@rotorsoft/act-tck 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,8 +39,12 @@ That's the whole integration. `run*Tck` calls vitest's `describe`/`it` internall
39
39
  ## API
40
40
 
41
41
  - **`runStoreTck(options)`** — every `Store` method, capability-gated where optional.
42
+ - **`runStoreDifferentialTck(options)`** — drives a family of randomized, seeded workloads against multiple `Store` instances and asserts identical normalized output (event order, `with_snaps` floor, `query_stats` / `query_streams`) for every workload. Catches cross-adapter drift a single-adapter suite can't.
43
+ - **`runStorePropertyTck(options)`** — property-based store invariants (commit version monotonicity, claim/lease no-leak, watermark monotonicity, block exclusion) over fast-check-generated sequences.
42
44
  - **`runCacheTck(options)`** — every `Cache` method, cross-stream isolation, dispose idempotency.
45
+ - **`runCacheDifferentialTck(options)`** — drives randomized `set` / `invalidate` / `clear` workloads against multiple `Cache` instances and asserts identical observable `get()` after every op.
43
46
  - **`runLoggerTck(options)`** — structural smoke test of the `Logger` contract.
47
+ - **`runLoggerDifferentialTck(options)`** — drives the identical call surface against multiple `Logger` instances and asserts robustness + structural parity (what throws, what conforms).
44
48
  - **`runStabilityTck(options)`** — snapshot-based public-API stability gate. Catches accidental rename / removal / signature drift on a package's public surface before it merges.
45
49
  - **`StoreCapabilities`** / **`CacheCapabilities`** / **`LoggerCapabilities`** — flag types for opting into optional surface (e.g., `Store.notify`).
46
50
  - Fixture helpers re-exported from `@rotorsoft/act-tck/fixtures` for adapter-specific tests that want the same Counter domain.
@@ -63,14 +67,76 @@ Every method on the `Store` interface in [`libs/act/src/types/ports.ts`](https:/
63
67
  - `query_stats` — array + filter forms, opt-in count/tail/names, exclude + before, snapshot count via `names`
64
68
  - `notify` (capability-gated) — subscribe + dispose smoke test
65
69
 
70
+ ### `runStoreDifferentialTck`
71
+
72
+ Where `runStoreTck` proves each adapter honors the contract in isolation, the differential harness proves they honor it _identically_. It replays a **family of randomized, seeded workloads** — commits, inline snapshots, truncates, subscriptions, in a seed-varying order — against two or more `Store` instances (in-memory as the reference, durable adapters as comparands), then asserts their **normalized** outputs match exactly for every workload. Each workload runs from a distinct seed (`seed`, `seed + 1`, …, `seed + runs - 1`), so divergence is hunted across the input space rather than one fixed script; a failing workload names its seed for replay:
73
+
74
+ - global forward `query` order
75
+ - per-stream `with_snaps` snapshot floor
76
+ - backward traversal order
77
+ - `query_stats` head / tail / count / names (plus filter-form key order)
78
+ - `query_streams` rows (source, watermark, blocked, priority, lane)
79
+
80
+ Normalization drops only what legitimately differs between stores (absolute event ids, `created` timestamps, correlation/causation uuids). A one-adapter `with_snaps` regression surfaces as a diff against the reference. Wire it with the in-memory store first:
81
+
82
+ ```ts
83
+ import { runStoreDifferentialTck } from "@rotorsoft/act-tck";
84
+ import { InMemoryStore } from "@rotorsoft/act";
85
+ import { MysqlStore } from "../src/index.js";
86
+
87
+ runStoreDifferentialTck({
88
+ name: "InMemory vs Mysql",
89
+ stores: [
90
+ { name: "InMemoryStore", factory: () => new InMemoryStore() },
91
+ { name: "MysqlStore", factory: () => new MysqlStore({ /* … */ }) },
92
+ ],
93
+ });
94
+ ```
95
+
66
96
  ### `runCacheTck`
67
97
 
68
98
  Every method on the `Cache` interface: `get` on unset stream returns `undefined`; `set` then `get` round-trip; `set` overwrites; `invalidate` removes one stream, leaves others; `invalidate`/`clear` no-op on absent state; `clear` empties every stream; cross-stream isolation; `dispose` idempotency.
69
99
 
100
+ ### `runCacheDifferentialTck`
101
+
102
+ The `Cache` analog of the store differential. It drives a family of randomized, seeded workloads (`set` / `invalidate` / `clear` over a small key set kept within capacity, so eviction — an adapter policy, not a contract guarantee — never enters the comparison) against two or more `Cache` instances and asserts their observable `get()` snapshot is identical after **every** operation. A cache that mishandles overwrite ordering, leaks an invalidated key, or clears partially diverges on the exact op that broke it.
103
+
104
+ ```ts
105
+ import { runCacheDifferentialTck } from "@rotorsoft/act-tck";
106
+ import { InMemoryCache } from "@rotorsoft/act";
107
+ import { RedisCache } from "../src/index.js";
108
+
109
+ runCacheDifferentialTck({
110
+ name: "InMemory vs Redis",
111
+ caches: [
112
+ { name: "InMemoryCache", factory: () => new InMemoryCache({ maxSize: 1000 }) },
113
+ { name: "RedisCache", factory: () => new RedisCache({ /* … */ }) },
114
+ ],
115
+ });
116
+ ```
117
+
70
118
  ### `runLoggerTck`
71
119
 
72
120
  Structural smoke test of the `Logger` interface: `level` is a non-empty string; every level method callable with both overload signatures; `null` and cyclic payloads don't throw; `child(bindings)` returns a Logger satisfying the same contract; `dispose` is idempotent and awaitable.
73
121
 
122
+ ### `runLoggerDifferentialTck`
123
+
124
+ A logger has no portable output to byte-compare — its format is adapter-specific by design. The meaningful differential is **robustness and structural parity**: driven through the identical call surface (every level, both overloads, `null` + cyclic payloads, child spawning), two implementations must agree on what throws and what conforms. A logger that throws on a cyclic payload the reference tolerates, or returns a non-conforming child, diverges from the reference outcome vector.
125
+
126
+ ```ts
127
+ import { runLoggerDifferentialTck } from "@rotorsoft/act-tck";
128
+ import { ConsoleLogger } from "@rotorsoft/act";
129
+ import { PinoLogger } from "../src/index.js";
130
+
131
+ runLoggerDifferentialTck({
132
+ name: "Console vs Pino",
133
+ loggers: [
134
+ { name: "ConsoleLogger", factory: () => new ConsoleLogger({ level: "trace" }) },
135
+ { name: "PinoLogger", factory: () => new PinoLogger({ level: "trace" }) },
136
+ ],
137
+ });
138
+ ```
139
+
74
140
  ### `runStabilityTck`
75
141
 
76
142
  Walks the source of every declared entry point, follows relative re-exports recursively, and snapshots the concatenated text via Vitest. Any rename, removed export, or signature change to the public surface shows up as a snapshot diff in the PR — reviewers either accept the change (re-run with `-u`) or push back. Stops at non-relative imports (other packages, `node:*`); each package owns its own snapshot.