Skip to content

CLI verbs (import, diff, lint, doc), applier modes, packaging - #4

Merged
polyedre merged 12 commits into
mainfrom
main-updates
Sep 5, 2026
Merged

CLI verbs (import, diff, lint, doc), applier modes, packaging#4
polyedre merged 12 commits into
mainfrom
main-updates

Conversation

@polyedre

@polyedre polyedre commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • hexol import: wrap existing k8s YAML / Terraform JSON
  • hexol diff [--explain], applier mode (apply|plan|diff), render --validate
  • hexol lint; kernel attributes nested-fold (hx-each) deltas to outer path
  • hexol doc: introspect define-construct schemas from the CLI
  • Version, changelog, install paths (container, nix, guix); guix.scm package
  • ledger: pad-account min 2 spaces fix
  • docs: scrub last CMDB references

🤖 Generated with Claude Code

Polyedre and others added 8 commits September 3, 2026 13:46
guix build -f guix.scm; guix shell -f guix.scm -- hexol
…dd hexol lint

for-each-into now binds a fold frame (prefix . outer-state) around each
entry's resolve, and apply-op re-embeds traced after-states through the
frames, so explain/show see (regions alpha5 …) paths instead of a bare
sub-state that never matched. Flat paths are unaffected.

Add an access log (resolve-with-access, note-read!): surface get/attr
record the (prefixed) paths they read, and (hexol lint) walks one fold to
warn on a path read before its last write — the stale-$ case model.md
defers to a lint pass. New `hexol lint` verb, exit 1 on any warning.

k8s: declare `rule` under #:replace, silencing the core-binding warning
every inventory importing (hexol k8s) printed.
The CMDB subsystem was deleted in fc804d8 but docs/authoring.md still
listed its layout and .gitignore its log. Remove both and add a README
Documentation pointer to the cmdb branch where the prototype survives.
define-construct now registers each construct's schema (name, module, head,
fields with required/default/flag/doc, construct doc) in (hexol construct)
at definition time — one register-construct! call in the expansion, call
semantics unchanged. New #:doc keyword at construct and field level.

New (hexol doc) formats the registry; `hexol doc [CONSTRUCT] [-i INV]`
lists every loaded construct or prints one's signature, fields table and a
minimal example. k8s constructs get one-line docs. Test covers the registry.
…JSON

Migration on-ramp: `hexol import -f manifests.yaml` (or `-` for stdin) emits
an inventory with one `(resource '…)` per document; `--sugar` lifts
Namespace/ConfigMap/Secret/Service/Deployment to the typed constructs when
the candidate form, evaluated, rebuilds the object exactly. Server-populated
fields of a `kubectl get` dump are stripped by default (`--no-clean` keeps).
`--from terraform` reads *.tf.json into terraform-settings/-provider/
-resource/-data/-output forms (terraform-block for the rest); HCL text is
out of scope.

(hexol import) drives the (yaml libyaml) bindings directly because
read-yaml-file reads one document from a file and drops scalar style —
plain scalars get core-schema typing, quoted ones stay strings, so a
ConfigMap's "8080" survives the trip.

test/import.scm round-trips examples/kubernetes.scm (plain and --sugar) and
examples/terraform.scm through import + render, comparing resolved state
with maps order-insensitive.
…der --validate`

Applier signature becomes (state mode -> effects), mode ∈ apply | plan | diff;
the old dry? boolean still works (#t → plan, #f → apply, via `mode-of`).
terraform: plan/diff → `plan -detailed-exitcode` (2 = drift); kubectl: plan →
--dry-run=server, diff → `kubectl diff -f -` (1 = drift); talos config-apply
accepts --diff as --dry-run. wait-for/check skip under plan/diff.

New `hexol diff [--only SPEC] [--explain]`: same pipeline as apply in mode
diff, exit 0 clean / 1 drift / 2 error. --explain binds current-diff-explainer
over the resolve trace; the kubectl applier then fetches each live object
(`kubectl get -o json`), structural-diffs it against the rendered alist (new
hexol/diff.scm) and prints per changed path the value change and the op that
set it (label + file:line). Terraform streams its plan as-is.

`hexol render -o yaml --validate` pipes the stream through kubeconform
-strict -summary when on PATH (report on stderr, exit with its status);
`kubeconform-check` is the same as an appliers-pipeline check.

Tests: test/apply-mode.scm (mode dispatch against PATH shims in
test/fixtures/bin, structural differ) and test/diff-cli.sh (exit codes,
--explain output); both wired into `make test`.
- (hexol version) exports %hexol-version = 0.1.0; `hexol --version` and
  `hexol version` print it. guix.scm carries a commented copy.
- CHANGELOG.md (Keep a Changelog): 0.1.0 summarises today, Unreleased lists
  the verbs in flight.
- Containerfile: Alpine + guile/guile-json/jq, guile-libyaml built from source
  with nyacc in a first stage (neither Alpine nor Debian packages it). ~80 MB.
  `make image` builds it; `make image-guix` is the guix pack alternative.
- flake.nix: package + devShell; nixpkgs lacks nyacc and guile-libyaml too,
  so both are built in the flake. flake.lock committed.
- README Install: source/Guix, container, Nix; Status line -> CHANGELOG.
- CI: new image job builds the Containerfile and smoke-tests it (no push).
guile-libyaml 3.0.2 (what manifest.scm resolves to today, and what the
Containerfile builds) generates its bindings with nyacc's cdata backend;
(system ffi-help-rt) / (bytestructures guile) only existed in a stale
personal profile. Rewrite read-yaml-documents / convert-tree on cdata.

Containerfile: point the binding at libyaml-0.so.2 (Alpine's runtime
package has no unversioned .so). CI: guix pull before guix shell, since
the 1.4.0 binary ships the pre-cdata guile-libyaml.
@polyedre
polyedre merged commit 1aabda3 into main Sep 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant