Files
felhom.eu/documentation/PROMPT-TEMPLATE.md
T
admin ef6ac6fe74
gates / gates (push) Successful in 16s
One register, enforced by a gate; closed work compressed into siblings (R-376..R-378)
Records and process only. No machine contacted.

ONE REGISTER (operator ruling). 17 roadmap rows moved into OPEN-ITEMS.md keeping their
identifiers, evidence and original filing dates - the oldest R-10, filed 2026-07-15, 38 days.
15 ideas stay in ROADMAP.md, which is their home; the gate exempts them by their own state
word. 59 already-closed rows stay as history. Sorting rule recorded in the roadmap header:
does the item assert something about the shipped product a reader could check and find false?

scripts/one_register_gate.py, wired as the 11th gate. Control run: baseline passes, a planted
open roadmap-only row is convicted by name, removing it passes with the file byte-identical,
and a planted `idea` row is correctly exempt. Its four residual holes are in its docstring.

The gate earned its keep immediately: it caught R-103, a READY finding my hand-sort mis-read as
done because my regex matched the whole row where the body contains "shipped" - the gate matches
the state cell. It also caught R-203 and R-163, recorded closed in the register and still open in
the roadmap; the roadmap copies are marked SUPERSEDED with the register's verdict.

HOUSEKEEPING. OPEN-ITEMS 672,376 -> 327,109 bytes (-51%); ROADMAP 239,306 -> 78,110 (-67%).
Closed work compressed to 17% into CLOSED-ITEMS.md and ROADMAP-HISTORY.md; every entry names the
commit whose git show returns the full original text. Rule-sentences are kept verbatim under
"Reasoning kept" rather than judged entry by entry - 25 carry one.

CONTEXT.md deliberately NOT compressed and the disagreement is argued in the report: 86% of it is
standing rulings still in force, this prompt's own 3.4 says the log is never edited, and it has no
per-ruling delimiter. Filed as R-377 - the problem is navigational, not volumetric.

The hot/bulk placement decision was NEVER recorded as a decision anywhere - established, not
assumed. Now marked [DESIGN] with a pointer honest about having no original date, given a
decision-log entry that records what was rejected, and the [DESIGN]/[FACT] legend carried from 1
of 8 architecture documents to 8 of 8. Existing statements deliberately left unmarked (R-376).

PROMPT-TEMPLATE gains N.7: compress what you closed, rehome live reasoning before it goes, state
the register's size before and after.

Ceiling R-375 -> R-378.
2026-08-22 12:13:54 +02:00

