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 { resolvePolicyFacingImplementationIdentity } from "../src/cfc/implementation-identity.ts";
import { getVerifiedProvenance } from "../src/harness/verified-provenance.ts";
import type { Module, Pattern } from "../src/builder/types.ts";
import type { HarnessedFunction } from "../src/harness/types.ts";
import { trustModule } from "./support/trusted-builder.ts";
/**
* PRs C and E1 of
* docs/specs/content-addressed-action-identity-implementation-plan.md:
* content-addressed `$implRef` + CFC provenance, and the writer flip.
*
* - Functions become "verified" by being registered through the trust-gated
* module indexing during evaluation; their provenance carries the defining
* module's content identity and the artifact's export/`__cfReg` symbol.
* - Serialized javascript modules carry `$implRef: { identity, symbol }`;
* since the flip (E1) the legacy `implementationRef` is runtime-only and
* the body is omitted when the engine's strong implementation index proves
* the ref resolvable — including after the bounded artifact index evicts.
* - CFC policy identity resolves from the provenance (`moduleIdentity`,
* reload-stable) with the legacy registry as fallback; a forged function —
* even with byte-identical source — has no provenance and fails closed.
*/
const signer = await Identity.fromPassphrase("content-addressed-identity");
const PROGRAM = {
main: "/main.tsx",
files: [{
name: "/main.tsx",
contents: `///
import { handler, pattern, Writable } from "commonfabric";
const setName = handler<{ name?: string }, { name: Writable }>(
(event, state) => { state.name.set(event.name ?? ""); },
);
export default pattern(() => {
const name = new Writable("").for("name");
return { name, setName: setName({ name }) };
});
`,
}],
};
describe("content-addressed action identity", () => {
let storageManager: ReturnType | undefined;
let runtime: Runtime | undefined;
afterEach(async () => {
await runtime?.dispose();
await storageManager?.close();
runtime = undefined;
storageManager = undefined;
});
const setup = async () => {
storageManager = StorageManager.emulate({ as: signer });
runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager,
cfcEnforcementMode: "observe",
});
const pattern = await runtime.patternManager.compilePattern(PROGRAM);
await runtime.idle();
return pattern as Pattern;
};
const handlerModuleOf = (pattern: Pattern): Module => {
const node = pattern.nodes.find((n) =>
(n.module as Module).type === "javascript" &&
(n.module as Module).wrapper === "handler"
);
expect(node).toBeDefined();
return node!.module as Module;
};
it("records provenance for compiled module-scope artifacts", async () => {
const pattern = await setup();
const module = handlerModuleOf(pattern);
const fn = module.implementation as HarnessedFunction;
expect(typeof fn).toBe("function");
const provenance = getVerifiedProvenance(fn);
expect(provenance).toBeDefined();
expect(typeof provenance!.identity).toBe("string");
expect(provenance!.identity.length).toBeGreaterThan(0);
// The non-exported `bump` handler registers via the transformer's
// `__cfReg` hoist; its symbol is the hoist/binding name.
expect(typeof provenance!.symbol).toBe("string");
// The canonical fn.src points into the same module identity.
expect((fn as { src?: string }).src ?? "").toContain(
`cf:module/${provenance!.identity}`,
);
});
it("serializes javascript modules with $implRef only (no legacy fields)", async () => {
const pattern = await setup();
const module = handlerModuleOf(pattern);
const json = (module as Module & { toJSON?: () => unknown }).toJSON
? (module as Module & { toJSON: () => unknown }).toJSON() as Record<
string,
unknown
>
: JSON.parse(JSON.stringify(module));
const ref = json.$implRef as { identity: string; symbol: string };
expect(ref).toBeDefined();
const provenance = getVerifiedProvenance(
module.implementation as HarnessedFunction,
)!;
expect(ref.identity).toBe(provenance.identity);
expect(ref.symbol).toBe(provenance.symbol);
// PR E1 (the flip): the legacy `implementationRef` is no longer written,
// and the body stays omitted because this runtime's engine resolves the
// `$implRef` through its content-addressed implementation index.
expect("implementationRef" in json).toBe(false);
expect("implementation" in json).toBe(false);
});
it("a $implRef-only module survives artifact-index eviction (engine implementation index)", async () => {
const pattern = await setup();
const module = handlerModuleOf(pattern);
const json = (module as Module & { toJSON: () => unknown })
.toJSON() as Record;
const ref = json.$implRef as { identity: string; symbol: string };
expect(ref).toBeDefined();
expect("implementation" in json).toBe(false);
// The engine's implementation index admits the ref directly (this is the
// strong, session-lifetime index that replaces the legacy registry's
// eviction insurance for post-flip data).
expect(
typeof runtime!.harness.getVerifiedImplementation?.(
ref.identity,
ref.symbol,
),
).toBe("function");
// Simulate the bounded artifact index rolling the module out mid-session
// (FIFO eviction after ~1000 other identities) — the worst case for a
// `$implRef`-only stored graph, which has no legacy ref and no body.
const manager = runtime!.patternManager as unknown as {
addressableByIdentity: Map;
modulesByIdentity: Map;
};
manager.addressableByIdentity.clear();
manager.modulesByIdentity.clear();
expect(
runtime!.patternManager.artifactFromIdentitySync(
ref.identity,
ref.symbol,
),
).toBeUndefined();
// A graph carrying only the $implRef must still instantiate and execute.
const nodes = pattern.nodes.map((node) =>
(node.module as Module).type === "javascript" &&
(node.module as Module).wrapper === "handler"
? { ...node, module: json as unknown as Module }
: node
);
const rehydrated = { ...pattern, nodes } as unknown as Pattern;
const tx = runtime!.edit();
const resultCell = runtime!.getCell<{ name: string }>(
signer.did(),
"evicted-implref-resolution",
undefined,
tx,
);
// deno-lint-ignore no-explicit-any
const r = runtime!.run(tx, rehydrated, {}, resultCell) as any;
await tx.commit();
await r.pull();
r.key("setName").send({ name: "resolved-after-eviction" });
await runtime!.idle();
expect(r.key("name").get()).toBe("resolved-after-eviction");
});
it("minting a builder artifact inside a running action fails loudly", async () => {
// Identity E5 (design Phase 4): in-action-created artifacts used to limp
// along through the legacy registry channel — in-session only, never
// rehydratable across sessions. With that channel deleted, the mint
// itself throws at creation time, pointing at the module-level rule (and
// a possible transformer bug, since hoisting makes this unreachable from
// authored pattern source).
const DYNAMIC_PROGRAM = {
main: "/main.tsx",
files: [{
name: "/main.tsx",
contents: `///
import { type Cell, handler, lift, pattern } from "commonfabric";
export const dump = handler, { out: Cell }>(
{ type: "object", properties: {} },
{
type: "object",
properties: { out: { type: "string", asCell: ["cell"] } },
required: ["out"],
},
(_event, state) => {
const base = 1;
const created = lift((value: number) => value + base);
state.out.set(JSON.stringify(created));
},
);
export default pattern<{ out: string }>(({ out }) => ({
out,
dump: dump({ out }),
}));
`,
}],
};
storageManager = StorageManager.emulate({ as: signer });
runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager,
cfcEnforcementMode: "observe",
});
const pattern = await runtime.patternManager.compilePattern(
DYNAMIC_PROGRAM,
) as Pattern;
await runtime.idle();
const tx = runtime.edit();
const resultCell = runtime.getCell<{ out: string }>(
signer.did(),
"dynamic-artifact-mint-throws",
undefined,
tx,
);
// deno-lint-ignore no-explicit-any
const r = runtime.run(tx, pattern, {}, resultCell) as any;
await tx.commit();
await r.pull();
r.key("dump").send({});
await runtime.idle();
// The handler threw at the lift() mint: no output was ever written.
expect(r.key("out").get()).toBeUndefined();
});
it("CFC identity resolves from provenance with a stable moduleIdentity", async () => {
const pattern = await setup();
const module = handlerModuleOf(pattern);
const fn = module.implementation as HarnessedFunction;
const identity = resolvePolicyFacingImplementationIdentity(module, {
implementation: fn,
});
expect(identity).toBeDefined();
expect(identity!.kind).toBe("verified");
const verified = identity as {
kind: "verified";
moduleIdentity?: string;
sourceLocation?: { line: number; column: number };
};
expect(verified.moduleIdentity).toBe(getVerifiedProvenance(fn)!.identity);
expect(verified.sourceLocation).toBeDefined();
});
it("a forged function with identical source fails closed", async () => {
const pattern = await setup();
const module = handlerModuleOf(pattern);
const fn = module.implementation as HarnessedFunction;
// Byte-identical source text, constructed OUTSIDE verified evaluation:
// no provenance entry — NO identity at all (fail closed), never
// `verified`. (Pre-E2 the legacy registry arm reported `unsupported`;
// with the arm gone an unproven function simply resolves to nothing.)
const forged = new Function(
`return ${Function.prototype.toString.call(fn)}`,
)() as HarnessedFunction;
expect(getVerifiedProvenance(forged)).toBeUndefined();
const identity = resolvePolicyFacingImplementationIdentity(module, {
implementation: forged,
});
expect(identity).toBeUndefined();
});
it("$implRef from a stale/foreign ref resolves nothing executable", async () => {
await setup();
// A ref pointing at an identity that was never evaluated resolves to
// undefined in the index — the resolver falls back (legacy ref or
// stringified source); it can never make non-builder data executable
// because only trust-gated artifacts are indexed.
const missing = runtime!.patternManager.artifactFromIdentitySync(
"not-a-real-identity",
"default",
);
expect(missing).toBeUndefined();
});
});
// (The "provenance bundleId fallback" suite retired with the bundleId
// verification arm — identity E5, data-wipe decision.)
describe("$implRef resolution arm (Runner.resolveJavaScriptFunction)", () => {
let storageManager: ReturnType | undefined;
let runtime: Runtime | undefined;
afterEach(async () => {
await runtime?.dispose();
await storageManager?.close();
runtime = undefined;
storageManager = undefined;
});
// A lift makes the resolved executable's EFFECT observable through the
// result value, so the assertion below proves the function actually RAN —
// not merely that something resolved.
const LIFT_PROGRAM = {
main: "/main.tsx",
files: [{
name: "/main.tsx",
contents: `///
import { lift, pattern } from "commonfabric";
export const double = lift((x: number) => x * 2);
export default pattern<{ value: number }>(({ value }) => ({
doubled: double(value),
}));
`,
}],
};
it("a module with ONLY $implRef (legacy ref and body stripped) resolves and executes", async () => {
storageManager = StorageManager.emulate({ as: signer });
runtime = new Runtime({
apiUrl: new URL(import.meta.url),
storageManager,
cfcEnforcementMode: "observe",
});
const pattern = await runtime.patternManager.compilePattern(
LIFT_PROGRAM,
) as Pattern;
await runtime.idle();
// Serialize the compiled lift node's module the way a cell write would
// (dual-write: $implRef alongside implementationRef), then strip the
// legacy fields — the exact shape the planned flip produces (PR E of
// docs/specs/content-addressed-action-identity-implementation-plan.md:
// writers stop emitting implementationRef and the stringified
// implementation).
const node = pattern.nodes.find((n) =>
(n.module as Module).type === "javascript"
);
expect(node).toBeDefined();
const live = node!.module as Module & { toJSON?: () => unknown };
const json =
(live.toJSON
? live.toJSON()
: JSON.parse(JSON.stringify(live))) as Record;
const ref = json.$implRef as { identity: string; symbol: string };
expect(ref).toBeDefined();
delete json.implementationRef;
delete json.implementation;
// With both legacy fields gone, neither the legacy-registry arm (needs
// module.implementationRef) nor the stringified-source fallback (needs
// module.implementation; throws without it) can produce an executable.
// Only `resolveByImplRef` can — by resolving the ref in the artifact
// index. The functionCache cannot mask either: it keys on
// implementationRef, so it neither prewarms nor caches this module.
expect(
runtime.patternManager.artifactFromIdentitySync(ref.identity, ref.symbol),
).toBeDefined();
const stripped = trustModule(runtime, json as unknown as Module);
const tx = runtime.edit();
const resultCell = runtime.getCell(
signer.did(),
"implref-only-execution",
undefined,
tx,
);
const result = runtime.run(tx, stripped, 21, resultCell);
await tx.commit();
const out = await result.pull();
// 21 → 42: the registered artifact's implementation ran via $implRef.
expect(out).toBe(42);
});
});