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 * from "./candidate.js";
|
||||||
|
|
||||||
export { reviewSemanticMajors } from "../capability-model/evolution.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/);
|
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