Files
quixos-protocol/src/query/link.ts
T
Timothy J. Aveni ef9a4e067a Expose workspace data to the agent and curate its authoring guides
Check named public GraphQL documents against the serving installation using the
existing compiler, linker and authorized snapshot interpreter. Keep listObjects
as a convenience over workspace-root queries, with no separate filter language.
Bound compilation/results, cache by content and serving plan, and fence cutovers.

Share graph discovery and reads between qx-workspace graph and the gardener's
workspace_graph tool. Local authoring shells discover the live endpoint through
the private control socket; configured public mounts also support direct clients.
Fix pending paginated inspections to wait for their confirmed window, and retain
semantic integration assertions instead of locking a server ticket's shape.

Replace the long prompt with a 384-word entry point and five focused guides.
Build immutable bundles with profiles, deep links, source hashes and word/link
checks. Keep the docs-maintenance process and feedback outside agent guidance.
Running agents use current guides while authored source and checker pins remain.

Validation: nix fmt; nix fmt -- --check; nix run .#lint; existing CLI checks;
protocol/client/coordinator build tests; complete Studio integration; 13 agent
unit tests. A real GPT-6.1 Sol medium/priority turn summarized two synthetic saved
comments and a document draft in 27.7s without source changes or cutover. Live
reads also succeeded after an ordinary check/activation in the retained fixture.
2026-10-01 13:15:24 -07:00

211 lines
9.0 KiB
TypeScript

