import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; import { internSchema } from "@commonfabric/data-model-schema"; import type { JSONSchema, JSONSchemaObj } from "../../src/builder/types.ts"; import { droppedStoredClaim } from "../../src/cfc/claim-preservation.ts"; import { mergeCfcSchemaEnvelopes } from "../../src/cfc/schema-merge.ts"; import { acquireSchemaRegistryLease, registerSchemaDocument, } from "../../src/schema-registry.ts"; const CONTRACT = { helper: "UiAction", action: "PinNote" } as const; const PIN = { type: "string", ifc: { uiContract: CONTRACT } } as const; const LABELED = { type: "string", ifc: { confidentiality: ["writer-clause"] }, } as const; describe("droppedStoredClaim()", () => { it("returns `undefined` for a merge that keeps every claim and adds a label", () => { const stored = { type: "object", properties: { pin: PIN } } as const; const candidate = { type: "object", properties: { pin: LABELED } } as const; expect( droppedStoredClaim(stored, mergeCfcSchemaEnvelopes(stored, candidate)), ).toBeUndefined(); }); it("returns the claim and path a merged schema no longer declares", () => { const stored = { type: "object", properties: { pin: PIN } } as const; const merged = { type: "object", properties: { pin: LABELED } } as const; expect(droppedStoredClaim(stored, merged)).toBe( "the merged schema drops the stored uiContract at /pin", ); }); it("returns a claim a recursive definition loses below the level where it first recurs", () => { // The merged schema keeps the claim at `/node/pin` and swaps in a // definition without it from `/node/child` down. const stored = { type: "object", properties: { node: { $ref: "#/$defs/Node" } }, $defs: { Node: { type: "object", properties: { pin: PIN, child: { $ref: "#/$defs/Node" } }, }, }, } as const; const merged = { type: "object", properties: { node: { type: "object", properties: { pin: PIN, child: { $ref: "#/$defs/Other" } }, }, }, $defs: { Other: { type: "object", properties: { pin: LABELED, child: { $ref: "#/$defs/Other" } }, }, }, } as const; expect(droppedStoredClaim(stored, merged)).toBe( "the merged schema drops the stored uiContract at /node/child/pin", ); expect(droppedStoredClaim(stored, stored)).toBeUndefined(); }); it("returns `undefined` for a merge whose candidate unrolls a stored recursive definition less deeply", () => { // A message links the message it replies to, so its definition names // itself. The stored envelope writes two levels out before it refers to // the definition; the writer's schema refers to it at once, under a // stamp of its own. Where the two references meet again, the merge // keeps the stored stamp beneath, as it does above. const stamped = (moduleIdentity: string) => ({ writePolicyAnyOf: [{ writeAuthorizedBy: { __ctWriterIdentityOf: { file: "/room.tsx", path: ["react"], moduleIdentity, }, }, uiContract: CONTRACT, }], }); const message = (replyTo: JSONSchema): JSONSchemaObj => ({ type: "object", properties: { body: { type: "string" }, reactions: { type: "array", items: { $ref: "#/$defs/Reaction" }, asCell: ["cell"], }, replyTo, }, }); const reply = (message: JSONSchemaObj): JSONSchemaObj => ({ type: "object", properties: { message: { ...message, asCell: ["cell"] } }, }); const definitions = ( moduleIdentity: string, ): Record => ({ Message: message({ $ref: "#/$defs/Reply" }), Reply: reply({ $ref: "#/$defs/Message" }), Reaction: { type: "object", properties: { emoji: { type: "string" } }, ifc: stamped(moduleIdentity), }, }); const stored: JSONSchema = { ...message(reply(message({ $ref: "#/$defs/Reply" }))), $defs: definitions("m1"), }; const candidate: JSONSchema = { ...message({ $ref: "#/$defs/Reply" }), $defs: definitions("m2"), }; expect( droppedStoredClaim(stored, mergeCfcSchemaEnvelopes(stored, candidate)), ).toBeUndefined(); }); it("returns a claim held in one arm of a stored `anyOf` the merged arms do not keep", () => { const stored = { type: "object", properties: { pin: { anyOf: [{ $ref: "#/$defs/Pin" }, { type: "null" }] }, }, $defs: { Pin: PIN }, } as const; const merged = { type: "object", properties: { pin: { anyOf: [LABELED, { type: "null" }] } }, } as const; expect(droppedStoredClaim(stored, merged)).toBe( "the merged schema drops the stored uiContract at /pin", ); }); it("returns a claim held in a `oneOf` arm beside an `anyOf` that the merged node drops", () => { const stored = { type: "object", properties: { pin: { anyOf: [{ type: "string" }, { type: "null" }], oneOf: [PIN, { type: "null" }], }, }, } as const; const merged = { type: "object", properties: { pin: { anyOf: [{ type: "string" }, { type: "null" }] }, }, } as const; expect(droppedStoredClaim(stored, merged)).toBe( "the merged schema drops the stored uiContract at /pin", ); expect(droppedStoredClaim(stored, stored)).toBeUndefined(); }); it("returns a claim behind a `cid:` reference that the merged position replaces", () => { const release = acquireSchemaRegistryLease(); try { const document = { $defs: { Pin: PIN } } as const; const hash = internSchema(document, true).taggedHashString; registerSchemaDocument(hash, document); const stored = { type: "object", properties: { pin: { $ref: `cid:${hash}#/$defs/Pin` } }, } as JSONSchema; expect( droppedStoredClaim(stored, { type: "object", properties: { pin: LABELED }, }), ).toBe("the merged schema drops the stored uiContract at /pin"); expect( droppedStoredClaim( stored, mergeCfcSchemaEnvelopes(stored, { type: "object", properties: { pin: LABELED }, }), ), ).toBeUndefined(); } finally { release(); } }); it("returns a dropped claim for a stored reference that does not resolve", () => { expect( droppedStoredClaim({ type: "object", properties: { pin: { $ref: "#/$defs/Missing" } }, }, { type: "object", properties: { pin: PIN } }), ).toBe("a stored schema reference does not resolve at /pin"); }); describe("writer claims", () => { const binding = (extra: Record = {}) => ({ __ctWriterIdentityOf: { file: "/main.tsx", path: ["pin"], ...extra }, }); const at = (claim: unknown): JSONSchema => ({ type: "object", properties: { pin: { type: "string", ifc: { writeAuthorizedBy: claim } }, }, } as JSONSchema); it("returns `undefined` for a binding the merge stamps for the first time or respells", () => { expect( droppedStoredClaim( at(binding()), at(binding({ file: "main.tsx", moduleIdentity: "m1" })), ), ).toBeUndefined(); }); it("returns the claim for a stamp the merge replaces", () => { expect( droppedStoredClaim( at(binding({ moduleIdentity: "m1" })), at(binding({ moduleIdentity: "m2" })), ), ).toBe("the merged schema drops the stored writeAuthorizedBy at /pin"); }); it("returns `undefined` for a builtin list that narrows and the claim for one that grows", () => { expect(droppedStoredClaim(at(["a", "b"]), at(["a"]))).toBeUndefined(); expect(droppedStoredClaim(at(["a"]), at(["a", "c"]))).toBe( "the merged schema drops the stored writeAuthorizedBy at /pin", ); }); }); it("returns a claim an `allOf` member or a tuple slot makes that the merged schema drops", () => { expect( droppedStoredClaim( { type: "object", properties: { pin: { allOf: [PIN] } } }, { type: "object", properties: { pin: LABELED } }, ), ).toBe("the merged schema drops the stored uiContract at /pin"); expect( droppedStoredClaim( { type: "array", prefixItems: [PIN] }, { type: "array", prefixItems: [LABELED] }, ), ).toBe("the merged schema drops the stored uiContract at /0"); expect( droppedStoredClaim( { type: "array", prefixItems: [PIN] }, { type: "array", prefixItems: [PIN] }, ), ).toBeUndefined(); }); it("keeps apart two paths whose segments would join to the same string", () => { // `["a", "b"]` is foreign, `["a\u0000b"]` is not, and both hold the same // schema object, so a memo keyed on the joined path would reuse the // foreign verdict for the other one. const claimed = { type: "object", properties: { pin: PIN } } as const; const plain = { type: "object", properties: { pin: LABELED } } as const; const shape = (node: unknown) => ({ type: "object", properties: { a: { type: "object", properties: { b: node } }, "a\u0000b": node, }, }) as JSONSchema; const foreign = { holdsForeign: (path: readonly string[]) => path.length === 2 && path[0] === "a" && path[1] === "b", variesBelow: () => true, }; expect(droppedStoredClaim(shape(claimed), shape(plain), foreign)).toBe( "the merged schema drops the stored uiContract at /a\u0000b/pin", ); }); it("returns a claim on every property that the merged schema drops", () => { expect( droppedStoredClaim( { type: "object", additionalProperties: PIN }, { type: "object", additionalProperties: LABELED }, ), ).toBe("the merged schema drops the stored uiContract at /*"); }); it("returns a writer claim of another form the merged schema changes", () => { const at = (writeAuthorizedBy: unknown): JSONSchema => ({ type: "object", properties: { pin: { type: "string", ifc: { writeAuthorizedBy } } }, } as JSONSchema); expect(droppedStoredClaim(at({ other: 1 }), at({ other: 1 }))) .toBeUndefined(); expect(droppedStoredClaim(at({ other: 1 }), at({ other: 2 }))).toBe( "the merged schema drops the stored writeAuthorizedBy at /pin", ); }); it("returns a dropped claim for a stored reference that only names itself", () => { expect( droppedStoredClaim( { type: "object", properties: { pin: { $ref: "#/$defs/Loop" } }, $defs: { Loop: { $ref: "#/$defs/Loop" } }, } as JSONSchema, { type: "object", properties: { pin: LABELED } }, ), ).toBe("a stored schema reference does not resolve at /pin"); }); it("returns the floor a merge lowers and `undefined` for one it raises", () => { const floor = (atoms: string[]): JSONSchema => ({ type: "object", properties: { out: { type: "string", ifc: { requiredIntegrity: atoms } }, }, } as JSONSchema); expect(droppedStoredClaim(floor(["x"]), floor(["x", "y"]))) .toBeUndefined(); expect(droppedStoredClaim(floor(["x", "y"]), floor(["x"]))).toBe( "the merged schema drops the stored requiredIntegrity at /out", ); }); });