Files
harmony/AGENTS.md
Jean-Gabriel Gill-Couture 3a6006846b
All checks were successful
Run Check Script / check (push) Successful in 1m52s
Compile and package harmony_composer / package_harmony_composer (push) Successful in 8m3s
docs: align roadmap and review workflow
2026-07-14 23:52:04 -04:00

8.5 KiB

AGENTS.md

The engineering contract for this repo. Read it before any change.

The bar: minimal, DRY, no bloat — non-negotiable

Match the implementation to the real complexity of the problem, and stop there. The shortest correct solution wins. If 200 lines do a 20-line job, it's wrong — rewrite it. A senior calling your code "overcomplicated" is a failed review.

  • DRY in the purest sense. Every piece of knowledge has exactly one authoritative representation. The same condition written twice — even in unrelated parts of the system — is a defect. The discriminator: the same check at two trust boundaries (untrusted frontend + trusted backend) is not duplication — those are distinct axes of the problem. The same check inside one trust domain (app layer + DB layer, both trusted) is. Always ask: does this axis exist in the problem, or only in my solution?
  • No speculative abstraction. Solve the case in front of you, not a hypothetical future. Introduce an abstraction at the second real instance, not the first (Rule of Three). A struct/trait/enum/AppSpec at n=1 that is longer than what it replaces is a defect. YAGNI.
  • No ceremony. No struct where a field works; no trait where a function works; no error enum where anyhow at the binary edge works; no test that exists only to exercise a helper you added to enable that test; no config knob nothing sets; no job/feature wired for an axis that doesn't exist yet.
  • Core logic stays in one cohesive home. Never leak or scatter a concern across helpers, wrappers, and util modules. Don't reimplement in a test harness, CLI, or example what a Score (or any owning module) already does — compose it.
  • Comments: WHY only. A non-obvious constraint, an invariant, a workaround. Never narrate WHAT the code does. If deleting a comment loses nothing, it was bloat. No multi-paragraph docstrings.
  • This governs everything an agent emits — code, tests, docs, commit messages, and chat replies alike. Concise, information-dense, unbloated. Here brevity is correctness, not style.

Formal frame: minimize accidental complexity (Brooks); the floor is the problem's intrinsic (Kolmogorov) complexity. Over-duplication and over-abstraction are the same error — a description longer than the problem warrants.

For non-trivial implementation or code review, load the essential-code-review skill and run its simplification pass before considering the work complete. For substantial prose or documentation changes, load the humanizer skill and audit the final text before completion.

What Harmony is

Orchestration for decentralized micro datacenters — small clusters in homes, offices, and community spaces instead of hyperscalers. It replaces the Terraform/Ansible/Helm "YAML mud pit" (config validated at runtime, fragmented across tools, failing at 3 AM) with one Rust codebase where the compiler catches infrastructure misconfigurations before anything deploys. Infrastructure-as-real-code, not a wrapper around existing tools.

Score-Topology-Interpret — the core pattern

  • Score — declarative desired state; a struct generic over T: Topology. Serializable, cloneable, idempotent.
  • Topology — what an environment can do; exposes capabilities as traits (DnsServer, K8sclient, HelmCommand, LoadBalancer, …). E.g. K8sAnywhereTopology (local k3d or any cluster), HAClusterTopology (bare-metal HA).
  • Interpret — translates a Score into operations against a Topology's capabilities; returns an Outcome (SUCCESS, NOOP, FAILURE, RUNNING, QUEUED, BLOCKED).

Compile-time safety through trait bounds:

impl<T: Topology + DnsServer + DhcpServer> Score<T> for DnsScore { ... }

A Topology missing a required capability is a compile error, not a runtime surprise. Higher-order topologies compose via blanket impls: if T: PostgreSQL then FailoverTopology<T>: PostgreSQL automatically — zero boilerplate.

