myxce/.kiro/specs/admin-config-tool/brief.md

586 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Brief: admin-config-tool
## Problem
XCE operators have no tool to build and deploy cluster configuration. Currently:
- **Config authoring**: YAML config files (`configs/hosts.yaml`, `queues.yaml`, `resources.yaml`, `users.yaml`) exist as documentation examples with JSON schemas for validation (`configs/schema/*.schema.json`), but **no tool compiles them** into the `ClusterConfig` Cap'n Proto binary that the controller expects.
- **Config deployment**: `OP_upgradeConfig` exists on the controller side (`config_repl.c:1536` — receives binary blob, verifies SHA-256, saves, pushes to standbys, awaits majority ACK, signals restart), but **no CLI sends it**. Today: manual scp + restart.
End users use `xcecli` (Go/Cobra → REST → apiserver → CGO → controller) for non-admin operations. There is no admin-facing tool.
**Who**: XCE cluster operators. **Pain**: manual config management, error-prone YAML→binary translation, no primary auto-discovery.
## Current State
| What | Where | Status |
|------|-------|--------|
| `ClusterConfig` capnp struct | `src/schema/controller.capnp:710-732` | 21 fields, wire-ready |
| YAML config examples | `configs/{hosts,queues,resources,users}.yaml` | Human-authored, NOT loaded by any code |
| JSON schemas | `configs/schema/*.schema.json` | JSON Schema 2020-12 |
| `OP_upgradeConfig` handler | `config_repl.c:1536` `do_upgrade_config_with_bytes()` | Receives binary, verifies, pushes, awaits majority |
| `UpgradeConfigRequest/Reply` | `controller.capnp:792-797` | Defined in schema |
| libxce upgrade API | `request.c:1046` has extractors only | No send function |
| Controller wire protocol | TCP+TLS, capnp-encoded `Request`/`Reply`, same CA as controller-to-controller | Exists, used by all C daemons |
**Key gap**: YAML → ClusterConfig binary tool does not exist. Upload CLI does not exist.
## Desired Outcome
Single `xceadmin` Python CLI that operators use for cluster configuration management:
```
# Build: auto-discover YAML files + schemas from standard directory layout
xceadmin config build configs/ # default output: cluster.bin
xceadmin config build configs/ -o cluster.bin # explicit output
xceadmin config build configs/ -o - # stdout (for piping)
# Push: zero-config dev (same as xce_test), single --controller for production
xceadmin config push cluster.bin # default: localhost:9991
xceadmin config push --controller ctrl-1:9991 cluster.bin # production: any one controller
```
**Standard config directory layout** (auto-discovered):
```
configs/
├── hosts.yaml # optional — missing = empty hosts section
├── queues.yaml # optional
├── resources.yaml # optional
├── users.yaml # optional
└── schema/ # required for validation
├── hosts.schema.json
├── queues.schema.json
├── resources.schema.json
└── users.schema.json
```
- **config build**: Scan directory → validate present YAML files against corresponding JSON schemas → map to `ClusterConfig` capnp struct → serialize to `cluster.bin` → output SHA-256. Missing files = empty section (not error). Schema directory must exist.
- **config push**: Read `cluster.bin` → connect to controller (default `localhost:9991`) → probe current `configGeneration` → if binary's generation ≤ current, increment to `current + 1` and re-encode (controller rejects non-increasing generations as no-op) → controller reply includes peer list + primary address → follow redirect if needed → send capnp `Request{OP_upgradeConfig, UpgradeConfigRequest{sha256, bytes}}` → receive `Reply` → display `{versionTag, ackedCount, error}`. Admin can force a specific generation with `--generation N`.
- **Shared package**: `xcecommon` Python package with capnp schema handling, wire protocol, and config models — reusable by future Python-based tools
## Approach
**Chosen: Python CLI with single dependency on `controller.capnp`.**
```
src/schema/controller.capnp ← single source of truth
│
├──▶ c-capnproto → libxce → controller (C daemon, existing)
│
└──▶ pycapnp → xceadmin (Python, new)
│
├── config build: YAML → ClusterConfig → bytes → cluster.bin
└── config push: Request{OP_upgradeConfig, ...} → TLS → Reply
```
- **Language**: Python. YAML parsing trivial (`yaml`), JSON Schema validation (`jsonschema`), capnp encoding (`pycapnp`), CLI (`click` or `argparse`).
- **Protocol**: TCP+TLS directly to controller (NOT through apiserver). Zero-config dev experience — default `localhost:9991`, same as xce_test. Single `--controller` flag for production override.
- **Dependencies**: `controller.capnp` schema file only. Zero CGO, zero libxce. Pure Python, open-source ready.
- **Auto primary detection**: Connect to given controller → controller reply carries peer list + primary address (standby redirect, already in reply schema) → follow redirect to primary → send request. One controller is enough to bootstrap full cluster discovery. Same mechanism xce_test uses
- **No CGO**: Wire protocol is TLS connect → send one capnp `Request` → receive one capnp `Reply` → exit. ~150 lines of Python. No iomgr event loop, no vtask thread pool, no persistent connections — those are for daemons, not one-shot CLI tools.
### Architecture Decisions (from 2026-07-06 discovery session)
| Decision | Rationale |
|----------|-----------|
| **Python** over Go | Future open-source release; shared capnp infra with planned Python xcecli rewrite; `pip install` no compilation |
| **Pure Python wire** over CGO+libxce | Open-source friendly; no C compilation; wire protocol is one-shot send/receive (~150 lines) |
| **Direct controller** over apiserver | Admin path needs no RBAC middleware; apiserver may be restarting during config upgrade; controller SSL is same CA |
| **Single `controller.capnp` dependency** | Schema IS the protocol contract; versioned; no extra API surface |
| **Separate `xceadmin` binary** over extending xcecli | `xcecli` = user tool (HTTP → apiserver); `xceadmin` = operator tool (TLS → controller); different audiences, different protocols |
| **Auto primary detection** | Operator shouldn't need to know which controller is primary; connect to any one, discover peers + primary from reply |
| **Config build offline** | YAML→binary is pure data transform; no controller connection needed; cluster.bin can be pre-built, reviewed, version-controlled |
### xcecli vs xceadmin
| | xcecli | xceadmin |
|---|--------|----------|
| **Audience** | End users | Operators |
| **Protocol** | HTTP/HTTPS (REST, JSON) | TCP+TLS (capnp binary) |
| **Target** | apiserver | controller (primary) |
| **Auth** | JWT token (login → apiserver) | None — same zero-config TLS as xce_test |
| **Auth middleware** | Full: WhitelistURL→WhitelistIP→RateLimit→JWT→CORS→RBAC | None — direct TCP+TLS, same trust model as controller peers |
| **Use cases** | Submit jobs, list queues, check status | Push config, cluster admin, force snapshot |
| **Language** | Go/Cobra (current), Python (future) | Python |
## Project Decomposition (from 2026-07-06 discovery)
This discovery session (Path E — mixed decomposition) identified 3 workstreams:
| # | Workstream | Type | Language | Dependencies |
|---|-----------|------|----------|--------------|
| 1 | **admin-config-tool** | NEW spec | Python | `controller.capnp` only |
| 2 | **config-upgrade-snapshot** | EXISTING spec extension | C | entity-bearing-snapshots (blocked upstream) |
| 3 | **perf-tuning-extraction** | DIRECT work | doc | None |
**Dependency flow:**
```
admin-config-tool (standalone, ships first)
│
│ config push triggers controller-side path
▼
config-upgrade-snapshot (controller: replicate → snapshot → restart → activate)
│
│ blocked on
▼
entity-bearing-snapshots (upstream blocker, tracked in roadmap)
```
## Scope
- **In**:
- `xceadmin config build`: YAML parsing, JSON Schema validation, capnp ClusterConfig serialization, cluster.bin output with SHA-256. `configGeneration` defaults to 0 (offline-safe).
- `xceadmin config push`: single controller address (default `localhost:9991`), probe current configGeneration from controller, auto-increment if needed, auto primary detection via peer-list-from-reply, TLS+capnp wire protocol, capnp UpgradeConfigRequest/Reply, result display. `--generation N` flag for explicit override.
- `xceadmin config diff`: (nice-to-have) decode two cluster.bin files, human-readable field-by-field diff. Pre-push safety: "what will this config change actually do?"
- `xceadmin config push --dry-run`: (nice-to-have) validate controller will accept the config without committing it
- `xcecommon` shared Python package: capnp schema loading, wire protocol (TLS+capnp framing), config model types
- Build system integration: pip-based install (editable for dev, pinned for release), nix dev shell packages, xce-build.sh component target
- Skills integration (see Skill Integration section below):
- `xce-build`: add `xceadmin` component target (Python package build + test)
- `xce-deploy-ha`: replace manual `ha/data/cluster.bin` prerequisite with `xceadmin config build` step; add `--build-config` flag for on-the-fly YAML→binary generation
- `xce-test-ha`: (future) config push validation round in soak loop — deferred until `config-upgrade-snapshot` lands on controller side
- Validation: reject configs using reserved name `"lost_and_found"` for queue/host (defense-in-depth — controller enforces at runtime via FATAL, tool prevents at authoring time)
- **Out**:
- Controller-side config replication/snapshot/restart logic (owned by `config-upgrade-snapshot`)
- Entity-bearing snapshot payload (owned by `entity-bearing-snapshots`, upstream blocker)
- Performance tuning parameter extraction (separate direct work item)
- xcecli Python rewrite (future, separate spec)
- xce-test-ha config push validation round (deferred until `config-upgrade-snapshot` lands — config push to a controller that can't replicate/snapshot is a no-op from the soak's perspective)
## Skill Integration
Three existing skills touch `cluster.bin` — the binary config blob that `xceadmin config build` produces. Currently `cluster.bin` is a hand-crafted opaque artifact with no visible provenance. `xceadmin` replaces that with a reproducible build step.
### xce-build — add `xceadmin` component
New component target for the Python package:
```
/xce-build xceadmin # pip install -e . (editable, for dev)
/xce-build xceadmin release # pip install . (pinned, for production)
```
Build path differs from C components: Python, not C. Runs inside nix dev shell (pycapnp needs capnproto C++ runtime from nix). Build output: `xceadmin` CLI entry point + `xcecommon` package importable.
This is the **blocking** integration — other skills can't use xceadmin until this lands.
### xce-deploy-ha — generate cluster.bin from YAML
**Current flow:**
```
ha/data/cluster.bin (pre-built, manual, opaque) → deploy-ha.sh → controller/ctrl-1/conf/
```
**New flow:**
```
configs/*.yaml → xceadmin config build → cluster.bin → deploy-ha.sh → ...
```
Changes to the skill:
| What | Before | After |
|------|--------|-------|
| Prerequisites | `cluster.bin` must exist under `ha/data/` | `cluster.bin` built from `configs/*.yaml` via `xceadmin config build` |
| Config build step | None (manual) | `xceadmin config build configs/ -o ha/data/cluster.bin` (auto, or skip if `--use-existing-config`) |
| `--build-config` flag | N/A | New flag: force rebuild cluster.bin from YAML before deploy |
`deploy-ha.sh` gains an optional precondition: if `cluster.bin` missing or `--build-config` passed, run `xceadmin config build`. Otherwise use existing `cluster.bin` as-is (backward compatible — operators can still hand-craft for custom configs).
### xce-test-ha — config push validation (future)
Deferred until `config-upgrade-snapshot` lands on controller side. When ready:
- **New soak round type**: kill primary, push new config via `xceadmin config push`, verify controller accepted + standbys received
- **Coverage**: exercises `OP_upgradeConfig` → replication → majority ACK path under failover
- **Verdict**: config push failure = FAIL (config consistency is a data-safety property)
Not scoped into admin-config-tool tasks — belongs to `config-upgrade-snapshot` spec. Listed here for cross-spec visibility.
### Integration dependency order
```
xce-build (+xceadmin) ← THIS SPEC, blocking
│
▼
xce-deploy-ha ← THIS SPEC, immediate after xce-build
│
▼
xce-test-ha ← config-upgrade-snapshot spec (future)
(config push round) blocked on entity-bearing-snapshots
```
## Harness Enhancement
The `xceadmin` tool is Python — first Python component in a C/Go project. This requires harness changes across the development toolchain.
### xce-build.sh — new `xceadmin` component
Add a new case to `src/tools/xce-build.sh`:
```bash
xceadmin)
echo "=== Building xceadmin Python package ==="
pip install -e . --quiet
echo "=== Running xceadmin tests ==="
python -m pytest tests/ -v
;;
```
Python path — no meson setup. `pip install -e .` for dev (editable), `pip install .` for release. Tests via `pytest`, not meson test. Runs inside nix dev shell (capnproto C++ runtime needed for pycapnp compile). The `release` profile uses `pip install .` without `-e`.
### xce-build SKILL.md — add component + usage note
- **Usage line**: `Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin`
- **Profile notes**: `xceadmin` ignores meson profiles — it always uses pip. The profile arg affects C components only; xceadmin builds regardless of profile.
- **Examples**: `/xce-build xceadmin` (editable), `/xce-build xceadmin release` (pinned)
### xce-deploy-ha SKILL.md — config build step
Changes to the skill document:
- **Prerequisites**: replace "TLS certs and a `cluster.bin` must exist under `ha/data/`" with "TLS certs must exist under `ha/data/`. `cluster.bin` is built from `configs/*.yaml` via `xceadmin config build` (or use existing if `--use-existing-config`)."
- **New `--build-config` flag**: forces rebuild of `cluster.bin` from YAML before deploy. Without it, uses existing `cluster.bin` as-is (backward compatible).
- **New step** (between resolve paths and stop running): optional `xceadmin config build configs/ -o ha/data/cluster.bin` if `--build-config` passed or `cluster.bin` missing.
- **Troubleshooting**: add "`pycapnp` import error" → run `/xce-build xceadmin` first.
### settings.local.json — Python permissions
Add Bash permission allowlist entries for xceadmin development:
```json
"Bash(pip install *)",
"Bash(python -m pytest *)",
"Bash(xceadmin config build *)",
"Bash(xceadmin config push *)",
"Bash(ruff format *)",
"Bash(ruff check *)"
```
### PostToolUse hooks — Python formatting + lint
Add a ruff format hook for Python file edits (`settings.json`):
```json
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "[[ \"$FILE_PATH\" == *.py ]] && ruff format --quiet \"$FILE_PATH\" || true",
"timeout": 5
}
]
}
```
Add a ruff check hook for lint feedback:
```json
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "[[ \"$FILE_PATH\" == *.py ]] && ruff check --quiet \"$FILE_PATH\" || true",
"timeout": 5
}
]
}
```
### PreToolUse hook — YAML schema guard (nice-to-have)
Validate config YAML files against JSON schemas before accepting edits. Prevents commits of invalid config:
```json
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "[[ \"$FILE_PATH\" == configs/*.yaml ]] && xceadmin config validate \"$FILE_PATH\" || true",
"timeout": 5
}
]
}
```
This requires `xceadmin config validate` subcommand (nice-to-have: validates a single YAML file against its schema, no binary output). Deferred to a follow-up task after core `config build` lands.
### `/xce-admin` skill (deferred)
Thin skill wrapper around `xceadmin config build/push` for agent-driven workflows. Not needed for spec implementation — CLI is sufficient. Deferred until CLI is stable and operator workflows are validated.
### Harness change summary
| # | What | Where | Priority | Phase |
|---|------|-------|----------|-------|
| 1 | `xceadmin` case in build script | `src/tools/xce-build.sh` | **Blocking** | Implementation |
| 2 | Component list + pip note | `.claude/skills/xce-build/SKILL.md` | **Blocking** | Implementation |
| 3 | `--build-config` + config build step | `.claude/skills/xce-deploy-ha/SKILL.md` | **Blocking** | Implementation |
| 4 | pip/pytest/xceadmin permissions | `.claude/settings.local.json` | **Blocking** | Implementation |
| 5 | ruff format hook on `.py` edits | `.claude/settings.json` | Nice-to-have | Implementation |
| 6 | YAML schema guard on config edits | `.claude/settings.json` | Nice-to-have | Follow-up |
| 7 | `/xce-admin` skill wrapper | `.claude/skills/xce-admin/SKILL.md` | Deferred | Post-spec |
## Boundary Candidates
- **`xceadmin` CLI** — operator-facing commands, argument parsing, output formatting
- **`xcecommon` Python package** — shared capnp schema loading, wire protocol (TLS+capnp), config model types. Reusable by future Python xcecli.
- **`config-build` module** — YAML→ClusterConfig mapping, JSON Schema validation, serialization
- **`config-push` module** — primary auto-detection, TLS+capnp send/receive, result parsing
## Upstream / Downstream
- **Upstream**: `controller.capnp` schema (source of truth for ClusterConfig, Request/Reply, OP codes)
- **Downstream**: Controller `OP_upgradeConfig` handler (`config_repl.c` — already exists)
- **Consumed by**:
- `config-upgrade-snapshot` spec — xceadmin is the trigger; controller-side handles replication/snapshot/restart
- `xce-build` skill — adds `xceadmin` component target (blocking integration)
- `xce-deploy-ha` skill — `xceadmin config build` replaces manual `cluster.bin` prerequisite
- `xce-test-ha` skill — (future) config push validation round, deferred until config-upgrade-snapshot lands
- **Future consumer**: Python xcecli rewrite (imports `xcecommon` for capnp schema types)
## Constraints
- **Python ≥ 3.10** (match system Python, avoid bleeding-edge features)
- **pycapnp** requires capnproto C++ runtime (`libcapnp-dev`) — separate from project's c-capnproto (C bindings). Both capnp runtimes can coexist; binary format is standardized.
- **Zero-config TLS** — same dev experience as xce_test: default `localhost:9991`, no cert flags needed. Controller peer trust model (X.509 CN validation via `validate_host()` — tracked in roadmap hardening).
- **No dependency on a running apiserver** — admin tool connects directly to controller. Config deployment works even when apiserver is down or restarting.
- **Open-source readiness** — pure Python, `pip install` friendly, no compilation step, clear single-schema dependency.
## Nix Environment
`xceadmin` is the first Python component in the XCE project. The nix dev shell currently provides C/C++ tooling only — zero Python packages. All needed Python packages are available in nixpkgs.
### Packages to add
Current `src/nixSupport/xce.nix` `nativeBuildInputs` already includes `capnproto` (line 21) — the C++ runtime that pycapnp wraps. Need to add:
| Package | Nix attr | Purpose | Size |
|---------|----------|---------|------|
| Python 3 | `python3` | Interpreter (≥ 3.10) | ~50MB |
| pycapnp | `python3Packages.pycapnp` | capnp schema → Python types | small |
| PyYAML | `python3Packages.pyyaml` | YAML config parsing | small |
| jsonschema | `python3Packages.jsonschema` | JSON Schema validation | small |
| click | `python3Packages.click` | CLI argument parsing | small |
| pytest | `python3Packages.pytest` | Test framework | small |
| ruff | `python3Packages.ruff` | Python formatter + linter | ~10MB |
All confirmed available in nixpkgs (unstable): pycapnp 2.2.3, pyyaml 6.0.3, jsonschema 4.26.0, click 8.3.1, pytest 9.0.3, ruff 0.15.20.
### Where to add
Two options:
**Option A: Add to `xce.nix`** (recommended). Every dev gets Python toolchain. Simpler — one flake, one shell. Python deps are ~60MB total, negligible vs current C toolchain.
**Option B: Separate `xceadmin.nix`**. Lighter shell for C-only devs. More maintenance — two nix files, two shells, cross-component workflows need manual env switching.
Recommend **Option A** — xceadmin is part of the standard toolchain (like xcecli), not an optional extra. All devs should be able to `xceadmin config build` without switching shells.
### New `nativeBuildInputs` block (addition to xce.nix)
```nix
# Python — for xceadmin (admin config tool)
python3
python3Packages.pycapnp
python3Packages.pyyaml
python3Packages.jsonschema
python3Packages.click
python3Packages.pytest
python3Packages.ruff
```
### pycapnp build note
`pycapnp` wraps the capnproto C++ library at compile time (`pip install` compiles a C extension). Nix's `python3Packages.pycapnp` handles this correctly — it links against nix-provided `capnproto`. No additional `libcapnp-dev` or system package needed. This is one of the advantages of using nix pycapnp over `pip install pycapnp`.
### CI implications
- CI already uses nix dev shell — adding packages to `xce.nix` makes them available in CI automatically
- `config build` golden-file tests need pycapnp for capnp encode (can't mock without schema)
- `config push` mock tests need pycapnp + TLS (Python `ssl` stdlib, no extra dep)
## Testing Strategy
### config build (offline — pure data transform)
**Golden-file tests** (primary). Given a `configs/` directory, verify output matches expected:
```
tests/
├── fixtures/
│ ├── minimal/ # hosts.yaml + queues.yaml only
│ │ ├── configs/
│ │ │ ├── hosts.yaml
│ │ │ ├── queues.yaml
│ │ │ └── schema/
│ │ └── expected.bin # pre-built golden output
│ ├── full/ # all 4 YAML files
│ └── invalid/ # schema violations → expect error
├── test_config_build.py # pytest: build → compare binary, verify SHA-256
└── test_schema_validation.py # pytest: each schema violation case
```
- Framework: `pytest` (standard Python, no deps beyond dev)
- Zero external dependencies — no controller, no network
- Pre-build golden files once; commit to repo
- Schema validation: test each error case (missing required field, wrong type, invalid enum, reserved name `lost_and_found`)
### config push (online — needs controller)
**Option A: Mock controller** (unit/integration). Python TLS server that speaks enough capnp wire protocol to accept `OP_upgradeConfig` and return canned `UpgradeConfigReply`.
**Option B: Real controller** (E2E). Piggyback on existing `/xce-test-ha` cluster. After cluster is up, `xceadmin config push` a known config and verify controller accepts it.
- Option A for dev loop + CI (fast, no cluster setup)
- Option B for acceptance (real SSL, real replication, real controller)
Recommended: Option A for spec tasks. Option B deferred to xce-test-ha integration after config-upgrade-snapshot lands.
### CI Integration
- `config build` tests: run in any Python CI (no C deps needed — pycapnp only for encode, can mock)
- `config push` mock tests: need pycapnp + controller.capnp compiled for Python
- Target: 80%+ coverage (project standard)
## Open Design Questions
Issues identified during 2026-07-07 review — need resolution before or during requirements generation.
### Critical (resolve before requirements)
| # | Question | Why it matters | Candidates |
|---|----------|---------------|------------|
| 1 | **YAML→capnp mapping incomplete** | `ClusterConfig` has 21 fields; 4 YAML files cover ~10. Remaining 11 (clusterName, port, candidates, heartbeat/interval timeouts, jobmgr/workermgr/qmgr/resmgr hash configs, xstrings, configGeneration) have no YAML source | (a) xceadmin fills defaults from controller's `get_default_conf()` values, (b) add a `cluster.yaml` for system-level fields, (c) accept CLI flags `--cluster-name`, `--port`, `--peers` |
| 2 | **configGeneration** | Monotonic counter. **RESOLVED**: Controller uses it to detect new configs — same generation as current = no-op (nothing changed). xceadmin MUST read the current generation from the controller, increment it, and embed the incremented value in the binary before pushing. `xceadmin config build` sets it to 0 (offline-safe default). `xceadmin config push` auto-reads current generation, increments, rebuilds with new generation, then pushes. Admin can override with `--generation N`. Controller strictly rejects generations ≤ current (monotonic enforcement). | ✅ Decided: xceadmin-managed increment, controller-enforced monotonic |
| 3 | **candidates field** | Controller peer list (`List(MasterCandidate)`). Currently passed as CLI args to `deploy-ha.sh`, not in YAML. xceadmin needs peer info for config build | (a) `--peers localhost:9991,localhost:9992,localhost:9993` CLI flag, (b) read from `hosts.yaml` host list, (c) separate `peers.yaml` |
| 4 | **YAML key ≠ capnp field name** | YAML `groupname` → capnp `GroupConfig.name`, YAML `exclude_members` → capnp `exmembers`. Every field needs explicit mapping definition | Write a field-mapping table in design.md; auto-generate from capnp schema annotations if possible |
| 5 | **Nested struct defaults** | `QueueConf` has 14 fields — YAML sets ~6. Remaining (attrs, status, schedDelay, resreq, preExec, postExec) need defaults | Use controller's `get_default_conf()` values from `myconf.c`; document which fields are operator-facing vs system-defaulted |
### Important (resolve during design)
| # | Question | Why it matters | Candidates |
|---|----------|---------------|------------|
| 6 | **Wire framing format** | Controller expects 8-byte uint64 size prefix (host byte order) + capnp payload. ~5 lines of Python but wrong byte order = silent reject | Document in design.md; mock controller test catches framing errors |
| 7 | **TLS cert discovery** | xceadmin needs CA cert to verify controller TLS. Dev: `ha/data/xce.crt`. Production: configurable path | (a) `--ca-cert` flag, (b) `XCECERT` env var, (c) default `ha/data/xce.crt` for dev, require explicit for non-localhost |
| 8 | **capnp schema compilation** | `capnp compile -o python controller.capnp` → `controller_capnp.py`. When and where? | (a) build time in xce-build.sh (committed output), (b) runtime in xceadmin startup (needs capnp binary), (c) nix derivation that produces the .py file |
| 9 | **Python package layout** | Where does code live? Affects pip install, import paths, xce-build.sh | (a) `src/xceadmin/` alongside C code, (b) `tools/xceadmin/` with deploy-ha.sh, (c) top-level `xceadmin/` |
| 10 | **existing cluster.bin provenance** | `ha/data/cluster.bin` is 8704 bytes, SHA 4986624f..., built June 21 by unknown method. Can't validate xceadmin output against it | Regenerate from known YAML inputs; use as golden-file test once xceadmin produces identical output for same inputs |
## Configuration Lifecycle & Rollback (Future Roadmap)
### Source of truth: Git-managed YAML
YAML config files (`configs/*.yaml`) are the authoritative source of truth. Operators manage them in git — version control, code review, audit trail are all free. `cluster.bin` is a **derived artifact** (like a compiled binary), never committed to git.
```
git tag v1.2.0 → YAML at v1.2.0 → xceadmin config build → cluster.bin (deterministic)
│
xceadmin config push
│
▼
controller cluster
```
**Deterministic builds**: same YAML + same generation value + same xceadmin version → byte-identical `cluster.bin`. `configGeneration` defaults to 0 in offline builds (`xceadmin config build`) and is auto-incremented by `xceadmin config push` (read current from controller, +1). Any git revision can be rebuilt to the exact binary that was (or would have been) pushed. No need to store historical binaries.
### What git gives us (free)
| Capability | How |
|-----------|-----|
| Version history | `git log configs/` |
| Diff between versions | `git diff v1.2.0 v1.3.0 -- configs/` |
| Rollback source | `git checkout v1.2.0 -- configs/ && xceadmin config build` |
| Audit trail | `git blame configs/queues.yaml` |
| Code review | PR against `configs/` branch |
### What still needs building
Git manages the **source**. These parts need implementation:
| # | Capability | What it does | Owned by |
|---|-----------|-------------|----------|
| R1 | **Binary diff** | `xceadmin config diff cluster-v1.bin cluster-v2.bin` — human-readable diff of two built config binaries (decode both → compare field-by-field → pretty-print changes). Useful for pre-push review: "what will this config change actually do?" | admin-config-tool |
| R2 | **Dry-run push** | `xceadmin config push --dry-run cluster.bin` — validate controller will accept the config (SHA check, version check) without committing. Safety net before real push | admin-config-tool |
| R3 | **Config change snapshot** | After config upgrade, take an entity-bearing snapshot of the new configuration state. This anchors the config change in the snapshot chain — without it, rollback has no durable record of what the cluster looked like before/after the change. Controller-side: insert snapshot step into the `OP_upgradeConfig` pipeline (between step 5 "majority ACK" and step 6 "commit WAL_CONFIG_MARKER" in the current handler). | config-upgrade-snapshot |
| R4 | **Controller version history** | Controller tracks last N config versions (tag + SHA + timestamp). `xceadmin config history` queries it. Needed for: "what config is the cluster running?" and "what was running before the last push?" Depends on R3 (snapshot chain provides durable version records). | config-upgrade-snapshot |
| R5 | **Controller-side rollback** | `xceadmin config rollback` (or `xceadmin config push --rollback-to v1.2.0`) — push a previous config version. Controller-side concerns: (a) validate rollback target is safe (no running jobs reference deleted queues/hosts), (b) snapshot before rollback for forward-recovery, (c) bump configGeneration even on rollback (monotonic), (d) restart/activate the rolled-back config | config-upgrade-snapshot |
| R6 | **Rollback safety validation** | Before rolling back, verify: (a) no running jobs use queues being deleted, (b) no active workers on hosts being removed, (c) resource definitions haven't changed in ways that break in-flight allocations. Controller-side check, client-side initiated | config-upgrade-snapshot |
| R7 | **Config activation status** | `xceadmin config status` — show: current config tag, when applied, which nodes have it, any pending restart, snapshot status. Operator visibility into config rollout state | config-upgrade-snapshot |
| R8 | **Config promotion pipeline** | Dev → staging → production config promotion. Same YAML source, different `--peers`/`--cluster-name` flags at build time. Git branches map to environments | admin-config-tool (future) |
### Controller-side pipeline: current vs target
```
CURRENT (config_repl.c — exists today):
xceadmin push → SHA verify → decode → gen++ → save → replicate → WAL marker → restart signal
↑
step 8 MISSING
TARGET (after config-upgrade-snapshot):
xceadmin push → SHA verify → decode → gen++ → save → replicate → [SNAPSHOT] → WAL marker → restart signal
↑
R3: entity-bearing
snapshot of new config
```
The snapshot insertion point matters: it goes AFTER replication (all standbys have the config) but BEFORE the WAL marker commit (so the snapshot is part of the committed state). This ensures a rollback target always has a consistent snapshot to restore from.
### Rollback workflow (end-to-end vision)
```
# 1. Discover what went wrong
xceadmin config diff cluster-current.bin cluster-suspect.bin
# → "queues.normal.priority changed 10→1, queues.express removed"
# 2. Find the last good version
xceadmin config history
# → v1.2.0 (active), v1.1.0 (previous), v1.0.0 (initial)
# 3. Dry-run the rollback
xceadmin config build --git-ref v1.1.0 -o cluster-v1.1.0.bin
xceadmin config push --dry-run cluster-v1.1.0.bin
# → "OK: controller will accept this config (generation 3→4)"
# 4. Execute rollback
xceadmin config push cluster-v1.1.0.bin
# → "Pushed (tag=v1.1.0, SHA=..., acked=3/3). Controller restarting."
# 5. Verify
xceadmin config status
# → "v1.1.0 active on 3/3 nodes"
```
### Rollback safety: what can go wrong
| Scenario | Risk | Mitigation |
|----------|------|------------|
| Rollback deletes a queue | Running jobs on that queue become unmanageable | R5: controller refuses rollback while jobs reference deleted queues |
| Rollback removes a host | Workers on that host lose config | R5: controller refuses rollback while active workers on removed hosts |
| Rollback changes resource defs | In-flight allocations invalid | Controller fences: drain before rollback, or mark resources "changing" |
| Rollback during network partition | Standbys miss the config push | Majority ACK required (existing OP_upgradeConfig behavior) |
| Rapid rollback→forward→rollback | configGeneration churn, snapshot chain fragmentation | Rate-limit config changes (1 per N seconds); snapshot GC |
### What's in-scope for admin-config-tool vs deferred
| Capability | This spec | Why |
|-----------|-----------|-----|
| YAML source in git | ✅ (documentation) | Free — already how configs/ works |
| Deterministic build | ✅ (core) | Same YAML → same binary (generation not set — controller-owned) |
| Binary diff (R1) | ✅ (nice-to-have) | Pure Python, offline, high operator value |
| Dry-run push (R2) | ✅ (nice-to-have) | Needs `ProbeConfigTag` RPC (already exists in controller) |
| Config change snapshot (R3) | ❌ deferred | Controller-side — insert entity-bearing snapshot into OP_upgradeConfig pipeline; blocked on entity-bearing-snapshots |
| Version history (R4) | ❌ deferred | Controller-side — depends on R3 snapshot chain |
| Rollback (R5) | ❌ deferred | Controller-side — needs snapshot chain + restart |
| Safety validation (R6) | ❌ deferred | Controller-side — needs job/worker state queries |
| Config status (R7) | ❌ deferred | Controller-side — needs cluster-wide state tracking |
| Promotion pipeline (R8) | ❌ deferred | Wraps git branches + xceadmin build — future enhancement |
## Existing Spec Touchpoints
- **Extends**: `config-upgrade-snapshot` (provides the client-side trigger that config-upgrade-snapshot's controller-side path responds to; R3 config-change-snapshot is the missing controller-side step — insert entity-bearing snapshot between replication and WAL marker commit in the `OP_upgradeConfig` pipeline)
- **Adjacent**: `ha-term-safety` (the WAL_CONFIG_MARKER this tool's upload commits anchors on the Raft log via ha-term-safety's term-index infrastructure)
- **Upstream blocker**: `entity-bearing-snapshots` — must land before R3 (config change snapshot) and R4 (version history) can be implemented
- **Blocks**: nothing — xceadmin ships independently; the current controller pipeline (SHA verify → decode → gen++ → save → replicate → WAL marker → restart) already works without the snapshot step
- **Future**: configuration lifecycle (R1–R8 above) — split across admin-config-tool (R1, R2, R8) and config-upgrade-snapshot (R3–R7)