import { afterEach, describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { Identity } from "@commonfabric/identity"; import { StorageManager } from "@commonfabric/runner/storage/cache.deno"; import { Runtime } from "../src/runtime.ts"; import type { Cell } from "../src/cell.ts"; import type { ImplementationIdentity } from "../src/cfc/types.ts"; import type { JSONSchema } from "../src/builder/types.ts"; import type { MemorySpace } from "../src/storage/interface.ts"; import { setCfcImplementationIdentity } from "../src/storage/extended-storage-transaction.ts"; const ADMIT_MODULE = "nested-link-admit-module"; const ADMIT_FILE = "/patterns/admission.tsx"; const signer = await Identity.fromPassphrase( "cfc-nested-link-policy-applicability", ); const homeSpace = (await Identity.fromPassphrase( "cfc-nested-link-policy-applicability home", )).did() as MemorySpace; /** The one module the field's claim names. */ const asAdmitter: ImplementationIdentity = { kind: "verified", moduleIdentity: ADMIT_MODULE, sourceFile: ADMIT_FILE, bindingPath: ["admitPanel"], }; /** Another verified module, which the claim does not name. */ const asForger: ImplementationIdentity = { kind: "verified", moduleIdentity: "nested-link-forger-module", sourceFile: "/patterns/forger.tsx", bindingPath: ["admitPanel"], }; /** * A panel whose `addedByProfile` holds a handle to a profile, which only the * admitting module may write: the shape Loom's `PanelAdderProfile` declares, * without the principal claims. */ const panelSchema: JSONSchema = { type: "object", properties: { addedByProfile: { type: "object", properties: { name: { type: "string" }, avatar: { type: "string" } }, asCell: ["cell"], ifc: { writeAuthorizedBy: { __ctWriterIdentityOf: { moduleIdentity: ADMIT_MODULE, file: ADMIT_FILE, path: ["admitPanel"], }, }, }, }, }, }; /** The same panel as a module that declares no claim on it describes it. */ const claimlessPanelSchema: JSONSchema = { type: "object", properties: { addedByProfile: { type: "object", properties: { name: { type: "string" }, avatar: { type: "string" } }, asCell: ["cell"], }, }, }; /** A profile document labeled with integrity only, as a Fabric profile is. */ const profileSchema: JSONSchema = { type: "object", properties: { name: { type: "string" }, avatar: { type: "string" } }, ifc: { addIntegrity: ["nested-link-test-profile"] }, }; describe("cfc-nested-link-policy-applicability", () => { // A pattern result holds each field as a redirect link to the cell that // stores it, so a profile a person's home deploys reads `{ name: }` // where a plain document reads `{ name: "Ada" }`. The field's write claim // applies to a handle to either: the policy's condition does not read a // link below the handle as a value that fails `type: "string"`. let storageManager: ReturnType | undefined; let runtime: Runtime | undefined; afterEach(async () => { await runtime?.dispose(); await storageManager?.close(); storageManager = undefined; runtime = undefined; }); const open = (): Runtime => { storageManager = StorageManager.emulate({ as: signer }); return runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager, cfcEnforcementMode: "enforce-explicit", trustSnapshotProvider: () => ({ id: "cfc-nested-link-policy-applicability", actingPrincipal: signer.did(), }), }); }; /** A profile whose fields are plain values. */ const plainProfile = async ( rt: Runtime, space: MemorySpace, id = "plain-profile", ): Promise> => { const tx = rt.edit(); const profile = rt.getCell(space, id, profileSchema, tx); profile.set({ name: "Ada", avatar: "ada.png" }); tx.prepareCfc(); expect((await tx.commit().settled).error).toBeUndefined(); return profile; }; /** * A profile whose fields are redirect links to the cells that store them, * as a pattern result's are. */ const resultProfile = async ( rt: Runtime, space: MemorySpace, id = "result-profile", ): Promise> => { const tx = rt.edit(); const name = rt.getCell(space, `${id}-name`, undefined, tx); name.set("Ada"); const avatar = rt.getCell(space, `${id}-avatar`, undefined, tx); avatar.set("ada.png"); const profile = rt.getCell(space, id, profileSchema, tx); profile.setRaw({ name: name.getAsWriteRedirectLink(), avatar: avatar.getAsWriteRedirectLink(), }); tx.prepareCfc(); expect((await tx.commit().settled).error).toBeUndefined(); return profile; }; /** * Writes `profile` into a panel's `addedByProfile` as `identity`, through * `schema`: the claim-bearing panel schema unless given, and no schema at * all for `"none"`, so that the write records no schema input of its own. */ const writeProfile = async ( rt: Runtime, identity: ImplementationIdentity, panelId: string, profile: Cell, schema: JSONSchema | "none" = panelSchema, ): Promise => { const tx = rt.edit(); setCfcImplementationIdentity(tx, identity); if (schema === "none") { rt.getCell(signer.did(), panelId, undefined, tx).key("addedByProfile") .set(profile.withTx(tx)); } else { rt.getCell(signer.did(), panelId, schema, tx).set({ addedByProfile: profile.withTx(tx), }); } tx.prepareCfc(); const error = (await tx.commit().settled).error?.message; await rt.idle(); return error; }; for ( const [label, space] of [ ["in the panel's space", signer.did() as MemorySpace], ["in another space", homeSpace], ] as const ) { for ( const [kind, make] of [ ["plain", plainProfile], ["pattern-result", resultProfile], ] as const ) { it(`refuses a ${kind} profile ${label} written by a module the claim does not name`, async () => { const rt = open(); const profile = await make(rt, space); expect(await writeProfile(rt, asForger, `forged-${kind}`, profile)) .toMatch(/writeAuthorizedBy failed at \/addedByProfile/); }); it(`refuses a ${kind} profile ${label} written over a stored claim by a module whose schema declares none`, async () => { // The claim is stored by the admitting module's first write, and the // forger's own schema declares none. The write's schema input still // carries the stored claim, merged in before verification, so this // case holds with or without the link-write overlay; the case below, // which records no schema input, is the one that depends on it. const rt = open(); const profile = await make(rt, space); expect(await writeProfile(rt, asAdmitter, `stored-${kind}`, profile)) .toBeUndefined(); const replacement = await make(rt, space, `replacement-${kind}`); expect( await writeProfile( rt, asForger, `stored-${kind}`, replacement, claimlessPanelSchema, ), ).toMatch(/writeAuthorizedBy failed at \/addedByProfile/); }); it(`refuses a ${kind} profile ${label} written over a stored claim through a cell with no schema`, async () => { // The write records no schema input, so the one claim that gates it // is the stored one the link write's target path carries. const rt = open(); const profile = await make(rt, space); expect(await writeProfile(rt, asAdmitter, `bare-${kind}`, profile)) .toBeUndefined(); const replacement = await make(rt, space, `bare-replacement-${kind}`); expect( await writeProfile(rt, asForger, `bare-${kind}`, replacement, "none"), ).toMatch(/writeAuthorizedBy failed at \/addedByProfile/); }); it(`commits a ${kind} profile ${label} written by the module the claim names`, async () => { const rt = open(); const profile = await make(rt, space); expect(await writeProfile(rt, asAdmitter, `admitted-${kind}`, profile)) .toBeUndefined(); }); } } });