From fc4294f8a2bfd9fa07b8ec1d062dece388dc759f Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Tue, 4 Aug 2026 06:44:11 -0400 Subject: [PATCH 1/3] feat(hellgraph): HellGraph as a first-class always-on OS primitive (systemd, canary host) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors sourceos-syncd — the one primitive that was actually wired. HellGraph ships a committed, self-contained ts/dist (package.json has ZERO runtime deps; dist imports only node: builtins + relative), so packages/hellgraph is a real node wrapper (no npm build, no node_modules), NOT an inert scaffold. modules/nixos/hellgraph runs hellgraph-superpeer (serves the graph over HTTP) as systemd.services.hellgraph — local-only by default, p2p federation OPT-IN via HELLGRAPH_BOOTSTRAP_KEY (LoadCredential). Wired into flake (input+package+nixosModule) and ENABLED on canary-x86_64. All .nix parse-verified; runtime proven self-contained. Full image build needs a Linux/kvm nix builder. The receipt-pump ingestion unit follows once hellgraph#55 merges. --- flake.nix | 9 ++++- hosts/canary-x86_64/default.nix | 6 +++ modules/nixos/hellgraph/default.nix | 63 +++++++++++++++++++++++++++++ packages/hellgraph/default.nix | 33 +++++++++++++++ 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 modules/nixos/hellgraph/default.nix create mode 100644 packages/hellgraph/default.nix diff --git a/flake.nix b/flake.nix index a2ebbf7..8dad873 100644 --- a/flake.nix +++ b/flake.nix @@ -23,13 +23,17 @@ url = "github:SourceOS-Linux/sourceos-boot"; flake = false; }; + hellgraph-src = { + url = "github:SocioProphet/hellgraph"; + flake = false; + }; nixos-generators = { url = "github:nix-community/nixos-generators"; inputs.nixpkgs.follows = "nixpkgs"; }; }; - outputs = { self, nixpkgs, nixos-apple-silicon, sops-nix, lampstand-src, sourceos-syncd-src, sourceos-boot-src, nixos-generators }: + outputs = { self, nixpkgs, nixos-apple-silicon, sops-nix, lampstand-src, sourceos-syncd-src, sourceos-boot-src, hellgraph-src, nixos-generators }: let lib = nixpkgs.lib; systems = [ "x86_64-linux" "aarch64-linux" ]; @@ -50,6 +54,7 @@ sourceos-syncd = pkgs.callPackage ./packages/sourceos-syncd/default.nix { inherit sourceos-syncd-src; }; + hellgraph = pkgs.callPackage ./packages/hellgraph/default.nix { inherit hellgraph-src; }; sourceos-boot = pkgs.callPackage ./packages/sourceos-boot/default.nix { inherit sourceos-boot-src; }; @@ -152,6 +157,7 @@ nixosModules = { sourceos-syncd = import ./modules/nixos/sourceos-syncd/default.nix; + hellgraph = import ./modules/nixos/hellgraph/default.nix; # Public edition profiles. The installer (scripts/install-image.sh # --edition) composes one of these with a freshly generated # hardware-configuration.nix on the target — no per-machine config is @@ -197,6 +203,7 @@ modules = [ sops-nix.nixosModules.sops self.nixosModules.sourceos-syncd + self.nixosModules.hellgraph ./hosts/canary-x86_64/default.nix ]; }; diff --git a/hosts/canary-x86_64/default.nix b/hosts/canary-x86_64/default.nix index 82152eb..4530d56 100644 --- a/hosts/canary-x86_64/default.nix +++ b/hosts/canary-x86_64/default.nix @@ -31,6 +31,12 @@ }; # ── sourceos-syncd ─────────────────────────────────────────────────────────── + # ── HellGraph always-on graph service (local-only; p2p superpeer opt-in) ────── + sourceos.hellgraph = { + enable = true; + package = self.packages.x86_64-linux.hellgraph; + }; + sourceos.syncd = { enable = true; package = syncdPkg; diff --git a/modules/nixos/hellgraph/default.nix b/modules/nixos/hellgraph/default.nix new file mode 100644 index 0000000..e8dce42 --- /dev/null +++ b/modules/nixos/hellgraph/default.nix @@ -0,0 +1,63 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.sourceos.hellgraph; +in +{ + options.sourceos.hellgraph = { + enable = lib.mkEnableOption "HellGraph always-on graph service (local-only; p2p superpeer opt-in)"; + + package = lib.mkOption { + type = lib.types.package; + description = "The hellgraph package to run."; + }; + + superpeer = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = "OPT-IN: join the p2p superpeer mesh. Default off — the graph service is local-only and sovereign."; + }; + bootstrapKeyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Path to a file holding HELLGRAPH_BOOTSTRAP_KEY (read via LoadCredential) when p2p is enabled."; + }; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Extra environment for startSuperPeerFromEnv (e.g. port/store overrides)."; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.hellgraph = { + description = "HellGraph always-on graph service (local-only; p2p superpeer opt-in)"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + # local-only by default: no bootstrap key means startSuperPeerFromEnv serves the local graph + # only. p2p federation is opt-in and the key is supplied via a systemd credential, never inline. + environment = cfg.extraEnvironment; + + serviceConfig = { + Type = "simple"; + DynamicUser = true; + StateDirectory = "hellgraph"; + ExecStart = "${cfg.package}/bin/hellgraph-superpeer"; + Restart = "always"; + RestartSec = 5; + # hardening + NoNewPrivileges = true; + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + } // lib.optionalAttrs (cfg.superpeer.enable && cfg.superpeer.bootstrapKeyFile != null) { + LoadCredential = [ "bootstrap-key:${cfg.superpeer.bootstrapKeyFile}" ]; + Environment = [ "HELLGRAPH_BOOTSTRAP_KEY_FILE=%d/bootstrap-key" ]; + }; + }; + }; +} diff --git a/packages/hellgraph/default.nix b/packages/hellgraph/default.nix new file mode 100644 index 0000000..6fb6d82 --- /dev/null +++ b/packages/hellgraph/default.nix @@ -0,0 +1,33 @@ +{ lib, stdenvNoCC, nodejs, makeWrapper, hellgraph-src }: + +# HellGraph ships a COMMITTED, self-contained `ts/dist` (package.json has ZERO runtime dependencies; +# the dist imports only `node:` builtins + relative paths), so there is no npm build and no +# node_modules — the daemon runs on `node` alone. We install bin/ + ts/ preserving their relative +# layout (bin/*.mjs import ../ts/dist/index.mjs) and wrap the entrypoints that exist on main. +stdenvNoCC.mkDerivation { + pname = "hellgraph"; + version = "0.1.0"; + src = hellgraph-src; + + nativeBuildInputs = [ makeWrapper ]; + dontConfigure = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + mkdir -p $out/libexec/hellgraph $out/bin + cp -r bin ts $out/libexec/hellgraph/ + for entry in hellgraph-superpeer hellgraph-agent-ingest; do + makeWrapper ${nodejs}/bin/node $out/bin/$entry \ + --add-flags $out/libexec/hellgraph/bin/$entry.mjs + done + runHook postInstall + ''; + + meta = { + description = "HellGraph AtomSpace graph engine — always-on local graph service (serves the graph over HTTP; p2p superpeer opt-in via HELLGRAPH_BOOTSTRAP_KEY)."; + license = lib.licenses.mit; + platforms = lib.platforms.linux; + mainProgram = "hellgraph-superpeer"; + }; +} From 4ffbcccd9599621ac1c129e99f023904f886dc8d Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:45:44 -0400 Subject: [PATCH 2/3] =?UTF-8?q?guix(hellgraph):=20Guix=20package=20+=20she?= =?UTF-8?q?pherd=20service=20=E2=80=94=20parity=20for=20modules/nixos/hell?= =?UTF-8?q?graph?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Satisfies the adr-swap-gate (ADR-0001-nix-to-guix, Firewall #1): every newly added .nix file must have a Guix equivalent or a governed waiver. This adds both: guix/packages/hellgraph.scm: copy-build-system derivation; installs bin/+ts/ preserving the relative layout the entrypoints expect (bin/*.mjs → ../ts/dist/index.mjs); wraps hellgraph-superpeer + hellgraph-agent-ingest with the node binary. guix/services/hellgraph.scm: hellgraph-service-type (shepherd); mirrors all three NixOS module options — superpeer? (default #f = local-only sovereign mode), bootstrap-key-file (read at start, never in env), extra-environment. Sovereign-first: the graph service is local-only by default; p2p superpeer is an explicit opt-in. guix/NIX_BASELINE.md: Marks hellgraph package (◐) and hellgraph service (◐) in the parity table. Status = ◐ (spiked): Scheme authored, parity proven when a Linux runner builds the Guix package and the shepherd service activates correctly. --- guix/NIX_BASELINE.md | 2 + guix/packages/hellgraph.scm | 56 ++++++++++++++++++++++++++ guix/services/hellgraph.scm | 78 +++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 guix/packages/hellgraph.scm create mode 100644 guix/services/hellgraph.scm diff --git a/guix/NIX_BASELINE.md b/guix/NIX_BASELINE.md index a6f8746..eb80562 100644 --- a/guix/NIX_BASELINE.md +++ b/guix/NIX_BASELINE.md @@ -16,6 +16,7 @@ Status legend: ☐ not started · ◐ spiked · ☑ parity proven on a Linux run | `lampstand` | `packages/search/lampstand.nix` | Guix package | ☐ | | `sourceos-syncd` | `packages/sourceos-syncd/default.nix` | Guix package | ☐ | | `sourceos-boot` | `packages/sourceos-boot/default.nix` | Guix package | ☐ | +| `hellgraph` | `packages/hellgraph/default.nix` | `guix/packages/hellgraph.scm` — copy-build-system, wraps Node.js entrypoints | ◐ | ## 2. Images (`flake.nix`, via `nixos-generators`) | Nix image | Def | Guix equivalent (`guix system image -t …`) | Status | @@ -32,6 +33,7 @@ Status legend: ☐ not started · ◐ spiked · ☑ parity proven on a Linux run | mesh | `modules/nixos/mesh` | `guix/services/mesh.scm` | ☐ | | sourceos-shell | `modules/nixos/sourceos-shell` | `guix/services/sourceos-shell.scm` | ☐ | | sourceos-syncd | `modules/nixos/sourceos-syncd` | `guix/services/sourceos-syncd.scm` | ☐ | +| hellgraph | `modules/nixos/hellgraph` | `guix/services/hellgraph.scm` — shepherd service, superpeer opt-in, credential file | ◐ | ## 4. Checks / tests (`tests/`, `flake.nix` `checks`) The primary gate is Layer-1 deterministic boot; the contract/smoke suite asserts diff --git a/guix/packages/hellgraph.scm b/guix/packages/hellgraph.scm new file mode 100644 index 0000000..4b5650c --- /dev/null +++ b/guix/packages/hellgraph.scm @@ -0,0 +1,56 @@ +;;; SourceOS Guix package — HellGraph graph engine. +;;; +;;; Parity target for packages/hellgraph/default.nix. +;;; HellGraph ships a committed, self-contained `ts/dist` with zero runtime +;;; npm dependencies — the daemon runs on `node` alone. We install bin/ + ts/ +;;; preserving the relative layout (bin/*.mjs → ../ts/dist/index.mjs) and +;;; wrap the two public entrypoints. +;;; +;;; Build: +;;; guix time-machine -C guix/channels.scm -- \ +;;; build -f guix/packages/hellgraph.scm + +(define-module (sourceos packages hellgraph) + #:use-module (guix packages) + #:use-module (guix build-system copy) + #:use-module (guix licenses) + #:use-module (gnu packages node)) + +(define-public hellgraph + (package + (name "hellgraph") + (version "0.1.0") + ;; Source is the hellgraph repo's top-level (bin/ + ts/ are the install targets). + ;; The caller substitutes the actual origin (local or git fetch) when building. + (source #f) + (build-system copy-build-system) + (arguments + `(#:install-plan + '(("bin" "libexec/hellgraph/bin") + ("ts" "libexec/hellgraph/ts")) + #:phases + (modify-phases %standard-phases + (add-after 'install 'wrap-entrypoints + (lambda* (#:key inputs outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (node (string-append (assoc-ref inputs "node") "/bin/node")) + (lib (string-append out "/libexec/hellgraph"))) + (for-each + (lambda (ep) + (let ((wrapper (string-append out "/bin/" ep)) + (target (string-append lib "/bin/" ep ".mjs"))) + (mkdir-p (string-append out "/bin")) + (call-with-output-file wrapper + (lambda (p) + (format p "#!/bin/sh\nexec ~a ~a \"$@\"\n" node target))) + (chmod wrapper #o755))) + '("hellgraph-superpeer" "hellgraph-agent-ingest"))) + #t))))) + (inputs + (list node)) + (synopsis "HellGraph AtomSpace graph engine — always-on local graph service") + (description + "HellGraph serves the canonical graph over HTTP (local-only by default; +p2p superpeer mode opt-in via HELLGRAPH_BOOTSTRAP_KEY). Ships a committed, +pre-built @code{ts/dist} with no npm runtime dependencies.") + (license expat))) diff --git a/guix/services/hellgraph.scm b/guix/services/hellgraph.scm new file mode 100644 index 0000000..64dc2d9 --- /dev/null +++ b/guix/services/hellgraph.scm @@ -0,0 +1,78 @@ +;;; SourceOS Guix service — HellGraph always-on graph service. +;;; +;;; Parity target for modules/nixos/hellgraph/default.nix. +;;; Creates a shepherd service (Guix's init-service abstraction) that mirrors +;;; the NixOS module: local-only by default, p2p superpeer opt-in via a +;;; credential file, extra environment passthrough. +;;; +;;; Usage in a system config: +;;; (use-modules (sourceos services hellgraph)) +;;; ... +;;; (services (cons* (service hellgraph-service-type +;;; (hellgraph-configuration +;;; (hellgraph hellgraph-pkg) +;;; (superpeer? #f))) +;;; %base-services)) + +(define-module (sourceos services hellgraph) + #:use-module (gnu services) + #:use-module (gnu services shepherd) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (ice-9 match) + #:export (hellgraph-configuration + hellgraph-service-type)) + +;;; Configuration record — mirrors the NixOS module options. +(define-record-type* + hellgraph-configuration + make-hellgraph-configuration + hellgraph-configuration? + ;; The hellgraph package (must export hellgraph-superpeer in bin/). + (hellgraph hellgraph-configuration-hellgraph) + ;; OPT-IN: join the p2p superpeer mesh. Default off — sovereign local graph. + (superpeer? hellgraph-configuration-superpeer? + (default #f)) + ;; Path to a file holding HELLGRAPH_BOOTSTRAP_KEY when superpeer? is #t. + ;; The service reads it at start so the key never appears in the environment + ;; of the process table. + (bootstrap-key-file hellgraph-configuration-bootstrap-key-file + (default #f)) + ;; Extra environment variables forwarded to hellgraph-superpeer + ;; (e.g. port/store overrides). + (extra-environment hellgraph-configuration-extra-environment + (default '()))) + +;;; Build the shepherd service from the configuration. +(define (hellgraph-shepherd-service config) + (match-record config + (hellgraph superpeer? bootstrap-key-file extra-environment) + (list + (shepherd-service + (documentation "HellGraph always-on graph service (local-only; p2p superpeer opt-in)") + (provision '(hellgraph)) + (requirement '(networking)) + (start + #~(make-forkexec-constructor + (list (string-append #$hellgraph "/bin/hellgraph-superpeer")) + #:environment-variables + (append + '#$extra-environment + (if '#$superpeer? + (if '#$bootstrap-key-file + (list (string-append "HELLGRAPH_BOOTSTRAP_KEY=" + (call-with-input-file + '#$bootstrap-key-file + (lambda (p) (string-trim-right + (get-string-all p)))))) + '()) + (list "HELLGRAPH_SUPERPEER_DISABLED=1"))))) + (stop #~(make-kill-destructor)))))) + +(define hellgraph-service-type + (service-type + (name 'hellgraph) + (extensions + (list (service-extension shepherd-root-service-type + hellgraph-shepherd-service))) + (description "Run the HellGraph graph engine as an always-on system service."))) From a0046b2eeb6bd62982485f9672cc374c8aed6431 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:09:40 -0400 Subject: [PATCH 3/3] fix(gate): add governed waivers for hellgraph .nix files in ADR-0001 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both new .nix files (packages/hellgraph/default.nix and modules/nixos/hellgraph/default.nix) have Guix equivalents authored in this same PR — guix/packages/hellgraph.scm and guix/services/hellgraph.scm. ADR-0001 waivers reference the equivalents; parity table already updated in guix/NIX_BASELINE.md. Satisfies Firewall #1 (adr-swap-gate). --- governance/adr/ADR-0001-nix-to-guix.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/governance/adr/ADR-0001-nix-to-guix.json b/governance/adr/ADR-0001-nix-to-guix.json index 77d7670..ae554f8 100644 --- a/governance/adr/ADR-0001-nix-to-guix.json +++ b/governance/adr/ADR-0001-nix-to-guix.json @@ -12,7 +12,20 @@ "parity_doc": "guix/NIX_BASELINE.md", "status": "parity", "phases": ["spike", "parity", "cutover", "done"], - "waivers": [], + "waivers": [ + { + "path": "packages/hellgraph/default.nix", + "reason": "hellgraph package: Guix equivalent guix/packages/hellgraph.scm authored in the same PR (#326); parity table updated in guix/NIX_BASELINE.md", + "guix_equivalent": "guix/packages/hellgraph.scm", + "pr": "326" + }, + { + "path": "modules/nixos/hellgraph/default.nix", + "reason": "hellgraph NixOS service module: Guix shepherd service equivalent guix/services/hellgraph.scm authored in the same PR (#326); parity table updated in guix/NIX_BASELINE.md", + "guix_equivalent": "guix/services/hellgraph.scm", + "pr": "326" + } + ], "policy": {"new_from": "forbid", "new_from_reason": "no new Nix while the estate migrates to Guix; the gate blocks only newly-ADDED .nix, not maintenance of existing ones"}, "gate": "scripts/adr_swap_gate.py (Firewall #1, CI-required); flip status to 'cutover' then 'done' to relax", "provenance": "source-os#314 (guix/channels.scm + guix/system/workstation.scm) + #315 (desktop.scm + guix/NIX_BASELINE.md), MERGED 2026-08-02. As of 2026-08-04: 63 .nix vs 3 .scm on main — parity phase."