Compare commits

...
Author SHA1 Message Date
Quixos Subtree Publisher ea923c40c4 Publish quixos-nix-helpers from 039f055a7dcff2a29d59a8c37d7433b269b50f7a 2026-10-07 11:04:32 +00:00
Timothy J. Aveni 47fcb47d5c Implement VM-scoped restricted registry transport 2026-10-07 04:04:32 -07:00
Quixos Subtree Publisher 91679e8cc8 Publish quixos-nix-helpers from e5aec2bb26110ed474929f353ee51ff46d4f20d6 2026-10-01 11:03:19 +00:00
Timothy J. Aveni 1f7aa80130 Instrument and improve incremental workspace checks
Profile source capture, Nix evaluation/realization, resource/query checking,
and implementation builds without putting timings into immutable outputs.
Keep modular repositories; bound independent capture work, snapshot jj once
per observation, and retain final concurrent-edit and Git completeness checks.

Compose exact resource/closure/frontend/toolchain checks as memoized Nix
derivations. Preserve whole-workspace validation and the full compiler path
when no checked dependency is supplied. Share canonical Nixpkgs by content,
index workspace build contexts once, and narrow lock/driver/compiler inputs
with conservative complete-source fallback. Batch React witness checks only
when distinct source closures cannot share ambient declarations; otherwise
run the original independent TypeScript checks.

Seed local authoring toolchains with exact canonical fetch identities when
they match the selected inputs; retain store-path inputs for overrides.
Add generic correctness checks rather than permanent Reader timing/content
fixtures. Verify actual Reader edits and rejection/recovery in a private
workbench, preserving the original user's workbench and runtime.

Query checking currently caches at resource/closure boundaries, not individual
query semantic dependency boundaries. Template helper pins and reproducible
fixture snapshots follow in a separate publication change.
2026-10-01 04:03:19 -07:00
Timothy J. Aveni 6227d64b08 Build Camino packages from explicit contract inputs
Project source-free capability contracts and keep exact source provenance in package assembly. Reuse compiled groups across unrelated graph and dependency implementation edits. Keep React public inputs separate from workspace query lowering and retain explicit type-companion dependencies.

Allow independent execution-package checks, scope resource compilation to declared dependencies, and memoize source closure traversal. Document generic-specialization limits and measured Reader rebuild behavior.

Verified protocol, artifact, package, React, and managed activation checks; retained-Git independent/workspace artifact equality and incompatible type dependency rejection; nix fmt -- --check and lint. One-off Reader measurements recompiled 1 of 13 groups for a child edit and 0 for a workspace QX default edit.
2026-09-30 22:55:07 -07:00
Timothy J. Aveni 988e10263a Add graph-aware React controls with stable asynchronous selection
Add typed command/live bindings, Choice and Toggle, CRDT text wrappers, and explicit browser-local controls. Migrate Reader and current example inputs. Enforce editable-control authoring checks in the React artifact backend.

Command controls display query observations, permit one local execution at a time, and delay pending feedback for 200 ms. Include keyboard, focus, shadow-root, top-layer popup, rejection/no-op, native-field eligibility, and IME coverage.

Verified SDK builds and tests, synchronous-editing browser qualification, Reader/Tasks qualification, presentation-mount qualification, and the review-dashboard browser test. Monorepo formatting and lint pass.

