Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
export {
ReconciliationProtocol,
type ReconciliationProtocolConfig,
} from "./reconciliation-protocol.js";
export type {
ConflictReport,
MeshSequence,
ReconciliationOutcome,
ServerWatermark,
SessionAttributor,
} from "./reconciliation-protocol.types.js";

View File

@@ -0,0 +1,77 @@
# reconciliation-protocol
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ReconciliationProtocol` runs on the Tier-0 host the moment the server reconnects. It honours the server-supplied watermark (`meshEpoch` + per-session `sessionSequenceNumbers`), computes the differential set of buffered entries the server has not yet seen, attaches a `ConflictReport` whenever the host holds the same seq the server already records, and produces an ordered list of `ReconciliationOutcome`s the host can ship upstream. After the server acknowledges the diff, `commit(acceptedSeqs)` drops the reconciled entries from the local buffer.
The protocol is deliberately a pure-data engine: it does not own the gateway, the bus, or the network round-trip. Wiring those concerns is the plugin façade's job — this layer is unit-testable end-to-end without a server in the loop.
## Features
- Strict epoch validation: rejects watermarks more than one epoch behind, and any watermark ahead of the host. `RECONCILIATION_EPOCH_INVALID` is thrown synchronously so a misconfigured server can never silently corrupt local state.
- Configurable conflict resolver: defaults to last-writer-wins keyed by `bufferedAt` vs `issuedAt`, with ties resolving in favour of the host. Callers can swap in any `(report) => "host" | "server"` strategy without subclassing.
- Session-keyed differential: the `sessionAttributor` callback maps an entry to its session id; entries that have no session attribution flow through as host-priority (the server never had a chance to record them).
- Strict mode via `requireNoConflicts(outcomes)` — throws `RECONCILIATION_CONFLICT` if any conflict report is present. Used inside integration tests where silent LWW would mask a defect.
- `commit(acceptedSeqs)` is the only mutating method; `diff` is pure so it can be re-run safely (e.g. after a server-side rejection).
## Usage: Basic Example
```ts
import {
LogBuffer,
ReconciliationProtocol,
} from "@suaveplan/network-plugin-server-bridge";
const buffer = new LogBuffer({ capacity: 10_000 });
const proto = new ReconciliationProtocol({
buffer,
sessionAttributor: (entry) => extractSession(entry),
currentMeshEpoch: () => meshEpochManager.current(),
});
const outcomes = proto.diff(serverWatermark);
await gateway.send(serializeOutcomes(outcomes));
proto.commit(new Set(outcomes.map((o) => o.entry.seq)));
```
## Usage: Advanced Example
```ts
import { ReconciliationProtocol } from "@suaveplan/network-plugin-server-bridge";
const proto = new ReconciliationProtocol({
buffer,
sessionAttributor: (entry) => routeToSession(entry),
currentMeshEpoch: () => meshEpoch,
resolveConflict: (report) => {
// Custom strategy: server always wins for token-issuance entries,
// host wins for everything else.
if (report.sessionId.startsWith("token:")) return "server";
return report.hostBufferedAt >= report.serverIssuedAt
? "host"
: "server";
},
});
const outcomes = proto.diff(watermark);
proto.requireNoConflicts(outcomes); // fail-loud test mode
const acked = await server.replay(outcomes);
proto.commit(acked);
```
## API Reference
- `new ReconciliationProtocol(config)` — `buffer`, `sessionAttributor`, `currentMeshEpoch` are required.
- `acceptWatermark(watermark): void` — validates the epoch range. Throws `RECONCILIATION_EPOCH_INVALID` outside `[hostEpoch − 1, hostEpoch]`.
- `diff(watermark): ReconciliationOutcome[]` — computes the differential. Pure; safe to call repeatedly.
- `commit(acceptedSeqs): number` — drops acknowledged entries from the buffer. Returns the count dropped.
- `requireNoConflicts(outcomes): void` — strict mode; throws `RECONCILIATION_CONFLICT` if any conflict was applied.
## Implementation Notes
- The default LWW tiebreak favours the host because the host owns local activity by definition; the server only overrides when its own clock is strictly later. This matches the proposal's "server is authority but host is the source of activity" framing.
- `commit` calls `LogBuffer.ackThrough(seq)` per entry rather than scanning the whole set, so the cost is `O(accepted × bufferTail)`. For typical reconnect sizes (≤ a few thousand entries) this is well below the wall-clock cost of the network round-trip.
- The protocol is deliberately session-keyed rather than entry-keyed — per-session sequence numbers are what the server tracks at rest, and aligning the diff with that key avoids whole-buffer hash maps on every reconnect.

View File

@@ -0,0 +1,228 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { LogBuffer } from "../buffer/log-buffer.js";
import {
NETWORK_SB_CODES,
NetworkSbError,
} from "../error-codes/error-codes.js";
import { ReconciliationProtocol } from "./reconciliation-protocol.js";
import type { ServerWatermark } from "./reconciliation-protocol.types.js";
const wm = (overrides: Partial<ServerWatermark> = {}): ServerWatermark => ({
meshEpoch: 1,
sessionSequenceNumbers: {},
issuedAt: 100,
...overrides,
});
const setupBuffer = (clock = () => 100) => {
const buf = new LogBuffer({ capacity: 16, clock });
buf.append({ seq: 1, entryHash: "h1", bytes: new Uint8Array([1]) });
buf.append({ seq: 2, entryHash: "h2", bytes: new Uint8Array([2]) });
buf.append({ seq: 3, entryHash: "h3", bytes: new Uint8Array([3]) });
return buf;
};
describe("ReconciliationProtocol", () => {
it("acceptWatermark accepts current epoch", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 5,
});
expect(() => r.acceptWatermark(wm({ meshEpoch: 5 }))).not.toThrow();
});
it("acceptWatermark accepts epoch one behind", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 5,
});
expect(() => r.acceptWatermark(wm({ meshEpoch: 4 }))).not.toThrow();
});
it("acceptWatermark rejects epoch ahead of host", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 5,
});
expect(() => r.acceptWatermark(wm({ meshEpoch: 6 }))).toThrow(
NetworkSbError
);
});
it("acceptWatermark rejects epoch too far behind", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 5,
});
expect(() => r.acceptWatermark(wm({ meshEpoch: 3 }))).toThrow(
NetworkSbError
);
});
it("ships every entry as host-priority when attributor returns null", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(wm());
expect(outcomes).toHaveLength(3);
for (const o of outcomes) expect(o.winner).toBe("host");
});
it("treats missing session watermarks as -1 (every entry ships)", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "unknown-session",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(wm({ sessionSequenceNumbers: {} }));
expect(outcomes).toHaveLength(3);
for (const o of outcomes) expect(o.winner).toBe("host");
});
it("ships only entries past the per-session watermark", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S1",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(wm({ sessionSequenceNumbers: { S1: 1 } }));
// Entry seq=1 collides with watermark; entries 2,3 are clean
expect(outcomes.length).toBeGreaterThanOrEqual(2);
expect(outcomes.map((o) => o.entry.seq)).toContain(2);
expect(outcomes.map((o) => o.entry.seq)).toContain(3);
});
it("LWW: server wins when watermark is newer", () => {
const buf = setupBuffer(() => 50); // host bufferedAt = 50
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(
wm({
issuedAt: 200,
sessionSequenceNumbers: { S: 1 },
})
);
// Entry 1 conflicts; server wins (200 > 50) — entry 1 is dropped
expect(outcomes.find((o) => o.entry.seq === 1)).toBeUndefined();
});
it("LWW: host wins when host buffered later", () => {
const buf = setupBuffer(() => 500); // host newer
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(
wm({
issuedAt: 200,
sessionSequenceNumbers: { S: 1 },
})
);
const seq1 = outcomes.find((o) => o.entry.seq === 1);
expect(seq1?.winner).toBe("host");
expect(seq1?.conflict?.resolution).toBe("lww-host");
});
it("LWW tie defaults to host", () => {
const buf = setupBuffer(() => 100); // exactly equal to issuedAt
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(
wm({ issuedAt: 100, sessionSequenceNumbers: { S: 1 } })
);
const seq1 = outcomes.find((o) => o.entry.seq === 1);
expect(seq1?.conflict?.resolution).toBe("tie-host");
});
it("custom resolver overrides default", () => {
const buf = setupBuffer(() => 500);
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
resolveConflict: () => "server",
});
const outcomes = r.diff(
wm({ issuedAt: 200, sessionSequenceNumbers: { S: 1 } })
);
// entry 1 was server-wins → dropped
expect(outcomes.find((o) => o.entry.seq === 1)).toBeUndefined();
});
it("commit drops acknowledged entries from the buffer", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 1,
});
const dropped = r.commit(new Set([1, 2]));
expect(dropped).toBe(2);
expect(buf.peekAll().map((e) => e.seq)).toEqual([3]);
});
it("requireNoConflicts throws when conflicts present", () => {
const buf = setupBuffer(() => 500);
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(
wm({ issuedAt: 200, sessionSequenceNumbers: { S: 1 } })
);
expect(() => r.requireNoConflicts(outcomes)).toThrow(NetworkSbError);
});
it("requireNoConflicts is a no-op when clean", () => {
const buf = setupBuffer();
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(wm());
expect(() => r.requireNoConflicts(outcomes)).not.toThrow();
});
it("requireNoConflicts surfaces NETWORK_SB_CODES.RECONCILIATION_CONFLICT", () => {
const buf = setupBuffer(() => 500);
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const outcomes = r.diff(
wm({ issuedAt: 200, sessionSequenceNumbers: { S: 1 } })
);
try {
r.requireNoConflicts(outcomes);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(NetworkSbError);
expect((err as NetworkSbError).code).toBe(
NETWORK_SB_CODES.RECONCILIATION_CONFLICT
);
}
});
});

View File

@@ -0,0 +1,180 @@
import type { LogBuffer } from "../buffer/log-buffer.js";
import {
NETWORK_SB_CODES,
NetworkSbError,
} from "../error-codes/error-codes.js";
import type { BufferedEntry } from "../types/buffer.types.js";
import type {
ConflictReport,
ReconciliationOutcome,
ServerWatermark,
SessionAttributor,
} from "./reconciliation-protocol.types.js";
export interface ReconciliationProtocolConfig {
readonly buffer: LogBuffer;
/**
* Maps a buffered entry to a session id. Returning `null` ships the
* entry as a host-priority diff (no per-session sequence comparison).
*/
readonly sessionAttributor: SessionAttributor;
readonly clock?: () => number;
readonly currentMeshEpoch: () => number;
/**
* Conflict resolver. Default is last-writer-wins keyed by the larger
* of `hostBufferedAt` and `serverIssuedAt`; ties resolve in favour of
* the host (the proposal explicitly states the host owns local
* activity until the server overrides it).
*/
readonly resolveConflict?: (report: ConflictReport) => "host" | "server";
}
const defaultResolve = (report: ConflictReport): "host" | "server" => {
if (report.hostBufferedAt > report.serverIssuedAt) return "host";
if (report.serverIssuedAt > report.hostBufferedAt) return "server";
return "host";
};
/**
* Reconciliation protocol — runs on the Tier-0 host when the server
* reconnects. Handshake:
*
* 1. Server sends its `ServerWatermark` (`meshEpoch`,
* `sessionSequenceNumbers`).
* 2. Host validates the epoch (must equal the host's current epoch
* OR be exactly one behind — anything further apart is a fatal
* `RECONCILIATION_EPOCH_INVALID`).
* 3. Host computes the differential — every buffered entry whose seq
* is strictly greater than the watermark for its session.
* 4. Each differential entry is run through the resolver to attach a
* conflict report when the server has a competing record.
* 5. The output array is the ordered list the host sends upstream.
*/
export class ReconciliationProtocol {
private readonly buffer: LogBuffer;
private readonly attributor: SessionAttributor;
private readonly currentMeshEpoch: () => number;
private readonly resolve: (report: ConflictReport) => "host" | "server";
public constructor(config: ReconciliationProtocolConfig) {
this.buffer = config.buffer;
this.attributor = config.sessionAttributor;
this.currentMeshEpoch = config.currentMeshEpoch;
this.resolve = config.resolveConflict ?? defaultResolve;
}
/**
* Validate the server-supplied watermark. Throws
* `NetworkSbError(RECONCILIATION_EPOCH_INVALID)` if the epoch is
* unreasonable.
*/
public acceptWatermark(watermark: ServerWatermark): void {
const ours = this.currentMeshEpoch();
const drift = ours - watermark.meshEpoch;
if (drift < 0 || drift > 1) {
throw new NetworkSbError(
NETWORK_SB_CODES.RECONCILIATION_EPOCH_INVALID,
"sb.reconciliation.epoch_invalid",
{
context: {
hostEpoch: ours,
serverEpoch: watermark.meshEpoch,
},
}
);
}
}
/**
* Compute the ordered diff to ship upstream. Does NOT mutate the
* buffer — call `commit` after the server acknowledges the diff.
*/
public diff(watermark: ServerWatermark): ReconciliationOutcome[] {
this.acceptWatermark(watermark);
const out: ReconciliationOutcome[] = [];
for (const entry of this.buffer.peekAll()) {
const sessionId = this.attributor(entry);
if (sessionId === null) {
out.push({ entry, winner: "host" });
continue;
}
const watermarkSeq =
watermark.sessionSequenceNumbers[sessionId] ?? -1;
if (entry.seq <= watermarkSeq) {
// Server already has this seq — that's a conflict only when
// we genuinely ran ahead; if the server's seq dominates we
// simply skip our copy.
const report = this.buildConflict(entry, sessionId, watermark);
const winner = this.resolve(report);
if (winner === "host") {
out.push({ entry, winner: "host", conflict: report });
} else {
}
} else {
out.push({ entry, winner: "host" });
}
}
return out;
}
/**
* Once the server acknowledges the diff, drop reconciled entries
* from the local buffer. `acceptedSeqs` is the set of seqs the
* server confirmed.
*/
public commit(acceptedSeqs: ReadonlySet<number>): number {
let dropped = 0;
for (const entry of this.buffer.peekAll()) {
if (acceptedSeqs.has(entry.seq)) {
this.buffer.ackThrough(entry.seq);
dropped++;
}
}
return dropped;
}
/**
* Strict resolver — throws `NetworkSbError(RECONCILIATION_CONFLICT)`
* when any entry produces a conflict report. Use during integration
* tests to fail loud rather than silently picking a winner.
*/
public requireNoConflicts(
outcomes: readonly ReconciliationOutcome[]
): void {
for (const o of outcomes) {
if (o.conflict !== undefined) {
throw new NetworkSbError(
NETWORK_SB_CODES.RECONCILIATION_CONFLICT,
"sb.reconciliation.conflict",
{
context: {
sessionId: o.conflict.sessionId,
seq: o.conflict.seq,
resolution: o.conflict.resolution,
},
}
);
}
}
}
private buildConflict(
entry: BufferedEntry,
sessionId: string,
watermark: ServerWatermark
): ConflictReport {
const winner =
entry.bufferedAt > watermark.issuedAt
? "lww-host"
: entry.bufferedAt < watermark.issuedAt
? "lww-server"
: "tie-host";
return {
sessionId,
seq: entry.seq,
hostBufferedAt: entry.bufferedAt,
serverIssuedAt: watermark.issuedAt,
resolution: winner,
};
}
}

View File

@@ -0,0 +1,72 @@
# reconciliation-protocol.types
## Purpose
Companion type contracts for the reconciliation protocol. Defines `MeshSequence` (the abstract `(meshEpoch, sessionSequenceNumbers)` pair), `ServerWatermark` (the `MeshSequence` plus an `issuedAt` instant), `ReconciliationOutcome` (an entry-plus-winner record), `ConflictReport` (the per-conflict diagnostic the resolver receives), and `SessionAttributor` (the `(entry) => sessionId | null` callback the protocol uses to route entries through the per-session watermark check).
These types form the on-the-wire contract for the reconnect handshake. Both server and host must agree on their shape; the conformance test pins this so a server implementation can validate against this package's types via shared schema generation.
## Features
- `ServerWatermark.sessionSequenceNumbers` is `Readonly<Record<string, number>>` — the host treats every key as opaque; the server is the only side that mints session ids.
- `ReconciliationOutcome.winner` is a 2-value union (`"host" | "server"`); future tiebreaks like `"merged"` can extend the union without breaking existing consumers.
- `ConflictReport.resolution` is a 3-value union (`"lww-server" | "lww-host" | "tie-host"`) so dashboards can break down resolution distribution at a glance.
- `SessionAttributor` returning `null` is the explicit opt-out from per-session reconciliation; the entry still ships, but always as host-priority.
## Usage: Basic Example
```ts
import type {
ServerWatermark,
SessionAttributor,
} from "@suaveplan/network-plugin-server-bridge";
const watermark: ServerWatermark = {
meshEpoch: 1,
sessionSequenceNumbers: { "session-A": 42, "session-B": 17 },
issuedAt: Date.now(),
};
const attributor: SessionAttributor = (entry) => {
const view = new DataView(entry.bytes.buffer);
return view.getUint32(0).toString(16);
};
```
## Usage: Advanced Example
```ts
import type {
ConflictReport,
ReconciliationOutcome,
} from "@suaveplan/network-plugin-server-bridge";
function summarize(outcomes: readonly ReconciliationOutcome[]): {
hostWins: number;
serverWins: number;
conflicts: ConflictReport[];
} {
let hostWins = 0;
let serverWins = 0;
const conflicts: ConflictReport[] = [];
for (const o of outcomes) {
if (o.winner === "host") hostWins++;
else serverWins++;
if (o.conflict !== undefined) conflicts.push(o.conflict);
}
return { hostWins, serverWins, conflicts };
}
```
## API Reference
- `MeshSequence` — `{ meshEpoch: number; sessionSequenceNumbers: Readonly<Record<string, number>> }`.
- `ServerWatermark` — `MeshSequence & { issuedAt: number }`.
- `ReconciliationOutcome` — `{ entry: BufferedEntry; winner: "host" | "server"; conflict?: ConflictReport }`.
- `ConflictReport` — `{ sessionId; seq; hostBufferedAt; serverIssuedAt; resolution }`.
- `SessionAttributor` — `(entry: BufferedEntry) => string | null`.
## Implementation Notes
- Every member is `readonly` to discourage accidental mutation by handlers downstream of `diff()`. The protocol itself emits fresh objects per call so handlers are free to share references.
- The 64-bit `meshEpoch` is intentionally `number` (not `bigint`) — JavaScript's `Number.MAX_SAFE_INTEGER` is `2^53 − 1`, far above the realistic upper bound for mesh-epoch counters (one increment per mesh restart, ~9 quadrillion increments before precision loss).

View File

@@ -0,0 +1,43 @@
import type { BufferedEntry } from "../types/buffer.types.js";
export interface MeshSequence {
readonly meshEpoch: number;
readonly sessionSequenceNumbers: Readonly<Record<string, number>>;
}
/**
* Watermark the server reports during the reconnect handshake. The host
* replays every buffered entry whose `seq` is strictly greater than the
* matching `sessionSequenceNumbers[sessionId]` (or every entry if the
* server has no record for that session).
*/
export interface ServerWatermark extends MeshSequence {
/** Wall-clock instant the watermark was issued (ms since epoch). */
readonly issuedAt: number;
}
/**
* Single reconciled entry. Carries the original `BufferedEntry`,
* the resolved `winner` (in case of conflict), and a `conflict?: ConflictReport`
* when the protocol had to apply a tiebreak rule.
*/
export interface ReconciliationOutcome {
readonly entry: BufferedEntry;
readonly winner: "host" | "server";
readonly conflict?: ConflictReport;
}
export interface ConflictReport {
readonly sessionId: string;
readonly seq: number;
readonly hostBufferedAt: number;
readonly serverIssuedAt: number;
readonly resolution: "lww-server" | "lww-host" | "tie-host";
}
/**
* Function the host calls to attribute a buffered entry to a session.
* Returning `null` excludes the entry from session-keyed reconciliation
* (it will still ship as a host-priority entry).
*/
export type SessionAttributor = (entry: BufferedEntry) => string | null;

View File

@@ -0,0 +1,73 @@
/**
* NET-33 — log replay and reconciliation on reconnection.
*
* Pins: epoch sync, watermark validation, conflict detection.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { LogBuffer } from "../buffer/log-buffer.js";
import { NETWORK_SB_CODES } from "../error-codes/error-codes.js";
import { ReconciliationProtocol } from "./reconciliation-protocol.js";
describe("NET-33 — reconciliation", () => {
it("validates server epoch", () => {
const buf = new LogBuffer({ capacity: 4 });
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => null,
currentMeshEpoch: () => 5,
});
try {
r.acceptWatermark({
meshEpoch: 99,
sessionSequenceNumbers: {},
issuedAt: 1,
});
throw new Error("expected throw");
} catch (err) {
expect((err as { code: string }).code).toBe(
NETWORK_SB_CODES.RECONCILIATION_EPOCH_INVALID
);
}
});
it("ships only entries past the per-session watermark", () => {
const buf = new LogBuffer({ capacity: 8 });
for (const seq of [1, 2, 3, 4]) {
buf.append({
seq,
entryHash: `h${seq}`,
bytes: new Uint8Array([seq]),
});
}
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const out = r.diff({
meshEpoch: 1,
sessionSequenceNumbers: { S: 2 },
issuedAt: 0,
});
const seqs = out.map((o) => o.entry.seq);
expect(seqs).toContain(3);
expect(seqs).toContain(4);
});
it("flags conflicts when the server holds the same seq", () => {
const buf = new LogBuffer({ capacity: 8, clock: () => 50 });
buf.append({ seq: 1, entryHash: "h1", bytes: new Uint8Array([1]) });
const r = new ReconciliationProtocol({
buffer: buf,
sessionAttributor: () => "S",
currentMeshEpoch: () => 1,
});
const out = r.diff({
meshEpoch: 1,
sessionSequenceNumbers: { S: 1 },
issuedAt: 100,
});
// server-wins (issuedAt > bufferedAt) so the entry drops
expect(out).toHaveLength(0);
});
});