One register, enforced by a gate; closed work compressed into siblings (R-376..R-378)
gates / gates (push) Successful in 16s

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.
This commit is contained in:
2026-08-22 12:13:54 +02:00
parent fddfe00ce2
commit ef6ac6fe74
18 changed files with 1166 additions and 488 deletions
+27
View File
@@ -14,6 +14,33 @@
> language, one screen, no identifiers in the prose. Same subjects, different readers; merging them > language, one screen, no identifiers in the prose. Same subjects, different readers; merging them
> would make one of the two audiences stop reading. `STATUS.md` is also a **view of `OPEN-ITEMS.md`** > would make one of the two audiences stop reading. `STATUS.md` is also a **view of `OPEN-ITEMS.md`**
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below. > and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
## App data placement is a DECISION, and it had never been written down as one (2026-08-22, R-376)
**Recorded here on 2026-08-22 to give an existing decision a home. It is NOT new, and this entry is
not its date** — the choice is older and its original date is not on record. That is itself the
finding (R-376).
**The decision.** App data is placed **per volume, not per app**. `.felhom.yml` classes each volume
**hot** — database, config, cache — which goes to fast storage inside the guest and is **enforced**;
or **bulk** — media and files — which may live on slow attached storage. A photo app's database stays
on the SSD while its blobs go to the USB drive. Recorded shape:
`architecture/01-topology-and-trust.md:150-152`, now marked **[DESIGN]**.
**What follows from it, and is a consequence rather than a separate choice:** 40 of the 53 catalogue
templates declare no configurable data path, because they are all-hot apps. The 13 that do are the
media and library apps. Stated as **[FACT]** at `07-backup-architecture.md:296-299`.
**What was rejected, so the same proposal does not return.** Moving named-volume (all-hot) app data
onto the default data drive was proposed in `SPEC-app-data-placement-2026-08-21.md` §5 and is
**rejected**: it would move hot data onto storage this design classes as possibly-slow, and would put
it outside the guest vzdump that currently protects it (`01-topology-and-trust.md:154-156`). The spec
carries the correction inline.
**Why this entry exists at all.** The decision was real, implemented and load-bearing, and lived in a
single unmarked bullet. Between 19 and 22 August it was called a defect in four places (R-370),
because a reader met a marked `[FACT]` beside an unmarked choice. **A decision nobody wrote down as a
decision is one somebody will eventually report as a bug.**
## Read the architecture folder BEFORE calling something a defect (2026-08-22, R-370 / R-369) ## Read the architecture folder BEFORE calling something a defect (2026-08-22, R-370 / R-369)
**Between 19 and 22 August the reviewing side called a documented architectural decision a defect, in **Between 19 and 22 August the reviewing side called a documented architectural decision a defect, in
+238 -250
View File
@@ -1,308 +1,296 @@
# REPORT — correcting what we mis-called a defect, and finding what we wrote down and never filed (2026-08-22) # REPORT — one register, and a rule that keeps it readable (2026-08-22)
**Documentation and survey only. No code, no version bump, no bake, no deploy, no machine contacted.** **Records and process only. No controller, agent or hub code. No release, no bake, no machine
The previous report is preserved at `documentation/audits/REPORT-v0.218.0-r354-r355-2026-08-22.md`. contacted.** Previous report preserved at
`documentation/audits/REPORT-mis-called-defect-and-sweep-2026-08-22.md`.
--- ---
## 1. §2's four claims — ALL FOUR HELD. No halt. ## 1. Baselines — confirmed
| # | claim | verdict | Controller **0.218.0**, agent **0.130.0**, golden **0.218.0 vouched**, floor **0.218.0** — all read
back from the hub, not assumed. Register ceiling was **R-375** (grepped, not trusted). All four repos
clean, `HEAD == origin/main`.
**The prompt's measurements, re-measured.** `ROADMAP.md` 249 lines / 239,306 B and `CONTEXT.md`
2,580 lines matched **exactly**. `OPEN-ITEMS.md` read 691 lines / **672,376 B / 286 entries, 134
closed** against the prompt's 642,731 / 272 / 104 — the difference is **this project's own last
session**, which added 17 rows, plus a wider closed-vocabulary on my side (I count `RESOLVED` and
`FIXED` as terminal). Longest single entry **16,108 B (R-193)**, not 15,965 — same entry, since grown.
---
## 2. The 59-versus-72 reconciliation — **both were right when taken, and neither should size anything**
| method | value | stable? |
|---|---|---| |---|---|---|
| 1 | `07-backup-architecture.md:297` — 53 templates, 52 named volumes, exactly 13 with a `backup:` block, and those 13 are exactly the ones that bind a configurable path | **HOLDS**, at `:296-299`. The heading above it reads *"Coverage per app class — and an unresolved count"*, which the sweep then followed | | ids **mentioned** in ROADMAP minus ids mentioned in the register | **72** at commit `5a7502b`, **61** today | **no** |
| 2 | `_recovery-inventory-2026-07-28.md` Tier-3 row — *"…unpacked by no offsite action… no single action does it and no UI routes it. This is my own enumeration; it is not currently filed as a finding."* | **HOLDS**, verbatim, at `:373` | | ROADMAP **rows** minus register **rows** | **90**, unchanged across all five commits checked | **yes** |
| 3 | `:392-400` — `rootfs`, `mp0 /var/lib/docker`, `mp1 /mnt/sys_drive` as separate volumes | **HOLDS**, at `:392-396` — **but it describes a layout no current box has.** See below | | …of those 90, marked shipped/closed/killed/ruled | **59** | — |
| 4 | `00-capability-map.md:94` — one data volume; a local backup bounded by free space | **HOLDS**, at `:94` | | …of those 90, **not** so marked | **31** | — |
**Claims 3 and 4 describe different layouts, six days apart, and 4 is the live one.** The inventory **Why 72 became 61 without anything moving:** my own **R-369** row, written last session, *names* 11
(2026-07-28) records the pre-R-165 split. `felhom-agent/configs/build-golden.sh:29-40` — live source — of those identifiers in its prose. A "mentioned" count therefore falls when someone merely writes
bakes **ONE** data volume at `/var/lib/felhom`, with `/var/lib/docker` and `/mnt/sys_drive` as two about the problem. **That measure is unusable for sizing a migration.**
**binds of that same volume**, and states at `:99` that *"There is deliberately no mp1"*.
**So the prompt's own §1.2 instruction is out of date:** it asked me to say that named volumes and **And 59 is real** — it is the *history* half of the row-based population. So the prompt's figure and
first-tier backups are *"on two different guest volumes on one physical disk"*. They are on **one** mine were measuring the two halves of the same 90. **The count that matters is 31**, and the gate
guest volume, by two binds. The disk claim is stated precisely in §4 below. later found the true figure is **32** (§4).
**A correction to my own §2 working.** In verifying claim 2 I reported that a literal grep matched
`:373`. It did not — it returned nothing, and what printed was the second grep in the same cell.
`**not** currently filed` does not match `not currently filed`, because of the markdown bold. The
claim still holds (I read the line), but the check I quoted was wrong — and that false zero turned out
to matter, see §3.
--- ---
## 2. The sweep — three counts, and the control that earned them ## 3. The population, and the rule used to sort it
**Method** (`scripts` not committed; it is a throwaway, and the terms below are the durable part): **The rule, stated so it need not be re-invented:**
enumerate every **survey-class** document — inventory / audit / spike / review / diagnosis / recon /
campaign / report / validation / rehearsal / walk / finding / spec / triage — under
`documentation/{audits,architecture,pilot,backlog}`, excluding runbooks, templates, READMEs,
CHANGELOGs and ROADMAP, because those instruct rather than conclude. For each, ask whether any
`OPEN-ITEMS.md` row cites it by filename, then search every line for the **shapes** an unfiled gap
takes.
| count | value | > **Does the item assert something about the shipped product that a reader could go and check, and
|---|---| > find false?** If yes it is a **finding** and belongs in the register. If it proposes something that
| **documents examined** | **113** (survey-class; 114 after this session added one) | > does not exist yet — a feature, a spike, a curation task — there is nothing to be wrong about, and
| **gaps found** (documents saying, in their own words, that something was not filed) | **14** statements | > it stays in the roadmap as an intention.
| **of those, already filed** | **2** — and the other 12 break down in §3 |
**The search terms, so the next person can widen them:** Two refinements the population forced: **an owed operator decision moves too** (it is work owed, not
an idea), and **an item already satisfied moves as CLOSED**, so nobody redoes it.
``` | group | count | disposition |
not (currently )?filed never filed no finding not a finding
unresolved unfiled disagreement no single action
nothing (does|routes|reads|consults) never been should be
ought to is not (currently )?(tested|proven|observed|covered|wired)
has (never|not) been (seen|observed|walked|proven|done)
no (test|row|register) (pins|covers|cites) TODO FIXME ⚠ not covered gap
```
Every word-gap in the first four patterns is `[*_`~ ]*`, i.e. **markdown emphasis is tolerated** —
see §3.
### The positive control — and it convicted the sweep twice before it convicted the corpus
**Plant → find → remove → fail to find**, on a scratch copy of the whole `documentation/` tree:
| step | strongest-shape hits |
|---|---|
| unplanted scratch copy | **14** |
| a synthetic gap planted, **bolded and on one line** | **15** — convicted by name and quoted back |
| scratch discarded, real corpus re-run | **14** |
**The control found two defects in my own sweep before it found anything in the corpus, and both were
false zeros of exactly the class this session is about:**
1. **Markdown emphasis broke the strongest pattern.** The single most important line in the corpus is
written `it is **not** currently filed as a finding`, and `not (currently )?filed` does not match
it. Fixed by tolerating `[*_`~ ]*` between words.
2. **The reporter re-searched a truncated copy of the line.** Hits were stored as `line[:200]`; that
line is a long table row and the phrase sits past column 200, so the detector caught it and the
**report** dropped it. Fixed by matching the full line and truncating only for display. This alone
moved the count **12 → 14**.
A sweep whose first two runs would have under-reported by 2 is exactly why the control is mandatory.
---
## 3. What the 14 were, judged
| verdict | n | which |
|---|---|---| |---|---|---|
| **never a gap** — a reasoned "no finding" | 4 | `_design-review.md:9` (nothing deferred), `CAMPAIGN-11:501` (a measurement was honest), and the two **explicit `### Not filed` sections** in `CAMPAIGN-10-two-storage-soak` and `SPIKE-recovery-unit-space` — each item disposed with a reason. **This is good practice, not a failure, and it is what the rest should look like.** | | **open work — findings and owed items** | **17** | **moved to the register**, keeping identifier, evidence and original filing date |
| **already filed** | 2 | `REPORT-DRILL-backup-truth:321` (its Part 5 became R-360 and R-364, filed the same session) and `REPORT-DRILL:569` (the hub's controller-version blindness — the register already covers it, 2 hits; not re-filed) | | **ideas and proposals that were never findings** | **15** | **stay in `ROADMAP.md`** — see below |
| **still open → NEWLY FILED** | 5 | R-371, R-372, R-373, R-374, R-375 | | **already closed** | **59** | stay as history in the roadmap |
| **the headline** | 3 | `_recovery-inventory:373`, `:1043` and `07-backup-architecture.md:337` — all three the same gap, and it turns out it **was** given a number |
### The headline: it was filed. In the other register. **Where the 15 ideas live, since "not the register" is not an answer:** they stay in **`ROADMAP.md`**,
which is exactly what that file says it is — *"the prioritized decision log of planned/open work…
The gap the 2026-08-21 drill rediscovered **was already numbered R-107**, with a full write-up: Items are intentions"*. The gate exempts them **by their own state word**, so they are not
second-class; they are correctly filed. They are R-6, R-8, R-9, R-12, R-14, R-19, R-34, R-45, R-46,
> `ROADMAP.md:122` — *"**No offsite action unpacks the named-volume tars Tier-3 captures on every R-56, R-58, R-62, R-65, R-69, R-72.
> run.** `ReconstituteFromOffsite` skips the unit outright … `PlaceOffsiteRestore` places it only when
> the live unit is ABSENT"* — **M, READY, 2026-07-28**
and cross-referenced twice in `07-backup-architecture.md` (`:337`, `:902`). **It is absent from
`OPEN-ITEMS.md`**, which opens with the words *"the single source of truth for open work"*.
**The dating makes it a rule violation, not a gap in the rules.** `OPEN-ITEMS.md` was created and the
template gained its *"single source of truth"* bullet on **2026-07-27** (`655b69f`). R-107 went into
ROADMAP alone on **2026-07-28** (`070b0ce`) — **the day after**.
**Measured scope: 72 `R-` ids are in ROADMAP and not in OPEN-ITEMS; 29 are not marked shipped, closed
or killed.** Most of the 29 are feature *ideas*, which arguably belong only in ROADMAP. A minority are
**findings**: R-30, R-31, R-32 (all P2-HIGH), R-35, R-40, R-76, R-79, R-25, R-49, R-10, R-107.
**The risk is not double-minting** — the two files share ids and OPEN-ITEMS' ceiling (368) is above
ROADMAP's (331). **The risk is rediscovery**: a session greps one file, finds nothing, and redoes the
work. That is precisely what happened, and it cost an evening and a night. **Filed as R-369, HIGH.**
--- ---
## 4. Part 1 — the record corrected ## 4. The migration — 17 items, and the oldest
### The specification Each moved **verbatim**, nothing added or reinterpreted; the roadmap keeps its copy marked
`MOVED -> OPEN-ITEMS.md` and is not deleted, because that file's job is history.
`SPEC-app-data-placement-2026-08-21.md` now opens with a marked **⚠ CORRECTED 2026-08-22** block and | id | originally filed | note |
carries four inline `[CORRECTED 2026-08-22]` marks. **Nothing was deleted; every measurement stands.** |---|---|---|
| **R-10** | **2026-07-15** | **the oldest — 38 days** (T-6E-1, dir-fsync asymmetry) |
| R-25, R-40, R-49 | 2026-07-19 | |
| R-30, R-31, R-32, R-35 | 2026-07-21 | three of them marked **[P2-HIGH]** |
| R-78 | 2026-07-25 | an **owed operator decision**, not a defect |
| R-76, R-79 | 2026-07-26 | |
| R-96 | 2026-07-27 | **moved as CLOSED** — both rules are committed at `workspace-CLAUDE.md:48-70` under a heading naming R-96 |
| R-102, R-104, R-105, **R-107** | 2026-07-28 | R-107 **moved as CLOSED** — shipped as R-354 on 2026-08-22 |
| **R-103** | 2026-07-28 | **found by the gate, not by me** — see §5 |
What it got wrong: it treated the absence of a storage field on 40 templates as a choice being denied. **Staleness named, not edited away**, as instructed:
The architecture states the rule and states it as **enforced**:
> *"**App data placement is per-volume, not per-app:** `.felhom.yml` classifies each volume **hot** - **R-104** — *partly stale.* The self-heal it calls unreachable **was built**: `resticStep`
> (DB/config/cache → fast storage, **enforced**) vs **bulk** (media/files → may be slow)."* escalates to `unlock --remove-all` and retries once (`offbox.go:763-768`), and `unlockStale` runs
> — `01-topology-and-trust.md:150-152` before every off-site run and restore (`:1274`). Its premise is also doubtful — the probe is
`restic cat config`, a read that takes no lock. **What remains true:** `ClassifyOffsiteFailure`
(`:179-193`) still has no lock case.
- **R-105** — *partly fixed by its own update.* The `drives` third was traced and populated
2026-07-28; the other two thirds were not re-verified and are carried as written.
The 40 are all-hot apps; the 13 are the bulk apps. **And the deploy page has been telling the customer **No halt.** I checked the three READY findings for urgency before migrating them quietly. R-104 is
exactly this the whole time** — `deploy.html:624-625`: *„A kiválasztott meghajtón az alkalmazás the one that reads urgent — *"the tier stays dead until a human runs `restic unlock --remove-all`"* —
**fájljai** (média, dokumentumok) tárolódnak. Az **adatbázis a gyors belső SSD-n** fut."* and **it is not**, for the reasons above. Nothing in the 31 is both open and urgent.
### The disk claim, stated precisely
- **REAL:** a **physical-disk failure** loses the app's data and its first-tier copy together. That is
what the off-site and whole-machine tiers exist for, and it is **equally true of a drive-resident
app**, whose unit sits beside its data on the drive deliberately so a restore needs the drive and
nothing else.
- **OVERSTATED:** a **full data volume stopping the operating system.** The OS rootfs is a separate
volume, and the capture floor refuses per app before exhaustion (`00-capability-map.md:94`).
Watched working 2026-08-21 with `/var/lib/felhom` at 99%: the reserve refused one app per run, told
the hub, and all 15 containers stayed healthy.
- **WITHDRAWN:** the comparison to Tier 2's same-disk refusal (`tier2.go:329`). Tier 2 refuses a
**second** copy on the same disk; Tier 1's unit is *meant* to sit beside the data.
### Rows re-framed
- **R-352** — measurements (1)–(4) all stand; the conclusions drawn from (1) and (3) are marked
re-framed. (2) is now filed on its own as **R-368**; (4) needs no ruling.
- **R-356** — **re-checked and it SURVIVES UNCHANGED, strengthened.** Because the 40-class correctly
has no `HDD_PATH`, a restore that reads that as *"the app is not installed"* is misreading a correct
configuration. One sentence in it that leaned on the old framing is corrected in place.
### My own errors, named — R-370
**Four instances, not three.** The prompt said three; the evidenced count is four, all authored
2026-08-21: SPEC §1, SPEC §2.3, SPEC §5, and R-352 point (3). Recorded as a **process** failure with
its mechanism — the register and live source were read, `documentation/architecture/` was not — and
the missing step is now in the template. Also written into `CONTEXT.md`.
--- ---
## 5. What still needs the operator's ruling — **from this specification, almost nothing** ## 5. The gate — and it convicted me before it convicted anything else
- **The placement question is ANSWERED and the answer is no.** Points 1, 2 and 3 of the spec's §5 are `scripts/one_register_gate.py`, wired into `scripts/repo_gates.py` as **`one-register`** (11 gates now,
withdrawn as a live question; hot data belongs where it is. all OK). It fails when a `ROADMAP.md` row is **neither an idea nor done** and has **no counterpart row
- **Point 4 is the only thing left**, and it is smaller than it looked — see R-368 below. in `OPEN-ITEMS.md`**. The predicate is the roadmap's **own state column**, so it reads data that
- **Point 5 needs no ruling.** The Drives count is honest; it is a wording question the design system already exists rather than asking anyone to maintain a new marker.
already owns.
**One new thing does want your ruling, and it is R-369:** whether the two registers become one, or **The control — plant → convict → remove → pass:**
whether a gate enforces that a not-done `ROADMAP` row has an `OPEN-ITEMS` counterpart. Triage the 29
first — most are ideas, a minority are findings. | step | result |
|---|---|
| baseline | **PASS** |
| plant an open roadmap-only row | **CONVICTED by name**, rc=1, quoted back |
| remove the plant | **PASS**, file byte-identical (md5) |
| plant an `idea` row | **not convicted** — the exemption is real, not a blanket pass |
**The control caught my control first.** The first plant did *not* convict, and the counts did not
move at all. Cause: **`ROADMAP.md` has no trailing newline**, so `>>` appended the row onto the last
line and it never started at column 0. A flaw in my test, not the gate. Third session running in which
the instrument caught the operator before the corpus.
**And the gate then caught three rows my hand-sort missed** — the reason is worth recording because it
is this project's most repeated shape: **my sorting regex matched the whole row, where a finding's
body routinely contains the word "shipped"; the gate matches the state cell only.**
- **R-103** (`READY — 2026-07-28`) — a genuine finding, mis-read by me as done. **Migrated.**
- **R-23** (`BANKED in full`) and **R-13** (`first slice PROVEN-LIVE`) — this project's own done-words,
which the gate did not know. Vocabulary extended; both are correctly exempt.
**And on the split it caught a genuine disagreement between the two files:** **R-203** and **R-163**
are recorded closed in the register and still open in the roadmap — R-203 even carries two
contradictory roadmap rows. The register is right in both cases; the roadmap copies are marked
`SUPERSEDED 2026-08-22` with the register's verdict.
### What the gate cannot see — named, not implied
1. **A finding filed with the state `idea` escapes.** The state column is a human judgement.
2. **A finding written in prose with no `R-` identifier escapes entirely** — this gate matches ids.
That is the previous session's sweep territory and the template rule *"an enumerated gap becomes a
row"*.
3. **A finding that never reaches the roadmap escapes.** Nothing here reads audits or spikes.
4. It checks a counterpart **exists**, never that the two agree. A stale register row passes.
--- ---
## 6. Part 4 — the three answers, and the finding is smaller and different than believed ## 6. Housekeeping — sizes before and after
**1. Is the default store consulted at deploy time, for the 13 apps that take a path? — YES.** | file | before | after | |
`internal/web/templates/deploy.html:612`: |---|---|---|---|
| `backlog/OPEN-ITEMS.md` | 691 lines / **672,376 B** | 594 lines / **327,109 B** | **−51%**, and it now holds open work only |
| `backlog/CLOSED-ITEMS.md` | — | 156 lines / 61,580 B | **new sibling**, 128 compressed closed entries |
| `backlog/ROADMAP.md` | 249 lines / **239,306 B** | 160 lines / **78,110 B** | **−67%** |
| `backlog/ROADMAP-HISTORY.md` | — | 116 lines / 28,683 B | **new sibling**, 105 finished items |
| `CONTEXT.md` | 2,580 lines / 217,260 B | 2,607 lines / 219,104 B | **deliberately not compressed** — §7 |
``` **A sibling, not the bottom of the file** — appending keeps the byte count and the scroll, which is
{{else if and .IsDefault (not .NotAllowed)}}selected{{end}} the thing being fixed. Closed work compressed to **17%** of its bytes in both files.
```
For a new deploy the default drive **is pre-selected**. `DeployStoragePath` embeds **Nothing was deleted.** Every compressed entry ends `full text: git show fddfe00ce268:<path>`.
`settings.StoragePath` (`web/handlers.go:89-99`), so `.IsDefault` resolves.
**So `// new apps use this by default` (`settings.go:453`) is IMPRECISE ABOUT THE MECHANISM, NOT **Load-bearing reasoning was not compressed away.** Rather than judge 134 entries by hand, the
FALSE.** The earlier claim — *"the deploy route never reads it"* — is **wrong**, and it is wrong for compressor **keeps any sentence stating a rule, a fence or a deliberate refusal, verbatim**, under
the same reason as everything else this week: the grep behind it **Reasoning kept** — 25 entries carry one. Spot-checked: R-320's kept sentence itself records that its
(`grep -nE 'GetDefaultStoragePath|primaryHDDPath|IsDefault' deploy.go manager.go`) **searched Go files rule is now standing rule 5 in `workspace-CLAUDE.md`, and R-110's rule is in `felhom.eu/CLAUDE.md`
and never the templates.** ("The installer publishes by TAG, not by push (R-110)") — **both already homed outside the register,
verified by grep with a negative control.**
**The residual, and it is the whole finding:** the default lives in the **template**, not the server. ### The compressor moved six rows it should not have — caught and reversed
`POST /api/stacks/<n>/deploy` takes `values` verbatim; omit `HDD_PATH` and `withPathVars`
(`stacks/deploy.go:584`) gets `""` and no default applies. **That is why the invariant has no test —
there is nothing server-side to test.** Filed **R-368, LOW**.
**2. What the label promises the customer, quoted:** `storage.html:469` — **`PARTLY CLOSED` and `OPEN — NOT FIXED` both matched a closed-vocabulary applied to the whole status
**„Legyen alapértelmezett új telepítéseknél"** ("Be the default for new installations"), with the field.** R-123, R-190, R-214, R-264, R-295 and **R-352** were moved out of the register. Caught by a
badge **„Alapértelmezett"** at `:34`. **The promise is kept**: it is the default for new installs, follow-up check in the same session and **restored verbatim from commit `fddfe00ce268`** — not from
which is exactly what the pre-selection does. It does not promise that every app's data goes there. the compressed form, because an open row keeps its detail. **The same bug, in the same session, as the
one the gate had.** Filed as **R-378** so the next person to write a status predicate reaches for the
**3. What the Drives count counts:** `countAppsUsingPath` (`web/handlers.go:2162-2175`) counts leading verdict rather than the whole field.
deployed apps where `appCfg.Env["HDD_PATH"] == storagePath`. **A named-volume app has no `HDD_PATH`,
so it can never be counted — by construction, not by accident.** The number is honest; it means *"apps
that place bulk data on this drive"*, not *"apps using storage"*. Worth one sentence of wording, not a
ruling.
--- ---
## 7. Part 3 — the template's two new rules ## 7. Why `CONTEXT.md` was not compressed — a disagreement with the task, stated
**It had neither.** `enumerat` → 0 hits, `register row` → 0 hits; `architecture` appeared 9 times but **86% of that file — 187,913 of 217,260 bytes — is one `## Standing rulings` section**, carrying 39
§4 item 4 named only `02-controller-module-map.md`, and the S-1 rule at the end governs *updating* a `S-` ids and 153 bullets under a single heading. **Standing rulings are live reasoning, not finished
design doc, not *reading* one first. **No halt.** There is no separate authoring companion. work.**
**Rule 1, in §4 where files are read** — abridged; the full text carries a file→area table for all **This prompt's own §3.4 says the decision log is *"dated, never edited afterwards"*** and is *"the
eight architecture documents: only place that answers 'has this been proposed before, and why did we say no?'"*. Compressing it
destroys exactly that, and there is no per-ruling delimiter, so a mechanical split risks cutting a
live ruling from its reason — **the failure this whole arc is correcting.** 13 mentions of
`SUPERSEDED` sit inside that blob and cannot be separated from live text safely today.
> **THE ARCHITECTURE DOCUMENT FOR THE AREA THIS TASK TOUCHES — NAME IT AND SAY WHAT IT SAYS.** Not **So the problem is navigational, not volumetric, and the fix is structural:** give each ruling a
> "read the architecture folder": name the file, and state in one line what it rules about this area. sub-heading with its `S-` id and date, and it becomes linkable and findable **without a word being
> **A prompt that cannot name one says so explicitly, and that absence is itself recorded** — an edited**. Filed as **R-377 (LOW)** rather than done, because it is a careful pass of its own.
> undocumented architectural decision is how a deliberate design gets "fixed" by someone who did not
> know it was one. The file grew by 1,844 bytes — the new decision-log entry in §8.
---
## 8. Where the hot/bulk decision was recorded — **nowhere. That is the finding.**
Established by reading, not by citation: it exists as **one unmarked bullet** at
`architecture/01-topology-and-trust.md:150-152`. **No dated entry in the decision log, no `R-` row, no
record of when it was taken.** The only mention in `CONTEXT.md` before today was the entry I wrote
*yesterday about failing to read it*.
Meanwhile its **consequence** — 40 of 53 templates declare no path — is marked **[FACT]** at
`07-backup-architecture.md:296-299`. **A reader met a marked observation beside an unmarked choice**,
and reasonably asked whether it should be so. Four times.
**Marker usage, measured** (the prompt said "three times"; the real figure is 45):
| | before | after |
|---|---|---|
| documents carrying the legend | **1 of 8** (`07`: 10 `[DESIGN]`, 35 `[FACT]`) | **8 of 8** |
**Done:**
1. **The legend is carried into all seven** other architecture documents — same wording, no third
marker invented — each stating explicitly that **an unmarked statement means *not yet classified*,
never *observed***.
2. **The hot/bulk split is marked `[DESIGN]`**, with a pointer that is honest about what it can point
at: the decision has **no original date on record**, and the new log entry exists *to give it a
home, not to claim it was decided then*.
3. **A decision-log entry** in `CONTEXT.md` — what was chosen, what follows from it, and **what was
rejected** (moving all-hot data to the data drive), so the same proposal does not return.
**Deliberately not done, and filed:** the existing statements in those seven documents were **not**
swept into one marker or the other. A wrong mark is worse than none. **R-376 (MEDIUM)** records the
remainder and the rule: mark what a session touches.
---
## 9. The template's closing step, as written
It had none — `compress` → 0 hits, `CLOSED-ITEMS` → 0, `size before` → 0, with a negative control.
Added as **§N.7**, abridged here:
> **N.7 Housekeeping — before the report, not after (2026-08-22 ruling)**
> >
> **Three sources, in this order, before any claim: the architecture folder holds the REASONING, the > 1. **Compress what this session closed.** A closed row keeps its title, the version it shipped in,
> register holds the WORK, source holds the TRUTH.** > 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
> **And the test that catches it: _is what I am about to call a defect something we chose?_** If it > `git show` returns the full original text. **Open rows are not touched — their detail is doing a
> was chosen and the choice is wrong, that is **a proposal to change a decision** — it goes to the > job.**
> operator as a decision, not filed as a bug. **Cost of learning this (R-370):** between 19 and 22 > 2. **Rehome live reasoning before compressing it away.** … **Where it is a decision, mark the
> August a documented placement decision was called a defect in four places. > resulting shape `[DESIGN]` in the architecture document and point it at the log entry.**
> **Losing a reason is how a deliberate design becomes a bug in someone's eyes** — that cost four
**Rule 2, on the `OPEN-ITEMS.md` bullet, where a survey session lands:** > 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
> **AN ENUMERATED GAP BECOMES A ROW, IN THE SAME SESSION. PROSE IS NOT A RECORD.** > makes growth visible; prose about tidiness is not a mechanism.
>
> This binds **surveys, inventories, spikes, reviews and diagnoses**, not only implementation
> sessions… 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** … 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 … was enumerated on **2026-07-28** … and never entered here. **It was rediscovered from
> scratch 25 days later by an overnight drill**, and shipped as R-354.
Two rules, one place each, ~40 lines added to a 521-line document.
--- ---
## 8. Rows opened — ceiling R-367 → **R-375** ## 10. Part 4.2 — the storage default
| id | rank | what | first written down | age | **Confirmed correctly filed.** **R-368**, rank **LOW**, open, and it states the corrected position:
|---|---|---|---|---| the default **does** apply at deploy time (`deploy.html:612` pre-selects `.IsDefault`), the residual
| **R-368** | LOW | The storage default **does** apply at deploy time (`deploy.html:612`); the earlier "never reads it" was wrong. Residual: the default lives in the template, not the server, so the API has none and nothing server-side can be tested | 2026-08-21 (as the wrong claim) | 1 d | is that it lives in the **template, not the server**, so the API has no default and there is nothing
| **R-369** | **HIGH** | **Two registers.** 72 ids in ROADMAP only, 29 not done, some of them findings. R-107 sat there 25 days and was rediscovered by a drill | 2026-07-28 | **25 d** | server-side to test.
| **R-370** | CLOSED | Process: a documented decision called a defect four times; architecture folder never read. Rule now in the template | 2026-08-19 | 3 d |
| **R-371** | LOW | The off-site tier is the only tier that announces nothing on success | 2026-08-05 | 17 d |
| **R-372** | LOW | A Tier-2 copy that has **never** been produced (source missing) is not surfaced distinctly | **2026-07-15** | **38 d — the oldest** |
| **R-373** | LOW | `SysDataGrowGB` is the intended sizing lever, it works, and nothing sets it | 2026-08-02 | 20 d |
| **R-374** | LOW | Three C1 refusal cases judged borderline, left unfiled **and never named**, so nobody can re-judge them | 2026-08-08 | 14 d |
| **R-375** | LOW | A PBS datastore audit signal noted and explicitly not filed | 2026-08-18 | 4 d |
**Oldest gap recovered: R-372, written down 2026-07-15, 38 days.** **No row anywhere still asserts the default never applies.** The one occurrence of *"the deploy route
**Most consequential: R-369**, because it explains the other seven. never reads it"* in the register is inside R-368's own quotation of the claim it corrects; the SPEC
carries `[CORRECTED 2026-08-22]` blocks; `grep` across the register, `CLOSED-ITEMS.md` and the SPEC
returns nothing else.
--- ---
## 9. What was dropped, and observations ## 11. Rows and ceiling
**Dropped: nothing.** Parts 1, 2, 3 and 4 all completed. Part 4 was droppable-first and was done. **Opened:** R-376 (MEDIUM), R-377 (LOW), R-378 (CLOSED same session).
**Migrated in, keeping their original identifiers and dates:** R-10, R-25, R-30, R-31, R-32, R-35,
R-40, R-49, R-76, R-78, R-79, R-96 (closed), R-102, R-103, R-104, R-105, R-107 (closed).
**Restored after a compressor error:** R-123, R-190, R-214, R-264, R-295, R-352.
**Ceiling R-375 → R-378.**
---
## 12. What was dropped, and observations
**Dropped: nothing from Parts 1–4.** Part 4 (droppable first) and Part 3 both completed.
**One thing deliberately not done and argued rather than obeyed:** compressing `CONTEXT.md` — §7,
filed as R-377.
**Observations — noticed, not acted on:** **Observations — noticed, not acted on:**
- **The two `### Not filed` sections are the model.** `CAMPAIGN-10-two-storage-soak:383` and - **`ROADMAP.md` has no trailing newline.** It broke my own gate control and would break any future
`SPIKE-recovery-unit-space:228` list what they decided not to file **and why, item by item**. That `>>` append. One byte; not fixed here because it belongs to whoever next edits that file
is a disposition, not a shrug. If the new rule needs an exemplar, those are it. deliberately.
- **`07-backup-architecture.md:337` already names this gap and points at R-107**, so the architecture - **R-203 has two contradictory rows in the roadmap** — one open, one shipped. Only the open one was
doc was right and current while the register was empty. The document was not the weak link. marked superseded; the duplicate remains as history.
- **The `_recovery-inventory` cites `07-backup-architecture.md:81` for a phrase that is no longer - **The migrated rows are large.** Seventeen verbatim roadmap rows are now in the register, which is
there** (`grep 'missing-only merge'` → only the inventory's own copy). A stale line-number citation; why it fell 51% rather than further. They are open work and keep their detail, by the rule.
not filed, because the doc it points into has since been rewritten wholesale and the claim it - **`OPEN-ITEMS.md` is still 327 KB.** The remaining bulk is open rows with long evidence sections —
supported is now handled by R-354. that is detail doing a job, and the next reduction comes from closing work, not from editing it.
- **The sweep's "cited by no row" count (89) is not a defect count.** Many audits are cited by the - **The one-register gate compares existence, not content.** R-203/R-163 were caught only because the
capability map, by `where-felhom-stands.yaml` or by other reports rather than by a register row. It register had *no* row for them after the split; a stale-but-present row would pass. Named in the
is a *candidate* filter, and it earned its keep only in combination with the shape search. gate's own docstring as residual hole 4.
- **`REPORT-hub-blindness.md` exists at the repo root and is cited by no register row**, but the
register does cover its subject (2 hits). Left alone.
---
## 10. CI, confirmed by ID
`felhom.eu` **id=382 / run_number=250**, head `091a4b74` — **success**. Commit `091a4b7`, pushed to
`main`, all 10 gates OK, tree clean. No other repo was touched.
+20
View File
@@ -398,6 +398,26 @@ 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.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 ## 11. Tests
@@ -1,5 +1,20 @@
# 00 — Felhom Capability Map # 00 — Felhom Capability Map
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> **What this is:** the single cross-component truth table of what the Felhom platform can do > **What this is:** the single cross-component truth table of what the Felhom platform can do
> **today**, at what confidence level, with verifiable evidence. Rows are *scenarios* (user- or > **today**, at what confidence level, with verifiable evidence. Rows are *scenarios* (user- or
> operator-visible outcomes), not modules — a scenario spans agent + controller + hub + catalog, > operator-visible outcomes), not modules — a scenario spans agent + controller + hub + catalog,
@@ -1,5 +1,20 @@
# Felhom Controller Architecture — Part 1: Topology & Trust # Felhom Controller Architecture — Part 1: Topology & Trust
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
**Status:** draft (decisions from the topology/trust design sessions). **Status:** draft (decisions from the topology/trust design sessions).
**Platform facts** referenced here live in `docs/proxmox-platform.md`; this document **Platform facts** referenced here live in `docs/proxmox-platform.md`; this document
records *Felhom's decisions*, not Proxmox behaviour. records *Felhom's decisions*, not Proxmox behaviour.
@@ -147,9 +162,23 @@ credentials.
at attach), role, encrypted credentials, schedule/retention. The agent creates the Proxmox at attach), role, encrypted credentials, schedule/retention. The agent creates the Proxmox
storages, continuously checks presence/reachability, and reports per-target status (a storages, continuously checks presence/reachability, and reports per-target status (a
disconnected target → actionable notification). disconnected target → actionable notification).
- **App data placement is per-volume, not per-app:** `.felhom.yml` classifies each volume - **[DESIGN] App data placement is per-volume, not per-app:** `.felhom.yml` classifies each volume
**hot** (DB/config/cache → fast storage, enforced) vs **bulk** (media/files → may be slow). **hot** (DB/config/cache → fast storage, enforced) vs **bulk** (media/files → may be slow).
A photo app's DB stays on SSD while its blobs go to the USB. A photo app's DB stays on SSD while its blobs go to the USB.
> **Marked [DESIGN] on 2026-08-22 (R-376), and the pointer is honest about what it can point at.**
> **This decision was never recorded as a decision anywhere** — it was established by reading, not
> by citation: it exists as this bullet and nowhere else, with no dated entry in the decision log
> and no `R-` row. A log entry was written on 2026-08-22 (`CONTEXT.md`, "App data placement is a
> DECISION") **to give it a home, not to claim it was decided then**; the choice is older than the
> entry and its original date is not on record.
>
> **What being unmarked cost.** The consequence of this bullet — that 40 of 53 catalogue templates
> declare no configurable path because they are all-hot — is stated as **[FACT]** at
> `07-backup-architecture.md:296-299`. A reader met a marked observation beside an unmarked choice
> and reasonably asked whether it *should* be so. **Between 19 and 22 August that reader called this
> decision a defect in four places** (R-370), and one session's work went into correcting the record
> rather than into the product.
- **Backup scoping:** hot data (LXC rootfs) rides the guest `vzdump` → tiers + PBS. Bulk data - **Backup scoping:** hot data (LXC rootfs) rides the guest `vzdump` → tiers + PBS. Bulk data
on external mount points is **excluded** from the guest vzdump (per-mount `backup` flag) and on external mount points is **excluded** from the guest vzdump (per-mount `backup` flag) and
gets its own per-volume policy (file-level to a tier, slower cadence — or explicitly *not* gets its own per-volume policy (file-level to a tier, slower cadence — or explicitly *not*
@@ -1,5 +1,20 @@
# Felhom Controller Architecture — Part 2: Controller Module Map # Felhom Controller Architecture — Part 2: Controller Module Map
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> **EXECUTED (slice 8C, 2026-06-10 — controller v0.37.0).** This map's target state is now realized: > **EXECUTED (slice 8C, 2026-06-10 — controller v0.37.0).** This map's target state is now realized:
> the disk-execution subsystem (`storage/*`, restic, cross-drive, drive-restore, `disk_layout`, > the disk-execution subsystem (`storage/*`, restic, cross-drive, drive-restore, `disk_layout`,
> `local_infra`, `infra_backup`, `setup/scanner`, `monitor/watchdog`+`pinger`, the storage UI) is > `local_infra`, `infra_backup`, `setup/scanner`, `monitor/watchdog`+`pinger`, the storage UI) is
@@ -1,5 +1,20 @@
# Architecture Part 3 — The Host Agent # Architecture Part 3 — The Host Agent
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> Status: design draft (decision content). To be grounded by Claude Code against > Status: design draft (decision content). To be grounded by Claude Code against
> `docs/proxmox-platform.md` and `docs/architecture/02-controller-module-map.md`, > `docs/proxmox-platform.md` and `docs/architecture/02-controller-module-map.md`,
> then placed at `docs/architecture/03-host-agent.md`. > then placed at `docs/architecture/03-host-agent.md`.
@@ -1,5 +1,20 @@
# Architecture Part 4 — Control-plane authorization (operator signing) # Architecture Part 4 — Control-plane authorization (operator signing)
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> Status: design draft (decision content), grounded on `docs/tests/phase4-signing-findings.md`. > Status: design draft (decision content), grounded on `docs/tests/phase4-signing-findings.md`.
> To be reviewed by Claude Code against that spike + `03` §4, then placed at > To be reviewed by Claude Code against that spike + `03` §4, then placed at
> `docs/architecture/04-control-plane-authorization.md`. > `docs/architecture/04-control-plane-authorization.md`.
@@ -1,5 +1,20 @@
# Architecture Part 5 — The Hub # Architecture Part 5 — The Hub
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> Status: design draft (decision content). To be validated by Claude Code against the **actual > Status: design draft (decision content). To be validated by Claude Code against the **actual
> felhom-hub source** (`felhom.eu` repo, `hub/`) + Parts 01–04, then placed at > felhom-hub source** (`felhom.eu` repo, `hub/`) + Parts 01–04, then placed at
> `docs/architecture/05-hub-architecture.md`. > `docs/architecture/05-hub-architecture.md`.
@@ -1,5 +1,20 @@
# Architecture Part 6 — Offsite Connectivity (the backup transport) # Architecture Part 6 — Offsite Connectivity (the backup transport)
> **How to read this document.** Two kinds of statement appear, and where this document marks them it
> marks them like this — the same wording as `07-backup-architecture.md:11-17`, carried here on
> 2026-08-22 (R-376) so a reader meets one convention and not eight:
>
> - **[DESIGN]** — a decision taken. Not derived from code; the code may not implement it yet.
> - **[FACT]** — an observed property, carrying a `file:line`, a live command output or a citation.
>
> **Statements in this document are NOT yet all marked.** Marking them wholesale is a large judgement
> exercise and a wrong mark is worse than none, so only what a session touches is marked (R-376).
> **An unmarked statement therefore means "not yet classified", never "observed".** That ambiguity is
> exactly what cost this project three sessions in August 2026: the hot/bulk placement decision sat
> unmarked beside a marked `[FACT]`, and was read as an observation and reported as a defect.
> Status: **design-of-record** (2026-07-03). Records the settled offsite-backup-transport > Status: **design-of-record** (2026-07-03). Records the settled offsite-backup-transport
> decisions; grounded against felhom.eu @ `bf099f6` and felhom-agent @ `4ba1b14` (v0.63.0). > decisions; grounded against felhom.eu @ `bf099f6` and felhom-agent @ `4ba1b14` (v0.63.0).
> Evidence base: `documentation/audits/SPIKE-connectivity-wireguard-2026-07-03.md` (all > Evidence base: `documentation/audits/SPIKE-connectivity-wireguard-2026-07-03.md` (all
@@ -0,0 +1,308 @@
# REPORT — correcting what we mis-called a defect, and finding what we wrote down and never filed (2026-08-22)
**Documentation and survey only. No code, no version bump, no bake, no deploy, no machine contacted.**
The previous report is preserved at `documentation/audits/REPORT-v0.218.0-r354-r355-2026-08-22.md`.
---
## 1. §2's four claims — ALL FOUR HELD. No halt.
| # | claim | verdict |
|---|---|---|
| 1 | `07-backup-architecture.md:297` — 53 templates, 52 named volumes, exactly 13 with a `backup:` block, and those 13 are exactly the ones that bind a configurable path | **HOLDS**, at `:296-299`. The heading above it reads *"Coverage per app class — and an unresolved count"*, which the sweep then followed |
| 2 | `_recovery-inventory-2026-07-28.md` Tier-3 row — *"…unpacked by no offsite action… no single action does it and no UI routes it. This is my own enumeration; it is not currently filed as a finding."* | **HOLDS**, verbatim, at `:373` |
| 3 | `:392-400` — `rootfs`, `mp0 /var/lib/docker`, `mp1 /mnt/sys_drive` as separate volumes | **HOLDS**, at `:392-396` — **but it describes a layout no current box has.** See below |
| 4 | `00-capability-map.md:94` — one data volume; a local backup bounded by free space | **HOLDS**, at `:94` |
**Claims 3 and 4 describe different layouts, six days apart, and 4 is the live one.** The inventory
(2026-07-28) records the pre-R-165 split. `felhom-agent/configs/build-golden.sh:29-40` — live source —
bakes **ONE** data volume at `/var/lib/felhom`, with `/var/lib/docker` and `/mnt/sys_drive` as two
**binds of that same volume**, and states at `:99` that *"There is deliberately no mp1"*.
**So the prompt's own §1.2 instruction is out of date:** it asked me to say that named volumes and
first-tier backups are *"on two different guest volumes on one physical disk"*. They are on **one**
guest volume, by two binds. The disk claim is stated precisely in §4 below.
**A correction to my own §2 working.** In verifying claim 2 I reported that a literal grep matched
`:373`. It did not — it returned nothing, and what printed was the second grep in the same cell.
`**not** currently filed` does not match `not currently filed`, because of the markdown bold. The
claim still holds (I read the line), but the check I quoted was wrong — and that false zero turned out
to matter, see §3.
---
## 2. The sweep — three counts, and the control that earned them
**Method** (`scripts` not committed; it is a throwaway, and the terms below are the durable part):
enumerate every **survey-class** document — inventory / audit / spike / review / diagnosis / recon /
campaign / report / validation / rehearsal / walk / finding / spec / triage — under
`documentation/{audits,architecture,pilot,backlog}`, excluding runbooks, templates, READMEs,
CHANGELOGs and ROADMAP, because those instruct rather than conclude. For each, ask whether any
`OPEN-ITEMS.md` row cites it by filename, then search every line for the **shapes** an unfiled gap
takes.
| count | value |
|---|---|
| **documents examined** | **113** (survey-class; 114 after this session added one) |
| **gaps found** (documents saying, in their own words, that something was not filed) | **14** statements |
| **of those, already filed** | **2** — and the other 12 break down in §3 |
**The search terms, so the next person can widen them:**
```
not (currently )?filed never filed no finding not a finding
unresolved unfiled disagreement no single action
nothing (does|routes|reads|consults) never been should be
ought to is not (currently )?(tested|proven|observed|covered|wired)
has (never|not) been (seen|observed|walked|proven|done)
no (test|row|register) (pins|covers|cites) TODO FIXME ⚠ not covered gap
```
Every word-gap in the first four patterns is `[*_`~ ]*`, i.e. **markdown emphasis is tolerated** —
see §3.
### The positive control — and it convicted the sweep twice before it convicted the corpus
**Plant → find → remove → fail to find**, on a scratch copy of the whole `documentation/` tree:
| step | strongest-shape hits |
|---|---|
| unplanted scratch copy | **14** |
| a synthetic gap planted, **bolded and on one line** | **15** — convicted by name and quoted back |
| scratch discarded, real corpus re-run | **14** |
**The control found two defects in my own sweep before it found anything in the corpus, and both were
false zeros of exactly the class this session is about:**
1. **Markdown emphasis broke the strongest pattern.** The single most important line in the corpus is
written `it is **not** currently filed as a finding`, and `not (currently )?filed` does not match
it. Fixed by tolerating `[*_`~ ]*` between words.
2. **The reporter re-searched a truncated copy of the line.** Hits were stored as `line[:200]`; that
line is a long table row and the phrase sits past column 200, so the detector caught it and the
**report** dropped it. Fixed by matching the full line and truncating only for display. This alone
moved the count **12 → 14**.
A sweep whose first two runs would have under-reported by 2 is exactly why the control is mandatory.
---
## 3. What the 14 were, judged
| verdict | n | which |
|---|---|---|
| **never a gap** — a reasoned "no finding" | 4 | `_design-review.md:9` (nothing deferred), `CAMPAIGN-11:501` (a measurement was honest), and the two **explicit `### Not filed` sections** in `CAMPAIGN-10-two-storage-soak` and `SPIKE-recovery-unit-space` — each item disposed with a reason. **This is good practice, not a failure, and it is what the rest should look like.** |
| **already filed** | 2 | `REPORT-DRILL-backup-truth:321` (its Part 5 became R-360 and R-364, filed the same session) and `REPORT-DRILL:569` (the hub's controller-version blindness — the register already covers it, 2 hits; not re-filed) |
| **still open → NEWLY FILED** | 5 | R-371, R-372, R-373, R-374, R-375 |
| **the headline** | 3 | `_recovery-inventory:373`, `:1043` and `07-backup-architecture.md:337` — all three the same gap, and it turns out it **was** given a number |
### The headline: it was filed. In the other register.
The gap the 2026-08-21 drill rediscovered **was already numbered R-107**, with a full write-up:
> `ROADMAP.md:122` — *"**No offsite action unpacks the named-volume tars Tier-3 captures on every
> run.** `ReconstituteFromOffsite` skips the unit outright … `PlaceOffsiteRestore` places it only when
> the live unit is ABSENT"* — **M, READY, 2026-07-28**
and cross-referenced twice in `07-backup-architecture.md` (`:337`, `:902`). **It is absent from
`OPEN-ITEMS.md`**, which opens with the words *"the single source of truth for open work"*.
**The dating makes it a rule violation, not a gap in the rules.** `OPEN-ITEMS.md` was created and the
template gained its *"single source of truth"* bullet on **2026-07-27** (`655b69f`). R-107 went into
ROADMAP alone on **2026-07-28** (`070b0ce`) — **the day after**.
**Measured scope: 72 `R-` ids are in ROADMAP and not in OPEN-ITEMS; 29 are not marked shipped, closed
or killed.** Most of the 29 are feature *ideas*, which arguably belong only in ROADMAP. A minority are
**findings**: R-30, R-31, R-32 (all P2-HIGH), R-35, R-40, R-76, R-79, R-25, R-49, R-10, R-107.
**The risk is not double-minting** — the two files share ids and OPEN-ITEMS' ceiling (368) is above
ROADMAP's (331). **The risk is rediscovery**: a session greps one file, finds nothing, and redoes the
work. That is precisely what happened, and it cost an evening and a night. **Filed as R-369, HIGH.**
---
## 4. Part 1 — the record corrected
### The specification
`SPEC-app-data-placement-2026-08-21.md` now opens with a marked **⚠ CORRECTED 2026-08-22** block and
carries four inline `[CORRECTED 2026-08-22]` marks. **Nothing was deleted; every measurement stands.**
What it got wrong: it treated the absence of a storage field on 40 templates as a choice being denied.
The architecture states the rule and states it as **enforced**:
> *"**App data placement is per-volume, not per-app:** `.felhom.yml` classifies each volume **hot**
> (DB/config/cache → fast storage, **enforced**) vs **bulk** (media/files → may be slow)."*
> — `01-topology-and-trust.md:150-152`
The 40 are all-hot apps; the 13 are the bulk apps. **And the deploy page has been telling the customer
exactly this the whole time** — `deploy.html:624-625`: *„A kiválasztott meghajtón az alkalmazás
**fájljai** (média, dokumentumok) tárolódnak. Az **adatbázis a gyors belső SSD-n** fut."*
### The disk claim, stated precisely
- **REAL:** a **physical-disk failure** loses the app's data and its first-tier copy together. That is
what the off-site and whole-machine tiers exist for, and it is **equally true of a drive-resident
app**, whose unit sits beside its data on the drive deliberately so a restore needs the drive and
nothing else.
- **OVERSTATED:** a **full data volume stopping the operating system.** The OS rootfs is a separate
volume, and the capture floor refuses per app before exhaustion (`00-capability-map.md:94`).
Watched working 2026-08-21 with `/var/lib/felhom` at 99%: the reserve refused one app per run, told
the hub, and all 15 containers stayed healthy.
- **WITHDRAWN:** the comparison to Tier 2's same-disk refusal (`tier2.go:329`). Tier 2 refuses a
**second** copy on the same disk; Tier 1's unit is *meant* to sit beside the data.
### Rows re-framed
- **R-352** — measurements (1)–(4) all stand; the conclusions drawn from (1) and (3) are marked
re-framed. (2) is now filed on its own as **R-368**; (4) needs no ruling.
- **R-356** — **re-checked and it SURVIVES UNCHANGED, strengthened.** Because the 40-class correctly
has no `HDD_PATH`, a restore that reads that as *"the app is not installed"* is misreading a correct
configuration. One sentence in it that leaned on the old framing is corrected in place.
### My own errors, named — R-370
**Four instances, not three.** The prompt said three; the evidenced count is four, all authored
2026-08-21: SPEC §1, SPEC §2.3, SPEC §5, and R-352 point (3). Recorded as a **process** failure with
its mechanism — the register and live source were read, `documentation/architecture/` was not — and
the missing step is now in the template. Also written into `CONTEXT.md`.
---
## 5. What still needs the operator's ruling — **from this specification, almost nothing**
- **The placement question is ANSWERED and the answer is no.** Points 1, 2 and 3 of the spec's §5 are
withdrawn as a live question; hot data belongs where it is.
- **Point 4 is the only thing left**, and it is smaller than it looked — see R-368 below.
- **Point 5 needs no ruling.** The Drives count is honest; it is a wording question the design system
already owns.
**One new thing does want your ruling, and it is R-369:** whether the two registers become one, or
whether a gate enforces that a not-done `ROADMAP` row has an `OPEN-ITEMS` counterpart. Triage the 29
first — most are ideas, a minority are findings.
---
## 6. Part 4 — the three answers, and the finding is smaller and different than believed
**1. Is the default store consulted at deploy time, for the 13 apps that take a path? — YES.**
`internal/web/templates/deploy.html:612`:
```
{{else if and .IsDefault (not .NotAllowed)}}selected{{end}}
```
For a new deploy the default drive **is pre-selected**. `DeployStoragePath` embeds
`settings.StoragePath` (`web/handlers.go:89-99`), so `.IsDefault` resolves.
**So `// new apps use this by default` (`settings.go:453`) is IMPRECISE ABOUT THE MECHANISM, NOT
FALSE.** The earlier claim — *"the deploy route never reads it"* — is **wrong**, and it is wrong for
the same reason as everything else this week: the grep behind it
(`grep -nE 'GetDefaultStoragePath|primaryHDDPath|IsDefault' deploy.go manager.go`) **searched Go files
and never the templates.**
**The residual, and it is the whole finding:** the default lives in the **template**, not the server.
`POST /api/stacks/<n>/deploy` takes `values` verbatim; omit `HDD_PATH` and `withPathVars`
(`stacks/deploy.go:584`) gets `""` and no default applies. **That is why the invariant has no test —
there is nothing server-side to test.** Filed **R-368, LOW**.
**2. What the label promises the customer, quoted:** `storage.html:469` —
**„Legyen alapértelmezett új telepítéseknél"** ("Be the default for new installations"), with the
badge **„Alapértelmezett"** at `:34`. **The promise is kept**: it is the default for new installs,
which is exactly what the pre-selection does. It does not promise that every app's data goes there.
**3. What the Drives count counts:** `countAppsUsingPath` (`web/handlers.go:2162-2175`) counts
deployed apps where `appCfg.Env["HDD_PATH"] == storagePath`. **A named-volume app has no `HDD_PATH`,
so it can never be counted — by construction, not by accident.** The number is honest; it means *"apps
that place bulk data on this drive"*, not *"apps using storage"*. Worth one sentence of wording, not a
ruling.
---
## 7. Part 3 — the template's two new rules
**It had neither.** `enumerat` → 0 hits, `register row` → 0 hits; `architecture` appeared 9 times but
§4 item 4 named only `02-controller-module-map.md`, and the S-1 rule at the end governs *updating* a
design doc, not *reading* one first. **No halt.** There is no separate authoring companion.
**Rule 1, in §4 where files are read** — abridged; the full text carries a file→area table for all
eight architecture documents:
> **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.
>
> **Three sources, in this order, before any claim: the architecture folder holds the REASONING, the
> register holds the WORK, source holds the TRUTH.**
>
> **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.
**Rule 2, on the `OPEN-ITEMS.md` bullet, where a survey session lands:**
> **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… 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** … 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 … was enumerated on **2026-07-28** … and never entered here. **It was rediscovered from
> scratch 25 days later by an overnight drill**, and shipped as R-354.
Two rules, one place each, ~40 lines added to a 521-line document.
---
## 8. Rows opened — ceiling R-367 → **R-375**
| id | rank | what | first written down | age |
|---|---|---|---|---|
| **R-368** | LOW | The storage default **does** apply at deploy time (`deploy.html:612`); the earlier "never reads it" was wrong. Residual: the default lives in the template, not the server, so the API has none and nothing server-side can be tested | 2026-08-21 (as the wrong claim) | 1 d |
| **R-369** | **HIGH** | **Two registers.** 72 ids in ROADMAP only, 29 not done, some of them findings. R-107 sat there 25 days and was rediscovered by a drill | 2026-07-28 | **25 d** |
| **R-370** | CLOSED | Process: a documented decision called a defect four times; architecture folder never read. Rule now in the template | 2026-08-19 | 3 d |
| **R-371** | LOW | The off-site tier is the only tier that announces nothing on success | 2026-08-05 | 17 d |
| **R-372** | LOW | A Tier-2 copy that has **never** been produced (source missing) is not surfaced distinctly | **2026-07-15** | **38 d — the oldest** |
| **R-373** | LOW | `SysDataGrowGB` is the intended sizing lever, it works, and nothing sets it | 2026-08-02 | 20 d |
| **R-374** | LOW | Three C1 refusal cases judged borderline, left unfiled **and never named**, so nobody can re-judge them | 2026-08-08 | 14 d |
| **R-375** | LOW | A PBS datastore audit signal noted and explicitly not filed | 2026-08-18 | 4 d |
**Oldest gap recovered: R-372, written down 2026-07-15, 38 days.**
**Most consequential: R-369**, because it explains the other seven.
---
## 9. What was dropped, and observations
**Dropped: nothing.** Parts 1, 2, 3 and 4 all completed. Part 4 was droppable-first and was done.
**Observations — noticed, not acted on:**
- **The two `### Not filed` sections are the model.** `CAMPAIGN-10-two-storage-soak:383` and
`SPIKE-recovery-unit-space:228` list what they decided not to file **and why, item by item**. That
is a disposition, not a shrug. If the new rule needs an exemplar, those are it.
- **`07-backup-architecture.md:337` already names this gap and points at R-107**, so the architecture
doc was right and current while the register was empty. The document was not the weak link.
- **The `_recovery-inventory` cites `07-backup-architecture.md:81` for a phrase that is no longer
there** (`grep 'missing-only merge'` → only the inventory's own copy). A stale line-number citation;
not filed, because the doc it points into has since been rewritten wholesale and the claim it
supported is now handled by R-354.
- **The sweep's "cited by no row" count (89) is not a defect count.** Many audits are cited by the
capability map, by `where-felhom-stands.yaml` or by other reports rather than by a register row. It
is a *candidate* filter, and it earned its keep only in combination with the shape search.
- **`REPORT-hub-blindness.md` exists at the repo root and is cited by no register row**, but the
register does cover its subject (2 hits). Left alone.
---
## 10. CI, confirmed by ID
`felhom.eu` **id=382 / run_number=250**, head `091a4b74` — **success**. Commit `091a4b7`, pushed to
`main`, all 10 gates OK, tree clean. No other repo was touched.
+156
View File
@@ -0,0 +1,156 @@
# CLOSED-ITEMS — finished work, compressed
> **What this is.** Every register row that reached a terminal state, compressed to its title, the
> version it shipped in, its evidence paths, and any sentence that states a RULE rather than a
> narrative. **Nothing was deleted:** each entry names the commit that holds its full original text,
> and `git show <commit>:documentation/backlog/OPEN-ITEMS.md` returns it verbatim.
>
> **Why it exists (operator ruling, 2026-08-22).** `OPEN-ITEMS.md` had grown to 672 KB across 286
> entries, over half of it finished work, with one single entry at 16 KB. A file that cannot be read
> is a file that cannot be checked — and this project has already 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. The register now holds **open work only**, so its size tracks the work
> rather than the project's age.
>
> **A sibling rather than the bottom of the register**, deliberately: appending to the same file keeps
> the byte count and the scroll, which is the thing being fixed.
>
> **Load-bearing reasoning was NOT compressed away.** Where a closed row states a rule, a fence or a
> deliberate refusal, that sentence is carried here verbatim under **Reasoning kept**. Rules that
> outlive their work item also live in their proper homes — `workspace-CLAUDE.md` standing rules,
> `felhom.eu/CLAUDE.md`, `CONTEXT.md`, and the architecture folder — and this file is not their
> primary record.
>
> **This file is not the register.** Nothing here is open. `OPEN-ITEMS.md` remains the single source
> of truth for open work; `scripts/one_register_gate.py` enforces that against `ROADMAP.md`.
---
| **R-216** | **A correct recovery code was reported to the customer as wrong.** Shipped in 0.120.0, v0.125.0. | **SHIPPED** (controller v0.201.0 + hub v0.97.0/0.97.1) — **but see R-223**: the feature does not work on a NEW box until the manifest vouches agent 0.125.0. Until then such a box is correctly HELD, not lied to | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-218** | **Succeeding at recovery stopped the box asking for what it still needed.** Shipped in v0.203.0. Evidence: `documentation/tests/part4-rewalk-2026-08-06/journal.md`. | **CLOSED 2026-08-06 — controller v0.203.0, proven live.** *(State corrected 2026-08-06: this field read REOPENED while the body below already recorded the fix shipped and proven. The history of the over-claim is kept deliberately — it is why the row is worded as it is.)* **The over-claim, as it stood: the fix covered the DECLARATION half only.** Measured on the R-201 re-walk: the box declared, and **`offsiteheal` re-staged the secret at 11:44:57** saying *"the box re-consumes on its next cycle"* — **the next cycle came and went** (`host-report` 11:55:46, `Received report` 11:55:54, a full cycle **with a positive control that it ran**) **and the credential was still not consumed.** 23 minutes after the re-stage the box's last off-site-apply attempt was still the pre-re-stage one. A census of the customer-reachable actions on `/backups/remote` (`config`, `reset`, `run`, `toggle`) found **none that fetches a staged credential**, and the only lever is `systemctl restart felhom-controller-bootstrap.service` **inside the guest** — which worked in **18 s** (Campaign 11 measured 17), confirming nothing was wrong with the credential, the target or the key: **the only thing missing is anything at all to trigger a retry.** **This is the FIRST of the two dead ends that keep the recovery journey failing** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-219** | **The listing the screen promises could never render on the shape it exists for.** | **SHIPPED** (controller v0.201.0) — the unlock now places the key, brings the tier up, then lists | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-217** | **An unreadable store reported as "opened, with unattributable content".** | **SHIPPED** (controller v0.201.0) — opened / empty / unreadable are three distinguishable states | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-222** | **Reaching for a RETAINED earlier package read as a wrong code.** | **SHIPPED** (controller v0.201.0 + hub v0.97.0) — the ACK carries `superseded_present`/`superseded_at` and the screen names the situation. **It states what the hub knows and promises nothing** — the read path is still unbuilt (R-199's inventory) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-215** | **`GET /recovery` rendered the recovery story on a box that never had off-site backups.** | **SHIPPED** (controller v0.201.0) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-220** | **After a rebuild the customer's drives cannot be re-enrolled, and the refusal names an impossible action.** | **CLOSED 2026-08-06 — shipped in agent v0.127.0 and PROVEN LIVE on a genuinely rebuilt box.** The fix is **corroborated, not a widened prefix**: a mountpoint outside `/mnt/felhom-drives` is forgiven only when the SAME device is also mounted under the managed path — a pairing only Felhom's own enrolment produces, so a disk another system is using at `/srv/data` or even `/mnt/someone-elses-disk` is still refused (own test + red-proof). Read from `/proc/mounts` deliberately: the lsblk invocation is pinned verbatim in the sudoers file, so switching to plural `MOUNTPOINTS` would have shipped a sudoers change with the binary. Fail-safe: an unreadable mount table corroborates nothing. **Measured on the Part 4 venue after a real guest purge, with both raw mounts still present on the surviving host:** `/disks/candidates` returned both drives in `attach` and `initialize` (before the fix: two empty lists), and both **re-attached through the customer endpoint** (`registered: true`). The customer-facing refusal was corrected in controller v0.203.0. | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-221** | **A rebuilt box cannot run the escrow ceremony at all.** | **CLOSED 2026-08-08 — agent v0.128.0.** `Apply` re-asserts the seed BEFORE the idempotent early return; the return itself is kept and pinned by a zero-Proxmox-calls assertion. **The writer was established at `file:line` rather than assumed** — see the follow-through section below | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-223** | **The Day-0 manifest vouched agent 0.120.0 while the recovery feature needs 0.125.0 — and a reinstall DOWNGRADES a box that was fixed by hand.** Shipped in 0.120.0, 0.125.0, 0.192.0. | **CLOSED 2026-08-05.** Golden **0.201.0** baked in the drill VM (658 165 766 B, sha `e730d7cab343eb35…f007654`, **round-trip verified from Gitea**), then manifest set in one save: `agent=0.125.0 golden=0.201.0 min_agent=0.125.0`. A fresh install now lands on current agent AND current controller | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-224** | **Every non-code failure on the unlock path is reported to the customer as a statement about their code.** **Reasoning kept:** **And R-216's gate cannot catch it**: the box's own ring reads `recovery capability gate: offsite_key_recovery=yes (source=version)` — the gate discriminates the agent's **age**, not its **reachability**, so a dead agent of the right version sails through the guard whose own comment says *"An attemp | **CLOSED 2026-08-06 — controller v0.202.0 + agent v0.126.0.** The discriminator is now a VALUE: `escrow.ErrBundleFetch` → **HTTP 502** at the agent, `agentapi.RecoveryRefusal` carrying the status at the controller, and `ClassifyRecoveryFailure` mapping it to one of five classes **from the value, never the text**. **PROVEN LIVE on the venue**, same wrong code, only the hub's reachability changed: `hub up → 400 "…did not open the sealed bundle"` · `hub REJECTed → 502 "…could not be fetched — the recovery code was NOT used"` · `hub restored → 400`. Red-proof: deleting the agent case reproduces `got 400, want 502` with the wrong-code sentence. **Coupled `MinAgent 0.126.0`** — an older agent answers 400 for both causes, so the reading is withheld and the 400 degrades to NEUTRAL; the gate blocks nothing. **The customer-facing messages were NOT re-driven end-to-end**: `/recovery` correctly redirects since F7 set the old data aside, and restoring that state is the reconfiguration §11 forbids — they are covered by handler tests + red-proofs | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-225** | **The remote store reports `0 pillanatkép · 0 / 50 GB` when the box cannot read it — directly above a card stating the store holds backups.** | **CLOSED 2026-08-06 — controller v0.202.0.** `StatsKnown` is a **named** state (the `OffsiteInventory.Empty` pattern), because zero is what an unread store and an empty one both look like and `omitempty` makes "absent" and "0" the same bytes. The fill bar renders only when the fill is known — a 0 %-wide bar is a picture of emptiness. **PROVEN LIVE both ways**: before a run the venue read „a pillanatképek száma még ismeretlen"; after one, „2 pillanatkép … / 50 GB". A measured zero still says zero | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-226** | **M1 — the only message that tells a customer to check their typing — is unreachable on any box that has re-escrowed.** | **CLOSED 2026-08-06 — controller v0.202.0.** The retained-package message now names **both** possibilities and restores the ten-words prompt, because the two are indistinguishable at the engine and saying so is the honest thing. It still does not promise the earlier package can be opened. Red-proof: removing the clause makes the prompt unreachable again | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-228** | **After „I do not want the old data", the set-aside history becomes invisible — the box records where it is and shows it to nobody.** | **CLOSED 2026-08-06 — controller v0.202.0.** `OrphanedRenamedTo` is surfaced as two facts and stops. **It does not promise the history can be reopened** — it cannot be, by anyone, today (R-199's inventory is unbuilt) — and the set-aside **confirmation copy was corrected** for the same reason: *"a helyreállítási kód nélkül többé nem lesznek megnyithatók"* implied that WITH the code they could be. The field's own comment said "recovery-code-recoverable", the same over-promise in the code. **PROVEN LIVE**: the notice renders on the venue | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-227** | **A controller restart mid-unlock returns a raw English `Bad Gateway`.** | **CLOSED 2026-08-06 — controller v0.202.0, partially and stated as such.** **The layer that answers is traefik**, whose config this repo generates — but traefik v3 serves no static files, so a branded proxy page needs a **new always-up container** for every 502 on the box: **scoped, not built**. Shipped: the unlock posts via `fetch` and answers a gateway failure in Hungarian in-page. **Progressive enhancement — with no JS the plain POST still shows the proxy's error** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-234** | **An off-site run reports success while silently omitting an app the customer just switched on.** Shipped in v0.205.0. | **CLOSED 2026-08-06** — controller v0.205.0 | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-236** | **After a guest rebuild the hub never re-stages the off-site credential.** | **CLOSED 2026-08-06 — not a defect** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-237** | **After a successful recovery the customer is shown no backups at all, because the restore surface is keyed on apps that are currently installed and currently marked for future remote backup.** | **CLOSED 2026-08-06** — controller v0.204.0: the list is now built from `OffsiteInventoryList` (the repository's own snapshot tags). Installed-ness became a property OF a row, never a filter; an unreadable store renders as UNKNOWN **and keeps the action offered**; `felhom-offbox` and `_shares` are excluded. 7 new tests incl. a rendered-page test for the rebuilt shape, and a red-proof that keys the list back on installed-and-toggled apps. | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-238** | **„Teljes visszaállítás előkészítése" accepts the click and does nothing.** Shipped in v0.204.0. | **CLOSED 2026-08-06** — controller v0.204.0 | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-239** | **The fixes are written, tested, pushed — and a machine installed tonight gets none of them.** Shipped in 0.127.0, 0.203.0, 0.204.0. Evidence: `tests/finalwalk-r201-2026-08-07/journal.md`, `tests/golden-0.205.0-2026-08-07/`. | **CLOSED 2026-08-07** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-241** | **The credential self-heal, succeeding, locks the customer out of their own recovery.** Shipped in v0.206.0, v0.98.0. Evidence: `audits/SPIKE-r241-recovery-offer-2026-08-07.md`, `tests/finalwalk-r201-2026-08-07/journal.md`. | **FIXED 2026-08-07 — v0.206.0 / hub v0.98.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-247** | **The box is being told something false, in its own words, and it recommends the destructive act.** Shipped in v0.206.0. | **CLOSED 2026-08-08** — controller v0.209.0. The field is received and the box tells the two conditions apart; see the Campaign-12 follow-through section below. The WRONG FLAG itself is R-246 (operator act, hub-side) and the customer-facing card copy is unchanged — both stated rather than folded in | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-249** | **The retrieval passphrase ships in the customer page's HTML, so any headless read puts it in a transcript.** Shipped in v0.206.0, v0.207.0. **Reasoning kept:** **Severity MEDIUM:** it is a live per-customer secret that fetches the whole config (`GET /api/v1/config/<id>` with `X-Retrieval-Password`), but the exposure is to someone who can already read the operator page — a defence-in-depth failure, not a boundary crossed. | **CLOSED 2026-08-08** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-252** | **After a rebuild the restore refuses because the data drives are not registered, and nothing on the recovery path says so.** Shipped in v0.207.0. | **CLOSED 2026-08-08** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-253** | **The restore page promises it will reinstall the app, and the restore then refuses because the app is not installed — in the customer's own language, three lines apart.** Shipped in v0.207.0. | **CLOSED 2026-08-08** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-212** | **The orphaned-ciphertext deletion HALTED: the stores on the storage box do not match this register's record.** | **CLOSED 2026-08-05 — all three deleted after the operator confirmed the corrected list** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-94** | **~~A hand-synced version constant drifts, and the gate that would catch it is never run~~** Shipped in 1.22.0, 9.9.9. | **CLOSED — SHIPPED** (hub v0.87.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-110** | **`main` is the installer's publish channel — there is no staging.** Shipped in 1.22.0, v1.23.0, v4.4.0. **Reasoning kept:** E-2a's `felhom-backup-target-apply` (`:2116`) is installed **0755 to `/usr/local/sbin` and root-fenced in sudoers**, validated only by `bash -n` — a root-executed artifact taken from `main` with no pinned integrity, which is this row's class exactly. Fixing only (i) leaves a tagged installer pulling nine untagged files from `main` at run time — a staging story that is false in the place it matters most, since one of those nine (`felhom-backup-target-apply`) is installed **0755 into `/usr/local/sbin` and root-fenced in sudoers**, validated only b | **CLOSED — SHIPPED** (installer v1.23.0, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-111** | **The Day-0 artifact channel is 17 agent releases stale — a box installed today gets agent `0.96.0`, not `0.113.0`.** Shipped in 0.113.0, 0.114.0, 0.161.0. Evidence: `audits/E2D-fresh-vm-2026-07-29.md`. **Reasoning kept:** **The global controller floor was deliberately NOT raised**: the golden now bakes 0.185.1, so a fresh box needs no self-update, and raising it would have been an unnecessary fleet-wide write. | **SHIPPED 2026-07-29 — the channel now serves agent 0.113.0 + golden 0.185.1** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-115** | **Publishing is a remembered step, and it was forgotten within eight hours of being documented as forgettable.** Shipped in 0.113.0, 0.114.0, 0.119.0. **Reasoning kept:** **Class: → R-29, one layer up** — a control that exists and is never walked; deliberately NOT given its own ID. It calls the existing `publish-agent.sh` rather than reimplementing it, refuses a dirty or unpushed tree, refuses to re-release an existing version (one version name must never mean two binaries), and **deliberately does not vouch** — vouching points machines at a version and stays the operator's ac | **CLOSED — SHIPPED** (`release-agent.sh` + `check-published-versions.py`, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-116** | **The drive-absent alarm and its recovery were a MISMATCHED PAIR — absent fired the GENERIC `storage_disconnected`, return the SPECIFIC `backup_target_restored`; `backup_target_absent` never fired at all** Shipped in 0.185.1, v0.115.0, v1.25.0. Evidence: `audits/R116-v0116-2026-07-30.md`, `audits/SPIKE-r117-bind-liveness-2026-07-30.md`. | **SHIPPED + PROVEN-LIVE** (agent v0.116.0, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-120** | **The golden baked a controller that predated R-114 + R-112, so a FRESH box showed the customer the WRONG absent-target message** Shipped in 0.113.0, 0.116.0, 0.156.0. Evidence: `audits/R120-golden-rebake-2026-07-30.md`. | **CLOSED — golden rebaked + PROVEN-LIVE, and the class now has an ENFORCED gate** (golden 0.186.0 + hub v0.82.0, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-117** | **A drive's guest bind becomes a DEAD MOUNT while every signal reads healthy — and it happens in TWO ways, only one of which the original framing covered.** Shipped in 0.113.0, 0.117.0. Evidence: `audits/R117-v0117-2026-07-30.md`. **Reasoning kept:** **No block I/O proven by strace** (only `/proc/self/mountinfo`, **0** statfs) — the Part 1 `CLAUDE.md` fence applied to its own first consumer. | **SHIPPED + PROVEN-LIVE** (agent **v0.117.0**, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-113** | **The drive-absent gate CANNOT FIRE on device loss — E-2b's alarm is wired to an unreachable condition.** Shipped in 0.113.0, 0.114.0, v0.185.0. Evidence: `audits/E2D-fresh-vm-2026-07-29.md`, `audits/SESSION-C-2026-07-29.md`. | **SHIPPED + PROVEN-LIVE** (agent v0.114.0, 2026-07-29) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-112** | **E-2's degraded banner and offer have NO UI CONSUMER — the endpoint is correct and the customer never sees it.** Shipped in v0.185.1. Evidence: `audits/E2D-fresh-vm-2026-07-29.md`, `audits/SESSION-C-2026-07-29.md`. | **SHIPPED + PROVEN-LIVE** (controller v0.186.0, 2026-07-29) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-114** | **On target-drive loss the customer is told the wrong story and offered the drive that just vanished.** Shipped in 0.113.0. Evidence: `audits/E2D-fresh-vm-2026-07-29.md`, `audits/SESSION-C-2026-07-29.md`. | **SHIPPED + PROVEN-LIVE** (controller v0.186.0, 2026-07-29) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-29** | **The green gates are not enforced anywhere — one was RED for 16 releases before anyone ran it.** Shipped in 1.19.0, 1.22.0, v0.129.0. | **CLOSED — both halves shipped** (2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-86** | **Restore-tests are interval-scheduled, not backup-aligned** | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-03** (agent **v0.121.0**, hub **v0.91.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-87** | **The restic tier is never restore-tested** **Reasoning kept:** **And R-95 still applies:** that credential can delete, so a restic restore-test must never be able to write to the repo CC | **READY — RE-RANKED UP 2026-08-03 (R-86 closed)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-185** | **The agent cannot see the host backup tier's archives on demo-felhom — the PVE token has no ACL on `/storage/felhom-backup`, so the content listing returns EMPTY where root sees three archives.** Shipped in v0.123.0. | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-03** (agent **v0.123.0**, installer **1.24.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-186** | **A released agent binary's sha256 cannot be reproduced from its tag.** Shipped in v0.120.1, v0.121.0, v0.121.2. | **CLOSED — SHIPPED + MEASURED 2026-08-03** (agent **v0.122.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-187** | **R-115's one-command release had never actually run its publish leg — the first real use died there.** Shipped in v0.121.0. | **CLOSED — SHIPPED 2026-08-03** (`felhom-agent`) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-188** | **Every agent release has a ~50 % chance of emailing the operator a CI failure for a release that is correct.** Shipped in 0.121.2, v0.121.0, v0.121.1. | **CLOSED — SHIPPED 2026-08-03** (agent **v0.122.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-189** | **A passing restore-test can be invisible to the hub forever — and R-86 made that window a week instead of a day.** Shipped in v0.121.1. | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-03** (agent **v0.122.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-195** | **A customer with no machine ever bound e-mailed an `expected_dbdump_missed` ERROR every morning.** Shipped in v0.73.0. **Reasoning kept:** Fail-**open** on a read error (an unreadable binding must never SUPPRESS a real alarm), and the deferral is LOGGED with its own counter (the v0.73.0 Part-7 precedent: a quiet check must not look like a check that did not run). | **SHIPPED** (hub **v0.92.0**, 2026-08-04) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-196** | **`escrow_stale` is wired to the ONE path that does not change the repo password, and absent from the path that does.** Shipped in v0.95.0. Evidence: `audits/DRILL-r201-night-run-2026-08-04.md`, `audits/SPIKE-offsite-credential-recovery-2026-08-04.md`. | **CLOSED 2026-08-05 — hub v0.95.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-197** | **The hub holds both halves of the evidence that a box's offsite DATA key changed, and reads neither.** Shipped in v0.78.0, v0.93.0. Evidence: `audits/SPIKE-offsite-credential-recovery-2026-08-04.md`. **Reasoning kept:** The in-between shapes (a first-ever hash, a hash-less supersession) are LOGGED rather than dropped, so *"we chose not to alarm"* and *"the check did not run"* never look identical. | **SHIPPED** (hub **v0.93.0**, 2026-08-04) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-198** | **The hub's superseded-escrow retention does NOT retain the offsite repository password — and the ceremony the system tells the customer to run is what destroys the last copy.** Shipped in v0.92.0, v0.93.0. Evidence: `audits/RECON-offsite-dr-chain-2026-08-04.md`. | **SHIPPED** (hub **v0.93.0**, 2026-08-04) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-199** | **The hub serves recovery blobs on two endpoints that have no client anywhere in the system.** Evidence: `audits/RECON-offsite-dr-chain-2026-08-04.md`. | **SHIPPED + PROVEN-LIVE 2026-08-04** (hub **v0.94.0**, agent **v0.125.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-203** | **A customer-declared MANDATORY data directory was silently absent from the off-site snapshot while the run reported `ok`.** Evidence: `audits/DRILL-r201-offsite-recovery-2026-08-04.md`. | **SHIPPED + PROVEN-LIVE 2026-08-04** (controller **v0.197.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-204** | **A rebuilt box can recover its off-site key and still cannot use it: the remedy that reconfigures the tier is the thing that blocks the recovery.** Shipped in v0.198.0, v0.199.0, v0.95.0. Evidence: `audits/DRILL-r201-night-run-2026-08-04.md`. **Reasoning kept:** **Live on demo-felhom 9201, nothing restarted (`restarts=0`, container older than both mints): the superseded code returned „Hibás vagy lejárt kód" and the current one was accepted first time.** **Item 2 (a re-issue marks a healthy escrow stale) — CLOSED, → R-196.** Test-proven; deliberately NOT fir **What is deliberately NOT automated: the escrow ceremony.** A credential is replaceable; the recovery code is not. | **ALL FOUR ITEMS CLOSED 2026-08-05** (items 1–3 controller v0.198.0 + hub v0.95.0; item 4 controller v0.199.0 + hub v0.96.0) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-192** | **`offsite_delivery_stuck` tells the operator the opposite of what the detector measured, and the self-heal silently refuses for exactly the reason the message denies.** Shipped in 0.187.0, 0.192.0, v0.199.0. Evidence: `audits/RECON-offsite-dr-chain-2026-08-04.md`, `audits/SPIKE-offsite-credential-recovery-2026-08-04.md`. | **CLOSED 2026-08-05 — the guard's scoping half closed BY REPLACEMENT** (hub v0.96.0) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-193** | **A guest rebuild silently drops the off-site app-data tier, and nothing restages the credential.** Shipped in 0.156.0, 0.187.0, 0.192.0. Evidence: `audits/RECON-offsite-dr-chain-2026-08-04.md`, `audits/SPIKE-offsite-credential-recovery-2026-08-04.md`. | **CLOSED 2026-08-05 — controller v0.200.0** (credential half v0.199.0/v0.96.0; the recovery SCREEN v0.200.0) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-90** | **~~ep0 RAM headroom — 4 GiB swap survived its first reboot 2026-07-27; 3.8 GB RAM unchanged~~** | **CLOSED — the operator rescaled ep0 to a CX33 on 2026-08-03** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-97** | **~~Whole-guest backup tier had no hub signal; quiesce blamed the apps~~** Shipped in v0.79.0. | **SHIPPED** (controller v0.177.0 + hub v0.78.0/v0.79.0, 2026-07-27) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-100** | **~~A restic offsite tier that fails every night never goes stale on the hub — `isStale` counted from `LastRun`** **Reasoning kept:** The real defect is **defeated defence in depth**: the hub-side *pull* net was anchored on a field the failing controller keeps refreshing, so it could not compensate for a lost *push* (cf. | **SHIPPED + PROVEN-LIVE** (controller v0.181.0 + hub v0.80.0, 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-101** | **~~Tier-2 `LastRun` is written on failure and rendered to the customer as „Legutóbbi másolat" — including in t** | **SHIPPED + PROVEN-LIVE** (controller v0.182.0, 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-108** | **~~Network storage can host an app's namespace, and FileBrowser binds a network share at its ROOT~~** Evidence: `audits/R108-network-app-namespace-2026-07-30.md`. | **SHIPPED + PROVEN-LIVE** (controller v0.187.0, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-109** | **~~The DR recipe records no backup target~~** Evidence: `audits/R106-R109-recipe-completeness-2026-07-30.md`. | **SHIPPED + PROVEN-LIVE** (agent v0.118.1 + hub v0.83.0, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-106** | **~~The DR recipe records the PBS namespace as `"root"` on every box~~** | **SHIPPED + PROVEN-LIVE** (agent v0.118.1, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-122** | **~~`AssembleDRRecipe` silently DROPPED `offsite_restic` — the offsite recovery location never reached any reci** | **SHIPPED** (hub v0.83.0, 2026-07-30) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-125** | **A "test through the production path" is only true up to the seam it injects at.** Shipped in v0.118.0. Evidence: `audits/R106-R109-recipe-completeness-2026-07-30.md`. | **FIXED** (agent v0.118.1) — filed for the DOCTRINE point | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-128** | **~~`build-felhom-iso.sh:44` comments that `ISO_VERSION` "aligns with felhom-host-install SCRIPT_VERSION" — a c** | **CLOSED** (iso v1.26.0, 2026-07-31) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-154** | **~~`[first-boot]` is automated-install-only and nothing in the Felhom tree said so~~** Evidence: `audits/SPIKE-universal-iso-3-2026-07-31.md`. | **CLOSED** (iso v1.26.0, 2026-07-31) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-155** | **~~`iso-repack.sh` refuses any ISO without `auto-installer-mode.toml`, blocking the no-`answer.toml` posture~~** | **CLOSED** (iso v1.26.0, 2026-07-31) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-156** | **An app's data is neither persisted nor backed up, and it reports healthy.** Shipped in 26.6.1. **Reasoning kept:** **Provenance, stated because it decides the row:** the observation is `docker ps -a` on demo-hp's **guest 9201** returning empty, supplied with the 2026-08-02 task; **this session did not re-measure** (documentation-only, every box fenced). | **CLOSED — all three apps fixed** (papra template, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-157** | **`bootrecon`'s start-ONCE sweep misses the boot orphan it exists to recover — TWO mechanisms.** | **CLOSED — SHIPPED + PROVEN-LIVE** (B: controller v0.189.0; A: v0.190.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-170** | **The drive-backed boot gate infers a customer's Stop from a container count.** Shipped in v0.190.0. | **CLOSED — SHIPPED + PROVEN-LIVE** (controller v0.190.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-171** | **The boot sweep started apps whose data drive was ABSENT — a regression introduced by v0.189.0, now FIXED.** Shipped in v0.189.0. Evidence: `audits/DIAG-bootrecon-drive-absent-2026-08-02.md`. | **CLOSED — SHIPPED + PROVEN-LIVE** (controller v0.190.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-172** | **A false `host_stale` alarm fires when the hub's SQLite refuses two consecutive host reports.** **Reasoning kept:** **Retry options (b) and (c) were deliberately NOT taken** — with readers no longer blocking writers a surviving `SQLITE_BUSY` would be a real signal, and a retry would hide it; revisit only on evidence. | **CLOSED — SHIPPED + PROVEN-LIVE** (hub v0.88.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-174** | **The app-stop guard's crash recovery started apps onto MISSING drives — a regression in v0.189.0 code.** Shipped in v0.189.0. | **CLOSED — SHIPPED + PROVEN-LIVE** (controller v0.191.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-175** | **`07-backup-architecture.md` §7.5 states ONE box's size bound as if it were the fleet's.** Shipped in 0.192.0, 7.5.1. Evidence: `audits/SPIKE-r165-mp1-merge-2026-08-02.md`. | **CLOSED — FIXED 2026-08-03** (same pass as R-165) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-183** | **A fresh install fetched the vouched agent BINARY and its sixteen CONFIG files from two different refs, and nothing compared them.** Shipped in v0.120.0. **Reasoning kept:** **Why it is a defect and not only untidiness:** these files are the agent's own operating surface — its systemd unit, its sudoers, its guarded wrappers — and `configs/felhom-backup-target-apply` is installed **0755 into `/usr/local/sbin` and root-fenced in sudoers**, validated only by `bash -n`. | **CLOSED — SHIPPED** (installer v1.23.0, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-182** | **A full disk tells the operator about ONE app and silently swallows every other app's refusal for an hour.** Shipped in v0.194.0, v0.90.0, v0.90.1. | **CLOSED — SHIPPED** (controller v0.194.0 + hub v0.90.0/.1, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-181** | **The capture floor guards the cheap leg and not the leg that fills the volume — and its refusal message asserts an invariant the code does not provide.** Shipped in v0.192.0, v0.193.0, v0.193.1. | **CLOSED — SHIPPED** (controller v0.193.0 + v0.193.1, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-178** | **The merged golden (0.192.0) is built and published but NO BOX HAS BEEN REINSTALLED FROM IT, and it is deliberately UNVOUCHED.** Shipped in 0.119.0, 0.120.0, 0.192.0. | **CLOSED — BOTH BOXES REINSTALLED AND PROVEN (2026-08-03)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-158** | **A local Tier-1 app-data backup failure reaches no hub channel.** Shipped in v0.78.0. | **CLOSED BY R-167 — SHIPPED + PROVEN-LIVE** (controller v0.191.0 + hub v0.89.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-159** | **wishlist's data landed in an ANONYMOUS volume — never backed up, orphaned by a redeploy.** | **SHIPPED** (`templates/wishlist/docker-compose.yml`, 2026-08-02) — filed to record the CLASS | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-160** | **gramps-web persisted three paths and wrote to none of them.** | **SHIPPED** (`templates/gramps-web/docker-compose.yml`, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-163** | **`mp1` is RETENTION, not staging — and it is sized as if it were neither.** Shipped in v0.192.0. | **CLOSED by R-165 — the ceiling it describes no longer exists** (golden v3.0.0, 2026-08-03) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-165** | **Merge `mp1` into `mp0` — the dedicated 20 G backup partition stops existing.** Shipped in 0.192.0, v0.192.0. Evidence: `audits/SPIKE-r165-phase0-2026-08-03.md`. | **SHIPPED — golden `build-golden.sh` v3.0.0 + agent v0.120.0 + controller v0.192.0 (B2), 2026-08-03. IMPLEMENTED — the LAYOUT is proven live on both boxes (R-178, 2026-08-03); the BULKHEAD'S REPLACEMENT IS NOT (→ R-181)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-166** | **App state gets a desired/observed model with its own store.** Shipped in v0.189.0. | **SHIPPED + PROVEN-LIVE** (controller v0.189.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-167** | **Storage monitoring and backup alerts.** Shipped in v0.191.1, v0.191.2. | **CLOSED — SHIPPED + PROVEN-LIVE** (controller v0.191.0/.1/.2 + hub v0.89.0, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-168** | **~~CI: no runner exists, and with trunk-based pushes CI can DETECT but not BLOCK~~** Shipped in 0.1.0. Evidence: `audits/SPIKE-ci-runner-2026-08-02.md`. | **SHIPPED — and the alarm is DEMONSTRATED** (2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-205** | **`RootFsPressureDespiteHousekeeping` can never fire** Evidence: `audits/SPIKE-dooplex-buildcache-2026-08-05.md`. | **CLOSED — SHIPPED + RED-PROVEN LIVE** (`homelab-manifests` `6808a4b`, 2026-08-05) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-258** | **C3 — the customer's per-app backup tick is green on the PRESENCE of a restore point, and its only red condition is a GLOBAL one.** | **CLOSED 2026-08-08 — controller v0.210.0.** `appDumpVerdict` reads THIS app's own dump result; three states, no icon when nothing is known. **Recency deliberately not added** — see the observation in the follow-through section | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-259** | **C4 — a disk read that FAILS renders as „0.0 GB / 0.0 GB (0%)" in the nominal colour, on the dashboard's most-looked-at meter.** | **CLOSED 2026-08-08 — controller v0.210.0.** `readDiskUsage` reports success; `SystemInfo.DiskKnown`/`HDDKnown`; the template draws no figure, no percentage and no meter fill when unknown. **The hub leg is deliberately NOT fixed and is now R-266** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-260** | **C5 — the agent reports at least eight decision-bearing facts the hub models NOWHERE, and the sharpest one blinds the check that answers „can the operator get into this box".** | **CLOSED 2026-08-08** — the class is GATED (G-1, `scripts/wire_contract_gate.py`) and the sharpest instance is fixed (hub v0.99.0). The remaining unconsumed facts are **R-264, OPEN** — allowlisted with reasons, which is not the same as decided. See the follow-through section below | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-265** | **A CI run can fail with NO LOG PERSISTED, and the alarm mail then points the operator at a log that does not exist.** | **CLOSED 2026-08-08 — `timeout-minutes: 5` on the gates job, and the alarm mail now states elapsed seconds and qualifies its own "names itself in the run log" sentence.** ⚠ **The unknown is NOT closed and must not be read as closed:** whether the `if: failure()` alarm fires for a REAPED job is still unverified. The timeout makes the reap unreachable in practice; it does not answer what happens inside one | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-267** | **The Configuration page is 2.6× faster and is still ~10 s, and the remaining cost is ONE Gitea call whose latency swings 20× with load.** Shipped in 0.100.2, v0.100.0. | **CLOSED 2026-08-08 — hub v0.101.0 + a registry prune.** **Final: cold 5.4 s, warm 0.14 s** (was 26.2 s). Three serialisation legs took it to 9.85 s mean, the 60 s in-memory memo took the warm path to a quarter-second, and the prune halved what remains of the cold path. **⚠ TWO CORRECTIONS TO THIS ROW'S OWN EARLIER TEXT, because both were wrong and both mattered.** **(1) "Only 50 generic versions exist" WAS NOT A COUNT, IT WAS A PAGE LIMIT.** `?type=generic&limit=1000` returns at most 50; the 50 I measured was exactly the cap, and three older agent versions (0.81.0, 0.80.0, 0.79.0) only became visible after the first 30 deletions moved them onto page one. **An unpaginated listing is not evidence of a total** — this repo's own "an empty listing is not evidence of emptiness" rule, walked into while measuring. **(2) THE OPERATOR'S "REDUCE THE NUMBER OF ARTIFACTS" WAS THE BETTER CALL AND MY MEASUREMENT SAID OTHERWISE.** I reported it helps "sub-linearly" and "is not the lever". Measured after: trimming to 10+10 took the COLD load from 13.4 s to 5.4 s — a 2.5× improvement on the path the memo cannot help, because the fan-out is per-version. Recorded rather than quietly dropped (the R-96 standing rule). **Pruned to the newest 10 per package on the operator's rule**, with the live-vouched golden/agent/floor asserted into the KEEP set before a single DELETE was issued; 33 deletions, all HTTP 204, and golden 0.210.0 / agent 0.128.0 / agent 0.127.0 verified still fetchable afterwards. `drill-r50` runs agent 0.113.0, now deleted — flagged to the operator first; it is a disposable nested drill VM and only its re-download path is gone | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-268** | **A live per-guest local-API token was printed into a session transcript.** Shipped in 169.254.253. | **CLOSED — ROTATED + PROVEN LIVE 2026-08-09** (rehearsal pre-phase, `audits/REHEARSAL-byo-reinstall-2026-08-09.md` §3) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-273** | **RANK 1 — the hub vouched an agent version that was never git-tagged, and every install fleet-wide now fails at step 5/8.** Shipped in 0.127.0, 0.128.0, v0.127.0. | **CLOSED 2026-08-09 — tag pushed, install PROVEN** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-278** | **demo-felhom's off-site tier has never completed a run and has been stuck for six days.** Shipped in 0.200.0, v0.93.0. | **CLOSED 2026-08-10 — protection RESTORED, and the recovery it waited for could never have worked** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-280** | **RANK 1 — after a reinstall the data drive cannot be re-attached through ANY dashboard route, and the restore page promises it is "two clicks".** | **CLOSED — controller v0.211.0, delivered via golden 0.211.0 (vouched 2026-08-10)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-281** | **The hub said NOTHING through an entire reinstall — and the tripwire for a sealed-backup unseal did not fire on a real unseal.** | **WITHDRAWN 2026-08-09** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-293** | **CENSUS, 2026-08-10 — no machine that is not ours can be in the state that cost demo-felhom its history, and here is the whole population.** | **CLOSED-INFORMATIONAL 2026-08-10** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-294** | **The orphan card promises restorability that the box rendering it cannot evaluate — specified, not implemented.** Evidence: `documentation/design/SPEC-orphan-card-copy-2026-08-10.md`. | **CLOSED — controller v0.211.0; see R-299 for the sentence it missed** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-296** | **The orphan card's OTHER sentence makes the same promise, and the spec says it is fine.** | **CLOSED — shipped in controller v0.212.0 (R-299); verified: the sentence at backups_remote.html:98 was replaced and the stem guard covers it** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-297** | **An install took whatever golden was lying around.** Shipped in 0.153.0, 0.210.0, 0.213.0. | **CLOSED — observed live + PUBLISHED as `installer-v1.27.0` (both refs bumped)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-299** | **The orphan card's OTHER sentence made the same unevaluable promise, and the spec called it accurate.** Shipped in v0.211.0. | **CLOSED — controller v0.212.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-300** | **Our own uninstall left the thing that makes our own reinstall refuse.** Shipped in 0.0.0, 10.0.2, 127.0.0. | **CLOSED — observed live + PUBLISHED as `installer-v1.27.0` (both refs bumped)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-301** | **The abandon countdown banner makes the retired promise a third time, and as a flat statement.** **Reasoning kept:** the customer chose to abandon a recovery offer that exists — which is why it was NOT changed (this session was fenced to the orphan card). | **CLOSED — premise CONFIRMED and fixed in controller v0.213.0 (R-302)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-302** | **The abandon banner promised retrieval it could not see was still true — fixed by PINNING a fingerprint at the decision.** Shipped in v0.213.0. **Reasoning kept:** Empty is not a match on either side; a countdown started before v0.213.0 carries no pin and takes the cautious branch (deliberately NOT backfilled). | **CLOSED — controller v0.213.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-305** | **The R-300 cleanup fires exactly once per machine, and the second reinstall hits the original wall.** Shipped in 0.0.0, v1.27.0. | **CLOSED — superseded by R-316** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-307** | **`demo-felhom` carries a LIVE abandon countdown that this drill did not start — and the end state says there should be none.** **Reasoning kept:** The drill's fence forbade starting, shortening or triggering a countdown, and none was; but its required end state was *"no abandon countdown anywhere"*, and one exists. | **CLOSED — countdown cancelled 2026-08-12 on the operator's ruling** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-308** | **~~The stored controller password no longer opens `demo-felhom`~~ — WITHDRAWN 2026-08-12, this was MY BUG, not a defect.** | **WITHDRAWN — not a defect (my error)** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-309** | **The day-0 runbook says pushing the installer publishes it. It has not since R-110.** Shipped in 1.25.0, 1.27.0. Evidence: `documentation/runbooks/day0-install.md`. | grep -m1 '^SCRIPT_VERSION'`. **Measured while writing it: served `1.28.0`, `main` `1.28.0`, both pins `installer-v1.28.0` — the three agreeing is the observation; any one alone is not.** **The claim was copied elsewhere and the copy was hunted:** `audits/SPIKE-universal-iso-3-2026-07-31.md:184` said the same thing and **cited `day0-install.md` as its source**, which is how it spread. It was **true on the day it was written** (R-110 shipped 2026-08-03), so the dated finding is kept verbatim and carries a SUPERSEDED note rather than being rewritten — falsifying a dated record to tidy it is its own defect. Two other hits are correct in context: `hostinstall_gates.py:198` states the consequence of the manifest LOSING its tag, and the 2026-08-12 drill record already names the sentence as false | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-311** | **A correct recovery code for a retained package stopped being reported as wrong.** Shipped in 0.126.0, 0.128.0, 0.129.0. | **CLOSED — shipped + delivered: hub v0.103.0 + agent v0.129.0 + controller v0.214.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-316** | **The removal now genuinely reverses the installation — R-305's once-per-machine defect closed.** Shipped in 0.0.0, v1.27.0, v1.28.0. | **CLOSED — shipped + published, observed on the cycle that actually fails** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-318** | **No honest marker exists that says Felhom installed dnsmasq on a machine already in the field, and none can be invented.** Shipped in v1.27.0. **Reasoning kept:** `/var/log/dpkg.log` does record the install — and is a **timestamp**, which the standing rule refuses as a heuristic dressed as a fact. | **CLOSED — established, no action possible for existing boxes** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-319** | **The guest-network watchdog finally has a reader — the first of R-264's twenty-one, and it is the repair COUNT that matters, not the state.** Shipped in 0.92.0, v0.92.0. | **CLOSED — shipped hub-side 2026-08-13** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-320** | **Evidence has been destroyed twice in three days, in the same place, by the same act.** Shipped in v1.28.0. Evidence: `audits/DRILL-retained-key-2026-08-12.md`, `audits/REPORT-r316-installer-v1.28.0-2026-08-13.md`. **Reasoning kept:** **The rule, now standing rule 5 in `workspace-CLAUDE.md` (so it loads in every session) and repeated where a session actually meets it — `runbooks/target-selection.md`, `RUNBOOK-rehearsal-v3.md`, and the `PROMPT-TEMPLATE.md` report section: evidence is copied off the machine at the end of the phase | **CLOSED — rule written, four homes** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-321** | **A box on which reporting is deliberately switched off still alarms as stale, then down.** Shipped in v0.105.0. | **CLOSED — shipped hub v0.105.0, both doors** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-322** | **The claim guard has never scanned the hub, and the hub sends the customer's first sentence.** **Reasoning kept:** **Recommended shape, and the reason it is not one line:** the gate is invoked by `controller_gates.py`, so pointing it at a sibling repo makes a controller gate fail on a felhom.eu edit — the cross-repo lesson from G-1 (a gate needing a sibling passes locally and exits INCONCLUSIVE in CI, and must n | **CLOSED 2026-08-13 by R-324** — `scripts/hub_copy_gate.py`, registered in `repo_gates.py`, scanning 95 hub files for retired names and four declared customer surfaces for retrieval stems, with a plant→convict→remove→pass selftest that caught a defect in its own instrument on the first run. The stem list IS shared (`scripts/customer_copy_vocab.py`) and no controller gate was made to depend on a felhom.eu clone; the controller gate's adoption of the shared list is R-325, and until it happens the two are drift-checked rather than left to diverge | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-323** | **The third near-homograph — the five-word phrase is „Tulajdonosi jelmondat” now.** | **CLOSED — shipped hub v0.105.0** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-324** | **The hub's customer copy is under a guard for the first time — and the guard has been watched catching, ignoring and releasing.** | **CLOSED — shipped, selftest green** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-326** | **"Which claims are unproven?" is a question a machine can answer now — and the number everyone was repeating answered a different question.** | **CLOSED — shipped** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-328** | **The disk alert was emailed to nobody, and one word is the whole reason.** Evidence: `audits/DIAG-smart-passed-trap-2026-08-14.md`. | **CLOSED — controller v0.215.0, PROVEN LIVE 2026-08-14.** Now `"warning"`, and `DiskAlertKind.Severity()` is exported so the contract is assertable from any package rather than duplicated as a literal. **The proof is a side-by-side pair pushed through the REAL hub event endpoint** from demo-hp's controller: severity `"warning"` → stored `warning`, `notification_log` **id 689, channel `operator`, status `sent`**; the identical push at `"warn"` → stored **`info`**, and **no `notification_log` row exists at all**. Pinned by `TestNotifyDiskHealthDegraded_SeverityRoutes`, which asserts membership of the hub's accepted set (not just the literal) and names both hub locations; its red-proof — restoring `"warn"` — fails all three assertions | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-334** | **CLOSED 2026-08-18 — golden 0.216.0 baked, published and VOUCHED; CI green by run id.** Shipped in 0.214.0, 0.215.0, 0.216.0. Evidence: `documentation/tests/golden-*`, `documentation/tests/golden-0.214.0-2026-08-12`. | **CLOSED 2026-08-18.** Baked from `RUNBOOK-manual-build.md` §4.0+§4.1 in the DooPlex drill VM and published: **`GOLDEN_VERSION=0.216.0`**, **`GOLDEN_SHA256=ac004dc90d8cefccc5448377892f9cff3a4c3e1e27d0e11129120e38ac31c34b`**, 656,970,239 bytes at `…/generic/felhom-golden/0.216.0/golden.tar.zst`. **The published bytes were verified, not just the script's print** — the artifact was downloaded back out of Gitea and hashed, and it matches. **Vouched by the operator, all THREE fields together**, confirmed by reading the hub's own store rather than the save: `artifact_golden_version=0.216.0`, `artifact_agent_version=0.129.0`, `artifact_min_agent=0.129.0` (2026-08-18 11:00:59–11:01:00), and the hub's recorded sha256 matches the downloaded artifact. The R-216 shape was checked on the machine: `MinAgent` 0.129.0 is **equal to**, not above, the newest **published** agent. **`golden_currency_gate.py` rc=0 and `repo_gates.py --fast` rc=0 — all nine gates — and CI is GREEN BY RUN ID: run **353**, `head_sha 7d81681d6`, conclusion `success`** (the two prior runs 351/352 on this same afternoon were red on exactly this row, which is the contrast). That push needed **no `--no-verify`** — the first of the day that did not. Evidence: `documentation/tests/golden-0.216.0-2026-08-18/`, report `REPORT-golden-0.216.0.md`. **Closed with the run id quoted deliberately**: this row was re-confirmed once and widened once, and closing it on a local green a third time would have left the same ambiguity | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-335** | **One physical disk was walked TWICE per run, and the second walk sustained it against itself.** Shipped in v0.215.0. **Reasoning kept:** **This is the shape standing rule 3 warns about: an absent alarm was not evidence — the two artefacts had to be read AGAINST each other** — — CC | **CLOSED — controller v0.216.0, 2026-08-14.** Each `diskKey` is evaluated once per run; both entries stay marked `seen` so neither looks like a disappeared disk, and the card still renders both storage rows (the dedup is about state and alerts, not display). Pinned by `TestDiskCheck_SameDiskTwiceIsEvaluatedOnce`; companion red-proof run and reverted — deleting the guard makes the first sighting emit `Kind:2` (Hiba-from-sectors) at 8 sectors | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-344** | **`felhom-agent` leaks one TCP connection to PBS per poll cycle, forever, on both sides — and it is the whole of the ep0 descriptor leak.** Shipped in 0.129.0, 0.130.0. Evidence: `audits/SPIKE-ep0-established-connections-2026-08-20.md`. **Reasoning kept:** **The proof obligation is the fd count, not the diff:** per standing rule 3 the positive observable is ep0's ESTAB count going FLAT between proxy restarts, measured over a window long enough to matter — a green test suite proves nothing here, and a 30-minute window proves nothing here either (that e **control 4 cycles -> 4 leaks; fixed 4 cycles -> 0 leaks.** **Positive observable per standing rule 3** (a zero leak is equally consistent with "the agent stopped working"): the fixed box's four poll cycles are in ep0's log, and the boxes' other traffic is near-identical (libwww-perl 924 vs 926, pro | **CLOSED 2026-08-20 — fixed, proven live on both boxes, published and vouched** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-347** | **The R-344 fix exists on two demo boxes by hand and NOWHERE ELSE — a box installed from the current image still ships the leaking agent.** Shipped in 0.129.0, 0.130.0, 0.216.0. Evidence: `documentation/runbooks/publish-train-rules.md`. | **CLOSED 2026-08-20 — published, vouched, and the fleet reconciled onto the published bytes** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-351** | **The restore never read back where the backup said the data lived, and a second press started a second restore.** | \.NamespaceRoot\b' --include=*.go` found **no non-test reader anywhere** — the reconstitution opened the manifest (`offbox_reconstitute.go:235`) purely for the coherence stamp and resolved its destination from the LIVE app instead. **A restore into a destination different from the recorded one therefore succeeded silently, under a green message.** **(b) The second press.** All seven restore handlers gated on `backupMgr.IsRunning()` — the CONCURRENCY flag, acquired *inside* the goroutine (`offbox_reconstitute.go:180`) **after** the handler returned. Established with a test before any change: both the reconstitute and place handlers answered „…elindult" and **overwrote the first restore's op/stack**. The wizard had read the correct flag since v0.154.0 and said so in a comment; the handlers were never moved over. **(c)** The banner gated its terminal result on a page-local `sawRunning`, so a restore that finished before the page opened — the 8.666 s OpenGist restore — was shown to nobody. | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-354** | **The off-site full restore has NO named-volume leg — the tar is in the unit, in the snapshot and in the checking folder, and is never replayed.** Shipped in 0.217.0, 0.218.0. | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-22** (controller **v0.218.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-355** | **`paperless-ngx`'s PostgreSQL is dumped into a directory for a stack that does not exist, so its unit has never contained a database dump — and the destructive restore therefore takes no safety dump and tells the customer the app has no database.** Shipped in 0.217.0, 0.218.0. | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-22** (controller **v0.218.0**) | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-339** | **The hub was SILENT when it lost sight of the off-site stores — and a 9 h 37 m outage proved it.** **Reasoning kept:** That is **correct for a fill signal** — a missing reading must never be mistaken for 0%, which is why degraded data drives no band transition — but the consequence was that a completely dead off-site endpoint and a healthy one were **indistinguishable on the operator channel**. | **SHIPPED — hub v0.106.0, 2026-08-18.** Reachability is now a second, independent signal: consecutive failed fetch windows counted per checker, `pbsdr_box_unreachable` / `offsite_box_unreachable` (severity `warning`) past a default **3 windows (≈30–45 min)**, with paired `*_recovered` all-clears wired into `recoveredPairedDownTypes` — necessary because both recoveries are severity `info` and `severityNotifies` drops `info`. Threshold tunable via `alerting.box_unreachable_windows`. **The fill logic is untouched**: no threshold, throttle, band or escalate-once behaviour changed. Evidence: `internal/monitor/box_reachability_test.go` (Scenarios A–F) + `internal/notify/dispatcher_box_reachability_test.go` (the cross-package wiring, asserting an actual operator mail), plus three companion red-proofs each seen failing with a message naming the right cause | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-370** | **PROCESS: between 2026-08-19 and 2026-08-22 the reviewing side called a documented architectural decision a defect, in four places, because it read the register and live source and never `documentation/architecture/`.** Evidence: `documentation/architecture/`. **Reasoning kept:** R-352 (re-framed), R-369 The record is corrected in place with the framing marked rather than deleted, per the standing rule that a document which quietly changes its mind teaches nobody. | **CLOSED — corrected 2026-08-22** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-96** | **Two standing rules were agreed in chat and never committed** Evidence: `documentation/runbooks/workspace-CLAUDE.md:48-70`. **Reasoning kept:** **Two standing rules were agreed in chat and never committed** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-27, size XS, roadmap state `idea — found 2026-07-27`.** Moved verbatim; nothing added or reinterpreted. | **CLOSED — migrated from ROADMAP 2026-08-22** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
| **R-107** | **No offsite action unpacks the named-volume tars Tier-3 captures on every run.** Shipped in v0.218.0. | **CLOSED — migrated from ROADMAP 2026-08-22** | full text: `git show fddfe00ce268:documentation/backlog/OPEN-ITEMS.md` |
File diff suppressed because one or more lines are too long
+116
View File
@@ -0,0 +1,116 @@
# ROADMAP-HISTORY — finished and superseded roadmap items, compressed
> Companion to `ROADMAP.md`, created 2026-08-22 under the same operator ruling that produced
> `CLOSED-ITEMS.md`. Each entry keeps its identifier, title, shipped version and final state, and
> names the commit holding its full original text. **Nothing was deleted.**
>
> `ROADMAP.md` now carries only live intentions and the reasoning behind them, which is what its
> own first paragraph has always said it is for.
---
| **R-115** | **Publishing is a remembered step — forgotten within eight hours of being documented as forgettable** Shipped in 0.113.0, 0.114.0, v0.113.0. | **CLOSED — SHIPPED 2026-08-03** (`release-agent.sh` + `check-published-versions.py`, no version bump). Releasing now builds, tags, publishes and **verifies by an independent download** in one act; a ` | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-111** | **The Day-0 artifact channel is 17 agent releases stale — a box installed today gets agent `0.96.0`, not `0.113.0`** Shipped in 0.110.0, 0.113.0, 0.161.0. | **SHIPPED 2026-07-29** — agent 0.113.0 published (sha `5f3247f7…`, round-trip verified) + golden **0.185.1** baked and published (sha `dba00f3e…`, embeds controller 0.185.1); hub Day-0 manifest moved | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-1** | **Peti convergence** Shipped in v0.57.0. | **rehearsal DONE; Peti half open** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-2** | **~~Resolve ~215 lines of foreign WIP in felhom.eu clone (`hub/internal/notify/`, `store.go`, `hub/internal/cla** Shipped in v0.50.0, v0.54.0, v0.55.0. | **killed** (2026-07-16) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-4** | **Claim-code deliverability: test-send to gmail.com / freemail.hu; tighten DMARC `p=none` → `p=quarantine`** Shipped in 1.1.1, 8.8.8. | **DONE 2026-07-21 (all three halves)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-39** | **[P2-HIGH] The PBS DR tier can be `applied` and dead at the same time — and nothing notices.** Shipped in 0.68.1, 0.90.0, 0.90.1. | **CLOSED 2026-07-21 — PROVEN LIVE (hub 0.68.1 + agent 0.91.2)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-30** | **[P2-HIGH] Liveness presence should come from the wait channel, not the report clock.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-31** | **[P2-HIGH] Offsite provisioning is synchronous with no status affordance.** Shipped in v0.138.0. | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-32** | **[P2-HIGH] RESET must purge the customer base dir; the orphan card must stay honest; unattributed bytes must be visible.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-33** | **[P2-HIGH] Bootstrap pairing-poll spams the customer-visible console.** Shipped in v1.21.0. | **SHIPPED (scripts v1.21.0, 2026-07-18)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-5** | **Hub: offsite storage visibility — RESTIC box aggregate (v0.64.0) + PBS DR datastore (v0.65.0), each with fill** Shipped in v0.64.0, v0.65.0, v1.2.0. | **SHIPPED (hub v0.64.0 + v0.65.0 + tenantsync v1.2.0, 2026-07-17)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-7** | **server** Shipped in 1.0.0, v0.144.0. | **SHIPPED slice 1 (controller v0.144.0 + `felhom-samba:1.0.0`, 2026-07-18)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-10** | **T-6E-1: DB-dump dir-fsync asymmetry (LOW, confirmed in 6E)** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-11** | **Tester-facing one-pager: what the box does, known limitations, how to report** | **RULED 2026-07-21 (channel); doc is the architect's** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-16** | **Operator hygiene: campaign6 autofs orphan (clears on host reboot) + tied-CreatedAt flash duplicates (audioboo** Shipped in v1.17.0. | open (doc-drift bit CLOSED) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-22** | **PBS-DR pre-check self-grant (F4).** Shipped in v0.89.0. | **SHIPPED + PROVEN-LIVE agent v0.89.0** (2026-07-17) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-17** | **Old-box archive (u629193-sub1) retirement — 9/9 byte-identical restores verified** | **CLOSED 2026-07-22 — archive deleted (operator console)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-198** | **The superseded-escrow retention kept the K-escrow and dropped the identity blob** Shipped in v0.60.0, v0.93.0. | **SHIPPED (hub v0.93.0, 2026-08-04)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-197** | **The hub stored both halves of "did this box's offsite data key change" and compared them nowhere** Shipped in v0.93.0. | **SHIPPED (hub v0.93.0, 2026-08-04)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-196** | **Five comments claimed `ReissueCredentials` rotates the restic repo password** Shipped in v0.93.0, v0.95.0. | **CLOSED — behaviour shipped hub v0.95.0 (2026-08-05)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-199** | **The hub served recovery blobs on endpoints with no client anywhere** Shipped in v0.125.0, v0.94.0. | **SHIPPED + PROVEN-LIVE (hub v0.94.0 + agent v0.125.0, 2026-08-04)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-200** | **The password-injection seam had a handler and no form** Shipped in v0.195.0. | **plumbing SHIPPED (controller v0.195.0); the form is NOT built** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-203** | **A mandatory customer data directory was silently absent from the off-site snapshot while the run reported `ok`** Shipped in v0.197.0. | **SUPERSEDED 2026-08-22 — the register records SHIPPED + PROVEN-LIVE (controller v0.197.0, 2026-08-04); this row was never updated when it shipped** (was:**OPEN — halted the R-201 drill 2026-08-04**) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-203** | **The app and its backup looked in different directories, and a run that skipped a mandatory folder still said `ok`** Shipped in v0.197.0. | **SHIPPED + PROVEN-LIVE (controller v0.197.0, 2026-08-04)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-204** | **The recovered key cannot be used: the Re-issue that reconfigures a rebuilt box's off-site tier marks the escrow stale, which gates every run, and the only way to clear it destroys the key** Shipped in v0.199.0, v0.96.0. | **ALL FOUR ITEMS SHIPPED (controller v0.199.0 + hub v0.96.0, 2026-08-05)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-193** | **A rebuilt box's off-site history was unreachable, and later reachable only from a command line** Shipped in v0.199.0, v0.200.0, v0.96.0. | **CLOSED (2026-08-05)** — credential half controller v0.199.0 + hub v0.96.0; the customer-facing SCREEN controller v0.200.0 | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-201** | **The wipe-and-recover drill** | **PASSED + PROVEN-LIVE (2026-08-04 night)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-20** | **~~Verify operator-key pinning is fully in the day-0 install flow~~** | **closed** (2026-07-16) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-23** | **Immediate-sync Direction-2 follow-ups** Shipped in 0.153.0, 0.154.0, 0.155.0. | **(a) BANKED in full; only (b) cosmetic remains** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-35** | **Config-apply should not end the customer's session.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-36** | **Post-RESET re-enroll leaves offsite "enabled but unprovisioned" — silently.** Shipped in v0.67.0. | **SHIPPED (hub v0.67.0, 2026-07-18)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-50** | **[P2-HIGH] Island-bridge control plane — make controller↔agent independent of the LAN.** Shipped in 0.96.0, 169.254.253, 192.168.0. | **SHIPPED 2026-07-25 — fleet-migrated (agent v0.96.0 + host-install v1.19.0)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-51** | **Dead-primary alerting — a multi-container app whose MAIN container is dead must alert.** Shipped in v0.156.0. | **SHIPPED 2026-07-21 — controller v0.156.0** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-52** | **Boot desired-state reconciliation — a `deployed: true` app should be running after boot.** Shipped in v0.156.0. | **SHIPPED 2026-07-21 — controller v0.156.0** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-54** | **[P2-HIGH] The guest's DHCP client is unsupervised — its death takes the box off the internet 1-2 hours later, invisibly.** Shipped in 0.92.0, 0.92.1, 192.168.0. | **SHIPPED 2026-07-21 — agent v0.92.1** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-55** | **[P2] A customer's deliberate Stop does NOT survive a guest reboot for any drive-backed app** Shipped in v0.157.0. | **SHIPPED 2026-07-21 — controller v0.157.0** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-57** | **An app can be withdrawn from the catalog without orphaning the customers already running it** Shipped in v0.158.0, v0.158.1. | **SHIPPED 2026-07-21 — controller v0.158.0 (+ v0.158.1 fix), LIVE-PROVEN** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-59** | **[P1] A no-DHCP install must HARD-ABORT — instead it bakes the installer's fallback address as a STATIC config and completes, producing a box that can never call home.** Shipped in 192.168.100, v1.24.0. | **SHIPPED v1.24.0 (2026-07-22) — as a FIRST-BOOT refuse-loudly gate, with a RECORDED DEVIATION: the install-time abort is out of scope (the 192.168.100.2 fallback is baked inside the Proxmox auto-inst | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-60** | **[P2] First-boot NIC sweep self-heal: if the hub is unreachable, try DHCP across every carrier-bearing NIC before settling.** Shipped in v1.24.0. | **SHIPPED v1.24.0 (2026-07-22) — spike + nested drill proven (SPIKE-firstboot-nic-sweep-2026-07-22.md): cable move → sweep → heal + hub registration unaided in <1 min; sweep is structurally first-boot | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-61** | **[P1] The baked root password must be knowable by the operator — the recurring console lockout.** Shipped in v1.24.0. | **slice 1 SHIPPED v1.24.0 (2026-07-22): the build emits the plaintext into a 0600 sibling `<iso>.rootpw.txt` (single record of truth — never logged/manifested/committed); drill-verified against the in | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-63** | **The install console learns ő/ű** Shipped in 0.153.0, 0.156.0, 0.161.0. | **SHIPPED (scripts v1.25.0, 2026-07-23)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-66** | **The box's own address becomes visible — „Hálózat" card, Debug network dump, NetBIOS hint.** Shipped in v0.159.0. | **SHIPPED (controller v0.159.0, 2026-07-22)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-67** | **The NAS share appears in FileBrowser — browse what you mounted.** Shipped in v0.160.0. | **SHIPPED (controller v0.160.0, 2026-07-22)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-68** | **Notification train: paired recovery mails + prefs seeding at claim + priority headers (power-outage audit F11+F12+F14-light).** Shipped in 0.160.0, v0.71.0. | **SHIPPED (hub v0.71.0, 2026-07-22)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-70** | **[P2-HIGH] The offsite last mile is invisible on BOTH surfaces — the hub cannot tell "staged" from "delivered" from "applied".** Shipped in v0.161.0, v0.72.0. | **SHIPPED (hub v0.72.0 + controller v0.161.0, 2026-07-23)** — detector `offsite.DeliveryStateFor` (one impl, all consumers), customer-card state line with age (static "delivered once" copy GONE), `off | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-71** | **[P1] Day-0 race: the managed floor-update kills the offsite apply-bridge between password-consume and persist — the one-shot credential is burned and the box lands in the silent consume-404 dead-end forever.** Shipped in 0.153.0, 0.156.0, v0.162.0. | **SHIPPED — (a)+(c); (b) rejected-by-design.** **(a) SHIPPED (controller v0.162.0, 2026-07-24): the apply-bridge settle-gate.** `offsiteapply.SettleProvider.SettleState()` + `SettleFunc` adapter over | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-53** | **`app_export.html` substituted the CSRF token where the customer domain belongs** Shipped in v0.150.0. | **SHIPPED (controller v0.150.0, 2026-07-20)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-102** | **Tier-2 writes a full `recovery-unit/` mirror on every run and no code path reads it.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: READY — 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-103** | **The Tier-2 no-coverage refusal names the working action but does not route to it.** Shipped in v0.183.0. | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: READY — 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-104** | **An interrupted offsite run leaves an exclusive restic lock the existing self-heal cannot reach.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: READY — 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-105** | **Three hub-held DR records are empty on the entire live fleet.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: READY — 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-106** | **~~The DR recipe records the PBS namespace as `"root"` on every box~~** Shipped in v0.118.1. | **SHIPPED** — agent v0.118.1, 2026-07-30 | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-107** | **No offsite action unpacks the named-volume tars Tier-3 captures on every run.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: READY — 2026-07-28) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-108** | **~~Network storage can host an app's namespace, and FileBrowser binds a network share at its ROOT — this BLOCK** Shipped in v0.187.0. | **SHIPPED** — controller v0.187.0, 2026-07-30 | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-109** | **~~The DR recipe records no backup target~~** Shipped in v0.118.1, v0.83.0. | **SHIPPED** — agent v0.118.1 + hub v0.83.0, 2026-07-30 | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-27** | **Customer-facing self-bind page (R-21 slice C follow-on).** Shipped in v0.62.0, v0.66.0. | **SHIPPED (slice 1, hub v0.66.0, 2026-07-17)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-25** | **Device-node TOCTOU hardening (drive init).** Shipped in v0.141.0. | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-24** | **Guest RAM resize (live) — SHIPPED (agent v0.90.0 + controller v0.143.0, 2026-07-17).** Shipped in v0.143.0, v0.90.0. | **SHIPPED + PROVEN-LIVE** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-13** | **OOB management arc: dual-use existing WireGuard + hub desired-state channel as mutual-repair** Shipped in 10.77.0, v0.59.0, v0.89.0. | **first slice PROVEN-LIVE (poke channel)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-28** | **Agent fast-tick-until-first-convergence — SHIPPED (agent v0.90.0, 2026-07-17).** Shipped in v0.90.0. | **SHIPPED** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-73** | **SMART history / trending (hub-side) — SUPERSEDED by the phased disk-health arc; see R-330 (Phase 2) and R-331 (Phase 3)** Shipped in v0.215.0. | **partly SHIPPED** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-75** | **Catalog-derived userdata skeleton + import surfaces** Shipped in v0.172.0. | **SHIPPED (controller v0.172.0 + catalog, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-77** | **Endpoint-drift detection, samba protected-set gate, channel log honesty** Shipped in 169.254.253, v0.173.0, v0.74.0. | **SHIPPED (controller v0.173.0 + hub v0.74.0, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-78** | **`local_api` authority ruling — auto-reconcile vs detect-only** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea (deferred OUT of R-77 on purpose)) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-79** | **`report.Issues` / `report.Warnings` are English on customer-facing surfaces** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-80** | **`expected_backup_missed` false alarm — diagnosed + class-fixed** Shipped in v0.75.0. | **SHIPPED (hub v0.75.0, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-81** | **"No signal" is not "bad signal" — anchor the backup deadline check** Shipped in v0.12.0, v0.73.0, v0.75.0. | **SHIPPED (hub v0.75.0, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-82** | **The backup target split — local daily + offsite weekly** Shipped in 1.20.0, v0.100.0, v0.101.0. | **SHIPPED (agent v0.102.0 + controller v0.175.0 + hub v0.76.0 + host-install 1.20.0, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-83** | **Ratify or retire `07-backup-architecture.md`** | **DISCHARGED (2026-07-26) — brought current, NOT ratified** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-84** | **The agent's cold backup `Store` no longer causes a redundant backup** Shipped in v0.103.0. | **SHIPPED (agent v0.103.0, 2026-07-26)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-85** | **The DR tier must be restore-tested UNATTENDED, and its failure must be HEARD** Shipped in v0.104.0, v0.77.0. | **Code SHIPPED (agent v0.104.0 + hub v0.77.0, 2026-07-27); rotation NOT YET OBSERVED LIVE** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-86** | **Backup-ALIGNED restore-test scheduling — test a tier ~1 day after ITS OWN backup** Shipped in v0.121.0, v0.91.0. | **CLOSED — SHIPPED + PROVEN-LIVE 2026-08-03 (agent v0.121.0 + hub v0.91.0)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-88** | **An UNREACHABLE backup target reads as "no backup exists" → the agent fires a doomed backup at it** Shipped in 10.77.0, v0.176.0. | **Part 1 SHIPPED (controller v0.176.0, 2026-07-27); Part 2 OPEN (agent wire change)** — **Part 1** added the failure breaker: consecutive failures tracked per TARGET, backoff `15m→30m→1h→2h→4h` capped | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-89** | **Retention is a COMMERCIAL attribute — it belongs to the hub, not to ep0 or a box** | idea — operator ruling 2026-07-27, first increment SHIPPED same day | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-97** | **The whole-guest backup tier has NO failure signal to the hub — `internal/quiesce` never notifies** Shipped in v0.164.0, v0.177.0, v0.78.0. | **SHIPPED (controller v0.177.0 + hub v0.78.0, 2026-07-27)** — **R-97a:** `quiesce.TierNotifier`, a seam (not an import) wired by an init-only setter, edge-triggered on the R-88 breaker ARMING so a fai | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-94** | **~~A hand-synced version constant drifts, and the gate that would catch it is never run~~** Shipped in 9.9.9, v0.87.0. | **CLOSED — SHIPPED hub v0.87.0, 2026-08-02** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-110** | **`main` is the installer's publish channel — there is no staging** Shipped in 1.22.0, v1.23.0. | **CLOSED — SHIPPED 2026-08-03** (installer v1.23.0). `/scripts/` syncs `installer-v1.23.0`; the website still tracks `main`. Proven by HTTP: a push to `main` left the served bytes byte-identical, movi | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-128** | **`ISO_VERSION` "aligns with SCRIPT_VERSION" was a comment nothing evaluated** Shipped in v1.26.0. | **CLOSED — iso v1.26.0, 2026-07-31** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-154** | **`[first-boot]` is automated-install-only, and nothing in the tree said so** Shipped in v1.26.0. | **CLOSED — iso v1.26.0, 2026-07-31** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-155** | **`iso-repack.sh` refused any ISO without `auto-installer-mode.toml`** Shipped in v1.26.0. | **CLOSED — iso v1.26.0, 2026-07-31** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-156** | **An app's data is neither persisted nor backed up, and it reports healthy** | **CLOSED — all three apps fixed, 2026-08-03.** papra's template now mounts `papra_data:/app/app-data` (the app's own data ROOT, chosen over reconfiguring three env vars so a future upstream path canno | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-157** | **`bootrecon`'s start-ONCE sweep misses the boot orphan it exists to recover** Shipped in v0.189.0, v0.190.0. | **CLOSED — SHIPPED + PROVEN-LIVE (v0.189.0 + v0.190.0, 2026-08-02)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-170** | **The drive-backed boot gate infers a Stop from a container count** Shipped in v0.190.0. | **CLOSED — SHIPPED + PROVEN-LIVE (v0.190.0, 2026-08-02)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-171** | **The boot sweep started apps whose data drive was ABSENT** Shipped in v0.189.0, v0.190.0. | **CLOSED — SHIPPED + PROVEN-LIVE (v0.190.0, 2026-08-02)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-166** | **App state gets a desired/observed model with its own store** Shipped in v0.189.0. | **SHIPPED + PROVEN-LIVE — controller v0.189.0, 2026-08-02** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-158** | **~~A local Tier-1 backup failure reaches no hub channel~~** Shipped in v0.191.0, v0.89.0. | **SHIPPED — controller v0.191.0 + hub v0.89.0, 2026-08-02** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-167** | **~~Storage monitoring and backup alerts (decision D-c)~~** Shipped in v0.191.0, v0.89.0. | **SHIPPED — controller v0.191.0/.1/.2 + hub v0.89.0, 2026-08-02** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-165** | **~~Merge `mp1` into `mp0` — the dedicated backup partition stops existing (decision D-a)~~** Shipped in v0.120.0, v0.192.0, v0.193.0. | **CLOSED — PROVEN-LIVE 2026-08-03.** Variant V-c shipped (golden v3.0.0 + agent v0.120.0 + controller v0.192.0); both demo boxes reinstalled and proven end to end (R-178). **Its stated replacement for | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-159** | **wishlist's data landed in an ANONYMOUS volume — never backed up, orphaned by a redeploy** | **SHIPPED** (`templates/wishlist/`, 2026-08-02) — filed for the CLASS | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-160** | **gramps-web persisted three paths and wrote to none of them** | **SHIPPED** (`templates/gramps-web/`, 2026-08-02) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-161** | **The volume-persistence gate is enforced by convention, not automatically** | **RULED + SHIPPED at reduced scope** (operator, 2026-08-02; `app-catalog` `fd7747d`) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-163** | **`mp1` is RETENTION, not staging — and it is sized as if it were neither** Shipped in v3.0.0. | **SUPERSEDED 2026-08-22 — the register records CLOSED by R-165 (golden v3.0.0, 2026-08-03): the ceiling this row describes no longer exists** (was:**WAITING-ON-OPERATOR** — the ratio is a tier-sizing | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-90** | **ep0 has 3.8 GB and NO swap — it OOMs under a restore-test, and that gates R-86** | **CLOSED 2026-08-03 — the operator rescaled ep0 to a CX33.** MEASURED on the box, not read from an invoice: `Mem: 7757` MB (**8 GB**, was 3.8), `nproc` **4**, and the 4 GiB swapfile added 2026-07-27 * | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-96** | **Two standing rules were agreed in chat and never committed** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea — found 2026-07-27) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-76** | **FileBrowser-created folders break the setgid chain, and a drop-zone's mode is not stable** Shipped in 1.3.3. | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea (surfaced by the R-75 spike, 2026-07-26)) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-21** | **Bare-metal Felhom ISO** Shipped in 0.92.1, v0.62.0, v1.18.0. | **SHIPPED + PHYSICALLY CLOSED (slices A+B+C; rehearsal executed 2026-07-18)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-168** | **~~CI: no runner exists, and with trunk-based pushes CI can DETECT but not BLOCK~~** | **CLOSED — SHIPPED 2026-08-02** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-37** | **Post-RESET health card shows stale pre-RESET warnings.** Shipped in v0.67.0. | **SHIPPED (hub v0.67.0, 2026-07-18)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-38** | **Installer GRUB slice.** Shipped in v1.21.0, v1.22.0. | **SHIPPED (scripts v1.22.0, 2026-07-19)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-40** | **[P2-HIGH] The update path cannot express a MULTI-HOP major upgrade.** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-42** | **RULED: sidecar majors follow the APP, never the newest tag.** | **RULED 2026-07-21 — option (a)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-43** | **[P2-HIGH] No offsite restore path can restore a database — offsite restore cannot reconstitute a DB-indexed app.** Shipped in v0.148.0. | **SHIPPED controller v0.148.0 (2026-07-19) — live acceptance PENDING** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-44** | **[P2-HIGH] A manual offsite push ships an unrefreshed DB dump — "backed up now" is false for the DB half.** Shipped in v0.148.0. | **SHIPPED controller v0.148.0 (2026-07-19)** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-47** | **[P2-HIGH] The DB replay races the application's own schema repair.** Shipped in 0.153.0, 0.90.0, v0.153.0. | **SHIPPED — controller v0.153.0, 2026-07-20** | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
| **R-49** | **[P2] The offsite capture set is ~90% cache and duplication — 1.1 GB of a 1.2 GB immich "photo backup".** | **MOVED 2026-08-22 -> OPEN-ITEMS.md** (was: idea) | full text: `git show fddfe00ce268:documentation/backlog/ROADMAP.md` |
File diff suppressed because one or more lines are too long
+22
View File
@@ -1,3 +1,25 @@
## one_register_gate.py v1.0.0 + repo_gates registration — one register, enforced (2026-08-22)
**Operator ruling: one register.** `OPEN-ITEMS.md` calls itself the single source of truth for open
work; `ROADMAP.md` also held it. R-107 — a READY finding filed 2026-07-28 — sat in the roadmap alone
and was invisible to every rule that says *"grep the register"*, until an overnight drill rediscovered
it from scratch **25 days later**.
The gate fails when a roadmap row is **neither an idea nor done** and has no counterpart row in the
register. **The predicate is the roadmap's own state column**, so it reads data that already exists.
**Two things it taught while being built, both worth keeping:**
1. **Match the LEADING verdict, not the whole field.** A first cut matched the state words anywhere in
the row — and a finding's body routinely contains "shipped". The same bug appeared independently in
this session's compressor, where `PARTLY CLOSED` and `OPEN — NOT FIXED` both read as closed and
moved six still-open rows out of the register (R-378).
2. **`BANKED` and `PROVEN-LIVE` are this project's own done-words**, found by running the gate rather
than by reading the vocabulary.
**Residual holes are in the docstring, not implied:** a finding filed as `idea` escapes, a finding with
no `R-` id escapes entirely, and the gate checks that a counterpart exists — never that the two agree.
## render_stands.py — the page stopped disagreeing with its own source (2026-08-22) ## render_stands.py — the page stopped disagreeing with its own source (2026-08-22)
**The header's commit shas were hardcoded in the renderer, not read from the YAML.** `verified_on` **The header's commit shas were hardcoded in the renderer, not read from the YAML.** `verified_on`
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""one_register_gate.py — ONE register. Operator ruling, 2026-08-22 (R-369).
WHAT IT CONVICTS ON (a FAIL, exit 1): a row in `ROADMAP.md` that is
* neither an IDEA (a proposal for something that does not exist yet), nor
* DONE (shipped / closed / killed / ruled / moved),
and that has **no counterpart row in `OPEN-ITEMS.md`**.
WHY IT EXISTS, and the cost that bought it. Two files held open work and only one of them called
itself the source of truth. R-107 — "no offsite action unpacks the named-volume tars Tier-3
captures" — was written up properly on 2026-07-28, marked READY, and put here. Every standing rule
says "grep the register before minting", and every one of them greps the other file. So it was
invisible, and on 2026-08-21 an overnight drill rediscovered it from scratch by planting files and
watching them not come back. **25 days.**
THE PREDICATE IS THE ROADMAP'S OWN STATE COLUMN, deliberately — it already distinguishes `idea` from
`READY`, so this gate reads data that exists rather than asking anyone to maintain a new marker.
WHAT THIS GATE CANNOT SEE — the residual holes, named rather than implied:
1. **A finding filed with the state `idea` escapes.** The state column is a human judgement, and a
defect written under `idea` looks exactly like a proposal to this gate. It is the same shape as
the problem it fixes, one level up.
2. **A finding written in prose with no `R-` identifier escapes entirely.** This gate matches ids.
A survey that enumerates a gap and never numbers it is invisible here — that is the previous
session's sweep territory and the PROMPT-TEMPLATE rule "an enumerated gap becomes a row".
3. **A finding that never reaches the roadmap at all escapes.** Nothing here reads audits, spikes
or inventories.
4. It checks that a counterpart EXISTS, never that the two say the same thing. A stale register row
beside a live roadmap row passes.
Run: python3 scripts/one_register_gate.py [--fast]
"""
import io
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ROADMAP = os.path.join(ROOT, "documentation", "backlog", "ROADMAP.md")
REGISTER = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md")
ROW = re.compile(r"^\|\s*\*{0,2}R-(\d+)\*{0,2}\s*\|")
# DONE and IDEA are matched against the row's STATE cell only, never the whole row: the body of a
# finding routinely contains the word "shipped" while describing something else.
# BANKED and PROVEN-LIVE are this project's own done-words and were found by running the gate: a row
# reading "BANKED in full" or "first slice PROVEN-LIVE" is shipped work, not an open finding.
DONE = re.compile(r"\b(SHIPPED|CLOSED|KILLED|DONE|SUPERSEDED|OBSOLETE|WITHDRAWN|MERGED|DISCHARGED|"
r"RULED|MOVED|BANKED|PROVEN-LIVE)\b", re.I)
IDEA = re.compile(r"\b(IDEA|SPIKED|PARKED|DEFERRED|BACKLOG|PROPOSAL)\b", re.I)
def rows(path):
"""Yield (id, state_cell, whole_line) for every R- row in a pipe table."""
for line in io.open(path, encoding="utf-8"):
line = line.rstrip("\n")
m = ROW.match(line)
if not m:
continue
cells = line.split("|")
state = cells[4].strip() if len(cells) > 4 else ""
yield m.group(1), state, line
def main():
if not os.path.exists(ROADMAP) or not os.path.exists(REGISTER):
print("one-register gate: a backlog file is missing — FAILURE, never a skip")
return 1
have = set(rid for rid, _, _ in rows(REGISTER))
offenders, ideas, done, ok = [], 0, 0, 0
for rid, state, line in rows(ROADMAP):
if DONE.search(state):
done += 1
continue
if IDEA.search(state):
ideas += 1
continue
if rid in have:
ok += 1
continue
title = re.sub(r"\s+", " ", line.split("|")[2] if len(line.split("|")) > 2 else line)
offenders.append((rid, state, title.strip()[:120]))
print("one-register gate — ROADMAP rows: %d done, %d ideas, %d open-with-a-register-row, "
"%d WITHOUT" % (done, ideas, ok, len(offenders)))
if offenders:
print()
print("CONVICTED — open work in ROADMAP.md with no row in OPEN-ITEMS.md:")
for rid, state, title in offenders:
print(" R-%-5s state=%-24s %s" % (rid, state[:24], title))
print()
print("OPEN-ITEMS.md is the single source of truth for open work (its own first line).")
print("Either move the row there keeping its id and filing date, or — if it is a proposal for")
print("something that does not exist yet — mark its state `idea`, which is what it is.")
return 1
print("one-register gate OK — every open ROADMAP row has a register counterpart.")
return 0
sys.exit(main())
+4
View File
@@ -89,6 +89,10 @@ GATES = [
("hub-copy", os.path.join(SCRIPTS, "hub_copy_gate.py"), [], True), ("hub-copy", os.path.join(SCRIPTS, "hub_copy_gate.py"), [], True),
# R-341 — dated checks in the register were prose that nothing read. Fast: stdlib file read. # R-341 — dated checks in the register were prose that nothing read. Fast: stdlib file read.
("due-checks", os.path.join(SCRIPTS, "due_checks_gate.py"), [], True), ("due-checks", os.path.join(SCRIPTS, "due_checks_gate.py"), [], True),
# R-369 — two files held open work and only one called itself the source of truth, so a READY
# finding sat in ROADMAP.md for 25 days invisible to every "grep the register" rule and was
# rediscovered by an overnight drill. Fast: two file reads.
("one-register", os.path.join(SCRIPTS, "one_register_gate.py"), [], True),
] ]
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"} VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}