fix: preserve reconciliation authorities

This commit is contained in:
DongHyeonka
2026-08-02 03:15:22 +09:00
parent d9afccdd60
commit 184bd98d92
9 changed files with 287 additions and 22 deletions
@@ -25,6 +25,82 @@ function scope() {
}
describe("revision-safe optimistic layer runtime", () => {
it.each(["ROLLBACK", "RECONCILE"] as const)(
"rejects a composite-invalid candidate without losing prior %s authority",
(authority) => {
const client = new QueryClient();
const key = ["query", "composite-candidate", authority];
client.setQueryData(key, ["base"]);
const selectedScope = scope();
const runtime = createOptimisticLayerRuntime(client);
const first = runtime.begin(
key,
"first",
(previous, input) => [...(previous as string[]), input],
selectedScope.snapshot,
);
if (authority === "RECONCILE") first?.markUncertain();
const second = runtime.begin(
key,
"second",
(previous, input) => {
const values = previous as string[];
if (values.includes("first")) {
throw new Error("candidate composes only over the base");
}
return [...values, input];
},
selectedScope.snapshot,
);
expect(second).toBeNull();
expect(client.getQueryData(key)).toEqual(["base", "first"]);
if (authority === "ROLLBACK") {
first?.rollback();
} else {
first?.reconcile("NOT_APPLIED");
}
expect(client.getQueryData(key)).toEqual(["base"]);
},
);
it("writes a prevalidated candidate without invoking its updater twice", () => {
const client = new QueryClient();
const key = ["query", "single-candidate-invocation"];
client.setQueryData(key, ["base"]);
const selectedScope = scope();
const runtime = createOptimisticLayerRuntime(client);
const first = runtime.begin(
key,
"first",
(previous, input) => [...(previous as string[]), input],
selectedScope.snapshot,
);
let candidateCalls = 0;
const second = runtime.begin(
key,
"second",
(previous, input) => {
candidateCalls += 1;
if (candidateCalls > 1) {
throw new Error("candidate updater must not be replayed on admission");
}
return [...(previous as string[]), input];
},
selectedScope.snapshot,
);
expect(second).not.toBeNull();
expect(candidateCalls).toBe(1);
expect(client.getQueryData(key)).toEqual(["base", "first", "second"]);
second?.rollback();
expect(client.getQueryData(key)).toEqual(["base", "first"]);
first?.rollback();
expect(client.getQueryData(key)).toEqual(["base"]);
});
it("removes only the failed layer when commands settle out of order", () => {
const client = new QueryClient();
const key = ["query", "resources"];