/** * A pattern-owned cell scoped narrower than its result cell starts from its * default in every instance, not only in the instance of whoever set the * pattern up first. */ import { expect } from "@std/expect"; import { afterEach, beforeEach, describe, it } from "@std/testing/bdd"; import { Identity } from "@commonfabric/identity"; import type * as MemoryV2Server from "@commonfabric/memory/v2/server"; import type { JSONSchema } from "../src/builder/types.ts"; import { commitCfcFieldValue } from "../src/cfc/label-representation.ts"; import { readStoredCfcMetadata } from "../src/cfc/metadata.ts"; import { applyCfcPolicyToExistingValue } from "../src/cfc/policy-application.ts"; import { setCfcImplementationIdentity } from "../src/cfc/trust-authority.ts"; import { Runtime } from "../src/runtime.ts"; import { RetryImmediately } from "../src/scheduler/retry-immediately.ts"; import { stampSpeculationRunContext } from "../src/speculation/overlay-destination.ts"; import { EmulatedStorageManager, newLoopbackServer, } from "../src/storage/cache.deno.ts"; import { createTrustedBuilder } from "./support/trusted-builder.ts"; const owner = await Identity.fromPassphrase("scoped internal cell seed"); const visitor = await Identity.fromPassphrase( "scoped internal cell seed visitor", ); const space = owner.did(); interface Draft { title: string; members: string; } const EMPTY_DRAFT: Draft = { title: "", members: "" }; const draftSchema = { type: "object", properties: { title: { type: "string" }, members: { type: "string" }, }, required: ["title", "members"], } as const satisfies JSONSchema; const resultSchema = { type: "object", properties: { draft: { ...draftSchema, asCell: ["cell"] }, }, required: ["draft"], } as const satisfies JSONSchema; const privateDraftProgram = (protectedDraft = false) => ({ main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { Confidential, CurrentPrincipal, handler, pattern, Writable, WriteAuthorizedBy } from "commonfabric"; type Private = Confidential; export const save = handler<{ value: string }, { draft: Writable }>( ({ value }, { draft }) => draft.set(value), ); type Draft = ${ protectedDraft ? "Private>" : "Private" }; type Output = { shared: Private; draft: Writable }; const Editor = pattern<{ shared: Private }, Output>(({ shared }) => { const draft = Writable.perUser.of(""); return { shared, draft }; }); export default pattern, Output>(() => { const shared = Writable.perSpace.of>("shared"); return Editor({ shared }); }); `, }], }); describe("scoped-internal-cell-seed", () => { let server: MemoryV2Server.Server; let opened: { runtime: Runtime; manager: EmulatedStorageManager }[]; beforeEach(() => { server = newLoopbackServer({ subscriptionRefreshDelayMs: 0 }); opened = []; }); afterEach(async () => { for (const { runtime, manager } of opened) { await manager.synced(); await runtime.dispose(); await manager.close(); } await server.close(); }); /** Opens a runtime with a memory session of its own, as a page load does. */ function openRuntime(as: Identity, serverExecution = false) { const manager = EmulatedStorageManager.connectTo(server, { as }); const runtime = new Runtime({ apiUrl: new URL(import.meta.url), storageManager: manager, experimental: { serverExecution }, }); opened.push({ runtime, manager }); return runtime; } /** The pattern that holds a draft cell of the given scope. */ function draftHolder( runtime: Runtime, scope: "space" | "user" | "session", initial = EMPTY_DRAFT, ) { const { pattern, Cell } = createTrustedBuilder(runtime).commonfabric; const scoped = scope === "space" ? Cell.perSpace : scope === "user" ? Cell.perUser : Cell.perSession; return pattern( () => ({ draft: scoped.of(initial) }), { type: "object", properties: {} }, resultSchema, ); } /** * Loads the draft-holding piece in a runtime of its own, and returns the * piece's draft cell. */ async function load(scope: "user" | "session", as: Identity) { const runtime = openRuntime(as); const resultCell = runtime.getCell(space, "draft holder", resultSchema); const result = await runtime.runSynced( resultCell, draftHolder(runtime, scope), {}, ); await runtime.idle(); return { runtime, draft: result.key("draft") }; } /** Writes `title` into the draft's own field, as a bound text input does. */ async function writeTitle( { runtime, draft }: Awaited>, title: string, ) { const tx = runtime.edit(); draft.withTx(tx).key("title").set(title); expect((await tx.commit().settled).error).toBeUndefined(); await runtime.idle(); } for ( const [scope, later] of [ ["session", "a later session of the same user"], ["user", "another user"], ] as const ) { it(`reads a field written by ${later} beside the ${scope} cell's defaults`, async () => { const first = await load(scope, owner); await writeTitle(first, "first"); const second = await load(scope, scope === "user" ? visitor : owner); await writeTitle(second, "second"); expect(second.draft.get()).toEqual({ title: "second", members: "" }); expect(first.draft.get()).toEqual({ title: "first", members: "" }); }); } it("keeps a user's per-user value when that user loads again", async () => { await writeTitle(await load("user", owner), "kept"); const reloaded = await load("user", owner); expect(reloaded.draft.get()).toEqual({ title: "kept", members: "" }); }); for (const protectedDraft of [false, true]) { it(`starts another user's ${protectedDraft ? "protected " : ""}private draft beside a shared confidential value`, async () => { const first = openRuntime(owner); const compiled = await first.patternManager.compilePattern( privateDraftProgram(protectedDraft), { space }, ); const piece = first.getCell(space, "private draft holder"); await first.runSynced(piece, compiled, {}); await first.idle(); const second = openRuntime(visitor); const failures: unknown[] = []; second.pieceStartCommitFailureObserver = ({ error }) => failures.push(error); const reopened = second.getCell(space, "private draft holder"); await reopened.sync(); await second.runner.syncStoredPieceCells(reopened, compiled); expect(reopened.key("draft").resolveAsCell().getRawUntyped()) .toBeUndefined(); const tx = second.edit(); second.run(tx, compiled, {}, reopened); second.prepareTxForCommit(tx); expect((await tx.commit().settled).error).toBeUndefined(); await second.idle(); expect(failures).toEqual([]); const privateDraft = reopened.key("draft").resolveAsCell(); expect(privateDraft.getRawUntyped()).toBe(""); const metadataTx = second.edit(); try { const metadata = readStoredCfcMetadata( metadataTx, privateDraft.getAsNormalizedFullLink(), ); const clauses = metadata?.labelMap.entries.flatMap((entry) => entry.label.confidentiality ?? [] ); expect(clauses).toEqual(expect.arrayContaining([{ type: "https://commonfabric.org/cfc/atom/User", subject: visitor.did(), }])); expect(JSON.stringify(clauses)).not.toContain(owner.did()); } finally { metadataTx.abort("private draft label assertions complete"); } const write = second.edit(); privateDraft.withTx(write).set("kept"); const written = await write.commit().settled; if (protectedDraft) expect(written.error).toBeDefined(); else expect(written.error).toBeUndefined(); await second.idle(); const third = openRuntime(visitor); third.pieceStartCommitFailureObserver = ({ error }) => failures.push(error); const reloaded = third.getCell(space, "private draft holder"); await reloaded.sync(); const reloadTx = third.edit(); third.run(reloadTx, compiled, {}, reloaded); third.prepareTxForCommit(reloadTx); expect((await reloadTx.commit().settled).error).toBeUndefined(); await third.idle(); expect(failures).toEqual([]); expect(reloaded.key("draft").resolveAsCell().getRawUntyped()).toBe( protectedDraft ? "" : "kept", ); }); } it("keeps a cold-started actor's private default linked to its owning piece", async () => { const first = openRuntime(owner); const compiled = await first.patternManager.compilePattern( privateDraftProgram(), { space }, ); const original = first.getCell(space, "cold private holder"); await first.runSynced(original, compiled, {}); await first.idle(); const second = openRuntime(visitor); const piece = second.getCell(space, "cold private holder"); await piece.sync(); expect(await second.runner.start(piece)).toBe(true); await second.idle(); const draft = piece.key("draft").resolveAsCell(); expect(draft.getRawUntyped()).toBe(""); expect(draft.getMetaRaw("result")).toEqual( original.key("draft").resolveAsCell().getMetaRaw("result"), ); expect(draft.getMetaRaw("result")).toBeDefined(); }); for ( const [explicitPublic, title] of [ [ false, "preserves the owning piece's confidentiality on a cold session default", ], [ true, "labels an explicitly public cold session default without widening its declaration", ], ] as const ) { it(title, async () => { // The owner's session instance is read by the owner alone, so its // residency admits the owning piece's `User` label over any // declaration (§8.12.4), and the label lands as the value's derived // stamp. Nothing is declared, so a public declaration stays public. const first = openRuntime(owner); const compiled = await first.patternManager.compilePattern({ main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { Confidential, pattern, Writable } from "commonfabric"; export default pattern>(() => ({ tab: Writable.perSession.of<${ explicitPublic ? "Confidential" : "string" }>("overview"), }));`, }], }, { space }); const original = first.getCell(space, "private owner with session state"); await first.runSynced(original, compiled, {}); await first.idle(); const ownerLabel = { type: "https://commonfabric.org/cfc/atom/User", subject: owner.did(), }; const protect = first.edit(); setCfcImplementationIdentity(protect, { kind: "builtin", builtinId: "scoped-seed-owner-policy", }); applyCfcPolicyToExistingValue( original.withTx(protect).asSchema({ ifc: { confidentiality: [ownerLabel] }, }), ); expect((await protect.commit().settled).error).toBeUndefined(); await first.storageManager.synced(); const second = openRuntime(owner); const piece = second.getCell(space, "private owner with session state"); await piece.sync(); expect(await second.runner.start(piece)).toBe(true); await second.idle(); const tab = piece.key("tab").resolveAsCell(); expect(tab.getRawUntyped()).toBe("overview"); const inspect = second.edit(); try { const entries = readStoredCfcMetadata( inspect, tab.getAsNormalizedFullLink(), )?.labelMap.entries; expect(entries).toEqual(expect.arrayContaining([ expect.objectContaining({ path: [], origin: "derived", observes: "value", label: expect.objectContaining({ confidentiality: expect.arrayContaining([ownerLabel]), }), }), ])); expect(entries?.filter((entry) => entry.origin === "declared")) .toEqual([]); } finally { inspect.abort("cold session label assertions complete"); } }); } it("labels a cold session default with the owning piece's committed personal-space label", async () => { // A piece whose label crossed a space boundary holds its personal-space // owner as a `{digestOf}` commitment. A cold start seeds its session // default under that label, in the owner's own session instance, whose // residency admits it (§8.12.4), so the label lands as the value's // derived stamp. const first = openRuntime(owner); const compiled = await first.patternManager.compilePattern({ main: "/main.tsx", files: [{ name: "/main.tsx", contents: `/// import { pattern, Writable } from "commonfabric"; export default pattern>(() => ({ tab: Writable.perSession.of("overview"), }));`, }], }, { space }); const original = first.getCell(space, "personal space with session state"); await first.runSynced(original, compiled, {}); await first.idle(); const committedPersonalSpace = { type: "https://commonfabric.org/cfc/atom/PersonalSpace", owner: commitCfcFieldValue(space), }; const protect = first.edit(); setCfcImplementationIdentity(protect, { kind: "builtin", builtinId: "scoped-seed-owner-policy", }); applyCfcPolicyToExistingValue( original.withTx(protect).asSchema({ ifc: { confidentiality: [committedPersonalSpace] }, }), ); expect((await protect.commit().settled).error).toBeUndefined(); await first.storageManager.synced(); const second = openRuntime(owner); const piece = second.getCell(space, "personal space with session state"); await piece.sync(); expect(await second.runner.start(piece)).toBe(true); await second.idle(); const tab = piece.key("tab").resolveAsCell(); expect(tab.getRawUntyped()).toBe("overview"); const inspect = second.edit(); try { const metadata = readStoredCfcMetadata( inspect, tab.getAsNormalizedFullLink(), ); expect(metadata?.labelMap.entries).toEqual(expect.arrayContaining([ expect.objectContaining({ path: [], origin: "derived", observes: "value", label: expect.objectContaining({ confidentiality: expect.arrayContaining([committedPersonalSpace]), }), }), ])); } finally { inspect.abort("committed personal space label assertions complete"); } }); it("initializes defaults in the run when dependency loading rejects", async () => { await load("user", owner); const second = openRuntime(visitor); const piece = second.getCell(space, "draft holder", resultSchema); const pattern = draftHolder(second, "user"); await second.runner.syncStoredPieceCells(piece, pattern); let attempts = 0; second.runner.accessForTestingOnly.dependencySyncer = () => { attempts++; return Promise.reject(new Error("dependency sync unavailable")); }; try { const tx = second.edit(); second.run(tx, pattern, {}, piece); expect((await tx.commit().settled).error).toBeUndefined(); await second.idle(); } finally { second.runner.accessForTestingOnly.dependencySyncer = undefined; } expect(attempts).toBe(1); expect(piece.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); }); for (const kind of ["derivation", "event-handler"] as const) { it(`keeps a speculative ${kind}'s default out of durable storage`, async () => { await load("user", owner); const second = openRuntime(visitor, true); const piece = second.getCell(space, "draft holder", resultSchema); const pattern = draftHolder(second, "user"); await second.runner.syncStoredPieceCells(piece, pattern); const tx = second.edit(); stampSpeculationRunContext(tx, { actionId: "speculative-draft-start", kind, ...(kind === "event-handler" ? { eventId: "draft-event" } : {}), }); second.run(tx, pattern, {}, piece); expect((await tx.commit().settled).error).toBeUndefined(); await second.idle(); expect(piece.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); const durable = openRuntime(visitor); const stored = durable.getCell(space, "draft holder", resultSchema); await durable.runner.syncStoredPieceCells( stored, draftHolder(durable, "user"), ); expect(stored.key("draft").resolveAsCell().getRawUntyped()) .toBeUndefined(); // An authored start in the same runtime must not reuse a speculative // landing as evidence that this actor's durable default was prepared. const authored = second.edit(); second.run(authored, pattern, {}, piece); expect((await authored.commit().settled).error).toBeUndefined(); await second.idle(); await durable.runner.syncStoredPieceCells( stored, draftHolder(durable, "user"), ); expect(stored.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); }); } it("initializes the requested pattern when its stored manifest changes during naming", async () => { const first = await load("user", owner); const original = first.runtime.getCell(space, "draft holder", resultSchema); const second = openRuntime(visitor); const piece = second.getCell(space, "draft holder", resultSchema); const requested = draftHolder(second, "user"); await piece.sync(); const entered = Promise.withResolvers(); const release = Promise.withResolvers(); second.runner.accessForTestingOnly.dependencySyncer = async ( cell, pattern, inputs, sync, ) => { entered.resolve(); await release.promise; return await sync(cell, pattern, inputs); }; try { const tx = second.edit(); second.run(tx, requested, {}, piece); expect((await tx.commit().settled).error).toBeUndefined(); await entered.promise; await first.runtime.runSynced( original, draftHolder(first.runtime, "user", { title: "new default", members: "", }), {}, ); await first.runtime.idle(); await piece.sync(); } finally { release.resolve(); await second.idle(); second.runner.accessForTestingOnly.dependencySyncer = undefined; } expect(piece.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); }); it("keeps a caller-owned setup's defaults inside its commit", async () => { await load("user", owner); const second = openRuntime(visitor); const piece = second.getCell(space, "draft holder", resultSchema); await piece.sync(); const tx = second.edit(); await second.runSynced(piece.withTx(tx), draftHolder(second, "user"), {}, { start: false, }); tx.abort("caller declined setup"); expect(piece.key("draft").resolveAsCell().getRawUntyped()).toBeUndefined(); }); for (const callerOwned of [true, false]) { it(`initializes another user's default through ${callerOwned ? "caller-owned" : "owned"} setup`, async () => { await load("user", owner); const second = openRuntime(visitor); const piece = second.getCell(space, "draft holder", resultSchema); const pattern = draftHolder(second, "user"); await second.runner.syncStoredPieceCells(piece, pattern); const tx = callerOwned ? second.edit() : undefined; await second.runner.setup(tx, pattern, {}, piece); if (tx) expect((await tx.commit().settled).error).toBeUndefined(); expect(piece.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); }); } for (const overlap of [false, true]) { it(`initializes after ${overlap ? "joining" : "an abandoned"} caller-owned dependency load`, async () => { await load("user", owner); const second = openRuntime(visitor); const piece = second.getCell(space, "draft holder", resultSchema); const pattern = draftHolder(second, "user"); await piece.sync(); const entered = Promise.withResolvers(); const release = Promise.withResolvers(); second.runner.accessForTestingOnly.dependencySyncer = async ( cell, pattern, inputs, sync, ) => { entered.resolve(); await release.promise; return await sync(cell, pattern, inputs); }; const tx = second.edit(); const pending = Promise.resolve( second.runner.runInTransaction(tx, pattern, {}, piece), ) .then(() => { throw new Error("Expected dependency load retry"); }, (error: unknown) => { expect(error).toBeInstanceOf(RetryImmediately); }); try { await entered.promise; if (!overlap) { release.resolve(); await pending; tx.abort("caller declined retry"); } const runTx = second.edit(); second.run(runTx, pattern, {}, piece); expect((await runTx.commit().settled).error).toBeUndefined(); } finally { release.resolve(); await pending; if (overlap) tx.abort("caller declined retry"); await second.idle(); second.runner.accessForTestingOnly.dependencySyncer = undefined; } expect(piece.key("draft").resolveAsCell().getRawUntyped()).toEqual( EMPTY_DRAFT, ); }); } it("leaves an actor's default absent when its start is stopped during synchronization", async () => { const first = openRuntime(owner); const compiled = await first.patternManager.compilePattern( privateDraftProgram(), { space }, ); await first.runSynced( first.getCell(space, "cancelled private holder"), compiled, {}, ); await first.idle(); const second = openRuntime(visitor); const piece = second.getCell(space, "cancelled private holder"); await piece.sync(); const entered = Promise.withResolvers(); const release = Promise.withResolvers(); second.runner.accessForTestingOnly.dependencySyncer = async ( cell, pattern, inputs, sync, ) => { entered.resolve(); await release.promise; return await sync(cell, pattern, inputs); }; try { const tx = second.edit(); second.run(tx, compiled, {}, piece); expect((await tx.commit().settled).error).toBeUndefined(); await entered.promise; second.runner.stop(piece); } finally { release.resolve(); await second.idle(); second.runner.accessForTestingOnly.dependencySyncer = undefined; } expect(piece.key("draft").resolveAsCell().getRawUntyped()).toBeUndefined(); }); for (const [from, to] of [["user", "space"], ["session", "user"]] as const) { it(`reads a field written after the cell's scope changes from ${from} to ${to} beside its defaults`, async () => { // The same partial cause names the cell in both versions, so the // manifest entry the first version left is for the cell's other scope. const runtime = openRuntime(owner); const resultCell = runtime.getCell( space, "rescoped holder", resultSchema, ); await runtime.runSynced(resultCell, draftHolder(runtime, from), {}); const result = await runtime.runSynced( resultCell, draftHolder(runtime, to), {}, ); await runtime.idle(); const draft = result.key("draft"); await writeTitle({ runtime, draft }, "rescoped"); expect(draft.resolveAsCell().getAsNormalizedFullLink().scope).toBe(to); expect(draft.get()).toEqual({ title: "rescoped", members: "" }); }); } });