# REPORT — one register, and a rule that keeps it readable (2026-08-22) **Records and process only. No controller, agent or hub code. No release, no bake, no machine contacted.** Previous report preserved at `documentation/audits/REPORT-mis-called-defect-and-sweep-2026-08-22.md`. --- ## 1. Baselines — confirmed 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? | |---|---|---| | ids **mentioned** in ROADMAP minus ids mentioned in the register | **72** at commit `5a7502b`, **61** today | **no** | | ROADMAP **rows** minus register **rows** | **90**, unchanged across all five commits checked | **yes** | | …of those 90, marked shipped/closed/killed/ruled | **59** | — | | …of those 90, **not** so marked | **31** | — | **Why 72 became 61 without anything moving:** my own **R-369** row, written last session, *names* 11 of those identifiers in its prose. A "mentioned" count therefore falls when someone merely writes about the problem. **That measure is unusable for sizing a migration.** **And 59 is real** — it is the *history* half of the row-based population. So the prompt's figure and mine were measuring the two halves of the same 90. **The count that matters is 31**, and the gate later found the true figure is **32** (§4). --- ## 3. The population, and the rule used to sort it **The rule, stated so it need not be re-invented:** > **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 > does not exist yet — a feature, a spike, a curation task — there is nothing to be wrong about, and > it stays in the roadmap as an intention. 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 | |---|---|---| | **open work — findings and owed items** | **17** | **moved to the register**, keeping identifier, evidence and original filing date | | **ideas and proposals that were never findings** | **15** | **stay in `ROADMAP.md`** — see below | | **already closed** | **59** | stay as history in the roadmap | **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… 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, R-56, R-58, R-62, R-65, R-69, R-72. --- ## 4. The migration — 17 items, and the oldest 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. | id | originally filed | note | |---|---|---| | **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 | **Staleness named, not edited away**, as instructed: - **R-104** — *partly stale.* The self-heal it calls unreachable **was built**: `resticStep` escalates to `unlock --remove-all` and retries once (`offbox.go:763-768`), and `unlockStale` runs 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. **No halt.** I checked the three READY findings for urgency before migrating them quietly. R-104 is the one that reads urgent — *"the tier stays dead until a human runs `restic unlock --remove-all`"* — and **it is not**, for the reasons above. Nothing in the 31 is both open and urgent. --- ## 5. The gate — and it convicted me before it convicted anything else `scripts/one_register_gate.py`, wired into `scripts/repo_gates.py` as **`one-register`** (11 gates now, all OK). It fails when a `ROADMAP.md` row is **neither an idea nor done** and has **no counterpart row in `OPEN-ITEMS.md`**. The predicate is the roadmap's **own state column**, so it reads data that already exists rather than asking anyone to maintain a new marker. **The control — plant → convict → remove → pass:** | 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. Housekeeping — sizes before and after | file | before | after | | |---|---|---|---| | `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 the thing being fixed. Closed work compressed to **17%** of its bytes in both files. **Nothing was deleted.** Every compressed entry ends `full text: git show fddfe00ce268:`. **Load-bearing reasoning was not compressed away.** Rather than judge 134 entries by hand, the compressor **keeps any sentence stating a rule, a fence or a deliberate refusal, verbatim**, under **Reasoning kept** — 25 entries carry one. Spot-checked: R-320's kept sentence itself records that its rule is now standing rule 5 in `workspace-CLAUDE.md`, and R-110's rule is in `felhom.eu/CLAUDE.md` ("The installer publishes by TAG, not by push (R-110)") — **both already homed outside the register, verified by grep with a negative control.** ### The compressor moved six rows it should not have — caught and reversed **`PARTLY CLOSED` and `OPEN — NOT FIXED` both matched a closed-vocabulary applied to the whole status field.** R-123, R-190, R-214, R-264, R-295 and **R-352** were moved out of the register. Caught by a follow-up check in the same session and **restored verbatim from commit `fddfe00ce268`** — not from 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 leading verdict rather than the whole field. --- ## 7. Why `CONTEXT.md` was not compressed — a disagreement with the task, stated **86% of that file — 187,913 of 217,260 bytes — is one `## Standing rulings` section**, carrying 39 `S-` ids and 153 bullets under a single heading. **Standing rulings are live reasoning, not finished work.** **This prompt's own §3.4 says the decision log is *"dated, never edited afterwards"*** and is *"the 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. **So the problem is navigational, not volumetric, and the fix is structural:** give each ruling a sub-heading with its `S-` id and date, and it becomes linkable and findable **without a word being edited**. Filed as **R-377 (LOW)** rather than done, because it is a careful pass of its own. 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)** > > 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.** … **Where it is a decision, mark the > 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 > 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. --- ## 10. Part 4.2 — the storage default **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 is that it lives in the **template, not the server**, so the API has no default and there is nothing server-side to test. **No row anywhere still asserts the default never applies.** The one occurrence of *"the deploy route 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. --- ## 11. Rows and ceiling **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.** **CI:** `felhom.eu` **id=384 / run_number=252**, head `ef6ac6fe` — **success**. Commit `ef6ac6f`, pushed to `main`, all **11** gates OK, tree clean. No other repo touched. --- ## 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:** - **`ROADMAP.md` has no trailing newline.** It broke my own gate control and would break any future `>>` append. One byte; not fixed here because it belongs to whoever next edits that file deliberately. - **R-203 has two contradictory rows in the roadmap** — one open, one shipped. Only the open one was marked superseded; the duplicate remains as history. - **The migrated rows are large.** Seventeen verbatim roadmap rows are now in the register, which is why it fell 51% rather than further. They are open work and keep their detail, by the rule. - **`OPEN-ITEMS.md` is still 327 KB.** The remaining bulk is open rows with long evidence sections — that is detail doing a job, and the next reduction comes from closing work, not from editing it. - **The one-register gate compares existence, not content.** R-203/R-163 were caught only because the register had *no* row for them after the split; a stale-but-present row would pass. Named in the gate's own docstring as residual hole 4.