import { createHash } from "node:crypto";
import type {
WorkspaceRevision,
Binding,
AtomId,
InterfaceRevisionId,
MemberId,
PersistentAttachment,
} from "../capability-model/types.js";
import { QueryCompileError, type CheckedQuery } from "./types.js";
import { queryRuntimePlan } from "./proto.js";
import { canonicalJson } from "../capability-model/evolution.js";
import { resolveForwardedBinding } from "../capability-model/forwarding.js";
export interface LinkedQueryField {
atomId: AtomId;
interfaceRevisionId: InterfaceRevisionId;
memberId: MemberId;
getter: string;
binding: Binding;
watch?: { start: string; stop: string; binding: Binding };
}
export interface LinkedQuery {
runtime?: import("@bufbuild/protobuf").JsonValue;
id: string;
packageRevisionId: string;
checked: CheckedQuery;
bindingDigest: string;
fields: LinkedQueryField[];
attachments: PersistentAttachment[];
rootAtoms: AtomId[];
}
/** Link every possible implementation, including atoms absent from today's data. */
export function linkQueries(
workspace: WorkspaceRevision,
additional: readonly {
packageRevisionId: string;
checked: CheckedQuery;
}[] = [],
): LinkedQuery[] {
const interfaces = new Map(workspace.interfaceImports.map((entry) => [entry.revisionId, entry]));
const attachments = [
...workspace.sharedAttachments,
...workspace.conformances.flatMap((entry) => entry.privateAttachments),
];
const linked: LinkedQuery[] = [];
for (const pkg of [
...workspace.packageImports,
...additional.map(({ packageRevisionId, checked }) => ({
revisionId: packageRevisionId,
displayName: "Public query",
queries: [checked.declaration],
checkedQueries: [checked],
})),
])
for (const declaration of pkg.queries ?? []) {
const checked = pkg.checkedQueries?.find((query) => query.declaration.id === declaration.id);
if (!checked || JSON.stringify(checked.declaration) !== JSON.stringify(declaration))
throw new QueryCompileError(
"QUERY_ARTIFACT_MISSING",
`${pkg.displayName}.${declaration.displayName} has no checked source artifact`,
);
const fields: LinkedQueryField[] = [];
const needed = new Set<string>();
for (const view of declaration.views)
if (
!workspace.conformances.some(
(entry) => entry.atomId === view.atomId && entry.interfaceRevisionId === view.interfaceRevisionId,
)
)
throw new QueryCompileError(
"QUERY_VIEW_REQUIRED",
`${view.atomId} does not conform to declared query view ${view.interfaceRevisionId}`,
);
for (const effect of checked.effects) {
const contract = interfaces.get(effect.interfaceRevisionId);
const member = contract?.members.find((entry) => entry.id === effect.memberId);
if (
!member ||
member.kind === "operation" ||
member.kind === "module" ||
member.queryRead?.execution !== effect.execution
)
throw new QueryCompileError(
"QUERY_STALE_CONTRACT",
`Query field ${effect.interfaceRevisionId}.${effect.memberId} changed`,
);
const getter = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve"));
if (!getter) throw new QueryCompileError("QUERY_CONTRACT", `Missing getter ${effect.memberId}`);
for (const conformance of workspace.conformances.filter(
(entry) => entry.interfaceRevisionId === effect.interfaceRevisionId,
)) {
const provider = conformance.operationBindings.find((entry) => entry.operationId === getter.id);
if (!provider)
throw new QueryCompileError("QUERY_CONTRACT", `Missing binding ${conformance.atomId}.${getter.id}`);
const binding = resolveForwardedBinding(
workspace,
conformance.atomId,
conformance.interfaceRevisionId,
getter.id,
).binding;
if (binding.kind === "package") {
if (effect.execution === "native")
throw new QueryCompileError(
"QUERY_NATIVE_BINDING_REQUIRED",
`Native query field ${effect.memberId} binds package code`,
);
if (effect.execution === "rpc-permitted" && !provider.queryReason?.trim())
throw new QueryCompileError(
"QUERY_RPC_PROVIDER_REASON",
`Package query field ${effect.memberId} needs query-reason`,
);
} else if (binding.kind === "state" && binding.primitive === "read") needed.add(binding.slotId);
else if (
(binding.kind === "journal" && binding.primitive === "head") ||
(binding.kind === "subscription" && binding.primitive === "status")
)
needed.add(binding.slotId);
else if (binding.kind === "edge" && binding.primitive === "resolve") needed.add(binding.edgeTypeId);
else
throw new QueryCompileError(
"QUERY_NATIVE_BINDING_REQUIRED",
`Unsupported query read binding ${effect.memberId}`,
);
const start = member.operations.find((op) => op.displayName === "watch-start");
const stop = member.operations.find((op) => op.displayName === "watch-stop");
const watch =
start && stop && conformance.operationBindings.some((entry) => entry.operationId === start.id)
? resolveForwardedBinding(workspace, conformance.atomId, conformance.interfaceRevisionId, start.id)
: undefined;
if (
binding.kind === "state" &&
watch &&
(watch.binding.kind !== "state" ||
watch.binding.slotId !== binding.slotId ||
watch.binding.primitive !== "watch-start")
)
throw new QueryCompileError(
"QUERY_WATCH_CONTRACT",
`Native query field ${effect.memberId} watch disagrees with its getter`,
);
if (
binding.kind === "edge" &&
watch &&
(watch.binding.kind !== "edge" ||
watch.binding.edgeTypeId !== binding.edgeTypeId ||
watch.binding.projectionId !== binding.projectionId)
)
throw new QueryCompileError(
"QUERY_WATCH_CONTRACT",
`Native query relation ${effect.memberId} watch disagrees with its resolver`,
);
fields.push({
atomId: conformance.atomId,
interfaceRevisionId: effect.interfaceRevisionId,
memberId: effect.memberId,
getter: getter.id,
binding,
...(watch && start && stop ? { watch: { start: start.id, stop: stop.id, binding: watch.binding } } : {}),
});
}
}
const storage = attachments.filter((entry) => needed.has(entry.id));
const rootConformances = workspace.conformances.filter(
(entry) => entry.interfaceRevisionId === declaration.root.interface,
);
const rootAtoms = [...new Set(rootConformances.map((entry) => entry.atomId))].sort();
const qualifiedInterfaces = new Set<string>();
const qualifications = (node: unknown): void => {
if (!node || typeof node !== "object") return;
if ("kind" in node && node.kind === "as" && "interfaceRevisionId" in node)
qualifiedInterfaces.add(String(node.interfaceRevisionId));
for (const child of Object.values(node)) qualifications(child);
};
qualifications(checked.selection);
const bindingDigest = createHash("sha256")
.update(
canonicalJson({
definition: checked.definitionDigest,
qualifiedConformances: workspace.conformances
.filter((entry) => qualifiedInterfaces.has(entry.interfaceRevisionId))
.map((entry) => ({ atomId: entry.atomId, id: entry.id, interfaceRevisionId: entry.interfaceRevisionId }))
.sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))),
rootConformances: rootConformances
.map((entry) => ({ atomId: entry.atomId, id: entry.id, interfaceRevisionId: entry.interfaceRevisionId }))
.sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))),
fields: [...fields].sort((a, b) => canonicalJson(a).localeCompare(canonicalJson(b))),
storage: [...storage].sort((a, b) => a.id.localeCompare(b.id)),
}),
)
.digest("hex");
linked.push({
id: `${pkg.revisionId}:${declaration.id}`,
packageRevisionId: pkg.revisionId,
checked,
bindingDigest,
fields,
attachments: storage,
rootAtoms,
});
}
for (const entry of linked) entry.runtime = queryRuntimePlan(entry, workspace);
if (new Set(linked.map((entry) => entry.id)).size !== linked.length)
throw new QueryCompileError(
"QUERY_ID_COLLISION",
"Query export identities collide; choose distinct package/query IDs",
);
return linked;
}