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 { markRendererTrustedEvent } from "../src/cfc/ui-contract.ts";
import type { RuntimeProgram } from "../src/harness/types.ts";
import { Runtime } from "../src/runtime.ts";
import { runtimePresets } from "../src/runtime-presets.ts";
import { recordReplayedArgumentSlots } from "../src/cfc/reference-initialization.ts";
import { parseLink } from "../src/link-utils.ts";
import { readStoredCfcMetadata } from "../src/cfc/metadata.ts";
import { recomposeSchema } from "../src/schema-decompose.ts";
import { lookupSchemaDocument } from "../src/schema-registry.ts";
import type { JSONSchema } from "../src/builder/types.ts";
// A writer policy says which handler may modify a path, from any runtime. A
// runtime that starts a piece it did not create replays the setup of the
// sub-pieces its pattern composes, and the replay re-stages each argument
// document with the bytes it already holds: the writer-policied inputs among
// them, with whatever their named handler has put there since. That
// re-stage modifies nothing. Refusing it would refuse the piece-start
// commit, which tears the started graph down in every runtime but the
// creator's. It is admitted without admitting anything else: a write from
// another handler that changes a guarded byte is refused, in either runtime.
const signer = await Identity.fromPassphrase("cfc-writer-policy-cold-start");
const space = signer.did();
const PROGRAM: RuntimeProgram = {
main: "/main.tsx",
files: [{
name: "/main.tsx",
contents: `///
import {
type Confidential,
Default,
handler,
pattern,
Stream,
Writable,
type WriteAuthorizedBy,
} from "commonfabric";
import {
exchangeRule,
exchangeRules,
type PolicyOf,
THIS_POLICY,
} from "commonfabric/cfc";
export const neverRelease = exchangeRule({
appliesTo: THIS_POLICY,
pre: { integrity: ["never-present-atom"] },
post: { dropClause: true },
});
export const rules = exchangeRules([neverRelease]);
type Sealed = Confidential]>;
interface Entry {
seat: number;
digest: string;
}
const submit = handler<{ seat: number }, { entries: Writable[]> }>(
({ seat }, { entries }) => {
entries.push({ seat, digest: "d" + seat });
},
);
const freeze = handler<{ digest: string }, { frozen: Writable> }>(
({ digest }, { frozen }) => {
frozen.set({ seat: 0, digest });
},
);
interface Input {
entries?: Default[], typeof submit>, []>;
frozen?: Default<
WriteAuthorizedBy, typeof freeze>,
{ seat: -1; digest: "" }
>;
// Room state no writer policy guards.
topic?: Default;
}
interface Output {
topic: string;
entries: Sealed[];
frozen: Sealed;
submit: Stream<{ seat: number }>;
freeze: Stream<{ digest: string }>;
}
const Room = pattern(({ entries, frozen, topic }) => ({
topic,
entries,
frozen,
submit: submit({ entries }),
freeze: freeze({ frozen }),
}));
// The room is a sub-piece, as it is wherever a pattern composes one: starting
// the parent replays the room's setup.
export default pattern, { room: Output }>(() => ({
room: Room({}),
}));
`,
}],
};
// A sub-piece whose input a UI contract guards: only an event from the
// room's own surface may write it, whichever handler receives the event.
const CONTRACT_PROGRAM: RuntimeProgram = {
main: "/main.tsx",
files: [{
name: "/main.tsx",
contents: `///
import {
Default,
handler,
pattern,
Stream,
type TrustedActionUiContract,
Writable,
} from "commonfabric";
const pin = handler<{ text: string }, { badge: Writable<{ text: string }> }>(
({ text }, { badge }) => {
badge.set({ text });
},
);
interface Input {
badge?: Default<
TrustedActionUiContract<{ text: string }, "Pin", "GuardedRoom">,
{ text: "" }
>;
}
interface Output {
badge: { text: string };
pin: Stream<{ text: string }>;
}
const Room = pattern(({ badge }) => ({
badge,
pin: pin({ badge }),
}));
export default pattern, { room: Output }>(() => ({
room: Room({}),
}));
`,
}],
};
type Room = {
entries: { seat: number; digest: string }[];
frozen: { seat: number; digest: string };
topic: string;
};
type Piece = { room: Room };
describe("writer-policied inputs of a sub-piece", () => {
let storageManager: ReturnType;
beforeEach(() => {
storageManager = StorageManager.emulate({ as: signer });
});
afterEach(async () => {
await storageManager?.close();
});
// The posture a pattern runs under everywhere a member runs one: labels
// persist, and a member's own slots materialize per user.
const newRuntime = () =>
new Runtime(runtimePresets.patternTest({
apiUrl: new URL(import.meta.url),
storageManager,
experimental: {},
}));
// `start()` resolves before its piece-start commit settles, so a refused
// commit reaches a test only through the observer seam.
const observeStartFailures = (runtime: Runtime): string[] => {
const failures: string[] = [];
runtime.pieceStartCommitFailureObserver = ({ error }) => {
failures.push(String((error as Error)?.message ?? error));
};
return failures;
};
const send = async (
runtime: Runtime,
cell: ReturnType,
stream: string,
event: unknown,
) => {
(cell.key("room").key(stream) as unknown as {
send: (e: unknown) => void;
}).send(event);
await runtime.idle();
await cell.pull();
await runtime.idle();
};
// Create the piece and give each writer-policied input content from its
// named handler, so the replay re-stages non-default bytes.
const storePiece = async (runtime: Runtime, name: string) => {
const tx = runtime.edit();
const pattern = await runtime.patternManager.compilePattern(PROGRAM, {
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();
await send(runtime, running, "submit", { seat: 1 });
await send(runtime, running, "freeze", { digest: "first" });
expect(running.get().room.entries.map((entry) => entry.seat)).toEqual([1]);
expect(running.get().room.frozen.digest).toBe("first");
await runtime.storageManager.synced();
};
const startCold = async (runtime: Runtime, name: string) => {
const cell = runtime.getCell(space, name);
await cell.sync();
expect(await runtime.start(cell)).toBe(true);
await cell.pull();
await runtime.idle();
return cell;
};
it("starts it, and the named handlers write from there", async () => {
const creator = newRuntime();
const cold = newRuntime();
const failures = observeStartFailures(cold);
try {
await storePiece(creator, "writer-policy-cold-start");
const cell = await startCold(cold, "writer-policy-cold-start");
expect(
failures,
"the piece-start commit was refused, so the started graph was torn " +
"down",
).toEqual([]);
await send(cold, cell, "submit", { seat: 2 });
await send(cold, cell, "freeze", { digest: "second" });
expect(cell.get().room.entries.map((entry) => entry.seat)).toEqual([
1,
2,
]);
expect(cell.get().room.frozen.digest).toBe("second");
} finally {
await cold.dispose();
await creator.dispose();
}
});
it("still refuses a write from anything but the named handler", async () => {
const creator = newRuntime();
const cold = newRuntime();
const failures = observeStartFailures(cold);
try {
await storePiece(creator, "writer-policy-cold-forge");
const cell = await startCold(cold, "writer-policy-cold-forge");
// A refusal below is only evidence if the graph is up to refuse it.
expect(failures).toEqual([]);
const argument = cell.key("room").resolveAsCell().getArgumentCell<
Room
>()!;
for (
const [runtime, source] of [[cold, argument], [
creator,
creator.getCellFromLink(argument.getAsNormalizedFullLink()),
]] as const
) {
const append = runtime.edit();
source.withTx(append).key("entries").push({ seat: 9, digest: "x" });
expect((await append.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
const overwrite = runtime.edit();
source.withTx(overwrite).key("frozen").set({ seat: 9, digest: "x" });
expect((await overwrite.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
const empty = runtime.edit();
source.withTx(empty).key("entries").set([]);
expect((await empty.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
}
await cell.pull();
expect(cell.get().room.entries.map((entry) => entry.seat)).toEqual([1]);
expect(cell.get().room.frozen.digest).toBe("first");
} finally {
await cold.dispose();
await creator.dispose();
}
});
it("refuses pattern code rewriting a guarded field with its own bytes", async () => {
// What admits the replay is the runtime's record of the slots it carries
// over, not the absence of a change: pattern code setting a whole
// document, guarded fields included, still needs their writer, even
// where it leaves them byte for byte as they were.
const creator = newRuntime();
try {
await storePiece(creator, "writer-policy-whole-document");
const cell = creator.getCell(
space,
"writer-policy-whole-document",
);
await cell.sync();
const argument = cell.key("room").resolveAsCell().getArgumentCell<
Room
>()!;
const rewrite = creator.edit();
const current = argument.withTx(rewrite).get();
argument.withTx(rewrite).set({ ...current, topic: "lunch" });
expect((await rewrite.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
const forge = creator.edit();
argument.withTx(forge).set({
...argument.withTx(forge).get(),
frozen: { seat: 9, digest: "x" },
});
expect((await forge.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
await creator.idle();
expect(argument.get().topic).toBe("");
expect(argument.get().entries.map((entry) => entry.seat)).toEqual([1]);
expect(argument.get().frozen.digest).toBe("first");
} finally {
await creator.dispose();
}
});
describe("a transaction carrying the replay's record of its slots", () => {
// The runtime's record of the slots a replay carries over is what makes a
// refusal there a candidate for deferral, never what admits it: the
// commit still proves the transaction leaves each guarded slot as it
// found it. These tests carry that record into transactions that change
// a guarded slot, write authoritatively or relabel, and each is refused;
// one that changes only an unguarded slot is admitted.
const replayingTx = async (runtime: Runtime, name: string) => {
await storePiece(runtime, name);
const cell = runtime.getCell(space, name);
await cell.sync();
const argument = cell.key("room").resolveAsCell().getArgumentCell<
Room
>()!;
const tx = runtime.edit();
recordReplayedArgumentSlots(
tx,
argument.getAsNormalizedFullLink(),
{},
argument.withTx(tx).getRaw(),
);
return { argument, tx };
};
it("refuses one that changes a guarded byte", async () => {
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-changes",
);
argument.withTx(tx).key("frozen").set({ seat: 0, digest: "forged" });
expect((await tx.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
await runtime.idle();
expect(argument.get().frozen.digest).toBe("first");
} finally {
await runtime.dispose();
}
});
it("admits a whole-document write that leaves each guarded slot as it was", async () => {
// The replay writes the argument document whole, over slots a caller
// names and slots it carries over. A slot no writer policy guards may
// change; the guarded ones are compared inside the whole write.
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-whole",
);
argument.withTx(tx).set({
...argument.withTx(tx).get(),
topic: "lunch",
});
expect((await tx.commit().settled).error).toBeUndefined();
await runtime.idle();
expect(argument.get().topic).toBe("lunch");
expect(argument.get().frozen.digest).toBe("first");
} finally {
await runtime.dispose();
}
});
it("refuses a whole-document write that changes a guarded slot inside it", async () => {
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-whole-forge",
);
argument.withTx(tx).set({
...argument.withTx(tx).get(),
frozen: { seat: 0, digest: "forged" },
});
expect((await tx.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
await runtime.idle();
expect(argument.get().frozen.digest).toBe("first");
} finally {
await runtime.dispose();
}
});
it("refuses one that replaces the document above the guarded slots", async () => {
// A write whose shape changes above a guarded slot records its detail
// there, not at the slot, so the slot is compared inside it.
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-root",
);
(argument.withTx(tx) as unknown as { set(value: unknown): void }).set(
"replaced",
);
expect((await tx.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
await runtime.idle();
expect(argument.get().frozen.digest).toBe("first");
} finally {
await runtime.dispose();
}
});
it("refuses an authoritative one, which commits the document whole", async () => {
// An authoritative commit writes every document it touched over what
// the store holds by then, so that it saw the bytes unchanged says
// nothing about what it leaves behind.
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-authoritative",
);
// The extended transaction marks itself only where it serves a seal
// destination; the mode itself lives on the transaction it wraps.
expect(tx.tx.markAuthoritativeWrites).toBeDefined();
tx.tx.markAuthoritativeWrites!();
expect(tx.isAuthoritativeWrites?.()).toBe(true);
const frozen = argument.withTx(tx).key("frozen");
frozen.set({ ...frozen.get() });
expect((await tx.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
} finally {
await runtime.dispose();
}
});
it("refuses one that would store another label at the slot", async () => {
// Unchanged bytes are half of modifying nothing: a write that would
// store a different policy at the guarded slot, byte for byte the
// same value beneath it, still needs the slot's writer.
const runtime = newRuntime();
try {
const { argument, tx } = await replayingTx(
runtime,
"writer-policy-replay-relabels",
);
// The schema the replay writes with: the one the piece's argument
// link carries.
const room = runtime.getCell(
space,
"writer-policy-replay-relabels",
).key("room").resolveAsCell();
const link = parseLink(room.getMetaRaw("argument"));
const schema = recomposeSchema(
(link?.schema as { $ref: string }).$ref,
lookupSchemaDocument,
) as Record;
expect(schema.properties).toBeDefined();
const storedEnvelope = () => {
const read = runtime.edit();
const { space, id, scope } = argument.getAsNormalizedFullLink();
try {
return readStoredCfcMetadata(read, { space, id, scope });
} finally {
read.abort();
}
};
const before = storedEnvelope();
expect(before).toBeDefined();
const properties = schema.properties as Record;
const frozenIfc = (properties.frozen as {
ifc: { confidentiality?: unknown[] };
}).ifc;
const relabeled = {
...schema,
properties: {
...properties,
frozen: {
...(properties.frozen as Record),
ifc: {
...frozenIfc,
confidentiality: [
...(frozenIfc.confidentiality ?? []),
"replay-secret",
],
},
},
},
};
const frozen = argument.asSchema(relabeled as JSONSchema).withTx(tx)
.key("frozen");
frozen.set({ ...(frozen.get() as object) } as never);
expect((await tx.commit().settled).error?.message).toContain(
"writeAuthorizedBy",
);
await runtime.idle();
expect(storedEnvelope()).toEqual(before);
} finally {
await runtime.dispose();
}
});
});
it("starts one whose input a UI contract guards, and the surface's events write it from there", async () => {
const pinEvent = (text: string) => {
const event = {
text,
provenance: {
origin: "dom",
trusted: true,
ui: {
pattern: "GuardedRoom",
eventIntegrity: ["GuardedRoom"],
uiContractDataset: { uiAction: "Pin" },
},
},
};
markRendererTrustedEvent(event);
return event;
};
const creator = newRuntime();
const cold = newRuntime();
const failures = observeStartFailures(cold);
try {
const tx = creator.edit();
const pattern = await creator.patternManager.compilePattern(
CONTRACT_PROGRAM,
{ space, tx },
);
const created = creator.getCell<{ room: { badge: { text: string } } }>(
space,
"contract-cold-start",
undefined,
tx,
);
const running = creator.run(tx, pattern, {}, created);
creator.prepareTxForCommit(tx);
expect((await tx.commit().settled).error).toBeUndefined();
await running.pull();
await creator.idle();
await send(creator, running, "pin", pinEvent("first"));
expect(running.get().room.badge.text).toBe("first");
await creator.storageManager.synced();
const cell = cold.getCell<{ room: { badge: { text: string } } }>(
space,
"contract-cold-start",
);
await cell.sync();
expect(await cold.start(cell)).toBe(true);
await cell.pull();
await cold.idle();
expect(
failures,
"the piece-start commit was refused, so the started graph was torn " +
"down",
).toEqual([]);
await send(cold, cell, "pin", pinEvent("second"));
expect(cell.get().room.badge.text).toBe("second");
} finally {
await cold.dispose();
await creator.dispose();
}
});
});