The full composition qualification stops at the unchanged combined-to-review workspace fixture transition: STORAGE_MODEL_CHANGED from changed/removed stored conformance owners. Its dashboard browser test passes when run directly.
2026-09-30 00:20:00 -07:00
Timothy J. Aveni 8e55a11c4a Correct Reader producer completion and prepare only the active source
Route durable completion to the initiating PresentationMount, guarded against stale navigation and unrelated fan-out. Publish Gutenberg passages directly, preserve unfinished manual drafts, and open completed documents in the main reader pane. Prepare the selected provider and explicitly opened inspector only. Consolidate Studio factory contracts so optional execution placement is determined by implementations. Support typed stateful presentation props and local-module role selection in the React build layer. Update integration, browser, portal, and authoring documentation.
2026-09-28 21:54:18 -07:00
Timothy J. Aveni b0d9453a51 Make local graph execution optional across composed packages 2026-09-28 21:05:42 -07:00
Timothy J. Aveni 009c5fcb4d Complete prepared presentation build and runtime integration 2026-09-27 18:51:05 -07:00
Timothy J. Aveni d6efe5622d Add shared Quixos React controls and delayed component fallback 2026-09-24 12:11:15 -07:00
Timothy J. Aveni 99b6b5110f Make Camino examples and scaffolds complete Yarn and Nix packages 2026-09-22 15:31:42 -07:00
27 changed files with 1626 additions and 152 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
"sourceCommit": "54e443af5dd963f1fa6f83f062f4943b46ad2535",
"sourceCommit": "039f055a7dcff2a29d59a8c37d7433b269b50f7a",
"sourcePath": "quixos-instance/quixos-nix-helpers",
"exportName": "quixos-nix-helpers",
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-nix-helpers.git"
+77 -2
View File
@@ -8,9 +8,9 @@ receipts. It has no migration step or package-defined shell hooks.
```nix
# Inside a per-system flake output:
quixosPackages.checkedArtifacts =
{ plan, generator, core, sharedRuntime, typescript, packageRevisionId }:
{ buildContract, sourceDigest, generator, core, sharedRuntime, typescript, packageRevisionId, ... }:
helpers.mkCaminoArtifacts {
inherit pkgs plan core sharedRuntime typescript packageRevisionId;
inherit pkgs buildContract sourceDigest core sharedRuntime typescript packageRevisionId;
protocol = generator;
src = ./.;
portable = {
@@ -22,6 +22,25 @@ quixosPackages.checkedArtifacts =
};
```
`buildContract` is a canonical, source-free capability slice. `sourceDigest`
records the exact selected package source in the final installation manifest;
it does not enter the reusable compilation group. Ordinary source dependencies,
SDKs and React props companions remain explicit Nix inputs. Raw QX and resource
lock files enter through the checked contract and evaluated recipe, and are
excluded from the implementation source tree. The `plan` argument on the low-level
helper is a composition convenience for fixtures; it is projected before building.
Package recipes receive the contract directly and can be checked without a workspace.
Inside `checkedArtifacts = context: ...`, the package helpers expose
`camino.packageSet { inherit nixpkgs system context; }` as the usual package set
selection. It shares the workspace composition's evaluated Nixpkgs when the
source content, system, and default import arguments match. Pass ordinary Nixpkgs
arguments such as `config` or `overlays` to use a separate import. A direct
`import nixpkgs { inherit system; }` also works, with additional evaluation cost.
The implementation groups depend on the complete execution compiler bundle and
JavaScript build drivers by content; unrelated checker CLI and publication
metadata changes do not enter those inputs.
The handler entry exports `registry = createRegistry(handlers)` from the generated
bindings. One registry contains many exports. The builder always supplies a server
implementation for the generated process service. A service-only package instead
@@ -72,3 +91,59 @@ proves that a component-only edit preserves a mixed package's service digest.
Run `nix run .#camino-component-source-qualification` from the monorepo to
materialize separate retained Git resources and compile an ordinary Nix candidate.
This is a source/build qualification harness; it does not deploy a workspace.
## Restricted execution in a workspace VM
Add `service.restricted = true` to a checked portable package to select the pinned
`workerd` executor. Generated bindings and the ordinary transaction-port protocol
stay the same. All server exports must belong to one checked portable registry;
remote exports and custom process services retain their ordinary VM execution
path. The restricted build rejects Node imports and unresolved dependencies,
and supplies an explicit SDK for value codecs, identities and declared graph ports.
```nix
service = {
restricted = true;
limits = {
deadlineMs = 4000;
maxBytes = 1048576;
maxPortCalls = 1000;
maxQueued = 8;
};
};
```
Each invocation gets a fresh engine process. The adapter admits one active frame
and a bounded FIFO; queue wait consumes the deadline. Overflow is an explicit
`RESTRICTED_QUEUE_FULL` result. Cancellation kills the engine process group and
removes its sockets before acknowledgement. All graph calls inherit the trusted
invocation identity; authored code cannot choose another frame or workspace.
Native graph grants and transaction commit remain enforced by Camino outside
the guest. Initialization and synchronous loops are subject to the same deadline.
Direct network access, native filesystem imports, ambient clock and entropy,
parent environment credentials, and unlisted modules are denied. Explicit dates,
`Intl.Segmenter`, deterministic hashes and normal JavaScript computation remain
available. Host-managed HTTP bridges expose only the current frame's declared
graph ports. Authored serialization is untrusted protocol input.
This is an inner execution restriction, **not a hardened shared-process sandbox**.
One workspace owns the surrounding VM. Systemd limits the entire package cgroup,
including adapter and child engines; memory exhaustion terminates that group.
No per-isolate memory protection is claimed. A guest user can control its VM,
so authoritative graph grants and credentials must remain outside it.
Qualification commands:
```sh
nix build .#checks.x86_64-linux.camino-restricted-execution
nix build .#checks.x86_64-linux.camino-restricted-package-runners
nix build .#checks.x86_64-linux.camino-restricted-execution-vm
```
The first uses the actual pinned engine for frame isolation, graph ports,
SDK codecs, denied ambient access, cancellation and overload. The second runs
ordinary package dispatch, rollback, PostgreSQL and browser replica tests with
the restricted reader fixture. The VM check adds actual cgroup allocation
exhaustion, neighboring-service survival, native socket egress rejection and
fresh-frame timing. VM qualification is independent of hosted rollout.
+73 -8
View File
@@ -2,7 +2,25 @@
{
pkgs,
src,
plan,
plan ? null,
buildContract ?
(import ./package-context.nix {
inherit
pkgs
protocol
plan
packageRevisionId
;
}).buildContract,
sourceDigest ?
(import ./package-context.nix {
inherit
pkgs
protocol
plan
packageRevisionId
;
}).sourceDigest,
protocol,
core,
sharedRuntime,
@@ -16,19 +34,25 @@
group ? false,
}:
let
compiler = "${protocol}/libexec/quixos-protocol/execution-world.mjs";
buildTools = import ./build-tools.nix;
# The standalone bundle is the complete code dependency of this driver.
# Changes to unrelated checker CLIs must not invalidate every package group.
compiler = builtins.path {
path = "${protocol}/libexec/quixos-protocol/execution-world.mjs";
name = "execution-world.mjs";
};
config = pkgs.writeText "camino-artifact-build.json" (
builtins.toJSON {
inherit
plan
buildContract
compiler
packageRevisionId
core
sharedRuntime
nodeModules
service
group
;
group = true;
components =
if components == null then
null
@@ -39,7 +63,10 @@ let
bindingOutput = components.bindingOutput or "src/gen/client.ts";
contracts = components.contracts or [ ];
};
source = src;
source = import ./implementation-source.nix {
inherit (pkgs) lib;
inherit src;
};
portable =
if portable == null then
null
@@ -69,9 +96,47 @@ let
esbuild = "${pkgs.esbuild}/bin/esbuild";
shell = "${pkgs.runtimeShell}";
serviceAdapter = ./portable-service.mjs;
restricted =
if service.restricted or false then
let
executor = import ./restricted-execution { inherit pkgs core; };
in
{
runtime = "${executor.runtime}/bin/quixos-restricted-registry";
sdk = "${executor.sdk}/core.mjs";
limits = service.limits or { };
}
else
null;
}
);
compiled =
pkgs.runCommand "camino-compiled-artifacts"
{
passthru.buildConfig = config;
QUIXOS_TRACE_PROFILE = "1";
}
''
${pkgs.nodejs_24}/bin/node ${buildTools}/build-artifacts.mjs ${config}
'';
in
pkgs.runCommand "camino-checked-artifacts" { passthru.buildConfig = config; } ''
${pkgs.nodejs_24}/bin/node ${./.}/build-artifacts.mjs ${config}
''
if group then
compiled
else
(import ./assemble-package.nix {
inherit pkgs;
artifacts = [ compiled ];
context = {
inherit
buildContract
sourceDigest
packageRevisionId
sharedRuntime
;
generator = protocol;
};
})
// {
caminoGroup = compiled;
buildConfig = config;
}
+14 -20
View File
@@ -6,9 +6,10 @@ import { buildService } from "./build-service.mjs";
const config = JSON.parse(await fs.readFile(process.argv[2], "utf8"));
const compiler = await import(pathToFileURL(config.compiler));
const plan = JSON.parse(await fs.readFile(config.plan, "utf8"));
const pkg = plan.definition.packages.find((pkg) => pkg.revision === config.packageRevisionId);
if (!pkg || compiler.hexDigest(plan.definition) !== plan.digest) throw Error("Invalid package assembly context");
const contract = JSON.parse(await fs.readFile(config.buildContract, "utf8"));
compiler.checkPackageBuildContract(contract);
const pkg = contract.definition.package;
if (pkg.revision !== config.packageRevisionId) throw Error("Invalid package assembly context");
const output = process.env.out;
if (!output) throw Error("Package assembly requires an output directory");
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -23,18 +24,17 @@ const recordFile = async (name, mediaType) => {
};
await fs.mkdir(path.join(output, "share/quixos/generated"), { recursive: true });
for (const root of config.groups) {
const manifest = compiler.parsePackageArtifacts(
const manifest = compiler.parseArtifactGroup(
JSON.parse(await fs.readFile(path.join(root, "share/quixos/package-artifacts.json"), "utf8")),
);
if (
manifest.packageRevisionId !== pkg.revision ||
manifest.bindingDigest !== pkg.bindingDigest ||
manifest.sourceDigest !== pkg.sourceDigest ||
JSON.stringify(manifest.versions) !== JSON.stringify(plan.definition.versions)
manifest.bindingDigest !== contract.digest ||
JSON.stringify(manifest.versions) !== JSON.stringify(contract.definition.versions)
)
throw Error("Artifact group belongs to a different checked package");
const receipt = JSON.parse(await fs.readFile(path.join(root, "share/quixos/build-receipt.json"), "utf8"));
const expected = compiler.expectedArtifactReceipt(plan, manifest, generatorDigest);
const expected = compiler.expectedGroupReceipt(contract, manifest, generatorDigest);
if (JSON.stringify(receipt) !== JSON.stringify(expected.receipt))
throw Error("Artifact group has an invalid build receipt");
const read = await compiler.artifactFileReader({ [pkg.revision]: root });
@@ -80,24 +80,18 @@ if (!implementations.some((entry) => entry.compatibility === "quixos-service-v1"
const manifest = compiler.parsePackageArtifacts({
schemaVersion: 1,
packageRevisionId: pkg.revision,
bindingDigest: pkg.bindingDigest,
sourceDigest: pkg.sourceDigest,
versions: plan.definition.versions,
bindingDigest: contract.digest,
sourceDigest: config.sourceDigest,
versions: contract.definition.versions,
implementations,
...(components.length ? { components } : {}),
});
const { receipt, sources } = compiler.expectedArtifactReceipt(plan, manifest, generatorDigest);
const { receipt, sources } = compiler.expectedPackageReceipt(contract, manifest, config.sourceDigest, generatorDigest);
for (const [name, bytes] of sources) {
const actual = await fs.readFile(path.join(output, name));
if (!Buffer.from(bytes).equals(actual)) throw Error("Assembly lacks checked generated source " + name);
}
await fs.writeFile(path.join(output, "share/quixos/package-artifacts.json"), JSON.stringify(manifest, null, 2));
await fs.writeFile(path.join(output, "share/quixos/build-receipt.json"), JSON.stringify(receipt, null, 2));
// Check package coverage with the same public validator used at installation.
const packagePlan = { definition: { ...plan.definition, packages: [pkg] }, digest: "" };
packagePlan.digest = compiler.hexDigest(packagePlan.definition);
await compiler.checkExecutionWorld(
packagePlan,
[manifest],
await compiler.artifactFileReader({ [pkg.revision]: output }),
);
// Source selection is attested here; compilation groups retain their own receipt.
compiler.checkPackageCoverage({ ...pkg, bindingDigest: contract.digest, sourceDigest: config.sourceDigest }, manifest);
+13 -3
View File
@@ -4,12 +4,22 @@
artifacts,
}:
let
buildTools = import ./build-tools.nix;
groups = map (artifact: artifact.caminoGroup or artifact) artifacts;
compiler = builtins.path {
path = "${context.generator}/libexec/quixos-protocol/execution-world.mjs";
name = "execution-world.mjs";
};
config = pkgs.writeText "camino-package-assembly.json" (
builtins.toJSON {
inherit groups;
inherit (context) plan packageRevisionId sharedRuntime;
compiler = "${context.generator}/libexec/quixos-protocol/execution-world.mjs";
inherit (context)
buildContract
sourceDigest
packageRevisionId
sharedRuntime
;
inherit compiler;
node = "${pkgs.nodejs_24}/bin/node";
esbuild = "${pkgs.esbuild}/bin/esbuild";
shell = "${pkgs.runtimeShell}";
@@ -25,5 +35,5 @@ pkgs.runCommand "camino-package"
};
}
''
${pkgs.nodejs_24}/bin/node ${./.}/assemble-package.mjs ${config}
${pkgs.nodejs_24}/bin/node ${buildTools}/assemble-package.mjs ${config}
''
+65 -50
View File
@@ -1,4 +1,5 @@
import { installDependencies } from "./install-dependencies.mjs";
import { measureBuild } from "./build-profile.mjs";
import { buildService } from "./build-service.mjs";
import { buildComponents } from "./build-components.mjs";
/** Trusted Nix build driver. No package-defined build hooks run between generation and witnessing. */
@@ -10,9 +11,10 @@ import { execFileSync } from "node:child_process";
import { pathToFileURL } from "node:url";
const config = JSON.parse(await fs.readFile(process.argv[2], "utf8"));
const compiler = await import(pathToFileURL(config.compiler));
const plan = JSON.parse(await fs.readFile(config.plan, "utf8"));
const pkg = plan.definition.packages.find((p) => p.revision === config.packageRevisionId);
if (!pkg || compiler.hexDigest(plan.definition) !== plan.digest) throw Error("Invalid checked execution plan/package");
const contract = JSON.parse(await fs.readFile(config.buildContract, "utf8"));
compiler.checkPackageBuildContract(contract);
const pkg = contract.definition.package;
if (pkg.revision !== config.packageRevisionId) throw Error("Invalid build contract package");
const output = process.env.out;
if (!output) throw Error("Missing Nix output");
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -55,6 +57,7 @@ const targets = portable ? [...new Set([...portable.targets, "server"])] : [];
const builds = targets.map((target) => ({ target, mode: target, settings: portable }));
if (config.remote) builds.push({ target: "server", mode: "remote", settings: config.remote });
for (const { target, mode, settings } of builds) {
const executionTarget = target === "browser" ? "target:org.quixos.execution.javascript@1" : "server";
if (!["browser", "server"].includes(target)) throw Error(`Unsupported JS build target ${target}`);
const work = path.resolve(`work-${mode}`);
await fs.cp(config.source, work, { recursive: true, dereference: false });
@@ -85,53 +88,66 @@ for (const { target, mode, settings } of builds) {
const bindingPath = relative(settings.bindingOutput);
const source =
mode === "remote"
? compiler.generateRemoteTypeScriptBindings(plan.definition.bindingSchema, pkg.revision)
: compiler.generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target });
? compiler.generateRemoteTypeScriptBindings(contract.definition.bindings, pkg.revision)
: compiler.generatePortableTypeScriptBindings(contract.definition.bindings, pkg.revision, {
target: executionTarget,
});
await fs.mkdir(path.dirname(path.join(work, bindingPath)), { recursive: true });
await fs.writeFile(path.join(work, bindingPath), source);
generated.set(mode, { file: path.join(work, bindingPath), source });
await fs.writeFile(path.join(output, "share/quixos/generated", `${mode}.ts`), source);
await fs.writeFile(
path.join(
output,
mode === "remote" ? "share/quixos/generated/remote.ts" : compiler.generatedBindingPath(executionTarget),
),
source,
);
const entry = relative(settings.entries?.[target] ?? settings.entry);
execFileSync(
config.node,
[
config.tsc,
"--noEmit",
"--strict",
"--skipLibCheck",
"false",
"--target",
"es2024",
"--lib",
"es2024,dom,esnext.disposable",
"--module",
"nodenext",
"--moduleResolution",
"nodenext",
entry,
],
{ cwd: work, stdio: "inherit" },
measureBuild("portable-typecheck", { package: pkg.revision, target, mode }, () =>
execFileSync(
config.node,
[
config.tsc,
"--noEmit",
"--strict",
"--skipLibCheck",
"false",
"--target",
"es2024",
"--lib",
"es2024,dom,esnext.disposable",
"--module",
"nodenext",
"--moduleResolution",
"nodenext",
entry,
],
{ cwd: work, stdio: "inherit" },
),
);
const bundlePath =
mode === "remote" ? "share/quixos/remote/server/module.mjs" : `share/quixos/portable/${target}/module.mjs`;
await fs.mkdir(path.dirname(path.join(output, bundlePath)), { recursive: true });
const coreImport = target === "browser" ? coreURL : coreServer;
const restricted = !!config.restricted && target === "server";
const coreImport = target === "browser" ? coreURL : restricted ? config.restricted.sdk : coreServer;
const metadata = path.join(work, "bundle-meta.json");
execFileSync(
config.esbuild,
[
entry,
"--bundle",
`--platform=${target === "browser" ? "browser" : "node"}`,
"--format=esm",
"--minify-whitespace",
"--target=es2024",
`--outfile=${path.join(output, bundlePath)}`,
`--metafile=${metadata}`,
`--alias:@quixos/camino-replica-core=${coreImport}`,
`--external:${coreImport}`,
],
{ cwd: work, stdio: "inherit" },
measureBuild("portable-bundle", { package: pkg.revision, target, mode }, () =>
execFileSync(
config.esbuild,
[
entry,
"--bundle",
`--platform=${target === "browser" || restricted ? "browser" : "node"}`,
"--format=esm",
"--minify-whitespace",
"--target=es2024",
`--outfile=${path.join(output, bundlePath)}`,
`--metafile=${metadata}`,
`--alias:@quixos/camino-replica-core=${coreImport}`,
`--external:${coreImport}`,
],
{ cwd: work, stdio: "inherit" },
),
);
const meta = JSON.parse(await fs.readFile(metadata, "utf8"));
const built = Object.values(meta.outputs).find((o) => o.entryPoint);
@@ -143,7 +159,7 @@ for (const { target, mode, settings } of builds) {
(i) =>
i.external &&
i.path !== coreImport &&
!(target === "server" && builtinModules.includes(i.path.replace(/^node:/, ""))),
!(target === "server" && !restricted && builtinModules.includes(i.path.replace(/^node:/, ""))),
),
)
)
@@ -152,12 +168,12 @@ for (const { target, mode, settings } of builds) {
.filter((e) =>
mode === "remote"
? e.execution.kind === "remote"
: e.execution.kind !== "remote" && e.execution.targets.includes(target),
: e.execution.kind !== "remote" && e.execution.targets.includes(executionTarget),
)
.map((e) => e.id);
const body = {
id: `${mode}-module`,
target,
target: executionTarget,
compatibility: "quixos-js-module-v1",
path: bundlePath,
ports: "quixos-transaction-ports-v1",
@@ -170,22 +186,21 @@ for (const { target, mode, settings } of builds) {
const components =
config.group && !config.components
? []
: await buildComponents({ config, compiler, plan, pkg, output, recordFile, generated });
: await buildComponents({ config, compiler, contract, pkg, output, recordFile, generated });
if (!config.group || config.service.entry)
await buildService({ config, compiler, pkg, output, coreServer, sharedFiles, implementations, recordFile });
const manifest = compiler.parsePackageArtifacts({
const manifest = compiler.parseArtifactGroup({
schemaVersion: 1,
packageRevisionId: pkg.revision,
bindingDigest: pkg.bindingDigest,
sourceDigest: pkg.sourceDigest,
versions: plan.definition.versions,
bindingDigest: contract.digest,
versions: contract.definition.versions,
implementations,
...(components.length ? { components } : {}),
});
for (const { file, source } of generated.values())
if ((await fs.readFile(file, "utf8")) !== source) throw Error("Generated bindings changed during build");
const { receipt, sources } = compiler.expectedArtifactReceipt(plan, manifest, generatorDigest);
const { receipt, sources } = compiler.expectedGroupReceipt(contract, manifest, generatorDigest);
for (const [file, bytes] of sources)
if (!Buffer.from(bytes).equals(await fs.readFile(path.join(output, file))))
throw Error("Receipt generation differs from compiled bindings");
+60 -58
View File
@@ -1,10 +1,13 @@
import { installDependencies } from "./install-dependencies.mjs";
import { checkComponentTypes } from "./component-types.mjs";
import { measureBuild } from "./build-profile.mjs";
import fs from "node:fs/promises";
import path from "node:path";
import { createHash } from "node:crypto";
import { execFileSync } from "node:child_process";
import { pathToFileURL } from "node:url";
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
export async function buildComponents({ config, compiler, plan, pkg, output, recordFile, generated }) {
export async function buildComponents({ config, compiler, contract, pkg, output, recordFile, generated }) {
const settings = config.components;
if (!settings) {
if (pkg.components?.length) throw Error("Declared components require checked component build settings");
@@ -100,12 +103,14 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
await fs.writeFile(
shim,
`export * from ${JSON.stringify(url)};` +
(name === "@quixos/camino-react" ? "" : `export {default} from ${JSON.stringify(url)};`),
(["@quixos/camino-react", "@quixos/camino-react/ui"].includes(name)
? ""
: `export {default} from ${JSON.stringify(url)};`),
);
sharedAliases.push("--alias:" + name + "=" + shim, "--external:" + url);
}
const bindingPath = compiler.artifactPath(settings.bindingOutput),
bindings = compiler.generateComponentClientBindings(plan, pkg.revision),
bindings = contract.definition.component,
assets = compiler.componentAssetDeclarations();
await fs.mkdir(path.dirname(path.join(work, bindingPath)), { recursive: true });
await fs.writeFile(path.join(work, bindingPath), bindings);
@@ -123,10 +128,9 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
for (const root of settings.contracts ?? []) {
const companion = JSON.parse(await fs.readFile(path.join(root, "react-contracts.json"), "utf8"));
if (companion.schemaVersion !== 1) throw Error("Unsupported React props companion");
const contracts = plan.definition.workspace.interfaceImports.filter(
const contracts = contract.definition.moduleInterfaces.filter(
(iface) =>
iface.revisionId === companion.interfaceRevisionId ||
iface.application?.definitionId === companion.interfaceRevisionId,
iface.revisionId === companion.interfaceRevisionId || iface.definitionId === companion.interfaceRevisionId,
);
if (!contracts.length) continue;
for (const [memberId, entry] of Object.entries(companion.members)) {
@@ -136,12 +140,7 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
`import type {${entry.export} as ${alias}} from ${JSON.stringify(path.join(root, "types", compiler.artifactPath(entry.path)))};`,
);
for (const iface of contracts) {
if (
!(iface.template?.members ?? iface.members).some(
(member) => member.kind === "module" && member.id === memberId,
)
)
throw Error("Props companion names an unknown module member");
if (!iface.members.includes(memberId)) throw Error("Props companion names an unknown module member");
const key = JSON.stringify([iface.revisionId, memberId]);
if (seenProps.has(key)) throw Error("Duplicate React props companion for " + key);
seenProps.add(key);
@@ -155,46 +154,41 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
propsImports.join("\n") +
`\nexport {};\ndeclare module ${JSON.stringify("./" + bindingPath.replace(/\.ts$/, ".js"))} { interface ModuleProps {${propsMembers.join("\n")}} }\n`,
);
const components = [];
const witnesses = [];
for (const [id, entry] of entries) {
const declaration = declarations.find((component) => component.id === id),
source = compiler.artifactPath(entry.entry);
if (!/^[$A-Z_a-z][$\w]*$/.test(entry.export)) throw Error("Invalid component export name");
const key = digest(Buffer.from(id)).slice(0, 24),
directory = "share/quixos/components/" + key;
const key = digest(Buffer.from(id)).slice(0, 24);
const witness = path.join(work, "component-check-" + key + ".tsx");
const module = pkg.modules.find((module) => module.id === id);
await fs.writeFile(
witness,
`import type {Modules} from ${JSON.stringify("./" + bindingPath.replace(/\.ts$/, ".js"))};import {${entry.export} as Component} from ${JSON.stringify("./" + source.replace(/\.[cm]?tsx?$/, ".js"))};const checked:Modules[${JSON.stringify(declaration.displayName)}]=Component;export default checked;\n` +
(module.contract === "org.quixos.react.subject-only/1"
? `import type {ComponentProps} from "@quixos/camino-react";declare const subject:ComponentProps<${JSON.stringify(declaration.subject)}>['subject'];const mount = <Component subject={subject}/>;\n`
? `import type {ComponentProps} from "react";declare const props:Pick<ComponentProps<Modules[${JSON.stringify(declaration.displayName)}]>,"subject"|"input">;const mount = <Component {...props}/>;\n`
: ""),
);
execFileSync(
config.node,
[
config.tsc,
"--noEmit",
"--strict",
"--skipLibCheck",
"false",
"--target",
"es2024",
"--lib",
"es2024,dom,dom.iterable,esnext.disposable",
"--module",
"nodenext",
"--moduleResolution",
"nodenext",
"--jsx",
"react-jsx",
path.basename(witness),
assetPath,
propsPath,
],
{ cwd: work, stdio: "inherit" },
);
witnesses.push(path.basename(witness));
}
checkComponentTypes({
node: config.node,
tsc: config.tsc,
work,
witnesses,
assets: assetPath,
props: propsPath,
packageRevision: pkg.revision,
});
const components = [];
const { checkControls } = await import(pathToFileURL(path.join(settings.sdk, "lint-controls.mjs")));
const { default: ts } = await import(pathToFileURL(path.resolve(path.dirname(config.tsc), "../lib/typescript.js")));
const checkedControls = new Set();
for (const [id, entry] of entries) {
const declaration = declarations.find((component) => component.id === id),
source = compiler.artifactPath(entry.entry),
key = digest(Buffer.from(id)).slice(0, 24),
directory = "share/quixos/components/" + key;
const metadata = path.join(work, "component-meta-" + key + ".json");
const facade = "component-entry-" + key + ".ts";
await fs.writeFile(
@@ -202,27 +196,35 @@ export async function buildComponents({ config, compiler, plan, pkg, output, rec
`export {${entry.export}} from ${JSON.stringify("./" + source.replace(/\.[cm]?tsx?$/, ".js"))};\n`,
);
await fs.mkdir(path.join(output, directory), { recursive: true });
execFileSync(
config.esbuild,
[
facade,
"--bundle",
"--platform=browser",
"--format=esm",
"--target=es2024",
"--jsx=automatic",
'--define:process.env.NODE_ENV="production"',
"--metafile=" + metadata,
"--outfile=" + path.join(output, directory, "module.mjs"),
...sharedAliases,
...["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"].map(
(extension) => "--loader:." + extension + "=dataurl",
),
],
{ cwd: work, stdio: "inherit" },
measureBuild("component-bundle", { package: pkg.revision, component: id }, () =>
execFileSync(
config.esbuild,
[
facade,
"--bundle",
"--platform=browser",
"--format=esm",
"--target=es2024",
"--jsx=automatic",
'--define:process.env.NODE_ENV="production"',
"--metafile=" + metadata,
"--outfile=" + path.join(output, directory, "module.mjs"),
...sharedAliases,
...["svg", "png", "jpg", "jpeg", "gif", "webp", "avif", "woff", "woff2", "ttf", "ico"].map(
(extension) => "--loader:." + extension + "=dataurl",
),
],
{ cwd: work, stdio: "inherit" },
),
);
const meta = JSON.parse(await fs.readFile(metadata, "utf8")),
outputs = Object.entries(meta.outputs);
for (const file of Object.keys(meta.inputs)) {
if (!/\.[cm]?[jt]sx?$/.test(file) || file.includes("node_modules/") || checkedControls.has(file)) continue;
checkedControls.add(file);
const errors = checkControls(ts, await fs.readFile(path.resolve(work, file), "utf8"), file);
if (errors.length) throw Error(errors.join("\n"));
}
const main = outputs.find(([file]) => file.endsWith("/module.mjs"))?.[1];
if (!main?.exports.includes(entry.export)) throw Error("Component bundle lacks declared export");
if (
+12
View File
@@ -0,0 +1,12 @@
/** Timing is log data, never part of an artifact or its witness. */
export function measureBuild(phase, details, action) {
const started = performance.now();
try {
return action();
} finally {
if (process.env.QUIXOS_TRACE_PROFILE === "1")
console.error(
`[qx-build-profile] ${JSON.stringify({ phase, ms: Math.round(performance.now() - started), ...details })}`,
);
}
}
+16 -4
View File
@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { installDependencies } from "./install-dependencies.mjs";
const config = JSON.parse(await fs.readFile(process.argv[2], "utf8"));
const resource = JSON.parse(await fs.readFile(config.interface, "utf8"));
if (resource.kind !== "interface") throw Error("React props belong to an interface resource");
@@ -29,14 +30,24 @@ const metadata = await fs.readFile(packageFile, "utf8").then(JSON.parse, (error)
throw error;
});
await fs.writeFile(packageFile, JSON.stringify({ ...metadata, type: "module" }));
if (config.dependencies) await fs.symlink(config.dependencies, path.join(work, "node_modules"));
const modules = path.join(output, "node_modules");
await fs.mkdir(modules, { recursive: true });
await installDependencies({
destination: modules,
application: config.dependencies,
selected: config.reactSdk ? { "@quixos/camino-react": config.reactSdk } : {},
reserved: ["@quixos/camino-react"],
});
await fs.symlink(modules, path.join(work, "node_modules"));
const members = Object.create(null);
let index = 0;
for (const [id, entry] of Object.entries(config.members)) {
const member = (iface.template?.members ?? iface.members).find((member) => member.id === id);
if (
member?.kind !== "module" ||
!["org.quixos.react.component/1", "org.quixos.react.subject-only/1"].includes(member.contract)
!["org.quixos.react.component/1", "org.quixos.react.component/2", "org.quixos.react.subject-only/1"].includes(
member.contract,
)
)
throw Error("Props entry is not a React module member: " + id);
if (
@@ -52,7 +63,7 @@ for (const [id, entry] of Object.entries(config.members)) {
`import type {${entry.export} as Props} from ${typeImport};
type Assert<T extends true> = T;
type ObjectProps = Assert<Props extends object ? true : false>;
type ReservedSubject = Assert<"subject" extends keyof Props ? false : true>;
type ReservedRendererProps = Assert<Extract<keyof Props, "subject" | "input" | "navigation" | "usingComponent" | "provider" | "coverage" | "componentCodeFallback"> extends never ? true : false>;
${member.contract === "org.quixos.react.subject-only/1" ? "type SubjectOnly = Assert<{} extends Props ? true : false>;" : ""}
export type {Props};\n`,
);
@@ -69,6 +80,8 @@ execFileSync(
"--strict",
"--target",
"es2024",
"--lib",
"es2024,dom,dom.iterable,esnext.disposable",
"--module",
"nodenext",
"--moduleResolution",
@@ -88,7 +101,6 @@ for (const file of declarations) {
await fs.copyFile(path.join(work, file), path.join(output, "types", file));
}
await fs.writeFile(path.join(output, "package.json"), JSON.stringify({ type: "module" }));
if (config.dependencies) await fs.symlink(config.dependencies, path.join(output, "node_modules"));
await fs.writeFile(
path.join(output, "react-contracts.json"),
JSON.stringify({ schemaVersion: 1, interfaceRevisionId: iface.revisionId, members }, null, 2),
+56
View File
@@ -13,6 +13,62 @@ export async function buildService({
recordFile,
}) {
const relative = compiler.artifactPath;
if (config.restricted) {
if (config.service.entry || pkg.exports.some((e) => e.execution.kind === "remote"))
throw Error(
"Restricted services require portable snapshot/transaction exports; remote/custom services keep their VM path",
);
const modules = implementations.filter((i) => i.target === "server");
if (modules.length !== 1 || pkg.exports.some((e) => !modules[0].exports.includes(e.id)))
throw Error("Restricted service requires exactly one checked portable registry covering all exports");
const limits = config.restricted.limits;
if (!limits || typeof limits !== "object" || Array.isArray(limits))
throw Error("Restricted limits must be an object");
if (Object.keys(limits).some((key) => !["deadlineMs", "maxBytes", "maxPortCalls", "maxQueued"].includes(key)))
throw Error("Unknown restricted service limit");
for (const [key, min, max] of [
["deadlineMs", 1, 60000],
["maxBytes", 1024, 4194304],
["maxPortCalls", 1, 10000],
["maxQueued", 0, 64],
])
if (limits[key] !== undefined && (!Number.isSafeInteger(limits[key]) || limits[key] < min || limits[key] > max))
throw Error("Invalid restricted service limit " + key);
await fs.mkdir(path.join(output, "bin"), { recursive: true });
await fs.mkdir(path.join(output, "share/quixos/service"), { recursive: true });
const configFile = "share/quixos/service/restricted.json";
const module = modules[0];
await fs.writeFile(
path.join(output, configFile),
JSON.stringify({
module: path.relative("share/quixos/service", module.path),
registryExport: module.registryExport,
exports: module.exports,
sdk: [{ name: config.restricted.sdk, path: config.restricted.sdk }],
...limits,
}),
);
await fs.writeFile(
path.join(output, "bin/service"),
`#!${config.shell}\nservice_root=\${0%/*}/..\nexec ${config.restricted.runtime} "$service_root/${configFile}"\n`,
{ mode: 0o755 },
);
const body = {
id: "service",
target: "server",
compatibility: "quixos-service-v1",
path: "bin/service",
ports: "quixos-transaction-ports-v1",
exports: module.exports,
files: [
await recordFile("bin/service", "text/x-shellscript"),
await recordFile(configFile, "application/json"),
...module.files,
],
};
implementations.unshift({ ...body, contentDigest: compiler.implementationDigest(body) });
return;
}
if (pkg.exports.length || config.service.entry) {
await fs.mkdir(path.join(output, "bin"), { recursive: true });
await fs.mkdir(path.join(output, "share/quixos/service"), { recursive: true });
+17
View File
@@ -0,0 +1,17 @@
# Standalone build drivers import sibling JavaScript modules. Their code is an
# input; Nix helper APIs and test sources do not belong to that dependency.
builtins.path {
path = ./.;
name = "camino-build-tools";
filter =
filename: type:
let
name = builtins.baseNameOf filename;
in
(type == "directory" && filename == toString ./.)
|| (
builtins.dirOf filename == toString ./.
&& builtins.match ".*\\.mjs" name != null
&& builtins.match ".*\\.test\\.mjs" name == null
);
}
+49
View File
@@ -0,0 +1,49 @@
import path from "node:path";
import { createRequire } from "node:module";
const [tsc, count, ...args] = process.argv.slice(2);
const ts = createRequire(import.meta.url)(path.resolve(path.dirname(tsc), "../lib/typescript.js"));
const parsed = ts.parseCommandLine(args);
const witnesses = parsed.fileNames.slice(0, Number(count));
const shared = parsed.fileNames.slice(Number(count));
const host = ts.createCompilerHost(parsed.options, true);
// These programs only discover source closures; they do not bind or check.
// Sharing immutable parsed source files avoids reading/parsing the same libs
// for every witness. Only the final combined program performs typechecking.
const sources = new Map();
const getSourceFile = host.getSourceFile;
host.getSourceFile = (filename, ...options) => {
if (!sources.has(filename)) sources.set(filename, getSourceFile(filename, ...options));
return sources.get(filename);
};
for (const method of ["fileExists", "directoryExists", "getDirectories", "readFile", "realpath"]) {
if (!host[method]) continue;
const original = host[method],
cache = new Map();
host[method] = (filename) => {
if (!cache.has(filename)) cache.set(filename, original(filename));
return cache.get(filename);
};
}
const closures = witnesses.map(
(witness) => new Set(ts.createProgram([witness, ...shared], parsed.options, host).getSourceFiles()),
);
const common = new Set([...closures[0]].filter((source) => closures.every((closure) => closure.has(source))));
const ambient = (node) =>
ts.isModuleDeclaration(node) || ts.isNamespaceExportDeclaration(node) || ts.forEachChild(node, ambient);
// A file that is visible to just some witnesses must not contribute globals
// or ambient module declarations to the others. Otherwise a combined success
// could hide an independent failure. Script files are conservatively unsafe.
if (
closures.some((closure) =>
[...closure].some((source) => !common.has(source) && (!ts.isExternalModule(source) || ambient(source))),
)
)
process.exit(2);
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const diagnostics = [...parsed.errors, ...ts.getPreEmitDiagnostics(program)];
if (diagnostics.length) {
console.error(ts.formatDiagnosticsWithColorAndContext(diagnostics, host));
process.exit(1);
}
+51
View File
@@ -0,0 +1,51 @@
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { measureBuild } from "./build-profile.mjs";
// Component witnesses share bindings and usually the same source graph.
// Check a bounded batch once when distinct source closures cannot supply
// ambient declarations to one another. Otherwise use the original checks.
export function checkComponentTypes({ node, tsc, work, witnesses, assets, props, packageRevision }) {
const checkTypes = (files, combined = false) =>
execFileSync(
node,
[
...(combined ? [fileURLToPath(new URL("./component-type-batch.mjs", import.meta.url))] : []),
tsc,
...(combined ? [String(files.length)] : []),
"--noEmit",
"--strict",
"--skipLibCheck",
"false",
"--target",
"es2024",
"--lib",
"es2024,dom,dom.iterable,esnext.disposable",
"--module",
"nodenext",
"--moduleResolution",
"nodenext",
"--jsx",
"react-jsx",
...files,
assets,
props,
],
{ cwd: work, stdio: combined ? "pipe" : "inherit" },
);
measureBuild("component-typecheck", { package: packageRevision, components: witnesses.length }, () => {
for (let start = 0; start < witnesses.length; start += 1000) {
const batch = witnesses.slice(start, start + 1000);
if (batch.length === 1) checkTypes(batch);
else {
try {
checkTypes(batch, true);
} catch (error) {
if (!(error.status > 0) && error.code !== "ENOBUFS") throw error;
console.error("Component batch requires independent witness checks");
for (const witness of batch) checkTypes([witness]);
}
}
}
});
}
+59
View File
@@ -0,0 +1,59 @@
import assert from "node:assert/strict";
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { checkComponentTypes } from "./component-types.mjs";
const tsc = process.env.QUIXOS_TYPESCRIPT;
if (!tsc) throw Error("Pass the selected TypeScript compiler path");
async function fixture(context, left, right) {
const work = await mkdtemp(path.join(os.tmpdir(), "component-types-"));
context.after(() => rm(work, { recursive: true, force: true }));
await Promise.all(
Object.entries({ "left.ts": left, "right.ts": right, "assets.d.ts": "", "props.d.ts": "" }).map(([file, source]) =>
writeFile(path.join(work, file), source),
),
);
return {
node: process.execPath,
tsc,
work,
witnesses: ["left.ts", "right.ts"],
assets: "assets.d.ts",
props: "props.d.ts",
packageRevision: "test",
};
}
test("shared component typechecking accepts all valid witnesses", async (context) => {
checkComponentTypes(await fixture(context, "export const left: string = 'ok';", "export const right: number = 1;"));
});
test("independently valid ambient declarations use the full individual check fallback", async (context) => {
checkComponentTypes(
await fixture(
context,
"export {}; declare global { interface Application { value: string; } }",
"export {}; declare global { interface Application { value: number; } }",
),
);
});
test("a type error in any witness still rejects the artifact", async (context) => {
const input = await fixture(context, "export const left: string = 'ok';", "export const right: string = 1;");
assert.throws(() => checkComponentTypes(input));
});
test("one witness's globals cannot make an independently invalid witness pass", async (context) => {
const input = await fixture(
context,
"export {}; declare global { interface Application { value: string; } }",
"export const right: Application = {value: 'ok'};",
);
assert.throws(() => checkComponentTypes(input));
});
test("an ambient module from another source closure cannot resolve a missing import", async (context) => {
const input = await fixture(
context,
"declare module 'missing-component-package' { export const value: string; }",
"import {value} from 'missing-component-package'; export const right = value;",
);
assert.throws(() => checkComponentTypes(input));
});
+30
View File
@@ -0,0 +1,30 @@
# QX and recursive pins enter through the checked contract and build recipe.
# The compiler receives authored implementation files, not resource provenance.
{ lib, src }:
builtins.path {
path = src;
name = "camino-implementation-source";
filter =
path: type:
let
name = builtins.baseNameOf path;
in
if type == "directory" then
!(builtins.elem name [
".git"
".jj"
"node_modules"
])
else
!(builtins.elem name [
"flake.nix"
"flake.lock"
"quixos.lock"
"quixos.check.json"
"quixos.react.json"
"yarn-project.nix"
"components.nix"
"contracts.nix"
])
&& !(lib.hasSuffix ".qx" name);
}
+2
View File
@@ -0,0 +1,2 @@
# The selected protocol owns semantic projection; builders receive its result.
args@{ protocol, ... }: import "${protocol}/share/package-context.nix" args
+57
View File
@@ -0,0 +1,57 @@
{
pkgs,
nixpkgs,
system,
}:
let
packageSet = (import ./quixos-package-helpers.nix).packageSet;
shared = {
marker = "shared";
};
environment = {
inherit system;
nixpkgsSource = toString nixpkgs;
pkgs = shared;
narHash = nixpkgs.narHash;
};
standard = {
inherit nixpkgs system;
context.buildEnvironment = environment;
};
fallback = packageSet { inherit nixpkgs system; };
in
assert packageSet standard == shared;
assert
packageSet (
standard
// {
context.buildEnvironment = environment // {
nixpkgsSource = "same-content-under-another-name";
};
}
) == shared;
assert
(packageSet (
standard
// {
context.buildEnvironment = environment // {
system = "another-system";
};
}
)).path == fallback.path;
assert
(packageSet (
standard
// {
context.buildEnvironment = environment // {
nixpkgsSource = "another-source";
narHash = "another-hash";
};
}
)).path == fallback.path;
assert
(packageSet (standard // { overlays = [ (_: _: { marker = "overlay"; }) ]; })).marker == "overlay";
assert (packageSet (standard // { config.allowBroken = true; })).config.allowBroken;
pkgs.runCommand "camino-package-set-check" { } ''
touch "$out"
''
+34
View File
@@ -847,6 +847,40 @@ let
in
{
mkCaminoReactPackage = import ./react-package.nix;
# Share the composition's package set only for the same exact Nixpkgs source,
# system and default import arguments. Config/overlay/cross-build choices use
# ordinary Nixpkgs evaluation. No package implementation is substituted.
packageSet =
args@{
nixpkgs,
system,
context ? { },
...
}:
let
authoredArgs = builtins.removeAttrs args [
"nixpkgs"
"context"
];
importArgs = {
config = { };
overlays = [ ];
}
// authoredArgs;
environment = context.buildEnvironment or null;
in
if
builtins.attrNames authoredArgs == [ "system" ]
&& environment != null
&& environment.system == system
&& (
environment.nixpkgsSource == toString nixpkgs
|| ((nixpkgs.narHash or null) != null && nixpkgs.narHash == (environment.narHash or null))
)
then
environment.pkgs
else
import nixpkgs importArgs;
mkCaminoReactContracts = import ./react-contracts.nix;
mkCaminoYarnDependencies = import ./yarn-dependencies.nix;
mkCaminoYarnShell = import ./yarn-shell.nix;
+20 -3
View File
@@ -6,16 +6,33 @@
dependencies ? null,
}:
let
buildTools = import ./build-tools.nix;
config = pkgs.writeText "camino-react-contracts.json" (
builtins.toJSON {
source = src;
interface = context.interface;
source = import ./implementation-source.nix {
inherit (pkgs) lib;
inherit src;
};
interface = builtins.toFile "camino-interface-contract.json" (
builtins.unsafeDiscardStringContext (
builtins.toJSON (
let
resource = builtins.fromJSON (builtins.readFile context.interface);
in
{
kind = resource.kind;
revision = builtins.removeAttrs resource.revision [ "source" ];
}
)
)
);
inherit members dependencies;
reactSdk = context.reactSdk or null;
node = "${pkgs.nodejs_24}/bin/node";
tsc = "${context.typescript}/node_modules/typescript/bin/tsc";
}
);
in
pkgs.runCommand "camino-react-contracts" { } ''
${pkgs.nodejs_24}/bin/node ${./.}/build-react-contracts.mjs ${config}
${pkgs.nodejs_24}/bin/node ${buildTools}/build-react-contracts.mjs ${config}
''
+1 -1
View File
@@ -10,7 +10,7 @@ let
group = import ./artifacts.nix {
inherit pkgs src;
inherit (context)
plan
buildContract
core
sharedRuntime
typescript
+47
View File
@@ -0,0 +1,47 @@
{ pkgs, core }:
let
workerd = import ./workerd.nix { inherit pkgs; };
# Transaction-port/value adapters do not need the browser's CRDT/Wasm engine.
# Keep this surface explicit; it is bundled into checked restricted handlers.
sdk = pkgs.runCommand "quixos-restricted-sdk" { nativeBuildInputs = [ pkgs.esbuild ]; } ''
mkdir -p "$out"
cat > entry.mjs <<'JS'
export {definePortableRegistry,QxReject} from '${core}/dist/portable.js';
export {encodeArgument,decodeArgument,canonicalJson,parseJson} from '${core}/dist/value.js';
export {workspaceId,operationId,objectId,entryId,hashIdentity} from '${core}/dist/identity.js';
export {reference} from '${core}/dist/references.js';
export {CodecError,ExecutionError} from '${core}/dist/limits.js';
JS
esbuild entry.mjs --bundle --platform=neutral --format=esm --target=es2024 --outfile="$out/core.mjs"
'';
runtime =
pkgs.runCommand "quixos-restricted-execution" { nativeBuildInputs = [ pkgs.makeWrapper ]; }
''
mkdir -p "$out/lib" "$out/bin"
cp ${./service.mjs} "$out/lib/service.mjs"
cp ${./worker.mjs} "$out/lib/worker.mjs"
makeWrapper ${pkgs.nodejs_24}/bin/node "$out/bin/quixos-restricted-registry" \
--add-flags "$out/lib/service.mjs" \
--set QUIXOS_RESTRICTED_WORKERD ${workerd}/bin/workerd \
--set QUIXOS_RESTRICTED_WORKER "$out/lib/worker.mjs"
'';
qualification =
pkgs.runCommand "quixos-restricted-execution-qualification"
{
nativeBuildInputs = [ pkgs.nodejs_24 ];
QUIXOS_TEST_WORKERD = "${workerd}/bin/workerd";
QUIXOS_TEST_SDK = "${sdk}/core.mjs";
}
''
node --test ${./.}/service.test.mjs
touch "$out"
'';
in
{
inherit
workerd
sdk
runtime
qualification
;
}
+404
View File
@@ -0,0 +1,404 @@
import { spawn } from "node:child_process";
import { createServer, request as httpRequest } from "node:http";
import { mkdtemp, readFile, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
const abi = "quixos-transaction-ports-v1";
const fixedError = (code) => Object.assign(Error(code), { code });
const object = (v) => !!v && typeof v === "object" && !Array.isArray(v);
const absolute = (value) =>
typeof value === "string" &&
value.startsWith("/") &&
!/[\s\u0000-\u001f]/.test(value) &&
!value.split("/").includes("..");
/** Trusted stdio adapter inside one workspace VM. Engine processes never own
* coordinator credentials. The supervising service must impose VM/OS limits.
* Per-frame process replacement prevents retained globals/handles crossing calls. */
export async function serveRestrictedRegistry(config, io = process) {
if (
!object(config) ||
!absolute(config.workerd) ||
!absolute(config.worker) ||
!absolute(config.module) ||
!/^[$A-Z_a-z][$\w]*$/.test(config.registryExport) ||
!Array.isArray(config.exports) ||
!config.exports.length ||
config.exports.length > 10000 ||
config.exports.some((x) => typeof x !== "string" || !x || x.length > 4096) ||
new Set(config.exports).size !== config.exports.length ||
!Array.isArray(config.sdk) ||
config.sdk.length > 8 ||
config.sdk.some(
(m) =>
!object(m) ||
!absolute(m.path) ||
typeof m.name !== "string" ||
!m.name ||
/[\u0000-\u001f]/.test(m.name) ||
["entry.mjs", "package.mjs"].includes(m.name),
) ||
new Set(config.sdk.map((m) => m.name)).size !== config.sdk.length
)
throw fixedError("INVALID_RESTRICTED_CONFIGURATION");
const limits = {
maxBytes: config.maxBytes ?? 1024 * 1024,
maxPortCalls: config.maxPortCalls ?? 1000,
deadlineMs: config.deadlineMs ?? 4000,
maxQueued: config.maxQueued ?? 8,
};
if (
!Number.isSafeInteger(limits.maxBytes) ||
limits.maxBytes < 1024 ||
limits.maxBytes > 4 * 1024 * 1024 ||
!Number.isSafeInteger(limits.maxPortCalls) ||
limits.maxPortCalls < 1 ||
limits.maxPortCalls > 10000 ||
!Number.isSafeInteger(limits.deadlineMs) ||
limits.deadlineMs < 1 ||
limits.deadlineMs > 60000 ||
!Number.isSafeInteger(limits.maxQueued) ||
limits.maxQueued < 0 ||
limits.maxQueued > 64
)
throw fixedError("INVALID_RESTRICTED_LIMITS");
const packageSource = await readFile(config.module, "utf8");
if (Buffer.byteLength(packageSource) > 1024 * 1024) throw fixedError("RESTRICTED_SOURCE_LIMIT");
const workerSource = await readFile(config.worker, "utf8");
if (Buffer.byteLength(workerSource) > 65536) throw fixedError("RESTRICTED_ENTRY_LIMIT");
const sdk = await Promise.all(config.sdk.map(async (m) => ({ ...m, source: await readFile(m.path, "utf8") })));
if (sdk.some((m) => Buffer.byteLength(m.source) > 2 * 1024 * 1024)) throw fixedError("RESTRICTED_SDK_LIMIT");
let active,
buffered = Buffer.alloc(0),
stopped = false;
const queued = [];
const send = (value) => {
if (stopped) return;
const line = JSON.stringify({ abi, ...value }) + "\n";
if (
Buffer.byteLength(line) > limits.maxBytes ||
io.stdout.writableLength + Buffer.byteLength(line) > limits.maxBytes * 2
)
throw fixedError("RESTRICTED_BACKPRESSURE");
io.stdout.write(line);
};
const retire = async (call, reason) => {
if (call.retiring) return call.retiring;
call.open = false;
call.retiring = (async () => {
clearTimeout(call.timer);
const index = queued.indexOf(call);
if (index >= 0) queued.splice(index, 1);
for (const pending of call.pending.values()) pending.reject(reason);
call.pending.clear();
// Cancellation can arrive during async directory/socket preparation.
// Wait for it to settle before claiming every process/handle is gone.
await call.preparing?.catch(() => {});
if (call.child?.pid) {
try {
process.kill(-call.child.pid, "SIGKILL");
} catch (error) {
if (error.code !== "ESRCH") throw error;
}
await call.exited;
}
call.server?.closeAllConnections();
if (call.server) await new Promise((resolve) => call.server.close(resolve));
if (call.directory) await rm(call.directory, { recursive: true, force: true });
if (active === call) active = undefined;
})();
return call.retiring;
};
const fail = async (error) => {
if (stopped) return;
stopped = true;
io.stdin.destroy();
for (const call of [...queued]) await retire(call, error);
if (active) await retire(active, error);
io.stdout.end();
io.exitCode = 1;
};
const body = async (stream) => {
const chunks = [];
let bytes = 0;
for await (const chunk of stream) {
bytes += chunk.length;
if (bytes > limits.maxBytes) throw fixedError("RESTRICTED_MESSAGE_LIMIT");
chunks.push(chunk);
}
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks)));
};
const pump = () => {
if (!stopped && !active && queued.length) void invoke(queued.shift()).catch(fail);
};
const finish = async (call, reason, reply) => {
await retire(call, reason);
send(reply);
pump();
};
const invoke = async (call) => {
const { message } = call;
active = call;
const terminate = (code) => {
if (!call.open) return;
void finish(call, fixedError(code), {
invocation: message.invocation,
status: "failed",
error: { code, message: code },
}).catch(fail);
};
call.terminate = terminate;
try {
let engine;
call.preparing = (async () => {
call.directory = await mkdtemp(join(tmpdir(), "qx-frame-"));
if (!call.open) return;
const broker = join(call.directory, "broker.sock");
engine = join(call.directory, "engine.sock");
call.server = createServer(async (request, response) => {
try {
if (!call.open || request.method !== "POST" || request.url !== "/port") throw fixedError("EXPIRED_CONTEXT");
const port = await body(request);
if (
!call.open ||
!object(port) ||
Object.keys(port).some((key) => !["portId", "action", "input", "key"].includes(key)) ||
typeof port.portId !== "string" ||
!object(port.action) ||
!Object.hasOwn(port, "input")
)
throw fixedError("INVALID_RESTRICTED_PORT");
if (++call.sequence > limits.maxPortCalls || call.pending.size >= limits.maxPortCalls)
throw fixedError("RESTRICTED_PORT_LIMIT");
const sequence = call.sequence;
const result = new Promise((resolve, reject) => call.pending.set(sequence, { resolve, reject }));
void result.catch(() => {});
send({
kind: "port",
invocation: message.invocation,
frame: message.frame,
sequence,
portId: port.portId,
action: { ...port.action, ...(port.key === undefined ? {} : { key: port.key }) },
input: port.input,
});
const value = await result;
if (!call.open) throw fixedError("EXPIRED_CONTEXT");
const encoded = JSON.stringify(value);
if (Buffer.byteLength(encoded) > limits.maxBytes) throw fixedError("RESTRICTED_MESSAGE_LIMIT");
response.writeHead(200, { "content-type": "application/json" });
response.end(encoded);
} catch {
response.writeHead(409);
response.end('{"status":"failed","error":{"code":"RESTRICTED_PORT_FAILED"}}');
}
});
await new Promise((resolve, reject) => {
call.server.once("error", reject);
call.server.listen(broker, resolve);
});
await writeFile(join(call.directory, "package.mjs"), packageSource);
await writeFile(join(call.directory, "entry.mjs"), workerSource);
const modules = [
{ name: "entry.mjs", esModule: 'embed "entry.mjs"' },
{ name: "package.mjs", esModule: 'embed "package.mjs"' },
];
for (const [index, module] of sdk.entries()) {
const file = `sdk-${index}.mjs`;
await writeFile(join(call.directory, file), module.source);
modules.push({ name: module.name, esModule: "embed " + JSON.stringify(file) });
}
const setting = { registryExport: config.registryExport, exports: config.exports, maxBytes: limits.maxBytes };
const configuration = `using Workerd = import "/workerd/workerd.capnp"; const config :Workerd.Config = (
services = [(name="main", worker=(modules=[${modules.map((m) => `(name=${JSON.stringify(m.name)}, esModule=${m.esModule})`).join(",")}],
compatibilityDate="2026-10-05", globalOutbound="deny",
bindings=[(name="BROKER", service="broker"),(name="CONFIG",json=${JSON.stringify(JSON.stringify(setting))})])),
(name="broker",external=(address=${JSON.stringify("unix:" + broker)},http=())), (name="deny",network=(allow=[]))],
sockets=[(name="frame",address=${JSON.stringify("unix:" + engine)},http=(),service="main")]);`;
const file = join(call.directory, "config.capnp");
await writeFile(file, configuration);
if (!call.open) return;
call.child = spawn(config.workerd, ["serve", file], {
detached: true,
env: { TZ: "UTC", LANG: "C.UTF-8" },
cwd: call.directory,
stdio: ["ignore", "ignore", "pipe"],
});
call.exited = new Promise((resolve) => {
call.child.once("exit", resolve);
call.child.once("error", resolve);
});
let diagnostics = 0;
call.child.stderr.on("data", (chunk) => {
if ((diagnostics += chunk.length) > 65536) terminate("RESTRICTED_DIAGNOSTIC_LIMIT");
});
call.child.once("error", () => terminate("RESTRICTED_START_FAILED"));
void call.exited.then(() => {
if (call.open) terminate("RESTRICTED_ENGINE_EXITED");
});
})();
await call.preparing;
if (!call.open) return;
const result = await new Promise((resolve, reject) => {
const attempt = () => {
if (!call.open) {
reject(fixedError("EXPIRED_CONTEXT"));
return;
}
const request = httpRequest({ socketPath: engine, path: "/", method: "POST" }, async (response) => {
try {
const result = await body(response);
if (response.statusCode !== 200) throw fixedError("RESTRICTED_PACKAGE_FAILED");
resolve(result);
} catch (error) {
reject(error);
}
});
request.once("error", (error) => {
if (call.open && ["ENOENT", "ECONNREFUSED"].includes(error.code)) setTimeout(attempt, 5);
else reject(error);
});
request.end(JSON.stringify(message));
};
attempt();
});
if (!call.open) return;
if (
!object(result) ||
!["returned", "failed"].includes(result.status) ||
Object.keys(result).some(
(key) => !(result.status === "returned" ? ["status", "output"] : ["status", "error"]).includes(key),
) ||
(result.status === "returned"
? !Object.hasOwn(result, "output")
: !object(result.error) || !/^[A-Z0-9_]{1,128}$/.test(result.error.code))
)
throw fixedError("RESTRICTED_PACKAGE_FAILED");
// Authored code can alter serialization prototypes. Treat even the
// platform worker's JSON as untrusted; never accept routing/ABI fields.
const reply =
result.status === "returned"
? { invocation: message.invocation, status: "returned", output: result.output }
: {
invocation: message.invocation,
status: "failed",
error: { code: result.error.code, message: result.error.code },
};
await finish(call, fixedError("INVOCATION_ENDED"), reply);
} catch (error) {
if (call.open) {
const code = /^RESTRICTED_[A-Z_]+$/.test(error.code ?? "") ? error.code : "RESTRICTED_PACKAGE_FAILED";
await finish(call, error, { invocation: message.invocation, status: "failed", error: { code, message: code } });
}
}
};
const receive = (message) => {
if (!object(message) || message.abi !== abi) throw fixedError("RESTRICTED_ABI_MISMATCH");
if (message.kind === "shutdown") {
void fail(fixedError("SERVICE_SHUTDOWN"));
return;
}
if (message.kind === "invoke") {
if (
!config.exports.includes(message.exportId) ||
!["snapshot", "transaction"].includes(message.effect) ||
typeof message.invocation !== "string" ||
!message.invocation ||
message.invocation.length > 4096 ||
typeof message.frame !== "string" ||
!message.frame ||
message.frame.length > 4096
)
throw fixedError("INVALID_RESTRICTED_INVOCATION");
if ([active, ...queued].some((call) => call?.message.invocation === message.invocation))
throw fixedError("RESTRICTED_DUPLICATE_INVOCATION");
if (active && queued.length >= limits.maxQueued) {
send({
invocation: message.invocation,
status: "failed",
error: { code: "RESTRICTED_QUEUE_FULL", message: "RESTRICTED_QUEUE_FULL" },
});
return;
}
const call = { message, open: true, pending: new Map(), sequence: 0 };
// Deadline includes queue wait: overload cannot extend a transaction frame.
call.timer = setTimeout(() => {
if (!call.open) return;
void finish(call, fixedError("RESTRICTED_DEADLINE"), {
invocation: message.invocation,
status: "failed",
error: { code: "RESTRICTED_DEADLINE", message: "RESTRICTED_DEADLINE" },
}).catch(fail);
}, limits.deadlineMs);
queued.push(call);
pump();
return;
}
const waiting = queued.find(
(call) => call.message.invocation === message.invocation && call.message.frame === message.frame,
);
if (waiting && message.kind === "cancel") {
void finish(waiting, fixedError("INVOCATION_CANCELLED"), {
kind: "cancelled",
invocation: message.invocation,
frame: message.frame,
}).catch(fail);
return;
}
const call = active;
if (!call || message.invocation !== call.message.invocation || message.frame !== call.message.frame) {
if (message.kind === "cancel") send({ kind: "cancelled", invocation: message.invocation, frame: message.frame });
else if (message.kind !== "port-result") throw fixedError("RESTRICTED_UNKNOWN_INVOCATION");
return;
}
if (message.kind === "cancel") {
void finish(call, fixedError("INVOCATION_CANCELLED"), {
kind: "cancelled",
invocation: message.invocation,
frame: message.frame,
}).catch(fail);
return;
}
if (message.kind !== "port-result" || !["returned", "failed"].includes(message.status))
throw fixedError("INVALID_RESTRICTED_RESULT");
const pending = call.pending.get(message.sequence);
if (!pending) throw fixedError("RESTRICTED_UNKNOWN_PORT");
call.pending.delete(message.sequence);
pending.resolve(message);
};
io.stdin.on("data", (chunk) => {
if (stopped) return;
try {
let offset = 0;
while (offset < chunk.length) {
const newline = chunk.indexOf(10, offset),
end = newline < 0 ? chunk.length : newline;
if (buffered.length + end - offset > limits.maxBytes) throw fixedError("RESTRICTED_MESSAGE_LIMIT");
buffered = Buffer.concat([buffered, chunk.subarray(offset, end)]);
if (newline < 0) break;
receive(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffered)));
buffered = Buffer.alloc(0);
offset = newline + 1;
if (stopped) break;
}
} catch (error) {
void fail(error);
}
});
io.stdin.on("end", () => void fail(fixedError("COORDINATOR_DISCONNECTED")));
io.stdin.on("error", (error) => void fail(error));
io.stdout.on("error", (error) => void fail(error));
send({ kind: "ready", exports: config.exports });
return { close: () => fail(fixedError("SERVICE_SHUTDOWN")) };
}
if (process.argv[1] === new URL(import.meta.url).pathname) {
const config = JSON.parse(await readFile(process.argv[2], "utf8"));
await serveRestrictedRegistry({
...config,
workerd: process.env.QUIXOS_RESTRICTED_WORKERD,
worker: process.env.QUIXOS_RESTRICTED_WORKER,
module: join(dirname(process.argv[2]), config.module),
});
}
+327
View File
@@ -0,0 +1,327 @@
import test from "node:test";
import assert from "node:assert/strict";
import { PassThrough } from "node:stream";
import { mkdtemp, writeFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { serveRestrictedRegistry } from "./service.mjs";
const workerd = process.env.QUIXOS_TEST_WORKERD;
if (!workerd) throw Error("Set QUIXOS_TEST_WORKERD to the pinned engine");
const worker = fileURLToPath(new URL("./worker.mjs", import.meta.url));
const abi = "quixos-transaction-ports-v1";
let next = 0;
async function fixture(source, options = {}) {
const directory = await mkdtemp(join(tmpdir(), "qx-restricted-test-"));
const module = join(directory, "module.mjs");
await writeFile(module, source);
const io = { stdin: new PassThrough(), stdout: new PassThrough(), exitCode: 0 };
const waiting = [],
queued = [];
let buffer = "";
io.stdout.on("data", (chunk) => {
buffer += chunk;
let index;
while ((index = buffer.indexOf("\n")) >= 0) {
const value = JSON.parse(buffer.slice(0, index));
buffer = buffer.slice(index + 1);
if (waiting.length) waiting.shift()(value);
else queued.push(value);
}
});
const receive = async () => {
if (queued.length) return queued.shift();
let timer;
try {
return await Promise.race([
new Promise((resolve) => waiting.push(resolve)),
new Promise((_, reject) => {
timer = setTimeout(() => reject(Error("TEST_MESSAGE_TIMEOUT")), 5000);
}),
]);
} finally {
clearTimeout(timer);
}
};
const service = await serveRestrictedRegistry(
{ workerd, worker, module, registryExport: "registry", exports: ["test"], sdk: [], ...options },
io,
);
assert.deepEqual(await receive(), { abi, kind: "ready", exports: ["test"] });
const send = (value) => io.stdin.write(JSON.stringify({ abi, ...value }) + "\n");
const start = (input = null) => {
const invocation = "invoke-" + ++next,
frame = "frame-" + next;
send({
kind: "invoke",
invocation,
frame,
exportId: "test",
workspace: "workspace:restricted",
receiver: null,
input,
effect: "snapshot",
});
return { invocation, frame };
};
return {
io,
receive,
send,
start,
async close() {
await service.close();
await rm(directory, { recursive: true, force: true });
},
};
}
const registry = (code) => `export const registry={exports:['test'],async invoke(request,channel){${code}}};`;
test("real engine returns results and resets all globals between frames", async () => {
const f = await fixture(
registry(
"globalThis.retained=(globalThis.retained??0)+1; return {count:globalThis.retained,text:new Intl.Segmenter('zh',{granularity:'word'}).segment('雨后的书店').containing(3).segment};",
),
);
try {
for (let n = 0; n < 3; n++) {
const call = f.start();
const result = await f.receive();
assert.equal(result.invocation, call.invocation);
assert.equal(result.status, "returned");
assert.equal(result.output.count, 1);
assert.equal(typeof result.output.text, "string");
}
} finally {
await f.close();
}
});
test("captured frame is the sole source of nested port identities", async () => {
const f = await fixture(
registry("return await channel({portId:'read',action:{kind:'state',primitive:'read'},input:null});"),
);
try {
const call = f.start();
const port = await f.receive();
assert.equal(port.kind, "port");
assert.equal(port.frame, call.frame);
assert.equal(port.invocation, call.invocation);
f.send({ kind: "port-result", ...call, sequence: port.sequence, status: "returned", output: 42 });
const result = await f.receive();
assert.equal(result.status, "returned");
assert.equal(result.output, 42);
} finally {
await f.close();
}
});
test("authored serialization cannot forge result routing or ABI fields", async () => {
const f = await fixture(
registry(
"Object.prototype.toJSON=function(){return this.status==='returned'?{status:'returned',output:42,invocation:'forged',abi:'forged'}:this;};return 42;",
),
);
try {
const call = f.start();
const result = await f.receive();
assert.equal(result.invocation, call.invocation);
assert.equal(result.abi, abi);
assert.equal(result.status, "failed");
assert.equal(result.error.code, "RESTRICTED_PACKAGE_FAILED");
} finally {
await f.close();
}
});
test("the checked portable SDK keeps codecs and nested graph ports inside the frame", async () => {
const sdk = process.env.QUIXOS_TEST_SDK;
assert.ok(sdk, "Set QUIXOS_TEST_SDK to the checked portable SDK");
const C = await import(sdk),
workspace = C.workspaceId("workspace:restricted");
const integer = { kind: "scalar", name: "int64" },
unit = { kind: "builtin", name: "unit" };
const declarations = [
{
id: "test",
name: "test",
effect: "snapshot",
receiver: null,
input: integer,
output: integer,
methods: [
{
name: "get",
portName: "count",
portId: "count-read",
action: { kind: "state", primitive: "read" },
input: unit,
output: integer,
keyed: false,
},
],
},
];
const source = `import {definePortableRegistry} from 'qx:core'; export const registry=definePortableRegistry(${JSON.stringify(declarations)},{test:async ctx=>ctx.input+await ctx.ports.count.get()});`;
const f = await fixture(source, { sdk: [{ name: "qx:core", path: sdk }] });
try {
const call = f.start(C.encodeArgument(integer, 2n, { workspace }));
const port = await f.receive();
assert.equal(port.kind, "port");
assert.equal(C.decodeArgument(unit, port.input, { workspace }), null);
f.send({
kind: "port-result",
...call,
sequence: port.sequence,
status: "returned",
output: C.encodeArgument(integer, 4n, { workspace }),
});
const result = await f.receive();
assert.equal(result.status, "returned");
assert.equal(C.decodeArgument(integer, result.output, { workspace }), 6n);
} finally {
await f.close();
}
});
test("ambient clock, entropy, network and native imports are denied", async () => {
for (const code of [
"Date.now()",
"new Date()",
"new Intl.DateTimeFormat().format()",
"new Intl.DateTimeFormat().formatToParts()",
"Math.random()",
"crypto.randomUUID()",
"fetch('https://example.com')",
"(await import('node:fs')).readFileSync('/etc/passwd')",
"(await import('./unlisted.mjs')).secret",
]) {
const f = await fixture(registry(`return ${code};`));
try {
f.start();
assert.equal((await f.receive()).status, "failed", code);
} finally {
await f.close();
}
}
});
test("explicit dates remain useful without an implicit clock", async () => {
const f = await fixture(registry("return new Date('2026-01-01T00:00:00Z').toISOString();"));
try {
f.start();
assert.equal((await f.receive()).output, "2026-01-01T00:00:00.000Z");
} finally {
await f.close();
}
});
test("initialization and regex loops are killed, then a fresh frame can start", async () => {
for (const source of [
"while(true){};" + registry("return 1;"),
registry("return /(a+)+$/.test('a'.repeat(100000)+'!');"),
]) {
const f = await fixture(source, { deadlineMs: 350 });
try {
for (let n = 0; n < 2; n++) {
f.start();
const result = await f.receive();
assert.equal(result.error.code, "RESTRICTED_DEADLINE");
}
} finally {
await f.close();
}
}
});
test("cancellation kills the entire frame before acknowledgment and rejects late port replies", async () => {
const f = await fixture(
registry("return await channel({portId:'read',action:{kind:'state',primitive:'read'},input:null});"),
);
try {
const call = f.start();
const port = await f.receive();
f.send({ kind: "cancel", ...call });
const cancelled = await f.receive();
assert.equal(cancelled.kind, "cancelled");
f.send({ kind: "port-result", ...call, sequence: port.sequence, status: "returned", output: "late" });
const next = f.start();
const nextPort = await f.receive();
assert.equal(nextPort.invocation, next.invocation);
f.send({ kind: "port-result", ...next, sequence: nextPort.sequence, status: "returned", output: "fresh" });
const result = await f.receive();
assert.equal(result.output, "fresh");
assert.equal(result.invocation, next.invocation);
} finally {
await f.close();
}
});
test("cancellation during asynchronous preparation leaves no running frame", async () => {
const f = await fixture(registry("return 1;"));
try {
for (let n = 0; n < 5; n++) {
const call = f.start();
f.send({ kind: "cancel", ...call });
assert.equal((await f.receive()).kind, "cancelled");
}
f.start();
assert.equal((await f.receive()).output, 1);
} finally {
await f.close();
}
});
test("bounded FIFO rejects overload without losing active or queued frames", async () => {
const f = await fixture(
registry("return await channel({portId:'read',action:{kind:'state',primitive:'read'},input:request.input});"),
{ maxQueued: 2 },
);
try {
const first = f.start(1),
port = await f.receive();
const second = f.start(2),
third = f.start(3),
overflow = f.start(4);
const refused = await f.receive();
assert.equal(refused.invocation, overflow.invocation);
assert.equal(refused.error.code, "RESTRICTED_QUEUE_FULL");
f.send({ kind: "cancel", ...second });
assert.equal((await f.receive()).invocation, second.invocation);
f.send({ kind: "port-result", ...first, sequence: port.sequence, status: "returned", output: 1 });
assert.equal((await f.receive()).output, 1);
const nextPort = await f.receive();
assert.equal(nextPort.invocation, third.invocation);
assert.equal(nextPort.input, 3);
f.send({ kind: "port-result", ...third, sequence: nextPort.sequence, status: "returned", output: 3 });
assert.equal((await f.receive()).output, 3);
f.start(5);
const fresh = await f.receive();
f.send({
kind: "port-result",
invocation: fresh.invocation,
frame: fresh.frame,
sequence: fresh.sequence,
status: "returned",
output: 5,
});
assert.equal((await f.receive()).output, 5);
} finally {
await f.close();
}
});
test("queue wait consumes the deadline and cancelled work never reaches its ports", async () => {
const f = await fixture(
registry("return await channel({portId:'read',action:{kind:'state',primitive:'read'},input:null});"),
{ deadlineMs: 350 },
);
try {
const first = f.start();
await f.receive();
const queued = f.start();
const replies = [await f.receive(), await f.receive()];
assert.deepEqual(new Set(replies.map((x) => x.invocation)), new Set([first.invocation, queued.invocation]));
assert.ok(replies.every((x) => x.error.code === "RESTRICTED_DEADLINE"));
const fresh = f.start();
const port = await f.receive();
assert.equal(port.invocation, fresh.invocation);
f.send({ kind: "port-result", ...fresh, sequence: port.sequence, status: "returned", output: "fresh" });
assert.equal((await f.receive()).output, "fresh");
} finally {
await f.close();
}
});
+94
View File
@@ -0,0 +1,94 @@
// Platform entrypoint, evaluated before loading the authored registry. A fresh
// engine process owns exactly one frame. BROKER grants only that frame's ports.
const deny = () => {
throw Error("RESTRICTED_AMBIENT_ACCESS");
};
const replace = (object, key, value) =>
Object.defineProperty(object, key, { value, configurable: false, writable: false });
const NativeDate = Date;
const FixedDate = new Proxy(NativeDate, {
construct(target, args) {
if (!args.length) deny();
return Reflect.construct(target, args, target);
},
apply() {
deny();
},
});
replace(NativeDate, "now", deny);
replace(NativeDate.prototype, "constructor", FixedDate);
replace(globalThis, "Date", FixedDate);
replace(Math, "random", deny);
// Identity/value codecs need deterministic SHA-256, never entropy or key creation.
const digest = crypto.subtle.digest.bind(crypto.subtle);
replace(globalThis, "crypto", Object.freeze({ subtle: Object.freeze({ digest }) }));
for (const name of ["performance", "Temporal", "scheduler", "WebSocket", "EventSource", "SharedArrayBuffer", "Atomics"])
replace(globalThis, name, undefined);
for (const name of ["setTimeout", "setInterval", "fetch"]) replace(globalThis, name, deny);
// Intl's no-argument format defaults to the clock without consulting Date.now.
const format = Object.getOwnPropertyDescriptor(Intl.DateTimeFormat.prototype, "format").get;
Object.defineProperty(Intl.DateTimeFormat.prototype, "format", {
configurable: false,
get() {
const bound = format.call(this);
return (value) => {
if (value === undefined) deny();
return bound(value);
};
},
});
const formatToParts = Intl.DateTimeFormat.prototype.formatToParts;
replace(Intl.DateTimeFormat.prototype, "formatToParts", function (value) {
if (value === undefined) deny();
return formatToParts.call(this, value);
});
let used = false;
export default {
async fetch(request, env) {
if (used) return new Response("FRAME_EXPIRED", { status: 409 });
used = true;
const input = await request.json();
const source = await import("./package.mjs");
const registry = source[env.CONFIG.registryExport];
if (
!registry ||
typeof registry.invoke !== "function" ||
JSON.stringify(registry.exports) !== JSON.stringify(env.CONFIG.exports)
)
throw Error("RESTRICTED_REGISTRY_MISMATCH");
let open = true;
const channel = async (value) => {
if (!open) throw Error("EXPIRED_CONTEXT");
const response = await env.BROKER.fetch("http://frame/port", {
method: "POST",
body: JSON.stringify(value),
});
const result = await response.json();
if (!open) throw Error("EXPIRED_CONTEXT");
if (result.status !== "returned")
throw Object.assign(Error(result.error?.message ?? "Port failed"), {
code: result.error?.code ?? "PORT_FAILED",
});
return result.output;
};
try {
const output = await registry.invoke(
{ exportId: input.exportId, workspace: input.workspace, receiver: input.receiver, input: input.input },
channel,
);
const body = JSON.stringify({ status: "returned", output });
if (new TextEncoder().encode(body).length > env.CONFIG.maxBytes) throw Error("RESTRICTED_OUTPUT_LIMIT");
return new Response(body, { headers: { "content-type": "application/json" } });
} catch (error) {
// Only authored/codec rejection codes cross the engine boundary. Engine
// diagnostics stay private to the supervisor, never become protocol data.
const code =
typeof error?.code === "string" && /^[A-Z0-9_]{1,128}$/.test(error.code)
? error.code
: "RESTRICTED_PACKAGE_FAILED";
return Response.json({ status: "failed", error: { code, message: code } });
} finally {
open = false;
}
},
};
+45
View File
@@ -0,0 +1,45 @@
# Pinned upstream engine. This binary supplies an import/capability boundary;
# production execution additionally requires a workspace VM and OS limits.
{ pkgs }:
let
version = "1.20261005.1";
platforms = {
x86_64-linux = {
name = "linux-64";
hash = "sha256-zm0T3YLo49ORPwR4iwP+WkQBMkiZQYftc+ArBQEoNGY=";
};
aarch64-linux = {
name = "linux-arm64";
hash = "sha256-jkvHyexr8Sn2wyqmLJJEOOGfAkfaVPLI7llHOX+FbxU=";
};
};
platform =
platforms.${pkgs.stdenv.hostPlatform.system}
or (throw "Restricted execution requires Linux x86_64 or aarch64");
in
pkgs.stdenvNoCC.mkDerivation {
pname = "quixos-workerd";
inherit version;
src = pkgs.fetchurl {
url = "https://registry.npmjs.org/@cloudflare/workerd-${platform.name}/-/workerd-${platform.name}-${version}.tgz";
inherit (platform) hash;
};
nativeBuildInputs = [ pkgs.autoPatchelfHook ];
buildInputs = [ pkgs.stdenv.cc.cc.lib ];
sourceRoot = "package";
installPhase = ''
runHook preInstall
install -Dm755 bin/workerd "$out/bin/workerd"
runHook postInstall
'';
doInstallCheck = true;
installCheckPhase = ''
"$out/bin/workerd" --version | grep -Fx 'workerd 2026-10-05'
'';
meta = {
description = "Pinned workerd engine for VM-scoped Quixos restricted execution";
homepage = "https://github.com/cloudflare/workerd";
license = pkgs.lib.licenses.asl20;
platforms = builtins.attrNames platforms;
};
}
+1 -1
View File
@@ -15,7 +15,7 @@ let
group = import ./artifacts.nix {
inherit pkgs src;
inherit (context)
plan
buildContract
core
sharedRuntime
typescript
+1 -1
View File
@@ -11,7 +11,7 @@ let
group = import ./artifacts.nix {
inherit pkgs src service;
inherit (context)
plan
buildContract
core
sharedRuntime
typescript