Expose bounded public reflection and presentation discovery
This commit is contained in:
@@ -17,3 +17,5 @@ export { generatePortableClientBindings } from "../bindings/portable-client.js";
|
||||
export * from "./candidate.js";
|
||||
|
||||
export { reviewSemanticMajors } from "../capability-model/evolution.js";
|
||||
|
||||
export * from "./public-reflection.js";
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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/);
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user