318 lines
14 KiB
TypeScript
318 lines
14 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { createHash } from "node:crypto";
|
|
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import {
|
|
compileCapabilityResourceSource,
|
|
compileCapabilitySource,
|
|
formatQx,
|
|
parseQx,
|
|
walkSyntax,
|
|
} from "../src/capability-language/index.js";
|
|
import {
|
|
compileExecutionPlan,
|
|
checkExecutionWorld,
|
|
componentArtifactDigest,
|
|
componentSharedModules,
|
|
expectedArtifactReceipt,
|
|
checkExecutionInstallation,
|
|
clientContractCatalog,
|
|
createClientContractCatalog,
|
|
CandidateMetadataBudget,
|
|
compileKernelProgram,
|
|
type ComponentArtifact,
|
|
type PackageArtifacts,
|
|
} from "../src/execution-world/index.js";
|
|
import { validateWorkspaceRevision } from "../src/capability-model/index.js";
|
|
import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js";
|
|
import { compileQueryComposition } from "../src/query/composition.js";
|
|
import type { InspectionKernel } from "../src/execution-world/inspection.js";
|
|
|
|
const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) };
|
|
const contract = (name: string) => {
|
|
const result = compileCapabilityResourceSource(
|
|
`interface ${name} id "interface:${name}" revision "interface:${name}@1" { }`,
|
|
{ source },
|
|
);
|
|
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
|
|
return result.resource.revision;
|
|
};
|
|
const data = contract("Data"),
|
|
other = contract("Other");
|
|
const interfaces = new Map([
|
|
["Data", data],
|
|
["Other", other],
|
|
]);
|
|
const role = compileCapabilityResourceSource(
|
|
`import interface Data;
|
|
interface View id "interface:view" revision "interface:view@1" {
|
|
module render id "member:view:render" contract "org.quixos.react.subject-only/1" subject interface Data;
|
|
}`,
|
|
{ source, environment: { interfaces, interfaceClosure: [...interfaces.values()] } },
|
|
);
|
|
assert.ok(role.ok && role.resource.kind === "interface", JSON.stringify(role.diagnostics));
|
|
interfaces.set("View", role.resource.revision);
|
|
const componentSource = (body = 'module Card id "component:card" implements View.render;') =>
|
|
`import interface Data; import interface View; package UI id "package:ui" revision "package:ui@1" { ${body} }`;
|
|
const compilePackage = (body?: string) =>
|
|
compileCapabilityResourceSource(componentSource(body), {
|
|
source,
|
|
environment: { interfaces, interfaceClosure: [...interfaces.values()] },
|
|
});
|
|
const workspace = (presentation = 'presentation Card id "presentation:card" for Data using UI.Card;') => {
|
|
const pkg = compilePackage();
|
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
|
const preparation = presentationDeclarations('{"modules":{"Card":{"inputs":[]}}}', pkg.resource.revision, [
|
|
...interfaces.values(),
|
|
]);
|
|
pkg.resource.revision.buildArtifacts = { [preparationArtifact]: preparation.artifact };
|
|
pkg.resource.revision.queryCompositions = preparation.compositions.map((entry) =>
|
|
compileQueryComposition(entry, new Map()),
|
|
);
|
|
return compileCapabilitySource(
|
|
`workspace Example id "workspace:example" revision "workspace:example@1" commit "${source.commit}" {
|
|
import interface Data; import interface Other; import interface View; import package UI;
|
|
atom One id "atom:one"; atom Two id "atom:two";
|
|
conform One as Data id "conformance:one:data" { }
|
|
conform Two as Data id "conformance:two:data" { }
|
|
${presentation}
|
|
}`,
|
|
"workspace.qx",
|
|
{
|
|
interfaces,
|
|
interfaceClosure: [...interfaces.values()],
|
|
packages: new Map([["UI", pkg.resource.revision]]),
|
|
},
|
|
);
|
|
};
|
|
|
|
test("interface-only component declarations serve multiple conforming atoms without capability exports", () => {
|
|
const result = workspace();
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
const plan = compileExecutionPlan(result.workspace);
|
|
assert.equal(plan.definition.packages[0]!.exports.length, 0);
|
|
assert.deepEqual(plan.definition.packages[0]!.components, [
|
|
{
|
|
id: "component:card",
|
|
displayName: "Card",
|
|
subject: "interface:Data@1",
|
|
runtime: "react",
|
|
},
|
|
]);
|
|
assert.equal(plan.definition.presentations?.[0]?.componentId, "component:card");
|
|
assert.equal(plan.definition.workspace.conformances.length, 2);
|
|
assert.equal(plan.definition.operations.length, 0);
|
|
});
|
|
|
|
test("component declarations survive formatting and syntax inspection", () => {
|
|
const text = formatQx(componentSource());
|
|
const parsed = parseQx(text);
|
|
assert.deepEqual(parsed.diagnostics, []);
|
|
assert.ok([...walkSyntax(parsed.root)].some((node) => node.kind === "moduleExport"));
|
|
assert.equal(formatQx(text), text);
|
|
});
|
|
|
|
test("component and presentation contracts reject wrong subjects, duplicates and unavailable runtimes", () => {
|
|
for (const [input, message] of [
|
|
['module Card id "component:card" implements Missing.render;', /Missing/],
|
|
['module Card id "component:card" implements Data.missing;', /module member/],
|
|
[
|
|
'module Card id "component:card" implements View.render; module Other id "component:card" implements View.render;',
|
|
/Duplicate or empty module/,
|
|
],
|
|
[
|
|
'module Card id "component:card" implements View.render; module Card id "component:other" implements View.render;',
|
|
/Duplicate or empty module/,
|
|
],
|
|
] as const) {
|
|
const result = compilePackage(input);
|
|
assert.equal(result.ok, false);
|
|
assert.match(JSON.stringify(result.diagnostics), message);
|
|
}
|
|
for (const [input, message] of [
|
|
['presentation Card id "presentation:card" for Other using UI.Card;', /Component requires/],
|
|
['presentation Card id "presentation:card" for Data using UI.Missing;', /Unknown component/],
|
|
[
|
|
'presentation Card id "presentation:card" for Data using UI.Card; presentation Duplicate id "presentation:card" for Data using UI.Card;',
|
|
/Duplicate presentation/,
|
|
],
|
|
] as const) {
|
|
const result = workspace(input);
|
|
assert.equal(result.ok, false);
|
|
assert.match(JSON.stringify(result.diagnostics), message);
|
|
}
|
|
});
|
|
|
|
test("model validation rechecks presentation metadata instead of trusting the source parser", () => {
|
|
const result = workspace();
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
result.workspace.presentations![0]!.componentId = "missing";
|
|
assert.ok(validateWorkspaceRevision(result.workspace).some((issue) => issue.code === "invalid-presentation"));
|
|
assert.throws(() => compileExecutionPlan(result.workspace), /no installed package component/);
|
|
});
|
|
|
|
test("standalone presentation rejects a general component contract that may require extra props", () => {
|
|
const result = workspace();
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
const candidate = structuredClone(result.workspace);
|
|
const member = candidate.interfaceImports.find((iface) => iface.revisionId === "interface:view@1")!.members[0]!;
|
|
assert.equal(member.kind, "module");
|
|
if (member.kind !== "module") throw Error("Expected module");
|
|
member.contract = "org.quixos.react.component/1";
|
|
assert.ok(
|
|
validateWorkspaceRevision(candidate).some((issue) => /supported React presentation contract/.test(issue.message)),
|
|
);
|
|
});
|
|
|
|
test("checked component-only artifacts install without a service and reject missing or substituted entries", async () => {
|
|
const result = workspace();
|
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
|
const plan = compileExecutionPlan(result.workspace),
|
|
pkg = plan.definition.packages[0]!;
|
|
const bytes = new TextEncoder().encode("export const Card = () => null;\n");
|
|
const paths = [
|
|
"components/card.mjs",
|
|
...componentSharedModules.map((_name, i) => `share/quixos/shared/runtime-${i}.mjs`),
|
|
];
|
|
const files = paths.map((file) => ({
|
|
path: file,
|
|
digest: createHash("sha256").update(bytes).digest("hex"),
|
|
bytes: bytes.length,
|
|
mediaType: "text/javascript",
|
|
}));
|
|
const body: Omit<ComponentArtifact, "contentDigest"> = {
|
|
id: "component:card",
|
|
subject: "interface:Data@1",
|
|
compatibility: "org.quixos.react.esm/1",
|
|
path: paths[0]!,
|
|
exportName: "Card",
|
|
files,
|
|
sharedModules: Object.fromEntries(
|
|
componentSharedModules.map((name, i) => [name, paths[i + 1]!]),
|
|
) as ComponentArtifact["sharedModules"],
|
|
};
|
|
const manifest: PackageArtifacts = {
|
|
schemaVersion: 1,
|
|
packageRevisionId: pkg.revision,
|
|
bindingDigest: pkg.bindingDigest,
|
|
sourceDigest: pkg.sourceDigest,
|
|
versions: plan.definition.versions,
|
|
implementations: [],
|
|
components: [{ ...body, contentDigest: componentArtifactDigest(body) }],
|
|
};
|
|
const world = await checkExecutionWorld(plan, [manifest], async () => bytes);
|
|
assert.equal(world.artifacts.length, 1);
|
|
assert.equal(world.artifacts[0]!.target, "browser-renderer");
|
|
await assert.rejects(
|
|
checkExecutionWorld(plan, [{ ...manifest, components: [] }], async () => bytes),
|
|
/Missing component artifact/,
|
|
);
|
|
const substitute = structuredClone(manifest);
|
|
substitute.components![0]!.subject = "interface:Other@1";
|
|
const { contentDigest: _digest, ...changed } = substitute.components![0]!;
|
|
substitute.components![0]!.contentDigest = componentArtifactDigest(changed);
|
|
await assert.rejects(
|
|
checkExecutionWorld(plan, [substitute], async () => bytes),
|
|
/declared subject/,
|
|
);
|
|
await assert.rejects(
|
|
checkExecutionWorld(plan, [manifest], async () => new Uint8Array(bytes.length)),
|
|
/Artifact bytes/,
|
|
);
|
|
const missingShared = structuredClone(manifest);
|
|
delete (missingShared.components![0]!.sharedModules as Partial<ComponentArtifact["sharedModules"]>).react;
|
|
await assert.rejects(
|
|
checkExecutionWorld(plan, [missingShared], async () => bytes),
|
|
/missing fields/,
|
|
);
|
|
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "camino-component-contract-"));
|
|
try {
|
|
for (const file of files) {
|
|
await mkdir(path.dirname(path.join(root, file.path)), { recursive: true });
|
|
await writeFile(path.join(root, file.path), bytes);
|
|
}
|
|
await mkdir(path.join(root, "share/quixos"), { recursive: true });
|
|
await writeFile(path.join(root, "share/quixos/package-artifacts.json"), JSON.stringify(manifest));
|
|
const generator = "a".repeat(64);
|
|
const { receipt, sources } = expectedArtifactReceipt(plan, manifest, generator);
|
|
for (const [file, content] of sources) {
|
|
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
|
|
await writeFile(path.join(root, file), content);
|
|
}
|
|
await writeFile(path.join(root, "share/quixos/build-receipt.json"), JSON.stringify(receipt));
|
|
const installation = await checkExecutionInstallation(plan, { [pkg.revision]: root }, generator);
|
|
assert.equal(installation.packages[0]!.service, undefined);
|
|
assert.equal(installation.components[0]!.id, "component:card");
|
|
assert.ok(installation.routes[installation.components[0]!.url]);
|
|
assert.equal(JSON.stringify(installation.components).includes(root), false);
|
|
assert.equal(JSON.stringify(installation.components).includes("slot:"), false);
|
|
const catalog = await clientContractCatalog(plan, installation, [pkg.revision]);
|
|
assert.equal(catalog.presentations[0]!.component, "component:card");
|
|
assert.equal(catalog.components[0]!.subject, "interface:Data@1");
|
|
assert.equal(JSON.stringify(catalog).includes(root), false);
|
|
assert.equal(JSON.stringify(catalog).includes("bindingSchema"), false);
|
|
await assert.rejects(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/);
|
|
await assert.rejects(
|
|
() => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]),
|
|
/CATALOG_PLAN_MISMATCH/,
|
|
);
|
|
const budget = new CandidateMetadataBudget();
|
|
const scope = budget.acquire("catalog-test");
|
|
const prepared = createClientContractCatalog(
|
|
plan,
|
|
installation,
|
|
scope,
|
|
compileKernelProgram(plan) as InspectionKernel,
|
|
);
|
|
try {
|
|
await assert.rejects(() => prepared.select(["absent"]), /UNINSTALLED_PACKAGE/);
|
|
assert.equal(scope.bytes, 0, "Unknown requests do not allocate compiler metadata");
|
|
const [first, coalesced] = await Promise.all([prepared.select([pkg.revision]), prepared.select([pkg.revision])]);
|
|
assert.deepEqual(first, catalog);
|
|
assert.equal(first.operations, coalesced.operations, "Concurrent preparation shares one metadata reservation");
|
|
const retained = scope.bytes;
|
|
assert.ok(retained > 0);
|
|
assert.ok(retained < 32 * 1024 * 1024, "Small catalogs retain a proportional metadata charge");
|
|
const full = await prepared.select([pkg.revision]);
|
|
const empty = await prepared.select([]);
|
|
assert.deepEqual(empty.components, []);
|
|
assert.deepEqual(empty.presentations, []);
|
|
assert.deepEqual(empty.moduleBindings, []);
|
|
assert.equal(empty.operations, full.operations, "Shared compiled metadata is reused");
|
|
assert.equal(Object.isFrozen(full.operations), true);
|
|
// Any plan access now would reveal a repeated digest/compile on the warm path.
|
|
const definition = plan.definition;
|
|
Object.defineProperty(plan, "definition", {
|
|
configurable: true,
|
|
get() {
|
|
throw Error("RECOMPILED_PLAN");
|
|
},
|
|
});
|
|
try {
|
|
assert.deepEqual(await prepared.select([pkg.revision]), full);
|
|
assert.equal(scope.bytes, retained, "Package projections do not accumulate cache entries");
|
|
} finally {
|
|
Object.defineProperty(plan, "definition", { configurable: true, value: definition });
|
|
}
|
|
prepared.close();
|
|
assert.equal(scope.bytes, 0);
|
|
await assert.rejects(() => prepared.select([pkg.revision]), /CANDIDATE_METADATA_CLOSED/);
|
|
} finally {
|
|
prepared.close();
|
|
scope.release();
|
|
}
|
|
const tight = new CandidateMetadataBudget({ bytes: 1000, ownerBytes: 1000, candidates: 1 });
|
|
const tightScope = tight.acquire("catalog-test");
|
|
const refused = createClientContractCatalog(plan, installation, tightScope);
|
|
await assert.rejects(() => refused.select([pkg.revision]), /CANDIDATE_METADATA_LIMIT/);
|
|
assert.equal(tightScope.bytes, 0);
|
|
refused.close();
|
|
tightScope.release();
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|