/** * Contract-identity ACCEPTANCE §1–§4 (apps/cloud/docs/contract-identity). * `catalog-fixture-a` + golden fcid live in `fixture.ts`; the Cloud-side parity * half of §1 imports the same constants. */ import { describe, expect, it } from "vitest"; import { CATALOG_FIXTURE_A_FCID, catalogFixtureA, catalogFixtureAContractPayload, } from "./fixture.js"; import { contractHash } from "./hash.js"; import { type ContractCatalogInput, type ContractComponentInput, type ContractTokenInput, contractComponentsFromFragments, diffContractDomains, projectContractPreimage, } from "./preimage.js"; import { formatStampPin } from "./stamp.js"; function fcidOf(catalog: ContractCatalogInput): string { return contractHash(projectContractPreimage(catalog)); } describe("§1 byte parity — one fixture, one hash, every runtime", () => { it("hashes catalog-fixture-a to the committed golden fcid", () => { expect(fcidOf(catalogFixtureA)).toBe(CATALOG_FIXTURE_A_FCID); }); it("is independent of adapter iteration order", () => { const reordered: ContractCatalogInput = { ...catalogFixtureA, components: [...(catalogFixtureA.components ?? [])].reverse().map((component) => ({ ...component, props: component.props ? [...component.props].reverse() : undefined, })), tokens: [...(catalogFixtureA.tokens ?? [])].reverse(), canonicalMappings: [...(catalogFixtureA.canonicalMappings ?? [])].reverse(), }; expect(fcidOf(reordered)).toBe(CATALOG_FIXTURE_A_FCID); }); it("hashes the empty catalog deterministically too", () => { expect(fcidOf({})).toMatch(/^[0-9a-f]{64}$/); expect(fcidOf({})).toBe(fcidOf({ components: [], tokens: [], policy: null })); }); it("the payload companion assembles to the fixture's components domain", () => { // Guards the Cloud parity materials: editing `catalogFixtureA` without its // payload-shaped companion (or vice versa) fails here, in core, before the // Convex test ever runs. expect( fcidOf({ ...catalogFixtureA, components: contractComponentsFromFragments(catalogFixtureAContractPayload.fragments), }) ).toBe(CATALOG_FIXTURE_A_FCID); }); }); describe("§2 enforced-only — prose doesn't move the hash, enforcement does", () => { it("(a) display-data edits leave the identity unchanged", () => { // Adapters may pass wider objects (enrichment descriptions, docs URLs…); // the projection's pick-list is the enforced-only boundary. const withProseEdits: ContractCatalogInput = { ...catalogFixtureA, components: (catalogFixtureA.components ?? []).map( (component) => ({ ...component, description: "A lovingly rewritten description", docsUrl: "https://usefragments.com/docs/button", }) as ContractComponentInput ), tokens: (catalogFixtureA.tokens ?? []).map( (token) => ({ ...token, description: "now with prose" }) as ContractTokenInput ), }; expect(fcidOf(withProseEdits)).toBe(CATALOG_FIXTURE_A_FCID); }); it("(b) confirming a mapping changes the identity — and only the canonicalMap domain", () => { const withConfirmedMapping: ContractCatalogInput = { ...catalogFixtureA, canonicalMappings: (catalogFixtureA.canonicalMappings ?? []).map((mapping) => mapping.status === "proposed" ? { ...mapping, status: "confirmed" as const } : mapping ), }; expect(fcidOf(withConfirmedMapping)).not.toBe(CATALOG_FIXTURE_A_FCID); expect( diffContractDomains( projectContractPreimage(catalogFixtureA), projectContractPreimage(withConfirmedMapping) ) ).toEqual(["canonicalMap"]); }); }); describe("§3 proposed mappings are not identity", () => { it("adding a new proposed mapping leaves the fcid unchanged", () => { const withNewProposedMapping: ContractCatalogInput = { ...catalogFixtureA, canonicalMappings: [ ...(catalogFixtureA.canonicalMappings ?? []), { component: "AppButton", canonical: "Button", status: "proposed" as const }, ], }; expect(fcidOf(withNewProposedMapping)).toBe(CATALOG_FIXTURE_A_FCID); }); }); describe("§4 domain diff names what moved", () => { it("confirming a mapping + adding a composition rule changes canonicalMap and policy", () => { const preimageBase = projectContractPreimage(catalogFixtureA); const vNext: ContractCatalogInput = { ...catalogFixtureA, canonicalMappings: (catalogFixtureA.canonicalMappings ?? []).map((mapping) => mapping.status === "proposed" ? { ...mapping, status: "confirmed" as const } : mapping ), policy: { ...catalogFixtureA.policy, compositionPatterns: [ ...(catalogFixtureA.policy?.compositionPatterns ?? []), { id: "dialog-structure", region: "Dialog", require: ["Dialog.Title"] }, ], }, }; const preimageNext = projectContractPreimage(vNext); expect(diffContractDomains(preimageBase, preimageNext).sort()).toEqual([ "canonicalMap", "policy", ]); }); it("identical preimages diff to nothing", () => { const preimage = projectContractPreimage(catalogFixtureA); expect(diffContractDomains(preimage, projectContractPreimage(catalogFixtureA))).toEqual([]); }); }); describe("formatStampPin", () => { it("renders the display convention: version + abbreviated fcid", () => { expect( formatStampPin({ contractVersion: 12, fcid: "a3f9e21b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b785", }) ).toBe("contract v12 · a3f9e21"); }); });