Implement progressive query admission, session boundaries and event-driven Studio observations
This commit is contained in:
@@ -15,7 +15,10 @@ import {
|
||||
} from "./metadata.js";
|
||||
const rootPattern = /^\/nix\/store\/[a-z0-9]{32}-[^/\s]+$/;
|
||||
async function readJson(root: string, file: string, scope: CandidateMetadataScope) {
|
||||
const input = await readCandidateMetadata(join(root, file), scope, 32 * 1024 * 1024);
|
||||
// Query Trees add checked projections to an already large application plan.
|
||||
// The Reader now exceeds 32 MiB even without indentation. This file bound
|
||||
// does not replace the stricter aggregate/owner parse-allocation budgets.
|
||||
const input = await readCandidateMetadata(join(root, file), scope, 64 * 1024 * 1024);
|
||||
try {
|
||||
return {
|
||||
value: JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes)),
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { BindingSchema } from "../bindings/index.js";
|
||||
import type { PackageBuildContract } from "./build-contract.js";
|
||||
import { preparationArtifact, type ReactPreparationArtifact } from "../react-build/declarations.js";
|
||||
import { moduleTypeContext } from "./module-contracts.js";
|
||||
import { queryTreeBlueprint, queryTreeType } from "./query-trees.js";
|
||||
|
||||
/** Public shape only: no slot inventory, implementation binding, or workspace identity. */
|
||||
export interface PublicOperationContract {
|
||||
@@ -76,6 +77,7 @@ export interface PublicQueryContract {
|
||||
sourceQuery?: string;
|
||||
input: unknown;
|
||||
output: unknown;
|
||||
tree?: ReturnType<typeof queryTreeBlueprint>;
|
||||
}
|
||||
export function publicQueryContract(pkg: PackageRevision, queryId: string): PublicQueryContract {
|
||||
const query = pkg.checkedQueries?.find((q) => q.declaration.id === queryId);
|
||||
@@ -88,9 +90,10 @@ export function publicQueryContract(pkg: PackageRevision, queryId: string): Publ
|
||||
definitionDigest: query.definitionDigest,
|
||||
root: query.declaration.root,
|
||||
placement: placement?.kind ?? "server",
|
||||
...(placement?.kind === "local" ? { sourceQuery: placement.sourceQueryId } : {}),
|
||||
...(placement?.kind === "local" ? { sourceQuery: query.treeProjection?.tree ?? placement.sourceQueryId } : {}),
|
||||
input: portableValueSchema(query.variables),
|
||||
output: portableValueSchema(query.output),
|
||||
...(query.tree ? { tree: queryTreeBlueprint(pkg.revisionId, query) } : {}),
|
||||
};
|
||||
}
|
||||
export function linkPackageQuery(plan: ExecutionPlan, contract: PublicQueryContract) {
|
||||
@@ -156,10 +159,10 @@ export function generatePackageQueryClientBindings(
|
||||
checkedQueries: pkg.checkedQueries?.filter((query) => !internal.includes(query.declaration.id)),
|
||||
};
|
||||
const queries = (pkg.checkedQueries ?? [])
|
||||
.filter((query) => !query.declaration.fragment)
|
||||
.filter((query) => !query.declaration.fragment && !query.treeProjection)
|
||||
.map(
|
||||
(query) =>
|
||||
`${JSON.stringify(query.declaration.displayName)}: definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}, ${JSON.stringify(publicQueryContract(pkg, query.declaration.id).placement)}>(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`,
|
||||
`${JSON.stringify(query.declaration.displayName)}: ${query.tree ? `definePackageQueryTree<${tsType(query.variables)}, ${queryTreeType(query)}, ${JSON.stringify(query.declaration.root.interface)}>` : `definePackageQuery<${tsType(query.variables)}, ${tsType(query.output)}, ${JSON.stringify(publicQueryContract(pkg, query.declaration.id).placement)}>`}(${JSON.stringify(publicQueryContract(pkg, query.declaration.id))})`,
|
||||
);
|
||||
const emitFragment = (provider: PackageRevision, id: string) => {
|
||||
const query = provider.checkedQueries?.find((query) => query.declaration.id === id);
|
||||
@@ -210,7 +213,7 @@ export function generatePackageQueryClientBindings(
|
||||
fragments.push(
|
||||
`${JSON.stringify(alias)}: {${[...entries].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(",")}}`,
|
||||
);
|
||||
return `// Generated from checked package query contracts. Regenerate; do not edit.\nimport { definePackageQuery, definePackageFragment, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const queries = {${queries.join(",\n")}} as const;\nexport const fragments = {${fragments.join(",\n")}} as const;\n`;
|
||||
return `// Generated from checked package query contracts. Regenerate; do not edit.\nimport { definePackageQuery, definePackageFragment, ${queries.some((query) => query.includes("definePackageQueryTree<")) ? "definePackageQueryTree, type QueryTreeShape, type QueryTreeOccurrence, " : ""}type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const queries = {${queries.join(",\n")}} as const;\nexport const fragments = {${fragments.join(",\n")}} as const;\n`;
|
||||
}
|
||||
|
||||
/** Receipt-covered bindings expose the package's imports, component subjects and
|
||||
@@ -279,7 +282,7 @@ export function generatePackageComponentBindings(
|
||||
.join("\n");
|
||||
return (
|
||||
interfaces +
|
||||
'\nimport {createObjectRenderer,definePackageQuery,definePackageFragment,type ComponentType,type ComponentProps,type PresentationHandle} from "@quixos/camino-react";\n' +
|
||||
'\nimport {createObjectRenderer,definePackageQuery,definePackageFragment,definePackageQueryTree,type QueryTreeShape,type QueryTreeOccurrence,type QueryTreeHandle,type ComponentType,type ComponentProps,type PresentationHandle} from "@quixos/camino-react";\n' +
|
||||
subjectBindings +
|
||||
"\n" +
|
||||
queries +
|
||||
@@ -319,7 +322,13 @@ export function generatePackageComponentBindings(
|
||||
const handle = `PresentationHandle<${JSON.stringify(child.subject)}, ${extra}>`;
|
||||
return `readonly ${JSON.stringify(child.name)}: ${child.each ? `readonly ${handle}[]` : child.with || child.when ? `${handle} | null` : handle}`;
|
||||
});
|
||||
const input = `${tsType(preparation.input ?? composition.output)} & {readonly children: {${children.join(";")}}}`;
|
||||
const trees = (preparation.trees ?? []).map((tree) => {
|
||||
const query = pkg.checkedQueries?.find((query) => query.declaration.id === tree.query);
|
||||
if (!query?.tree)
|
||||
throw new ExecutionPlanError("MISSING_QUERY_TREE", tree.query, "Expected a checked Query Tree");
|
||||
return `readonly ${JSON.stringify(tree.name)}: QueryTreeHandle<${queryTreeType(query)}>`;
|
||||
});
|
||||
const input = `${tsType(preparation.input ?? composition.output)} & {readonly children: {${children.join(";")}}${trees.length ? `;readonly trees:{${trees.join(";")}}` : ""}}`;
|
||||
return `${JSON.stringify(component.displayName)}: ComponentType<ComponentProps<${JSON.stringify(component.subject)}, ${JSON.stringify(module.contract)}, ${moduleArguments(find(module.interfaceRevisionId))}, ${input}> & ExtraModuleProps<${JSON.stringify(key)}>>;`;
|
||||
})
|
||||
.join("\n") +
|
||||
|
||||
@@ -10,10 +10,10 @@ export class CandidateMetadataBudget {
|
||||
private scopes = 0;
|
||||
private readonly owners = new Map<string, number>();
|
||||
// A cutover holds both the serving and proposed installation plus transient
|
||||
// parsing space. The 57-repository reader retains ~133 MiB per installation;
|
||||
// a 256 MiB owner ceiling cannot admit its ordinary atomic replacement.
|
||||
// parsing space. The Reader with checked Query Trees retains ~176 MiB per
|
||||
// installation; its replacement also needs transient parse/index space.
|
||||
// Keep the aggregate ceiling independent and leave room for parsing overlap.
|
||||
constructor(limits = { bytes: 512 * 1024 * 1024, ownerBytes: 384 * 1024 * 1024, candidates: 128 }) {
|
||||
constructor(limits = { bytes: 768 * 1024 * 1024, ownerBytes: 512 * 1024 * 1024, candidates: 128 }) {
|
||||
for (const value of Object.values(limits))
|
||||
if (!Number.isSafeInteger(value) || value < 1) throw Error("INVALID_CANDIDATE_METADATA_BUDGET");
|
||||
if (limits.ownerBytes > limits.bytes) throw Error("INVALID_CANDIDATE_METADATA_BUDGET");
|
||||
|
||||
@@ -4,6 +4,7 @@ import { portableValueSchema } from "../bindings/portable.js";
|
||||
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
|
||||
import type { ValueType } from "../capability-model/types.js";
|
||||
import { compileQueryComposition } from "../query/composition.js";
|
||||
import { queryTreeBlueprint } from "./query-trees.js";
|
||||
|
||||
const lowerTypes = (value: unknown): unknown =>
|
||||
JSON.parse(
|
||||
@@ -59,6 +60,12 @@ export function compileQueryProgram(plan: ExecutionPlan): unknown {
|
||||
codec: "camino-query-program-v1",
|
||||
plan: (compileKernelProgram(plan) as { plan: string }).plan,
|
||||
...(compositions.length ? { compositions } : {}),
|
||||
trees: plan.definition.queries
|
||||
.filter((entry) => entry.query.checked.tree)
|
||||
.map((entry) => ({
|
||||
id: entry.id,
|
||||
...queryTreeBlueprint(entry.query.packageRevisionId, entry.query.checked),
|
||||
})),
|
||||
queries: [
|
||||
...compileInspectionTemplates(plan).queries,
|
||||
...plan.definition.queries.map((entry) => queryProgramEntry(plan, entry)),
|
||||
@@ -85,7 +92,10 @@ export function queryProgramEntry(plan: ExecutionPlan, entry: import("./plan.js"
|
||||
placement: checked.declaration.placement?.kind ?? "remote",
|
||||
source:
|
||||
checked.declaration.placement?.kind === "local"
|
||||
? JSON.stringify([query.packageRevisionId, checked.declaration.placement.sourceQueryId])
|
||||
? JSON.stringify([
|
||||
query.packageRevisionId,
|
||||
checked.treeProjection?.tree ?? checked.declaration.placement.sourceQueryId,
|
||||
])
|
||||
: null,
|
||||
root: checked.declaration.root,
|
||||
atoms: query.rootAtoms,
|
||||
@@ -105,6 +115,19 @@ export function queryProgramEntry(plan: ExecutionPlan, entry: import("./plan.js"
|
||||
operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]),
|
||||
};
|
||||
}),
|
||||
admittedOperations,
|
||||
admittedOperations: checked.tree
|
||||
? [
|
||||
...new Set([
|
||||
...admittedOperations,
|
||||
...plan.definition.queries
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.query.packageRevisionId === query.packageRevisionId &&
|
||||
entry.query.checked.treeProjection?.tree === checked.declaration.id,
|
||||
)
|
||||
.flatMap((entry) => entry.operations),
|
||||
]),
|
||||
].sort()
|
||||
: admittedOperations,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { CheckedQuery, QueryTreeNode } from "../query/types.js";
|
||||
import type { ValueType } from "../capability-model/types.js";
|
||||
import { treeProjectionQuery } from "../query/tree.js";
|
||||
import { tsType } from "../bindings/portable.js";
|
||||
|
||||
export function queryTreeBlueprint(packageId: string, query: CheckedQuery) {
|
||||
const tree = query.tree!;
|
||||
const projection = (id: string) =>
|
||||
JSON.stringify([packageId, treeProjectionQuery(query.declaration.id, id, "local")]);
|
||||
const admission = (id: string) =>
|
||||
JSON.stringify([packageId, treeProjectionQuery(query.declaration.id, id, "replica")]);
|
||||
return {
|
||||
root: tree.root,
|
||||
maxOccurrences: tree.maxOccurrences,
|
||||
nodes: tree.nodes.map((node) => ({
|
||||
...node,
|
||||
projection: projection(node.projection),
|
||||
admission: admission(node.projection),
|
||||
bindings: node.bindings.map((binding) => ({
|
||||
...binding,
|
||||
projection: projection(binding.projection),
|
||||
admission: admission(binding.projection),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/** Emit the shape of owned handles separately from graph result codecs. */
|
||||
export function queryTreeType(query: CheckedQuery): string {
|
||||
const tree = query.tree!;
|
||||
const nodes = new Map(tree.nodes.map((node) => [node.id, node]));
|
||||
const projections = new Map(tree.projections.map((projection) => [projection.id, projection]));
|
||||
const rootOutput = (id: string) => {
|
||||
const shape = projections.get(id)!.output;
|
||||
if (shape.kind !== "record" || !shape.fields.root) throw Error("Unchecked Query Tree projection");
|
||||
return shape.fields.root;
|
||||
};
|
||||
const shape = (id: string): string => {
|
||||
const node = nodes.get(id)!;
|
||||
return `QueryTreeShape<${tsType(rootOutput(node.projection))}, {${node.boundaries
|
||||
.map(
|
||||
(boundary) =>
|
||||
`readonly ${JSON.stringify(boundary.name)}${boundary.conditions.length ? "?" : ""}: ${shape(boundary.node)}`,
|
||||
)
|
||||
.join(";")}}, {readonly root: {${node.bindings
|
||||
.map(
|
||||
(binding) =>
|
||||
`readonly ${JSON.stringify(binding.name)}: ${bindingType(rootOutput(binding.projection), binding.targets, [binding.name])}`,
|
||||
)
|
||||
.join(";")}}}>`;
|
||||
};
|
||||
const bindingType = (
|
||||
type: ValueType,
|
||||
targets: QueryTreeNode["bindings"][number]["targets"],
|
||||
prefix: string[],
|
||||
): string => {
|
||||
const at = (type: ValueType, targets: QueryTreeNode["bindings"][number]["targets"]): string => {
|
||||
if (type.kind === "optional") return `(${at(type.value, targets)} | null)`;
|
||||
if (type.kind === "list")
|
||||
return `ReadonlyArray<${at(
|
||||
type.value,
|
||||
targets.map((target) => ({ ...target, path: target.path.slice(1) })),
|
||||
)}>`;
|
||||
const target = targets.find((target) => !target.path.length);
|
||||
if (target) {
|
||||
if (type.kind !== "record" || type.fields._camino?.kind !== "record" || !type.fields._camino.fields.ref)
|
||||
throw Error("Unchecked binding identity");
|
||||
return `QueryTreeOccurrence<${tsType(type.fields._camino.fields.ref)}, ${shape(target.node)}>`;
|
||||
}
|
||||
if (type.kind !== "record") return tsType(type);
|
||||
return `{${Object.entries(type.fields)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`readonly ${JSON.stringify(key)}: ${at(
|
||||
value,
|
||||
targets
|
||||
.filter((target) => target.path[0] === key)
|
||||
.map((target) => ({ ...target, path: target.path.slice(1) })),
|
||||
)}`,
|
||||
)
|
||||
.join(";")}}`;
|
||||
};
|
||||
let selected = type;
|
||||
for (const key of prefix) {
|
||||
if (selected.kind !== "record" || !selected.fields[key]) throw Error("Unchecked binding field");
|
||||
selected = selected.fields[key];
|
||||
}
|
||||
return at(
|
||||
selected,
|
||||
targets.map((target) => ({ ...target, path: target.path.slice(prefix.length) })),
|
||||
);
|
||||
};
|
||||
return shape(tree.root);
|
||||
}
|
||||
Reference in New Issue
Block a user