From 7e4fb60155340477adc0847d2d6e61a7391f13b0 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Tue, 8 Sep 2026 14:27:24 -0700 Subject: [PATCH 1/3] Generate typed QX bindings and add source editing tools --- quixos-package-helpers.nix | 46 +++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/quixos-package-helpers.nix b/quixos-package-helpers.nix index 44e9c5b..6ff117a 100644 --- a/quixos-package-helpers.nix +++ b/quixos-package-helpers.nix @@ -510,6 +510,20 @@ EOF ''; }; + # Language-neutral, offline schema compilation. Snapshot directories must be + # fixed Nix inputs matching the resource lock's complete dependency closure. + mkQxBindingSchema = { pkgs, protocol, src, repository, commit, resources ? [ ] }: + let + snapshots = pkgs.writeText "qx-binding-snapshots.json" (builtins.toJSON { inherit resources; }); + in pkgs.runCommand "qx-binding-schema.json" { } '' + ${protocol}/bin/quixos-resource-compile \ + --root ${src} --kind package \ + --repository ${pkgs.lib.escapeShellArg repository} \ + --commit ${pkgs.lib.escapeShellArg commit} \ + --checkout-root "$TMPDIR/checkouts" \ + --snapshot-map ${snapshots} --snapshot-only true --schema-out "$out" > /dev/null + ''; + mkCaminoTsYarnNixifyFlake = { inputs, @@ -522,6 +536,9 @@ EOF nodejsAttr ? "nodejs_24", buildCommand ? "yarn build", buildEnv ? { }, + # An exact, compiler-produced BindingSchema JSON artifact and a backend. + # Other language helpers can consume the same schema with their own generator/runtime. + bindings ? null, nativeBuildInputs ? [ ], devShellPackages ? [ ], devShellHook ? "", @@ -560,6 +577,29 @@ EOF attrs ); + bindingConfig = if bindings == null then null else callOption bindings; + bindingSchema = if bindingConfig == null then null else bindingConfig.schema or (mkQxBindingSchema { + inherit pkgs; + protocol = bindingConfig.generator; + src = packageRoot; + inherit (bindingConfig) repository commit; + resources = bindingConfig.resources or [ ]; + }); + bindingOutput = if bindingConfig == null then "src/gen/qx.ts" else bindingConfig.output or "src/gen/qx.ts"; + bindingOptions = if bindingConfig == null then null else + pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (bindingConfig.options or { })); + bindingCommand = if bindingConfig == null then "" else '' + mkdir -p ${lib.escapeShellArg (dirOf bindingOutput)} + ${bindingConfig.generator}/bin/quixos-codegen-ts \ + ${lib.escapeShellArg (toString bindingSchema)} \ + ${lib.escapeShellArg bindingConfig.packageRevisionId} \ + ${lib.escapeShellArg bindingOutput} ${bindingOptions} + ''; + generateBindings = pkgs.writeShellApplication { + name = "qx-generate-bindings"; + text = bindingCommand; + }; + bundleConfig = if bundle == null then { } else bundle; bundleOutfile = bundleConfig.outfile or "server.mjs"; bundlePlatform = bundleConfig.platform or "node"; @@ -634,6 +674,8 @@ EOF buildPhase = '' runHook preBuild ${exportsFor (callOption buildEnv)} + ${bindingCommand} + ${lib.optionalString (bindingConfig != null) "yarn exec tsc --noEmit"} ${buildCommand} ${bundleCommand} runHook postBuild @@ -671,7 +713,8 @@ EOF packages = [ nodejs project.yarn-freestanding - ] ++ callOption devShellPackages; + ] ++ lib.optional (bindingConfig != null) generateBindings ++ callOption devShellPackages; + # Explicit command keeps shell entry free of source mutations. shellHook = devShellHookBase + callOption devShellHook; }; } // maybeServerOutputs @@ -683,5 +726,6 @@ in mkTsPackageServer mkSchemaSupport mkCaminoSourcePackage + mkQxBindingSchema mkCaminoTsYarnNixifyFlake; } From 32652d52795338feb72f53e585de347f0eb71cd9 Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Thu, 10 Sep 2026 18:27:41 -0700 Subject: [PATCH 2/3] Implement workspace evolution, migrations, and runtime continuity Enable evolution by default for source-backed workspaces. Add stable conformance ownership, semantic-major review, candidate typechecking, and durable fenced cutover with explicit migrations and forward recovery. Independently supervise package runtimes so unchanged resource owners keep their processes and connections across cutover. Add scoped invocation authority, resource sessions, and typed callback rebinding. Wire opaque object references through generated bindings and RPCs. Add canonical relationship sets, keyed maps, and ordered lists with scoped transactional mutations, revision checks, and inverse consistency. Support planned cascade deletion, protection, tombstones, and lifecycle foundations. Add journaled structural edits, package/function/migration scaffolding, managed repository creation, and resumable bottom-up dependency pin publication. Document lifetime boundaries, revision pinning, prototype compatibility policy, commands, and deferred work. Validate with 210 tests, user-systemd process/connection continuity, generated-package TypeScript checks, and Nix host/protocol checks. TTL handoff, physical reclamation, general multi-step migrations, and root-systemd migration isolation acceptance remain deferred. --- check-receipt.mjs | 17 +++++++++++++++++ quixos-package-helpers.nix | 27 ++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 check-receipt.mjs diff --git a/check-receipt.mjs b/check-receipt.mjs new file mode 100644 index 0000000..5e2d602 --- /dev/null +++ b/check-receipt.mjs @@ -0,0 +1,17 @@ +import fs from "node:fs"; +import crypto from "node:crypto"; +const [schemaPath, packageRevisionId, bindingOutput, generatorPath, output] = process.argv.slice(2); +if (!schemaPath || !packageRevisionId || !bindingOutput || !generatorPath || !output) throw new Error("Missing candidate check receipt inputs"); +const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object" + ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, canonical(entry)])) : value; +const hash = (value) => `sha256:${crypto.createHash("sha256").update(JSON.stringify(canonical(value))).digest("hex")}`; +const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8")); +if (!schema.packages.some((entry) => entry.revisionId === packageRevisionId)) throw new Error("Checked binding schema lacks the package"); +const generated = fs.readFileSync(bindingOutput, "utf8"); +if (generated !== fs.readFileSync(".qx-checked-bindings", "utf8")) throw new Error("Build replaced candidate-generated bindings; its check is not evidence for this candidate"); +const receipt = { + schemaVersion: 1, packageRevisionId, success: true, bindingSchema: schema, + bindingSchemaDigest: hash(schema), generatedDigest: hash(generated), + checkerDigest: hash({ generatorPath, compiler: JSON.parse(fs.readFileSync("node_modules/typescript/package.json", "utf8")), lock: fs.readFileSync("yarn.lock", "utf8") }), +}; +fs.writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`, { flag: "wx" }); diff --git a/quixos-package-helpers.nix b/quixos-package-helpers.nix index 6ff117a..bd4ac77 100644 --- a/quixos-package-helpers.nix +++ b/quixos-package-helpers.nix @@ -539,6 +539,9 @@ EOF # An exact, compiler-produced BindingSchema JSON artifact and a backend. # Other language helpers can consume the same schema with their own generator/runtime. bindings ? null, + # A dedicated entrypoint calling SDK serveMigration; never start the + # normal package server in the isolated migration execution boundary. + migrationEntrypoint ? null, nativeBuildInputs ? [ ], devShellPackages ? [ ], devShellHook ? "", @@ -661,6 +664,17 @@ EOF ${lib.optionalString (installConfig ? descriptorPath && descriptorPath != null) '' cp ${lib.escapeShellArg descriptorPath} "$out/${descriptorPath}" ''} + ${lib.optionalString (bindingConfig != null) '' + install -m 0444 quixos-check.json "$out/quixos-check.json" + ''} + ${lib.optionalString (migrationEntrypoint != null) '' + install -Dm444 migration.mjs "$out/libexec/${serverLibexecName}/migration.mjs" + cat > "$out/bin/migrate" < Date: Sun, 13 Sep 2026 19:29:33 -0700 Subject: [PATCH 3/3] Migrate TODO implementations to generated candidate bindings --- checked-package.nix | 13 +++++++++++++ quixos-package-helpers.nix | 21 ++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 checked-package.nix diff --git a/checked-package.nix b/checked-package.nix new file mode 100644 index 0000000..8e82ae6 --- /dev/null +++ b/checked-package.nix @@ -0,0 +1,13 @@ +# The caller has compiled this package and its exact recursive candidate graph. +# No credentials, mutable references, or workspace-wide unrelated schema enter +# the package derivation. Ordinary #server builds are not promotion evidence. +{ source, schema, generator, packageRevisionId, system ? builtins.currentSystem }: +let + package = builtins.getFlake ("path:" + source); + checked = package.quixosPackages.${system}.checkedServer or + (throw "Package ${packageRevisionId} lacks checkedServer. Upgrade its Nix helper and adopt generated implementation bindings before cutover."); +in checked { + inherit packageRevisionId; + schema = builtins.path { path = /. + schema; name = "candidate-package-bindings.json"; }; + generator = builtins.storePath generator; +} diff --git a/quixos-package-helpers.nix b/quixos-package-helpers.nix index bd4ac77..23c9162 100644 --- a/quixos-package-helpers.nix +++ b/quixos-package-helpers.nix @@ -539,6 +539,7 @@ EOF # An exact, compiler-produced BindingSchema JSON artifact and a backend. # Other language helpers can consume the same schema with their own generator/runtime. bindings ? null, + bindingOptions ? { }, # A dedicated entrypoint calling SDK serveMigration; never start the # normal package server in the isolated migration execution boundary. migrationEntrypoint ? null, @@ -551,6 +552,8 @@ EOF flake-utils.lib.eachDefaultSystem ( system: let + outputsFor = candidateBindings: + let pkgs = import nixpkgs { inherit system; }; lib = pkgs.lib; nodejs = pkgs.${nodejsAttr}; @@ -580,7 +583,8 @@ EOF attrs ); - bindingConfig = if bindings == null then null else callOption bindings; + bindingConfig = if candidateBindings != null then candidateBindings + else if bindings == null then null else callOption bindings; bindingSchema = if bindingConfig == null then null else bindingConfig.schema or (mkQxBindingSchema { inherit pkgs; protocol = bindingConfig.generator; @@ -589,14 +593,14 @@ EOF resources = bindingConfig.resources or [ ]; }); bindingOutput = if bindingConfig == null then "src/gen/qx.ts" else bindingConfig.output or "src/gen/qx.ts"; - bindingOptions = if bindingConfig == null then null else - pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (bindingConfig.options or { })); + bindingOptionsFile = if bindingConfig == null then null else + pkgs.writeText "qx-typescript-options.json" (builtins.toJSON (bindingConfig.options or bindingOptions)); bindingCommand = if bindingConfig == null then "" else '' mkdir -p ${lib.escapeShellArg (dirOf bindingOutput)} ${bindingConfig.generator}/bin/quixos-codegen-ts \ ${lib.escapeShellArg (toString bindingSchema)} \ ${lib.escapeShellArg bindingConfig.packageRevisionId} \ - ${lib.escapeShellArg bindingOutput} ${bindingOptions} + ${lib.escapeShellArg bindingOutput} ${bindingOptionsFile} ''; generateBindings = pkgs.writeShellApplication { name = "qx-generate-bindings"; @@ -742,7 +746,14 @@ EOF # Explicit command keeps shell entry free of source mutations. shellHook = devShellHookBase + callOption devShellHook; }; - } // maybeServerOutputs + } // maybeServerOutputs; + in (outputsFor null) // { + # The workspace supplies a compiler-produced schema for the exact + # candidate graph. Standalone builds may use checked-in authoring types, + # but only this build path regenerates and witnesses candidate contracts. + quixosPackages.checkedServer = { schema, generator, packageRevisionId }: + (outputsFor { inherit schema generator packageRevisionId; }).packages.server; + } ); in {