import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { Identity } from "@commonfabric/identity"; import { StorageManager } from "../src/storage/cache.deno.ts"; import type { RuntimeProgram } from "../src/harness/types.ts"; import { Runtime } from "../src/runtime.ts"; import { runtimePresets } from "../src/runtime-presets.ts"; import { setCfcImplementationIdentity } from "../src/cfc/trust-authority.ts"; import type { Cell } from "../src/cell.ts"; import type { JSONSchema } from "../src/builder/types.ts"; // A document stores its policy envelope from the schema that defines it: a // pattern's argument from the pattern's input schema, a created cell from the // cell's. A writer policy that schema reached where no syntax names the // writer would leave the document with no stored write restriction at all, // writable by any handler and by any other code holding WRITE on the space. // Each such shape must either keep its writer, so that only the named // handler writes the field, or fail compilation. const signer = await Identity.fromPassphrase( "cfc-writer-claim-document-shapes", ); const space = signer.did(); /** * A program whose default pattern returns `value`, a field protected by * `writerA`'s policy, with `setA` and `setB` writing it through `writerA` and * through `writerB`, a handler of the same shape that the policy does not * name. `head` declares the pattern and its input; `body` binds `value`; * `returned` is what the pattern returns, those three by default. */ const program = ( head: string, body: string, returned = "{ value, setA: writerA({ value }), setB: writerB({ value }) }", ): RuntimeProgram => ({ main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { type Cfc, computed, type CurrentPrincipal, handler, pattern, type RepresentsCurrentUser, type Stream, Writable, type WriteAuthorizedBy } from "commonfabric"; type Policy = WriteAuthorizedBy; type Owned = RepresentsCurrentUser, { ownerPrincipal: CurrentPrincipal }>>; export const writerA = handler<{ v: string }, { value: Writable }>( ({ v }, { value }) => { value.set(v); }, ); export const writerB = handler<{ v: string }, { value: Writable }>( ({ v }, { value }) => { value.set(v); }, ); ${head} ${body} return ${returned}; }); `, }], }); /** * What became of the policy: compilation failed on the writer it could not * read, only the named writer writes the field, or other writers do too. */ type Outcome = "refused" | "enforced" | "open"; describe("a writer policy a document's schema reaches", () => { let storageManager: ReturnType; let runtime: Runtime; beforeEach(() => { storageManager = StorageManager.emulate({ as: signer }); runtime = new Runtime(runtimePresets.patternTest({ apiUrl: new URL(import.meta.url), storageManager, experimental: {}, })); }); afterEach(async () => { await runtime?.dispose(); await storageManager?.close(); }); const outcomeOf = async ( source: RuntimeProgram, name: string, ): Promise => { const tx = runtime.edit(); let pattern; try { pattern = await runtime.patternManager.compilePattern(source, { space, tx, }); } catch (error) { tx.abort(); expect(String(error)).toContain("could not be read"); return "refused"; } const cell = runtime.getCell<{ value: string }>(space, name, undefined, tx); const running = runtime.run(tx, pattern, {}, cell); runtime.prepareTxForCommit(tx); expect((await tx.commit().settled).error).toBeUndefined(); await running.pull(); await runtime.idle(); const value = running.key("value") as Cell; const send = async (stream: "setA" | "setB", v: string) => { (running.key(stream) as unknown as { send: (e: unknown) => void }).send({ v, }); await runtime.idle(); await running.pull(); await runtime.idle(); }; // Other code holding WRITE on the space, writing the document the field // resolves to by its bare link, through a schema that declares nothing. const { schema: _schema, ...bare } = value.resolveAsCell() .getAsNormalizedFullLink(); const member = await runtime.editWithRetry((memberTx) => { setCfcImplementationIdentity(memberTx, { kind: "verified", moduleIdentity: "sha256:member-code", symbol: "member", bindingPath: ["member"], }); runtime.getCellFromLink(bare, { type: "string" } as JSONSchema, memberTx) .set("from member code"); }); await send("setB", "from writerB"); const afterOthers = value.get(); await send("setA", "from writerA"); expect(value.get()).toBe("from writerA"); return member.error === undefined || afterOthers === "from member code" || afterOthers === "from writerB" ? "open" : "enforced"; }; /** * Sets up `source` in a result cell named `name`, and returns the error, if * any, of other code holding WRITE on the space writing the field at `path` * of the result by its bare link. */ const memberWriteError = async ( source: RuntimeProgram, name: string, path: readonly string[], ): Promise => { const tx = runtime.edit(); const pattern = await runtime.patternManager.compilePattern(source, { space, tx, }); const cell = runtime.getCell(space, name, undefined, tx); const running = runtime.run(tx, pattern, {}, cell); runtime.prepareTxForCommit(tx); expect((await tx.commit().settled).error).toBeUndefined(); await running.pull(); await runtime.idle(); const field = path.reduce( (target: Cell, key) => target.key(key) as Cell, running as Cell, ); const { schema: _schema, ...bare } = field.resolveAsCell() .getAsNormalizedFullLink(); const member = await runtime.editWithRetry((memberTx) => { setCfcImplementationIdentity(memberTx, { kind: "verified", moduleIdentity: "sha256:member-code", symbol: "member", bindingPath: ["member"], }); runtime.getCellFromLink(bare, { type: "string" } as JSONSchema, memberTx) .set("from member code"); }); return member.error?.message; }; it("enforces a policy written on the input's own field", async () => { expect( await outcomeOf( program( "export default pattern<{ value: Policy }>((input) => {", "const value = input.value;", ), "direct-field", ), ).toBe("enforced"); }); it("enforces a policy written as a created cell's type argument", async () => { expect( await outcomeOf( program( "export default pattern<{ initial: string }>((input) => {", 'const value = new Writable("seed").for("value");', ), "explicit-cell", ), ).toBe("enforced"); }); const shapes: Record = { "a generic alias": program( `type Box = { value: T }; export default pattern>>((input) => {`, "const value = input.value;", ), "an interface's generic base": program( `interface Base { value: T } interface In extends Base> {} export default pattern((input) => {`, "const value = input.value;", ), "`Record`": program( "export default pattern<{ byId: Record> }>((input) => {", 'const value = input.byId["k"];', ), "an index signature": program( "export default pattern<{ byId: { [key: string]: WriteAuthorizedBy } }>((input) => {", 'const value = input.byId["k"];', ), "a tuple": program( "export default pattern<{ pair: [WriteAuthorizedBy] }>((input) => {", "const value = input.pair[0];", ), "an owner policy in a generic alias": program( `type Box = { value: T }; export default pattern>>((input) => {`, "const value = input.value;", ), "a created cell's value": program( "export default pattern<{ initial: string }>((input) => {", `const seed = "seed" as WriteAuthorizedBy; const value = new Writable(seed).for("value");`, ), }; for (const [reach, source] of Object.entries(shapes)) { it(`enforces or refuses a policy reached through ${reach}`, async () => { expect(await outcomeOf(source, reach)).not.toBe("open"); }); } it("refuses fresh data an inferred result holds under a writer policy read from its type", async () => { // The result document holds that data itself, so it would store no write // restriction for it. const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( program( "export default pattern<{ initial: string }>((input) => {", 'const value = "seed" as WriteAuthorizedBy;', "{ value }", ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); describe("fresh data an inferred result returns under an owner policy", () => { // The result document holds that data itself, whatever shape the object // returning it takes, so it stores the whole policy or compilation fails. for ( const [shape, body, returned] of [ [ "a spread object", "", '{ ...{ value: "seed" as Owned } }', ], [ "a spread constant", 'const fresh = { value: "seed" as Owned };', "{ ...fresh }", ], [ "a static computed key", "", '{ box: { ["value"]: "seed" as Owned } }', ], [ "an array spread", "", '{ list: [...["seed" as Owned]] }', ], [ "twenty nested objects", "", `{ box: ${ "{ nested: ".repeat(20) }"seed" as Owned${" }".repeat(20)} }`, ], ] as const ) { it(`refuses it through ${shape}`, async () => { const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( program( "export default pattern<{ initial: string }>((input) => {", body, returned, ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); } for ( const returned of ["{ viewed, fresh }", "{ fresh, viewed }"] as const ) { it(`refuses it returned as ${returned}, beside a view of the same named type`, async () => { const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( program( `interface Erased { value: [T][0] } interface Item { box: Erased> } export default pattern<{ initial: string }>((input) => {`, `const viewed = computed(() => ({ box: { value: "seed" } }) as Item); const fresh = { box: { value: "seed" } } as Item;`, returned, ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); } for ( const [where, body, returned] of [ [ "in the result a map callback returns", "", '{ list: items.map((item) => ({ item, value: "seed" as Owned })) }', ], [ "in the result a map callback returns, where a computed reads the list", 'const list = items.map((item) => ({ item, value: "seed" as Owned }));', "{ first: computed(() => list[0]) }", ], ] as const ) { it(`refuses it ${where}`, async () => { // The element pattern's result is a document of its own. const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( program( "export default pattern<{ items: string[] }>(({ items }) => {", body, returned, ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); } it("refuses it beside a view of a type named as its definition would be", async () => { const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( program( `interface Erased { value: [T][0] } interface Item { box: Erased> } interface Item_document { box: Erased> } export default pattern<{ initial: string }>((input) => {`, `const viewed = computed(() => ({ box: { value: "seed" } }) as Item_document); const fresh = { box: { value: "seed" } } as Item;`, "{ viewed, fresh }", ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); it("stores the whole policy through a constant whose declared type is an index signature, which refuses other writers", async () => { expect( await memberWriteError( program( "export default pattern<{ initial: string }>((input) => {", 'const fresh: { [key: string]: Owned } = { value: "seed" };', "{ box: fresh }", ), "fresh-index-signature", ["box", "value"], ), ).toContain("writeAuthorizedBy failed at /box/value"); }); }); for ( const [how, declaration, reference] of [ ["a constant", "const schema = toSchema();", "schema"], [ "a property of a constant bound to an object literal", "const generated = { input: toSchema() };\nconst schemas = generated;", "schemas.input", ], [ "a nested property", "const schemas = { outer: { input: toSchema() } };", "schemas.outer.input", ], ] as const ) { it(`refuses a writer an input schema passed through ${how} cannot read`, async () => { // The `toSchema` call the reference leads to defines the argument // document, as it would written in place. const source: RuntimeProgram = { main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { handler, pattern, toSchema, Writable, type WriteAuthorizedBy } from "commonfabric"; export const writerA = handler<{ v: string }, { value: Writable }>( ({ v }, { value }) => { value.set(v); }, ); type Box = { value: [W][0] }; type In = Box>; ${declaration} export default pattern((input: In) => ({ value: input.value }), ${reference}); `, }], }; const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern(source, { space, tx }), ).rejects.toThrow( "The writer binding of `WriteAuthorizedBy` could not be read", ); tx.abort(); }); } for ( const [how, declaration, argument] of [ [ "reassigns", "function make(schema: Schema) {\n schema = toSchema();\n return schema;\n}", '{ type: "object" }', ], [ "writes into", "function make(schemas: { input?: Schema }) {\n schemas.input = toSchema();\n return schemas.input;\n}", "{}", ], ] as const ) { it(`refuses a created cell's schema a function returns from a parameter it ${how}`, async () => { // The parameter no longer holds what the call passed it, so the // schema cannot be read back to the `toSchema` call that makes it. const source: RuntimeProgram = { main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { handler, pattern, toSchema, Writable, type WriteAuthorizedBy } from "commonfabric"; export const writerA = handler<{ v: string }, { value: Writable }>( ({ v }, { value }) => { value.set(v); }, ); type Schema = ReturnType; interface Erased { value: [W][0] } type In = { byId: Erased> }; ${declaration} export default pattern<{}>(() => { const x = new Writable({ byId: { value: "seed" } }, make(${argument})).for("x"); return { x }; }); `, }], }; const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern(source, { space, tx }), ).rejects.toThrow("could not be read back"); tx.abort(); }); } describe("fresh data a callback's return annotation types", () => { // No document the result links to stores the policy: the result document // holds the data itself, and stores the policy the annotation declares. const annotated = (resultType: string, returned: string) => program( `type Box = { value: T }; type Erased = { value: [W][0] }; export default pattern<{ initial: string }>((input): ${resultType} => {`, "", returned, ); it("stores the whole policy declared through a generic alias, which refuses the pattern's own write", async () => { const tx = runtime.edit(); const pattern = await runtime.patternManager.compilePattern( annotated("Box>", '{ value: "seed" }'), { space, tx }, ); const cell = runtime.getCell<{ value: string }>( space, "annotated-generic-alias", undefined, tx, ); runtime.run(tx, pattern, {}, cell); runtime.prepareTxForCommit(tx); expect((await tx.commit().settled).error?.message).toContain( "writeAuthorizedBy requires a trusted verified binding identity at /value", ); }); it("stores the whole policy declared through an index signature, which refuses other writers", async () => { expect( await memberWriteError( annotated( "{ byId: { [key: string]: Owned } }", '{ byId: { k: "seed" } }', ), "annotated-index-signature", ["byId", "k"], ), ).toContain("writeAuthorizedBy failed at /byId/k"); }); it("refuses a policy declared through an indexed access", async () => { const tx = runtime.edit(); await expect( runtime.patternManager.compilePattern( annotated( "{ byId: Erased> }", '{ byId: { value: "seed" } }', ), { space, tx }, ), ).rejects.toThrow("could not be read"); tx.abort(); }); }); describe("read in part by a view", () => { // A result inferred from the pattern's callback, and a computed's result, // read an owner policy from its type, where its writer cannot be read. // They leave its owner and its claim naming the current principal out // with the writer, since either alone refuses every write against it. it("sets up an inferred result returning an owner-protected input, and enforces the policy through it", async () => { expect( await outcomeOf( program( "export default pattern<{ value: Owned }>((input) => {", "const value = input.value;", ), "owner-inferred-result", ), ).toBe("enforced"); }); it("writes the result of a computed that copies an owner-protected value", async () => { const tx = runtime.edit(); const pattern = await runtime.patternManager.compilePattern( program( "export default pattern<{ value: Owned }, { copy: string; setA: Stream<{ v: string }> }>((input) => {", "const value = input.value;", "{ copy: computed(() => value), setA: writerA({ value }) }", ), { space, tx }, ); const cell = runtime.getCell<{ copy: string }>( space, "owner-computed-copy", undefined, tx, ); const running = runtime.run(tx, pattern, {}, cell); runtime.prepareTxForCommit(tx); expect((await tx.commit().settled).error).toBeUndefined(); await running.pull(); await runtime.idle(); (running.key("setA") as unknown as { send: (e: unknown) => void }).send({ v: "from writerA", }); await runtime.idle(); await running.pull(); await runtime.idle(); expect(running.key("copy").get()).toBe("from writerA"); }); }); });