Add bounded public member inspection through shared query scopes
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
import { compileKernelProgram } from "./kernel.js";
|
||||||
|
import { hexDigest, type ExecutionPlan } from "./plan.js";
|
||||||
|
import type { PublicQueryContract } from "./client-contracts.js";
|
||||||
|
import { portableValueSchema } from "../bindings/portable.js";
|
||||||
|
const scalar = (name: string) => ({ kind: "scalar", name });
|
||||||
|
const record = (fields: Record<string, unknown>) => ({ kind: "record", fields });
|
||||||
|
const optional = (value: unknown) => ({ kind: "optional", value });
|
||||||
|
const selection = (name: string, extra: Record<string, unknown> = {}) => ({
|
||||||
|
name,
|
||||||
|
key: name,
|
||||||
|
arguments: {},
|
||||||
|
conditions: [],
|
||||||
|
selection: [],
|
||||||
|
...extra,
|
||||||
|
});
|
||||||
|
const variable = (name: string) => ({ kind: "variable", name });
|
||||||
|
const meta = () => selection("_camino", { selection: [selection("ref")] });
|
||||||
|
export const inspectionPackage = "org.quixos.inspection/1";
|
||||||
|
export interface InspectionTemplate {
|
||||||
|
interface: string;
|
||||||
|
members: { id: string; name: string; key: string; flag: string; lane: "local" | "server"; many: boolean }[];
|
||||||
|
inputs?: PublicQueryContract;
|
||||||
|
view?: PublicQueryContract;
|
||||||
|
server?: PublicQueryContract;
|
||||||
|
}
|
||||||
|
/** A finite installed template per interface/lane, not a per-client query compiler.
|
||||||
|
* Flags select public cache boundaries; normal scope identity bounds specialization. */
|
||||||
|
export function compileInspectionTemplates(plan: ExecutionPlan) {
|
||||||
|
const kernel = compileKernelProgram(plan) as {
|
||||||
|
operations: {
|
||||||
|
key: string;
|
||||||
|
atom: string;
|
||||||
|
interface: string;
|
||||||
|
operation: string;
|
||||||
|
effect: string;
|
||||||
|
targets: string[];
|
||||||
|
unsupported: string | null;
|
||||||
|
output: unknown;
|
||||||
|
binding: { kind: string };
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
const queries: unknown[] = [],
|
||||||
|
contracts: PublicQueryContract[] = [],
|
||||||
|
templates: InspectionTemplate[] = [];
|
||||||
|
for (const iface of plan.definition.workspace.interfaceImports) {
|
||||||
|
if (iface.template) continue;
|
||||||
|
const atoms = plan.definition.workspace.conformances
|
||||||
|
.filter((c) => c.interfaceRevisionId === iface.revisionId)
|
||||||
|
.map((c) => c.atomId)
|
||||||
|
.sort();
|
||||||
|
if (!atoms.length) continue;
|
||||||
|
const members = iface.members
|
||||||
|
.filter((member) => (member.kind === "value" || member.kind === "relationship") && member.queryRead)
|
||||||
|
.sort((a, b) => (a.id < b.id ? -1 : 1));
|
||||||
|
if (!members.length || members.length > 128) continue;
|
||||||
|
const template: InspectionTemplate = { interface: iface.revisionId, members: [] };
|
||||||
|
for (const lane of ["local", "server"] as const) {
|
||||||
|
const fields: unknown[] = [],
|
||||||
|
selections: unknown[] = [],
|
||||||
|
inputFields: Record<string, unknown> = {},
|
||||||
|
outputFields: Record<string, unknown> = {};
|
||||||
|
for (const [index, member] of members.entries()) {
|
||||||
|
if (member.kind !== "value" && member.kind !== "relationship") continue;
|
||||||
|
const getter = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve"));
|
||||||
|
if (!getter) continue;
|
||||||
|
const providers = atoms.map((atom) =>
|
||||||
|
kernel.operations.find(
|
||||||
|
(op) => op.atom === atom && op.interface === iface.revisionId && op.operation === getter.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue;
|
||||||
|
const portable = providers.every((op) => op!.effect === "snapshot" && op!.targets.includes("browser"));
|
||||||
|
if (lane === "local" && !portable) continue;
|
||||||
|
const key = "m" + index,
|
||||||
|
flag = "s" + index;
|
||||||
|
const many = member.kind === "relationship" && ["many", "many-unique"].includes(member.cardinality);
|
||||||
|
// Only native edges promise the relationship traversal used by the interpreter.
|
||||||
|
if (member.kind === "relationship" && providers.some((op) => op!.binding.kind !== "edge")) continue;
|
||||||
|
if (lane === "local" || !portable)
|
||||||
|
template.members.push({ id: member.id, name: member.displayName, key, flag, lane, many });
|
||||||
|
inputFields[flag] = scalar("bool");
|
||||||
|
const field = selection(member.displayName, {
|
||||||
|
key,
|
||||||
|
interfaceRevisionId: iface.revisionId,
|
||||||
|
memberId: member.id,
|
||||||
|
conditions: [{ include: true, value: variable(flag) }],
|
||||||
|
});
|
||||||
|
let output = portableValueSchema(getter.outputType);
|
||||||
|
if (member.kind === "relationship") {
|
||||||
|
const expectation =
|
||||||
|
member.target.kind === "atom"
|
||||||
|
? { kind: "atom", id: member.target.atomId }
|
||||||
|
: { kind: "interface", id: member.target.interfaceRevisionId };
|
||||||
|
const node = record({ _camino: record({ ref: { kind: "object-ref", expectation } }) });
|
||||||
|
if (member.target.kind === "interface")
|
||||||
|
Object.assign(field, { targetInterfaceRevisionId: member.target.interfaceRevisionId });
|
||||||
|
if (many) {
|
||||||
|
inputFields[key + "First"] = scalar("int32");
|
||||||
|
inputFields[key + "After"] = optional(scalar("string"));
|
||||||
|
Object.assign(field, {
|
||||||
|
arguments: { first: variable(key + "First"), after: variable(key + "After") },
|
||||||
|
selection: [
|
||||||
|
selection("entries", { selection: [selection("key"), selection("node", { selection: [meta()] })] }),
|
||||||
|
selection("pageInfo", { selection: [selection("hasNextPage"), selection("endCursor")] }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
output = record({
|
||||||
|
entries: { kind: "list", value: record({ key: scalar("string"), node }) },
|
||||||
|
pageInfo: record({ hasNextPage: scalar("bool"), endCursor: optional(scalar("string")) }),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.assign(field, { selection: [meta()] });
|
||||||
|
output = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputFields[key] = optional(output);
|
||||||
|
selections.push(field);
|
||||||
|
for (const op of providers)
|
||||||
|
fields.push({
|
||||||
|
atom: op!.atom,
|
||||||
|
interface: iface.revisionId,
|
||||||
|
member: member.id,
|
||||||
|
name: member.displayName,
|
||||||
|
operation: op!.key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!selections.length) continue;
|
||||||
|
const root = { kind: "object" as const, interface: iface.revisionId };
|
||||||
|
const input = record(inputFields),
|
||||||
|
output = record({ root: record(outputFields) });
|
||||||
|
const base = {
|
||||||
|
root,
|
||||||
|
atoms,
|
||||||
|
variables: input,
|
||||||
|
defaults: {},
|
||||||
|
selection: [selection("root", { selection: selections })],
|
||||||
|
fields,
|
||||||
|
admittedOperations: [],
|
||||||
|
budgets: {
|
||||||
|
rows: 512,
|
||||||
|
depth: 8,
|
||||||
|
resultBytes: 8 * 1024 * 1024,
|
||||||
|
candidates: 1000,
|
||||||
|
rpcCalls: 128,
|
||||||
|
concurrency: 8,
|
||||||
|
deadlineMs: 10000,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const kind of lane === "local" ? (["inputs", "view"] as const) : (["server"] as const)) {
|
||||||
|
const query = JSON.stringify([iface.revisionId, kind]);
|
||||||
|
const sourceQuery = JSON.stringify([iface.revisionId, "inputs"]);
|
||||||
|
const program = {
|
||||||
|
...base,
|
||||||
|
id: JSON.stringify([inspectionPackage, query]),
|
||||||
|
placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "remote",
|
||||||
|
source: kind === "view" ? JSON.stringify([inspectionPackage, sourceQuery]) : null,
|
||||||
|
};
|
||||||
|
const contract: PublicQueryContract = {
|
||||||
|
package: inspectionPackage,
|
||||||
|
query,
|
||||||
|
definitionDigest: hexDigest(program),
|
||||||
|
root,
|
||||||
|
placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "server",
|
||||||
|
...(kind === "view" ? { sourceQuery } : {}),
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
};
|
||||||
|
queries.push(program);
|
||||||
|
contracts.push(contract);
|
||||||
|
template[kind] = contract;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (template.members.length) templates.push(template);
|
||||||
|
}
|
||||||
|
return { queries, contracts, templates };
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { loadCheckedArtifactWorld } from "./receipts.js";
|
import { loadCheckedArtifactWorld } from "./receipts.js";
|
||||||
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
|
||||||
@@ -24,6 +25,7 @@ export interface ExecutionGroupIdentity {
|
|||||||
readonly executionDigest: string;
|
readonly executionDigest: string;
|
||||||
}
|
}
|
||||||
export interface ClientContractCatalog {
|
export interface ClientContractCatalog {
|
||||||
|
readonly inspections: readonly InspectionTemplate[];
|
||||||
readonly resultInterfaces: Readonly<Record<string, readonly string[]>>;
|
readonly resultInterfaces: Readonly<Record<string, readonly string[]>>;
|
||||||
readonly operations: readonly PublicOperationContract[];
|
readonly operations: readonly PublicOperationContract[];
|
||||||
readonly queries: readonly PublicQueryContract[];
|
readonly queries: readonly PublicQueryContract[];
|
||||||
@@ -66,14 +68,17 @@ export function clientContractCatalog(
|
|||||||
.map((operation) => publicOperationContract(iface, operation)),
|
.map((operation) => publicOperationContract(iface, operation)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const inspection = compileInspectionTemplates(plan);
|
||||||
return {
|
return {
|
||||||
|
inspections: inspection.templates,
|
||||||
resultInterfaces: Object.fromEntries(
|
resultInterfaces: Object.fromEntries(
|
||||||
plan.definition.operations.map((operation) => [operation.key, resultInterfaces(plan, operation.key)]),
|
plan.definition.operations.map((operation) => [operation.key, resultInterfaces(plan, operation.key)]),
|
||||||
),
|
),
|
||||||
operations,
|
operations,
|
||||||
queries: packages.flatMap((pkg) =>
|
queries: [
|
||||||
(pkg.checkedQueries ?? []).map((q) => publicQueryContract(pkg, q.declaration.id)),
|
...inspection.contracts,
|
||||||
),
|
...packages.flatMap((pkg) => (pkg.checkedQueries ?? []).map((q) => publicQueryContract(pkg, q.declaration.id))),
|
||||||
|
],
|
||||||
components: installation.components.filter((c) => requestedPackages.includes(c.packageRevision)),
|
components: installation.components.filter((c) => requestedPackages.includes(c.packageRevision)),
|
||||||
moduleBindings: plan.definition.workspace.conformances.flatMap((c) =>
|
moduleBindings: plan.definition.workspace.conformances.flatMap((c) =>
|
||||||
(c.moduleBindings ?? [])
|
(c.moduleBindings ?? [])
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { compileInspectionTemplates } from "./inspection.js";
|
||||||
import { compileKernelProgram } from "./kernel.js";
|
import { compileKernelProgram } from "./kernel.js";
|
||||||
import { hexDigest, type ExecutionPlan } from "./plan.js";
|
import { hexDigest, type ExecutionPlan } from "./plan.js";
|
||||||
import { clientContractCatalog, type ExecutionInstallation } from "./installation.js";
|
import { clientContractCatalog, type ExecutionInstallation } from "./installation.js";
|
||||||
@@ -66,17 +67,26 @@ export function publicDefinitionSnapshot(plan: ExecutionPlan, installation: Exec
|
|||||||
(binding) => binding.atom === c.atomId && binding.interface === c.interfaceRevisionId,
|
(binding) => binding.atom === c.atomId && binding.interface === c.interfaceRevisionId,
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
queries: workspace.packageImports.flatMap((pkg) =>
|
queries: [
|
||||||
(pkg.checkedQueries ?? []).map((query) => ({
|
...compileInspectionTemplates(plan).contracts.map((descriptor) => ({
|
||||||
id: JSON.stringify([pkg.revisionId, query.declaration.id]),
|
id: JSON.stringify([descriptor.package, descriptor.query]),
|
||||||
name: query.declaration.displayName,
|
name: "Public inspection",
|
||||||
package: pkg.revisionId,
|
package: descriptor.package,
|
||||||
descriptor: publicQueryContract(pkg, query.declaration.id),
|
descriptor,
|
||||||
fragment: query.declaration.fragment,
|
system: true,
|
||||||
limits: query.declaration.budgets,
|
|
||||||
watch: query.declaration.watch,
|
|
||||||
})),
|
})),
|
||||||
),
|
...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) => ({
|
presentations: catalog.components.map((component) => ({
|
||||||
id: JSON.stringify([component.packageRevision, component.id]),
|
id: JSON.stringify([component.packageRevision, component.id]),
|
||||||
component,
|
component,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { compileInspectionTemplates } from "./inspection.js";
|
||||||
import { portableValueSchema } from "../bindings/portable.js";
|
import { portableValueSchema } from "../bindings/portable.js";
|
||||||
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
|
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
|
||||||
import type { ValueType } from "../capability-model/types.js";
|
import type { ValueType } from "../capability-model/types.js";
|
||||||
@@ -26,44 +27,47 @@ export function compileQueryProgram(plan: ExecutionPlan): unknown {
|
|||||||
return {
|
return {
|
||||||
codec: "camino-query-program-v1",
|
codec: "camino-query-program-v1",
|
||||||
plan: plan.digest,
|
plan: plan.digest,
|
||||||
queries: plan.definition.queries.map(({ id, query, admittedOperations }) => {
|
queries: [
|
||||||
const checked = query.checked;
|
...compileInspectionTemplates(plan).queries,
|
||||||
return {
|
...plan.definition.queries.map(({ id, query, admittedOperations }) => {
|
||||||
id,
|
const checked = query.checked;
|
||||||
...(checked.declaration.fragment && !checked.declaration.fragment.admission
|
return {
|
||||||
? {
|
id,
|
||||||
fragment: {
|
...(checked.declaration.fragment && !checked.declaration.fragment.admission
|
||||||
package: query.packageRevisionId,
|
? {
|
||||||
id: checked.declaration.id,
|
fragment: {
|
||||||
digest: checked.definitionDigest,
|
package: query.packageRevisionId,
|
||||||
},
|
id: checked.declaration.id,
|
||||||
}
|
digest: checked.definitionDigest,
|
||||||
: {}),
|
},
|
||||||
placement: checked.declaration.placement?.kind ?? "remote",
|
}
|
||||||
source:
|
: {}),
|
||||||
checked.declaration.placement?.kind === "local"
|
placement: checked.declaration.placement?.kind ?? "remote",
|
||||||
? JSON.stringify([query.packageRevisionId, checked.declaration.placement.sourceQueryId])
|
source:
|
||||||
: null,
|
checked.declaration.placement?.kind === "local"
|
||||||
root: checked.declaration.root,
|
? JSON.stringify([query.packageRevisionId, checked.declaration.placement.sourceQueryId])
|
||||||
atoms: query.rootAtoms,
|
: null,
|
||||||
variables: portableValueSchema(checked.variables),
|
root: checked.declaration.root,
|
||||||
defaults: checked.variableDefaults,
|
atoms: query.rootAtoms,
|
||||||
selection: lowerTypes(checked.selection),
|
variables: portableValueSchema(checked.variables),
|
||||||
budgets: checked.declaration.budgets,
|
defaults: checked.variableDefaults,
|
||||||
fields: query.fields.map((field) => {
|
selection: lowerTypes(checked.selection),
|
||||||
const member = interfaces
|
budgets: checked.declaration.budgets,
|
||||||
.find((i) => i.revisionId === field.interfaceRevisionId)!
|
fields: query.fields.map((field) => {
|
||||||
.members.find((m) => m.id === field.memberId)!;
|
const member = interfaces
|
||||||
return {
|
.find((i) => i.revisionId === field.interfaceRevisionId)!
|
||||||
atom: field.atomId,
|
.members.find((m) => m.id === field.memberId)!;
|
||||||
interface: field.interfaceRevisionId,
|
return {
|
||||||
member: field.memberId,
|
atom: field.atomId,
|
||||||
name: member.displayName,
|
interface: field.interfaceRevisionId,
|
||||||
operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]),
|
member: field.memberId,
|
||||||
};
|
name: member.displayName,
|
||||||
}),
|
operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]),
|
||||||
admittedOperations,
|
};
|
||||||
};
|
}),
|
||||||
}),
|
admittedOperations,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { loadReaderWorkspace } from "./reader-fixture.js";
|
||||||
|
import { compileExecutionPlan, compileQueryProgram } from "../src/execution-world/index.js";
|
||||||
|
import { compileInspectionTemplates } from "../src/execution-world/inspection.js";
|
||||||
|
test("public member inspection installs a finite set of typed lazy templates", async () => {
|
||||||
|
const plan = compileExecutionPlan(await loadReaderWorkspace());
|
||||||
|
const result = compileInspectionTemplates(plan);
|
||||||
|
assert.ok(result.templates.length);
|
||||||
|
const document = result.templates.find(
|
||||||
|
(template) =>
|
||||||
|
template.members.some((member) => member.name === "content") &&
|
||||||
|
template.members.some((member) => member.name === "highlights"),
|
||||||
|
)!;
|
||||||
|
assert.ok(document.inputs && document.view && document.server);
|
||||||
|
assert.equal(document.view.sourceQuery, document.inputs.query);
|
||||||
|
assert.ok(document.members.find((member) => member.name === "highlights")?.many);
|
||||||
|
assert.ok(!document.members.some((member) => member.name === "revise"));
|
||||||
|
const compiled = compileQueryProgram(plan) as {
|
||||||
|
queries: { id: string; selection: { selection: { conditions: unknown[] }[] }[] }[];
|
||||||
|
};
|
||||||
|
for (const descriptor of result.contracts) {
|
||||||
|
const query = compiled.queries.find(
|
||||||
|
(query) => query.id === JSON.stringify([descriptor.package, descriptor.query]),
|
||||||
|
)!;
|
||||||
|
assert.ok(query);
|
||||||
|
assert.ok(
|
||||||
|
query.selection[0]!.selection.every((field) => field.conditions.length === 1),
|
||||||
|
"Each field admission is independently selected",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert.ok(!JSON.stringify(result.templates).includes("slot:"));
|
||||||
|
assert.equal(result.queries.length, result.contracts.length);
|
||||||
|
assert.ok(result.queries.length <= plan.definition.workspace.interfaceImports.length * 3);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user