588 lines
33 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.
# Claude Code Prompt: [Feature / Fix Name]
<!--
FELHOM PROMPT TEMPLATE v1.0
Adapted from the jarrs.eu template + harmonised with the live Felhom conventions
(controller/agent/hub/app-catalog, Go stdlib, trunk-based, two-AI workflow).
WHO WRITES THIS: Claude.ai (architecture/validation partner). WHO EXECUTES IT: Claude Code (CC),
which has live SSH to the build server, the demo nodes, and felhom-pve, plus the Gitea push token.
HOW TO USE:
1. Copy this template into a TASK-*.md (a spec for CC to implement).
2. Pick the TASK CLASS (§0) — it decides which sections are mandatory vs "N/A".
3. Fill in every mandatory section. If a section doesn't apply, write "N/A — [reason]"
(don't delete it, so CC knows it was considered).
4. Verify all confirmed baselines and reuse line-refs against LIVE Gitea source before sending
(cardinal rule: do not trust memory or a prior REPORT.md).
5. Run the AUTHOR CHECKLIST at the bottom before handing the prompt to CC.
This is NOT a RUNBOOK. TASK-*.md = "build this, push, document." RUNBOOK-*.md = "execute this
operational procedure (incl. live validation)." If the deliverable is an operational run, write a
RUNBOOK instead and use only §0, §1, §13, §15 of this template.
-->
---
## For the operator — what this fixes, in one page (plain language)
<!--
MANDATORY for any M+ task and for any task carrying a STOP. Optional but encouraged below that.
Precedent: TASK-B, TASK-C and TASK-D shipped with this section and it is why those runs needed no
mid-flight explanation; TASK-E made it a standard block.
WHO IT IS FOR: a technical-but-not-in-the-code reader — someone who runs the business and the
hardware, knows what the components are, and has not read the file you are about to change.
IT MUST SAY, in this order:
1. WHAT BREAKS TODAY — the observable symptom, not the code defect. "Your Stop is silently
undone on a reboot", not "shouldRecreateOnBoot ignores container state".
2. WHAT CHANGES — the new behaviour, in the same observable terms.
3. WHAT THE OPERATOR WILL BE ASKED TO DO — every STOP, every ruling needed, every physical
action (flash this, press that, confirm this), and anything that is HUMAN-only. If the answer
is "nothing", say "nothing".
4. WHAT IS NOT AFFECTED — especially for anything touching customer data, credentials or
recovery. "Existing codes still work" belongs here.
RULES:
- No jargon the ROADMAP row doesn't already explain. If a term must be introduced, define it in
the sentence that uses it.
- No file paths, no function names, no version numbers except the ones the operator acts on.
- One page. If it does not fit, the task is probably two tasks.
- Write it LAST, after the parts are settled, but place it HERE — it is what gets read first.
- It is a summary, never the spec: it must not be the only place a requirement appears, or CC
will implement from prose. Everything here is restated precisely in §0 onward.
-->
---
## 0. Task class & scope
<!-- Pick ONE primary class. It sets which sections are mandatory. -->
- [ ] **Implementation** — code change with unit-test coverage; build + deploy + verify. (All sections.)
- [ ] **Spike** — empirically validate an unproven mechanism BEFORE any production spec is written.
Output is a findings doc in `felhom.eu/documentation/audits/SPIKE-<topic>-<date>.md`, not shipped
production code. (§1, §2, §3, §4, §13-verify, §15. Skip §5-§12 unless the spike writes throwaway probes.)
- [ ] **Risky / supervised** — touches agent / golden image / provisioning / destructive disk ops, or
anything that mutates real customer data. Spec it; implement during the supervised session itself,
directly on `main`. Mandatory STOP point in §13. (All sections + §13 STOP is load-bearing.)
- [ ] **Runbook-style validation** — see note above; prefer a RUNBOOK-*.md.
**Repos touched** (check all — each gets its own baseline, green gate, build/deploy, and wrap-up):
- [ ] `felhom-controller` (in-guest; Docker-only; **no Proxmox creds**) — app domain
- [ ] `felhom-agent` (host; operator-tier; owns ALL Proxmox interaction)
- [ ] `felhom.eu` (`hub/`, `website/`, `documentation/`)
- [ ] `app-catalog-felhom.eu` (compose templates + `.felhom.yml`)
> Don't confuse the two ex-"controllers": **felhom-agent** (host, was `proxmox-controller`) vs this
> **felhom-controller** (in-guest, was `deploy-felhom-compose`). Host/disk/Proxmox/Cloudflare logic
> lives in the agent; the controller keeps stack/deploy/UI/app-data-backup/metrics/git-sync/notifications.
---
## 1. Confirmed baselines (anti-drift anchor — MANDATORY)
<!-- Verify each against LIVE Gitea before sending. This is what catches "CC built on a stale tree".
curl -H "Authorization: token $TOKEN" \
"https://gitea.dooplex.hu/api/v1/repos/admin/<repo>/commits?sha=main&limit=1"
and read the version from the source of truth (ldflags var / VERSION / latest CHANGELOG entry). -->
| Repo | `main` @ commit | Current version | → Target version |
|------|-----------------|-----------------|------------------|
| felhom-controller | `<hash>` | `v0.XX.0` | `v0.XX+1.0` |
| felhom-agent | `<hash>` | `v0.YY.0` | `v0.YY+1.0` |
| felhom.eu (hub) | `<hash>` | `v0.ZZ.0` | `v0.ZZ+1.0` |
**An unpushed change does not exist** for validation or deployment. CC commits directly to `main`
(trunk-based — **no feature/fix branches**). If a change can't be cleanly shipped, **revert + report**;
never park it on a branch.
---
## 2. Overview
<!-- 3–5 sentences. What this implements, the user-visible outcome when done, which architecture/spike
doc it corresponds to, and which slice/phase it belongs to. Orientation, not specification. -->
---
## 3. Spike-first gate
<!-- Answer honestly. If this touches a mechanism whose behaviour has NOT been empirically proven
(a new external API, an undocumented Proxmox/PBS quirk, a filesystem/mount edge), this is a SPIKE,
not an implementation. The PBS spike caught `ignore-verified=true`, the node-name-from-UPID
requirement, and the dual privsep+token grant BEFORE any code — that is the bar. -->
- Does this rely on any unvalidated mechanism? **yes / no**
- If yes: is it already validated in a spike doc? Cite it: `felhom.eu/documentation/audits/SPIKE-...`.
- If yes and not validated: **STOP — switch task class to Spike.** Do not spec production code on
assumptions.
---
## 4. Read these files first
<!-- Order matters: workspace/repo conventions first, then architecture, then the exact source.
Be explicit about WHY each matters. -->
1. `/mnt/5_hdd/felhom.eu/git/CLAUDE.md` — workspace orientation (the felhom system, shared
conventions, access). Versioned copy: `felhom.eu/documentation/runbooks/workspace-CLAUDE.md`.
2. `<repo>/CLAUDE.md` — repo build/deploy, code-quality rules, trunk-based + live-validation rules.
3. `<repo>/CONTEXT.md` — current project state / decisions / roadmap.
4. **THE ARCHITECTURE DOCUMENT FOR THE AREA THIS TASK TOUCHES — NAME IT AND SAY WHAT IT SAYS.**
Not "read the architecture folder": name the file, and state in one line what it rules about this
area. **A prompt that cannot name one says so explicitly, and that absence is itself recorded** —
an undocumented architectural decision is how a deliberate design gets "fixed" by someone who did
not know it was one.
| area | file |
|---|---|
| what the platform does today, per scenario | `00-capability-map.md` |
| topology, trust, **app-data placement (hot vs bulk)**, backup scoping | `01-topology-and-trust.md` |
| controller packages: KEEP/PORT/DELETE/MODIFY | `02-controller-module-map.md` |
| the host agent | `03-host-agent.md` |
| control-plane authorization | `04-control-plane-authorization.md` |
| the hub | `05-hub-architecture.md` |
| off-site connectivity | `06-offsite-connectivity.md` |
| tiers, capture sets, restore paths, recovery model | `07-backup-architecture.md` |
**Three sources, in this order, before any claim: the architecture folder holds the REASONING, the
register holds the WORK, source holds the TRUTH.** Skipping the first is how a decision gets
reported as a bug.
**And the test that catches it: _is what I am about to call a defect something we chose?_** If it
was chosen and the choice is wrong, that is **a proposal to change a decision** — it goes to the
operator as a decision, not filed as a bug. **Cost of learning this (R-370):** between 19 and 22
August a documented placement decision was called a defect in four places, because the register and
live source were read and `documentation/architecture/` was not.
`02-controller-module-map.md` remains **required reading before touching `backup/`, `storage/`,
`system/`, `config/`.**
5. `felhom.eu/documentation/controller/<feature>.md` — code-verified feature docs (authoritative; match
code, not summaries, if they drift).
6. `controller/README.md` (or `felhom-agent/README.md`) — module map, feature reference, REST API.
7. [exact source file(s) to modify] — [why it's relevant].
8. [exact source file(s) to depend on] — [why it's relevant].
---
## 5. Existing functions / helpers to reuse (MUST use — do NOT reinvent)
<!-- For Go: cite file + exported symbol + short signature. Verify EVERY symbol against live source
(read the file; don't write names from memory — they drift). Line numbers are LANDMARKS only:
the bundled files CC reads are not the source files, so reference by symbol/landmark and tell CC to
reconfirm the line. If a helper must change to support this task, say "Yes — [exact change]" in
Modify? — that becomes an explicit task in a Part below, not an implicit assumption.
The forbidden reuse: e.g. "do NOT reuse rsyncMirror (tier2.go) — it has --delete". Name the trap. -->
| Symbol | File (landmark) | Signature / use | Modify? |
|--------|-----------------|-----------------|---------|
| `Manager.StopStack` | `internal/stacks/manager.go` (~L682) | `StopStack(name string) error` — idempotent | No |
| `Manager.RedeployFromEnv` | `internal/stacks/deploy.go` (~L478) | writes app.yaml then `compose up -d` + post-start status | No |
| `appbackup.AppDataDir` | `internal/.../paths.go` | namespace+app → data dir | No |
| `writeDiskJSON` | `internal/.../agent_disk_handlers.go` | JSON envelope for disk endpoints | No |
| [helper that needs a change] | `path/file.go` | `Func(args)` | **Yes — add `intent` param; gate on `IntentEnrolled`** |
**Do NOT reuse:** [name the dangerous-lookalike + why], e.g. `rsyncMirror` (has `--delete`).
---
## 6. Pattern references (copy structure from these canonical files)
<!-- For every NEW file type this task creates, point to the ONE canonical file to copy from. -->
| Pattern | Canonical file | Key traits to copy |
|---------|----------------|--------------------|
| REST handler | `internal/api/router.go` + a sibling handler | stdlib `net/http`, JSON envelope, error logging |
| Local-API disk handler | `internal/localapi/disks.go` (`handleDiskEject`) | `withGuest`, `scopedFromBody`, role gate, fail-safe-to-protected |
| Scheduler job | `internal/.../scheduler` | register + skip-if-running, graceful shutdown |
| Crash-safe journal | `internal/stacks/backup.go` | journal-before-mutate, atomic tmp+fsync+rename, `Recover` on startup |
| Unit test | `internal/.../*_test.go` (a recent non-hollow one) | `t.TempDir()`, fakes/seams, effect assertions |
| HTML template | `internal/web/templates/*.html` | Hungarian UI, Go template funcs, no inline secrets |
---
## 7. Integration scenarios (must pass)
<!-- WRITE THESE BEFORE THE IMPLEMENTATION SECTIONS. They are the acceptance criteria.
Given/When/Then with concrete actors and exact counts/states. Always include the WRONG case.
EVERY scenario here MUST have a corresponding test in §11/Tests. -->
### Scenario A — [happy path]
```
Given: [concrete state]
When: [exact endpoint / CC action]
Then:
- [effect 1 — exact value/state]
- [effect 2]
- [the WRONG outcome this must NOT produce]
```
### Scenario B — [alternative / role / mode]
```
Given: [what differs from A]
When: [action]
Then: [how outcomes differ and WHY]
```
### Scenario C — [error / boundary / security gate]
```
Given: [bad input / unauthorised actor / data-bearing device]
When: [action that must be refused]
Then: [exact refusal — HTTP status, error, and the proven non-effect, e.g. "mkfs NOT called"]
```
---
## 8. Edge cases & deduplication rules
<!-- Explicit rules for overlapping concerns: "When X and Y both apply, the result is Z — not X+Y."
Use a truth table if >2 branches. (E.g. conflict-merge: target absent → copy; present+identical →
skip; present+differs → write lowest-free (N), never overwrite.) -->
| Condition A | Condition B | Result |
|-------------|-------------|--------|
| | | |
---
## 9. Strict rules
<!-- Earned through bugs. Copy verbatim; document any per-task exception. -->
1. **Green gate (Go):** after **every commit/phase**, in each touched repo:
`go build ./... && go vet ./... && go test ./...` — all green before proceeding. The build IS the
typecheck; do not accumulate compile errors.
2. **Minimal changes:** build only what's listed. No "while I'm here" refactors. Note anything worth
fixing under "Observations" (§15) — don't act on it.
3. **No silent failures:** never swallow a parse/exec error — log it. Check a subprocess's **own** exit
code; never pipe in a way that hides a 127. (The silent `.felhom.yml` quoting bug + the spike's
exit-swallow lesson.)
4. **Secrets safety:** log env var **keys**, never values. **Never write secrets** (tokens, passwords,
keys) into `CHANGELOG.md`, `REPORT.md`, or any committed file — reference as "stored out-of-band".
5. **Line numbers are landmarks:** reference code by symbol/landmark; reconfirm the line in the real
source before editing (bundled files ≠ source files).
6. **Trunk-based:** commit directly to `main`. No branches. Can't ship cleanly → revert + report.
7. **Version discipline:** never `:latest`; bump the version on every task; one CHANGELOG entry per
shippable change; **overwrite** REPORT.md.
8. **Coding standards:** follow `<repo>/CLAUDE.md` (cross-cutting) and the feature doc / README for
domain patterns.
---
## 10. Non-hollow test discipline (MANDATORY for any correctness/security fix)
<!-- The Felhom signature. A test that only asserts "no error" is hollow. -->
- **Assert the effect happened**, not just absence of error. Security example: prove `mkfs` was NOT
called on a data-bearing device by inspecting the device/fake state — not by trusting the caller.
- **Companion red-proof:** for every correctness fix, write the test so it **FAILS on the pre-fix /
trivial implementation**. Demonstrate this: run the mutation (or guard-removed shape), confirm the
test fails, revert. State the result in §15.
- **FS work** uses `t.TempDir()`; **orchestration** uses fakes/seams (introduce a small `stackOps` /
`copier` interface so tests don't shell out to docker/rsync).
- **Generation/idempotency:** assert the negative (e.g. "fetch count does NOT increment on an
unchanged heartbeat"; "a re-run finds its own prior `(N)` and adds no `(1)(1)`").
- **Seam discipline — every seam added gets ONE test through the PRODUCTION wiring path.** An
injected-seam test proves the component, never the caller. Three shipped defects in three days
make this non-negotiable: controller v0.154.0 (the wizard read a flag the handler never sets),
agent v0.91.0 (`main.go` never called `SetAuthSink`, so the whole auth-honesty leg was inert), and
agent v0.92.0 (the watchdog had no sudoers grant for three of its four probes). **Every one of
them was fully green.** Where the caller is `func main()` and cannot be invoked from a test, walk
its AST for the call — and note that a `strings.Contains` on the source is NOT sufficient: a
commented-out call still contains the string, which is how the controller's first version of that
test passed its own red-proof (2026-07-21).
---
## Part 1: [Foundation / engine]
### 1.1 [Task]
**File(s):** `path/file.go`
**Problem:** [what's wrong / what to build, and why]
**DO:** [explicit positive instructions; tricky code snippets only — CC fills the obvious parts]
**DO NOT:** [the specific anti-pattern for THIS task]
**Crash-safety (if stateful):** journal-before-mutate; atomic tmp+fsync+rename; `Recover` on startup;
guaranteed cleanup via `defer` **for graceful exits only** — a `defer` does NOT run on SIGKILL, so a
crash-safe cleanup needs an on-disk marker plus a startup `Recover()` (Campaign 8 fault 10 proved
this on live hardware); single-flight mutex; `ListLXC`-style ground truth in recovery.
**Green gate:** `go build ./... && go vet ./... && go test ./internal/<pkg>/`
---
## Part 2: [Core logic / external surface]
### 2.1 [Task]
**Phase order (risky surfaces):** Phase A structural → validate → Phase B security/external surface.
**Green gate:** full `go test ./...`
---
## Part 3: [Web / UI — if touched]
### 3.1 [Task]
<!-- Felhom UI is Hungarian, adult tone, minimal emoji (keep UI clean/professional). Specify the exact
strings. Reuse template funcs (stateColor/stateLabel/isOperational/logoURL/...). Progress panels:
reuse the deploy 3-step + format-status poll pattern. -->
**Hungarian strings:** "[exact copy]" — e.g. "Adatok másolása: <app> (X% — Y/Z GB)…".
**Green gate:** `go build ./...` + `go test ./...`
---
## Part N: Documentation & versioning
### N.1 CHANGELOG.md (each touched repo)
Cumulative, **newest entry on top**. New version number. Cite the files/commits for each item.
### N.2 REPORT.md (each touched repo) — **OVERWRITE**
Most-recent implementation only (not cumulative). MUST contain: confirmed baselines, per-commit
hashes, per-test results, deployed versions, and an explicit **"NOT yet live-validated — awaiting
supervised [Bx]"** list. No secrets.
### N.3 CONTEXT.md
Decisions made, architectural state, what's next.
### N.4 README / architecture docs
`<repo>/README.md` if architecture/features changed; authoritative architecture/feature docs in
`felhom.eu/documentation/{architecture,controller}/`. Spikes/audits → `felhom.eu/documentation/audits/`.
### N.5 The coupling rule — FOUR artifacts, same session (end-of-session checklist)
If the task **changed what the platform can do** (new/removed capability, a capability moved status,
or **what is open changed**), update **all four** in the SAME session:
- **`architecture/00-capability-map.md`** — add/adjust the affected *scenario* row with the correct
**status** (PROVEN-LIVE requires an `audits/`|`tests/` citation; else IMPLEMENTED/PARTIAL) and an
evidence citation. A leg not exercised live → PARTIAL/IMPLEMENTED with a note saying which leg and
a `→ R-n` pointer, never PROVEN-LIVE.
- **`backlog/ROADMAP.md`** — add any new open work / deferred legs as an item (next free `R-n`),
collapse a now-shipped item to its one-liner + version, and honor the **coupling rule** (every
roadmap item names the map row it flips; every map gap row points back by ID). Parked sub-features
(e.g. an alternative transport, an agent-plane leg) go as a note under the owning arc's item.
- **the owning `architecture/*.md`** — ruled as **S-1** in `CONTEXT.md` (2026-07-26, R-81): any task
that changes an **architectural contract** (tiers, targets, cadences, trust boundaries) updates the
owning design doc in the same session. It was ruled but never written here, in the template CC
actually reads — so it bound nobody. Now it does.
- **`backlog/OPEN-ITEMS.md`** — the register, and the single source of truth for open work. A task
that changes what is open without touching it re-creates exactly the thread-loss the register was
built to solve: `REPORT.md` is overwritten every session, so nothing durable may live only there.
> **AN ENUMERATED GAP BECOMES A ROW, IN THE SAME SESSION. PROSE IS NOT A RECORD.**
>
> This binds **surveys, inventories, spikes, reviews and diagnoses**, not only implementation
> sessions — those are the documents that enumerate gaps, and they are the ones that have lost them.
> If a document says a thing is missing, unhandled, unreachable or *"not currently filed"*, it does
> not leave the session as prose. It leaves as a row here, with a rank and an owner. Writing
> *"not filed"* is not a disposition; it is a note that the work was seen and dropped.
>
> **A row in `ROADMAP.md` alone does not satisfy this.** Both files hold open work and only this one
> calls itself the source of truth, so a finding recorded solely there is invisible to every
> standing rule that says *"grep the register before minting"* (**R-369**).
>
> **The cost, recorded so the rule can be narrowed later rather than becoming permanent by
> accident:** R-107 — *"no offsite action unpacks the named-volume tars Tier-3 captures"* — was
> enumerated on **2026-07-28**, given a number, written into `ROADMAP.md` and
> `07-backup-architecture.md`, and never entered here. **It was rediscovered from scratch 25 days
> later by an overnight drill that planted files and watched them not come back**, and shipped as
> R-354. The work was right the first time; only the filing was missing.
**Report which `OPEN-ITEMS.md` rows the task opened, closed or re-ranked** (§15). Every row carries
an owner — a row nobody owns is how items got lost in the first place.
### N.6 Website version bump (if controller/hub version is shown on the site).
### N.7 Housekeeping — before the report, not after (2026-08-22 ruling)
**Nothing was ever pruned and the numbers got bad:** the register reached 672 KB across 286 entries,
over half of it finished work, one entry at 16 KB. **A file that cannot be read is a file that cannot
be checked**, and this project has paid for that twice — a record nobody could find because it sat
inside an entry about something else, and a finding rediscovered because nobody could see it.
1. **Compress what this session closed.** A closed row keeps its title, the version it shipped in, its
evidence paths, and any sentence stating a rule. Everything else goes, and it moves to
`backlog/CLOSED-ITEMS.md`. **Nothing is deleted:** the compressed entry names the commit whose
`git show` returns the full original text. **Open rows are not touched — their detail is doing a job.**
2. **Rehome live reasoning before compressing it away.** If a closed entry carries the reason a rule
exists or a fence sits where it does, that reasoning moves — to `CONTEXT.md` if it is a decision,
to the owning `architecture/*.md` if it is a shape. **Where it is a decision, mark the resulting
shape `[DESIGN]` in the architecture document and point it at the log entry** (§4's map).
**Losing a reason is how a deliberate design becomes a bug in someone's eyes** — that cost four
mis-filed defect reports in August 2026 (R-370, R-376).
3. **State the register's size in the report, before and after.** A number every session is what makes
growth visible; prose about tidiness is not a mechanism.
---
## 11. Tests
<!-- Numbered, mapping DIRECTLY to §7 scenarios. Every scenario (incl. error) gets ≥1 test.
Each correctness/security test gets the §10 companion red-proof. -->
### Test Group A — [Scenario A]
```
- setup (t.TempDir / fakes)
- action (the exact call)
- assert (effects, exact values/counts)
- companion: [mutation that makes it fail on pre-fix code]
```
### Test Group B — [Scenario B]
### Test Group C — [Scenario C — error/security gate]
---
## 12. What NOT to do
<!-- Concrete temptations for THIS task, not "don't add features". -->
- Do NOT [specific out-of-scope thing CC might build].
- Do NOT [**the forbidden ACT**, and **its reason**: e.g. "modify the operator-signed
`DecommissionExecutor` — it is the signature boundary"]. **Fence the act, not the object.** A bare
"do not touch X" is read as covering every act on X, including ones nobody meant to restrict — that
is how "do not re-target demo-hp's backup target" became "do not use demo-hp at all", which pushed a
destructive drill onto the one machine holding the recovery chain. State the reason too: a rule whose
reason is recorded can be correctly narrowed later, and one without it becomes permanent by default.
- Do NOT reuse [the dangerous lookalike from §5].
- Do NOT refactor nearby code, change passing tests, or create a branch.
- Do NOT add "Co-Authored by..." anywhere
---
## 13. Build / deploy / STOP
<!-- Per touched repo. Real commands. CC runs ON DooPlex — builds are LOCAL, no SSH wrapper. -->
**Clean-tree gate first:** `git status --porcelain` empty AND `HEAD` == `origin/main` in the repo
being built. An unpushed change does not exist.
**If the task needs a machine to break — NAME IT.** A drill, destructive test or throwaway VM gets an
explicit target: *"run this on demo-hp"*. Do not leave it to be inferred from the prohibition list;
listing only what is off-limits leaves the most valuable unfenced machine as the residual choice, which
is exactly how a drill landed on DooPlex. Tiers and per-machine permitted/care/forbidden:
[`runbooks/target-selection.md`](runbooks/target-selection.md).
**Controller** (local build on DooPlex → guest via golden/bootstrap):
```bash
FELHOM_ROOT=/mnt/5_hdd/felhom.eu
# build + push
cd $FELHOM_ROOT/build/felhom-controller && git -C $FELHOM_ROOT/git/felhom-controller pull && ./build.sh <VER> --push
# deploy to guest (golden/bootstrap): docker pull → write /etc/felhom-controller-image → restart bootstrap svc
# verify:
ssh felhom-pve "pct exec 9201 -- docker ps --filter name=felhom-controller --format '{{.Image}} {{.Status}}'"
```
**Agent** (build locally, `scp /tmp/felhom-agent-<VER> felhom-pve:/tmp/` — one hop; deploy to
`felhom-pve`: backup prior binary, replace, restart service; verify clean restart — e.g.
`ReassertGuestBinds` does not rebind a non-`enrolled` drive).
**Hub** (`felhom.eu/hub/`): build + push locally, then bump `manifests/hub.yaml`'s `image:` tag in
git and do a deliberate ArgoCD sync — **never `kubectl set image`** (auto-sync is OFF and any
imperative change is reverted on the next sync). Verify Synced/Healthy + rollout + image + logs.
**Live validation rule:** if validating a user-facing feature, exercise the **real server pipeline**
(connect → enroll → deploy), or invoke the **exact endpoint the UI invokes** (acceptable proxy — no
server logic skipped). **Do NOT** hand-set state via raw agent attach (the F9 bypass). Browser
automation is **NOT available** on DooPlex — endpoint-level is the standard method; say which was used.
**STOP point (risky/supervised tasks):** build + unit-test + build/deploy images, but **do NOT run a
live destructive op or migrate real customer data** — that is the supervised [Bx] step. Throwaway
marker dirs under a scratch `/mnt` path are OK only if they touch no enrolled customer data.
**Teardown.** A run that provisions anything owns its removal. Three layers, and the third is the one
that gets missed:
1. **The machine** — the VM or guest and its volumes, deleted.
2. **The host** — `pvesm status` before and after, and the space actually returned.
3. **The hub** — the customer or appliance record the run created. State its disposition
**explicitly**: deleted; or retained as a fixture **with the reason**; or blocked by the
ONLINE→DOWN delete gate **with the command recorded for later**. Silence is how `drill-r50` became
both a blocked customer and the only drift fixture.
**Precedent: three drills, three orphaned customers** — `drill-r50`, `sess-c`, `sess-d`. `sess-c` was
not recorded by its own report, so the record said the teardown was clean when it was not. Layers 1 and
2 are the ones that get remembered because they are visible on the box; layer 3 is invisible from there
and has been missed every time.
Which machine to provision on in the first place: **`documentation/runbooks/target-selection.md`**.
---
## 14. Implementation order
```
Phase 1: [Part 1] → go build ./... && go vet ./... && go test ./internal/<pkg>/
Phase 2: [Part 2] → full go test ./...
Phase 3: [Part 3 — UI] → go build ./... && go test ./...
Phase N-1: [Tests] → full go test ./... (all green)
Phase N: [Build/deploy/verify + docs + push to main]
```
---
## 15. Final verification & deliverables
<!-- CC runs this after all phases. This report is what Claude.ai validates against pushed Gitea
source (file:line) — the cardinal rule. Make it checkable. -->
Run, per touched repo: `go build ./... && go vet ./... && go test ./...`
Report MUST include:
1. **Confirmed baselines** used (repo @ hash, version).
2. **Files created / modified** (paths).
3. **Per-commit hashes** pushed to `main`.
4. **Per-test results** (name → pass/fail) + the §10 companion red-proof outcomes.
5. **Test count before / after**; all green (or list failures).
6. **Deployed versions** + `docker ps` / agent-service / hub-pod verification output.
7. **NOT yet live-validated — awaiting supervised [Bx]:** explicit list (the real
put-data → operate → integrity flow).
8. **Evidence copied off BEFORE each revert** — for every phase that ran on a machine, the logs were
pulled to the evidence directory **at the end of that phase**, before any revert, snapshot restore
or teardown, **including the intermediate ones**. *The intermediate revert is the one that gets
forgotten: two sessions lost a phase's logs to a mid-run revert to `virgin` on 2026-08-12 and
2026-08-13 — same machine, same point, three days apart (R-320).* **If a phase's evidence is
already gone, the report says so plainly and the finding is REPRODUCED independently** — that is the
expectation, not an improvisation. A quotation read live and no longer re-readable is named as such.
9. **Teardown evidence — all three §13 layers**, if the run provisioned anything: the machine deleted,
`pvesm status` before/after with the space returned, and the **hub-side record's disposition named**
(deleted / retained-with-reason / gate-blocked-with-the-command). A run that provisioned nothing says
so. "Teardown clean" without layer 3 is not a report — it is the `sess-c` failure.
9. **Observations:** out-of-scope items noticed — documented, NOT acted on.
---
<!--
========================================================================
AUTHOR CHECKLIST (for Claude.ai — verify before handing to CC)
========================================================================
| # | Check | Prevents |
|---|-------|----------|
| 1 | Task class picked; mandatory sections filled (rest "N/A — reason")? | Bloated small tasks / under-covered risky ones |
| 2 | Confirmed baselines verified against LIVE Gitea (hash + version), not memory? | CC builds on a stale tree |
| 3 | Spike-first gate answered; unvalidated mechanism → switched to Spike? | Spec'ing production code on assumptions |
| 4 | Every reuse symbol verified against live source; line-refs marked landmark? | Wrong method names; line drift |
| 5 | Every helper marked "Modify? Yes" has an explicit Part task? | Helper change treated as afterthought |
| 6 | Dangerous-lookalike reuse explicitly forbidden (the trap named)? | `--delete` / over-broad reuse bug |
| 7 | Integration scenarios written BEFORE implementation, incl. the WRONG case? | CC reads top-down; ambiguous acceptance |
| 8 | Every scenario (incl. error/security) has ≥1 test? | Untested boundaries |
| 9 | Every correctness/security fix has a non-hollow test + companion red-proof? | Hollow "no error" tests; silent regressions |
| 10| Green gate stated per repo per phase (build && vet && test)? | Accumulated compile/test debt |
| 11| Secrets-safety: no secret written to committed files; keys-not-values in logs?| Leaked credentials in CHANGELOG/REPORT/logs |
| 12| Trunk-based: no branch instructions; revert-and-report escape hatch stated? | Work parked on a branch |
| 13| Build/deploy commands correct for each repo (controller golden / agent / hub)?| Failed or wrong deploy |
| 14| STOP point explicit for risky/supervised; live-validation hits real pipeline?| Destructive op run unsupervised; F9 bypass |
| 15| Doc routing correct (CHANGELOG cumulative, REPORT overwrite, CONTEXT, README, documentation/)? | Doc drift / cumulative REPORT |
| 15b| Capability changed → `00-capability-map.md` row + status/evidence updated AND `backlog/ROADMAP.md` item added/collapsed (coupling rule)? | Capability map goes stale; deferred legs lost |
| 16| UI strings Hungarian + adult tone + minimal emoji specified? | English/placeholder UI leaking to customers |
| 17| §15 report format demanded so the result is validatable against Gitea? | Unverifiable "done" claims (cardinal rule) |
If any check fails, fix the prompt before sending.
========================================================================
-->