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 {
|
import {
|
||||||
bindKicadCollab,
|
bindKicadCollab,
|
||||||
moduleItemsBridge,
|
moduleItemsBridge,
|
||||||
|
SexprVersionError,
|
||||||
type KicadBinding,
|
type KicadBinding,
|
||||||
type KicadItemsModule,
|
type KicadItemsModule,
|
||||||
type KicadItemsWindow,
|
type KicadItemsWindow,
|
||||||
|
|
@ -25,7 +26,7 @@ export {
|
||||||
type ProviderKind,
|
type ProviderKind,
|
||||||
type YjsProvider,
|
type YjsProvider,
|
||||||
} from "./provider";
|
} from "./provider";
|
||||||
export { bindKicadCollab, moduleItemsBridge };
|
export { bindKicadCollab, moduleItemsBridge, SexprVersionError };
|
||||||
export type { KicadBinding, KicadItemsModule, KicadItemsWindow };
|
export type { KicadBinding, KicadItemsModule, KicadItemsWindow };
|
||||||
export type { KicadItemsBridge } from "./kicad-binding";
|
export type { KicadItemsBridge } from "./kicad-binding";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,13 @@ import {
|
||||||
kicadLibSymbolsMap,
|
kicadLibSymbolsMap,
|
||||||
parseItemsWireDelta,
|
parseItemsWireDelta,
|
||||||
renderItem,
|
renderItem,
|
||||||
|
SEXPR_VERSION_CURRENT,
|
||||||
sexprToItems,
|
sexprToItems,
|
||||||
|
ydocSexprVersion,
|
||||||
yToDoc,
|
yToDoc,
|
||||||
type KicadItem,
|
type KicadItem,
|
||||||
} from "@pcbjam/shared";
|
} 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
|
* 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"/);
|
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,
|
kicadLibSymbolsMap,
|
||||||
parseItemsWireDelta,
|
parseItemsWireDelta,
|
||||||
seedDocToY,
|
seedDocToY,
|
||||||
|
SEXPR_VERSION_SUPPORTED,
|
||||||
upsertLibSymbolsToY,
|
upsertLibSymbolsToY,
|
||||||
wireItemUuids,
|
wireItemUuids,
|
||||||
wireLibSymbols,
|
wireLibSymbols,
|
||||||
Y_KDOC_META,
|
Y_KDOC_META,
|
||||||
Y_KDOC_SEED_NONCE,
|
Y_KDOC_SEED_NONCE,
|
||||||
ydocHasState,
|
ydocHasState,
|
||||||
|
ydocSexprVersion,
|
||||||
yToItemUnchecked,
|
yToItemUnchecked,
|
||||||
type ItemsWireDelta,
|
type ItemsWireDelta,
|
||||||
type KicadDoc,
|
type KicadDoc,
|
||||||
|
|
@ -72,7 +74,28 @@ export interface KicadBinding {
|
||||||
readonly items: KicadYItems;
|
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 {
|
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);
|
const items = kicadItemsMap(doc);
|
||||||
// Opaque per-instance origin tag so we can distinguish our own writes from peers'.
|
// Opaque per-instance origin tag so we can distinguish our own writes from peers'.
|
||||||
const ORIGIN = { local: true };
|
const ORIGIN = { local: true };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue