Expose bounded public reflection and presentation discovery

This commit is contained in:
Timothy J. Aveni
2026-09-22 17:22:48 -07:00
parent ca83dbd1a7
commit bcc915d597
3 changed files with 128 additions and 0 deletions
+2
View File
@@ -17,3 +17,5 @@ export { generatePortableClientBindings } from "../bindings/portable-client.js";
export * from "./candidate.js"; export * from "./candidate.js";
export { reviewSemanticMajors } from "../capability-model/evolution.js"; export { reviewSemanticMajors } from "../capability-model/evolution.js";
export * from "./public-reflection.js";
+94
View File
@@ -0,0 +1,94 @@
import { compileKernelProgram } from "./kernel.js";
import { hexDigest, type ExecutionPlan } from "./plan.js";
import { clientContractCatalog, type ExecutionInstallation } from "./installation.js";
import { publicOperationContract, publicQueryContract } from "./client-contracts.js";
/** Explicit public projection. Never serialize conformance storage or package ports. */
export function publicDefinitionSnapshot(plan: ExecutionPlan, installation: ExecutionInstallation) {
const workspace = plan.definition.workspace;
const kernel = compileKernelProgram(plan) as {
operations: {
atom: string;
interface: string;
operation: string;
unsupported: string | null;
effect: string;
targets: string[];
}[];
};
const catalog = clientContractCatalog(
plan,
installation,
installation.packages.map((pkg) => pkg.revision),
);
const rows = {
atoms: workspace.atoms.map((atom) => ({ id: atom.id, name: atom.displayName, documentation: atom.documentation })),
interfaces: workspace.interfaceImports.map((iface) => ({
id: iface.revisionId,
interface: iface.interfaceId,
name: iface.displayName,
source: iface.source,
template: iface.template,
application: iface.application,
requiredInterfaces: iface.requiredInterfaces ?? [],
argumentRequirements: iface.argumentRequirements ?? [],
members: iface.members.map((member) => ({
...member,
operations: member.operations.map((operation) => ({
...operation,
...(operation.mode === "call" && !iface.template
? { descriptor: publicOperationContract(iface, operation) }
: {}),
})),
})),
})),
conformances: workspace.conformances.map((c) => ({
id: JSON.stringify([c.atomId, c.interfaceRevisionId]),
atom: c.atomId,
interface: c.interfaceRevisionId,
semanticMajor: c.semanticMajor ?? 0,
operations: (
workspace.interfaceImports.find((iface) => iface.revisionId === c.interfaceRevisionId)?.members ?? []
).flatMap((member) =>
member.operations.map((operation) => {
const installed = kernel.operations.find(
(op) => op.atom === c.atomId && op.interface === c.interfaceRevisionId && op.operation === operation.id,
);
return {
operation: operation.id,
available: !!installed && !installed.unsupported,
unavailable: installed?.unsupported ?? (installed ? null : "NOT_CALLABLE"),
execution: installed ? { kind: installed.effect, targets: installed.targets } : null,
};
}),
),
modules: catalog.moduleBindings.filter(
(binding) => binding.atom === c.atomId && binding.interface === c.interfaceRevisionId,
),
})),
queries: workspace.packageImports.flatMap((pkg) =>
(pkg.checkedQueries ?? []).map((query) => ({
id: JSON.stringify([pkg.revisionId, query.declaration.id]),
name: query.declaration.displayName,
package: pkg.revisionId,
descriptor: publicQueryContract(pkg, query.declaration.id),
fragment: query.declaration.fragment,
limits: query.declaration.budgets,
watch: query.declaration.watch,
})),
),
presentations: catalog.components.map((component) => ({
id: JSON.stringify([component.packageRevision, component.id]),
component,
registrations: catalog.presentations.filter(
(p) => p.packageRevision === component.packageRevision && p.component === component.id,
),
})),
};
const publicRows = JSON.parse(JSON.stringify(rows)) as Record<
keyof typeof rows,
{ id: string; [key: string]: unknown }[]
>;
for (const entries of Object.values(publicRows)) entries.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
return { codec: "camino-definitions-v1" as const, id: hexDigest(publicRows), rows: publicRows };
}
+32
View File
@@ -308,3 +308,35 @@ test("a count-only local query still requires complete relationship admission",
assert.throws(() => compileExecutionPlan(workspace), /REPLICA_TRAVERSAL_MISSING|REPLICA_RELATION_INCOMPLETE/); assert.throws(() => compileExecutionPlan(workspace), /REPLICA_TRAVERSAL_MISSING|REPLICA_RELATION_INCOMPLETE/);
} }
}); });
test("public reflection contains nominal contracts and callable descriptors without private bindings", async () => {
const { publicDefinitionSnapshot } = await import("../src/execution-world/public-reflection.js");
const plan = compileExecutionPlan(await loadReaderWorkspace());
const installation = {
codec: "camino-execution-installation-v1" as const,
plan: plan.digest,
world: "test",
packages: plan.definition.workspace.packageImports.map((pkg) => ({
revision: pkg.revisionId,
root: "private-installation-root",
})),
components: [],
routes: {},
};
const snapshot = publicDefinitionSnapshot(plan, installation);
assert.ok(snapshot.rows.interfaces.length);
assert.ok(snapshot.rows.queries.length);
assert.ok(snapshot.rows.conformances.length);
const publicText = JSON.stringify(snapshot);
for (const privateKey of [
"privateAttachments",
"operationBindings",
"relationshipMaterializations",
"storagePolicy",
"ports",
])
assert.ok(!publicText.includes('"' + privateKey + '"'), privateKey);
assert.ok(publicText.includes('"descriptor"'));
assert.ok(publicText.includes('"scope":"class"'));
assert.equal(snapshot.id, publicDefinitionSnapshot(plan, installation).id);
});