Conversation
The apt-based CI couldn't satisfy the build: (hexol k8s)/cmdb need (json) from guile-json and (hexol ansible) needs (yaml) from guile-libyaml, which isn't packaged for Ubuntu. hexol is a Guix project anyway, so CI now installs Guix and runs the suite inside `guix shell -m manifest.scm`, making the manifest the single source of truth. - manifest.scm: add guile-json + guile-libyaml (the real deps). - ci.yml: install Guix, run make build/test/test-examples in the guix shell. - README: document the Guile-library dependencies and the `guix shell -m manifest.scm` install path. Verified locally: `guix shell -m manifest.scm -- make build test test-examples` is fully green.
guix-install.sh blocks on a yes/no prompt that piped input can't satisfy. Switch to the manual binary-tarball install from the Guix manual: extract, create build users, start the daemon, authorize the official substitute key, then run the suite in guix shell — all one step so the backgrounded daemon survives.
examples/terraform.scm reads ~/.ssh/id_*.pub at render time; the runner has none, so generate an ephemeral key (used only as rendered data, never to connect).
examples/secrets.scm ships an inline (secrets-store …) sealed to an age recipient plus its throwaway identity, so `hexol render -o yaml -i examples/secrets.scm` decrypts and substitutes real plaintext on a fresh clone (the secrets it guards are fake). A `fetch-age-key` hook documents where the decryption key comes from and how to override it. `hexol secret` now loads the inventory before shelling out to sops, so the inventory's own key provisioning — e.g. (setenv "SOPS_AGE_KEY" …) — is honored by the management verbs too, not just `render`. Wire the example into the render smoke-test (it degrades to placeholders where sops is absent, so CI stays green) and point the README at it.
Replace the file-writing terraform-outputter with two read-only CLI actions in (hexol apply): - terraform-output-reporter: `hexol output` mirrors `terraform output` — bare prints all outputs, a NAME positional prints that one with -raw (so `hexol output kubeconfig > deploy/kubeconfig` lands the file), --json for machine-readable. No files written by hexol itself. - terraform-validator: `hexol validate` writes the rendered config, `tofu init -backend=false` then `tofu validate` — checks exactly what apply would push, touching no infra/state/creds. Factor config-writing into write-terraform-config, shared by the applier and validator. Wire both actions into examples/homelab.scm.
The footer re-explained every verb's behavior in prose, pushing the verb/inventory-action list — the part --help is actually used for — far down. Keep only the notes that don't fit a synopsis line: where the inventory comes from and the global --color flag.
Inline secrets no longer need a hand-written id. `(hx-secret "ENC[…]")` is keyed by its path in the resolved state (e.g. kubernetes_resources.app-secrets.stringData.DB_PASSWORD); `(hx-secret 'id "ENC")` still pins an explicit, rename-safe id, and `(secret-ref 'id)` references one. resolve-secret-refs walks the state tracking the path, gathers each marker's ciphertext keyed by id-or-path, decrypts the shared envelope once, and substitutes. The inline registry is gone — the marker carries its cipher. `hexol secret` now folds the inventory (decryption disabled via the secret-resolution-disabled parameter) and runs the same path walk, so seal and decrypt agree on keys; ciphertext byte-search still locates each splice site. Verbs name secrets by path or id. set/edit/rm/edit-all now seal to the recipients already in the envelope (sops -e --age/--pgp), so they need no .sops.yaml and never silently switch a store's recipients; rekey still rotates via the .sops.yaml creation rule. This makes examples/secrets.scm self-managing in-repo without an age creation rule. examples/secrets.scm uses the no-symbol form.
cache.scm carried a byte-for-byte copy of kernel's FNV-1a/64 hash and its u64 constants (its own comment noted it 'mirrors (hexol kernel)'s addresser'). Export fnv1a-64 from the kernel and import it; cache keeps only the 3-line hex16 wrapper.
deep-merge-with and its merge-by-key helper (replace / append / replace-by-key strategies) had no caller outside their own test — every real merge goes through plain deep-merge. Collapse deep-merge to the self-contained no-strategy recursion and delete the strategy machinery, its exports, and its test block.
op:copy / op:move / op:delete and their hx-copy / hx-move / hx-delete surface macros had no caller in any example, library, or inventory — only their own tests. Remove the ops, the %copy/%move/%delete macros, the exports, and the tests. state-delete stays as a state primitive (symmetric with state-set/state-append).
Every typed constructor paired a %name define* (full #:key list) with a define-construct re-declaring the same fields, whose #:build only forwarded them — each field written three times. For the 15 helpers called by nothing but their own construct, fold the body straight into #:build and delete the helper. The 7 helpers shared by composites (%deployment, %ingress, %custom-resource, %namespace, %service-account, %cluster-role, %cluster-role-binding) stay: they're the runtime-keyword implementation the macro surface can't forward into (a #:map field can't splice a runtime alist). No output change — golden renders and the test suite are byte-identical.
state-diff carried map-alist?, a near-copy of (hexol yaml) object-shape? differing only by the distinct-key check. Resolved state never has duplicate keys (state-set/deep-merge always replace), so the two agree on every input state-diff sees; drop map-alist? and call object-shape? (already imported, already used by sequence-of-maps?). show/explain output byte-identical.
show's source s-expression was run through a hand-rolled, string-aware Scheme tinter (token-end + tint-atom + highlight-sexp, ~75 lines) purely to color a debug view. pretty-print already lays the form out; print it indented and uncolored. Tree labels, the hash gutter, and the red/green state delta keep their color. Non-TTY output (and the golden renders) unchanged.
The CLI's JSON renderer lived in the CMDB namespace, forcing bin/hexol to depend on cmdb just for sexp->json-string. Move it to (hexol json), reusing (hexol yaml) object-shape? instead of a third local copy, and repoint bin/hexol, cmdb/server, and the build target. Render output identical.
The event-sourced CMDB (HTTP fact-log server, versioned-library replay, waved rollout tooling) was a second product beside the inventory engine that the hexol CLI never invoked — its only tie, the JSON adapter, moved to (hexol json) in the previous commit. Remove cmdb/, bin/cmdb-server, bin/sync-inventory, bin/promote, docs/cmdb.md, the two cmdb tests, the cmdb-only examples/regions.scm, and the Makefile test lines; scrub the README/model.md references. (This also subsumes the v1/v2 library duplication finding.) docs/authoring.md still lists the cmdb layout but is mid-edit in the working tree, so its scrub is left to that change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds GitHub Actions CI and fixes the dependency provisioning so it can actually build.
.github/workflows/ci.yml) — on every push tomainand every PR: install Guix, then runmake build test test-examplesinsideguix shell -m manifest.scm.manifest.scm— now declares the real dependencies:guile,guile-json(the(json)module),guile-libyaml(the(yaml)module),jq. It's the single source of truth; CI and dev provision from it identically.test/examples.sh+make test-examples— render each standalone example through its own renderer and assert it exits 0 (smoke, not golden).guix shell -m manifest.scminstall path; FAQ no longer says "no CI yet".Why Guix and not apt
The first apt-based attempt went red twice:
(hexol k8s)/cmdb import(json)(guile-json) and(hexol ansible)imports(yaml)(guile-libyaml). guile-libyaml is FFI-based (nyacc + guile-bytestructures + libyaml) and isn't packaged for Ubuntu, so building it from apt is a fragile multi-package chain. hexol is already a Guix project (.envrcrunsuse_guix), so provisioning CI frommanifest.scmis reproducible and matches the dev environment.Verification
Locally,
guix shell -m manifest.scm -- make build test test-examplesis fully green (kernel/surface/construct/k8s/cmdb tests + all six example renders).Note:
mainis currently red — the earlier apt-based CI commits landed there before we switched to PRs. Merging this fixes it.🤖 Generated with Claude Code