Compare commits

..
Author SHA1 Message Date
Quixos Subtree Publisher 27af7af6c5 Publish quixos-protocol from 473886a58fdacf15f2c873b77da96faf3be7fa65 2026-10-31 14:55:33 +00:00
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065 7545f7392d Preserve authorized cold-load browser refusals and private aggregate metadata usage (#641) 2026-10-31 14:50:06 +00:00
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065 94ddc8bedf Expose bounded post-build Source Check failures to authors (#641) 2026-10-31 14:48:20 +00:00
Quixos Subtree Publisher c56948ebd0 Publish quixos-protocol from e8df5e3396060619b33ab3cf9458231ef220ccc7 2026-10-31 14:19:06 +00:00
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065 6f5b5265b6 Exercise alias pin discovery and refresh in always-run Nix fixtures (#713) 2026-10-31 14:15:38 +00:00
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065 31d155fd88 Keep managed source paths canonical across outer workbench aliases (#713) 2026-10-31 14:09:51 +00:00
Quixos Subtree Publisher a025525f47 Publish quixos-protocol from 806b0430d952b5a9aab5b2a63496c272aced8e50 2026-10-29 14:36:45 +00:00
Timothy J. Aveni e13fde36fb Resolve managed resource aliases within the canonical workbench (#588) 2026-10-29 07:36:45 -07:00
Quixos Subtree Publisher d829d584af Publish quixos-protocol from ca5b05584b89e90cb259b4596b1bc01d3d692136 2026-10-25 21:28:36 +00:00
Quixos Development Agent 5ffdc2ce-2936-4c70-8208-3a9da53f4aa9 ee84025322 Use explicit navigation projections for React presentation readiness (#339) 2026-10-24 06:39:33 +00:00
Timothy J. Aveni bc9a7ef2cf Integrate Kubernetes compute, scoped Agent placement and observable releases (#195) 2026-10-18 19:52:45 -07:00
Quixos Subtree Publisher badad181de Publish quixos-protocol from 6b91014ff1d6c9c54d0782f586935e694f9ee72f 2026-10-13 12:37:58 +00:00
Timothy J. Aveni 85163b6e67 Reduce repeated compiler startup in monorepo QX formatting 2026-10-13 05:37:58 -07:00
Quixos Subtree Publisher e8373a4cd1 Publish quixos-protocol from e4393946ba63cfef75580f6aa6ba840035e7d43c 2026-10-13 10:19:49 +00:00
Timothy J. Aveni e92b08025c Avoid rehashing validated Graph execution metadata during Candidate loading 2026-10-13 02:58:29 -07:00
Timothy J. Aveni 62a09605cf Reuse verified immutable plans and streamline cooperative hashing
Continue final hosted development reliability qualification.
2026-10-12 20:16:56 -07:00
Timothy J. Aveni 47b316180d Keep checked execution groups and public catalog preparation cooperative 2026-10-12 19:21:34 -07:00
Timothy J. Aveni a4ba6169a6 Reuse checked reflection inputs before acquiring controller ownership 2026-10-12 18:30:07 -07:00
Timothy J. Aveni a48fccc328 Keep cold Candidate verification cooperative with controller renewals 2026-10-12 18:24:38 -07:00
Timothy J. Aveni 755c008a88 Repair concurrent Graph recovery, Source Job residency and developer source provisioning 2026-10-12 15:39:34 -07:00
Timothy J. Aveni bbf9056dfa Unify scoped development with ordinary Workspace authoring commands 2026-10-12 13:09:47 -07:00
Timothy J. Aveni ecf97173b4 Retain private platform source while resolving its context-free Nix flake reference 2026-10-11 16:41:18 -07:00
Timothy J. Aveni 0f41b12ca0 Supply exact private platform source to sealed checks without private evaluator egress 2026-10-11 16:07:52 -07:00
Timothy J. Aveni fa698dd306 Protect active development Graphs from rejected launcher cleanup 2026-10-11 03:42:32 -07:00
Quixos Subtree Publisher b50a7f080c Publish quixos-protocol from 8014b4ba717313e1b494c59a8e2781df58bf9a85 2026-10-11 05:28:35 +00:00
Timothy J. Aveni 2139f138ca Write Agent bootstrap plans to files instead of bounded command output 2026-10-10 22:28:35 -07:00
Timothy J. Aveni 8fe374fe25 Run canonical application source in independent development Graphs
Initialize camino-dev from an exact canonical source pin with native Git/jj history, compiler-bound Workspace identity, and retained instructions/toolchain. Share the strict pin decoder with source acquisition; retain monorepo fixture behavior separately.

Qualified the maintained real Git/jj/compiler regression, all 57 Reader repositories, packaged launcher startup/session, retained-Graph restart and Chromium Text/settings opening without page errors. Lint and documentation checks pass. Hosted source lifecycle qualification and maintained-copy removal remain pending.
2026-10-10 19:37:35 -07:00
Timothy J. Aveni ad2a851b94 Consolidate Reader source ownership and pinned application instances
Add exact recursive source checkout, independent Git/jj developer workbenches and source-only backup/recovery commands, with Workspace instance binding outside source. Qualify two empty Reader Graphs, packaged source commands, Agent VM compilation, exact Gitea read grants and reconciled Reader settings/annotation stability. Publish checked Reader Package/root revisions to canonical review branches and retention tags, retain all 57 repositories independently in private versioned storage, verify downloaded-backup recovery and advance the example pin.

Add bounded Central pin acquisition and selection-specific read-only identities, qualified against all 57 canonical repositories without write authority. Replace regex fork dependency discovery with compiler-parsed lock imports and rewrite proposals; preserve source identities and unrelated files, passing instance binding through packed and ordinary host checks.

Hosted run-pin versus authoring preparation and maintained duplicate removal remain pending. Live source selections, Template defaults and default source branches are unchanged.
2026-10-10 18:38:57 -07:00
Quixos Subtree Publisher 721b730263 Publish quixos-protocol from a61bdda658be4878403b8d60aecb71fe5a614ac0 2026-10-10 10:49:05 +00:00
Timothy J. Aveni c2c01a9878 Implement progressive query admission, session boundaries and event-driven Studio observations 2026-10-10 03:49:05 -07:00
Quixos Subtree Publisher 9fae801b7f Publish quixos-protocol from 286ada5419c4fc2184b387cea4715fae1a74bde5 2026-10-10 00:10:14 +00:00
Timothy J. Aveni 2ae7f00acb Reuse checked bootstrap catalogs and coordinate execution deadlines 2026-10-09 17:10:14 -07:00
Timothy J. Aveni 041a9e26ad Document package architecture and organize current guidance and project backlog 2026-10-09 10:33:39 -07:00
Quixos Subtree Publisher 7f3fdfe64e Publish quixos-protocol from d1ab9c912133511cfd3de65550965e0e8806678f 2026-10-08 19:32:06 +00:00
Timothy J. Aveni 0a7bdf7657 Qualify restricted SDK syntax independently of build architecture 2026-10-08 12:32:06 -07:00
Quixos Subtree Publisher 137dcaa101 Publish quixos-protocol from 2ef07e2524ed167421be56b81a5f498a2759ca5d 2026-10-08 19:30:33 +00:00
Timothy J. Aveni a86de3fe5b Qualify restricted SDK syntax independently of build architecture 2026-10-08 12:30:33 -07:00
Timothy J. Aveni 165510341d Repair packed agent startup and bound activation metadata retention 2026-10-08 11:19:19 -07:00
Quixos Subtree Publisher b406738249 Publish quixos-protocol from 11d2ad06170609a09118cbabd4245b5828a148d1 2026-10-08 18:19:19 +00:00
Timothy J. Aveni ffd728b061 Keep pin-upgraded workbench paths relative for relocation 2026-10-08 07:59:49 -07:00
Quixos Subtree Publisher e78b7adf28 Publish quixos-protocol from c4319441699b7b8c5af7e70dade6e4ad8cccddea 2026-10-08 14:59:49 +00:00
Timothy J. Aveni a935273913 Use execution workspace review semantics during bottom-up pin upgrades 2026-10-08 07:43:32 -07:00
Quixos Subtree Publisher e162dc3013 Publish quixos-protocol from e4075003e669be0e307ec8c0585cccb1bd08276a 2026-10-08 14:43:32 +00:00
Quixos Subtree Publisher e65ddad798 Publish quixos-protocol from 932fe7714761fb8b5db4bd6401357cdff762a428 2026-10-08 05:08:28 +00:00
Timothy J. Aveni 31a48e746d Admit checked restricted artifacts without individual code approvals 2026-10-07 21:43:44 -07:00
63 changed files with 3116 additions and 418 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "abc91ab1cbbd61ffd7f0a49dbc5768598ac8745c",
"sourceRepo": "https://gitea.internal.quixos.org/quixos/quixos.git",
"sourceCommit": "473886a58fdacf15f2c873b77da96faf3be7fa65",
"sourcePath": "quixos-protocol",
"exportName": "quixos-protocol",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
"mirrorRemote": "https://repos.quixos.org/quixos-toolchain/quixos-protocol.git"
}
+42 -9
View File
@@ -6,6 +6,8 @@
kind,
generator,
contractOnly ? false,
workspaceId ? null,
platformSourceJSON ? null,
system ? builtins.currentSystem,
}:
let
@@ -70,9 +72,21 @@ let
else
{ };
execution = (configuration.backend or "typescript") == "execution";
runtime = builtins.getFlake (
"git+${root.lock.quixos.repository}?rev=${root.lock.quixos.commit}&ref=refs/tags/quixos-reachability/${root.lock.quixos.commit}"
);
platformSource = if platformSourceJSON == null then null else builtins.fromJSON platformSourceJSON;
runtime =
if platformSource == null then
builtins.getFlake (
"git+${root.lock.quixos.repository}?rev=${root.lock.quixos.commit}&ref=refs/tags/quixos-reachability/${root.lock.quixos.commit}"
)
else
assert platformSource.repository == root.lock.quixos.repository;
assert platformSource.commit == root.lock.quixos.commit;
# getFlake accepts a context-free flake reference. storePath first checks
# presence; the resulting flake retains its immutable source as an input.
# Dropping string context here does not permit an authored mutable path.
builtins.getFlake (
builtins.unsafeDiscardStringContext "path:${builtins.storePath platformSource.directory}"
);
runtimePackages = runtime.packages.${system};
resourceClosure =
includeBuild: node:
@@ -164,6 +178,11 @@ let
${queryGeneratorFlags root} \
--checked-resources ${checkedResources node} \
--source-root-commit ${pkgs.lib.escapeShellArg node.commit} \
${
pkgs.lib.optionalString (
workspaceId != null
) "--workspace-id ${pkgs.lib.escapeShellArg workspaceId}"
} \
--checkout-root "$TMPDIR/checkouts" --snapshot-map ${snapshots node} \
--graph-out "$out/graph.json" --schemas-out "$out/package-schemas.json" \
${
@@ -357,6 +376,13 @@ let
)
);
in
assert
workspaceId == null
|| (
kind == "workspace"
&& builtins.isString workspaceId
&& builtins.match "[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}" workspaceId != null
);
pkgs.runCommand (if contractOnly then "qx-contract" else "qx-checked-candidate") { } ''
mkdir -p "$out"
cp ${
@@ -381,12 +407,19 @@ pkgs.runCommand (if contractOnly then "qx-contract" else "qx-checked-candidate")
${pkgs.nodejs_24}/bin/node ${./check-execution.mjs} \
${protocol}/libexec/quixos-protocol/execution-world.mjs \
"$out/execution-plan.json" "$out/checks.json" "$out"
mkdir -p "$out/bin"
cat > "$out/bin/run-isolated" <<EOF
#!${pkgs.runtimeShell}
exec ${runtimePackages.camino-isolated-workspace}/bin/camino-isolated-workspace "$out" "\$@"
EOF
chmod +x "$out/bin/run-isolated"
# The optional development host is acquired only when explicitly run.
# Embedding its executable here made every source check build Web Studio
# and a full host even when only checked Package outputs were consumed.
cp ${
pkgs.writeText "qx-isolated-runtime.json" (
builtins.toJSON {
codec = "quixos-isolated-runtime/1";
source = {
inherit (root.lock.quixos) repository commit;
};
}
)
} "$out/isolated-runtime.json"
''}
''}
${pkgs.lib.optionalString (
+27 -22
View File
@@ -41,15 +41,19 @@ export function presentationQueries(pkg: PackageRevision, interfaces: readonly I
];
});
const stem = `PresentationNavigation${createHash("sha256").update(id).digest("hex").slice(0, 12)}`;
const inputs = `presentation:navigation:${id}:inputs`;
for (const local of [false, true]) {
const name = `${stem}${local ? "View" : "Inputs"}`;
const document = `generated/${name}.graphql`;
documents.set(
document,
`query ${name}($presentationMount: QxRef_PresentationMount!) {
// Retain full state for operations and hidden occurrences, independently
// from the identity projection used to prepare a rendering occurrence.
for (const identity of [false, true]) {
const prefix = `presentation:navigation:${id}:${identity ? "identity-" : ""}`;
const inputs = prefix + "inputs";
for (const local of [false, true]) {
const name = `${stem}${identity ? "Identity" : ""}${local ? "View" : "Inputs"}`;
const document = `generated/${name}.graphql`;
documents.set(
document,
`query ${name}($presentationMount: QxRef_PresentationMount!) {
root { ${local ? "availableObjects" : "objects"}(all:512, where:{_camino:{as:{PresentationState:{presentationMount:{eq:$presentationMount}}}}}) {
entries { node { ${fields.join("\n")}
entries { node { ${identity ? "" : fields.join("\n")}
... on PresentationState {
_presentationOccurrence: occurrence
_presentationSource: source {_camino {ref}}
@@ -59,20 +63,21 @@ export function presentationQueries(pkg: PackageRevision, interfaces: readonly I
} }
} }
}`,
);
generated.push({
id: local ? `presentation:navigation:${id}:view` : inputs,
displayName: name,
root: { kind: "workspace-interface", interface: contract.revisionId },
document,
operation: name,
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets, rows: 5000, candidates: 5000, resultBytes: 8388608 },
watch: local,
placement: local ? { kind: "local", sourceQueryId: inputs } : { kind: "replica" },
});
);
generated.push({
id: local ? prefix + "view" : inputs,
displayName: name,
root: { kind: "workspace-interface", interface: contract.revisionId },
document,
operation: name,
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets, rows: 5000, candidates: 5000, resultBytes: 8388608 },
watch: local,
placement: local ? { kind: "local", sourceQueryId: inputs } : { kind: "replica" },
});
}
}
}
for (const query of generated)
+8
View File
@@ -3,6 +3,7 @@ import path from "node:path";
import { profile } from "./profile.js";
import { loadQxSources, resolveQxSources } from "./source-loader.js";
import { compileQuery, compileRepositoryQuery } from "../query/compile.js";
import { queryTreeProjections } from "../query/tree.js";
import type { QueryDeclaration } from "../query/types.js";
import { compileQueryComposition, type QueryComposition } from "../query/composition.js";
import { checkQueryTemplate } from "../query/templates.js";
@@ -390,6 +391,13 @@ const createResourceGraphResolver = (
};
pkg.checkedQueries = [];
for (const query of pkg.queries!) pkg.checkedQueries.push(await compile(query.id));
const projections = pkg.checkedQueries.flatMap(queryTreeProjections);
for (const projection of projections) {
if (pkg.queries!.some((query) => query.id === projection.declaration.id))
throw new Error(`Generated Query Tree projection conflicts with ${projection.declaration.id}`);
pkg.queries!.push(projection.declaration);
pkg.checkedQueries.push(projection);
}
}
if (generated?.compositions?.length) {
const pkg = compiled.resource.revision;
@@ -97,6 +97,7 @@ export async function checkAuthoring(
(phase, ms) => {
timings[phase] = ms;
},
resource.kind === "workspace" ? context.workspaceId : undefined,
);
} finally {
timings.immutableCheckMs = Math.round(performance.now() - buildStarted);
+65 -16
View File
@@ -20,38 +20,87 @@ export async function authoringContext(start: string) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
if (text !== undefined) {
const graph = JSON.parse(text) as { resources: AuthoringResource[] };
const graph = JSON.parse(text) as { resources: AuthoringResource[]; workspaceId?: string };
if (!graph || typeof graph !== "object" || Array.isArray(graph))
throw new Error("Managed resource inventory is malformed");
if (
graph.workspaceId !== undefined &&
(typeof graph.workspaceId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}$/.test(graph.workspaceId))
)
throw new Error("Managed workbench has an invalid Workspace identity");
if (!Array.isArray(graph.resources)) throw new Error("Managed resource inventory is malformed");
const resources: AuthoringResource[] = [{ kind: "workspace", directory: "root" }];
const identities = new Set<string>(),
directories = new Set<string>(["root"]);
for (const entry of graph.resources) {
// Compiler graphs carry resolved paths; the authoring API presents
// stable workbench-relative names and validates containment here.
if (typeof entry.directory === "string" && path.isAbsolute(entry.directory))
entry.directory = path.relative(workbench, entry.directory);
for (const [index, entry] of graph.resources.entries()) {
const invalid = (reason: string): never => {
throw new Error(`Invalid managed resource registration at entry ${index + 1}: ${reason}`);
};
if (
!["interface", "package", "build"].includes(entry.kind) ||
!entry.source ||
!/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.directory)
) {
throw new Error("Invalid managed resource registration");
!entry ||
typeof entry !== "object" ||
Array.isArray(entry) ||
!["interface", "package", "build"].includes(entry.kind)
)
invalid("unsupported resource kind");
if (
entry.resourceId !== undefined &&
(typeof entry.resourceId !== "string" ||
entry.resourceId.length < 1 ||
entry.resourceId.length > 256 ||
entry.resourceId.includes("\0"))
)
invalid("resource identity must be a bounded nonempty identifier");
const source = entry.source;
if (
!source ||
typeof source !== "object" ||
Array.isArray(source) ||
source.resolver !== "git" ||
typeof source.repository !== "string" ||
!source.repository ||
source.repository.length > 4096 ||
source.repository.includes("\0") ||
typeof source.commit !== "string" ||
!/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(source.commit)
)
invalid("expected an immutable Git source");
// Git transport policy belongs to the source resolver, including its
// maintained local/file fixtures; registration validates only its shape.
if (
typeof entry.directory !== "string" ||
!entry.directory ||
entry.directory.length > 4096 ||
entry.directory.includes("\0")
)
invalid("directory must be a bounded path");
// Bootstrap intentionally records stable aliases for native histories.
// Compare physical paths on BOTH sides, then expose the same canonical
// workbench-relative selection for alias and physical working dirs.
let directory: string;
try {
directory = path.relative(workbench, await realpath(path.resolve(workbench, entry.directory)));
} catch {
invalid("registered directory is unavailable");
}
const identity = `${entry.kind}\0${entry.resourceId ?? entry.source.repository}`;
if (identities.has(identity) || directories.has(entry.directory)) {
throw new Error(`Multiple editable selections for ${entry.resourceId ?? entry.source.repository}`);
if (!/^resources\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(directory!))
invalid("registered directory must resolve beneath workbench resources");
const identity = `${entry.kind}\0${entry.resourceId ?? source!.repository}`;
if (identities.has(identity) || directories.has(directory!)) {
throw new Error(`Multiple editable selections for ${directory!}`);
}
identities.add(identity);
directories.add(entry.directory);
directories.add(directory!);
resources.push({
kind: entry.kind,
directory: entry.directory,
directory: directory!,
resourceId: entry.resourceId,
source: entry.source,
});
}
return {
workbench,
workspaceId: graph.workspaceId,
resources,
async baseline() {
const result = await loadQuixosLock(path.join(workbench, "root/quixos.lock"));
@@ -4,6 +4,7 @@ import { execFile as callback } from "node:child_process";
import { promisify } from "node:util";
import { randomUUID } from "node:crypto";
import { authoringContext } from "./authoring-context.js";
import { managedResourcePath } from "./managed-source-path.js";
import { snapshotCommit } from "./checked-build.js";
import { profile } from "./profile.js";
import {
@@ -85,6 +86,11 @@ export async function convergeAuthoring(start: string, target = "root") {
const context = await authoringContext(start);
const graphFile = path.join(context.workbench, ".quixos/resource-graph.json");
const graphBefore = await readFile(graphFile, "utf8");
const graph = JSON.parse(graphBefore);
// Discovery resolves outer guest aliases. Persist those same physical
// selections, without accepting symlinks within the managed workbench.
for (const resource of graph.resources)
resource.directory = await managedResourcePath(context.workbench, resource.directory);
const blockers: AuthoringBlocker[] = [];
const nodes = new Map<string, { directory: string; kind: string; source: GitSource; dependencies: string[] }>();
const selected = new Map<string, string>();
@@ -247,9 +253,6 @@ export async function convergeAuthoring(start: string, target = "root") {
profile("capture-recheck", recheckStarted, { repositories: complete.size });
// Persist successful selections even if another repository is still broken.
// Recovery must not depend on all parents succeeding in the same invocation.
const graph = JSON.parse(graphBefore);
for (const resource of graph.resources)
resource.directory = path.relative(context.workbench, path.resolve(context.workbench, resource.directory));
const replacements = new Map<string, string>();
for (const resource of graph.resources) {
const source = complete.get(resource.directory);
+13
View File
@@ -112,12 +112,23 @@ export async function buildImmutableCandidate(
logFile: string,
contractOnly = false,
observe?: (phase: "nixEvaluationMs" | "nixBuildMs", ms: number) => void,
workspaceId?: string,
platformSource?: { repository: string; commit: string; directory: string },
): Promise<string> {
if (workspaceId !== undefined && (kind !== "workspace" || !/^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,255}$/.test(workspaceId)))
throw new Error("Instance binding requires a valid Workspace identity and a Workspace root");
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(source.commit))
throw new Error("An immutable candidate requires an exact commit");
const url = new URL(source.repository);
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
throw new Error("Candidate origin must be credential-free HTTPS");
if (
platformSource &&
(Object.keys(platformSource).sort().join(",") !== "commit,directory,repository" ||
!/^[a-f0-9]{40}$/.test(platformSource.commit) ||
!/^\/nix\/store\/[a-z0-9]{32}-[^/\s]+$/.test(platformSource.directory))
)
throw Error("Invalid immutable platform source input");
const generator =
process.env.QUIXOS_CHECK_GENERATOR ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
if (!/^\/nix\/store\/[^/]+$/.test(generator)) throw new Error("Use the installed Quixos checker");
@@ -200,6 +211,8 @@ export async function buildImmutableCandidate(
"--arg",
"contractOnly",
contractOnly ? "true" : "false",
...(workspaceId === undefined ? [] : ["--argstr", "workspaceId", workspaceId]),
...(platformSource ? ["--argstr", "platformSourceJSON", JSON.stringify(platformSource)] : []),
"--add-root",
gcRoot,
"--indirect",
@@ -0,0 +1,97 @@
import { execFile as callback } from "node:child_process";
import { promisify } from "node:util";
import { randomUUID } from "node:crypto";
import { mkdir, mkdtemp, lstat, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { materializeWorkspaceSources, workspaceSourcePin, type WorkspaceSourcePin } from "./source-closure.js";
const exec = promisify(callback);
/** Developer workflow, not an ordinary Workspace Agent's implicit permission
* to publish against canonical repositories. Creates independent Git/jj copies,
* with instance binding outside committed source. No Graph is started here.
*/
export async function prepareDevelopmentSources(options: {
source: WorkspaceSourcePin;
directory: string;
cacheDirectory: string;
locations?: Record<string, string>;
workspaceId?: string;
}) {
const source = workspaceSourcePin(options.source);
if (options.workspaceId !== undefined && !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(options.workspaceId))
throw Error("Development source binding requires a Workspace UUID");
const destination = path.resolve(options.directory);
const cache = path.resolve(options.cacheDirectory);
if (cache === destination || cache.startsWith(destination + path.sep))
throw Error("The retained source cache must live outside the disposable workbench");
if (
await lstat(destination).catch((error: NodeJS.ErrnoException) => {
if (error.code !== "ENOENT") throw error;
return null;
})
)
throw Error("Choose a new development workbench directory; existing checkouts are never overwritten");
const closure = await materializeWorkspaceSources({
source,
directory: options.cacheDirectory,
locations: options.locations,
});
const origins = new Set<string>();
for (const entry of closure.resources) {
const identity = `${entry.kind}:${entry.repository}`;
if (origins.has(identity)) throw Error("Multiple revisions of one repository require explicit editable selection");
origins.add(identity);
}
const parent = path.dirname(destination);
await mkdir(parent, { recursive: true });
const staging = await mkdtemp(path.join(parent, ".development-sources-"));
// This local association does not create or grant access to a hosted Graph.
const workspaceId = options.workspaceId ?? randomUUID();
try {
const resources = [];
for (const entry of closure.resources) {
const relative = entry.kind === "workspace" ? "root" : "resources/" + entry.directory;
const checkout = path.join(staging, relative);
await mkdir(path.dirname(checkout), { recursive: true });
await exec("git", [
"clone",
"--no-hardlinks",
"--",
path.join(path.resolve(options.cacheDirectory), entry.directory),
checkout,
]);
await exec("git", ["remote", "set-url", "origin", entry.repository], { cwd: checkout });
await exec("git", ["checkout", "--detach", entry.commit], { cwd: checkout });
await exec("jj", ["git", "init", "--colocate"], {
cwd: checkout,
env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_SUBTREE_PUBLISH: "0" },
});
await exec("jj", ["describe", "-m", "Develop canonical application source"], {
cwd: checkout,
env: { ...process.env, QUIXOS_JJ_NO_CHECKPOINT: "1", QUIXOS_SUBTREE_PUBLISH: "0" },
});
if (entry.kind !== "workspace")
resources.push({
kind: entry.kind,
directory: relative,
source: { resolver: "git", repository: entry.repository, commit: entry.commit },
});
}
await mkdir(path.join(staging, ".quixos"), { mode: 0o700 });
await writeFile(
path.join(staging, ".quixos/resource-graph.json"),
JSON.stringify({ formatVersion: 1, workspaceId, resources }, null, 2) + "\n",
{ mode: 0o600 },
);
await writeFile(
path.join(staging, ".quixos/development-source.json"),
JSON.stringify({ source, workspaceId }, null, 2) + "\n",
{ mode: 0o600 },
);
await rename(staging, destination);
return { directory: destination, source, workspaceId, repositories: closure.resources.length };
} finally {
await rm(staging, { recursive: true, force: true });
}
}
+18 -11
View File
@@ -1,14 +1,19 @@
import { spawn } from "node:child_process";
export class FileLockBusyError extends Error {}
/** Kernel-owned lock: a crashed coordinator cannot leave a stale ownership file.
* The persistent file is just an inode; EOF releases the helper's lock. */
export async function withFileLock<T>(filename: string, work: () => Promise<T>): Promise<T> {
export async function withFileLock<T>(
filename: string,
work: () => Promise<T>,
{ nonblocking = false }: { nonblocking?: boolean } = {},
): Promise<T> {
const child = spawn(
"flock",
[
"--exclusive",
"--timeout",
"120",
...(nonblocking ? ["--nonblock"] : ["--timeout", "120"]),
"--conflict-exit-code",
"75",
filename,
@@ -32,15 +37,17 @@ export async function withFileLock<T>(filename: string, work: () => Promise<T>):
await new Promise<void>((resolve, reject) => {
let output = "";
child.once("error", reject);
child.once("exit", (code) =>
child.once("exit", (code) => {
reject(
new Error(
code === 75
? "Timed out after 120 seconds waiting for another authoring command; inspect that command before retrying"
: `Cannot acquire authoring lock: ${diagnostics}`,
),
),
);
code === 75
? new FileLockBusyError(
nonblocking
? "Another process owns this lock"
: "Timed out after 120 seconds waiting for another authoring command; inspect that command before retrying",
)
: new Error(`Cannot acquire authoring lock: ${diagnostics}`),
);
});
child.stdout.on("data", (chunk) => {
output += chunk;
if (output.includes("locked\n")) resolve();
@@ -0,0 +1,19 @@
import { realpath } from "node:fs/promises";
import path from "node:path";
/** Registration may name the workbench through its guest alias. Only that
* outer alias is permitted: resources and each checkout remain physical,
* directly contained directories. Persist the resulting root-relative path.
*/
export async function managedResourcePath(workbench: string, directory: string): Promise<string> {
const root = await realpath(workbench);
const selected = path.resolve(root, directory);
const name = path.basename(selected);
const resources = path.dirname(selected);
if (path.basename(resources) !== "resources" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name))
throw new Error(`Repository is outside the managed workbench: ${directory}`);
const relative = `resources/${name}`;
if ((await realpath(path.dirname(resources))) !== root || (await realpath(selected)) !== path.join(root, relative))
throw new Error(`Managed repository path crosses a symlink or escapes the workbench: ${directory}`);
return relative;
}
+58 -17
View File
@@ -15,8 +15,14 @@ import {
} from "./assembly.js";
import { createGitCapabilityResolver } from "./git-resolver.js";
import { snapshotCommit, buildImmutableCandidate } from "./checked-build.js";
import { planEvolution, type WorkspaceRevision, type EvolutionReview } from "../capability-model/index.js";
import {
planEvolution,
reviewSemanticMajors,
type WorkspaceRevision,
type EvolutionReview,
} from "../capability-model/index.js";
import { withFileLock } from "./file-lock.js";
import { managedResourcePath } from "./managed-source-path.js";
const execFile = promisify(callback);
type Source = { repository: string; commit: string };
export type UpgradeNode = { kind: "workspace" | "package" | "interface" | "build"; directory: string; source: Source };
@@ -93,7 +99,8 @@ const writeJournal = async (filename: string, journal: unknown) => {
}
};
export const discoverUpgradeSpec = async (workbench: string): Promise<UpgradeSpec> => {
export const discoverUpgradeSpec = async (workbenchPath: string): Promise<UpgradeSpec> => {
const workbench = await fs.realpath(workbenchPath);
const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"));
const root = await location(workbench, "root");
const nodes: UpgradeNode[] = [
@@ -105,11 +112,15 @@ export const discoverUpgradeSpec = async (workbench: string): Promise<UpgradeSpe
commit: await command(root, "jj", ["--ignore-working-copy", "log", "--no-graph", "-r", "@", "-T", "commit_id"]),
},
},
...graph.resources.map((entry: { kind: "interface" | "package" | "build"; directory: string; source: Source }) => ({
kind: entry.kind,
source: entry.source,
directory: path.relative(workbench, path.resolve(workbench, entry.directory)),
})),
...(await Promise.all(
graph.resources.map(
async (entry: { kind: "interface" | "package" | "build"; directory: string; source: Source }) => ({
kind: entry.kind,
source: entry.source,
directory: await managedResourcePath(workbench, entry.directory),
}),
),
)),
];
let baseline: string | undefined;
try {
@@ -187,6 +198,34 @@ export type UpgradeEffects = {
snapshot(root: string): Promise<string>;
publish(root: string, commit: string): Promise<void>;
};
/** Review a successfully built immutable candidate using the same boundary as
* ordinary authoring checks. Execution storage compatibility is checked against
* the current graph at activation; the legacy evolution model is not its schema.
*/
export async function reviewUpgradeCandidate({
artifact,
baseline,
candidate,
reviews = [],
}: {
artifact: string;
baseline: WorkspaceRevision | null;
candidate: WorkspaceRevision;
reviews?: EvolutionReview[];
}) {
let execution = false;
try {
await fs.access(path.join(artifact, "execution-plan.json"));
execution = true;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
return execution
? { filename: "semantic-review.json", review: reviewSemanticMajors(baseline, candidate, reviews) }
: { filename: "evolution.json", review: planEvolution(baseline, candidate, { reviews }) };
}
const effects: UpgradeEffects = {
async check(node, root, output, spec) {
if (node.kind === "workspace" && !spec.baseline && !spec.bootstrap)
@@ -206,10 +245,15 @@ const effects: UpgradeEffects = {
? (JSON.parse(await fs.readFile(spec.baseline, "utf8")) as WorkspaceRevision)
: null;
const reviews = spec.reviews ? (JSON.parse(await fs.readFile(spec.reviews, "utf8")) as EvolutionReview[]) : [];
const evolution = planEvolution(baseline, JSON.parse(candidate), { reviews });
await fs.writeFile(path.join(output, "evolution.json"), JSON.stringify(evolution, null, 2));
if (evolution.blockers.length)
throw new Error(`Refactor required in ${node.directory}: ${evolution.blockers.join("; ")}`);
const { filename, review } = await reviewUpgradeCandidate({
artifact,
baseline,
candidate: JSON.parse(candidate),
reviews,
});
await fs.writeFile(path.join(output, filename), JSON.stringify(review, null, 2));
if (review.blockers.length)
throw new Error(`Refactor required in ${node.directory}: ${review.blockers.join("; ")}`);
}
},
async snapshot(root) {
@@ -368,10 +412,7 @@ export const applyPinUpgrades = async (
previous.resources.map(async (entry: { kind: string; source: Source; directory: string }) => ({
kind: entry.kind,
...entry.source,
directory: await location(
plan.workbench,
path.relative(plan.workbench, path.resolve(plan.workbench, entry.directory)),
),
directory: await location(plan.workbench, await managedResourcePath(plan.workbench, entry.directory)),
})),
);
for (const node of plan.nodes.filter((entry) => entry.kind !== "workspace")) {
@@ -443,13 +484,13 @@ export const applyPinUpgrades = async (
quixos: compiled.lock.quixos,
directResources: [...compiled.directResources.entries()].map(([bindingKey, node]) => {
const [kind, binding] = bindingKey.split("\0");
return { kind, binding, resourceKey: node.key, directory: node.directory };
return { kind, binding, resourceKey: node.key, directory: path.relative(plan.workbench, node.directory) };
}),
resources: [...resources.values()].map((node) => ({
key: node.key,
kind: node.kind,
source: node.source,
directory: node.directory,
directory: path.relative(plan.workbench, node.directory),
...(node.resource.kind === "build"
? {}
: {
+117
View File
@@ -0,0 +1,117 @@
import { execFile as callback } from "node:child_process";
import { promisify } from "node:util";
import { createHash } from "node:crypto";
import { createReadStream } from "node:fs";
import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { retentionTagForCommit } from "../resource-lock/index.js";
import { materializeWorkspaceSources, workspaceSourcePin, type WorkspaceSourcePin } from "./source-closure.js";
const exec = promisify(callback);
type SourceBackup = {
formatVersion: 1;
root: WorkspaceSourcePin;
repositories: Array<{ repository: string; commits: string[]; bundle: string; sha256: string }>;
};
async function hash(filename: string) {
const digest = createHash("sha256");
for await (const chunk of createReadStream(filename)) digest.update(chunk);
return digest.digest("hex");
}
/** Source-only backup: no Graph state, checkout instructions or credentials.
* Git bundles retain exact commits and their ancestors without needing a forge.
* Storage/retention of the resulting directory belongs to the operator. */
export async function backupWorkspaceSources(options: {
source: WorkspaceSourcePin;
directory: string;
output: string;
locations?: Record<string, string>;
}): Promise<SourceBackup> {
const output = path.resolve(options.output);
if (
await lstat(output).catch((error: NodeJS.ErrnoException) => {
if (error.code !== "ENOENT") throw error;
return null;
})
)
throw Error("Source backup destination must not exist");
const closure = await materializeWorkspaceSources(options);
const grouped = new Map<string, Map<string, string>>();
for (const entry of closure.resources) {
const commits = grouped.get(entry.repository) ?? new Map<string, string>();
commits.set(entry.commit, path.join(path.resolve(options.directory), entry.directory));
grouped.set(entry.repository, commits);
}
await mkdir(path.dirname(output), { recursive: true });
const staging = await mkdtemp(path.join(path.dirname(output), ".source-backup-"));
const manifest: SourceBackup = { formatVersion: 1, root: closure.root, repositories: [] };
try {
for (const [repository, commits] of [...grouped].sort(([a], [b]) => a.localeCompare(b))) {
const bundle = createHash("sha256").update(repository).digest("hex") + ".bundle";
const gitDirectory = await mkdtemp(path.join(staging, ".git-"));
try {
await exec("git", ["init", "--bare", gitDirectory]);
const refs: string[] = [];
for (const [commit, checkout] of commits) {
const ref = retentionTagForCommit(commit);
await exec("git", ["-C", gitDirectory, "fetch", "--no-tags", "--", checkout, `${ref}:${ref}`]);
refs.push(ref);
}
await exec("git", ["-C", gitDirectory, "bundle", "create", path.join(staging, bundle), ...refs]);
} finally {
await rm(gitDirectory, { recursive: true, force: true });
}
manifest.repositories.push({
repository,
commits: [...commits.keys()].sort(),
bundle,
sha256: await hash(path.join(staging, bundle)),
});
}
await writeFile(path.join(staging, "backup.json"), JSON.stringify(manifest, null, 2) + "\n", { mode: 0o600 });
await rename(staging, output);
return manifest;
} finally {
await rm(staging, { recursive: true, force: true });
}
}
export async function restoreWorkspaceSources(options: { backup: string; directory: string }) {
const backup = path.resolve(options.backup);
const filename = path.join(backup, "backup.json");
const info = await lstat(filename);
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) throw Error("Invalid source backup manifest");
const manifest = JSON.parse(await readFile(filename, "utf8")) as SourceBackup;
if (
manifest.formatVersion !== 1 ||
!Array.isArray(manifest.repositories) ||
!manifest.repositories.length ||
manifest.repositories.length > 256
)
throw Error("Invalid source backup manifest");
const root = workspaceSourcePin(manifest.root);
const locations: Record<string, string> = {};
for (const entry of manifest.repositories) {
if (!entry || !Array.isArray(entry.commits) || !entry.commits.length || entry.commits.length > 256)
throw Error("Invalid source backup entry");
for (const commit of entry.commits) workspaceSourcePin({ repository: entry.repository, commit });
if (Object.hasOwn(locations, entry.repository)) throw Error("Duplicate source backup repository");
const expected = createHash("sha256").update(entry.repository).digest("hex") + ".bundle";
if (entry.bundle !== expected || !/^[a-f0-9]{64}$/.test(entry.sha256))
throw Error("Invalid source backup bundle identity");
const bundle = path.join(backup, expected);
const stat = await lstat(bundle);
if (!stat.isFile() || stat.isSymbolicLink() || (await hash(bundle)) !== entry.sha256)
throw Error("Source backup bundle integrity mismatch");
locations[entry.repository] = pathToFileURL(bundle).href;
}
if (
!manifest.repositories.some((entry) => entry.repository === root.repository && entry.commits.includes(root.commit))
)
throw Error("Source backup does not retain its root commit");
// Every discovered dependency must use the backup. Never fall through to the
// original forge if a manifest omits a required repository.
return materializeWorkspaceSources({ source: root, directory: options.directory, locations, requireLocations: true });
}
+14 -4
View File
@@ -2,7 +2,17 @@ import { buildImmutableCandidate } from "./checked-build.js";
// Installed platform driver, run only inside the sealed checker's unprivileged
// evaluator. Authored flakes never become the outer evaluator program.
const [repository, commit, log] = process.argv.slice(2);
if (!repository || !commit || !log || process.argv.length !== 5)
throw Error("source-check-driver REPOSITORY COMMIT LOG");
process.stdout.write((await buildImmutableCandidate({ repository, commit }, "workspace", log)) + "\n");
const [repository, commit, log, workspaceId] = process.argv.slice(2);
if (!repository || !commit || !log || !workspaceId || process.argv.length !== 6)
throw Error("source-check-driver REPOSITORY COMMIT LOG WORKSPACE_ID");
process.stdout.write(
(await buildImmutableCandidate(
{ repository, commit },
"workspace",
log,
false,
undefined,
workspaceId,
process.env.QUIXOS_CHECK_PLATFORM_SOURCE ? JSON.parse(process.env.QUIXOS_CHECK_PLATFORM_SOURCE) : undefined,
)) + "\n",
);
+176
View File
@@ -0,0 +1,176 @@
import { execFile as callback } from "node:child_process";
import { promisify } from "node:util";
import { createHash, randomUUID } from "node:crypto";
import { mkdir, mkdtemp, lstat, realpath, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { loadQuixosLock, retentionTagForCommit } from "../resource-lock/index.js";
const exec = promisify(callback);
import { workspaceSourcePin, type WorkspaceSourcePin } from "./source-pin.js";
export { workspaceSourcePin, type WorkspaceSourcePin } from "./source-pin.js";
export type SourceClosureEntry = WorkspaceSourcePin & {
kind: "workspace" | "interface" | "package" | "build";
directory: string;
};
export type WorkspaceSourceClosure = {
formatVersion: 1;
root: WorkspaceSourcePin;
resources: SourceClosureEntry[];
};
/** Trusted transport substitutions preserve source identity. A mirror or bundle
* is explicitly selected, never tried automatically after an origin failure.
* Git authentication stays in the caller's Git configuration, outside metadata.
*/
export async function materializeWorkspaceSources(options: {
source: WorkspaceSourcePin;
directory: string;
locations?: Record<string, string>;
concurrency?: number;
maxRepositories?: number;
requireLocations?: boolean;
/** Optional acquisition boundary, enforced before fetching each dependency.
* It limits canonical identity; explicit transport substitutions remain a
* separate trusted operator decision. */
repositoryPrefix?: string;
}): Promise<WorkspaceSourceClosure> {
const source = workspaceSourcePin(options.source);
const prefix = options.repositoryPrefix ? new URL(options.repositoryPrefix) : null;
if (
prefix &&
(prefix.protocol !== "https:" ||
prefix.username ||
prefix.password ||
prefix.search ||
prefix.hash ||
!prefix.pathname.endsWith("/") ||
/[%\\\\]/.test(prefix.pathname))
)
throw Error("Source repository prefix requires a credential-free HTTPS directory URL");
const concurrency = options.concurrency ?? 4,
maxRepositories = options.maxRepositories ?? 256;
if (
!Number.isInteger(concurrency) ||
concurrency < 1 ||
concurrency > 16 ||
!Number.isInteger(maxRepositories) ||
maxRepositories < 1 ||
maxRepositories > 4096
)
throw Error("Invalid source closure limits");
const locations = new Map(Object.entries(options.locations ?? {}));
for (const [canonical, location] of locations) {
workspaceSourcePin({ repository: canonical, commit: source.commit });
const url = new URL(location);
if (
!["https:", "file:"].includes(url.protocol) ||
url.username ||
url.password ||
url.search ||
url.hash ||
(url.protocol === "file:" && url.hostname !== "")
)
throw Error("Invalid source transport substitution");
}
await mkdir(options.directory, { recursive: true, mode: 0o700 });
const directory = await realpath(options.directory);
const entries = new Map<string, SourceClosureEntry>();
const pending: SourceClosureEntry[] = [];
const enqueue = (pin: WorkspaceSourcePin, kind: SourceClosureEntry["kind"]) => {
pin = workspaceSourcePin(pin);
const repository = new URL(pin.repository);
if (
prefix &&
(repository.origin !== prefix.origin ||
!repository.pathname.startsWith(prefix.pathname) ||
/[%\\\\]/.test(repository.pathname))
)
throw Error(`Source repository is outside the acquisition boundary: ${pin.repository}`);
if (options.requireLocations && !locations.has(pin.repository))
throw Error(`Source backup omits required repository: ${pin.repository}`);
const key = `${kind}\0${pin.repository}\0${pin.commit}`;
if (entries.has(key)) return;
if (entries.size >= maxRepositories) throw Error("Workspace source closure exceeds repository limit");
const name = `${kind}-${createHash("sha256").update(key).digest("hex").slice(0, 24)}`;
const entry = { ...pin, kind, directory: name };
entries.set(key, entry);
pending.push(entry);
};
enqueue(source, "workspace");
const git = async (cwd: string, args: string[]) =>
(
await exec("git", args, {
cwd,
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
maxBuffer: 4 * 1024 * 1024,
})
).stdout.trim();
const checkout = async (entry: SourceClosureEntry) => {
const destination = path.join(directory, entry.directory);
const verify = async () => {
const info = await lstat(destination);
if (!info.isDirectory() || info.isSymbolicLink() || (await realpath(destination)) !== destination)
throw Error("Source checkout must be a contained directory");
if ((await git(destination, ["rev-parse", "HEAD"])) !== entry.commit)
throw Error(`Source commit mismatch: ${entry.repository}`);
if (await git(destination, ["status", "--porcelain", "--untracked-files=all"]))
throw Error(`Source checkout was modified: ${entry.repository}`);
};
const existing = await lstat(destination).catch((error: NodeJS.ErrnoException) => {
if (error.code !== "ENOENT") throw error;
return null;
});
if (!existing) {
const staging = await mkdtemp(path.join(directory, ".fetch-"));
try {
await git(staging, ["init"]);
await git(staging, ["remote", "add", "origin", entry.repository]);
const ref = retentionTagForCommit(entry.commit);
// Full history supports normal jj development and independent bundles.
const location = locations.get(entry.repository) ?? entry.repository;
await git(staging, [
"fetch",
"--no-tags",
"--",
location.startsWith("file:") ? fileURLToPath(location) : location,
`${ref}:${ref}`,
]);
await git(staging, ["checkout", "--detach", entry.commit]);
try {
await rename(staging, destination);
} catch (error) {
if (!["EEXIST", "ENOTEMPTY"].includes((error as NodeJS.ErrnoException).code ?? "")) throw error;
}
} finally {
await rm(staging, { recursive: true, force: true });
}
}
await verify();
const lock = await loadQuixosLock(path.join(destination, "quixos.lock"));
if (!lock.ok)
throw Error(`Invalid source lock in ${entry.repository}: ${lock.diagnostics.map((d) => d.message).join("; ")}`);
return lock.lock.resources;
};
// Bounded breadth-first batches resolve diamonds once and avoid recursive
// promise deadlocks. Completion order cannot affect the recorded closure.
while (pending.length) {
const batch = pending.splice(0, concurrency);
const results = await Promise.allSettled(batch.map(checkout));
for (const result of results) {
if (result.status === "rejected") throw result.reason;
for (const resource of result.value) enqueue(resource.source, resource.kind);
}
}
const closure: WorkspaceSourceClosure = {
formatVersion: 1,
root: source,
resources: [...entries.values()].sort((a, b) =>
`${a.kind}:${a.repository}@${a.commit}`.localeCompare(`${b.kind}:${b.repository}@${b.commit}`),
),
};
const temporary = path.join(directory, `.closure-${randomUUID()}.json`);
await writeFile(temporary, JSON.stringify(closure, null, 2) + "\n", { flag: "wx", mode: 0o600 });
await rename(temporary, path.join(directory, "sources.json"));
return closure;
}
+48
View File
@@ -0,0 +1,48 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { loadQuixosLock, parseQuixosLockDocument, resolveQuixosLock } from "../resource-lock/index.js";
import { editStructure } from "./structural-edits.js";
import { workspaceSourcePin, type WorkspaceSourcePin } from "./source-closure.js";
/** Inspect/rewrite the root and its declared imports only, using the same lock
* grammar as compilation. Return proposed file contents; publishing them and
* selecting a Graph remain the caller's separate responsibilities. */
export async function workspaceSourceLocks(directory: string, input: unknown = []) {
if (!Array.isArray(input) || input.length > 256) throw Error("Invalid source lock replacements");
const replacements = new Map<string, WorkspaceSourcePin>();
const key = (source: WorkspaceSourcePin) => `${source.repository}\0${source.commit}`;
for (const entry of input) {
if (!entry || typeof entry !== "object") throw Error("Invalid source lock replacement");
const from = workspaceSourcePin(entry.from),
to = workspaceSourcePin(entry.to);
if (replacements.has(key(from))) throw Error("Duplicate source lock replacement");
replacements.set(key(from), to);
}
const lock = await loadQuixosLock(path.join(directory, "quixos.lock"));
if (!lock.ok) throw Error(`Invalid source lock: ${lock.diagnostics.map((d) => d.message).join("; ")}`);
const files = new Map<string, string>();
for (const filename of lock.lock.sourceFiles!) {
let source = await readFile(path.join(directory, filename), "utf8");
const parsed = parseQuixosLockDocument(source, filename);
if (!parsed.ok) throw Error("Source lock changed during inspection");
for (const dependency of parsed.document.resources) {
const replacement = replacements.get(key(dependency.source));
if (replacement)
source = editStructure(source, {
operation: "dependency",
kind: dependency.kind,
name: dependency.binding,
source: replacement,
});
}
files.set(filename, source);
}
const checked = await resolveQuixosLock(files.get("quixos.lock")!, async (filename) => {
const source = files.get(filename);
if (source === undefined) throw Error("Source lock import changed during inspection");
return source;
});
if (!checked.ok)
throw Error(`Invalid rewritten source locks: ${checked.diagnostics.map((d) => d.message).join("; ")}`);
return { lock: checked.lock, files: [...files].map(([path, source]) => ({ path, source })) };
}
+18
View File
@@ -0,0 +1,18 @@
export type WorkspaceSourcePin = { repository: string; commit: string };
export function workspaceSourcePin(value: unknown): WorkspaceSourcePin {
if (!value || typeof value !== "object" || Array.isArray(value)) throw Error("Invalid Workspace source pin");
const pin = value as WorkspaceSourcePin;
const url = new URL(pin.repository);
if (
url.protocol !== "https:" ||
url.username ||
url.password ||
url.search ||
url.hash ||
typeof pin.commit !== "string" ||
!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(pin.commit)
)
throw Error("Workspace source pins require credential-free HTTPS and an exact commit");
return { repository: pin.repository, commit: pin.commit };
}
+92 -3
View File
@@ -54,6 +54,65 @@ const planSummary = (plan: Awaited<ReturnType<typeof planStructure>>) => ({
const main = async () => {
const [command, ...args] = process.argv.slice(2);
if (command === "source-locks") {
if (!args[0] || (args.length !== 1 && (args.length !== 3 || args[1] !== "--replacements")))
throw Error("source-locks DIRECTORY [--replacements JSON_FILE]");
const { workspaceSourceLocks } = await import("./source-locks.js");
const replacements = args[2] ? JSON.parse(await readFile(args[2], "utf8")) : [];
process.stdout.write(JSON.stringify(await workspaceSourceLocks(path.resolve(args[0]), replacements)) + "\n");
return;
}
if (["source-checkout", "source-develop", "source-backup", "source-restore"].includes(command!)) {
const args = ["source", command.slice("source-".length), ...process.argv.slice(3)];
const { materializeWorkspaceSources } = await import("./source-closure.js");
if (!args[2]) throw Error("source requires a pinned source JSON file");
const flags = new Map<string, string>();
for (let i = 3; i < args.length; i += 2) {
if (
![
"--dir",
...(args[1] !== "restore" ? ["--locations"] : []),
...(args[1] === "checkout" ? ["--repository-prefix"] : []),
...(args[1] === "develop" ? ["--cache", "--workspace-id"] : []),
...(args[1] === "backup" ? ["--output"] : []),
].includes(args[i]) ||
!args[i + 1] ||
flags.has(args[i])
)
throw Error("Invalid source command options; use qx-workspace help source");
flags.set(args[i], args[i + 1]);
}
if (!flags.get("--dir")) throw Error("source requires --dir DIRECTORY");
if (args[1] === "restore") {
const { restoreWorkspaceSources } = await import("./source-backup.js");
const result = await restoreWorkspaceSources({ backup: args[2], directory: path.resolve(flags.get("--dir")!) });
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
return;
}
const locationsFile = flags.get("--locations");
const options = {
source: JSON.parse(await readFile(args[2], "utf8")),
directory: path.resolve(flags.get("--dir")!),
...(locationsFile ? { locations: JSON.parse(await readFile(locationsFile, "utf8")) } : {}),
...(flags.has("--repository-prefix") ? { repositoryPrefix: flags.get("--repository-prefix") } : {}),
};
let result;
if (args[1] === "develop") {
if (!flags.get("--cache")) throw Error("source develop requires --cache DIRECTORY");
const { prepareDevelopmentSources } = await import("./development-sources.js");
result = await prepareDevelopmentSources({
...options,
cacheDirectory: path.resolve(flags.get("--cache")!),
...(flags.has("--workspace-id") ? { workspaceId: flags.get("--workspace-id")! } : {}),
});
} else if (args[1] === "backup") {
if (!flags.get("--output")) throw Error("source backup requires --output DIRECTORY");
const { backupWorkspaceSources } = await import("./source-backup.js");
result = await backupWorkspaceSources({ ...options, output: path.resolve(flags.get("--output")!) });
} else result = await materializeWorkspaceSources(options);
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
return;
}
if (command === "source-manifest") {
if (args.length !== 1) throw new Error("source-manifest WORKSPACE_ROOT");
process.stdout.write(JSON.stringify(await qxSourceManifest(args[0])) + "\n");
@@ -197,10 +256,16 @@ const main = async () => {
}
if (command === "check-committed" && !args.includes("--help")) {
const [kind, repository, commit, log, ...extra] = args;
if (!["workspace", "interface", "package", "build"].includes(kind) || !log || extra.length)
throw new Error("usage: quixos-qx check-committed workspace|interface|package|build REPOSITORY COMMIT LOG_FILE");
if (
!["workspace", "interface", "package", "build"].includes(kind) ||
!log ||
(extra.length !== 0 && (kind !== "workspace" || extra.length !== 2 || extra[0] !== "--workspace-id" || !extra[1]))
)
throw new Error(
"usage: quixos-qx check-committed workspace|interface|package|build REPOSITORY COMMIT LOG_FILE [--workspace-id ID]",
);
process.stdout.write(
`${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package" | "build", log)}\n`,
`${await buildImmutableCandidate({ repository, commit }, kind as "workspace" | "interface" | "package" | "build", log, false, undefined, extra[1])}\n`,
);
return;
}
@@ -530,6 +595,30 @@ const main = async () => {
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
return;
}
if (command === "format" && (args[0] === "--check" || args[0] === "--write")) {
const check = args[0] === "--check",
files = args.slice(1);
if (!files.length || files.some((file) => file.startsWith("-")))
throw new Error("usage: quixos-qx format --check|--write FILE...");
for (const file of files) {
try {
const source = await readFile(file, "utf8"),
formatted = formatQx(source);
if (formatted === source) continue;
if (check) {
console.error(`Needs formatting: ${file}`);
process.exitCode = 1;
} else {
await writeFile(file, formatted);
console.log(`Formatted ${file}`);
}
} catch (error) {
console.error(`${file}: ${error instanceof Error ? error.message : error}`);
process.exitCode = 1;
}
}
return;
}
const [file, flag, ...rest] = args;
if (
!file ||
+8 -3
View File
@@ -17,7 +17,7 @@ import {
} from "../capability-model/index.js";
const usage = `usage: quixos-workspace-compile --root DIRECTORY --checkout-root DIRECTORY
[--snapshot-map PATH] [--graph-out PATH] [--workspace-id ID] [--workspace-revision-id ID]
[--snapshot-map PATH] [--graph-out PATH] [--plan-out PATH] [--workspace-id ID] [--workspace-revision-id ID]
[--source-root-commit GIT_REV] [--baseline PLAN_JSON] [--evolution-out PATH]
[--reviews REVIEW_JSON] [--schemas-out PATH] [--execution-plan-out PATH] [--build-contexts-out PATH]
[--query-generator BUILD_MODULE_PATH] [--checked-resources NIX_MAP]
@@ -41,6 +41,7 @@ const parseArgs = (args: string[]) => {
rootDirectory,
checkoutRoot,
graphOut: values.get("--graph-out"),
planOut: values.get("--plan-out"),
schemasOut: values.get("--schemas-out"),
executionPlanOut: values.get("--execution-plan-out"),
buildContextsOut: values.get("--build-contexts-out"),
@@ -84,6 +85,7 @@ const main = async () => {
`${JSON.stringify(
{
formatVersion: 1,
workspaceId: assembled.workspace.workspaceId,
quixos: assembled.lock.quixos,
directResources: [...assembled.directResources.entries()].map(([bindingKey, node]) => {
const [kind, binding] = bindingKey.split("\0");
@@ -118,7 +120,9 @@ const main = async () => {
if (options.executionPlanOut || options.buildContextsOut) {
const executionStarted = performance.now();
const plan = compileExecutionPlan(assembled.workspace);
if (options.executionPlanOut) await writeFile(options.executionPlanOut, `${JSON.stringify(plan, null, 2)}\n`);
// Machine-consumed immutable metadata should not spend its bounded input
// reservation on indentation. Its parsed allocation is charged separately.
if (options.executionPlanOut) await writeFile(options.executionPlanOut, `${JSON.stringify(plan)}\n`);
if (options.buildContextsOut)
await writeFile(options.buildContextsOut, JSON.stringify(packageBuildContexts(plan.definition.bindingSchema)));
profile("execution-plan-and-contexts", executionStarted);
@@ -155,7 +159,8 @@ const main = async () => {
`${JSON.stringify(planEvolution(baseline, candidate, { reviews }), null, 2)}\n`,
);
}
process.stdout.write(`${JSON.stringify(candidate, null, 2)}\n`);
if (options.planOut) await writeFile(options.planOut, `${JSON.stringify(candidate)}\n`);
else process.stdout.write(`${JSON.stringify(candidate, null, 2)}\n`);
};
main().catch((error: unknown) => {
+84
View File
@@ -1,4 +1,5 @@
import { createHash } from "node:crypto";
import { setImmediate } from "node:timers/promises";
import type { Binding, Conformance, DependencyBinding, PersistentAttachment, WorkspaceRevision } from "./types.js";
import { validateWorkspaceRevision } from "./validation.js";
@@ -21,6 +22,89 @@ export const canonicalJson = (value: unknown): string => {
};
export const contentDigest = (value: unknown) =>
`sha256:${createHash("sha256").update(canonicalJson(value)).digest("hex")}`;
/** Same canonical bytes as contentDigest, without monopolizing a live host's
* event loop while verifying large immutable metadata. No validation is skipped.
* This is for asynchronous loading, not a different build/wire identity. */
export async function contentDigestAsync(value: unknown): Promise<string> {
// An explicit stack visits each node once; recursive generator delegation
// otherwise forwards every token through every ancestor of a deep plan.
type Frame = { kind: "value" | "array" | "object"; value: unknown; index?: number; keys?: string[]; wrote?: boolean };
const hash = createHash("sha256"),
stack: Frame[] = [{ kind: "value", value }],
active = new WeakSet<object>();
let buffer = "",
sliceStarted = performance.now(),
steps = 0;
const append = (part: string) => {
buffer += part;
if (buffer.length >= 65536) {
hash.update(buffer);
buffer = "";
}
};
while (stack.length) {
const frame = stack[stack.length - 1]!;
if (frame.kind === "value") {
const value = frame.value;
if (value !== null && typeof value === "object") {
if (active.has(value)) throw new Error("Cannot hash cyclic JSON values");
active.add(value);
frame.index = 0;
if (Array.isArray(value)) {
frame.kind = "array";
append("[");
} else {
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
throw new Error("Expected a plain JSON object");
frame.kind = "object";
frame.keys = Object.keys(value).sort(compareText);
frame.wrote = false;
append("{");
}
} else {
append(canonicalJson(value));
stack.pop();
}
} else if (frame.kind === "array") {
const array = frame.value as unknown[];
if (frame.index === array.length) {
append("]");
active.delete(array);
stack.pop();
} else {
const index = frame.index!;
frame.index = index + 1;
if (index) append(",");
// Match canonicalJson's Array.map behavior for sparse arrays too.
if (index in array) stack.push({ kind: "value", value: array[index] });
}
} else {
const object = frame.value as Record<string, unknown>;
if (frame.index === frame.keys!.length) {
append("}");
active.delete(object);
stack.pop();
} else {
const index = frame.index!;
frame.index = index + 1;
const key = frame.keys![index]!,
entry = object[key];
if (entry !== undefined) {
if (frame.wrote) append(",");
frame.wrote = true;
append(JSON.stringify(key) + ":");
stack.push({ kind: "value", value: entry });
}
}
}
if (++steps % 512 === 0 && performance.now() - sliceStarted >= 8) {
await setImmediate();
sliceStarted = performance.now();
}
}
hash.update(buffer);
return `sha256:${hash.digest("hex")}`;
}
const semantic = (value: unknown): unknown => {
if (Array.isArray(value)) return value.map(semantic);
if (value && typeof value === "object")
+11 -5
View File
@@ -1,5 +1,11 @@
# Closed execution plans and artifacts
This page documents current compiler/runtime representations. Its universal
artifact inventory, React metadata and overlapping world/installation structures
are [known architectural gaps](../../../docs/ARCHITECTURE_STATUS.md), not the
[core package contract](../../../docs/ARCHITECTURE.md). Historical LF task numbers
refer to the original implementation sequence, not an active queue.
`compileExecutionPlan` validates a workspace, links its checked queries, resolves
public forwarding, and emits one versioned plan with an SHA-256 digest. It retains
exact interface/package revisions and generic applications, owner initialization,
@@ -60,11 +66,11 @@ the core codec's canonical ordering and domain-separated SHA-256 framing. The
monorepo `camino-execution-world` check compares the compiler and core implementations.
No machine-local Nix path enters the world descriptor.
The current binding schema is the closed workspace schema. This is conservative:
an unrelated contract change can require checking/rebuilding several packages.
LF-05 may narrow it to exact checked per-package closures, but must use the same
schema for generation, receipts, and world validation. It must not substitute an
older package schema just because the source revision did not change.
Reusable package compilation now receives a canonical per-package build contract;
exact source provenance and workspace linking remain separate. See
[package build contracts](../../../docs/PACKAGE_BUILD_CONTRACTS.md). Generation,
receipts and checking must agree on that contract; unchanged source alone does
not justify reusing an incompatible binding schema.
## Transaction-port ABI
+23 -5
View File
@@ -1,6 +1,13 @@
import {
executionPlanDigestAsync,
type ExecutionPlan,
type PlanPackage,
ExecutionPlanError,
hexDigest,
executionVersions,
} from "./plan.js";
import { createHash } from "node:crypto";
import { canonicalJson } from "../capability-model/evolution.js";
import { type ExecutionPlan, type PlanPackage, ExecutionPlanError, hexDigest, executionVersions } from "./plan.js";
export interface ArtifactFile {
path: string;
@@ -11,7 +18,7 @@ export interface ArtifactFile {
export interface ArtifactImplementation {
id: string;
target: string;
compatibility: "quixos-service-v1" | "quixos-js-module-v1";
compatibility: "quixos-service-v1" | "quixos-js-module-v1" | "quixos-restricted-module-v1";
path: string;
ports: "quixos-transaction-ports-v1";
registryExport?: string;
@@ -225,7 +232,11 @@ export function parseArtifactGroup(value: unknown): ArtifactGroup {
["registryExport"],
path,
);
if (entry.compatibility !== "quixos-service-v1" && entry.compatibility !== "quixos-js-module-v1")
if (
!["quixos-service-v1", "quixos-js-module-v1", "quixos-restricted-module-v1"].includes(
String(entry.compatibility),
)
)
fail(path, "Unsupported artifact ABI");
if (entry.ports !== "quixos-transaction-ports-v1") fail(path, "Unsupported transaction port ABI");
if (
@@ -233,7 +244,9 @@ export function parseArtifactGroup(value: unknown): ArtifactGroup {
(entry.target !== "server" || entry.registryExport !== undefined)
)
fail(path, "Service must target server and has no JS registry export");
if (entry.compatibility === "quixos-js-module-v1") text(entry.registryExport, `${path}.registryExport`);
if (entry.compatibility !== "quixos-service-v1") text(entry.registryExport, `${path}.registryExport`);
if (entry.compatibility === "quixos-restricted-module-v1" && entry.target !== "server")
fail(path, "Restricted modules must target server");
const exports = array(entry.exports, `${path}.exports`).map((value) => text(value, `${path}.exports`));
unique(exports, `${path}.exports`);
const files = parseArtifactFiles(entry.files, `${path}.files`);
@@ -300,6 +313,11 @@ export function checkPackageCoverage(
for (const id of implementation.exports) {
const entry = expected.exports.find((e) => e.id === id);
if (!entry) fail(implementation.id, `Unknown export ${id}`);
if (
implementation.compatibility === "quixos-restricted-module-v1" &&
(entry!.execution.kind === "remote" || !entry!.execution.targets.includes("server"))
)
fail(implementation.id, `Export ${id} does not promise restricted server computation`);
if (
implementation.target !== "server" &&
(!entry?.execution ||
@@ -348,7 +366,7 @@ export async function checkExecutionWorld(
): Promise<CheckedWorld> {
if (canonicalJson(plan.definition.versions) !== canonicalJson(executionVersions))
fail("world", "Unsupported plan codec versions");
if (hexDigest(plan.definition) !== plan.digest) fail("plan", "Plan digest mismatch");
if ((await executionPlanDigestAsync(plan)) !== plan.digest) fail("plan", "Plan digest mismatch");
const manifests = inputs.map(parsePackageArtifacts);
unique(
manifests.map((m) => m.packageRevisionId),
+56 -26
View File
@@ -1,8 +1,12 @@
import { graphExecutionRepresentation } from "./graph-execution.js";
import { lstat } from "node:fs/promises";
import { join } from "node:path";
import { hexDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
import { loadExecutionInstallation, type PackageInstallationSelection } from "./installation.js";
import { hexDigest, hexDigestAsync, sealExecutionPlan, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
import {
loadExecutionInstallation,
createClientContractCatalog,
type PackageInstallationSelection,
} from "./installation.js";
import {
candidateMetadataBudget,
readCandidateMetadata,
@@ -11,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)),
@@ -36,33 +43,37 @@ export async function loadCheckedInstallationCandidate(
if (!rootPattern.test(root) || !(await lstat(root)).isDirectory())
throw Error("Candidate must be an immutable Nix output directory");
const scope = (resources.budget ?? candidateMetadataBudget).acquire(resources.owner ?? root);
let releaseValidation = () => {};
try {
// Validate the duplicated source contract by digest, then discard its tree
// before allocating the substantially larger checked execution plan.
const workspaceDigest = await (async () => {
const input = await readJson(root, "candidate.json", scope);
try {
const { executionContracts: _legacyContracts, ...workspace } = input.value;
return await hexDigestAsync(workspace);
} finally {
input.releaseTree();
}
})();
// Sequential reads avoid partially rejected Promise.all() work allocating
// after the failed candidate's reservation has been released.
const values = [];
for (const file of [
"candidate.json",
"execution-plan.json",
"execution-artifacts.json",
"source.json",
"entry.json",
"kernel.json",
"queries.json",
"graph-execution.json",
]) {
const parsed = await readJson(root, file, scope);
values.push(parsed.value);
// candidate.json duplicates the checked plan for validation. Nothing in
// the installed candidate retains this input tree after validation.
if (file === "candidate.json") releaseValidation = parsed.releaseTree;
}
const [candidate, plan, artifacts, source, entryRequest, kernel, queries, graph] = values;
const { executionContracts: _legacyContracts, ...workspace } = candidate;
const [plan, artifacts, source, entryRequest, kernel, queries] = values;
if (
plan?.definition?.codec !== "camino-execution-plan-v1" ||
hexDigest(plan.definition) !== plan.digest ||
hexDigest(workspace) !== hexDigest(plan.definition.workspace)
!(await sealExecutionPlan(plan)) ||
workspaceDigest !== (await hexDigestAsync(plan.definition.workspace))
)
throw new ExecutionPlanError(
"CANDIDATE_PLAN_MISMATCH",
@@ -106,21 +117,36 @@ export async function loadCheckedInstallationCandidate(
["kernel", kernel, "camino-kernel-program-v1"],
["queries", queries, "camino-query-program-v1"],
] as const)
if (program?.codec !== codec || program.plan !== kernel.plan || hexDigest(program) !== artifacts.programs?.[name])
if (
program?.codec !== codec ||
program.plan !== kernel.plan ||
(await hexDigestAsync(program)) !== artifacts.programs?.[name]
)
throw new ExecutionPlanError(
"CANDIDATE_PROGRAM_MISMATCH",
name,
"Stored interpreter input must match the checked build and plan",
);
const expectedGraph = graphExecutionRepresentation(plan, installation, kernel, queries);
if (hexDigest(graph) !== hexDigest(expectedGraph) || artifacts.execution !== hexDigest(graph))
throw new ExecutionPlanError(
"CANDIDATE_EXECUTION_MISMATCH",
root,
"Stored graph execution differs from checked inputs",
);
// The stored graph embeds the same kernel/query programs. Validate it in a
// short-lived scope and retain the representation that shares those trees.
const graphDigest = await (async () => {
const input = await readJson(root, "graph-execution.json", scope);
try {
const digest = await hexDigestAsync(input.value);
if (digest !== (await hexDigestAsync(expectedGraph)) || artifacts.execution !== digest)
throw new ExecutionPlanError(
"CANDIDATE_EXECUTION_MISMATCH",
root,
"Stored graph execution differs from checked inputs",
);
return digest;
} finally {
input.releaseTree();
}
})();
const provenance = {
workspaceRevision: workspace.id as string,
workspaceRevision: plan.definition.workspace.id as string,
source: { repository: source.repository as string, commit: source.commit as string },
generator: artifacts.generator as string,
packages: (plan as ExecutionPlan).definition.packages.map((pkg) => ({
@@ -139,30 +165,34 @@ export async function loadCheckedInstallationCandidate(
programs: artifacts.programs,
}),
};
const catalog = createClientContractCatalog(plan, installation, scope, kernel);
return {
reference,
workspace: plan.definition.workspace.workspaceId,
world: hexDigest(graph),
// The checked representation was already compared with this digest.
world: graphDigest,
plan: kernel.plan as string,
graphExecution: graph,
graphExecution: expectedGraph,
provenance,
entry,
kernel,
queries,
installation,
executionPlan: plan as ExecutionPlan,
clientContractCatalog: catalog.select,
metadata: {
get bytes() {
return scope.bytes;
},
release: scope.release,
release: () => {
catalog.close();
scope.release();
},
},
};
} catch (error) {
scope.release();
throw error;
} finally {
releaseValidation();
}
}
+20 -20
View File
@@ -1,3 +1,4 @@
import { executionPlanDigest, type ExecutionPlan, resolvePlannedOperation, ExecutionPlanError } from "./plan.js";
import type {
InterfaceRevision,
InterfaceOperation,
@@ -6,11 +7,12 @@ import type {
} from "../capability-model/types.js";
import { canonicalJson } from "../capability-model/evolution.js";
import { portableValueSchema, tsType } from "../bindings/portable.js";
import { type ExecutionPlan, resolvePlannedOperation, hexDigest, ExecutionPlanError } from "./plan.js";
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 {
@@ -54,7 +56,7 @@ export function publicOperationContract(
}
/** Resolve a public contract to internal dispatch. This does not authorize the receiver. */
export function linkInterfaceOperation(plan: ExecutionPlan, atom: string, contract: PublicOperationContract) {
if (hexDigest(plan.definition) !== plan.digest)
if (executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("INVALID_PLAN", atom, "Invalid execution plan digest");
const iface = plan.definition.workspace.interfaceImports.find((i) => i.revisionId === contract.interface);
const operation = iface?.members.flatMap((m) => m.operations).find((o) => o.id === contract.operation);
@@ -76,6 +78,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,13 +91,14 @@ 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) {
if (hexDigest(plan.definition) !== plan.digest)
if (executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("INVALID_PLAN", contract.query, "Invalid execution plan digest");
const pkg = plan.definition.workspace.packageImports.find((p) => p.revisionId === contract.package);
if (!pkg || canonicalJson(publicQueryContract(pkg, contract.query)) !== canonicalJson(contract))
@@ -156,10 +160,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 +214,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
@@ -254,17 +258,7 @@ export function generatePackageComponentBindings(
target.kind === "atom"
? `ManagedReference<"atom", ${JSON.stringify(target.atomId)}>`
: `ManagedReference<"interface", ${JSON.stringify(target.interfaceRevisionId)}>`;
const fields =
target.kind === "interface"
? find(target.interfaceRevisionId).members.flatMap((member) => {
if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return [];
const getter = member.operations.find(
(operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"),
);
return getter ? [`readonly ${JSON.stringify(member.displayName)}: ${tsType(getter.outputType)}`] : [];
})
: [];
return `{readonly reference: ${reference}; readonly values: {${fields.join(";")}}}`;
return `{readonly reference: ${reference}}`;
})
.join(",")}]`;
const interfaces = generateInterfaceClientBindings(imports, "@quixos/camino-react", props);
@@ -279,7 +273,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 +313,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") +
+172 -175
View File
@@ -1,3 +1,4 @@
import { executionPlanDigestAsync, type ExecutionPlan, hexDigest, hexDigestAsync, ExecutionPlanError } from "./plan.js";
import { portableReflectionValue } from "./reflection.js";
import type {
BoundDependency,
@@ -6,7 +7,6 @@ import type {
PersistentAttachment,
} from "../capability-model/types.js";
import { type ExecutionInstallation, type ExecutionGroupIdentity } from "./installation.js";
import { type ExecutionPlan, hexDigest, ExecutionPlanError } from "./plan.js";
export interface ExecutionEnvironment {
/** Explicit host placement. Merely including an optional module does not select it. */
@@ -23,12 +23,12 @@ export interface InstalledExecutionGroup extends ExecutionGroupIdentity {
}
/** Conservative outbound closure. Runtime graph values, reverse callers, source
* provenance and the workspace epoch are deliberately absent from this identity. */
export function compileExecutionGroups(
export async function compileExecutionGroups(
plan: ExecutionPlan,
installation: ExecutionInstallation,
environments: Readonly<Record<string, ExecutionEnvironment>>,
): InstalledExecutionGroup[] {
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
): Promise<InstalledExecutionGroup[]> {
if (installation.plan !== plan.digest || (await executionPlanDigestAsync(plan)) !== plan.digest)
throw new ExecutionPlanError("GROUP_PLAN_MISMATCH", plan.digest, "Expected one checked installation");
const workspace = plan.definition.workspace;
const packages = new Map(workspace.packageImports.map((p) => [p.revisionId as string, p]));
@@ -82,178 +82,175 @@ export function compileExecutionGroups(
};
const bindingContract = (value: DependencyBinding) =>
value.kind === "query" ? { ...value, packageRevisionId: logical(value.packageRevisionId) } : value;
return installation.packages
.filter((p) => !!implementations.get(p.revision))
.map((root) => {
const consumed = new Map<string, unknown>(),
visited = new Set<string>(),
visitedQueries = new Set<string>(),
consumedPackages = new Set<string>();
const remember = (key: string, value: unknown) => consumed.set(key, value);
const storage = (id: string) => {
const value = attachments.get(id);
if (!value) throw new ExecutionPlanError("GROUP_STORAGE_MISSING", id, "Uninstalled consumed storage");
remember(`storage:${id}`, attachmentContract(value));
};
// Deletion may visit related objects and their owned state. Conservatively
// include the connected schema component, including possible future edges.
const lifecycle = (atom: string) => {
const visited = new Set<string>(),
pending = [atom];
const atoms = (
constraint: Extract<PersistentAttachment, { kind: "edge" }>["endpoints"][number]["constraint"],
) =>
constraint.kind === "atom"
? [constraint.atomId as string]
: workspace.conformances
.filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId)
.map((c) => c.atomId as string);
while (pending.length) {
const current = pending.pop()!;
if (visited.has(current)) continue;
visited.add(current);
for (const owner of workspace.conformances.filter((c) => c.atomId === current))
for (const attachment of owner.privateAttachments) storage(attachment.id);
for (const attachment of attachments.values())
if (attachment.kind === "edge" && attachment.endpoints.some((e) => atoms(e.constraint).includes(current))) {
storage(attachment.id);
pending.push(...attachment.endpoints.flatMap((e) => atoms(e.constraint)));
}
}
};
const targets = (binding: DependencyBinding, atom: string): string[] => {
if (binding.kind === "interface" && binding.receiver === "input")
return workspace.conformances
.filter((c) => c.interfaceRevisionId === binding.interfaceRevisionId)
.map((c) => c.atomId);
if (!("via" in binding) || !binding.via) return [atom];
storage(binding.via.edgeTypeId);
const edge = attachments.get(binding.via.edgeTypeId);
if (!edge || edge.kind !== "edge")
throw new ExecutionPlanError("GROUP_EDGE_MISSING", atom, "Missing traversal edge");
const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!;
const constraint = endpoint.constraint;
return constraint.kind === "atom"
? [constraint.atomId]
const groups: InstalledExecutionGroup[] = [];
for (const root of installation.packages.filter((p) => !!implementations.get(p.revision))) {
const consumed = new Map<string, unknown>(),
visited = new Set<string>(),
visitedQueries = new Set<string>(),
consumedPackages = new Set<string>();
const remember = (key: string, value: unknown) => consumed.set(key, value);
const storage = (id: string) => {
const value = attachments.get(id);
if (!value) throw new ExecutionPlanError("GROUP_STORAGE_MISSING", id, "Uninstalled consumed storage");
remember(`storage:${id}`, attachmentContract(value));
};
// Deletion may visit related objects and their owned state. Conservatively
// include the connected schema component, including possible future edges.
const lifecycle = (atom: string) => {
const visited = new Set<string>(),
pending = [atom];
const atoms = (constraint: Extract<PersistentAttachment, { kind: "edge" }>["endpoints"][number]["constraint"]) =>
constraint.kind === "atom"
? [constraint.atomId as string]
: workspace.conformances
.filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId)
.map((c) => c.atomId);
};
const query = (revision: string, id: string) => {
const key = JSON.stringify([revision, id]);
if (visitedQueries.has(key)) return;
visitedQueries.add(key);
const value = plan.definition.queries.find((q) => q.id === key);
if (!value) throw new ExecutionPlanError("GROUP_QUERY_MISSING", key, "Missing consumed query");
remember(`query:${JSON.stringify([logical(revision), id])}`, value.query.checked.definitionDigest);
for (const operation of value.operations) visit(operation);
};
const dependencies = (entry: PackageExport, bindings: readonly BoundDependency[], atom: string) => {
for (const bound of bindings) {
const target = bound.binding,
port = entry.dependencyPorts.find((p) => p.id === bound.portId)!;
if ("via" in target && target.via) storage(target.via.edgeTypeId);
if (target.kind === "reflection") {
for (const selector of target.selectors)
remember("reflection:" + selector, portableReflectionValue(workspace, selector));
} else if (target.kind === "state") storage(target.slotId);
else if (target.kind === "edge") storage(target.edgeTypeId);
else if (target.kind === "query") query(target.packageRevisionId, target.queryId);
else if (target.kind === "interface" && port.requirement.kind === "interface") {
const iface = workspace.interfaceImports.find((i) => i.revisionId === target.interfaceRevisionId)!;
for (const targetAtom of targets(target, atom))
for (const op of port.requirement.operations ??
iface.members.flatMap((m) => m.operations.map((o) => o.id)))
visit(JSON.stringify([targetAtom, target.interfaceRevisionId, op]));
} else if (target.kind === "creation") {
for (const seed of target.owners) {
const owner = workspace.conformances.find(
(c) => c.atomId === target.atomId && c.interfaceRevisionId === seed.interfaceRevisionId,
)!;
remember(`birth:${owner.id}`, {
id: owner.id,
atom: owner.atomId,
interface: owner.interfaceRevisionId,
initialization: owner.initialization ?? [],
});
for (const attachment of owner.privateAttachments) storage(attachment.id);
}
} else
throw new ExecutionPlanError(
"GROUP_DEPENDENCY_UNSUPPORTED",
bound.portId,
"Cannot establish execution reuse for this dependency",
);
}
};
const visit = (key: string) => {
if (visited.has(key)) return;
visited.add(key);
const operation = operations.get(key);
if (!operation) throw new ExecutionPlanError("GROUP_OPERATION_MISSING", key, "Missing consumed operation");
const binding = operation.binding;
if (binding.kind === "package") {
const pkg = packages.get(binding.packageRevisionId)!;
const implementation = implementations.get(pkg.revisionId);
if (!implementation)
throw new ExecutionPlanError(
"GROUP_IMPLEMENTATION_MISSING",
pkg.revisionId,
"Consumed package has no selected server implementation",
);
consumedPackages.add(pkg.packageId);
remember(`implementation:${pkg.packageId}`, {
digest: implementation.contentDigest,
environment: environment(pkg.revisionId),
});
const entry = pkg.exports.find((e) => e.id === binding.exportId)!;
const bindings = binding.dependencies.map((d) => ({ portId: d.portId, binding: bindingContract(d.binding) }));
const acceptance = (binding.acceptanceDependencies ?? []).map((d) => ({
portId: d.portId,
binding: bindingContract(d.binding),
}));
const contract = {
input: operation.input,
output: operation.output,
execution: operation.execution,
scope: operation.scope,
binding: {
package: pkg.packageId,
export: binding.exportId,
dependencies: bindings,
acceptanceDependencies: acceptance,
},
};
// Deduplicate aliases of the same bound export, rather than keying its owner by reverse callers.
remember(`binding:${hexDigest(contract)}`, contract);
dependencies(entry, binding.dependencies, operation.atomId);
if (entry.acceptanceExportId)
dependencies(
pkg.exports.find((e) => e.id === entry.acceptanceExportId)!,
binding.acceptanceDependencies ?? [],
operation.atomId,
);
} else {
remember(`operation:${key}`, operation);
if (binding.kind === "state") storage(binding.slotId);
if (binding.kind === "edge") storage(binding.edgeTypeId);
if (binding.kind === "lifecycle") lifecycle(operation.atomId);
}
};
for (const operation of plan.definition.operations)
if (operation.binding.kind === "package" && operation.binding.packageRevisionId === root.revision)
visit(operation.key);
const own = implementations.get(root.revision)!;
const exports = packages.get(root.revision)!.exports.map(({ displayName: _name, dependencyPorts, ...entry }) => ({
...entry,
dependencyPorts: dependencyPorts.map(({ displayName: _name, ...port }) => port),
}));
const entries = [...consumed].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
return {
logicalId: logical(root.revision),
packageRevision: root.revision,
executionDigest: `sha256:${hexDigest({ implementation: own.contentDigest, exports, environment: environment(root.revision), versions: plan.definition.versions, consumed: entries })}`,
consumedPackages: [...consumedPackages].sort(),
};
.map((c) => c.atomId as string);
while (pending.length) {
const current = pending.pop()!;
if (visited.has(current)) continue;
visited.add(current);
for (const owner of workspace.conformances.filter((c) => c.atomId === current))
for (const attachment of owner.privateAttachments) storage(attachment.id);
for (const attachment of attachments.values())
if (attachment.kind === "edge" && attachment.endpoints.some((e) => atoms(e.constraint).includes(current))) {
storage(attachment.id);
pending.push(...attachment.endpoints.flatMap((e) => atoms(e.constraint)));
}
}
};
const targets = (binding: DependencyBinding, atom: string): string[] => {
if (binding.kind === "interface" && binding.receiver === "input")
return workspace.conformances
.filter((c) => c.interfaceRevisionId === binding.interfaceRevisionId)
.map((c) => c.atomId);
if (!("via" in binding) || !binding.via) return [atom];
storage(binding.via.edgeTypeId);
const edge = attachments.get(binding.via.edgeTypeId);
if (!edge || edge.kind !== "edge")
throw new ExecutionPlanError("GROUP_EDGE_MISSING", atom, "Missing traversal edge");
const endpoint = edge.endpoints.find((e) => e.projectionId !== binding.via!.projectionId)!;
const constraint = endpoint.constraint;
return constraint.kind === "atom"
? [constraint.atomId]
: workspace.conformances
.filter((c) => c.interfaceRevisionId === constraint.interfaceRevisionId)
.map((c) => c.atomId);
};
const query = (revision: string, id: string) => {
const key = JSON.stringify([revision, id]);
if (visitedQueries.has(key)) return;
visitedQueries.add(key);
const value = plan.definition.queries.find((q) => q.id === key);
if (!value) throw new ExecutionPlanError("GROUP_QUERY_MISSING", key, "Missing consumed query");
remember(`query:${JSON.stringify([logical(revision), id])}`, value.query.checked.definitionDigest);
for (const operation of value.operations) visit(operation);
};
const dependencies = (entry: PackageExport, bindings: readonly BoundDependency[], atom: string) => {
for (const bound of bindings) {
const target = bound.binding,
port = entry.dependencyPorts.find((p) => p.id === bound.portId)!;
if ("via" in target && target.via) storage(target.via.edgeTypeId);
if (target.kind === "reflection") {
for (const selector of target.selectors)
remember("reflection:" + selector, portableReflectionValue(workspace, selector));
} else if (target.kind === "state") storage(target.slotId);
else if (target.kind === "edge") storage(target.edgeTypeId);
else if (target.kind === "query") query(target.packageRevisionId, target.queryId);
else if (target.kind === "interface" && port.requirement.kind === "interface") {
const iface = workspace.interfaceImports.find((i) => i.revisionId === target.interfaceRevisionId)!;
for (const targetAtom of targets(target, atom))
for (const op of port.requirement.operations ?? iface.members.flatMap((m) => m.operations.map((o) => o.id)))
visit(JSON.stringify([targetAtom, target.interfaceRevisionId, op]));
} else if (target.kind === "creation") {
for (const seed of target.owners) {
const owner = workspace.conformances.find(
(c) => c.atomId === target.atomId && c.interfaceRevisionId === seed.interfaceRevisionId,
)!;
remember(`birth:${owner.id}`, {
id: owner.id,
atom: owner.atomId,
interface: owner.interfaceRevisionId,
initialization: owner.initialization ?? [],
});
for (const attachment of owner.privateAttachments) storage(attachment.id);
}
} else
throw new ExecutionPlanError(
"GROUP_DEPENDENCY_UNSUPPORTED",
bound.portId,
"Cannot establish execution reuse for this dependency",
);
}
};
const visit = (key: string) => {
if (visited.has(key)) return;
visited.add(key);
const operation = operations.get(key);
if (!operation) throw new ExecutionPlanError("GROUP_OPERATION_MISSING", key, "Missing consumed operation");
const binding = operation.binding;
if (binding.kind === "package") {
const pkg = packages.get(binding.packageRevisionId)!;
const implementation = implementations.get(pkg.revisionId);
if (!implementation)
throw new ExecutionPlanError(
"GROUP_IMPLEMENTATION_MISSING",
pkg.revisionId,
"Consumed package has no selected server implementation",
);
consumedPackages.add(pkg.packageId);
remember(`implementation:${pkg.packageId}`, {
digest: implementation.contentDigest,
environment: environment(pkg.revisionId),
});
const entry = pkg.exports.find((e) => e.id === binding.exportId)!;
const bindings = binding.dependencies.map((d) => ({ portId: d.portId, binding: bindingContract(d.binding) }));
const acceptance = (binding.acceptanceDependencies ?? []).map((d) => ({
portId: d.portId,
binding: bindingContract(d.binding),
}));
const contract = {
input: operation.input,
output: operation.output,
execution: operation.execution,
scope: operation.scope,
binding: {
package: pkg.packageId,
export: binding.exportId,
dependencies: bindings,
acceptanceDependencies: acceptance,
},
};
// Deduplicate aliases of the same bound export, rather than keying its owner by reverse callers.
remember(`binding:${hexDigest(contract)}`, contract);
dependencies(entry, binding.dependencies, operation.atomId);
if (entry.acceptanceExportId)
dependencies(
pkg.exports.find((e) => e.id === entry.acceptanceExportId)!,
binding.acceptanceDependencies ?? [],
operation.atomId,
);
} else {
remember(`operation:${key}`, operation);
if (binding.kind === "state") storage(binding.slotId);
if (binding.kind === "edge") storage(binding.edgeTypeId);
if (binding.kind === "lifecycle") lifecycle(operation.atomId);
}
};
for (const operation of plan.definition.operations)
if (operation.binding.kind === "package" && operation.binding.packageRevisionId === root.revision)
visit(operation.key);
const own = implementations.get(root.revision)!;
const exports = packages.get(root.revision)!.exports.map(({ displayName: _name, dependencyPorts, ...entry }) => ({
...entry,
dependencyPorts: dependencyPorts.map(({ displayName: _name, ...port }) => port),
}));
const entries = [...consumed].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
groups.push({
logicalId: logical(root.revision),
packageRevision: root.revision,
executionDigest: `sha256:${await hexDigestAsync({ implementation: own.contentDigest, exports, environment: environment(root.revision), versions: plan.definition.versions, consumed: entries })}`,
consumedPackages: [...consumedPackages].sort(),
});
}
return groups;
}
+3 -2
View File
@@ -1,4 +1,5 @@
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import { executionPlanDigest, ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import type { ExecutionInstallation } from "./installation.js";
/** Execution inputs, not build evidence. Nominal revision IDs remain dispatch
@@ -10,7 +11,7 @@ export function graphExecutionRepresentation(
kernel: unknown,
queries: unknown,
) {
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
if (installation.plan !== plan.digest || executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("GRAPH_PLAN_MISMATCH", "graph", "Expected one checked installation");
if (
!kernel ||
+17 -14
View File
@@ -35,20 +35,23 @@ export interface InspectionTemplate {
}
/** 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 };
}[];
};
export interface InspectionKernel {
operations: {
key: string;
atom: string;
interface: string;
operation: string;
effect: string;
targets: string[];
unsupported: string | null;
output: unknown;
binding: { kind: string };
}[];
}
export function compileInspectionTemplates(
plan: ExecutionPlan,
kernel: InspectionKernel = compileKernelProgram(plan) as InspectionKernel,
) {
const queries: unknown[] = [],
contracts: PublicQueryContract[] = [],
templates: InspectionTemplate[] = [];
+100 -7
View File
@@ -1,9 +1,10 @@
import { compileInspectionTemplates, type InspectionTemplate } from "./inspection.js";
import { executionPlanDigestAsync, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
import { compileInspectionTemplates, type InspectionTemplate, type InspectionKernel } from "./inspection.js";
import path from "node:path";
import { loadCheckedArtifactWorld } from "./receipts.js";
import { contentArtifactUrl, loadArtifactWorld, type LoadedArtifactWorld } from "./files.js";
import { selectArtifactImplementation, type ArtifactImplementation, type ComponentArtifact } from "./artifacts.js";
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import {
publicOperationContract,
publicQueryContract,
@@ -41,12 +42,13 @@ export interface ClientContractCatalog {
}[];
}
/** Explicit projection prevents accidental spreading of filesystem roots or private bindings. */
export function clientContractCatalog(
export async function clientContractCatalog(
plan: ExecutionPlan,
installation: ExecutionInstallation,
requestedPackages: readonly string[],
): ClientContractCatalog {
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
checkedKernel?: InspectionKernel,
): Promise<ClientContractCatalog> {
if (installation.plan !== plan.digest || (await executionPlanDigestAsync(plan)) !== plan.digest)
throw new ExecutionPlanError(
"CATALOG_PLAN_MISMATCH",
plan.digest,
@@ -69,7 +71,7 @@ export function clientContractCatalog(
.map((operation) => publicOperationContract(iface, operation)),
),
);
const inspection = compileInspectionTemplates(plan);
const inspection = compileInspectionTemplates(plan, checkedKernel);
return {
inspections: inspection.templates,
resultInterfaces: Object.fromEntries(
@@ -98,6 +100,83 @@ export function clientContractCatalog(
};
}
/** Candidate-owned compiler output, never graph state or authorization. Compile
* once, then project each requested package set without hashing/recompiling the
* plan. Reserve bounded metadata before compilation; disposal releases both the
* catalog and its charge. Callers must supply the candidate's checked inputs. */
export function createClientContractCatalog(
plan: ExecutionPlan,
installation: ExecutionInstallation,
metadata: CandidateMetadataScope,
checkedKernel?: InspectionKernel,
) {
const revisions = plan.definition.workspace.packageImports.map((pkg) => pkg.revisionId);
const installed = new Set<string>(revisions);
let catalog: ClientContractCatalog | undefined;
let preparing: Promise<void> | undefined;
let release: (() => void) | undefined;
let closed = false;
const freeze = (value: unknown, seen = new WeakSet<object>()) => {
if (!value || typeof value !== "object" || seen.has(value)) return;
seen.add(value);
for (const child of Object.values(value)) freeze(child, seen);
Object.freeze(value);
};
return {
async select(requestedPackages: readonly string[]): Promise<ClientContractCatalog> {
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
for (const revision of requestedPackages)
if (!installed.has(revision))
throw new ExecutionPlanError("UNINSTALLED_PACKAGE", revision, "Requested catalog package is not installed");
if (!catalog) {
// Like other candidate admission charges, this is an allocation estimate,
// not a V8 heap limit. The process/cgroup ceiling bounds transient work.
preparing ??= (async () => {
const reservation = metadata.reserve(32 * 1024 * 1024);
try {
const compiled = await clientContractCatalog(plan, installation, revisions, checkedKernel);
if (closed) throw Error("CANDIDATE_METADATA_CLOSED");
const bytes = Buffer.byteLength(JSON.stringify(compiled));
if (bytes > 8 * 1024 * 1024) throw Error("CANDIDATE_CATALOG_LIMIT");
freeze(compiled);
// No await between reservations: replacing the transient upper bound
// with a smaller retained estimate cannot race another admission.
reservation();
release = metadata.reserve(Math.max(64 * 1024, bytes * 4));
catalog = compiled;
} catch (error) {
reservation();
throw error;
}
})().finally(() => {
preparing = undefined;
});
await preparing;
}
if (closed || !catalog) throw Error("CANDIDATE_METADATA_CLOSED");
const requested = new Set(requestedPackages);
return {
inspections: catalog.inspections,
resultInterfaces: catalog.resultInterfaces,
operations: catalog.operations,
queries: [
...catalog.queries.filter((query) => !installed.has(query.package)),
...requestedPackages.flatMap((revision) => catalog!.queries.filter((query) => query.package === revision)),
],
components: catalog.components.filter((component) => requested.has(component.packageRevision)),
moduleBindings: catalog.moduleBindings.filter((binding) => requested.has(binding.packageRevision)),
presentations: catalog.presentations.filter((presentation) => requested.has(presentation.packageRevision)),
};
},
close() {
closed = true;
catalog = undefined;
release?.();
release = undefined;
},
};
}
export interface PackageInstallationSelection {
readonly browser?: { readonly id?: string };
readonly service?: { readonly id?: string };
@@ -113,6 +192,8 @@ export interface ExecutionInstallation {
readonly root: string;
readonly service?: ArtifactImplementation;
readonly module?: ArtifactImplementation;
/** Checked candidate only; the qualified host verifies its import surface and SDK. */
readonly restricted?: ArtifactImplementation;
readonly browserImplementation?: ArtifactImplementation;
readonly browserURL?: string;
readonly browserRegistry?: string;
@@ -251,6 +332,11 @@ function assembleExecutionInstallation(
exports: exports.map((entry) => entry.id),
})
: undefined;
const restrictedCandidates = manifest.implementations.filter(
(entry) => entry.compatibility === "quixos-restricted-module-v1",
);
if (restrictedCandidates.length > 1) fail(revision, "Ambiguous restricted module candidates");
const restricted = restrictedCandidates[0];
const browserExports = exports.filter(
(entry) =>
entry.execution.kind !== "remote" &&
@@ -258,7 +344,13 @@ function assembleExecutionInstallation(
);
if (!browserExports.length) {
if (selection.browser) fail(revision, "Package has no browser exports to select");
packages.push({ revision, root, ...(service ? { service } : {}), ...(module ? { module } : {}) });
packages.push({
revision,
root,
...(service ? { service } : {}),
...(module ? { module } : {}),
...(restricted ? { restricted } : {}),
});
continue;
}
const browser = selectArtifactImplementation(manifest, {
@@ -289,6 +381,7 @@ function assembleExecutionInstallation(
root,
...(service ? { service } : {}),
...(module ? { module } : {}),
...(restricted ? { restricted } : {}),
browserImplementation: browser,
browserURL: contentArtifactUrl(entry),
browserRegistry: browser.registryExport,
+3 -2
View File
@@ -1,3 +1,4 @@
import { executionPlanDigest, type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js";
import { portableReflectionType, portableReflectionValue } from "./reflection.js";
import type {
ValueType,
@@ -7,7 +8,7 @@ import type {
BoundDependency,
PackageExport,
} from "../capability-model/types.js";
import { type ExecutionPlan, ExecutionPlanError, hexDigest } from "./plan.js";
import { portableValueSchema } from "../bindings/portable.js";
import { subscriptionContract } from "../capability-model/journals.js";
type PreparedStep = string | null | { case: string; id: string };
@@ -25,7 +26,7 @@ function preparedInputPaths(type: ValueType, path: PreparedStep[] = []): Prepare
}
/** Small data-only interpreter input; no compiler, parser, Node module or SQL dependency reaches the browser. */
export function compileKernelProgram(plan: ExecutionPlan): unknown {
if (hexDigest(plan.definition) !== plan.digest)
if (executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("INVALID_PLAN", "kernel", "Plan digest mismatch");
const workspace = plan.definition.workspace;
if (workspace.sharedAttachments.some((attachment) => attachment.kind !== "edge"))
+13 -3
View File
@@ -1,6 +1,9 @@
import { constants } from "node:fs";
import { open } from "node:fs/promises";
const admissionFailure = (code: string, kind: "file-bytes", measured: number, limit: number) =>
Object.assign(Error(code), { metadataAdmission: { kind, measured, limit } });
/** Admission estimates, not a hard V8 heap limit. A process/cgroup ceiling is
* still required. Charge input, parse trees and derived indexes before reading
* or parsing. Each live candidate retains its reservation until explicit close. */
@@ -9,7 +12,11 @@ export class CandidateMetadataBudget {
private bytes = 0;
private scopes = 0;
private readonly owners = new Map<string, number>();
constructor(limits = { bytes: 512 * 1024 * 1024, ownerBytes: 256 * 1024 * 1024, candidates: 128 }) {
// A cutover holds both the serving and proposed installation plus transient
// 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: 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");
@@ -18,6 +25,9 @@ export class CandidateMetadataBudget {
snapshot() {
return { bytes: this.bytes, candidates: this.scopes, owners: this.owners.size, limits: this.limits };
}
ownedBytes(owner: string) {
return this.owners.get(owner) ?? 0;
}
acquire(owner: string) {
if (!owner || owner.length > 4096) throw Error("INVALID_CANDIDATE_METADATA_OWNER");
if (this.scopes >= this.limits.candidates) throw Error("CANDIDATE_METADATA_LIMIT");
@@ -109,8 +119,8 @@ export async function readCandidateMetadata(
let releaseInput: (() => void) | undefined;
try {
const stat = await handle.stat();
if (!stat.isFile() || !Number.isSafeInteger(stat.size) || stat.size > limit)
throw Error("CANDIDATE_METADATA_FILE_LIMIT");
if (!stat.isFile() || !Number.isSafeInteger(stat.size)) throw Error("CANDIDATE_METADATA_FILE_LIMIT");
if (stat.size > limit) throw admissionFailure("CANDIDATE_METADATA_FILE_LIMIT", "file-bytes", stat.size, limit);
// Reserve the input buffer and UTF-16 decoding before allocating either.
releaseInput = scope.reserve((stat.size + 1) * 3);
const bytes = Buffer.alloc(stat.size + 1);
+86 -9
View File
@@ -1,6 +1,7 @@
import { executionPlanDigest, ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import { parse } from "@babel/parser";
import { builtinModules } from "node:module";
import { ExecutionPlanError, hexDigest, type ExecutionPlan } from "./plan.js";
import type { ExecutionInstallation } from "./installation.js";
import type { ArtifactImplementation, ArtifactReader } from "./artifacts.js";
@@ -23,6 +24,75 @@ export interface ImportFinding {
readonly detail: string;
readonly line: number | null;
}
export const restrictedSDKSpecifier = "quixos:restricted-sdk/v1";
export const restrictedSDKPath = "share/quixos/restricted/core.mjs";
/** Compare trusted SDK syntax without build-machine paths in source comments.
* Keep literal/directive spelling and every executable AST node. This is SDK
* compatibility only: artifact files and the executed host SDK still have exact
* byte digests, and authored SDK bytes are never loaded by the restricted host.
*/
export function restrictedSDKIdentity(source: string): string {
if (Buffer.byteLength(source) > 2 * 1024 * 1024) throw Error("RESTRICTED_SDK_ANALYSIS_LIMIT");
const syntax = parse(source, { sourceType: "module", attachComment: false });
const metadata = new Set([
"start",
"end",
"loc",
"comments",
"leadingComments",
"innerComments",
"trailingComments",
"parenStart",
]);
let nodes = 0;
const encoded = JSON.stringify(syntax, (key, value) => {
if (metadata.has(key)) return undefined;
if (value && typeof value === "object" && typeof value.type === "string" && ++nodes > 100000)
throw Error("RESTRICTED_SDK_ANALYSIS_LIMIT");
return value;
});
return hexDigest({ codec: "quixos-restricted-sdk-syntax-v1", syntax: JSON.parse(encoded) });
}
/** Mechanical candidate checks, not authorization or an engine-safety proof.
* The caller must first verify the manifest and all declared file digests. */
export async function inspectRestrictedImplementation(
revision: string,
implementation: ArtifactImplementation,
read: ArtifactReader,
qualifiedSDKIdentity?: string,
): Promise<ImportFinding[]> {
const entry = implementation.files.find((file) => file.path === implementation.path);
const sdk = implementation.files.find((file) => file.path === restrictedSDKPath);
if (
implementation.compatibility !== "quixos-restricted-module-v1" ||
implementation.target !== "server" ||
implementation.files.length !== 2 ||
!entry ||
!sdk ||
entry === sdk ||
entry.mediaType !== "text/javascript" ||
sdk.mediaType !== "text/javascript" ||
!/^[$A-Z_a-z][$\w]*$/.test(implementation.registryExport ?? "")
)
return [{ code: "RESTRICTED_CLOSURE", detail: "Expected one closed module and the restricted SDK", line: null }];
if (entry.bytes > 1024 * 1024 || sdk.bytes > 2 * 1024 * 1024)
return [{ code: "ANALYSIS_LIMIT", detail: "Restricted closure exceeds the source budget", line: null }];
if (qualifiedSDKIdentity) {
const source = Buffer.from(await read(revision, sdk.path, sdk.bytes)).toString("utf8");
try {
if (restrictedSDKIdentity(source) !== qualifiedSDKIdentity) throw Error("SDK syntax differs");
} catch {
return [
{ code: "RESTRICTED_SDK_UNSUPPORTED", detail: "SDK differs from the qualified host profile", line: null },
];
}
}
return inspectJavaScriptSurface(Buffer.from(await read(revision, entry.path, entry.bytes)).toString("utf8"), [
restrictedSDKSpecifier,
]);
}
const nativeImports = new Set(builtinModules.flatMap((name) => [name, name.replace(/^node:/, "")]));
const ambient = new Set([
"process",
@@ -136,16 +206,14 @@ export function inspectJavaScriptSurface(source: string, checkedSDKImports: read
return findings;
}
/** Derived metadata only. Browser/server portability and available targets say
* nothing about sandbox eligibility. Build reports have no platform approval
* catalog, so they conservatively require VM placement. A host can override
* placement only for independently reviewed, exact restricted-code approvals. */
/** Derived metadata only. A build report can describe a closed candidate but
* cannot qualify the host's engine, SDK, OS sandbox or resource controller. */
export async function reportExecutionPlacement(
plan: ExecutionPlan,
installation: ExecutionInstallation,
read: ArtifactReader,
) {
if (installation.plan !== plan.digest || hexDigest(plan.definition) !== plan.digest)
if (installation.plan !== plan.digest || executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError(
"PLACEMENT_PLAN_MISMATCH",
plan.digest,
@@ -156,11 +224,13 @@ export async function reportExecutionPlacement(
const packages = await Promise.all(
installation.packages.map(async (pkg) => {
const implementations: { id: string; digest: string; compatibility: string; findings: ImportFinding[] }[] = [];
for (const implementation of [pkg.service, pkg.module].filter(
for (const implementation of [pkg.service, pkg.module, pkg.restricted].filter(
(entry): entry is ArtifactImplementation => !!entry,
)) {
let findings: ImportFinding[];
if (implementation.compatibility === "quixos-js-module-v1") {
if (implementation.compatibility === "quixos-restricted-module-v1")
findings = await inspectRestrictedImplementation(pkg.revision, implementation, read);
else if (implementation.compatibility === "quixos-js-module-v1") {
const file = implementation.files.find((file) => file.path === implementation.path)!;
if (!file || file.bytes > 1024 * 1024)
findings = [
@@ -192,7 +262,14 @@ export async function reportExecutionPlacement(
revision: pkg.revision,
requiresVM: implementations.length > 0,
restrictedEligible: false as const,
reason: "NO_QUALIFIED_RESTRICTED_RUNTIME",
reason: pkg.restricted ? "HOST_PROFILE_QUALIFICATION_REQUIRED" : "NO_RESTRICTED_IMPLEMENTATION",
restrictedCandidate: pkg.restricted
? {
digest: pkg.restricted.contentDigest,
exports: pkg.restricted.exports,
compatible: implementations.find((entry) => entry.id === pkg.restricted!.id)!.findings.length === 0,
}
: null,
implementations,
};
}),
+52 -1
View File
@@ -11,12 +11,13 @@ import type {
OperationId,
} from "../capability-model/types.js";
import { validateWorkspaceRevision } from "../capability-model/validation.js";
import { canonicalJson, contentDigest } from "../capability-model/evolution.js";
import { canonicalJson, contentDigest, contentDigestAsync } from "../capability-model/evolution.js";
import { resolveForwardedBinding } from "../capability-model/forwarding.js";
import { linkQueries, type LinkedQuery } from "../query/link.js";
import { type BindingSchema } from "../bindings/index.js";
import { unsupportedQueryOperator } from "./query-operators.js";
import { packageBuildContexts } from "./build-contract.js";
import { setImmediate } from "node:timers/promises";
export class ExecutionPlanError extends Error {
constructor(
@@ -29,6 +30,56 @@ export class ExecutionPlanError extends Error {
}
}
export const hexDigest = (value: unknown): string => contentDigest(value).slice("sha256:".length);
export const hexDigestAsync = async (value: unknown): Promise<string> =>
(await contentDigestAsync(value)).slice("sha256:".length);
// Checked runtime metadata is owned JSON, not live Graph data. Reuse its proof
// only after every reachable record is immutable. Weak keys retain neither old
// Candidates nor an application cache; unsealed compiler inputs are rehashed.
const sealedPlanDigests = new WeakMap<ExecutionPlan, string>();
export const executionPlanDigest = (plan: ExecutionPlan): string =>
sealedPlanDigests.get(plan) ?? hexDigest(plan.definition);
export const executionPlanDigestAsync = async (plan: ExecutionPlan): Promise<string> =>
sealedPlanDigests.get(plan) ?? (await hexDigestAsync(plan.definition));
/** Transfers owned parsed JSON into immutable runtime metadata. Freeze before
* yielding to hashing so concurrent mutation cannot invalidate the proof. */
export async function sealExecutionPlan(plan: ExecutionPlan): Promise<boolean> {
if (sealedPlanDigests.has(plan)) return true;
if (!Object.hasOwn(plan, "definition") || !Object.hasOwn(plan, "digest")) return false;
const pending: object[] = [plan],
seen = new WeakSet<object>();
let sliceStarted = performance.now(),
count = 0;
while (pending.length) {
const value = pending.pop()!;
if (seen.has(value)) continue;
if (
!Array.isArray(value) &&
Object.getPrototypeOf(value) !== Object.prototype &&
Object.getPrototypeOf(value) !== null
)
throw new ExecutionPlanError("INVALID_PLAN_JSON", "plan", "Expected owned JSON records");
for (const key of Reflect.ownKeys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
if (typeof key !== "string" || !Object.hasOwn(descriptor, "value"))
throw new ExecutionPlanError(
"INVALID_PLAN_JSON",
"plan",
"Runtime metadata cannot contain accessors or symbols",
);
if (descriptor.value !== null && typeof descriptor.value === "object") pending.push(descriptor.value);
}
Object.freeze(value);
seen.add(value);
if (++count % 1024 === 0 && performance.now() - sliceStarted >= 8) {
await setImmediate();
sliceStarted = performance.now();
}
}
const digest = await hexDigestAsync(plan.definition);
if (digest !== plan.digest) return false;
sealedPlanDigests.set(plan, digest);
return true;
}
const sorted = <T>(values: readonly T[], key: (value: T) => string): T[] =>
[...values].sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0));
export const publicOperationKey = (atom: string, iface: string, operation: string): string =>
+3 -2
View File
@@ -1,4 +1,5 @@
import { ExecutionPlanError, hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js";
import { executionPlanDigest, ExecutionPlanError, type ExecutionPlan, type PlannedQuery } from "./plan.js";
/** Contracts promised by a direct result. Interface dependencies are explicit; accidental
* overlap among today's implementations does not silently widen an exported interface contract. */
export function resultInterfaces(plan: ExecutionPlan, operationKey: string): string[] {
@@ -23,7 +24,7 @@ export function resultInterfaces(plan: ExecutionPlan, operationKey: string): str
}
/** Compile-time observeResult compatibility. Observation does not grant read or mutation authority. */
export function checkResultProjection(plan: ExecutionPlan, operationKey: string, queryId: string): PlannedQuery {
if (hexDigest(plan.definition) !== plan.digest)
if (executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("INVALID_PLAN", "projection", "Plan digest mismatch");
const query = plan.definition.queries.find((q) => q.id === queryId);
if (!query) throw new ExecutionPlanError("UNKNOWN_PROJECTION", queryId, "No installed query");
+13 -23
View File
@@ -1,27 +1,17 @@
import { compileInspectionTemplates } from "./inspection.js";
import { compileKernelProgram } from "./kernel.js";
import { hexDigest, type ExecutionPlan } from "./plan.js";
import { clientContractCatalog, type ExecutionInstallation } from "./installation.js";
import { compileInspectionTemplates, type InspectionKernel } from "./inspection.js";
import { hexDigestAsync, type ExecutionPlan } from "./plan.js";
import type { ClientContractCatalog } 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) {
/** Explicit public projection of checked inputs. Never serialize conformance
* storage or package ports, or recompile a historical Candidate at runtime.
* The caller supplies that Candidate's checked, cached public catalog. */
export async function publicDefinitionSnapshot(
plan: ExecutionPlan,
kernel: InspectionKernel,
catalog: ClientContractCatalog,
) {
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) => ({
@@ -68,7 +58,7 @@ export function publicDefinitionSnapshot(plan: ExecutionPlan, installation: Exec
),
})),
queries: [
...compileInspectionTemplates(plan).contracts.map((descriptor) => ({
...compileInspectionTemplates(plan, kernel).contracts.map((descriptor) => ({
id: JSON.stringify([descriptor.package, descriptor.query]),
name: "Public inspection",
package: descriptor.package,
@@ -100,5 +90,5 @@ export function publicDefinitionSnapshot(plan: ExecutionPlan, installation: Exec
{ 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 };
return { codec: "camino-definitions-v1" as const, id: await hexDigestAsync(publicRows), rows: publicRows };
}
+28 -4
View File
@@ -1,9 +1,11 @@
import { executionPlanDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
import { compileKernelProgram } from "./kernel.js";
import { compileInspectionTemplates } from "./inspection.js";
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(
@@ -23,7 +25,7 @@ const lowerTypes = (value: unknown): unknown =>
/** Checked query IR and installed getter identities, without GraphQL schemas/parsers or SQL adapters.
* Keep authored selection names only for output paths; field access uses resolved interface/member IDs. */
export function compileQueryProgram(plan: ExecutionPlan): unknown {
if (hexDigest(plan.definition) !== plan.digest)
if (executionPlanDigest(plan) !== plan.digest)
throw new ExecutionPlanError("INVALID_PLAN", "queries", "Plan digest mismatch");
const allQueries = new Map(plan.definition.queries.map(({ id, query }) => [id, query.checked]));
const compositions = plan.definition.workspace.packageImports.flatMap((pkg) =>
@@ -59,6 +61,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 +93,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 +116,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,
};
}
+3 -2
View File
@@ -1,8 +1,9 @@
import { executionPlanDigest, hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js";
import { compileQuery } from "../query/compile.js";
import { linkQueries } from "../query/link.js";
import { capabilityId } from "../capability-model/types.js";
import { portableValueSchema } from "../bindings/portable.js";
import { hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js";
import { compileKernelProgram } from "./kernel.js";
import { queryProgramEntry } from "./queries.js";
import { unsupportedQueryOperator } from "./query-operators.js";
@@ -17,7 +18,7 @@ export interface QueryDocument {
/** Runtime query documents use the same language, nominal contracts, linking
* and interpreter as packaged queries. They introduce no resources or code. */
export async function compilePublicQuery(plan: ExecutionPlan, request: QueryDocument) {
if (hexDigest(plan.definition) !== plan.digest) throw Error("INVALID_PLAN");
if (executionPlanDigest(plan) !== plan.digest) throw Error("INVALID_PLAN");
if (
typeof request.document !== "string" ||
Buffer.byteLength(request.document) > 32768 ||
+94
View File
@@ -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);
}
+61 -4
View File
@@ -41,6 +41,7 @@ import { objectRow } from "./relational-schema.js";
import { workspaceQueryRootId } from "./root.js";
import { relationalCompiler } from "./relational-compile.js";
import { fragmentSources, type FragmentResolver } from "./fragments.js";
import { lowerQueryTree, stripQueryBoundaries } from "./tree.js";
import {
QueryCompileError,
type QueryDeclaration,
@@ -158,7 +159,7 @@ export async function compileQuery(
if (node.name.value.startsWith("__")) fail("QUERY_UNSUPPORTED_FEATURE", "Introspection is not supported", node);
},
Directive(node) {
if (!["include", "skip"].includes(node.name.value))
if (!["include", "skip", "boundary"].includes(node.name.value))
fail("QUERY_UNSUPPORTED_FEATURE", `Unsupported directive ${node.name.value}`, node);
},
});
@@ -421,6 +422,7 @@ export async function compileQuery(
for (const selection of set.selections) {
if (++expandedFields > 10000) fail("QUERY_WORK_LIMIT", "Expanded query exceeds 10000 fields", selection);
if (selection.kind === "FragmentSpread") {
if (selection.directives?.some((directive) => directive.name.value === "boundary")) continue;
if (fragmentContracts.get(selection.name.value)?.mode === "reference") continue;
add(
fragments.get(selection.name.value)!.selectionSet,
@@ -519,7 +521,7 @@ export async function compileQuery(
};
const conditions = (directives: readonly DirectiveNode[] = []) =>
directives
.filter((directive) => directive.name.value !== "live")
.filter((directive) => ["include", "skip"].includes(directive.name.value))
.map((directive) => ({
include: directive.name.value === "include",
value: argument(directive.arguments!.find((entry) => entry.name.value === "if")!.value),
@@ -540,6 +542,52 @@ export async function compileQuery(
const contract = fragmentContracts.get(node.name.value);
if (contract && generated.byName.get(parent.name)?.revisionId !== contract.root)
fail("FRAGMENT_ROOT_MISMATCH", "Imported fragment requires its exact nominal root interface", node);
const directive = node.directives?.find((directive) => directive.name.value === "boundary");
if (directive) {
const name = directive.arguments?.find((argument) => argument.name.value === "name")?.value;
if (name?.kind !== "StringValue" || !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name.value))
return fail(
"QUERY_BOUNDARY_NAME",
"A boundary needs a literal identifier of at most 64 characters",
directive,
);
const root = generated.byName.get(parent.name)?.revisionId;
if (!root || root === workspaceQueryRootId)
return fail("QUERY_BOUNDARY_ROOT", "A boundary must select a nominal Graph Object", node);
if (contract?.mode === "reference")
fail("QUERY_BOUNDARY_REFERENCE", "Use an ordinary checked Fragment spread for a boundary", node);
const fragment = fragments.get(node.name.value)!;
const token = node.loc?.startToken;
return [
{
name: "$boundary",
key: "",
arguments: {},
conditions: inheritedConditions,
boundary: {
name: name.value,
root,
output: required(parent, fragment.selectionSet),
...(token ? { location: { file: node.loc!.source.name, line: token.line, column: token.column } } : {}),
},
selection: [
...(contract
? [
{
name: "$fragment",
key: "",
selection: [],
arguments: {},
conditions: [],
fragmentSpread: contract,
},
]
: []),
...selection(fragment.selectionSet, parent),
],
},
];
}
return [
...(contract
? [
@@ -598,6 +646,13 @@ export async function compileQuery(
];
});
const contract = generated.byName.get(parent.name);
const names = new Set<string>();
for (const field of selections)
if (field.boundary) {
if (names.has(field.boundary.name))
fail("QUERY_BOUNDARY_DUPLICATE", `Duplicate boundary ${field.boundary.name}`);
names.add(field.boundary.name);
}
if (!contract || contract.revisionId === workspaceQueryRootId) return selections;
// Fragment expansion may repeat metadata. Combine its children so a later
// implicit reference cannot overwrite an explicitly selected aggregate.
@@ -632,10 +687,11 @@ export async function compileQuery(
const normalized = print(document),
schema = printSchema(generated.schema);
const selections = selection(operations[0]!.selectionSet, generated.schema.getQueryType()!);
const tree = lowerQueryTree(declaration, selections, result);
const definitionDigest = createHash("sha256")
.update(
canonicalJson({
semantics: 6,
semantics: tree ? 7 : 6,
declaration,
normalized,
fragmentContracts: Object.fromEntries(fragmentContracts),
@@ -667,7 +723,8 @@ export async function compileQuery(
variables: { kind: "record", fields: variables },
output: result,
effects: [...effects.values()],
selection: selections,
selection: tree ? stripQueryBoundaries(selections) : selections,
...(tree ? { tree } : {}),
variableDefaults: Object.fromEntries(
(operations[0]!.variableDefinitions ?? [])
.filter((entry) => entry.defaultValue)
+10 -1
View File
@@ -11,6 +11,8 @@ import {
GraphQLList,
GraphQLNonNull,
GraphQLSchema,
GraphQLDirective,
DirectiveLocation,
specifiedDirectives,
visit,
type GraphQLOutputType,
@@ -294,7 +296,14 @@ export function querySchema(
return result;
};
const schema = new GraphQLSchema({
directives: specifiedDirectives,
directives: [
...specifiedDirectives,
new GraphQLDirective({
name: "boundary",
locations: [DirectiveLocation.FRAGMENT_SPREAD],
args: { name: { type: new GraphQLNonNull(GraphQLString) } },
}),
],
// Nominal projections can mention an interface not reached by a relation.
types: (() => {
const requested = new Set<string>();
+282
View File
@@ -0,0 +1,282 @@
import { valueType, type InterfaceRevisionId, type ValueType } from "../capability-model/types.js";
import {
QueryCompileError,
type QueryDeclaration,
type QuerySelection,
type QueryTreePlan,
type QueryTreeNode,
} from "./types.js";
import type { CheckedQuery } from "./types.js";
import { createHash } from "node:crypto";
import { canonicalJson } from "../capability-model/evolution.js";
export const stripQueryBoundaries = (fields: readonly QuerySelection[]): QuerySelection[] =>
fields
.filter((field) => !field.boundary)
.map((field) => ({ ...field, selection: stripQueryBoundaries(field.selection) }));
const containsBoundary = (fields: readonly QuerySelection[]): boolean =>
fields.some((field) => !!field.boundary || containsBoundary(field.selection));
/** GraphQL repeated paths form one projection. Equivalent checked selections
* reuse their binding; differing windows/guards never silently share it. */
function mergeTreeFields(fields: readonly QuerySelection[]): QuerySelection[] {
const result: QuerySelection[] = [];
for (const input of fields) {
const field = { ...input, selection: mergeTreeFields(input.selection) };
const previous = result.find(
(candidate) =>
!candidate.boundary &&
!field.boundary &&
!candidate.fragmentSpread &&
!field.fragmentSpread &&
candidate.key === field.key,
);
if (!previous) {
result.push(field);
continue;
}
const { selection: _left, ...left } = previous;
const { selection: _right, ...right } = field;
if (canonicalJson(left) !== canonicalJson(right)) {
if (containsBoundary(previous.selection) || containsBoundary(field.selection))
throw new QueryCompileError(
"QUERY_TREE_BINDING_CONFLICT",
`Alias differently guarded or parameterized paths selected as ${field.key}`,
);
result.push(field);
continue;
}
previous.selection = mergeTreeFields([...previous.selection, ...field.selection]);
}
const names = new Set<string>();
for (const field of result)
if (field.boundary) {
if (names.has(field.boundary.name))
throw new QueryCompileError("QUERY_BOUNDARY_DUPLICATE", `Duplicate boundary ${field.boundary.name}`);
names.add(field.boundary.name);
}
return result;
}
const required = (type: ValueType): ValueType =>
type.kind === "optional" || type.kind === "list" ? required(type.value) : type;
const fieldType = (type: ValueType, key: string): ValueType => {
const shape = required(type);
if (shape.kind !== "record" || !shape.fields[key])
throw new QueryCompileError("QUERY_TREE_SHAPE", `Missing checked field ${key}`);
return shape.fields[key];
};
const objectInterface = (fields: readonly QuerySelection[]): InterfaceRevisionId | undefined =>
fields
.find((field) => field.name === "_camino" && field.key === "_camino")
?.selection.find((field) => field.name === "ref")?.targetInterfaceRevisionId;
const identity = (fields: readonly QuerySelection[]): QuerySelection[] =>
fields
.filter((field) => field.name === "_camino" && field.key === "_camino")
.map((field) => ({ ...field, conditions: [], selection: field.selection.filter((child) => child.name === "ref") }));
function projectionType(type: ValueType, fields: readonly QuerySelection[]): ValueType {
if (type.kind === "optional") return valueType.optional(projectionType(type.value, fields));
if (type.kind === "list") return valueType.list(projectionType(type.value, fields));
if (type.kind !== "record") return type;
return {
kind: "record",
fields: Object.fromEntries(
fields
.filter((field) => !field.fragmentSpread)
.map((field) => {
const source = type.fields[field.key] ?? (field.key === "key" ? valueType.string : undefined);
if (!source) throw new QueryCompileError("QUERY_TREE_SHAPE", `Missing checked projection ${field.key}`);
return [field.key, projectionType(source, field.selection)];
}),
),
};
}
const keyField = (): QuerySelection => ({ name: "key", key: "key", conditions: [], arguments: {}, selection: [] });
/** Lower into complete projections and minimal binding projections. No runtime
* component inspection, executable client source, or graph handle value is involved.
* Each projection is rooted at its own nominal subject; lists retain entry keys.
*/
export function lowerQueryTree(
declaration: QueryDeclaration,
selection: QuerySelection[],
output: ValueType,
): QueryTreePlan | undefined {
if (!containsBoundary(selection)) return;
selection = mergeTreeFields(selection);
if (declaration.placement?.kind !== "replica" || declaration.fragment)
throw new QueryCompileError(
"QUERY_TREE_PLACEMENT",
"Declare a Query Tree as a replica query; its checked projections supply local views",
);
const root = selection.find((field) => field.name === "root" && field.key === "root");
if (!root || selection.some((field) => field !== root))
throw new QueryCompileError("QUERY_TREE_ROOT", "A Query Tree requires one unaliased root selection");
const plan: QueryTreePlan = { root: "root", nodes: [], projections: [], maxOccurrences: 0 };
const collectionBound = (field: QuerySelection): number | undefined => {
const limit = field.arguments.first ?? field.arguments.all;
// Checked GraphQL Int literals retain their decimal spelling in the IR.
if (limit?.kind !== "literal" || !["string", "number"].includes(typeof limit.value)) return;
const value = Number(limit.value);
return Number.isSafeInteger(value) && value > 0 ? Math.min(value, declaration.budgets.rows) : undefined;
};
const project = (id: string, subject: InterfaceRevisionId, fields: QuerySelection[], shape: ValueType) => {
if (plan.projections.length >= 256)
throw new QueryCompileError("QUERY_TREE_LIMIT", "Query Tree exceeds 256 projections");
plan.projections.push({
id,
root: id === "root/value" ? declaration.root : { kind: "object", interface: subject },
selection: [{ name: "root", key: "root", arguments: {}, conditions: [], selection: fields }],
output: { kind: "record", fields: { root: shape } },
});
return id;
};
const node = (
id: string,
subject: InterfaceRevisionId,
fields: QuerySelection[],
shape: ValueType,
occurrences: number,
): string => {
plan.maxOccurrences += occurrences;
if (!Number.isSafeInteger(plan.maxOccurrences) || plan.maxOccurrences > 256)
throw new QueryCompileError("QUERY_TREE_LIMIT", "Query Tree exceeds 256 bounded node occurrences");
const current: QueryTreeNode = {
id,
projection: project(id + "/value", subject, stripQueryBoundaries(fields), shape),
boundaries: [],
bindings: [],
};
plan.nodes.push(current);
for (const field of fields)
if (field.boundary) {
const child = field.boundary;
current.boundaries.push({
name: child.name,
conditions: field.conditions,
node: node(id + "/boundary/" + child.name, child.root, field.selection, child.output, occurrences),
});
}
for (const field of fields) {
if (field.boundary || !containsBoundary(field.selection)) continue;
const targets: { path: string[]; node: string }[] = [];
const prune = (
fields: QuerySelection[],
shape: ValueType,
path: string[],
bound: number,
listLimit = declaration.budgets.rows,
): QuerySelection[] => {
if (shape.kind === "optional") return prune(fields, shape.value, path, bound, listLimit);
if (shape.kind === "list") return prune(fields, shape.value, [...path, "[]"], bound * listLimit);
const target = objectInterface(fields);
if (target) {
targets.push({
path,
node: node(id + "/binding/" + path.map(encodeURIComponent).join("/"), target, fields, shape, bound),
});
return identity(fields);
}
const minimal = fields
.filter((field) => containsBoundary(field.selection))
.map((field) => ({
...field,
selection: prune(
field.selection,
fieldType(shape, field.key),
[...path, field.key],
bound,
collectionBound(field) ?? listLimit,
),
}));
// Relationship occurrence identity is its entry key, including duplicate
// target Objects. Never substitute list position or Object identity.
if (minimal.some((field) => field.name === "node") && !minimal.some((field) => field.name === "key"))
minimal.unshift(fields.find((field) => field.name === "key" && field.key === "key") ?? keyField());
return minimal;
};
const type = fieldType(shape, field.key);
const minimal = {
...field,
selection: prune(field.selection, type, [field.key], occurrences, collectionBound(field)),
};
const selected = [...identity(fields), minimal];
current.bindings.push({
name: field.key,
targets,
projection: project(
id + "/bindings/" + encodeURIComponent(field.key),
subject,
selected,
projectionType(shape, selected),
),
});
}
return id;
};
node("root", declaration.root.interface, root.selection, fieldType(output, "root"), 1);
return plan;
}
export const treeProjectionQuery = (tree: string, projection: string, role: "replica" | "local") =>
`${tree}/@tree/${projection}/${role}`;
/** Checked synthetic queries remain ordinary nominal queries for linking,
* authority, native input acquisition and execution. Public trees own the plan;
* applications do not copy these projections into their source declarations. */
export function queryTreeProjections(query: CheckedQuery): CheckedQuery[] {
if (!query.tree) return [];
const { tree: _tree, ...base } = query;
return query.tree.projections.flatMap((projection) => {
const selected = new Set<string>();
const walk = (fields: readonly QuerySelection[]) => {
for (const field of fields) {
if (field.interfaceRevisionId && field.memberId)
selected.add(JSON.stringify([field.interfaceRevisionId, field.memberId]));
walk(field.selection);
}
};
walk(projection.selection);
const effects = query.effects.filter(
(effect) =>
selected.has(JSON.stringify([effect.interfaceRevisionId, effect.memberId])) ||
effect.uses.some((use) => use !== "select"),
);
if (effects.some((effect) => effect.execution === "rpc-permitted"))
throw new QueryCompileError(
"QUERY_TREE_LOCAL_DEPENDENCY",
"A replica Query Tree requires locally executable snapshot projections",
);
return (["replica", "local"] as const).map((role): CheckedQuery => {
const id = treeProjectionQuery(query.declaration.id, projection.id, role);
const declaration: QueryDeclaration = {
...query.declaration,
id,
displayName: id,
root: projection.root,
placement:
role === "replica"
? { kind: "replica" }
: { kind: "local", sourceQueryId: treeProjectionQuery(query.declaration.id, projection.id, "replica") },
};
return {
...base,
declaration,
effects,
selection: projection.selection,
output: projection.output,
treeProjection: { tree: query.declaration.id, projection: projection.id, role },
definitionDigest: createHash("sha256")
.update(
canonicalJson({
parent: query.definitionDigest,
declaration,
selection: projection.selection,
output: projection.output,
}),
)
.digest("hex"),
};
});
});
}
+33
View File
@@ -94,6 +94,9 @@ export class QueryCompileError extends Error {
}
}
export interface CheckedQuery {
/** Checked decomposition; handles belong to a Client Session, never graph values. */
tree?: QueryTreePlan;
treeProjection?: { tree: string; projection: string; role: "replica" | "local" };
fragmentContracts?: Record<string, NonNullable<QuerySelection["fragmentSpread"]>>;
declaration: QueryDeclaration;
definitionDigest: string;
@@ -115,6 +118,12 @@ export type QueryArgument =
| { kind: "list"; values: QueryArgument[] }
| { kind: "object"; fields: Record<string, QueryArgument> };
export interface QuerySelection {
boundary?: {
name: string;
root: InterfaceRevisionId;
output: ValueType;
location?: QuerySourceLocation;
};
/** A checked occurrence marker, not an output field or storage capability. */
fragmentSpread?: {
mode?: "reference";
@@ -135,3 +144,27 @@ export interface QuerySelection {
relational?: import("./relational.js").QueryRelationalPlan;
predicate?: import("./relational.js").QueryPredicate;
}
export interface QueryTreeProjection {
id: string;
root: QueryRoot;
selection: QuerySelection[];
output: ValueType;
}
export interface QueryTreeNode {
id: string;
projection: string;
boundaries: { name: string; conditions: QuerySelection["conditions"]; node: string }[];
bindings: {
name: string;
projection: string;
/** [] denotes an occurrence in a bounded list; its entry key owns identity. */
targets: { path: string[]; node: string }[];
}[];
}
export interface QueryTreePlan {
root: string;
nodes: QueryTreeNode[];
projections: QueryTreeProjection[];
maxOccurrences: number;
}
+27 -2
View File
@@ -20,6 +20,9 @@ export interface ReactModulePreparation {
/** Public input type before workspace query lowering adds private projections. */
input?: ValueType;
children: ReactChildDeclaration[];
/** Binds roots/arguments only. The ordinary checked Query Tree owns every
* acquisition path; the React declaration does not repeat its descendants. */
trees?: Pick<QueryCompositionStep, "name" | "query" | "root" | "arguments">[];
}
export interface ReactPreparationArtifact {
/** Frontend queries belong to installed preparation, not the authored client API. */
@@ -51,7 +54,7 @@ export function presentationDeclarations(
};
for (const [name, value] of Object.entries(modules)) {
const declaration = object(value, `module ${name}`);
if (Object.keys(declaration).some((key) => !["inputs", "children"].includes(key)))
if (Object.keys(declaration).some((key) => !["inputs", "children", "trees"].includes(key)))
throw Error(`${name}: unknown preparation field`);
const module = pkg.modules?.find((entry) => entry.displayName === name);
if (!module) throw Error(`React declaration: unknown module ${name}`);
@@ -129,7 +132,29 @@ export function presentationDeclarations(
...(role ? { role } : {}),
};
});
artifact.modules.push({ module: module.id, composition, children: parsed });
const trees = declaration.trees ?? [];
if (!Array.isArray(trees) || trees.length > 16) throw Error(`${name}: trees must be a bounded array`);
const treeNames = new Set<string>();
const preparedTrees = trees.map((value) => {
const tree = object(value, `${name} tree`) as unknown as NonNullable<ReactModulePreparation["trees"]>[number];
if (
Object.keys(tree).some((key) => !["name", "query", "root", "arguments"].includes(key)) ||
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(tree.name) ||
treeNames.has(tree.name)
)
throw Error(`${name}: tree requires a unique name and checked root/arguments`);
treeNames.add(tree.name);
const query = pkg.queries?.find((entry) => entry.displayName === tree.query);
if (!query || query.placement?.kind !== "replica")
throw Error(`${name}.${tree.name}: requires a replica Query Tree`);
return { ...tree, query: query.id };
});
artifact.modules.push({
module: module.id,
composition,
children: parsed,
...(preparedTrees.length ? { trees: preparedTrees } : {}),
});
}
for (const module of pkg.modules ?? []) {
const contract = interfaces.find((entry) => entry.revisionId === module.interfaceRevisionId);
+3
View File
@@ -364,6 +364,9 @@ export async function expandPresentationQueries(workspace: WorkspaceRevision): P
for (const pkg of workspace.packageImports) {
for (const original of [...(pkg.checkedQueries ?? [])]) {
if (!original.declaration.id.startsWith("presentation:navigation:")) continue;
// Identity must remain independent of every occurrence's data projection.
// Full retained acquisition may include those requirements together.
if (/:identity-(inputs|view)$/.test(original.declaration.id)) continue;
const inputs = (stateInputs.get(original.declaration.root.interface) ?? []).map((input) => ({
...input,
query:
+12 -1
View File
@@ -1,7 +1,7 @@
import type { WorkspaceRevision, ValueType } from "../capability-model/types.js";
import { moduleContract } from "../capability-model/modules.js";
import { canonicalJson } from "../capability-model/evolution.js";
import { queryBindingType } from "../query/composition.js";
import { queryBindingType, compileQueryComposition } from "../query/composition.js";
import { moduleTypeContext } from "../execution-world/module-contracts.js";
import { preparationArtifact, type ReactPreparationArtifact } from "./declarations.js";
import { expandPresentationQueries } from "./expansion.js";
@@ -25,6 +25,17 @@ export async function completeWorkspace(workspace: WorkspaceRevision): Promise<v
if (!composition || composition.output.kind !== "record")
throw Error(`Missing checked inputs for ${parent.module.displayName}`);
parent.preparation.input = structuredClone(composition.output);
const treeQueries = new Map((parent.pkg.checkedQueries ?? []).map((query) => [query.declaration.id, query]));
for (const tree of parent.preparation.trees ?? []) {
if (!treeQueries.get(tree.query)?.tree)
throw Error(`${parent.module.displayName}.${tree.name}: expected a checked Query Tree`);
// Reuse Camino's ordinary nominal/argument binder. Only input parameters
// are available; a tree cannot wait on a whole presentation result.
compileQueryComposition(
{ id: `react:tree:${parent.module.id}:${tree.name}`, input: composition.input, steps: [tree], maxQueries: 1 },
treeQueries,
);
}
const outputs = new Map(Object.entries(composition.output.fields));
const dependencies: string[] = [];
const children = parent.preparation.children.map((child) => {
+15
View File
@@ -1,5 +1,20 @@
#!/usr/bin/env node
import { loadQuixosLock } from "./loader.js";
import { parseQuixosLockDocument } from "./parser.js";
import { readFile, stat } from "node:fs/promises";
// A trusted fetcher can inspect the root's declared platform pin without
// following authored import paths or evaluating Nix. The input is a fetched
// Git blob, not a filesystem tree provided by an author.
if (process.argv[2] === "--platform-only") {
const filename = process.argv[3];
if (process.argv.length !== 4 || !filename || (await stat(filename)).size > 1024 * 1024)
throw Error("INVALID_PLATFORM_SOURCE_LOCK");
const result = parseQuixosLockDocument(await readFile(filename, "utf8"));
if (!result.ok || result.document.kind !== "root") throw Error("INVALID_PLATFORM_SOURCE_LOCK");
console.log(JSON.stringify({ repository: result.document.quixos.repository, commit: result.document.quixos.commit }));
process.exit(0);
}
const fileName = process.argv[2];
if (!fileName || process.argv.length !== 3) {
+3
View File
@@ -118,6 +118,9 @@ export const loadQuixosLock = async (fileName: string): Promise<QuixosLockParseR
const absoluteRoot = path.resolve(fileName);
const repositoryRoot = path.dirname(absoluteRoot);
const rootName = path.basename(absoluteRoot);
const metadata = await lstat(absoluteRoot);
if (!metadata.isFile() || metadata.isSymbolicLink())
throw new Error("root locks must be ordinary files, not symbolic links");
const rootSource = await readFile(absoluteRoot, "utf8");
return await resolveQuixosLock(
rootSource,
+59
View File
@@ -4,6 +4,65 @@ import { mkdtemp, writeFile, symlink, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { CandidateMetadataBudget, readCandidateMetadata } from "../src/execution-world/metadata.js";
import { loadCheckedInstallationCandidate } from "../src/execution-world/candidate.js";
import { hexDigestAsync } from "../src/execution-world/plan.js";
test(
"a real checked application can stage its replacement within the default metadata budget",
{
skip: !process.env.QX_CHECKED_CANDIDATE,
},
async () => {
const root = process.env.QX_CHECKED_CANDIDATE!;
const budget = new CandidateMetadataBudget();
const first = await loadCheckedInstallationCandidate(root, {}, { budget, owner: "reader" });
try {
const retained = budget.snapshot().bytes;
const second = await loadCheckedInstallationCandidate(root, {}, { budget, owner: "reader" });
try {
assert.equal(budget.snapshot().bytes, retained * 2);
assert.deepEqual(second.reference, first.reference, "Compaction preserves exact checked identity");
assert.equal(first.world, await hexDigestAsync(first.graphExecution));
assert.equal(second.world, first.world, "Replacement retains the checked execution identity");
assert.equal(second.graphExecution.kernel, second.kernel, "Graph representation shares the validated program");
assert.equal(second.graphExecution.queries, second.queries);
// A deliberately tighter pool admits the serving installation but must
// reject replacement parsing without leaking a candidate or tree lease.
const tight = new CandidateMetadataBudget({
bytes: retained + 1,
ownerBytes: retained + 1,
candidates: 2,
});
const blocker = tight.acquire("reader");
blocker.charge(retained);
try {
await assert.rejects(
loadCheckedInstallationCandidate(root, {}, { budget: tight, owner: "reader" }),
/CANDIDATE_METADATA_LIMIT/,
);
assert.equal(tight.snapshot().bytes, retained);
assert.equal(tight.snapshot().candidates, 1);
} finally {
blocker.release();
}
const other = budget.acquire("other");
try {
assert.throws(() => other.charge(budget.limits.bytes), /CANDIDATE_METADATA_LIMIT/);
assert.equal(budget.snapshot().bytes, retained * 2, "Rejected admission must not leak charges");
} finally {
other.release();
}
} finally {
second.metadata.release();
}
assert.equal(budget.snapshot().bytes, retained, "The serving candidate survives failed or completed staging");
} finally {
first.metadata.release();
}
assert.equal(budget.snapshot().bytes, 0);
assert.equal(budget.snapshot().candidates, 0);
},
);
test("concurrent candidate reservations enforce aggregate and owner limits and release exactly once", () => {
const budget = new CandidateMetadataBudget({ bytes: 1000, ownerBytes: 600, candidates: 3 });
+22
View File
@@ -90,6 +90,28 @@ package Runtime id "package:runtime" revision "package:runtime@1" {
);
assert.equal(result.directResources.size, 1);
const resolveResource = async (source: { repository: string; commit: string }, kind: string) => ({
directory: directories.get(`${kind}\0${source.repository}\0${source.commit}`)!,
});
const bound = await Promise.all(
["workspace:first", "workspace:second"].map((workspaceId) =>
compileWorkspaceRepository({
rootDirectory: root,
resolveResource,
workspaceId,
sourceRootCommit: "5".repeat(40),
}),
),
);
assert.notEqual(bound[0]!.workspace.workspaceId, bound[1]!.workspace.workspaceId);
assert.notEqual(bound[0]!.workspace.id, bound[1]!.workspace.id);
for (const instance of bound) {
assert.equal(instance.workspace.sourceRootCommit, "5".repeat(40));
assert.deepEqual(instance.workspace.packageImports, result.workspace.packageImports);
assert.deepEqual(instance.workspace.interfaceImports, result.workspace.interfaceImports);
assert.deepEqual(instance.resources, result.resources, "binding does not fork resource identities");
}
const resource = await compileCapabilityResourceRepository({
rootDirectory: runtime,
kind: "package",
+8
View File
@@ -179,3 +179,11 @@ await writeFile(
"dist/portable-reader/variants/entries.ts",
"export type Entries = " + tsType(entries.valueType) + ";\n",
);
const { queryTreeFixture } = await import("./query-tree-fixture.js");
const { generatePackageQueryClientBindings } = await import("../src/execution-world/client-contracts.js");
const treeFixture = await queryTreeFixture();
await mkdir("dist/portable-reader/query-tree", { recursive: true });
await writeFile("dist/portable-reader/query-tree/queries.ts", generatePackageQueryClientBindings(treeFixture.pkg));
await writeFile("dist/portable-reader/query-tree/queries.json", JSON.stringify(compileQueryProgram(treeFixture.plan)));
await writeFile("dist/portable-reader/query-tree/kernel.json", JSON.stringify(compileKernelProgram(treeFixture.plan)));
+34
View File
@@ -3,6 +3,7 @@ import { test } from "node:test";
import { capabilityId as id, valueType, validateWorkspaceRevision } from "../src/capability-model/index.js";
import {
contentDigest,
contentDigestAsync,
planEvolution,
reviewSemanticMajors,
runtimeContracts,
@@ -51,6 +52,39 @@ test("hashing is canonical and rejects values outside the persisted JSON contrac
assert.throws(() => contentDigest(new Date()));
});
test("cooperative hashing preserves canonical identities and services the event loop", async () => {
const sparse = [1, 2, 3];
delete sparse[1];
const values: unknown[] = [
null,
false,
-0,
"中\\n\ud800",
sparse,
{ z: 1, a: { omitted: undefined, b: [false, 1e100] } },
];
const shared = { text: "中😀".repeat(22000) };
values.push({ first: shared, second: shared });
for (const value of values) assert.equal(await contentDigestAsync(value), contentDigest(value));
for (const value of [undefined, NaN, Infinity, new Date(), [undefined]])
await assert.rejects(contentDigestAsync(value));
const cyclic: { next?: unknown } = {};
cyclic.next = cyclic;
await assert.rejects(contentDigestAsync(cyclic), /cyclic JSON/);
const large = Array.from({ length: 50000 }, (_, index) => ({ z: "definition".repeat(8), a: index }));
const expected = contentDigest(large);
let serviced = false;
const timer = setTimeout(() => {
serviced = true;
}, 0);
try {
assert.equal(await contentDigestAsync(large), expected);
assert.equal(serviced, true, "large verification must let host renewals and RPCs run");
} finally {
clearTimeout(timer);
}
});
test("a workspace root change and display names do not restart package runtimes", () => {
const before = makeValidCapabilityWorkspace();
const after = structuredClone(before);
+8 -8
View File
@@ -45,7 +45,7 @@ const prepare = async (workspace: WorkspaceRevision) => {
test("execution reuse ignores world/source/display changes and unrelated packages", async () => {
const workspace = await loadReaderWorkspace(),
original = await prepare(workspace);
const before = original.groups()[0]!.executionDigest;
const before = (await original.groups())[0]!.executionDigest;
workspace.sourceRootCommit = "f".repeat(40);
workspace.packageImports[0]!.displayName = "Renamed";
workspace.packageImports[0]!.source.commit = "e".repeat(40);
@@ -59,13 +59,13 @@ test("execution reuse ignores world/source/display changes and unrelated package
const after = await prepare(workspace);
assert.notEqual(after.plan.digest, original.plan.digest);
assert.equal(
after.groups().find((g) => g.logicalId === workspace.packageImports[0]!.packageId)!.executionDigest,
(await after.groups()).find((g) => g.logicalId === workspace.packageImports[0]!.packageId)!.executionDigest,
before,
);
original.environments[workspace.packageImports[0]!.revisionId]!.credentialGeneration = "config:2";
assert.notEqual(original.groups()[0]!.executionDigest, before);
assert.notEqual((await original.groups())[0]!.executionDigest, before);
original.environments[workspace.packageImports[0]!.revisionId]!.placement = "module";
assert.notEqual(original.groups()[0]!.executionDigest, before);
assert.notEqual((await original.groups())[0]!.executionDigest, before);
});
test("outbound provider changes invalidate consumers while caller changes preserve providers", async () => {
@@ -108,15 +108,15 @@ test("outbound provider changes invalidate consumers while caller changes preser
dependencies: [{ portId, binding: { kind: "state", slotId: id.slot("slot:document:content") } }],
};
const prepared = await prepare(workspace);
const values = () => new Map(prepared.groups().map((g) => [g.logicalId, g.executionDigest]));
const before = values();
const values = async () => new Map((await prepared.groups()).map((g) => [g.logicalId, g.executionDigest]));
const before = await values();
prepared.installation.packages.find((p) => p.revision === "package:reader-runtime@1")!.service!.contentDigest =
"c".repeat(64);
const callerChanged = values();
const callerChanged = await values();
assert.equal(callerChanged.get("provider"), before.get("provider"));
assert.notEqual(callerChanged.get("package:reader-runtime"), before.get("package:reader-runtime"));
prepared.installation.packages.find((p) => p.revision === revision)!.service!.contentDigest = "d".repeat(64);
const providerChanged = values();
const providerChanged = await values();
assert.notEqual(providerChanged.get("provider"), callerChanged.get("provider"));
assert.notEqual(providerChanged.get("package:reader-runtime"), callerChanged.get("package:reader-runtime"));
});
+75 -2
View File
@@ -7,7 +7,11 @@ import {
checkExecutionWorld,
checkedWorldDigest,
implementationDigest,
clientContractCatalog,
hexDigest,
executionPlanDigest,
executionPlanDigestAsync,
sealExecutionPlan,
parsePackageArtifacts,
artifactPath,
type PackageArtifacts,
@@ -20,6 +24,54 @@ import { compileKernelProgram } from "../src/execution-world/kernel.js";
import { loadCompositeWorkspace } from "./composite-fixture.js";
import { standardNativeBackends } from "../src/execution-world/native-backends.js";
test("sealed runtime plans reuse verified integrity while mutable copies are rechecked", async () => {
const workspace = await loadReaderWorkspace();
const compiled = compileExecutionPlan(workspace);
const plan: ExecutionPlan = JSON.parse(JSON.stringify(compiled));
assert.equal(await sealExecutionPlan(plan), true);
assert.equal(executionPlanDigest(plan), compiled.digest);
assert.equal(await executionPlanDigestAsync(plan), compiled.digest);
assert.throws(() => {
plan.definition.workspace.sourceRootCommit = "f".repeat(40);
}, TypeError);
assert.throws(() => {
plan.digest = "0".repeat(64);
}, TypeError);
const stringify = JSON.stringify;
JSON.stringify = () => {
throw Error("Repeated serialization of sealed metadata");
};
try {
assert.equal(executionPlanDigest(plan), compiled.digest);
assert.equal(await executionPlanDigestAsync(plan), compiled.digest);
} finally {
JSON.stringify = stringify;
}
const changed: ExecutionPlan = structuredClone(plan);
changed.definition.workspace.sourceRootCommit = "f".repeat(40);
Object.freeze(changed);
assert.notEqual(executionPlanDigest(changed), plan.digest);
assert.notEqual(await executionPlanDigestAsync(changed), plan.digest);
assert.equal(await sealExecutionPlan(changed), false, "shallow freezing is not an integrity proof");
const resigned: ExecutionPlan = structuredClone(plan);
resigned.definition.workspace.sourceRootCommit = "e".repeat(40);
resigned.digest = executionPlanDigest(resigned);
assert.equal(await sealExecutionPlan(resigned), true);
assert.notEqual(executionPlanDigest(resigned), plan.digest);
workspace.sourceRootCommit = "d".repeat(40);
assert.equal(workspace.sourceRootCommit, "d".repeat(40), "sealing owned JSON leaves compiler inputs editable");
});
test("runtime plan sealing rejects accessor-backed metadata", async () => {
const plan: ExecutionPlan = compileExecutionPlan(await loadReaderWorkspace());
assert.equal(await sealExecutionPlan(Object.create(plan)), false, "inherited integrity fields cannot be sealed");
Object.defineProperty(plan.definition, "codec", { get: () => "camino-execution-plan-v1", enumerable: true });
await assert.rejects(sealExecutionPlan(plan), /INVALID_PLAN_JSON/);
});
test("optional targets close over calls into another package", async () => {
const workspace = await loadCompositeWorkspace();
const before = compileExecutionPlan(workspace).definition.operations.find(
@@ -385,7 +437,14 @@ test("public reflection contains nominal contracts and callable descriptors with
components: [],
routes: {},
};
const snapshot = publicDefinitionSnapshot(plan, installation);
const kernel = compileKernelProgram(plan) as import("../src/execution-world/inspection.js").InspectionKernel;
const catalog = await clientContractCatalog(
plan,
installation,
installation.packages.map((pkg) => pkg.revision),
kernel,
);
const snapshot = await publicDefinitionSnapshot(plan, kernel, catalog);
assert.ok(snapshot.rows.interfaces.length);
assert.ok(snapshot.rows.queries.length);
assert.ok(snapshot.rows.conformances.length);
@@ -400,5 +459,19 @@ test("public reflection contains nominal contracts and callable descriptors with
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);
assert.equal(snapshot.id, (await publicDefinitionSnapshot(plan, kernel, catalog)).id);
// Historical checked availability must survive a newer host compiler. If
// reflection silently regenerates the Kernel, this evidence disappears.
const historical = structuredClone(kernel);
const installed = historical.operations.find((operation) => !operation.unsupported)!;
assert.ok(installed);
installed.unsupported = "CHECKED_HISTORICAL_UNSUPPORTED";
const historicalCatalog = await clientContractCatalog(
plan,
installation,
installation.packages.map((pkg) => pkg.revision),
historical,
);
const historicalSnapshot = await publicDefinitionSnapshot(plan, historical, historicalCatalog);
assert.match(JSON.stringify(historicalSnapshot.rows.conformances), /CHECKED_HISTORICAL_UNSUPPORTED/);
});
+10 -2
View File
@@ -8,10 +8,18 @@ export const queryFixtureSource = {
repository: "https://query-fixture.example.test/source.git",
commit: "1".repeat(40),
};
export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
export async function queryWorkspaceFixture(options: { live?: boolean; snapshot?: boolean } = {}) {
const interfaces = new Map<string, InterfaceRevision>();
const sources: Record<string, string> = {};
function iface(name: string, members: string, parameters = "") {
if (options.snapshot) members = members.replaceAll(" ordered", "");
if (options.snapshot)
for (const member of ["name", "title", "done", "rank", "assignee", "items"])
for (const operation of ["get", "resolve"])
members = members.replace(
`${operation} id "${member}:${operation}";`,
`${operation} id "${member}:${operation}" execution snapshot;`,
);
const text = `${[...interfaces.keys()].map((name) => `import interface ${name};`).join("\n")}
interface ${name}${parameters} id "${name}" revision "${name}@1" {${members}}`;
sources[name] = text;
@@ -117,7 +125,7 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
${implementation("Task")} ${implementation("Reminder")}
conform Tasks as Collection<interface TaskFacts> id "tasks-collection" {
private edge Items id "items" {
atom Tasks projection items id "items:forward" many ordered;
atom Tasks projection items id "items:forward" many${options.snapshot ? "" : " ordered"};
interface TaskFacts projection collections id "items:inverse" many;
}
bind items.resolve to edge Items.items.resolve;
+32
View File
@@ -0,0 +1,32 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { managedResourcePath } from "../src/capability-language/managed-source-path.js";
test("source registration permits an outer workbench alias, never nested checkout or resources symlinks", async (t) => {
const scratch = await fs.mkdtemp(path.join(os.tmpdir(), "qx-managed-source-path-"));
t.after(() => fs.rm(scratch, { recursive: true, force: true }));
const root = path.join(scratch, "retained/workbench"),
alias = path.join(scratch, "guest"),
outside = path.join(scratch, "outside");
await fs.mkdir(path.join(root, "resources/Package"), { recursive: true });
await fs.mkdir(outside);
await fs.symlink(root, alias);
for (const registered of [
"resources/Package",
path.join(alias, "resources/Package"),
path.relative(root, path.join(alias, "resources/Package")),
])
assert.equal(await managedResourcePath(alias, registered), "resources/Package");
await fs.symlink(path.join(root, "resources/Package"), path.join(root, "resources/Alias"));
await assert.rejects(managedResourcePath(root, "resources/Alias"), /crosses a symlink/);
await fs.symlink(outside, path.join(root, "resources/Escape"));
await assert.rejects(managedResourcePath(root, "resources/Escape"), /escapes the workbench/);
await fs.mkdir(path.join(outside, "resources/Package"), { recursive: true });
await assert.rejects(managedResourcePath(root, path.join(outside, "resources/Package")), /escapes the workbench/);
await fs.rename(path.join(root, "resources"), path.join(root, "relocated-resources"));
await fs.symlink(path.join(root, "relocated-resources"), path.join(root, "resources"));
await assert.rejects(managedResourcePath(root, "resources/Package"), /crosses a symlink/);
});
+110 -2
View File
@@ -5,9 +5,90 @@ import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { execFile as callback } from "node:child_process";
import { planPinUpgrades, applyPinUpgrades, type UpgradeEffects } from "../src/capability-language/pin-upgrades.js";
import {
planPinUpgrades,
applyPinUpgrades,
discoverUpgradeSpec,
reviewUpgradeCandidate,
type UpgradeEffects,
} from "../src/capability-language/pin-upgrades.js";
import { capabilityId } from "../src/capability-model/index.js";
import { makeValidCapabilityWorkspace } from "./fixtures/capability-model.js";
const execFile = promisify(callback);
test("execution pin upgrades accept unchanged shared attachments without legacy migration planning", async (context) => {
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-execution-review-"));
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
await fs.writeFile(path.join(artifact, "execution-plan.json"), "{}");
const baseline = makeValidCapabilityWorkspace();
assert.ok(baseline.sharedAttachments.length);
const candidate = structuredClone(baseline);
candidate.sourceRootCommit = "f".repeat(40);
const { filename, review } = await reviewUpgradeCandidate({ artifact, baseline, candidate });
assert.equal(filename, "semantic-review.json");
assert.equal(review.checkerVersion, "quixos-semantic-review-v1");
assert.deepEqual(review.blockers, []);
assert.equal("migrationRequired" in review, false);
assert.equal("runtimeActions" in review, false);
});
test("execution pin upgrades require exact semantic-major receipts and reject foreign workspaces", async (context) => {
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-semantic-review-"));
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
await fs.writeFile(path.join(artifact, "execution-plan.json"), "{}");
const baseline = makeValidCapabilityWorkspace();
baseline.conformances[0]!.id = capabilityId.conformance("conformance:project:named");
const candidate = structuredClone(baseline);
candidate.conformances[0]!.semanticMajor = 2;
const { review } = await reviewUpgradeCandidate({ artifact, baseline, candidate });
assert.equal(review.reviews.length, 1);
assert.equal(review.reviews[0]!.accepted, false);
assert.match(review.blockers[0]!, /Semantic-major review required/);
const receipt = {
requirementDigest: review.reviews[0]!.requirementDigest,
decision: "accepted-unchanged" as const,
rationale: "Reviewed the consumer against the provider's unchanged label behavior",
agentId: "upgrade-fixture-agent",
};
assert.deepEqual(
(await reviewUpgradeCandidate({ artifact, baseline, candidate, reviews: [receipt] })).review.blockers,
[],
);
candidate.packageImports[0]!.source.commit = "f".repeat(40);
assert.equal(
(await reviewUpgradeCandidate({ artifact, baseline, candidate, reviews: [receipt] })).review.reviews[0]!.accepted,
false,
);
candidate.workspaceId = capabilityId.workspace("workspace:foreign");
await assert.rejects(() => reviewUpgradeCandidate({ artifact, baseline, candidate }), /different workspace/);
});
test("nonexecution pin upgrades retain their evolution checks", async (context) => {
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-evolution-review-"));
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
const baseline = makeValidCapabilityWorkspace();
const { filename, review } = await reviewUpgradeCandidate({
artifact,
baseline,
candidate: structuredClone(baseline),
});
assert.equal(filename, "evolution.json");
assert.equal(review.checkerVersion, "quixos-evolution-v1");
assert.ok(review.blockers.some((message) => message.includes("legacy workspace-shared attachments")));
assert.equal("migrationRequired" in review, true);
});
test("unexpected artifact access failures do not select nonexecution checks", async (context) => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-review-failure-"));
context.after(() => fs.rm(directory, { recursive: true, force: true }));
const artifact = path.join(directory, "not-a-directory");
await fs.writeFile(artifact, "not an artifact");
const candidate = makeValidCapabilityWorkspace();
await assert.rejects(() => reviewUpgradeCandidate({ artifact, baseline: candidate, candidate }), {
code: "ENOTDIR",
});
});
test(
"real jj snapshots and immutable Git publication propagate a changed interface into the root",
{ skip: !process.env.QX_CHECK_GENERATOR },
@@ -73,6 +154,21 @@ test(
await run(workbench, "git", ["--git-dir", path.join(workbench, "remotes/named.git"), "show-ref"]),
new RegExp(commit),
);
assert.equal(graph.resources[0].directory, "resources/Named");
assert.equal(graph.directResources[0].directory, "resources/Named");
// A retained workbench can move without keeping its original absolute path
// alive. Recovery journals remain bound to their original writer/root.
const relocated = `${workbench}-relocated`;
context.after(() => fs.rm(relocated, { recursive: true, force: true }));
await fs.cp(workbench, relocated, { recursive: true });
const discovered = await discoverUpgradeSpec(relocated);
assert.equal(discovered.nodes[1]!.directory, "resources/Named");
const relocatedPlan = await planPinUpgrades(relocated, { ...discovered, bootstrap: true });
assert.equal(relocatedPlan.workbench, relocated);
assert.deepEqual(
relocatedPlan.nodes.map((node) => node.directory),
["resources/Named", "root"],
);
},
);
@@ -80,6 +176,9 @@ for (const dependencyKind of ["interface", "build"] as const)
test(`${dependencyKind} pin upgrades publish children before parent locks and resume without republishing completed nodes`, async (context) => {
const workbench = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-test-"));
context.after(() => fs.rm(workbench, { recursive: true, force: true }));
const alias = `${workbench}-guest-alias`;
await fs.symlink(workbench, alias);
context.after(() => fs.unlink(alias));
const from = "a".repeat(40),
to = "b".repeat(40),
framework = "c".repeat(40);
@@ -148,7 +247,15 @@ for (const dependencyKind of ["interface", "build"] as const)
await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true });
await fs.writeFile(
path.join(workbench, ".quixos/resource-graph.json"),
JSON.stringify({ resources: [{ ...sources[1], source: { resolver: "git", ...sources[1].source } }] }),
JSON.stringify({
resources: [
{
...sources[1],
directory: path.join(alias, sources[1].directory),
source: { resolver: "git", ...sources[1].source },
},
],
}),
);
assert.deepEqual(
plan.nodes.map((node) => node.directory),
@@ -179,6 +286,7 @@ for (const dependencyKind of ["interface", "build"] as const)
assert.deepEqual(published, ["resources/Named", "root"]);
const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"));
assert.equal(graph.resources.length, 1);
assert.equal(graph.resources[0].directory, "resources/Named");
assert.equal(graph.resources[0].source.commit, to);
const next = await planPinUpgrades(workbench, {
nodes: [sources[0], { ...sources[1], source: graph.resources[0].source }],
+54
View File
@@ -6,10 +6,64 @@ import {
compileExecutionPlan,
hexDigest,
inspectJavaScriptSurface,
inspectRestrictedImplementation,
restrictedSDKIdentity,
restrictedSDKPath,
type ArtifactImplementation,
reportExecutionPlacement,
type ExecutionInstallation,
} from "../src/execution-world/index.js";
test("restricted SDK identity ignores build comments and positions, preserving executable syntax", () => {
const source = 'export function value() { return ("hello"); }';
const x86 = "// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-core/dist/portable.js\n" + source;
const arm = "// /nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-core/dist/portable.js\n\n" + source;
assert.equal(restrictedSDKIdentity(x86), restrictedSDKIdentity(arm));
for (const changed of [
'export function value() { return ("goodbye"); }',
'export function value() { return\n("hello"); }',
'export function value() { fetch("hello"); }',
'import x from "node:fs"; ' + source,
'export function value() { return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-core"; }',
])
assert.notEqual(restrictedSDKIdentity(source), restrictedSDKIdentity(changed));
assert.notEqual(restrictedSDKIdentity('"use strict";'), restrictedSDKIdentity('"use\\x20strict";'));
assert.notEqual(restrictedSDKIdentity('const value="// comment";'), restrictedSDKIdentity('const value="";'));
assert.throws(() => restrictedSDKIdentity("const ???"));
assert.throws(() => restrictedSDKIdentity(" ".repeat(2 * 1024 * 1024 + 1)), /ANALYSIS_LIMIT/);
});
test("restricted admission accepts the same SDK syntax and rejects changed SDK behavior", async () => {
const source = "export const registry = {};";
const sdk = 'export const value = "trusted";';
const implementation: ArtifactImplementation = {
id: "restricted",
target: "server",
compatibility: "quixos-restricted-module-v1",
path: "module.mjs",
ports: "quixos-transaction-ports-v1",
registryExport: "registry",
exports: ["value"],
contentDigest: "a".repeat(64),
files: [
{ path: "module.mjs", digest: "b".repeat(64), bytes: source.length, mediaType: "text/javascript" },
{ path: restrictedSDKPath, digest: "c".repeat(64), bytes: sdk.length, mediaType: "text/javascript" },
],
};
const check = (candidateSDK: string) => {
implementation.files[1]!.bytes = Buffer.byteLength(candidateSDK);
return inspectRestrictedImplementation(
"package:test@1",
implementation,
async (_revision, path) => Buffer.from(path === restrictedSDKPath ? candidateSDK : source),
restrictedSDKIdentity(sdk),
);
};
assert.deepEqual(await check("// a different build path\n" + sdk), []);
assert.equal((await check('export const value = "untrusted";'))[0]?.code, "RESTRICTED_SDK_UNSUPPORTED");
assert.equal((await check("export const ???"))[0]?.code, "RESTRICTED_SDK_UNSUPPORTED");
});
test("compiled-source inspection finds authority imports and ambient facilities without executing code", () => {
const surface = inspectJavaScriptSurface(
`
+59 -3
View File
@@ -19,12 +19,16 @@ import {
expectedArtifactReceipt,
checkExecutionInstallation,
clientContractCatalog,
createClientContractCatalog,
CandidateMetadataBudget,
compileKernelProgram,
type ComponentArtifact,
type PackageArtifacts,
} from "../src/execution-world/index.js";
import { validateWorkspaceRevision } from "../src/capability-model/index.js";
import { presentationDeclarations, preparationArtifact } from "../src/react-build/declarations.js";
import { compileQueryComposition } from "../src/query/composition.js";
import type { InspectionKernel } from "../src/execution-world/inspection.js";
const source = { repository: "https://example.test/components.git", commit: "a".repeat(40) };
const contract = (name: string) => {
@@ -245,16 +249,68 @@ test("checked component-only artifacts install without a service and reject miss
assert.ok(installation.routes[installation.components[0]!.url]);
assert.equal(JSON.stringify(installation.components).includes(root), false);
assert.equal(JSON.stringify(installation.components).includes("slot:"), false);
const catalog = clientContractCatalog(plan, installation, [pkg.revision]);
const catalog = await clientContractCatalog(plan, installation, [pkg.revision]);
assert.equal(catalog.presentations[0]!.component, "component:card");
assert.equal(catalog.components[0]!.subject, "interface:Data@1");
assert.equal(JSON.stringify(catalog).includes(root), false);
assert.equal(JSON.stringify(catalog).includes("bindingSchema"), false);
assert.throws(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/);
assert.throws(
await assert.rejects(() => clientContractCatalog(plan, installation, ["absent"]), /UNINSTALLED_PACKAGE/);
await assert.rejects(
() => clientContractCatalog(plan, { ...installation, plan: "bad" }, [pkg.revision]),
/CATALOG_PLAN_MISMATCH/,
);
const budget = new CandidateMetadataBudget();
const scope = budget.acquire("catalog-test");
const prepared = createClientContractCatalog(
plan,
installation,
scope,
compileKernelProgram(plan) as InspectionKernel,
);
try {
await assert.rejects(() => prepared.select(["absent"]), /UNINSTALLED_PACKAGE/);
assert.equal(scope.bytes, 0, "Unknown requests do not allocate compiler metadata");
const [first, coalesced] = await Promise.all([prepared.select([pkg.revision]), prepared.select([pkg.revision])]);
assert.deepEqual(first, catalog);
assert.equal(first.operations, coalesced.operations, "Concurrent preparation shares one metadata reservation");
const retained = scope.bytes;
assert.ok(retained > 0);
assert.ok(retained < 32 * 1024 * 1024, "Small catalogs retain a proportional metadata charge");
const full = await prepared.select([pkg.revision]);
const empty = await prepared.select([]);
assert.deepEqual(empty.components, []);
assert.deepEqual(empty.presentations, []);
assert.deepEqual(empty.moduleBindings, []);
assert.equal(empty.operations, full.operations, "Shared compiled metadata is reused");
assert.equal(Object.isFrozen(full.operations), true);
// Any plan access now would reveal a repeated digest/compile on the warm path.
const definition = plan.definition;
Object.defineProperty(plan, "definition", {
configurable: true,
get() {
throw Error("RECOMPILED_PLAN");
},
});
try {
assert.deepEqual(await prepared.select([pkg.revision]), full);
assert.equal(scope.bytes, retained, "Package projections do not accumulate cache entries");
} finally {
Object.defineProperty(plan, "definition", { configurable: true, value: definition });
}
prepared.close();
assert.equal(scope.bytes, 0);
await assert.rejects(() => prepared.select([pkg.revision]), /CANDIDATE_METADATA_CLOSED/);
} finally {
prepared.close();
scope.release();
}
const tight = new CandidateMetadataBudget({ bytes: 1000, ownerBytes: 1000, candidates: 1 });
const tightScope = tight.acquire("catalog-test");
const refused = createClientContractCatalog(plan, installation, tightScope);
await assert.rejects(() => refused.select([pkg.revision]), /CANDIDATE_METADATA_LIMIT/);
assert.equal(tightScope.bytes, 0);
refused.close();
tightScope.release();
} finally {
await rm(root, { recursive: true, force: true });
}
+8 -2
View File
@@ -520,7 +520,7 @@ test("React tooling generates checked owner queries from imported closed navigat
if (parsed.resource.kind !== "package") throw Error("package required");
const contracts = [...dependencies, ...(parsed.resource.specializations ?? [])];
const documents = presentationQueries(parsed.resource.revision, contracts);
assert.equal(documents.size, 2);
assert.equal(documents.size, 4);
const checked = await Promise.all(
parsed.resource.revision.queries!.map((query) =>
compileQuery(query, contracts, async (name) => documents.get(name)!),
@@ -528,10 +528,16 @@ test("React tooling generates checked owner queries from imported closed navigat
);
assert.deepEqual(
checked.map((query) => query.declaration.placement?.kind),
["replica", "local"],
["replica", "local", "replica", "local"],
);
assert.match(JSON.stringify(checked[0]!.output), /"custom":\{"kind":"record","fields":\{"label"/);
assert.match(JSON.stringify(checked[0]!.selection), /"kind":"as"/);
for (const identity of checked.slice(2)) {
assert.match(identity.declaration.id, /:identity-(inputs|view)$/);
assert.doesNotMatch(JSON.stringify(identity.output), /"custom"/);
assert.match(JSON.stringify(identity.output), /_presentationOccurrence/);
assert.match(JSON.stringify(identity.output), /_presentationSource/);
}
assert(
checked.every((query) =>
query.effects.some((effect) => effect.memberId === "mount" && effect.uses.includes("predicate")),
+37
View File
@@ -0,0 +1,37 @@
import { queryWorkspaceFixture } from "./fixtures/query-workspace.js";
import { compileQuery } from "../src/query/compile.js";
import { queryTreeProjections } from "../src/query/tree.js";
import { linkQueries } from "../src/query/link.js";
import { compileExecutionPlan } from "../src/execution-world/plan.js";
export async function queryTreeFixture() {
const fixture = await queryWorkspaceFixture({ live: true, snapshot: true });
const declaration = {
...fixture.pkg.queries!.find((query) => query.id === "upcoming")!,
id: "tree",
displayName: "Tree",
operation: "Tree",
placement: { kind: "replica" as const },
fragments: [],
};
const checked = await compileQuery(
declaration,
fixture.interfaces,
async () => `query Tree($first:Int!, $details:Boolean!) {
root {items(first:$first) {entries {node {
...Heading @boundary(name:"basic") ...Details @boundary(name:"details") @include(if:$details)
assignee {...Person @boundary(name:"person")}
}}}}
} fragment Heading on TaskFacts {title} fragment Details on TaskFacts {rank done}
fragment Person on PersonFacts {name}`,
);
const projections = queryTreeProjections(checked);
fixture.pkg.queries = [declaration, ...projections.map((query) => query.declaration)];
fixture.pkg.checkedQueries = [checked, ...projections];
// Workspace imports retain the checked Package by value in the assembler.
const imported = fixture.workspace.packageImports.find((pkg) => pkg.revisionId === fixture.pkg.revisionId)!;
imported.queries = fixture.pkg.queries;
imported.checkedQueries = fixture.pkg.checkedQueries;
fixture.workspace.linkedQueries = linkQueries(fixture.workspace);
return { ...fixture, plan: compileExecutionPlan(fixture.workspace) };
}
+206
View File
@@ -0,0 +1,206 @@
import assert from "node:assert/strict";
import test from "node:test";
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
import { compileQuery } from "../src/query/compile.js";
import type { InterfaceRevision } from "../src/capability-model/types.js";
const source = { repository: "https://example.test/tree", commit: "a".repeat(40) };
function iface(text: string, dependencies: InterfaceRevision[] = []) {
const result = compileCapabilityResourceSource(text, {
source,
environment: {
interfaces: new Map(dependencies.map((value) => [value.displayName, value])),
interfaceClosure: dependencies,
},
});
assert.ok(result.ok, JSON.stringify(result.diagnostics));
if (result.resource.kind !== "interface") throw Error("Expected Interface");
return result.resource.revision;
}
const item = iface(`interface Item id "item" revision "item@1" {
queryable value title id "title" : string {get id "title:get" execution snapshot;}
queryable value body id "body" : string {get id "body:get" execution snapshot;}
}`);
const navigation = iface(
`import interface Item; interface Navigation id "nav" revision "nav@1" {
queryable value chrome id "chrome" : string {get id "chrome:get" execution snapshot;}
queryable relation document id "document" : optional-one interface Item {resolve id "document:resolve" execution snapshot;}
queryable relation items id "items" : many interface Item ordered {resolve id "items:resolve" execution snapshot;}
}`,
[item],
);
function declaration(root: InterfaceRevision = item, dependencies: InterfaceRevision[] = []) {
const result = compileCapabilityResourceSource(
`import interface ${root.displayName}; package Views id "views" revision "views@1" {
query Open id "open" root ${root.displayName} document "open.graphql" operation "Open" {replica; max rows 10;}
}`,
{
source,
environment: { interfaces: new Map([[root.displayName, root]]), interfaceClosure: [root, item, ...dependencies] },
},
);
assert.ok(result.ok, JSON.stringify(result.diagnostics));
if (result.resource.kind !== "package") throw Error("Expected Package");
return result.resource.revision.queries![0]!;
}
const fragments = `fragment Heading on Item {title} fragment Body on Item {body}`;
test("boundaries remain complete typed projections; ordinary spreads stay inline", async () => {
const checked = await compileQuery(
declaration(),
[item],
async () => `query Open {root {...Heading ...Body @boundary(name:"body")}} ${fragments}`,
);
assert(checked.tree);
assert(!JSON.stringify(checked.output).includes('"body"'));
assert(!JSON.stringify(checked.selection).includes('"body"'));
const body = checked.tree.nodes.find((node) => node.id === "root/boundary/body")!;
assert(body);
assert(
JSON.stringify(checked.tree.projections.find((projection) => projection.id === body.projection)!.output).includes(
'"body"',
),
);
assert.equal(checked.tree.nodes[0]!.boundaries[0]!.name, "body");
const ordinary = await compileQuery(
declaration(),
[item],
async () => `query Open {root {...Heading ...Body}} ${fragments}`,
);
assert.equal(ordinary.tree, undefined);
assert(JSON.stringify(ordinary.output).includes('"body"'));
});
test("minimal nullable-root bindings exclude slow siblings and carry child handles", async () => {
const checked = await compileQuery(
declaration(navigation),
[item, navigation],
async () => `query Open {root {
...Chrome @boundary(name:"chrome") document {...Heading @boundary(name:"basic") ...Body @boundary(name:"body")}
}} fragment Chrome on Navigation {chrome} ${fragments}`,
);
const tree = checked.tree!;
const binding = tree.nodes[0]!.bindings[0]!;
assert.equal(binding.name, "document");
const projection = tree.projections.find((projection) => projection.id === binding.projection)!;
assert(!JSON.stringify(projection.selection).includes('"chrome"'));
assert(!JSON.stringify(projection.selection).includes('"title"'));
assert(!JSON.stringify(projection.selection).includes('"body"'));
assert.deepEqual(binding.targets[0]!.path, ["document"]);
assert.deepEqual(
tree.nodes.find((node) => node.id === binding.targets[0]!.node)!.boundaries.map((boundary) => boundary.name),
["basic", "body"],
);
});
test("bounded list bindings preserve entry identity without requesting details", async () => {
const checked = await compileQuery(
declaration(navigation),
[item, navigation],
async () => `query Open {root {
items(first: 3) {entries {node {...Body @boundary(name:"body")}}}
}} fragment Body on Item {body}`,
);
const tree = checked.tree!,
binding = tree.nodes[0]!.bindings[0]!;
assert.deepEqual(binding.targets[0]!.path, ["items", "entries", "[]", "node"]);
const projection = tree.projections.find((projection) => projection.id === binding.projection)!;
assert(JSON.stringify(projection.selection).includes('"name":"key"'));
assert(!JSON.stringify(projection.selection).includes('"name":"body"'));
assert(tree.maxOccurrences <= 256);
});
test("literal collection windows bound handles independently of detail row budgets", async () => {
const source = { ...declaration(navigation), budgets: { ...declaration(navigation).budgets, rows: 20000 } };
const query = (limit: string, variables = "") => `query Open${variables} {root {
items(first: ${limit}) {entries {node {...Body @boundary(name:"body")}}}
}} fragment Body on Item {body}`;
const checked = await compileQuery(source, [item, navigation], async () => query("3"));
assert.equal(checked.tree!.maxOccurrences, 7);
await assert.rejects(
compileQuery(source, [item, navigation], async () => query("200")),
/QUERY_TREE_LIMIT/,
);
await assert.rejects(
compileQuery(source, [item, navigation], async () => query("$first", "($first:Int!)")),
/QUERY_TREE_LIMIT/,
);
const group = iface(
`import interface Item; interface Group id "group" revision "group@1" {
queryable relation items id "group:items" : many interface Item ordered {resolve id "group:items:resolve" execution snapshot;}
}`,
[item],
);
const groups = iface(
`import interface Group; interface Groups id "groups" revision "groups@1" {
queryable relation groups id "groups:groups" : many interface Group ordered {resolve id "groups:groups:resolve" execution snapshot;}
}`,
[item, group],
);
const nested = { ...declaration(groups, [group]), budgets: source.budgets };
const nestedQuery = (outer: number, inner: number) => `query Open {root {
groups(first:${outer}) {entries {node {items(first:${inner}) {entries {node {...Body @boundary(name:"body")}}}}}}
}} fragment Body on Item {body}`;
const bounded = await compileQuery(nested, [item, group, groups], async () => nestedQuery(2, 3));
assert.equal(bounded.tree!.maxOccurrences, 15, "Nested windows multiply instead of reusing the outer bound");
await assert.rejects(
compileQuery(nested, [item, group, groups], async () => nestedQuery(20, 20)),
/QUERY_TREE_LIMIT/,
);
});
test("guards control boundary participation and invalid syntax fails checking", async () => {
const checked = await compileQuery(
declaration(),
[item],
async () => `query Open($show:Boolean!) {root {
...Body @boundary(name:"body") @include(if:$show)
}} fragment Body on Item {body}`,
);
assert.deepEqual(checked.tree!.nodes[0]!.boundaries[0]!.conditions, [
{ include: true, value: { kind: "variable", name: "show" } },
]);
for (const [text, error] of [
[`query Open {root {...Body @boundary(name:"")}} fragment Body on Item {body}`, /QUERY_BOUNDARY_NAME/],
[
`query Open($name:String!) {root {...Body @boundary(name:$name)}} fragment Body on Item {body}`,
/QUERY_BOUNDARY_NAME/,
],
[
`query Open {root {...Body @boundary(name:"x") ...Heading @boundary(name:"x")}} ${fragments}`,
/QUERY_BOUNDARY_DUPLICATE/,
],
[`query Open {root {body @boundary(name:"x")}}`, /QUERY_VALIDATION/],
[`query Open {root {...Cycle @boundary(name:"x")}} fragment Cycle on Item {...Cycle}`, /QUERY_VALIDATION/],
] as const)
await assert.rejects(
compileQuery(declaration(), [item], async () => text),
error,
);
});
test("equivalent repeated paths share bindings; aliases preserve different windows", async () => {
const checked = await compileQuery(
declaration(navigation),
[item, navigation],
async () => `query Open {root {
document {...Heading @boundary(name:"basic")} document {...Body @boundary(name:"body")}
recent:items(first:2) {entries {node {...Body @boundary(name:"body")}}}
earlier:items(first:3) {entries {node {...Body @boundary(name:"body")}}}
}} ${fragments}`,
);
assert.deepEqual(
checked.tree!.nodes[0]!.bindings.map((binding) => binding.name),
["document", "recent", "earlier"],
);
assert.deepEqual(
checked
.tree!.nodes.find((node) => node.id === "root/binding/document")!
.boundaries.map((boundary) => boundary.name),
["basic", "body"],
);
await assert.rejects(
compileQuery(
declaration(navigation),
[item, navigation],
async () => `query Open($show:Boolean!) {root {
document @include(if:$show) {...Heading @boundary(name:"basic")} document {...Body @boundary(name:"body")}
}} ${fragments}`,
),
/QUERY_TREE_BINDING_CONFLICT/,
);
});
+55
View File
@@ -0,0 +1,55 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, readFileSync, writeFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import test from "node:test";
import { formatQx } from "../src/capability-language/source.js";
const cli = resolve(process.cwd(), "dist/src/capability-language/tool-cli.js");
const run = (...args: string[]) => spawnSync(process.execPath, [cli, "format", ...args], { encoding: "utf8" });
const source = `workspace Test id "w" revision "w@1" commit "${"1".repeat(40)}" {\n// 雪 🦊 comment preserved\n}\n`;
test("QX batch checks are read-only; writes match single-file formatting and remain idempotent", (t) => {
const root = mkdtempSync(join(tmpdir(), "qx-format-"));
t.after(() => rmSync(root, { recursive: true, force: true }));
const files = [join(root, "one.qx"), join(root, "two 雪.qx")];
for (const file of files) writeFileSync(file, source);
const check = run("--check", ...files);
assert.notEqual(check.status, 0, check.stderr);
for (const file of files) {
assert(check.stderr.includes(file));
assert.equal(readFileSync(file, "utf8"), source);
}
const single = run(files[0]!);
assert.equal(single.status, 0, single.stderr);
assert.equal(single.stdout, formatQx(source));
const write = run("--write", ...files);
assert.equal(write.status, 0, write.stderr);
for (const file of files) assert.equal(readFileSync(file, "utf8"), single.stdout);
assert.equal(run("--check", ...files).status, 0);
assert.equal(run("--write", ...files).stdout, "");
writeFileSync(files[0]!, source);
assert.equal(run(files[0]!, "--write").status, 0);
assert.equal(readFileSync(files[0]!, "utf8"), single.stdout);
});
test("QX batch errors report every failure and do not truncate valid work", (t) => {
const root = mkdtempSync(join(tmpdir(), "qx-format-errors-"));
t.after(() => rmSync(root, { recursive: true, force: true }));
const broken = join(root, "broken.qx"),
missing = join(root, "missing.qx"),
valid = join(root, "valid.qx");
writeFileSync(broken, "workspace { invalid");
writeFileSync(valid, source);
const result = run("--write", broken, missing, valid);
assert.notEqual(result.status, 0);
assert(result.stderr.includes(broken));
assert(result.stderr.includes(missing));
assert.equal(readFileSync(broken, "utf8"), "workspace { invalid");
assert.equal(readFileSync(valid, "utf8"), formatQx(source));
writeFileSync(valid, source);
assert.notEqual(run("--write", valid, "--unknown").status, 0);
assert.equal(readFileSync(valid, "utf8"), source, "Validate arguments before writing any file");
assert.notEqual(run("--check").status, 0);
});
+236
View File
@@ -0,0 +1,236 @@
import assert from "node:assert/strict";
import { execFile as callback } from "node:child_process";
import { promisify } from "node:util";
import { mkdtemp, mkdir, writeFile, readFile, rm, symlink } from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import test from "node:test";
import { pathToFileURL } from "node:url";
import { materializeWorkspaceSources, workspaceSourcePin } from "../src/capability-language/source-closure.js";
import { prepareDevelopmentSources } from "../src/capability-language/development-sources.js";
import { backupWorkspaceSources, restoreWorkspaceSources } from "../src/capability-language/source-backup.js";
const exec = promisify(callback);
const lock = (resources: string) => `quixos-lock version 1 {
quixos source { repository "https://unavailable.test/toolchain.git"; commit "${"a".repeat(40)}"; }
${resources}
}`;
test("exact source closure recovers through explicitly selected bundles, preserves history and rejects edits", async (context) => {
const temporary = await mkdtemp(path.join(os.tmpdir(), "qx-closure-"));
context.after(() => rm(temporary, { recursive: true, force: true }));
const locations: Record<string, string> = {};
const repository = async (name: string, resources: string) => {
const directory = path.join(temporary, name);
await mkdir(directory);
const git = async (...args: string[]) => (await exec("git", args, { cwd: directory })).stdout.trim();
await git("init");
await writeFile(path.join(directory, "quixos.lock"), lock(""));
await git("add", ".");
const commit = async () => {
await git("-c", "user.name=Test", "-c", "user.email=test@example.test", "commit", "-am", "Source");
return await git("rev-parse", "HEAD");
};
const parent = await commit();
await writeFile(path.join(directory, "quixos.lock"), lock(resources));
await writeFile(path.join(directory, "marker"), name);
await git("add", ".");
const rev = await commit();
await git("tag", `quixos-reachability/${rev}`);
const bundle = path.join(temporary, `${name}.bundle`);
await git("bundle", "create", bundle, `refs/tags/quixos-reachability/${rev}`);
const source = { repository: `https://unavailable.test/${name}.git`, commit: rev };
locations[source.repository] = pathToFileURL(bundle).href;
return { ...source, parent };
};
const common = await repository("common", "");
const dependency = `interface Common source { repository "${common.repository}"; commit "${common.commit}"; }`;
const left = await repository("left", dependency),
right = await repository("right", dependency);
const root = await repository(
"root",
`
package Left source { repository "${left.repository}"; commit "${left.commit}"; }
package Right source { repository "${right.repository}"; commit "${right.commit}"; }`,
);
const directory = path.join(temporary, "checkout");
const closure = await materializeWorkspaceSources({ source: root, directory, locations, concurrency: 2 });
assert.deepEqual(
await materializeWorkspaceSources({ source: root, directory, repositoryPrefix: "https://unavailable.test/" }),
closure,
);
// Allow this root but not its dependencies: reject them before Git acquisition.
const scopedRoot = { ...root, repository: "https://unavailable.test/selected/root.git" };
await assert.rejects(
materializeWorkspaceSources({
source: scopedRoot,
directory: path.join(temporary, "scoped"),
locations: { ...locations, [scopedRoot.repository]: locations[root.repository] },
repositoryPrefix: "https://unavailable.test/selected/",
}),
/outside the acquisition boundary/,
);
await assert.rejects(
materializeWorkspaceSources({ source: root, directory, repositoryPrefix: "https://unavailable.test/root.git/" }),
/outside the acquisition boundary/,
);
assert.equal(closure.resources.length, 4, "diamond dependency is materialized once");
assert.deepEqual(JSON.parse(await readFile(path.join(directory, "sources.json"), "utf8")), closure);
const rootEntry = closure.resources.find((entry) => entry.kind === "workspace")!;
const rootDirectory = path.join(directory, rootEntry.directory);
const rootLock = path.join(rootDirectory, "quixos.lock");
const rootBody = await readFile(rootLock, "utf8");
const outsideLock = path.join(temporary, "outside.lock");
await writeFile(outsideLock, rootBody);
// Exercise the shared loader directly: committed root symlinks must not let
// privileged source inspection read outside the acquired repository.
const { loadQuixosLock } = await import("../src/resource-lock/index.js");
const linkedLock = path.join(temporary, "linked.lock");
await symlink(outsideLock, linkedLock);
await assert.rejects(loadQuixosLock(linkedLock), /root locks must be ordinary/);
const backup = path.join(temporary, "retained");
const manifest = await backupWorkspaceSources({ source: root, directory, output: backup });
assert.equal(manifest.repositories.length, 4);
const restored = await restoreWorkspaceSources({ backup, directory: path.join(temporary, "restored") });
assert.deepEqual(restored, closure, "Source-only recovery retains canonical identity without an available forge");
await assert.rejects(backupWorkspaceSources({ source: root, directory, output: backup }), /must not exist/);
const missing = {
...manifest,
repositories: manifest.repositories.filter((entry) => entry.repository !== common.repository),
};
await writeFile(path.join(backup, "backup.json"), JSON.stringify(missing));
await assert.rejects(
restoreWorkspaceSources({ backup, directory: path.join(temporary, "incomplete") }),
/omits required repository/,
);
await writeFile(path.join(backup, "backup.json"), JSON.stringify(manifest));
await writeFile(path.join(backup, manifest.repositories[0].bundle), "damaged backup");
await assert.rejects(
restoreWorkspaceSources({ backup, directory: path.join(temporary, "damaged") }),
/integrity mismatch/,
);
assert.equal((await exec("git", ["rev-parse", "HEAD^"], { cwd: rootDirectory })).stdout.trim(), root.parent);
assert.equal(
(await exec("git", ["remote", "get-url", "origin"], { cwd: rootDirectory })).stdout.trim(),
root.repository,
);
// Cached recovery does not need the primary or the backup.
assert.deepEqual(await materializeWorkspaceSources({ source: root, directory }), closure);
const pinFile = path.join(temporary, "source.json");
await writeFile(pinFile, JSON.stringify(root));
const cli = path.resolve(import.meta.dirname, "../src/capability-language/tool-cli.js");
const command = await exec(process.execPath, [cli, "source-checkout", pinFile, "--dir", directory]);
assert.deepEqual(JSON.parse(command.stdout), closure, "Protocol CLI preserves exact cached source identity");
const scoped = await exec(process.execPath, [
cli,
"source-checkout",
pinFile,
"--dir",
directory,
"--repository-prefix",
"https://unavailable.test/",
]);
assert.deepEqual(JSON.parse(scoped.stdout), closure);
const cliBackup = path.join(temporary, "cli-backup");
const backupCommand = await exec(process.execPath, [
cli,
"source-backup",
pinFile,
"--dir",
directory,
"--output",
cliBackup,
]);
assert.equal(JSON.parse(backupCommand.stdout).repositories.length, 4);
const restoreCommand = await exec(process.execPath, [
cli,
"source-restore",
cliBackup,
"--dir",
path.join(temporary, "cli-restore"),
]);
assert.deepEqual(JSON.parse(restoreCommand.stdout), closure);
const developmentCommand = await exec(process.execPath, [
cli,
"source-develop",
pinFile,
"--dir",
path.join(temporary, "cli-developer"),
"--cache",
directory,
]);
const developmentResult = JSON.parse(developmentCommand.stdout);
assert.equal(developmentResult.repositories, 4);
assert.deepEqual(developmentResult.source, closure.root);
const a = await prepareDevelopmentSources({
source: root,
cacheDirectory: directory,
directory: path.join(temporary, "a"),
});
const b = await prepareDevelopmentSources({
source: root,
cacheDirectory: directory,
directory: path.join(temporary, "b"),
});
assert.notEqual(a.workspaceId, b.workspaceId);
const assigned = "f8f2f8a3-328c-45a2-847a-31869ad28ce7";
const c = await prepareDevelopmentSources({
source: root,
cacheDirectory: directory,
directory: path.join(temporary, "assigned"),
workspaceId: assigned,
});
assert.equal(c.workspaceId, assigned);
assert.equal(
JSON.parse(await readFile(path.join(c.directory, ".quixos/resource-graph.json"), "utf8")).workspaceId,
assigned,
);
await assert.rejects(
prepareDevelopmentSources({
source: root,
cacheDirectory: directory,
directory: path.join(temporary, "invalid"),
workspaceId: "not-a-workspace",
}),
/Workspace UUID/,
);
const aRoot = path.join(a.directory, "root"),
bRoot = path.join(b.directory, "root");
assert.equal((await exec("git", ["remote", "get-url", "origin"], { cwd: aRoot })).stdout.trim(), root.repository);
assert.equal(
await readFile(path.join(aRoot, "quixos.lock"), "utf8"),
await readFile(path.join(rootDirectory, "quixos.lock"), "utf8"),
);
await exec("git", ["cat-file", "-e", root.parent], { cwd: aRoot });
await writeFile(path.join(aRoot, "marker"), "Independent developer change");
assert.equal(await readFile(path.join(bRoot, "marker"), "utf8"), "root");
assert.equal(await readFile(path.join(rootDirectory, "marker"), "utf8"), "root");
await assert.rejects(
prepareDevelopmentSources({ source: root, cacheDirectory: directory, directory: a.directory }),
/never overwritten/,
);
await writeFile(path.join(rootDirectory, "marker"), "edited");
await assert.rejects(materializeWorkspaceSources({ source: root, directory, locations }), /modified/);
await assert.rejects(
materializeWorkspaceSources({
source: root,
directory: path.join(temporary, "bounded"),
locations,
maxRepositories: 2,
}),
/repository limit/,
);
await assert.rejects(
materializeWorkspaceSources({
source: root,
directory,
locations: { [root.repository]: "https://secret:password@example.test/root.git" },
}),
/transport substitution/,
);
});
test("Workspace pins reject floating revisions and embedded credentials", () => {
assert.throws(() => workspaceSourcePin({ repository: "https://example.test/root.git", commit: "main" }));
assert.throws(() =>
workspaceSourcePin({ repository: "https://user:password@example.test/root.git", commit: "a".repeat(40) }),
);
});
+46
View File
@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import test from "node:test";
import { mkdtemp, writeFile, readFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { workspaceSourceLocks } from "../src/capability-language/source-locks.js";
test("fork lock proposals follow declared fragments and ignore comments and unrelated lock files", async (context) => {
const directory = await mkdtemp(path.join(os.tmpdir(), "qx-lock-fork-"));
context.after(() => rm(directory, { recursive: true, force: true }));
const from = { repository: "https://source.test/app/runtime.git", commit: "b".repeat(40) };
const to = { repository: "https://source.test/fork/runtime.git", commit: "c".repeat(40) };
const root = `quixos-lock version 1 {
quixos source { repository "https://source.test/quixos.git"; commit "${"a".repeat(40)}"; }
import "resources.lock";
// package Fake source { repository "https://unrelated.test/secret.git"; commit "${"d".repeat(40)}"; }
}`;
const fragment = `quixos-lock fragment version 1 {
// Preserve this note: 雨 📚
package Runtime source { repository "${from.repository}"; commit "${from.commit}"; }
}`;
await writeFile(path.join(directory, "quixos.lock"), root);
await writeFile(path.join(directory, "resources.lock"), fragment);
await writeFile(path.join(directory, "unrelated.lock"), "This is not a Quixos lock.");
const result = await workspaceSourceLocks(directory, [{ from, to }]);
assert.equal(result.lock.resources.length, 1);
assert.deepEqual(result.lock.resources[0].source, { resolver: "git", ...to });
assert.equal(result.files.find((file) => file.path === "quixos.lock")!.source, root);
assert.match(result.files.find((file) => file.path === "resources.lock")!.source, /Preserve this note: 雨 📚/);
assert.deepEqual(result.files.map((file) => file.path).sort(), ["quixos.lock", "resources.lock"]);
assert.equal(
await readFile(path.join(directory, "resources.lock"), "utf8"),
fragment,
"Proposing a fork does not edit selected source",
);
await assert.rejects(
workspaceSourceLocks(directory, [
{ from, to },
{ from, to },
]),
/Duplicate/,
);
await assert.rejects(workspaceSourceLocks(directory, [{ from, to: { ...to, commit: "main" } }]), /exact commit/);
await writeFile(path.join(directory, "quixos.lock"), root.replace('"resources.lock"', '"../outside.lock"'));
await assert.rejects(workspaceSourceLocks(directory), /Invalid source lock/);
});