fix: 严格校验隔离重放计数

This commit is contained in:
root
2026-07-10 15:52:39 +02:00
parent 2391badb21
commit 749d1656a4
2 changed files with 36 additions and 9 deletions
@@ -12,18 +12,42 @@ test("local replay classification reports transport_failed below server delivere
assert.deepEqual(result.counts, { delivered: 3, clientReceived: 2, decoded: 2, applied: 2 });
});
test("local replay classification reports transport_failed above server delivered", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 4, decoded: 4, applied: 4 });
assert.equal(result.ok, false);
assert.equal(result.code, "transport_failed");
});
test("local replay classification reports contract_invalid when server delivered is missing", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(), { clientReceived: 0, decoded: 0, applied: 0 });
assert.equal(result.ok, false);
assert.equal(result.code, "contract_invalid");
});
test("local replay classification reports decoder_rejected below client received", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 2, applied: 2 });
assert.equal(result.ok, false);
assert.equal(result.code, "decoder_rejected");
});
test("local replay classification reports decoder_rejected above client received", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 4, applied: 4 });
assert.equal(result.ok, false);
assert.equal(result.code, "decoder_rejected");
});
test("local replay classification reports reducer_rejected below decoded", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 2 });
assert.equal(result.ok, false);
assert.equal(result.code, "reducer_rejected");
});
test("local replay classification reports reducer_rejected above decoded", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 4 });
assert.equal(result.ok, false);
assert.equal(result.code, "reducer_rejected");
});
test("local replay classification preserves terminal_complete when every client stage agrees", () => {
const result = classifyWorkbenchKafkaDebugLocalResult(terminalResult(3), { clientReceived: 3, decoded: 3, applied: 3 });
assert.equal(result.ok, true);
@@ -32,13 +56,13 @@ test("local replay classification preserves terminal_complete when every client
assert.equal(result.localOverlay, true);
});
function terminalResult(delivered: number): WorkbenchKafkaDebugReplayResult {
function terminalResult(delivered?: number): WorkbenchKafkaDebugReplayResult {
return {
contractVersion: "workbench-kafka-debug-replay-v2",
ok: true,
phase: "terminal",
code: "terminal_complete",
terminalObserved: true,
counts: { delivered, clientReceived: null, decoded: null, applied: null }
counts: { ...(delivered === undefined ? {} : { delivered }), clientReceived: null, decoded: null, applied: null }
};
}
@@ -8,7 +8,7 @@ export interface WorkbenchKafkaDebugClientCounts {
applied: number;
}
export type WorkbenchKafkaDebugClientFailureCode = "transport_failed" | "decoder_rejected" | "reducer_rejected";
export type WorkbenchKafkaDebugClientFailureCode = "contract_invalid" | "transport_failed" | "decoder_rejected" | "reducer_rejected";
export function classifyWorkbenchKafkaDebugLocalResult(
serverResult: WorkbenchKafkaDebugReplayResult,
@@ -19,14 +19,17 @@ export function classifyWorkbenchKafkaDebugLocalResult(
if (serverResult.code !== "terminal_complete") return overlaid;
const delivered = nonNegativeInteger(serverResult.counts?.delivered);
if (delivered !== null && clientCounts.clientReceived < delivered) {
return clientFailure(overlaid, "transport_failed", `浏览器收到 ${clientCounts.clientReceived} 条,少于服务端已投递的 ${delivered} 条。`);
if (delivered === null) {
return clientFailure(overlaid, "contract_invalid", "服务端 terminal_complete 缺少有效的 delivered 计数。");
}
if (clientCounts.decoded < clientCounts.clientReceived) {
return clientFailure(overlaid, "decoder_rejected", `浏览器收到 ${clientCounts.clientReceived} 条,但只解码 ${clientCounts.decoded} 条。`);
if (clientCounts.clientReceived !== delivered) {
return clientFailure(overlaid, "transport_failed", `浏览器收到 ${clientCounts.clientReceived} 条,与服务端已投递的 ${delivered}不一致`);
}
if (clientCounts.applied < clientCounts.decoded) {
return clientFailure(overlaid, "reducer_rejected", `浏览器解码 ${clientCounts.decoded} 条,但生产 reducer 只应用 ${clientCounts.applied} 条。`);
if (clientCounts.decoded !== clientCounts.clientReceived) {
return clientFailure(overlaid, "decoder_rejected", `浏览器收到 ${clientCounts.clientReceived} 条,但解码计数为 ${clientCounts.decoded} 条。`);
}
if (clientCounts.applied !== clientCounts.decoded) {
return clientFailure(overlaid, "reducer_rejected", `浏览器解码 ${clientCounts.decoded} 条,但生产 reducer 应用计数为 ${clientCounts.applied} 条。`);
}
return { ...overlaid, ok: true, phase: "terminal", code: "terminal_complete" };
}