Files
harmony/ROADMAP/iot_platform/context_conversation.md
Jean-Gabriel Gill-Couture 65ef540b97
Some checks are pending
Run Check Script / check (pull_request) Waiting to run
feat: scaffold IoT walking skeleton — podman module, operator, and agent
- Add PodmanV0Score/IotScore (adjacent-tagged serde) and PodmanV0Interpret stub
- Gate virt behind kvm feature and podman-api behind podman feature
- Scaffold iot-operator-v0 (kube-rs operator stub) and iot-agent-v0 (NATS KV watch)
- Add PodmanV0 to InterpretName enum
- Fix aarch64 cross-compilation by making kvm/podman optional features
- Align async-nats across workspace, add workspace deps for tracing/toml/tracing-subscriber
- Remove unused deps (serde_yaml from agent, schemars from operator)
- Add Send+Sync to CredentialSource, fix &PathBuf → &Path, remove dead_code allow
- Update 5 KVM example Cargo.tomls with explicit features = ["kvm"]
2026-04-17 20:15:10 -04:00

246 lines
18 KiB
Markdown

# Conversation Summary: IoT Platform Architecture
**For agents implementing this system:** This document captures the full decision trail that led to the final `iot-platform-v0-walking-skeleton.md` plan. Understanding *why* decisions were made is as important as understanding *what* was decided — especially for judgment calls during implementation where the plan doesn't spell something out explicitly.
---
## The original ask
Sylvain (CTO of NationTech) wanted to build an IoT platform with these specific requirements:
- SSO via Zitadel
- Secrets via OpenBao
- Per-device identity, devices belong to groups
- Full CI/CD integration
- A "mini-kubelet" with NATS as the storage backend — each device is a node, reads its own resources, reconciles in a loop, reports status back to NATS KV
- Central operator with CRDs for deployments, device groups, devices — operator writes to NATS on CRD change and reports deployment status back
- CI/CD pipeline publishes hydrated Helm charts to Harbor registry; ArgoCD applies them; operator picks them up and pushes to NATS
- Devices run containers declared via Harmony Scores
- Strong consistency assumed free (NATS provides it)
- Zitadel/OpenBao integration already ~99% done in Harmony
Original constraints: simplicity key throughout, production-ready, don't go down rabbit holes, deadline and cost discipline.
---
## Phase 1: Initial architecture research and design
Claude researched NATS, Zitadel, and OpenBao integration patterns in depth using primary sources. Key findings that shaped the design:
**NATS auth callout with bearer-token JWTs is the right identity primitive.** Devices don't hold NATS signing material. An auth callout service mints a scoped per-connection user JWT with `bearer_token: true` (skips nonce signing, per the `nats-io/jwt` source) after verifying a Zitadel token the device presents at CONNECT time. This is cleaner than distributing long-lived NATS NKeys to devices. ADR-26 is the authoritative spec.
**Zitadel JWT Profile grant is the device auth path.** Service accounts with public keys registered in Zitadel, devices sign self-JWTs with their private key, exchange for access tokens. Zitadel Discussion #8406 documents exactly this pattern with working Go code for an IoT/TPM case. Key gotcha: external-OpenSSL keys need `ParsePKCS8PrivateKey`, not PKCS1.
**OpenBao's JWT auth method + templated policies** with `token_policies_template_claims` (PR #618) lets one policy resolve per-device based on JWT claims. One policy for N devices instead of N policies.
The three systems compose cleanly:
- Zitadel = identity (who or what)
- OpenBao = policy + secrets (what they can access)
- NATS = transport + subject-level authorization (where messages go)
A ~900-line architecture document captured this with primary-source citations. It remains the reference for implementation detail on auth flows.
---
## Phase 2: Planning iterations and scope calibration
Claude produced three planning documents in sequence, each refining the approach:
1. **Issue breakdown (22 issues, 9 tracks)** — human-executable parallel tracks
2. **Autonomous agent harness plan** — contract-first with phase gates, for agent-driven execution
3. **Walking skeleton plan** — thin end-to-end thread shipping Tuesday
**Sylvain's critical intervention:** when Claude produced the parallel-tracks plan targeting day 14, Sylvain pushed back. The real approach should be **walking skeleton** (Cockburn) / **tracer bullet** — ship a naive end-to-end loop first, let architecture emerge from running code, harden from there. This reduces the risk of reaching day 14 with nothing integrated.
Claude acknowledged overreach: the three documents shouldn't all exist. Walking skeleton supersedes the other two. The first two became reference material only.
---
## Phase 3: The "start from scratch objectively" challenge
Sylvain asked Claude to reconsider the architecture from scratch, with access to NationTech's full resource context (k8s clusters, ArgoCD, Harbor, Zitadel, OpenBao, Harmony ownership) but without emotional attachment to the previous design.
**Claude's initial recommendation:** k3s on each Pi + ArgoCD + external-secrets-operator. Boring, CNCF-standard, maintained by the ecosystem rather than by NationTech. Argued the custom NATS-mini-kubelet approach was "building a platform when you could buy one."
**Sylvain's decisive pushback** reframed this correctly. Claude had under-weighted several things:
1. **End-customer engineers are mechanical/electrical/chemical, not Kubernetes-literate.** They debug with `systemctl`, `journalctl`, `ps`. A k3s device forces them to learn kubectl/CRDs/CNI — a real productivity tax on a team that shouldn't have to pay it. A single Rust binary + podman is inspectable with tools they already know.
2. **The platform bet is strategic, not technical.** NationTech's positioning as "no vendor lock-in, decentralized, open-source enterprise cloud" gains credibility from having a product (Harmony), not from being "extraordinary plumbers for off-the-shelf CNCF." Building a custom platform on this bet is how you become a platform company instead of an integration shop.
3. **NationTech is its own largest customer.** Multiple OKD clusters already need coordination; manually connecting to each to make deployments is a major operational pain that hinders growth. The same architecture (agent reconciling against NATS KV) eventually manages podman on Pis, `kubectl apply` on OKD clusters, and VM-level operations. One abstraction, three instantiations.
4. **NATS is architecturally superior for federation.** ArgoCD doesn't naturally federate — it manages clusters *from one place*. A NATS supercluster with strict ordering across regions supports "operator in multiple clusters, ArgoCD instances all over, deployments coming from everywhere." For the long-term decentralized control plane, NATS is the correct substrate.
5. **Rancher code quality (k3s provenance) is real data, not nostalgia.** Sylvain has direct experience; Claude had over-indexed on CNCF graduation as a quality proxy.
6. **Harmony daemon-mode `Interpret` is already solved.** Claude had repeatedly flagged "does `Score::interpret()` work in a loop?" as a major unknown. Reality: `s.clone().interpret().await` is exactly the TUI's daemon pattern, and `harmony_agent` runs this in production for distributed CNPG PostgreSQL management. The concern was unfounded.
**Result:** Claude updated. The custom NATS-based platform is correct for this context. The k3s alternative genuinely doesn't fit. The walking skeleton plan stands.
Remaining real risks (acknowledged, not architecture-invalidating):
- Platform scope creep → walking skeleton discipline
- Bus factor → normal Harmony collaboration patterns with Jean-Gabriel
- Customers #2-N for the federation story → business question, not technical
---
## Phase 4: Strategic alignment and scope clarifications
Sylvain provided specific clarifications that shaped the final plan:
**Balena was considered and rejected.** It's the closest viable alternative, open-source, but requires custom OS (balenaOS — lock-in of a different kind), lacks native SSO + secrets integration, and positions NationTech as a Balena integrator rather than a platform company. AGPL Harmony vs. Balena has similar license profiles; NationTech can deliver honest no-lock-in positioning.
**Three-way relationship structure:** NationTech → Partner (custom software shop, engineering-quality-focused, does coaching) → End-customer (whose field-deployed Pi 5 devices run the partner's application). Tuesday's demo is for the Partner. Production deployment may involve direct end-customer contact later.
**Partner relationship is healthy and collaborative.** They want NationTech to succeed. Demo failure modes tolerable. Platform partnership is an active topic between the teams — they explicitly value having a platform partner they trust for landing their own customers.
**Other potential customers exist but aren't paying.** NationTech is managing their OKD clusters via other means for now. They can wait. NationTech's own OKD coordination pain is the largest driver.
---
## Phase 5: Technical nitty-gritty corrections
Sylvain corrected several technical details Claude had gotten wrong or overdesigned:
**No `harmony-podman-score` new crate.** It's a new module in `harmony/src/modules/podman/` following existing Harmony module conventions. Corrected in the plan.
**Use `podman-api` Rust crate, not shell-out.** Strongly typed API preferred. Requires `systemctl --user enable --now podman.socket` on the device. `podlet` crate worth evaluating later when Quadlet comes back in scope (v0.1+).
**Graceful shutdown is just `podman stop` with 5-min timeout then SIGKILL.** Not kubelet-style pod termination. Claude had overcomplicated this.
**Score envelope was overdesigned — drop it.** The "ScoreEnvelope with format/encoding/content_hash/data" pattern reminded Sylvain of SOAP. Use adjacently-tagged serde enum instead: `#[serde(tag = "type", content = "data")]`. Rust type name is the discriminator. Agent deserializes directly into the typed Score. No double-deserialization, no opaque bytes, no format version strings.
**Change detection via string comparison, not content hash.** Comparing serialized Score strings is cheap enough at this scale (a couple times per minute). Removes hashing-algorithm risk. More deterministic.
**Agent config is flat TOML for v0.** Long-term target is zero-config — device boots (PXE if budget allows), has a Zitadel URL + initial token, fetches real config from OpenBao, connects to NATS. OpenBao as source of truth for NATS credentials. v0 uses simple shared NATS credentials directly in TOML.
**OpenBao outage must not break NATS reconnect if token is still valid.** The auth callout in v0.2 should validate Zitadel tokens against JWKS directly; OpenBao lookup for group permissions should be cached in the callout. Availability-favoring design — reboot isn't more of a security event than a passing minute, and NATS rejects on actual token expiry anyway. No degradation of real security posture.
---
## Phase 6: aarch64 discovery
Late in the conversation, the single most important technical issue surfaced. Claude had been flagging "does Harmony `Interpret` work in daemon mode?" as the biggest Friday risk. Sylvain corrected that this was a non-issue.
**The real issue:** Harmony doesn't currently compile on aarch64. When `harmony_agent` was cross-compiled for ARM64, an upstream dependency had to be pulled out. Sylvain's 80% confidence: single sub-dependency used by only a few modules, feature-gatable, those modules become unavailable on ARM (acceptable — device doesn't need every Harmony feature).
**This replaces the Friday-evening "§6 decision" in the plan.** It becomes the first-hour investigation. Fallback paths exist: build agent against minimum aarch64-clean Harmony subset, or (worst case) pure Rust without Harmony Score traits for v0, adopt them in v0.1.
---
## Phase 7: Final plan adjustments
The walking skeleton plan was updated with all agreed decisions in a single coherent revision. Key decisions baked into the final doc:
**Section-by-section:**
- **§1 Strategic framing** now explicitly names NationTech as largest customer and describes the decentralized cloud vision (heating buildings, sovereign, etc.) so collaborators reading the plan understand this is long-term investment, not a side project.
- **§5.4 agent scope:** kubelet compatibility explicitly NOT a goal, kubelet architecture as north star only, v0 absolutely minimal. One paragraph, no enforced limits — discipline through inherent minimalism.
- **§5.5 Score message format:** adjacently-tagged serde enum, no envelope, no content hash, string comparison for change detection.
- **§6.7 agent config:** flat TOML for v0. v0.2 narrows to Zitadel-token-bootstrap model.
- **§7 aarch64 investigation** is the Friday-evening critical path. Fallbacks documented.
- **§8 Hour 1-2 field readiness:** heavy power-cycle testing, network-out-during-boot, agent crash loop. SD card wear / thermal / PoE explicitly ruled out per partner conversation.
- **Agent task cards:** A1 uses new Score format. A2 targets `harmony/src/modules/rpi/`. A3 commits to `podman-api` crate. Graceful shutdown simplified.
- **§12 v0.2 roadmap** includes availability-favoring auth callout design (cached OpenBao permissions, NATS handles token expiry).
- **§13 partner conversation:** technical strategy only; "others in your network" question dropped per Sylvain ("overstepping into sales, not your concern").
**Explicitly removed:**
- OKD-as-device future spike (kept in strategic framing only, not execution)
- Three-level Jean-Gabriel review process (normal Harmony collaboration applies)
- ScoreEnvelope wrapping
- Content hash in Score messages
- `iot-contracts` crate in v0 (extract v0.1)
- Thesis document Sunday dispatch (moved to v0.1 Week 2)
---
## Key principles for implementing agents
Drawing these out as they're load-bearing for judgment calls:
1. **The walking skeleton is the plan. Ship Tuesday with something crude but working.** Not production-ready, not complete. Working end-to-end thread from git push to container running on Pi.
2. **Inherent discipline over enforced limits.** The plan doesn't have line-count budgets or anti-scope lists because Sylvain argued (correctly) that walking-skeleton discipline makes them redundant. If you find yourself wanting to add PLEG event streams, per-workload worker pools, or housekeeping sweeps to v0 — don't. Periodic relist is enough.
3. **Architectural boundaries (§6) must survive v0 even under deadline pressure.** Score enum polymorphic from day one. Credentials behind a trait. Topology generalizable. CRD spec forward-compatible. NATS subject grammar matches long-term. These cost little now and save big later. Don't take shortcuts here to save 20 minutes.
4. **Scope cuts (§4) are real, not aspirational.** Zitadel/OpenBao deferred to v0.2. One device for Tuesday. No groups. No rollout state machine. No API. No TUI. No observability beyond journalctl. Fighting these cuts is the plan's biggest risk.
5. **Availability favored over strict security posture.** The auth callout caches OpenBao lookups. Token expiry is the authoritative revocation mechanism, not real-time policy lookup. A disconnected OpenBao doesn't brick the fleet.
6. **The `podman-api` crate is the happy path.** Shell-out to `podman` is fallback-only. Strong typing wins when available.
7. **Sylvain owns the critical code himself.** Agent A1 (operator), A2 (Pi provisioning), A3 (installer), A4 (demo script) are agent-dispatched. The agent binary itself and the `PodmanV0Score` implementation are Sylvain's work. The auth callout (v0.2) will also be human-written. Don't propose that agents take over these pieces.
8. **The partner relationship is strategic.** Tuesday demo conversation is half the Tuesday deliverable. Framing the v0.1/v0.2/v0.3 roadmap to them matters as much as the running code.
9. **End-customer debuggability is a UX constraint.** Mechanical/electrical/chemical engineers will touch these devices. `systemctl status iot-agent` must tell them what's happening. `journalctl -u iot-agent` must be parseable by humans. Error messages must be understandable without Kubernetes knowledge.
10. **NATS is the long-term architectural commitment.** Everything on NATS — not as a queue, as a coordination fabric. The "decentralized cluster management" future depends on this choice. Implementation decisions that weaken this (e.g., "let's just put a database in the middle") should be pushed back on.
---
## What failed or went wrong in the planning process
Noted for meta-awareness — avoid repeating:
- **Claude overproduced.** Three planning documents when two would do. Under deadline pressure, planning documents are distractions from execution. Sylvain eventually said this directly.
- **Claude under-weighted end-customer UX.** Initial k3s recommendation treated "Kubernetes is easy" as universal when it's only easy for people who already know Kubernetes.
- **Claude under-weighted strategic positioning.** Platform-building vs. integration-consulting is a business choice; Claude treated it as purely technical.
- **Claude repeatedly flagged the Harmony daemon-mode concern** despite it being already solved. A better first question would have been "does this work today?" rather than "what if this doesn't work?"
- **Claude's initial Zitadel/OpenBao integration estimate was too large** because Claude didn't fully internalize "integration is 99% done in Harmony." The remaining work is wiring, not implementing.
- **Claude started with the ScoreEnvelope pattern** before understanding Harmony's native serde patterns. The "SOAP" reaction was deserved.
---
## What's in the final plan
The final document `iot-platform-v0-walking-skeleton.md` (~700 lines) contains:
- Strategic framing (§1)
- Walking skeleton vs. alternatives comparison (§2)
- Tuesday demo definition (§3)
- Scope cuts with milestones (§4)
- End-to-end architecture (§5)
- Architecture boundaries to preserve (§6)
- Friday aarch64 investigation and fallbacks (§7)
- Hour-by-hour Friday-Tuesday plan (§8)
- Four agent task cards (§9)
- Anti-patterns prevented (§10)
- Failure-mode decision tree (§11)
- Post-Tuesday roadmap v0.1→v0.4+ (§12)
- Partner conversation structure for Tuesday (§13)
Companion documents for deep-dive reference:
- `iot-platform-architecture.md` — full architecture with primary-source citations, useful for v0.2+ when auth is implemented
---
## What agents should do when uncertain
The plan cannot anticipate everything. When an agent hits an ambiguity, the decision hierarchy is:
1. **Does this preserve the end-to-end thread for Tuesday?** If yes, proceed. If it breaks the thread, stop and escalate.
2. **Does this preserve architectural boundaries §6?** If unsure, favor the boundary.
3. **Does this add scope beyond §4's in-scope list?** If yes, don't do it, regardless of how easy it seems.
4. **Is this security-critical?** If yes, don't add new code — flag for human review. Especially relevant for v0.2 auth callout work.
5. **Would this be more elegant but take an extra hour?** Don't do it. Ship Tuesday.
6. **Is the end-customer engineer's debuggability harmed by this choice?** If yes, don't do it.
7. **Is this on the path to the OKD-cluster-as-device future?** Don't optimize for this in v0. The abstractions are correct; don't over-invest.
The walking skeleton's entire value is shipping Tuesday. Every decision that serves that goal is correct. Every decision that defers it (no matter how well-intentioned) is wrong.