Capability & Score rules (non-negotiable)

  • Capabilities are industry concepts, not tools. DnsServer, not OpnsenseDns; SecretVault, not OpenbaoStore. Test: if you can swap the backend without rewriting any Score, the boundary is right. Exception: when the developer codes to the implementation — PostgreSQL, not Database, because you write PG-specific SQL.
  • Scores encapsulate operational complexity. Init sequences, retries, distro quirks live inside the Score. A high-level example is ~15 lines, not ~400 of imperative orchestration.
  • Idempotent and order-independent. Twice = once. Declare needs via trait bounds; never assume another Score ran first.

See docs/guides/writing-a-score.md.

Deploy architecture (ADR-023, non-negotiable)

  • Deploy with Scores, not handrolled manifests. No k8s_openapi::api::* structs outside Score::interpret. CLIs, examples, and test harnesses compose *Score types. Building a Deployment/Service/ConfigMap in a harness is the mud pit in Rust clothing — reach for the Score, or write the missing one.
  • E2E uses the same Scores as prod. Only the Topology changes. If e2e needs something prod doesn't, add a knob to the Score — don't fork the manifest.
  • One Score per deployable component; compose upward (MyAppScore pulls in PostgresScore, …). No monolithic "deploy everything" Scores.
  • Deploy returns only after smoke-test success. helm install && hope is the anti-pattern Harmony exists to kill. Convergence errors read like rustc, not "exit 1 from helm". (Contract shape open; principle locked.)
  • Deploy logic lives in a *-deploy crate depending on both harmony and the runtime crate. Runtime binaries stay free of the harmony dep. One deploy crate per app area.
  • Topologies are compile-time, selected at runtime. A deploy binary lists its topologies; a new backend is a rebuild. No Box<dyn Topology> loaders.
  • Extend Scores with companions, not API changes. New cross-cutting capabilities (dry-run, observability, smoke-test) wrap a Score; the base Score/Interpret API stays small.
  • thiserror in libraries; anyhow only at binary glue (main.rs-level, where the error is just printed).

See docs/adr/023-deploy-architecture.md.

Architecture (hexagonal, ADR-002)

harmony/src/domain/ (score.rs, topology/, interpret/, inventory/, maestro/) is the framework core; infra/ (opnsense, brocade, kube, sqlx) holds driven adapters; modules/ (k8s, postgresql, okd, helm, monitoring, kvm, network, …) holds concrete deployment modules. The domain stays isolated from adapters.

Key crates

Crate Purpose
harmony Core framework: domain, infra adapters, deployment modules
harmony_cli CLI + optional TUI (--features tui)
harmony_config Unified config+secret management (env → SQLite → OpenBao → prompt)
harmony_secret / _derive Secret backends (LocalFile, OpenBao, Infisical)
harmony_execution Execution engine
harmony_agent / harmony_inventory_agent Persistent agent framework (NATS JetStream mesh), hardware discovery
harmony_assets Asset management (URLs, local cache, S3)
harmony_composer Infrastructure composition tool
harmony-k8s Kubernetes utilities
k3d Local K3D cluster management
brocade Brocade switch integration
opnsense-codegen / opnsense-api Typed OPNsense client (XML models → Rust); exist because OPNsense ships no typed API. Support crates, not core.

Key ADRs (docs/adr/)

001 Rust · 002 hexagonal · 003 capabilities at domain level (no vendor lock-in) · 005 Rust DSL over YAML · 007 k3d default runtime · 009 Helm inflated to vanilla k8s YAML · 015 higher-order topologies via blanket impls · 016 agent mesh on NATS JetStream · 020 unified config+secret · 023 deploy architecture.

Build & test

./build/check.sh                                   # full CI: check + fmt + clippy + test
cargo check --all-targets --all-features --keep-going
cargo fmt --check
cargo clippy
cargo test                                         # or: cargo test -p <crate> <test>
cargo run -p <example_crate>
mdbook build

Conventions

Rust edition 2024, resolver v2. Conventional commits (feat:/fix:/chore:/docs:/refactor:). Small, single-purpose PRs (~200 lines, excluding generated code). License AGPL v3. Lean on the type system — compile-time guarantees over runtime checks; abstractions domain-level, not provider-specific.

Dos and Don't

  • Never tag "Co-Authored-By" with an agent name in commit messages.