ef6ac6fe74
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.
309 lines
18 KiB
Markdown
309 lines
18 KiB
Markdown
# 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.
|