All checks were successful
Run Check Script / check (pull_request) Successful in 2m20s
Rewrite AGENTS.md to open with a blunt, non-negotiable engineering bar: match the implementation to the real complexity of the problem, DRY in the purest sense (one authoritative representation; the trust-boundary discriminator for what is and isn't duplication), no speculative abstraction (Rule of Three / YAGNI), no ceremony, core logic in one cohesive home (no leaking into external modules), WHY-only comments. Applies to all agent output — code, tests, docs, commits, replies. Reference sections compressed; 171 -> fewer lines, strictly more pointed.
95 lines
8.1 KiB
Markdown
95 lines
8.1 KiB
Markdown
# 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.
|
|
|
|
## 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:
|
|
```rust
|
|
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
|
|
|
|
```bash
|
|
./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.
|