ysync 0009 phases 1-3: bump pcbjam-shared (y-sexpr v2 codec); SexprVersionError skew guard in kicad-binding
Refuse to bind docs written by an unsupported s-expr encoding (update required); surfaced via WasmTool's fatal status path. Guard unit tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0142yAmMGZ3Ejrq3wwVr2Yq2
This commit is contained in:
parent
720cff54ba
commit
33e59fde00
4 changed files with 47 additions and 3 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 1c4103c583b7326245a288a5ff63efe18dafcbf1
|
||||
Subproject commit ce7e1e76464ee11bf3ef874016face590816e424
|
||||
|
|
@ -11,6 +11,7 @@ import type { CollabBridge } from "./types";
|
|||
import {
|
||||
bindKicadCollab,
|
||||
moduleItemsBridge,
|
||||
SexprVersionError,
|
||||
type KicadBinding,
|
||||
type KicadItemsModule,
|
||||
type KicadItemsWindow,
|
||||
|
|
@ -25,7 +26,7 @@ export {
|
|||
type ProviderKind,
|
||||
type YjsProvider,
|
||||
} from "./provider";
|
||||
export { bindKicadCollab, moduleItemsBridge };
|
||||
export { bindKicadCollab, moduleItemsBridge, SexprVersionError };
|
||||
export type { KicadBinding, KicadItemsModule, KicadItemsWindow };
|
||||
export type { KicadItemsBridge } from "./kicad-binding";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ import {
|
|||
kicadLibSymbolsMap,
|
||||
parseItemsWireDelta,
|
||||
renderItem,
|
||||
SEXPR_VERSION_CURRENT,
|
||||
sexprToItems,
|
||||
ydocSexprVersion,
|
||||
yToDoc,
|
||||
type KicadItem,
|
||||
} from "@pcbjam/shared";
|
||||
import { bindKicadCollab, type KicadItemsBridge } from "./kicad-binding";
|
||||
import { bindKicadCollab, SexprVersionError, type KicadItemsBridge } from "./kicad-binding";
|
||||
|
||||
/**
|
||||
* A fake editor implementing the v2 items bridge over an in-memory flattened
|
||||
|
|
@ -342,3 +344,21 @@ describe("lib_symbols flow through the binding (miss 08A)", () => {
|
|||
expect(symWire!.sexpr).toMatch(/^\(lib_symbols \(symbol "Device:R"/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sexprVersion skew guard (ysync 0009 §5)", () => {
|
||||
it("binds a fresh (empty) room and a current-version doc", () => {
|
||||
const { a, b } = pair();
|
||||
const edA = new FakeEditor();
|
||||
seedEditor(edA, FP);
|
||||
bindKicadCollab(a, edA).seed(); // empty room: reads as v1, stamped CURRENT on write
|
||||
expect(ydocSexprVersion(a)).toBe(SEXPR_VERSION_CURRENT);
|
||||
expect(() => bindKicadCollab(b, new FakeEditor())).not.toThrow(); // peer joins the v2 doc
|
||||
});
|
||||
|
||||
it("refuses to bind a doc written by a newer encoding (update required)", () => {
|
||||
const doc = new Y.Doc();
|
||||
doc.getMap("kdoc_meta").set("sexprVersion", SEXPR_VERSION_CURRENT + 1);
|
||||
expect(() => bindKicadCollab(doc, new FakeEditor())).toThrow(SexprVersionError);
|
||||
expect(() => bindKicadCollab(doc, new FakeEditor())).toThrow(/update required/);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,12 +10,14 @@ import {
|
|||
kicadLibSymbolsMap,
|
||||
parseItemsWireDelta,
|
||||
seedDocToY,
|
||||
SEXPR_VERSION_SUPPORTED,
|
||||
upsertLibSymbolsToY,
|
||||
wireItemUuids,
|
||||
wireLibSymbols,
|
||||
Y_KDOC_META,
|
||||
Y_KDOC_SEED_NONCE,
|
||||
ydocHasState,
|
||||
ydocSexprVersion,
|
||||
yToItemUnchecked,
|
||||
type ItemsWireDelta,
|
||||
type KicadDoc,
|
||||
|
|
@ -72,7 +74,28 @@ export interface KicadBinding {
|
|||
readonly items: KicadYItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* The doc uses an s-expr encoding this build cannot write (ysync 0009 §5's
|
||||
* client skew guard). Binding anyway would mix versions in one doc — a v1
|
||||
* writer against a v2 doc corrupts the granularity contract — so the bind is
|
||||
* REFUSED; the app surfaces this as "update required".
|
||||
*/
|
||||
export class SexprVersionError extends Error {
|
||||
constructor(readonly version: number) {
|
||||
super(
|
||||
`update required: document uses s-expr encoding v${version}; ` +
|
||||
`this build supports v${SEXPR_VERSION_SUPPORTED.join(", v")}`,
|
||||
);
|
||||
this.name = "SexprVersionError";
|
||||
}
|
||||
}
|
||||
|
||||
export function bindKicadCollab(doc: Y.Doc, bridge: KicadItemsBridge): KicadBinding {
|
||||
// Version skew guard — callers bind AFTER the provider's initial sync, so the
|
||||
// doc's version is authoritative here (an empty room reads as v1 and is
|
||||
// stamped CURRENT by the first write).
|
||||
const version = ydocSexprVersion(doc);
|
||||
if (!SEXPR_VERSION_SUPPORTED.includes(version)) throw new SexprVersionError(version);
|
||||
const items = kicadItemsMap(doc);
|
||||
// Opaque per-instance origin tag so we can distinguish our own writes from peers'.
|
||||
const ORIGIN = { local: true };
|
||||
|
|
|
|||
Loading…
Reference in a new issue