Backlog triage Part B: 125 finished rows + 20 id-less rows moved to CLOSED-ITEMS (full text at 9e2786c); open rows normalised to one 6-column shape; narratives archived verbatim; closed_register_gate RULE 3 refuses a finished row in OPEN-ITEMS (decoys, seen red); rules rehomed to CONTEXT + 07 §11; loose notes triaged; R-814..R-819 filed; register 444 -> 325
gates / gates (push) Successful in 30s
gates / gates (push) Successful in 30s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -20,6 +20,8 @@ repo. Sibling repos point here; this is where the pointed-at thing must actually
|
||||
| logging levels and phrasing | `runbooks/logging-conventions.md` |
|
||||
| a spike or campaign result | `audits/` |
|
||||
| every open finding | `backlog/OPEN-ITEMS.md` |
|
||||
| every finished finding, compressed | `backlog/CLOSED-ITEMS.md` |
|
||||
| finished notes and history moved out of a register | `archive/` |
|
||||
| the operator's one-screen view | root `STATUS.md` |
|
||||
|
||||
**Do not restate a fact that has a home** — point at it. Re-check an address rather than trusting one
|
||||
|
||||
@@ -131,7 +131,8 @@ something, not only sessions that touch `documentation/` — which is why it is
|
||||
- **`CHANGELOG.md` + `REPORT.md`** in every repo touched (see the workspace root for the rule, and
|
||||
the parallel-session caveat above).
|
||||
- **`REUSE.md`**, if a shared helper or pattern moved (same commit).
|
||||
- **`OPEN-ITEMS.md`** — every finding, with a number.
|
||||
- **`OPEN-ITEMS.md`** — every finding, with a number. **A row you close moves to `CLOSED-ITEMS.md` in the
|
||||
same commit** — `closed_register_gate.py` RULE 3 refuses a finished row left in the open register.
|
||||
- **Root `STATUS.md`** — at the end of every session in which something shipped, broke or was
|
||||
decided. It is a **view** of `OPEN-ITEMS.md`; nothing may exist only there. One screen, written for
|
||||
the operator in plain language, and deliberately **not** `CONTEXT.md`.
|
||||
|
||||
+25
@@ -25,6 +25,31 @@
|
||||
> reverse:** the severity scale (P1 now · P2 before the first paying customer · P3 during the first customers · P4 later)
|
||||
> and the eleven categories, both written at the top of `OPEN-ITEMS.md`.
|
||||
|
||||
> **Rules carried out of rows closed 2026-10-03 (`PROMPT-TEMPLATE.md` §N.7 item 2).** The triage moved 125 finished
|
||||
> rows to `CLOSED-ITEMS.md`; these sentences stated a rule and had no other durable home. Verbatim, with their row.
|
||||
> Decisions: **[DESIGN]** R-245 — *"An automatic ending should trigger on the harm, with a dated warning, never on a
|
||||
> date alone."* R-303 — *"the wrong fix (suppressing the orphan card during a countdown) would hide a real second
|
||||
> fault."* R-312 — *"retention is an operator-only capability, and nothing anywhere may promise the customer can
|
||||
> perform it themselves"* (all three: `07` §11, "Decided, and deliberately not built"). Method and traps: R-201 —
|
||||
> the drill's pass condition is *"a byte-identical sentinel sha256, not 'the store opened'"*, and *"a good snapshot
|
||||
> is not durable against a later bad run on the same day."* R-209 — *"`RequiresMountsFor` on a path with NO mount
|
||||
> unit is a SILENT NO-OP"*; *"`-X` is load-bearing — overlayfs stacking rides `trusted.overlay.*`."* R-520 —
|
||||
> *"`pct mount` maps the guest's ROOTFS ONLY and does not apply the guest's own internal mounts"*; *"An empty
|
||||
> directory is not evidence of an absent file."* R-582 — *"a guard ported between languages must be re-derived from
|
||||
> what the claim IS in the new language, not translated word for word"*. R-583 — *"the surface you would use to
|
||||
> CHECK a feature is the one most worth checking first — a broken instrument that reports success is worse than a
|
||||
> broken feature."* R-601 — *"a 'no access' claim must list what was tried; it does not say the list makes the claim
|
||||
> true"*; *"Six failed routes to a stale address are six failures of one assumption, not six pieces of evidence."*
|
||||
> R-611 — *"the report's FIRST section is 'not done', even when empty"*, and every part of a brief skipped,
|
||||
> shortened or changed is listed there with the reason. R-623 — *"an instrument that can report a success as a
|
||||
> timeout is not a measurement"*. R-633 — *"A refusal from the wrong rule is not evidence for the new one."* R-715 —
|
||||
> a catalog gate refuses a probe without a before/after measurement in its comment. R-736 — *"the Remove button runs
|
||||
> `RemoveStack`, not `DeleteStack`; `docker image ls` without `-a` hides the untagged digest-pulled images"*. R-745 —
|
||||
> *"the agent rolls back to the RUNNING image (what `/etc/felhom-controller-image` named), never to a previous one
|
||||
> the controller hands it"* (`09` decision 56's wording may say otherwise — R-817). R-752 — a settings-only change
|
||||
> reaches the stack file at the next sync (≤15 min) and the running app only at the next `compose up -d`
|
||||
> (Restart/Start, an Update, or a backup's restart).
|
||||
|
||||
> **2026-10-02 (afternoon) — the persistence re-sweep (R-801/R-788 CLOSED), controller v0.288.0 (R-800), golden 0.288.0.**
|
||||
> Rulings `09` §3 66 (licences: Tandoor like SparkyFitness; Emby/Plex/n8n kept; lawyer review before the first paying
|
||||
> customer, R-802) and 67 (userdata stays on every remove; dialog + result name it — `07` §6.5). The gate now sends
|
||||
|
||||
@@ -394,8 +394,9 @@ or **what is open changed**), update **all four** in the SAME session:
|
||||
> later by an overnight drill that planted files and watched them not come back**, and shipped as
|
||||
> R-354. The work was right the first time; only the filing was missing.
|
||||
|
||||
**Report which `OPEN-ITEMS.md` rows the task opened, closed or re-ranked** (§15). Every row carries
|
||||
an owner — a row nobody owns is how items got lost in the first place.
|
||||
**Report which `OPEN-ITEMS.md` rows the task opened, closed (and moved to `CLOSED-ITEMS.md`), narrowed
|
||||
or re-ranked** (§15). Every row carries an owner — a row nobody owns is how items got lost in the first
|
||||
place.
|
||||
|
||||
### N.6 Website version bump (if controller/hub version is shown on the site).
|
||||
|
||||
@@ -406,10 +407,14 @@ over half of it finished work, one entry at 16 KB. **A file that cannot be read
|
||||
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
|
||||
1. **Move what this session closed — IN THE SAME COMMIT THAT CLOSES IT.** 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.**
|
||||
**This is a gate, not a habit (2026-10-03):** `scripts/closed_register_gate.py` RULE 3 refuses a push
|
||||
whose `OPEN-ITEMS.md` holds a row LEADING with a finished word (CLOSED, SHIPPED, FIXED, DECIDED, …).
|
||||
The rule existed from 2026-08-22 without a gate and was followed only sometimes: on 2026-10-03 the open
|
||||
register held 113 finished rows, a quarter of the file.
|
||||
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
|
||||
|
||||
@@ -1381,6 +1381,20 @@ to now *implement* D5 remains an open scheduling decision, not a blocked one.
|
||||
|
||||
---
|
||||
|
||||
**Decided, and deliberately not built — carried here 2026-10-03 when their register rows moved to
|
||||
`backlog/CLOSED-ITEMS.md`** (the reasoning is in `CONTEXT.md`, "Rules carried out of rows closed 2026-10-03"):
|
||||
|
||||
- **[DESIGN] No automatic abandon on a date (R-245, decided 2026-08-07).** A customer who never decides is not
|
||||
auto-abandoned after 30 days. An automatic ending, if ever built, triggers on the HARM (quota — old history
|
||||
blocking new backups), with a dated warning, never on a date alone. Reopens on quota.
|
||||
- **[DESIGN] `markOrphaned` keeps no guard against an active abandon countdown (R-303, decided 2026-08-13).** The
|
||||
co-render is made harmless, not impossible; suppressing the orphan card during a countdown would hide a real
|
||||
second fault. Reopens on a real-world sighting.
|
||||
- **[DESIGN] No in-product route from the recovery screen to a set-aside store (R-312, decided 2026-08-13).**
|
||||
Retention is an operator-only capability, and nothing anywhere may promise the customer can perform it
|
||||
themselves. Re-evaluate on a real customer request. Its fixture, `demo-felhom`'s unrecoverable set-aside store
|
||||
(R-313), is kept until R-312 ships or is abandoned.
|
||||
|
||||
## 12. Evidence index
|
||||
|
||||
| Claim | Grade | Source |
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
# OPEN-ITEMS — the narrative sections, as they stood on 2026-10-03
|
||||
|
||||
> **History, not a register.** These sections sat between the register tables of
|
||||
> `documentation/backlog/OPEN-ITEMS.md` until the 2026-10-03 triage. They are moved here word for word
|
||||
> (`git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` holds them in place). Every row they discuss is in
|
||||
> `OPEN-ITEMS.md` (open) or `CLOSED-ITEMS.md` (finished). A status word below is the status ON THE DATE OF
|
||||
> ITS SECTION and may be stale — the registers are the authority. The ranking paragraphs ("Why the TOP
|
||||
> READY rows rank this way", "The 2026-08-02 intake, ranked") are superseded by
|
||||
> `documentation/audits/backlog-triage-2026-10-03/RECOMMENDATION.md`.
|
||||
|
||||
## Operator rulings — 2026-08-04
|
||||
|
||||
Recorded here because a ruling that lives only in a conversation binds nobody (the R-96 standing rule).
|
||||
|
||||
1. **Run the recovery drill, after R-198.** R-198 shipped in hub v0.93.0; **the drill is the next
|
||||
session.** Design: `audits/RECON-offsite-dr-chain-2026-08-04.md` §10 — demo-hp, ~3–4 h, a recovery
|
||||
code created and KEPT, a sentinel file, wipe, reinstall, recover, and **pass = a byte-identical
|
||||
sha256, not "the repository opened"**. → R-201
|
||||
2. **Delete the orphaned ciphertext** (~1.2 GB across the two demo boxes, in set-aside restic stores
|
||||
nothing prunes). **STILL OWED — not done in v0.93.0.** It is a destructive act on a protected
|
||||
endpoint and belongs to a session that is scoped for it, not to a release that ships a schema
|
||||
change. → R-193
|
||||
3. **Accept the risk on R-193 candidate (c)** — no repository password retained on the Proxmox host.
|
||||
**This is what makes R-198 load-bearing rather than tidy:** with no host-retained copy, the
|
||||
customer-present recovery path is the ONLY way back from a rebuild, and that path runs entirely
|
||||
through the retained identity blob. → R-193, R-199, R-200, R-201
|
||||
|
||||
~~**Still open and untouched by v0.93.0:** R-199, R-200, R-201.~~ **UPDATED 2026-08-04 (evening), after
|
||||
hub v0.94.0 + agent v0.125.0 + controller v0.195.0:**
|
||||
|
||||
- **R-199 — CLOSED, proven on hardware.** Chain links 6–8 are assembled and walked. The offsite
|
||||
repository password came back out of the sealed bundle **byte-identical** to the one on disk
|
||||
(`c60c8bc737a6…` from three independent sources: the box's file, the recovered bundle, and the hash
|
||||
the hub already stored).
|
||||
- **R-200 — the plumbing half shipped**, the customer-facing form did not, deliberately.
|
||||
- **R-201 — PASSED 2026-08-04 (night run).** A customer's file survived a machine rebuild and came
|
||||
back byte-identical, through the customer's own restore flow. **It passed only because a person was
|
||||
there:** four manual interventions stood between the recovered key and the restored file, none of
|
||||
them in any design document → R-204.
|
||||
- **R-202 — untouched.** The orphan card still promises recoverability unconditionally.
|
||||
- **The orphaned ciphertext deletion (~1.2 GB) is STILL OWED** — ruling 2, above.
|
||||
|
||||
**UPDATED 2026-08-05, after controller v0.198.0 + hub v0.95.0 (R-204):**
|
||||
|
||||
- **R-204 items 1–3 — CLOSED.** The reset code works without a restart; a re-issue no longer marks a
|
||||
healthy escrow stale (→ **R-196 CLOSED**); a unit restore states what it did NOT restore.
|
||||
- **R-204 item 4 — OPEN and unstarted:** a rebuilt box cannot obtain an off-site credential unaided.
|
||||
It needs an operator ruling on the one-shot credential design → **R-193**.
|
||||
- **Still open and untouched by this session, stated so nothing is presumed closed by association:**
|
||||
**R-202** (the orphan card's unconditional promise), **the ~1.2 GB orphaned-ciphertext deletion**
|
||||
(ruling 2 — still owed, still needs its own scoped session), and **R-198's retention, which remains
|
||||
UNIT-PROVEN ONLY** — nothing has superseded a key in production, and proving it needs a SECOND
|
||||
deliberate wipe. That retention drill is the next item, and it is not this session's.
|
||||
|
||||
v0.93.0 made the key survive. v0.94.0/v0.125.0/v0.195.0 make it come back. v0.197.0 got the file into
|
||||
the snapshot and the drill got it out again. **v0.198.0/v0.95.0 remove three of the four crutches the
|
||||
drill needed — the fourth is R-193, and until it goes the recovery is still operator-assisted.**
|
||||
|
||||
## R-201 — THE RE-WALK, 2026-08-06 (attended)
|
||||
|
||||
**The question was asked a second time, on the fixed build, on a brand-new appliance built from the
|
||||
published ISO. The answer is still no — but it is a nearer no.**
|
||||
|
||||
| half | verdict |
|
||||
|---|---|
|
||||
| **the data** | **PASS** — all three sentinels byte-identical, including a 12 MB binary and an accented Hungarian filename **whose NAME BYTES are also byte-identical** (verified as hex, not as rendered text). Restored in **23 s** out of the pre-destruction snapshot `a7bc23bd`, through the customer's own two-step full-restore flow |
|
||||
| **the journey** | **FAIL — two dead ends**, against Phase 1's four. One needed a command line **inside the guest**, one a **Proxmox-host** action |
|
||||
|
||||
**RTO: the unaided figure is STILL UNDEFINED**, because the unaided journey still does not complete.
|
||||
Attended: login 11:42:22 → key placed **+45 s** → tier up **+24 m 12 s** (after intervention 1) →
|
||||
all three sentinels restored and verified **+30 m 13 s** (after intervention 2). **The 30 m figure must
|
||||
not be quoted as the customer number.** The only segment that reflects the product working alone is
|
||||
**23 seconds** to pull 12.8 MB back once everything was in place.
|
||||
|
||||
**The two dead ends:** **R-218's consume half** (row corrected above) and **R-220** (drives
|
||||
unenrollable after a rebuild — reproduced and red-proved again; without it no app can be redeployed,
|
||||
and without a redeployed app the restore page is empty, which is R-213's territory and follows from
|
||||
R-220 rather than being separate).
|
||||
|
||||
**What PASSED and is worth keeping:** the recovery screen **appeared without being sought**
|
||||
(`/` → `/launcher` → `/recovery`); it answered all three of its questions and its seal date matched the
|
||||
hub's `created_at` exactly; the emailed reset code worked **first try**; the unlock took **1.528 s** —
|
||||
a real unseal — and placed the key; and **R-225's fix was seen working in the wild** (the store read
|
||||
„a pillanatképek száma még ismeretlen" rather than a false zero).
|
||||
|
||||
**R-216 part 4 reproduced live:** the reinstall **downgraded the agent 0.126.0 → 0.125.0**, back to the
|
||||
vouched version — an operator's hand-fix undone by the very event that makes recovery necessary.
|
||||
|
||||
⚠ **THE DELIVERY GAP, and it is owed.** A fresh install landed on controller **0.201.0** / agent
|
||||
**0.125.0** — the vouched versions, **neither carrying the fixes**. They were installed **by hand**.
|
||||
Fleet delivery needs a golden carrying 0.202.0 **and** a vouched agent 0.126.0. **Nothing was vouched;
|
||||
that is the operator's act.** **This re-walk proves the JOURNEY on the fixed build; it does NOT prove a
|
||||
customer would receive that build.**
|
||||
|
||||
Evidence: `tests/rewalk-r201-2026-08-06/journal.md`.
|
||||
|
||||
## CAMPAIGN 11 — the recovery journey, 2026-08-05
|
||||
|
||||
**The whole journey was walked end to end for the first time, on a throwaway appliance built from the
|
||||
published ISO. The data came back byte-identical; the journey did not exist.** **Ten findings from
|
||||
Phases 1 and 3, R-214 … R-223** (seven fixed in controller **v0.201.0** + hub **v0.97.0/0.97.1**;
|
||||
**three deliberately still open**, each blocking a real flow), **plus five from Phase 2's injected
|
||||
faults, R-224 … R-228.** Evidence: `tests/campaign11-evidence-2026-08-05/journal.md` (Phases 0/1/3)
|
||||
and `journal-phase24.md` (Phases 2/4). Campaign document:
|
||||
`audits/CAMPAIGN-11-recovery-journey-2026-08-05.md`.
|
||||
|
||||
> **Phase 2's verdict in one line.** The cryptography, the retention and the transport all work and
|
||||
> are now proven live. **What fails is being told the truth:** a mistyped code, a hub outage, a
|
||||
> stopped agent and a correct code for a retained earlier package all produce **one** message, and
|
||||
> three of the four are wrong.
|
||||
|
||||
### Phase 2 — the injected faults, 2026-08-05/06 (unattended)
|
||||
|
||||
> **ALL FIVE CLOSED 2026-08-06** in controller **v0.202.0** + agent **v0.126.0**. The rule they now
|
||||
> enforce, stated so it outlives them: **on the unlock path the customer is blamed only after a real
|
||||
> attempt REFUSED their code; every other outcome, including an unclassifiable one, says something
|
||||
> else.**
|
||||
>
|
||||
> **STILL OPEN AND DELIBERATELY UNTOUCHED BY THAT WORK — said explicitly rather than left to
|
||||
> inference:** **R-214** (the console never stops showing a stale pairing code), **R-220** (a rebuilt
|
||||
> box's drives cannot be re-enrolled — *currently worked around BY HAND on the campaign venue*, which
|
||||
> is the only reason an app could be deployed there at all), **R-221** (a rebuilt box cannot run the
|
||||
> escrow ceremony), **R-213** (putting files back), **R-202** (the orphan card's unconditional
|
||||
> promise — now the **last** place on that surface still promising recoverability, two doors from
|
||||
> where R-228 removed the same promise).
|
||||
|
||||
Eleven faults, each judged on **the message, not the outcome**, each with a positive control proving
|
||||
the fault was real. Full observables: `tests/campaign11-evidence-2026-08-05/journal-phase24.md`.
|
||||
|
||||
## Instruction files — deferred half, 2026-08-06
|
||||
|
||||
**Recorded against existing rows by Phase 2:**
|
||||
|
||||
- **R-216 — §4.1 is now MEASURED, not deduced.** The previous session could only offer two absences.
|
||||
The box's own `/settings` renders „Minimális verzió (üzemeltető) **0.200.0**" (`GetFloor()`, whose
|
||||
only writer is the report-ACK handler; **both hold branches serve `Floor=""`**, pinned by
|
||||
`managed_floor_test.go:94`), and a **cold-started** controller logs
|
||||
`settle-gate: GO — at/above floor 0.200.0 (we are 0.201.0)` against the same line reading
|
||||
`floor still unknown after 1m30s` while the hold was in force. The hub's HELD lines ran every
|
||||
15 min to 22:12:06 and stopped, with a liveness control proving the hub kept logging. **The floor
|
||||
is served.**
|
||||
- **⚠ A correction to how that positive was to be taken.** `SetFloor`'s line is `u.dbg(...)`, gated on
|
||||
`cfg.Logging.Level == "debug"` and written to the **logger** — it can **never** reach the logx debug
|
||||
ring, so it cannot appear in `/api/debug/logs` at any level. A controller restart alone would not
|
||||
have produced it. Confirmed with a level census on the ring first (1196 DEBUG / 2802 INFO / 2 WARN),
|
||||
so the absence was known to be structural rather than evidential.
|
||||
- **R-218 — the live half is STILL NOT MEASURED, deliberately.** The fix is present and correct
|
||||
(`needsOffsiteCredential` now retires on the **target**, not the key), but the venue **has** a
|
||||
target, so the box correctly does not declare; declaring here would be the bug. The state that
|
||||
exercises it is shape (a), which the venue no longer holds. **Recorded as not measured rather than
|
||||
inferred from the unit test.**
|
||||
- **R-217 — its fix HELD under exactly its fault** (F5): with the store blocked after a successful
|
||||
unlock, the page rendered M3 and **no listing block at all**; the three false-claim strings are
|
||||
absent, verified in UTF-8 with accented positive controls present.
|
||||
- **R-215 — its fix is present** and the `GET /recovery` gate consults the same predicate as the POST
|
||||
sibling.
|
||||
- **R-199's back-pointer in `architecture/00-capability-map.md` was already added** — the brief lists
|
||||
it as owed; it is present on the escrow-recovery row, explicitly labelled as the omitted
|
||||
back-pointer. **No action taken; the brief's assumption was stale.**
|
||||
|
||||
**Untouched by this session, stated so nothing is presumed closed by association:** **R-213** (putting
|
||||
files back — the half the recovery screen deliberately does not do) and **R-202** (the orphan card's
|
||||
unconditional promise, which CAMPAIGN-11 §7 step 7 measured the customer-facing cost of).
|
||||
|
||||
## CAMPAIGN 12 — the class sweep, 2026-08-08 (unattended)
|
||||
|
||||
Eight rows, **grouped by class so the classes are visible as classes**. Full method, controls, blind
|
||||
spots and the Part-4 gating ranking: `audits/CAMPAIGN-12-class-sweep-2026-08-08.md`. Gating candidates
|
||||
are in `ROADMAP.md`, not here. **C1 produced no new instance** and has no row, deliberately.
|
||||
|
||||
**⚠ A correction the campaign owed to its own brief:** the task described C5's `escrow_stale` instance
|
||||
as "closed individually". **It is not — it is R-247, `READY`.** The live repo is the source.
|
||||
|
||||
## CAMPAIGN 12 follow-through — G-1 built, R-260 and R-247 closed, 2026-08-08
|
||||
|
||||
**The gate was built BEFORE the fixes and was seen failing on 40 fields**, captured verbatim in
|
||||
`documentation/tests/wire-contract-gate-2026-08-08/BEFORE.md`. That order was the method, not
|
||||
bureaucracy: the night before, `deadcode` was rejected for class C6 precisely because it was made to
|
||||
prove itself first and found neither of the two defects it was meant for.
|
||||
|
||||
**⚠ A COUNT THIS SESSION'S OWN PROMPT GOT WRONG, corrected against the repo rather than quoted.** The
|
||||
prompt said *"465 emitted tags, eight unreachable"*. R-260's wording was "at least eight
|
||||
DECISION-BEARING facts", never eight tags in total, and its own census already listed more. Measured
|
||||
on the three declared wires: **210 tags checked, 51 skipped, 40 convicted.** Two prompt claims were
|
||||
wrong this week and both were caught the same way.
|
||||
|
||||
**Two things the gate's CONTROL caught before it was trusted**, each a defect in the instrument:
|
||||
|
||||
1. **A substring false negative.** `grep -F healed_at` also matches `privsep_healed_at`, so a
|
||||
genuinely dropped field read as received. R-260 named `healed_at`, so its absence from the output
|
||||
was the tell. Now a whole-token regex.
|
||||
2. **`dr_recipe` is not wholly opaque.** The hub stores each half as `json.RawMessage` and re-emits
|
||||
nested shapes verbatim — but the TOP-LEVEL section keys are decoded by `hostHalfShape` /
|
||||
`appHalfShape`, and **those are allow-lists**: a section an emitter adds is silently dropped until
|
||||
named in both. That already cost `offsite_restic` (R-122). The gate is therefore opaque BELOW
|
||||
depth 1, so the sections are checked; treating the whole subtree as opaque would have put R-122's
|
||||
shape back outside its reach.
|
||||
|
||||
**THE FORTY, BY DISPOSITION.** Full per-field reasons live in the gate's own `ALLOWLIST`, where each
|
||||
entry is a claim someone can re-check.
|
||||
|
||||
| # | field(s) | direction | decision | what changed |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `oob.operator_key_configured` | agent → hub | **RECEIVE AND ACT ON IT** | decoded as a pointer; `oobDegraded` now fails when the key is absent, and the alert NAMES it. Hub v0.99.0 |
|
||||
| 2 | `oob.wg_handshake_age_s`, `oob.healed_at` | agent → hub | **RECEIVE, for the message only** | decoded into `HostOOBRow` and put in the event payload; deliberately NOT in the predicate — widening the check beyond the fact that is now arriving is how a check stops being read |
|
||||
| 3 | `escrow_stale` | hub → controller | **RECEIVE AND ACT ON IT** | `report.EscrowStatus.Stale`; the box tells a withheld hash from a hash-less one. Controller v0.209.0. **This is R-247** |
|
||||
| 4 | `host.{cpu_temp_c,loadavg,memory_total_bytes,memory_used_bytes,uptime_seconds}`, `system.{load_avg_1,5,15, memory_total_mb, memory_used_mb, temperature_celsius, uptime_seconds}` | agent/controller → hub | **NO CONSUMER WANTED — redundant** | allowlisted: the hub decodes `cpu_percent` / `memory_percent` / `disk_percent` from the same stanzas and every threshold is expressed on those |
|
||||
| 5 | `guests.spec.{disk_bytes,memory_bytes}` | agent → hub | **NO CONSUMER WANTED — redundant** | allowlisted: guest sizing is hub-owned INTENT (the manifest), not mirrored reality |
|
||||
| 6 | `storage_targets.smart.model_name` | agent → hub | **NO CONSUMER WANTED — redundant** | allowlisted: a display label with no threshold on it; `smart.health` and every counter the hub bands on ARE decoded |
|
||||
| 7 | `wireguard.last_handshake_age_s` | agent → hub | **NO CONSUMER WANTED — redundant** | allowlisted: wgsync reconciles from its own state, and the OOB path's own handshake age is now decoded |
|
||||
| 8 | `guest_net` + its 7 children, `selfupdate_pending(_version)`, `mgmt_plane.healed_recently`, `pbs_dr.applied_at`, `restore_tests.mount_{parity,inventory}`, `config_hash`, `reporting_disabled`, `stacks`, `storage.migrated_to`, `backup.last_db_dump`, `backup.last_integrity_check` | agent/controller → hub | **NO CONSUMER TODAY, AND ONE IS ARGUABLY OWED** | allowlisted **against R-264, which stays OPEN**. Allowlisting is not deciding, and the entries say so |
|
||||
|
||||
**A C7 instance found while doing it, and corrected.** `HostReport.SelfUpdatePending`'s own comment
|
||||
claimed *"The hub reads an absent field as pending=false, the correct default."* The hub has no field
|
||||
for it and reads nothing either way. Comment corrected in the agent (no version bump — comment only).
|
||||
|
||||
**The hub's OOB test fixture was part of why this survived.** `oobReport()` omitted
|
||||
`operator_key_configured` entirely, so every pre-existing scenario ran against a report shape **no
|
||||
released agent produces**. Fixed, and the new tests drive the raw JSON decode boundary — a test that
|
||||
builds the receiving struct by hand cannot see a field that never decodes, which is the whole class.
|
||||
|
||||
**Explicitly still open, untouched by this session:** R-246 (the wrong stale flag on `demo-hp` —
|
||||
clearing it is an operator act hub-side), R-255, R-256, R-257, R-258, R-259, R-261, R-262, R-263, and
|
||||
**C7's test-comment half**, which Campaign 12 recorded as *owed, not done* (60 of 2652 production
|
||||
invariant comments sampled; none of the 1440 test comments).
|
||||
|
||||
## The seed that never ran twice, and three pictures that were not true — 2026-08-08
|
||||
|
||||
Four defects of one family: something the box already knows, either thrown away or drawn as its
|
||||
opposite. Agent **v0.128.0**, controller **v0.210.0**, `gates.yml` (no hub change, no hub bump).
|
||||
|
||||
**R-221's writer, ESTABLISHED at `file:line` rather than assumed** — the prompt asked for this and it
|
||||
was owed. `step_agent_config` (`felhom.eu/scripts/felhom-host-install.sh:2396`) renders `agent.json`
|
||||
from `base = {}` unless an explicit `--preserve-from` is passed (flag `:1246`, defaulting empty at
|
||||
`:256`), and writes it with `O_TRUNC` (`:2579`). **The render never writes an `escrow` section at
|
||||
all** — grep over the whole heredoc returns zero hits. The pbsdr marker lives host-side
|
||||
(`<agent-state>/pbsdr/marker.json`) and survives. So a rebuild keeps the marker and takes the key:
|
||||
same descriptor, same hash, early return, seed never re-runs. **The attribution in R-221 was
|
||||
correct.** A rebuild is nonetheless only the case that was measured — the same hole opens for a
|
||||
hand-edited or restored config, which is the honest reason the fix is at the seam and not in the
|
||||
installer.
|
||||
|
||||
**The idempotent early return was KEPT**, and that is load-bearing: it stops a converged box
|
||||
re-running Proxmox operations every 60 s.
|
||||
`TestSeedReasserted_OnConvergedTick_WithZeroProxmoxCalls` asserts **zero** recorded runner calls on
|
||||
that tick, so a "fix" that simply deleted the return fails the test. Verified by mutation.
|
||||
|
||||
**The §7.3 truth table as implemented** (R-258):
|
||||
|
||||
| this app's own most recent dump result | restore point | verdict |
|
||||
| any of its databases failed | yes | `error` — cross |
|
||||
| all clean | yes | `ok` — tick |
|
||||
| none recorded (no database / no run yet) | yes | **no icon**, time only, title „Erről a mentésről nincs eredményünk." |
|
||||
| any | no | no tier-1 row at all, unchanged |
|
||||
|
||||
**An existing test was asserting the defect and was corrected, not deleted.**
|
||||
`TestBuildAppBackupRows_Tier1FromRestorePoints` expected `"ok"` for a `FullBackupStatus` with **no
|
||||
`LastDBDump` at all** — a green tick derived from nothing but a file's existence, i.e. Scenario G.
|
||||
Its real subject, the `Tier1LastRun` time, is unchanged.
|
||||
|
||||
**The convention is now ruled** (§7.2, `CONTEXT.md` S-39): a `…Known bool` companion beside the
|
||||
figures. `ROADMAP.md` G-3 was blocked on that decision and is unblocked.
|
||||
|
||||
**Six red-proofs, every one demonstrated failing and restored, each with the mutation asserted
|
||||
applied.** The one that matters: Scenario A **fails against today's tree** with the intended message
|
||||
— so the test tests the defect.
|
||||
|
||||
**Explicitly still open, untouched by this session:** R-246, R-255, R-256, R-257, R-261, R-262,
|
||||
R-263, **R-264** (the twenty-one undecided facts — a design session of its own), R-240, R-243,
|
||||
R-202, R-213, R-244, R-214/R-235, and **C7's test-comment half**, which Campaign 12 recorded as
|
||||
*owed, not done*. **G-8's other half** (a hub-side check that notices a *vouch* has been forgotten)
|
||||
was deliberately not built: it is hub work whose payoff is a daily email, and this session already
|
||||
ends with a bake-and-vouch cycle in front of the operator.
|
||||
|
||||
## Why the TOP READY rows rank this way
|
||||
|
||||
This covers the next few only — it is deliberately **not** a full ordering of the table above, so that
|
||||
there is one ranking to maintain rather than two.
|
||||
|
||||
1. **R-95** — the largest *data* exposure: the tier holding the customer's documents and photos is the
|
||||
one whose credential can delete. **THIS PARAGRAPH WAS STALE UNTIL 2026-09-01 AND ITS OLD TEXT IS
|
||||
NAMED SO THE CORRECTION IS NOT MISTAKEN FOR A RE-RANK.** It said the snapshot mitigation was
|
||||
*"armed (daily 00:00, keep 7), but it has taken zero snapshots so far"*. **Both halves were wrong:**
|
||||
seven daily snapshots do exist (R-429), and the word "armed" was withdrawn by the spike that same
|
||||
day. **What is true now:** the snapshots exist and the box cannot write into them, but **no account
|
||||
we hold can read anything out of one** (R-433, measured — 777,600 names, zero hits, controlled), so
|
||||
they do not yet bound this exposure. The root cause is untouched either way — the box can still
|
||||
`forget --prune` its own repo, from two call sites. **The ORDER of this list is unchanged and is
|
||||
Viktor's**; only the facts under item 1 were corrected.
|
||||
2. **R-94** — **de-ranked 2026-07-29.** The prior rationale ("until it moves every hub-driven
|
||||
install gets the pre-R-82 default") was false: the constant selects no script and every install
|
||||
already fetches 1.22.0. What remains is a wrong label plus two pieces of dead safety equipment —
|
||||
a drift gate nobody runs and a test that compares a constant to itself. Cheap and worth doing;
|
||||
not high-consequence, and it blocks nothing.
|
||||
3. ~~**R-86**~~ — **CLOSED 2026-08-03**, agent v0.121.0 + hub v0.91.0, proven live on demo-felhom.
|
||||
4. **R-87** — **SPIKED 2026-08-31 and now a DECISION, not work.** It also spent 2026-08-22..31 in
|
||||
`CLOSED-ITEMS.md` by mistake while this paragraph ranked it fourth and pointed at nothing
|
||||
(R-405). The spike measured it rather than designing it: a scratch restore of all 8 apps costs
|
||||
25 s against the 40.3 s weekly check, but it would have caught ONE of the five drill-found
|
||||
restore defects. **Recommendation: build the NARROW version — prove the snapshot still
|
||||
CONTAINS a recoverable unit — or close the row.** Viktor's call; see
|
||||
`audits/SPIKE-restic-restore-test-2026-08-31.md`. *The 2026-08-03 rationale, kept:*
|
||||
R-86 built most of what it was waiting for (per-archive due-ness, a
|
||||
proof that names its archive, and a staleness window that learns a tier's rhythm). What is left is
|
||||
restic-specific — there is no scratch-guest analogue — so it still needs its own design, but it is
|
||||
no longer waiting on a scheduling model that did not exist.
|
||||
5. ~~**R-185**~~ — **CLOSED 2026-08-03**, agent v0.123.0 + installer 1.24.0, proven live on both demo
|
||||
boxes. The silence was fixed as well as the grant: the box now asks whether it may READ each tier
|
||||
it depends on, because an empty listing cannot distinguish forbidden from newborn.
|
||||
6. ~~**R-189**~~ — **CLOSED 2026-08-03** with **R-188** and **R-186**, agent v0.122.0. The three
|
||||
reporting/release signals that misreported their own work are fixed; **R-185 is the one that
|
||||
remains open from that group** and is untouched by this — it is a missing storage ACL on
|
||||
demo-felhom, not a reporting defect.
|
||||
7. **R-110** — last **because it is not a READY row**: the ruling is the operator's, not CC's, and
|
||||
there is nothing for CC to build until it lands. Ranked here rather than omitted because it is the
|
||||
only item on this page about the *publish channel* of the most privileged artifact Felhom ships,
|
||||
and today's exposure is zero — which makes now the cheapest moment it will ever be to decide.
|
||||
|
||||
### The 2026-08-02 intake (R-156 … R-164), ranked
|
||||
|
||||
Filed in one pass from Campaign 10, its two spikes, and the 53-template catalog persistence sweep.
|
||||
**R-156 and R-157 had lived only in audit documents** — the identical *"minted in a spike doc and
|
||||
never carried across"* failure the register already records for R-153/R-154/R-155, caught by the
|
||||
sweep's own §8.0 while it was happening. R-158 was minted by a second session on the same day for an
|
||||
unrelated finding, which is why the sweep's proposals were renumbered to R-159…R-162 at filing time.
|
||||
|
||||
1. **R-157** — highest: a `deployed: true` app can stay **down indefinitely** after a power cut or
|
||||
hard reset, and in mechanism **B** nothing reports it on any channel (`0 currently down`). It is the
|
||||
only row here where the customer loses service and has no signal at all.
|
||||
2. **R-156** — the class is now detectable and two of three apps are fixed; what remains is **papra's
|
||||
referral**, one app, well understood. *(Promoted 2026-08-02: R-161 was ranked here because nothing
|
||||
ran the gate; it now has a mandated entry point, so R-156's residue is the larger remaining item.)*
|
||||
4. **R-163** — a real ceiling that silently caps local backup once an app outgrows `mp1`, and it gates
|
||||
Tier-2 and Tier-3 as well. Ranked below the above only because **overflow itself is safe today** —
|
||||
it refuses per app and preserves the last good unit byte-identical. **RE-FRAMED 2026-08-02:** no
|
||||
longer waiting on a ratio — decision **D-a** merges `mp1` away, so the row is now the record of the
|
||||
constraint and the work moves to **R-165** (with **R-167** shipping in the same step). R-165 inherits
|
||||
this rank; it is the highest-ranked item that must land **before any external install**.
|
||||
5. **R-158** — the gap that makes R-163 dangerous: cross the size line and **one page** tells you. On
|
||||
its own it is a notification gap, not a silent failure, which is why it sits here and not higher.
|
||||
6. **R-164** — blocked on a predicate, no customer impact today; it only becomes urgent if the unit
|
||||
size in R-163 is judged unacceptable, since the tar-drop is the cheapest way to halve it.
|
||||
7. **R-161** — **de-ranked 2026-08-02, ruled and shipped at reduced scope.** The gate now has one
|
||||
mandated entry point (`catalog_gates.py`), which is the shape that actually gets run here. What is
|
||||
left is the automatic half, and that is sufficient while **one** person touches templates — so it
|
||||
ranks low by design, not by neglect. Revisit when a second does.
|
||||
8. **R-162** — `WATCHING` only. A limitation that fails closed; revisit if a non-overlay driver ships.
|
||||
|
||||
**R-159 and R-160 are SHIPPED** and are not ranked; they are filed to record the class, and R-159's
|
||||
class (an image `VOLUME` at an unmounted path) is still live — `immich-server` has one today.
|
||||
|
||||
<!-- RESTORED 2026-08-22: these rows are NOT fully closed and were moved to CLOSED-ITEMS.md in
|
||||
error by this session's compressor, whose status regex matched the word CLOSED inside
|
||||
'PARTLY CLOSED' and the word FIXED inside 'OPEN - NOT FIXED'. They are restored VERBATIM
|
||||
from commit fddfe00ce268, not from the compressed form: an open row keeps its detail. -->
|
||||
|
||||
<!-- ── MIGRATED FROM ROADMAP.md, 2026-08-22 (R-369 ruling: ONE register) ──────────────────
|
||||
16 rows moved: every ROADMAP row that asserted something checkable about the shipped
|
||||
product, plus one owed operator decision. Ideas and proposals stayed in ROADMAP, which
|
||||
is their home. Each row below keeps its ORIGINAL identifier and filing date — the age is
|
||||
the point. The roadmap keeps its copy as history, marked moved, with a pointer here. -->
|
||||
@@ -5,7 +5,7 @@
|
||||
Source findings: `DRILL-day0-cleanroom-2026-07-03.md` §9 **B5** (golden bakes a pre-floor controller →
|
||||
mandatory manual D.1b on every fresh install) and **B1** (controller-bootstrap only fires at boot; the
|
||||
hot-plugged config mount needed the installer's reboot crutch). Also closes the stale backlog note
|
||||
`documentation/backlog/FOLLOWUP-golden-default-controller-tag.md`.
|
||||
`documentation/archive/FOLLOWUP-golden-default-controller-tag.md` (moved from `backlog/` 2026-10-03).
|
||||
|
||||
**Verdict (short):** both findings are FIXED in the product. A fresh Day-0 install now lands controller
|
||||
**0.98.3** on first boot and self-manages from there (no D.1b), and the baked
|
||||
|
||||
@@ -26,6 +26,166 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-03 — the triage: finished rows moved out of the open register
|
||||
|
||||
> Every row below sat in `OPEN-ITEMS.md` with a finished LEADING verdict (or was verified finished against
|
||||
> live source on this day, or folded into an older duplicate). Full original text of each:
|
||||
> `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`. Since this day `closed_register_gate.py` RULE 3
|
||||
> refuses a finished row left in the open register.
|
||||
|
||||
| Row | What | Closed | Full text |
|
||||
|---|---|---|---|
|
||||
| **R-88a** | **~~Failing backup re-quiesces every 5 min, no backoff~~** **Reasoning kept:** «breaker 15m→4h, per-tier, never permanent» | SHIPPED (controller v0.176.0, 2026-07-27). Live on both boxes; breaker 15m→4h, per-tier, never permanent. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-88b** | **~~`/backup/due` cannot say *unknown*~~** | SHIPPED + PROVEN-LIVE (agent v0.105.0 + controller v0.178.0, 2026-07-27). `age_state=unknown` captured on real hardware during a deliberate ep0 outage; controller deferred, zero app stacks stopped. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-123** | **R-105 and R-106 were `READY` in `ROADMAP.md` with no row on THIS page** | CLOSED 2026-10-03 (triage, verified) — its last residue — "R-105 still needs a row" — is done: R-105 has its own row; the process gap is R-369's gate | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-131** | **`sess-f` is a fourth orphaned scratch customer** | CLOSED 2026-10-03 (triage, verified) — `sess-f` was already gone by 2026-08-05 (`documentation/tests/campaign11-evidence-2026-08-05/journal.md:24-27`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-176** | **Two prerequisites for the R-165 merge are UNMEASURED, and both are cheap.** | (a) ANSWERED 2026-08-03 (P1: PASS). (b) NOT REQUIRED — operator ruling: every node is reinstalled, none migrated (withdrawn, not deferred; CONTEXT.md:2076). — residue NOT-A-FINDING (2026-10-03): its residue ("re-run (a) once against agent v0.120.0") is moot: every node was reinstalled, none migrated (`CONTEXT.md:2076`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/SPIKE-r165-phase0-2026-08-03.md |
|
||||
| **R-191** | **Every weekly offsite backup UPLOADS successfully and then FAILS the job on a prune the box is deliberately not allowed to do — on both demo boxes.** **Reasoning kept:** «R-89 moved PBS pruning SERVER-SIDE — *"boxes set `keep_last: 0`, ep0 runs prune jobs; box tokens stay write-only, never widen the grant"*.» | CLOSED — SHIPPED 2026-08-04 (installer 1.25.0; both live boxes corrected to offsite `keep_last: 0`, `prune_pbs_allowed=false`). ep0 prune jobs verified first (18 tasks OK). hostinstall_gates.py asserts it (red-proved). The 'next weekly run OK' observation: offsite snapshots dated 2026-08-11 recorded in audits/INCIDENT-ep0-pbs-fd-exhaustion-2026-08-18.md:118. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/INCIDENT-ep0-pbs-fd-exhaustion-2026-08-18.md |
|
||||
| **R-201** | **Nothing in the offsite DR chain has ever been exercised past the ceremony — and the one live proof that exists predates the field it is cited for.** **Reasoning kept:** «**The pass condition is unchanged** — a byte-identical sentinel sha256, not "the store opened"» «**a good snapshot is not durable against a later bad run on the same day.**» | CLOSED 2026-08-07 — BOTH HALVES PASS on the fifth walk (DATA 2026-08-04 night run; JOURNEY 2026-08-07, zero guest command lines). Does NOT claim a smooth journey (R-252/R-253, both since CLOSED 2026-08-08) nor shape (c)'s positive half. Walls along the way: R-203, R-204, R-241. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-r201-night-run-2026-08-04.md, audits/RECON-offsite-dr-chain-2026-08-04.md, tests/finalwalk-r201-2026-08-07/journal.md, tests/walk5-r201-2026-08-07/journal.md |
|
||||
| **R-202** | **NOW EVIDENCED, NOT ARGUED (2026-08-10).** | CLOSED 2026-10-03 (triage, verified) — the false recoverability promise was removed (R-294/R-299; `controller/internal/web/templates/backups_remote.html:93-108`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-209** | **~~Should the containerd store move to SSD2 at all?~~** **Reasoning kept:** «**A TRAP was found while proving the guard, and it is the reusable part: `RequiresMountsFor` on a path with NO mount unit is a SILENT NO-OP**» «**`-X` is load-bearing** — overlayfs stacking rides `trusted.overlay.*`.» | EXECUTED 2026-08-05 on operator ruling — reboot validation DEFERRED → R-209a (still open, WATCHING, OPEN-ITEMS.md:339). Zero-loss move verified on four observables; storageReserved on SSD2 0 → 80 GB; pre-move tree moved aside. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/SPIKE-dooplex-buildcache-2026-08-05.md |
|
||||
| **R-214** | **The physical console never stops asking to be paired.** | CLOSED 2026-09-20 - ISO 1.28.0+/R-535, proven on a fresh install (localisation slice 6's walk on ISO 1.29.0: last console paint after self-bind is the bilingual bound banner). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/i18n-slice6-2026-09-20/drill/screens/22-console-after-claim.png |
|
||||
| **R-229** | **The instruction-file rightsizing landed for `felhom-controller` and the workspace root; three pieces were deliberately deferred.** | CLOSED 2026-10-03 (triage, verified) — legs (a), (b), (c) CLOSED 2026-08-06 in the row's own text; leg (d) moved to R-230 | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-233** | **The golden bake's acceptance checks were a list of strings the script does not print.** **Reasoning kept:** «**The general lesson:** a runbook's pass markers must be **copied from a captured log, never written from memory**» | CLOSED 2026-08-06 — fixed in `RUNBOOK-manual-build.md` (§4.1): markers re-captured from the real log, pre-gate URL corrected to `golden.tar.zst`, token moved off the command line into an in-VM runner script, positive control on the token-leak grep, vouch step rewritten. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: runbooks/RUNBOOK-manual-build.md |
|
||||
| **R-245** | **Should a customer who never decides be auto-abandoned after 30 days? RECORDED, NOT BUILT.** **Reasoning kept:** «**The real harm, if it comes, is QUOTA** — old history blocking new backups — and **that is a condition, not a calendar**. An automatic ending should trigger on the harm, with a dated warning, never on a date alone. **If this is ever built, build it that way.**» | DECIDED 2026-08-07 — not built; reopens on quota. Built instead: escalating reminders (1/3/7/14 days) and operator levers `--abandon-extend` / `--abandon-stop`. Re-filed 2026-08-08 as a decision taken. Reopens verbatim: "THE CONDITION THAT REOPENS IT, which the reasoning already names: QUOTA — old set-aside history blocking new backups." | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-248** | **A flag that changes behaviour is visible to nobody who would look for it.** | FOLDED into R-246 2026-10-03 — the same ruling: give `stale_at` a visible, evidence-bearing setter, or retire it | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-254** | **The same render-then-hide pattern R-249 fixed is live in two more places, and one of them carries a real per-install secret.** **Reasoning kept:** «`escrow_handlers.go`'s rule that a secret is revealed by an XHR and never templated server-side into HTML.» «**A form must carry what it submits.**» | CLOSED 2026-08-08 — both sites closed in controller v0.208.0: `POST /apps/<slug>/initial-credentials/reveal` (re-reads container, no-store, CSRF, logged) and `POST /stacks/<name>/auto-field/reveal` for deployed pages; pre-deploy hidden input kept deliberately. Gate `scripts/secret_in_markup_gate.py`; its blind spot → R-255. Measured exposure: none; rotation not indicated. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: scripts/secret_in_markup_gate.py |
|
||||
| **R-272** | **RANK 1 — Felhom's `--uninstall` leaves the exact condition that makes Felhom's own reinstall REFUSE.** | CLOSED 2026-10-03 (triage, verified) — the uninstall purges the dnsmasq Felhom installed (R-316; `scripts/felhom-host-install.sh:858-882`, `_dnsmasq_purge_owned`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-295** | **One name per secret — CONTROLLER HALF SHIPPED.** | CLOSED 2026-10-03 (triage, verified) — hub half shipped in hub v0.104.0 (`4d6ec7c`, hub `CHANGELOG.md:844,905`); the controller half shipped v0.211.0 | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-303** | **`markOrphaned` has no guard against an active abandon countdown — the co-render is made HARMLESS, not IMPOSSIBLE.** **Reasoning kept:** «the wrong fix (suppressing the orphan card during a countdown) would hide a real second fault.» | DECIDED 2026-08-13 — left as it is; reopens on a real-world sighting. Verbatim trigger: "an observation of the combined state occurring OUTSIDE a constructed test." Related: R-302. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-312** | **There is no in-product route from the recovery screen to a set-aside store, and building one is not wiring — it is new surface.** **Reasoning kept:** «retention is an **operator-only** capability, and nothing anywhere may promise the customer can perform it themselves» | DECIDED 2026-08-13 — deliberately not built; re-evaluate on a real customer request. Verbatim trigger: "a real need appearing — one request from a customer who is not us." Related: R-304, R-311. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-313** | **`demo-felhom`'s set-aside store is UNRECOVERABLE — 36 snapshots whose key we destroyed ourselves.** | DECIDED 2026-08-13 — kept as a test fixture; delete when R-312 ships or is abandoned. Verbatim: "when R-312 is built, or when R-312's ruling above is made permanent. On either event, delete it deliberately and record why". Related: R-198, R-307, R-312. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-341** | **Does the fd slope change after the PBS 4.2.5 upgrade? — two dated checks, and the answer is expected to be NO.** **Reasoning kept:** «the anchor must be `ps -o lstart= -p $MainPID`, NOT `systemctl show -p ActiveEnterTimestamp`, which reads 03:54:54Z for this generation (the upgrade re-exec'd the proxy; systemd never saw a stop, `NRestarts` is still 0) and would put the rate ~15% low.» | CLOSED 2026-08-30 — second check taken (fd back to baseline 17, ESTAB 0) but it cannot answer the question: the leak was REMOVED mid-interval by our own fix (R-344, agent 0.130.0). Question moot; fix confirmed holding at 12 days. First check 2026-08-20: unchanged, 201.6 fd/day. R-336 keeps the request-rate scaling item; ActiveEnterTimestamp trap → R-346. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/SPIKE-ep0-established-connections-2026-08-20.md, audits/evidence-r341-plus7d-2026-08-30/step1-fd-and-sockets.txt, evidence-ep0-pbs-upgrade-2026-08-18/stop1-ruling.txt |
|
||||
| **R-343** | **The managed controller floor was raised 0.214.0 → 0.216.0 — and it was NOT the no-op it was expected to be: it moved a live box nine seconds later.** | CLOSED 2026-10-03 (triage, verified) — obsolete — its only step was "confirm 0.216.0 is healthy in normal operation"; both demo boxes have run every release since and are on 0.288.0 (`STATUS.md`, 2026-10-02) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-369** | **There are TWO registers, only one calls itself the source of truth, and work filed in the other is invisible to every standing rule that says "grep the register".** | CLOSED 2026-10-03 (triage, verified) — RULED 2026-08-22 (one register) and enforced by `scripts/one_register_gate.py` (`ef6ac6f`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-378** | **A status word inside a longer verdict fooled the session's own compressor, moving six still-open rows into the closed file.** **Reasoning kept:** «Match the LEADING verdict.» | CLOSED 2026-08-22 — corrected in the same session; the six rows (R-123, R-190, R-214, R-264, R-295, R-352) restored verbatim from commit `fddfe00ce268`. Successor: R-369. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-385** | **A controller was built, baked AND vouched with no CHANGELOG entry of its own, and every gate stayed green.** **Reasoning kept:** «**Membership, not `baked > released`, deliberately:** a comparison against the newest heading alone goes green the moment any later entry is written, leaving the unrecorded version permanently unrecorded and the gate permanently silent about it.» | CLOSED — 2026-08-23. v0.221.1 given its own CHANGELOG heading (commit da75603); scripts/golden_currency_gate.py now requires the baked version's own `## vX.Y.Z` heading anywhere in the CHANGELOG (membership); INCONCLUSIVE exit 2 preserved; both directions red-proofed. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-r384-dead-db-alarm-2026-08-23/evidence/gate-0*.txt |
|
||||
| **R-387** | **The hub REWRITES an unknown severity and says nothing, and the guard built to catch that sits downstream of the rewrite.** **Reasoning kept:** «**The coercion STAYS; only the silence is fixed** — a rejected event is a LOST event, and losing an alarm is worse than mis-routing one.» | CLOSED — hub v0.107.0, 2026-08-23. Coercion kept; a WARN now names customer, event type, rejected value and consequence. Dispatcher severity branch kept (it is the only guard for monitor-originated events). Proven live with an `error` control silent. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-r329-r386-2026-08-23/evidence/live-19-scenarioH-after.txt |
|
||||
| **R-398** | **`resticStep` is not a seam, so no test can drive any restic-backed path.** | CLOSED 2026-10-03 (triage, verified) — premise corrected 2026-08-30; the execution test exists (`controller/internal/backup/r358_scratch_marker_test.go:200-205`, `TestR358_MarkerOrderingIsExecuted`) and the `resticStepFn` seam was deliberately NOT built — it would hide the `unlock --remove-all` escalation the test asserts | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-405** | **R-87 sat in CLOSED-ITEMS.md for nine days while still open; the ranking paragraph ranked it fourth pointing at nothing.** | CLOSED 2026-08-31 — corrected + gated in the same session: R-87 restored verbatim from `ef6ac6f^`; `scripts/closed_register_gate.py` is the 12th gate; R-398 stub turned to prose. Gate's residual holes in its docstring; hole 4 is R-406. Predecessor R-378. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-413** | **R-87's proof caught a naturally-produced hollow snapshot, end to end, unattended.** | CLOSED 2026-08-31 — the claim it upgrades is recorded (opengist `volumes_expected_none_captured`, one `offsite_proof_empty` at error; four apps ahead passed). Related R-87, R-412. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-428** | **The decoy-coverage gate identified a repository by its DIRECTORY NAME.** | CLOSED 2026-09-01 — fixed, and kept as the class's best example (felhom.eu CI job 490 found it; the gate now identifies a repo by which registered runner FILE exists under the root; verified under a renamed directory). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-432** | **A customer's own sub-account can REACH the snapshot door and is REFUSED writes — but sees it EMPTY, so per-file recovery is not product-reachable.** | ANSWERED 2026-09-01 — negatively; the panel-read next step is WITHDRAWN as unnecessary (777,600 exact snapshot names, zero hits; /.zfs/snapshot is a different filesystem from /home). Per-file recovery unreachable from the box entirely → R-433. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-r95-recovery-2026-09-01/ |
|
||||
| **R-434** | **The snapshot-drop alarm promises a recovery that cannot be performed.** **Reasoning kept:** «when a verdict changes which fact it counts from, the alarm text has to change with it, or the operator acts on a promise nobody can keep.» «**That sentence is true under EVERY possible answer to the provider questions, so it never needs a second rewrite** — which is the whole reason it was not blocked.» | CLOSED 2026-09-01 — hub v0.111.1; the promise was DELETED (not replaced) with a sentence true under every provider answer; three tests in `hub/internal/monitor/offsite_r434_test.go`, red-proofed. The "blocked on R-433" verdict above was mine and it was wrong. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-463** | **The day the catalog moves postgres:16 to 17, ELEVEN apps are affected and the container image will NOT perform the conversion. (P2)** **Reasoning kept:** «A later move of any of the three needs its own two-venue proof: the engine gate enforces it per app.» «PostgreSQL majors are converted BY THE BOX as a guarded-update step — save everything from the old engine, start the new one empty, load it back, check.» | CLOSED 2026-09-30 — 8 of 11 moved by the box's own conversion (docmost, paperless-ngx, tandoor, claper, calcom, rallly, outline, sparkyfitness; controller v0.273.0+, `09` decisions 16/42/43); 3 stay by decision 42's rule (zipline, adventurelog, immich). A later move of any of the three needs its own two-venue proof: the engine gate enforces it per app. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/SPIKE-r459-mariadb-upgrade-2026-09-06.md, audits/update-night-2026-09-21/24-Q5-postgres-conversion-costed.md, audits/pg-calcom-claper-2026-09-28/, audits/pg-last-six-2026-09-30/ |
|
||||
| **R-497** | **No product channel ever gives the customer the „Tulajdonosi jelmondat”, yet the self-bind mail says they received it at setup. (P2)** | CLOSED — hub v0.113.0 (2026-09-14, ArgoCD Synced): customer page renders the hand-over sentence; tests red first (`passphrase_handover_test.go`); mail change pinned by test, not read live. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DOORSTEP-walk-1270-2026-09-14.md |
|
||||
| **R-500** | **[P3-LOW] The dashboard shows the last backup in UTC while every backup page shows it in local time — two different clock times for one backup.** | CLOSED 2026-10-03 (triage, verified) — controller v0.283.0 — the dashboard's last-backup time uses `fmtTime`, local time (`dashboard.html:154`; controller `CHANGELOG.md:222`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-505** | **A fresh box on customer tester-1 connects its Cloudflare tunnel but receives NO routes, so the dashboard answers 503. (P1)** | CLOSED 2026-09-29 — proven end to end on a fresh box. Cause: the tester-1 tunnel had no public hostnames; operator added a route 2026-09-14; box-side hop proven by the 2026-09-29 new-household drill. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-506** | **[P3-LOW] `day0-install.md` A.1 says "the controller manages per-app hostnames itself via the tunnel" — it does not.** | CLOSED 2026-10-03 (triage, verified) — the day-0 runbook names the published-route step and says the controller creates no routes or DNS (`documentation/runbooks/day0-install.md:47-53`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-511** | **A rebuilt customer's box keeps its ep0 PBS token, and the DR tier can be neither provisioned nor re-issued. (P2)** | CLOSED 2026-09-16 — proven live on a fresh box: hub v0.114.0 re-issue ADOPTS the endpoint token (shipped 2026-09-15); made live by R-534's ep0 grant. Token-only release on host delete not built → R-526. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-p1fixes-2026-09-15/ |
|
||||
| **R-520** | **A power cut during a guarded Update leaves no record that an update was running. (P3)** **Reasoning kept:** «**`pct mount` maps the guest's ROOTFS ONLY and does not apply the guest's own internal mounts**, and 9202 keeps `/var/lib/docker` on its own ext4 mount under the `mp0` volume, so from the host that path is an EMPTY STUB.» «**An empty directory is not evidence of an absent file.**» | CLOSED 2026-09-21 — measured on a real version change (scratch 9202, controller v0.260.0, uptime-kuma 2.4.0→2.5.0 cut in `pulling`; box put the pin back itself). The `starting`-phase cut → R-610. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/update-arc-2026-09-21/ |
|
||||
| **R-524** | **When the catalog moves an app back to an older version, a box that already updated shows „Frissítés elérhető” — and the offered Update is a downgrade. (P2)** **Reasoning kept:** «**Ahead is NARROW on purpose:** every differing service must be orderable AND newer, or the verdict falls back to Behind — this gate can BLOCK an update, so it errs towards letting one run.» | CLOSED 2026-09-21 — controller v0.260.0: `stacks.CatalogOrder` four verdicts incl. Ahead; `UpdatePreflight` refuses `downgrade` (409); three red-proofs; `09` §3 decision 10 (decided by CC unattended — operator may reverse). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-534** | **The off-site tier cannot be provisioned or adopted for a rebuilt box: the hub's endpoint token lacks Datastore.Modify. (P1)** | CLOSED 2026-09-16 — grant given and proven end to end: DatastoreAdmin for the hub's `felhom@pbs` on `/datastore/felhom-offsite` only (narrowest role carrying Modify; PBS has no custom roles); re-issue then ADOPTED on a fresh box. Closes R-511. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-0243-2026-09-16/phase1-pbsdr.txt, audits/evidence-backup-promise-2026-09-16/phaseC-ep0-grant.txt, audits/evidence-backup-promise-2026-09-16/phaseC-reissue.txt |
|
||||
| **R-535** | **The box's console still says it waits for pairing long after the box is bound — and promises the screen refreshes itself. (P2)** | CLOSED 2026-09-16 — shipped in ISO 1.28.0 (published); on-screen effect not photographed (`print_bound_banner`). Later SEEN on screen by R-214's closure 2026-09-20. Deliberately does not name the dashboard URL nor reflect the later claim. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-0243-2026-09-16/screens/33-console-after-claim.png |
|
||||
| **R-536** | **The hub is told „Alkalmazás telepítve” the moment a deploy is ACCEPTED, so an install that never finishes is recorded as completed. (P2)** **Reasoning kept:** «The accept-time `app.yaml` is deliberately NOT deleted on failure — it is the crash-safe record with `Deployed:false` and it holds the settings the customer typed; the state every surface reads is `not_deployed`.» | CLOSED 2026-09-16 — controller v0.244.0 + hub v0.116.0: `app_deploy_started` at accept, `app_deployed` from the async end, `app_deploy_failed` (warning); both new types in `allowedEventTypes` and `customerMessages`; two red-proofs. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-537** | **The app-backup page labels the tier-1 backup „DB + Konfig + Adatok" and shows the data-drive size, but the tier-1 unit contains NO drive-side app data (P1-HIGH)** **Reasoning kept:** «The contents label is computed PER TIER from what that tier captures: Tier 1 says „Adatok" only when the app's data really is in the volumes the unit captured» | CLOSED 2026-09-16 — controller v0.244.0, proven live on demo-hp; label computed per tier; re-proven on a fresh box (ISO 1.28.0). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-0243-2026-09-16/phase2-f10.txt |
|
||||
| **R-538** | **A tier-1 app restore reports plain success, leaves Nextcloud listing files whose bytes were never backed up, and destroys the app's own trash (P1-HIGH)** **Reasoning kept:** «never present a DB-only restore of a class-A app as a complete one.» «A unit restore refuses before anything is touched when the unit cannot return the app's drive-side files, and names the route that can.» | CLOSED 2026-09-16 — controller v0.244.0, proven live on demo-hp; unit restore refuses when the unit cannot return the drive-side files; re-proven on a fresh box, off-site route returned 5/5 photos sha256-identical. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-0243-2026-09-16/phase2-f10.txt, audits/evidence-backup-promise-2026-09-16/phaseE-photos.txt |
|
||||
| **R-543** | **Off-site ON by default is not off-site WORKING: a fresh box's tier 3 waits on escrow and nothing asks the household. (P1)** **Reasoning kept:** «The pause is untouched: it is the zero-knowledge escrow design, and this row was never about the mechanism.» | CLOSED 2026-09-16 — shipped in controller v0.245.0 and proven live: escrow-pending bar on every authenticated page (via `executeTemplate`) + tier-1 sentence rendered by `tier3State`; both red-proofed; live on 9202 (paused) and 9201 (escrowed). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-recovery-code-2026-09-16/ |
|
||||
| **R-557** | **Localisation slice 2 — Go-side customer strings follow the language. (P3)** **Reasoning kept:** «Plurals are a BUNDLE rule (a key with `.one`/`.other` takes its count first), not a call-site flag.» «EXCEPT one producer: `"Sikeres — nincs mentésre jelölt alkalmazás"` (controller/internal/backup/offbox.go) must stay Hungarian until **R-570** closes, because the page's legacy fallback still reads it on boxes that have not run off-site since 0.251.0.» | CLOSED 2026-09-18 - controller v0.252.0 + v0.253.0 + v0.254.0 (release A: 226 literals + `i18n_go_parity.py`; B: all 179 error literals keyed via `util.MsgError`; C: saved notes in box language, globe switch). Gaps filed: R-570, R-572..R-578; R-566 closed with it. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-558** | **Localisation slice 3 — the hub's customer e-mails follow the household's language. (P3)** **Reasoning kept:** «The bind page is per-language with `expired` pinned to Hungarian (the language would otherwise be the oracle the text refuses to be).» | CLOSED 2026-09-18 - hub v0.118.1 + controller v0.256.1 (hub v0.118.0/v0.118.1 + controller v0.256.0/v0.256.1; 56 Hungarian mail goldens unchanged; proven live twice). Gaps filed: R-581..R-585; R-555 closed with it. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-559** | **Localisation slice 4 — the console banner and the download page in English. (P3)** | CLOSED 2026-09-18 — ISO 1.29.0 published (sha256 `dceacae5…e94829`), proof installs on both menu entries + reboot; `felhom.eu/en/download` live; release gate G16 rewritten per ruling 1b. Gaps filed: R-586, R-587, R-588. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-560** | **Localisation slice 5 — catalog cards, settings and first steps in English. (P3)** **Reasoning kept:** «lists replace WHOLE and every other list is matched by its own key (`env_var`, option `value`, `match_group`, `target`, `path`), never by position;» «**The blocks are GENERATED from a flat `{path: english}` map**, not hand-written: a mistyped `env_var` is INERT on the box rather than an error, and the generator can only write paths that exist on the Hungarian side.» | CLOSED 2026-09-20 — controller v0.257.0, catalog fully translated (catalog `e81d41e` + three batches; 1 031/1 032 strings, ceiling 1), fleet floor 0.257.0 (min_agent 0.131.0). Gaps filed: R-589..R-594. — residue NOT-A-FINDING (2026-10-03): its residue (Peti's box and tester-1 taking floor 0.257.0 unattended) is moot: Peti's box was retired 2026-09-25 and tester-1 takes every floor like any box | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-561** | **Localisation slice 6 — the volunteer guide in English, then a stranger's first hour in English; closes R-516. (P3)** | CLOSED 2026-09-20 - controller v0.258.0, the English guide, and the walk (`runbooks/VOLUNTEER-first-hour.en.md`; R-589/R-590/R-573 fixed; R-214 closed as a side effect). Verdict: not yet ready for an English tester (R-596). Gaps filed: R-596..R-599. R-516 does NOT close. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-first-hour-en-0258-2026-09-20.md, audits/i18n-slice6-2026-09-20/R-516-item-by-item.md, runbooks/VOLUNTEER-first-hour.en.md |
|
||||
| **R-566** | **Three page titles built in Go around an app name stay Hungarian in the English browser tab. (P3)** | CLOSED 2026-09-18 - controller v0.252.0 (four `%s` keys via `data["TitleArgs"]`; pinned by `TestParameterisedPageTitles` and `i18n_go_parity.py`). Part of R-557. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-572** | **[P3-LOW] Two copy-producing template helpers have no English form, so an English page renders „vasárnap" and „%d órája" in Hungarian.** | CLOSED 2026-10-03 (triage, verified) — controller v0.258.0 — `pruneLabel`/`nextPruneLabel` deleted as dead code (`controller/internal/web/funcmap.go:368-377`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-580** | **[P3-LOW] `curl -w '%{redirect_url}'` prints Basic-auth credentials back into the session transcript.** | FOLDED into R-132 2026-10-03 — the same `curl -w %{redirect_url}` credential echo, seen again 2026-09-18 | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-582** | **An English copy guard written from the Hungarian one matches ordinary words instead of the claim. (P3)** **Reasoning kept:** «**The general form worth keeping: a guard ported between languages must be re-derived from what the claim IS in the new language, not translated word for word — and a guard that convicts 141 true sentences is worse than no guard, because it earns an allowlist entry per sentence and then nobody reads it.**» | CLOSED 2026-09-18 - hub v0.118.0 (recorded for the lesson; three decoys incl. an innocent control): patterns carry the modal and admit an adverb; gate scans bundle VALUES only. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-583** | **The test-notification mail was the one customer mail that did not follow the language. (P3)** **Reasoning kept:** «**The general form worth keeping: the surface you would use to CHECK a feature is the one most worth checking first — a broken instrument that reports success is worse than a broken feature.**» | CLOSED 2026-09-18 - hub v0.118.1 (`mail.test.subject`/`mail.test.body`, red-proofed, proven live in both languages 74 s apart on demo-hp). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-589** | **The update badge is Hungarian on an English app page — and it is the badge, not a corner case. (P3)** **Reasoning kept:** «**The lesson, because it cost a reviewer pass and half a task brief: a reviewer who reads ONE producer cannot see a SECOND producer that overrides it** — reading `updatebadge.go` alone gives exactly the wrong answer.» | CLOSED 2026-09-21 — shipped in v0.258.0; the row was stale (English built in `web.localeFuncs` "updateBadge", pinned by `TestUpdateBadgeFollowsTheLanguage`; verified at controller `19ef0329ab66`). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-first-hour-en-0258-2026-09-20.md |
|
||||
| **R-590** | **[P3-LOW] The data-folder card tells an English household, in Hungarian, whether its files are backed up.** | CLOSED 2026-10-03 (triage, verified) — controller v0.258.0 — `consequenceFor` renders through message keys in the household's language (`controller/internal/web/datapath_card.go:32-50`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-592** | **Two defects in the new catalog copy gate, each found by its own decoy rather than by reading it. (P3)** | CLOSED 2026-09-20 — fixed in the same session, decoys added (`scripts/test_gate_decoys.py`, 33 cases): coverage scoped to named apps, credential and ASCII-stem bare-substring matches. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-595** | **The catalog's new copy gate could not RUN in CI at all — six pushes red, six alarm mails, while the local hook was green. (P2)** **Reasoning kept:** «**The general form, and the reason this is P2 rather than P3:** a new gate is written and tested on the machine that has every library, and the runner deliberately has none.» | CLOSED 2026-09-20 — degraded mode; CI job 791 green (catalog `18a6d2d`: without PyYAML the gate runs the freeze check and prints what it did not check; five decoys with PyYAML shadowed). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-596** | **The claim page — the FIRST screen an English household touches — is English chrome with HUNGARIAN messages. (P1)** **Reasoning kept:** «It was **deleted, not translated**: a translated dead field would have read for ever after as evidence that this page's title is decided in the handler.» | CLOSED 2026-09-21 — controller v0.259.0, proven live (fourteen sites via `s.msg(r, "claim.msg.*")`; dead `data["Title"]` deleted; language chain pinned by a test; live on guest 9201 both languages; red-proofed). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-597** | **The setup code is three Hungarian words inside an otherwise fully English e-mail to an English household. (P2)** **Reasoning kept:** «**The task's proposed "read it over the phone" filter was MEASURED and NOT adopted** — it removes 5270 of 7772 words (68%, 12.92 → 11.29 bits/word) and would make this list stricter than the one the product already uses for the code a household writes on paper during a disaster;» «The hub does not own that secret and no row was opened for it: a second definition here is the drift `backupTargetAbsentText` already demonstrates across two repos.» | CLOSED 2026-09-21 — hub v0.119.0: `RandomPassphraseFor(lang, use)`; setup code 4 en words (51.7 bits), owner passphrase 6 en (77.5); entropy floor computed from embedded lists, red-proofed. Recovery code was never Hungarian (agent, EFF list). Decision in source, operator may reverse. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-598** | **The Backup page's two protection warnings are Hungarian on an English dashboard. (P2)** **Reasoning kept:** «**The English is asserted to carry the same NEGATION the Hungarian does** ("protects against corrupted files, **but not** against a disk failure"); an English sentence that promised disk-failure protection would be worse than leaving it Hungarian.» | CLOSED 2026-09-21 — controller v0.259.0; the two warnings proven by render test, not live (tier names proven live on 9201; `degradedMessageFor` returns a KEY). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-601** | **demo-hp is unreachable — WRONG, WITHDRAWN THE SAME DAY. The box was never down; MY ROUTES WERE. (P2)** **Reasoning kept:** «**The standing rule says a "no access" claim must list what was tried; it does not say the list makes the claim true.**» «Six failed routes to a stale address are six failures of one assumption, not six pieces of evidence.» | CLOSED 2026-09-21 — withdrawn, the claim was false; the routes are fixed (both `~/.ssh/config` entries repointed to 192.168.0.104; `nodes.md` corrected; tailscale not installed on demo-hp). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-608** | **The controller swaps ITSELF in the middle of a guarded app update, and 04:30 sits inside the proposed auto-update window. (P2)** **Reasoning kept:** «**THE PROPERTY THAT MATTERS MOST IS THAT THE LOCK DOES NOT LATCH:** `Stack.Updating` is cleared on done, failed AND held, so a HELD app does not block the controller's own updates — including the release that might fix whatever held it.» «**`stacks` never imports `selfupdate`.**» | CLOSED 2026-09-21 — controller v0.261.0: `Manager.AnyUpdating()` → `Updater.SetAppUpdatingCheck`; `UpdatePreflight` refuses `self_updating`; lock does not latch (`TestR608_LockReleasesAfterHold`); four red-proofs. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-609** | **An update refusal has a machine-readable reason inside the process and none on the wire, so an unattended caller cannot tell "wait" from "never" (P3-LOW)** **Reasoning kept:** «`busy`/`updating`/`deploying`/`migrating`/`self_updating` are TRANSIENT and `held`/`downgrade` are TERMINAL until a person acts.» | CLOSED 2026-09-21 — controller v0.261.0: 409 body gains data.reason additively; the held refusal in actionStack (before UpdatePreflight) carries it too; red-proofed. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-611** | **A session reported "everything is done" over a phase it had silently skipped — the process failure, not the missing measurement (P3-LOW)** **Reasoning kept:** «the report's FIRST section is "not done", even when empty.» «Every part and scenario of a brief is listed there if it was skipped, shortened or changed, with the reason.» | CLOSED 2026-09-21 — run by the successor session (scenarios F and G); "not done" is now the report's first section. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/update-arc-gaps-2026-09-21/ |
|
||||
| **R-614** | **A stale `update_phase` survives a remove and redeploy, so a freshly installed app can read "Frissitve" before it has ever been updated (P3-LOW)** **Reasoning kept:** «The name is the only thing a new install shares with the old one, so the record has to go when the app does.» | CLOSED 2026-09-22 — controller v0.262.0: RemoveStack calls ClearUpdateState; red-proofed. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-623** | **The unattended-update caller turned every SUCCESS into a `timeout`, then refused to press that app again — the instrument, not the box (P3-LOW)** **Reasoning kept:** «an instrument that can report a success as a timeout is not a measurement» | CLOSED 2026-09-21 — fixed in audits/update-arc-gaps-2026-09-21/unattended-caller.py (unwraps data). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/update-arc-gaps-2026-09-21/unattended-caller.py |
|
||||
| **R-627** | **Nothing checked that the register is a well-formed table; an append ate two rows' state cells and R-254 was broken for 45 days (P2-MEDIUM)** | CLOSED 2026-09-22 — gate 14 (scripts/register_shape_gate.py in repo_gates.py), four decoys, register repaired 317→315. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-628** | **An empty search of a mailbox I do not control was turned into a claim about what a THIRD PARTY had done, and entered the register as fact (P2-MEDIUM)** **Reasoning kept:** «*an empty search may be reported as "absent from the place I looked", never as "it did not happen" — and only after a control query that MUST hit has been seen to hit.*» | CLOSED 2026-09-22 — rule recorded in the Gmail-access memory; R-433 corrected with the real answers. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-629** | **The drill catalog sent the operator 47 CI-failure alarms in one night; the drill method did not mention CI (P2-MEDIUM)** | CLOSED 2026-09-22 — has_actions false on admin/app-catalog-drill, `09` §6.5 method updated; the 47 mails are the operator's to clear. — residue NOT-A-FINDING (2026-10-03): its residue (47 unread CI mails) is the operator's inbox housekeeping, not product work | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-630** | **`paperless-ngx`'s health probe has never run on any box; a successful update stops the app (P2-MEDIUM, raised to P1)** **Reasoning kept:** «A stack with no probe is not healthy and not failing — it is SETTLED ON CONTAINER STATE (`09` §3), and never a reason to stop a running app.» | CLOSED 2026-09-22 — controller v0.262.0 + catalog: the no-probe wait settles on container state, the target is decidable (explicit container field), the gate refuses an ambiguity. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/the-28-2026-09-22/sidejobs/r630-controller-words.txt, audits/the-28-2026-09-22/sidejobs/r630.json |
|
||||
| **R-631** | **Five templates cannot be judged by the probe gate at all, and one is correct only by accident (P3-LOW)** **Reasoning kept:** «Add `expect: {status: 200}` to that template — a change that looks like a tightening — and home-assistant goes permanently unhealthy and every successful update of it starts stopping it.» | CLOSED 2026-09-22 — all five read live on guest 9202, all five probes correct; home-assistant's fragility is now a number (401 with no expect). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/the-28-2026-09-22/sidejobs/r631.json |
|
||||
| **R-632** | **Twenty-eight of the 53 templates have never been deployed by any update drill (P3-LOW)** | CLOSED 2026-09-22 — all 28 walked in one night (26 deployed, 6 proven); findings → R-630, R-633, R-634. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/DRILL-the-28-2026-09-22.md, audits/probe-fix-2026-09-22/not-judged.json |
|
||||
| **R-633** | **A remove sent while a restore runs reports success, deletes the record, and leaves a container restarting forever with a live public route (P2-MEDIUM)** **Reasoning kept:** «`down` returning 0 is a request, not a result» «A refusal from the wrong rule is not evidence for the new one.» | CLOSED 2026-09-22 — controller v0.262.0 (busy guard + verified teardown) + v0.262.1 (typed RemoveBusyError → HTTP 409); both halves proven live on 9202. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/the-28-2026-09-22/apps/gokapi/came-back-evidence.txt, audits/the-28-2026-09-22/apps/gokapi/log.txt |
|
||||
| **R-701** | **demo-hp's whole-guest restore test can never run: every 6 h it picks the right archive and the space preflight refuses it (P3-LOW)** | CLOSED — option (b), 2026-09-28 (operator, `09` §3 decision 44): restore_storage → nvme-scratch, FelhomAgentStore granted on /storage/nvme-scratch; manual and first scheduled cycle passed. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/version-travel-2026-09-26/D1/D1-cycle-demo-hp.txt, audits/evidence-golden-0276-2026-09-28/phaseD1-reclaim.txt, audits/logins-nvme-2026-09-28/C/ |
|
||||
| **R-702** | **Every claper install creates an admin `admin@claper.co` with the public password `claper`, published on the household's domain (P1-HIGH)** | CLOSED — claper fixed 2026-09-28 (catalog 9dc8a05, controller v0.279.0 after_install); the class → R-707. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/pg-calcom-claper-2026-09-28/box/C0-claper-default-admin.txt |
|
||||
| **R-703** | **calcom v6.2.0 cannot start at its catalog memory limit — a fresh install crash-loops and the box stops it (P2)** | CLOSED — catalog 9555e73, 2026-09-28: memory 768M → 1536M (anon peak 817 MiB, 53%). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/pg-calcom-claper-2026-09-28/box/C0-calcom-crash.txt, audits/pg-calcom-claper-2026-09-28/box/C0-calcom-memory.txt |
|
||||
| **R-708** | **grafana falls back to password `admin` when its admin field is empty (P3-LOW)** **Reasoning kept:** «no default in the compose (`${GF_SECURITY_ADMIN_PASSWORD:?}` refuses to start instead).» | CLOSED — 2026-09-29 (catalog d0e7e2e): `${GF_SECURITY_ADMIN_PASSWORD:?…}` refuses to start empty. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/login-gate-2026-09-29/D/D4-grafana-r708.txt |
|
||||
| **R-709** | **The deploy page writes the generated admin passwords of installed apps into its HTML (P3-LOW)** | CLOSED — 2026-09-29, controller v0.280.0: password field renders empty with a reveal eye; red-proofs RP14/RP15; live on 9202. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/login-gate-2026-09-29/D/D6-r709-live.txt |
|
||||
| **R-710** | **An app installed before its template gained an `after_install:` is never warned about its default login (P2-MEDIUM)** | CLOSED — 2026-09-29, controller v0.280.0 (RP13): absent record is "not run yet" only 30 min after install; „Megváltoztattam" press; live on demo-hp. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/login-gate-2026-09-29/A/A2-page-warning-after.txt, audits/login-gate-2026-09-29/A/A3-demo-hp-changed-it.txt |
|
||||
| **R-711** | **About a dozen class-4 apps keep open sign-up after their first admin exists — the setup gate does not close that (P2-MEDIUM)** | CLOSED — 2026-09-29 (decision 47, controller v0.281.0, catalog 6faf432): per-app signup_block written when the gate opens; wanderer → R-714. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/login-gate-2026-09-29/B/B-VERDICT.md, audits/gate-rollout-2026-09-29/ |
|
||||
| **R-712** | **wger refused every browser sign-in behind traefik: "CSRF verification failed" (P2-MEDIUM)** | CLOSED — 2026-09-29 (catalog d0e7e2e): CSRF_TRUSTED_ORIGINS + X_FORWARDED_PROTO_HEADER_SET; proven on a fresh install. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/login-gate-2026-09-29/D/D2-live.txt |
|
||||
| **R-713** | **claper's `after_install` pastes the household's password into Elixir code, and the controller does not refuse a value that would break such code (P3-LOW)** **Reasoning kept:** «a code-bound value holding a quote, backslash, `$`, `{`, `}`, backtick or line break is refused» | CLOSED — 2026-09-29, controller v0.281.0 (RP24) + catalog 6faf432: code-bound unsafe values refused, `${NAME¦base64}` added; proven live. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/gate-rollout-2026-09-29/ |
|
||||
| **R-714** | **wanderer cannot be gated: its web part calls its own database host through the public name (P2-MEDIUM)** | CLOSED — 2026-09-29 (decision 48, controller 0.282.0): resolved without a gate; sign-up closed by "Close sign-up now" block + PUBLIC_DISABLE_SIGNUP; proven on 9202. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/signup-lock-2026-09-29/ |
|
||||
| **R-715** | **The setup gate's probe reads only an HTTP-200 JSON object, so three apps with a real status get the button (P3-LOW)** **Reasoning kept:** «a catalog gate that refuses a probe without a before/after measurement in its comment.» | CLOSED — 2026-09-29, controller v0.282.0 (RP29, RP30): list indexes in field, done_status:; new catalog gate check-probe-measured.py (5 decoys). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/signup-lock-2026-09-29/ |
|
||||
| **R-716** | **Apps installed before controller 0.281.0 keep their open sign-up — decision 47 closes it only where the box opened the gate (P3-LOW)** **Reasoning kept:** «a catalog change never touches an installed app» | CLOSED — 2026-09-29: operator ruled A (decision 49); built in controller v0.282.0 and pressed on demo-hp adventurelog/opengist and demo-felhom opengist. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/gate-rollout-2026-09-29/0/P0-3-demo-boxes-after-push.txt, audits/signup-lock-2026-09-29/ |
|
||||
| **R-720** | **A new household's apps are not in the off-site copy: every app starts „3. mentés Kikapcsolva" (P2-MEDIUM)** | CLOSED 2026-09-30 — controller v0.283.0 (decision 50): fresh install on an off-site box switches the app ON; one-press offer for older apps. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-fixes-first-tester-2026-09-30/partA/ |
|
||||
| **R-721** | **The household presses Stop during a whole-guest backup, and the backup starts the app again (P2-MEDIUM)** **Reasoning kept:** «unquiesce restarts only stacks whose desired state is still running» | CLOSED 2026-09-30 — controller v0.283.1, proven live (v0.283.0 was wrong in production; v0.283.1 wires the adapter and pins the production types). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-drill-new-household-2026-09-30/phase1/step10-stop-undone-by-quiesce.log, audits/evidence-fixes-first-tester-2026-09-30/partE/ |
|
||||
| **R-722** | **The volunteer guide is stale in five places a volunteer reads literally (P2-MEDIUM)** | CLOSED 2026-09-30 — both guides rewritten as measured (operator: CC rewrites); every changed line dated. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: runbooks/VOLUNTEER-first-hour.md |
|
||||
| **R-727** | **The whole-guest restore test picks a PREVIOUS box's archive, fails on its key, and the ✗ is labelled with the wrong tier (P2-MEDIUM)** **Reasoning kept:** «The restore test skips an archive written with another key (logged by name).» | CLOSED 2026-09-30 — agent v0.138.0 + decision 51 (restore test skips archives written with another key); ✗ card names the tier in controller v0.283.0; ep0 drill archives in tester-1 removed; delivered by signed jobs to both demo boxes. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/evidence-fixes-first-tester-2026-09-30/partC/ |
|
||||
| **R-730** | **The published ISO 1.29.0 was built from a tree that git cannot name, so it cannot be tagged (P3-LOW)** **Reasoning kept:** «`scripts/iso/build-felhom-iso.sh` now runs a clean-tree gate right after argument parsing: any uncommitted or untracked change, or HEAD ≠ `origin/main`, refuses the build (no bypass flag); the manifest records `repo-commit` from the gate and `iso-version-tag : iso-v<version>`.» «the `installer-v*` tags are the host-install SCRIPT's versions (`SCRIPT_VERSION` 1.28.0 on main; the website serves `/scripts/` from `installer-v1.28.0`), a different number line from the ISO — an `installer-v1.29.0` tag would block the next script release.» | CLOSED 2026-09-30 — the gate + test; 1.29.0 untagged by decision (its build tree is not a commit). build-felhom-iso.sh clean-tree gate (no bypass), manifest records iso-v<version>; test scripts/iso/test/clean-tree.sh red-proofed. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/pg-last-six-2026-09-30/E/E4-installer-tag.txt, scripts/iso/test/clean-tree.sh |
|
||||
| **R-732** | **immich's FIRST start at the catalog pin could not finish on the bench: its database container was OOM-killed at 512 MiB (P2-MEDIUM)** | CLOSED 2026-09-30 — catalog `56c4888` (768M + v3.2.4); older step 0b8272068aab36bf re-proven at 768M (catalog `48440ce`, `63a96b0`); an installed immich gets it with its next guarded Update. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/pg-last-six-2026-09-30/F/immich-first-start-oom.txt, audits/immich-first-start-2026-09-30/A-cause.md, audits/more-night-apps-2026-09-30/ |
|
||||
| **R-735** | **The test bench generates a `password:N:special` deploy field WITHOUT a special character, so calibre-web refuses it (P3-LOW)** | CLOSED 2026-09-30 — catalog `5b1972b` (`_gen` mirrors `randomWithSpecial`; test seen failing first). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/bench/apps/calibre-web/, A/R735-red.txt, A/R735-green.txt |
|
||||
| **R-736** | **Removing or updating an app never deletes the image it pulled, so a box's Docker disk fills until an install or update is refused (P2-MEDIUM)** **Reasoning kept:** «Keep set box-wide at delete time; exact id; no pass while an update runs; one summary line per pass; a one-time sweep.» «the Remove button runs `RemoveStack`, not `DeleteStack`; `docker image ls` without `-a` hides the untagged digest-pulled images» | CLOSED 2026-09-30 — controller v0.284.2, floor 0.284.2 (decision 53, option A; 0.284.0/0.284.1 never floored). Old controller images → R-745. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/box/T2-images-removed-by-name.txt, audits/night-rulings-2026-09-30/ |
|
||||
| **R-737** | **wger's app login API answers 500 on a CORRECT password: the template sets no `JWT_PRIVATE_KEY` (P3-LOW)** | CLOSED 2026-09-30 — catalog `45d8482` (RSA pair generated once by wger's `manage.py generate-jwt-keys`, kept 0600 on the data volume; bench-proven, not proven on a box). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/B/wger-probe.txt, audits/night-rulings-2026-09-30/ |
|
||||
| **R-740** | **A same-tag upstream security fix (postgres:18-alpine, redis:7-alpine, mariadb:12.3 …) reaches NO box, because the catalog never records a re-test of a tag at a new digest (P2-MEDIUM)** **Reasoning kept:** «Not a cron job (runbook `runbooks/monthly-floating-retest.md` says why); who presses it monthly is the operator's word (STATUS).» | CLOSED 2026-09-30 — built (catalog `6a3ead9`); the monthly run is a standing step. Decision 52 option A: re-test entries + `scripts/retest-floating.py`; proven end to end on 9202. Exact-tag re-pushes → R-743. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/C/C3-floating-repush.txt, audits/more-night-apps-2026-09-30/C/C4-same-tag-retest.txt, runbooks/monthly-floating-retest.md, audits/night-rulings-2026-09-30/ |
|
||||
| **R-741** | **For a few seconds after a fresh install, an `after_install` app answers its PUBLIC default login through the front door (P3-LOW)** **Reasoning kept:** «an `after_install` app is installed HELD behind the setup gate's door until the login is replaced (or the household says it changed it).» | CLOSED 2026-09-30 — controller v0.284.2 (install hold: an after_install app is installed held behind the setup gate's door until the login is replaced; red-proofs RP-IH1..4). mealie lock → R-747. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/A/A3-calibre-default-login-window.txt, audits/night-rulings-2026-09-30/ |
|
||||
| **R-742** | **zipline 4.8.0 cannot be reached from 4.6.1 in one step: it refuses to start until the database ran the release before it (P3-LOW)** | CLOSED 2026-09-30 — catalog `a9700e2` + `fb87030` (two steps 4.6.1 → 4.7.0 → 4.8.0, each proven on both venues). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/more-night-apps-2026-09-30/bench/apps/zipline/, audits/more-night-apps-2026-09-30/box/zipline/ |
|
||||
| **R-743** | **Exact version tags are re-pushed under the same name too — not only floating lines (P3-LOW)** **Reasoning kept:** «decisions 54 (a CC session the operator starts with the standing brief runs it monthly) and 55 (every app with a proven ladder; `--engines-only` a switch).» | CLOSED 2026-10-01 — decisions 54, 55; both tags re-tested (nextcloud catalog `3b59dfb`, sonarr `1a37032`). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/night-rulings-2026-09-30/A/, audits/retest-2026-10/ |
|
||||
| **R-744** | **outline's fixture cannot seed outline 1.10.1: no `csrfToken` cookie after `installation.create` (P3-LOW)** | CLOSED 2026-10-01 — fixture fixed, proven on 9202 (accepts `__Host-csrfToken`; catalog `9fc7052`). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/rulings-2026-10-01/D/D2-outline-fixture-9202.txt |
|
||||
| **R-745** | **Old CONTROLLER images are never deleted: ~50 versions on each demo box (P3-LOW)** **Reasoning kept:** «the agent rolls back to the RUNNING image (what `/etc/felhom-controller-image` named), never to a previous one the controller hands it.» | CLOSED 2026-10-01 — controller v0.285.0, floor 0.285.0 (decision 56: keep running + previous, delete older/untagged). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/night-rulings-2026-09-30/E/, audits/rulings-2026-10-01/B/ |
|
||||
| **R-746** | **`image_digest.resolve` ignores a `@digest` in its argument — it answers the TAG's current digest (P3-LOW)** | CLOSED 2026-10-01 — catalog 804884a (manifest asked by digest; malformed digest refused; scripts/test_image_digest.py red-proofed). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/rulings-2026-10-01/D/D1-r746-digest.txt |
|
||||
| **R-748** | **The register-shape gate skipped every row whose id has a letter suffix (R-88a, R-88b, R-209a) (P3-LOW)** | CLOSED 2026-09-30 — `scripts/register_shape_gate.py` (`R-\d+[a-z]?`; decoy `suffix-row-eaten-state`). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-749** | **`retest-floating.py` could never start on a fresh bench: it checked for `/opt/upg/upgrade-test.py` before the step that copies it (P3-LOW)** | CLOSED 2026-10-01 — catalog 9e53205. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/rulings-2026-10-01/A/ |
|
||||
| **R-750** | **The registry no longer holds controller releases older than 0.213.0 — something removed them, and nothing records what (P3-LOW)** **Reasoning kept:** «`gitea-image-prune.sh` keeps the newest 20 + every version in use (floor, vouched golden, vouched agent, `min_agent`, the golden's baked images, the running hub), refuses (exit 3) when that list is unreadable» | CLOSED 2026-10-01 — decision 62, misc-scripts c9d5ed5 (cause: manual `gitea-image-prune.sh --keep 7` run 2026-08-22/23, HM-024; prune now keeps newest 20 + every in-use version, exit 3 when unreadable). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/rulings-2026-10-01/B/, audits/lockouts-2026-10-01/C/C1-registry-read.txt, audits/calibre-name-and-prune-2026-10-01/B/ |
|
||||
| **R-751** | **The image clean-up after an app update could crash the whole controller: nil stack dereference when the app was gone (P2)** | CLOSED 2026-10-01 — controller v0.285.0, floor 0.285.0 (TestRetainImagesAfterUpdate_AppGoneDoesNotPanic seen panicking first). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/rulings-2026-10-01/B/B1-red-proofs.txt |
|
||||
| **R-752** | **Four more catalog apps let a stranger lock the household out with wrong passwords for a known login name — like mealie (P3-LOW)** **Reasoning kept:** «Installed apps: a settings-only change reaches the stack file at the next sync (images equal, ≤15 min) and the running app at the next `compose up -d` — Restart/Start (measured: the env changed only at Restart), an Update, or a backup's restart (`backup.go:972`, read).» | CLOSED 2026-10-01 — decisions 58–61 (wger catalog `82fff32`; BookStack, Grafana kept; calibre-web generated ADMIN_USER catalog `e9f50b5`). Installed calibre-web's invented name → R-757. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/lockouts-2026-10-01/B/ |
|
||||
| **R-753** | **Behind the tunnel every visitor reaches an app with the SAME address, so every per-address guard is an 'everyone' guard (P3-LOW)** **Reasoning kept:** «cloudflared alone on `felhom-tunnel` at the fixed `172.16.253.2`, traefik trusts forwarded headers from it only, an entrypoint middleware removes client-writable host/path/address headers» | CLOSED 2026-10-01 — controller v0.286.1 (decision 63; v0.286.0 never floored; catalog `04e9516`..`50e4fb4`) (rows R-776..R-779 carry what is left). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/lockouts-2026-10-01/A/A1-client-address.txt, audits/visitors-2026-10-01/A/ |
|
||||
| **R-754** | **`01-topology-and-trust.md` §7 says cloudflared runs on the Proxmox HOST; on every box it runs INSIDE the guest (P3-LOW)** **Reasoning kept:** «the data path is not independent of the guest.» | CLOSED 2026-10-01 — document corrected (operator: the build is right). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-765** | **A new app that builds its login from a `type: password` value at EVERY start loses the login on a restore after a remove — Radicale (P2-MEDIUM)** **Reasoning kept:** «a restore carries only `type: secret` values (`stacks.PortableSecretEnvVars`, the D5 ruling — a `type: password` is an internet-reachable login and stays out of the drive's backup)» «the login file is written on the FIRST start only and lives on the data volume.» | CLOSED 2026-10-01 — Radicale's template, before publishing (login file written on first start only, on the data volume). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/new-apps-2026-10-01/box/radicale-attempt1/restore.txt, audits/new-apps-2026-10-01/box/radicale/restore.txt |
|
||||
| **R-767** | **MeTube has no login at all, by design, and the box has no permanent household-only door — so it is not built (P3-LOW)** | CLOSED 2026-10-02 — controller v0.287.0 + catalog (MeTube published) behind the family gate, decision 64, no exception. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/new-apps-2026-10-01/FIT.md, audits/family-gate-2026-10-02/A/items.txt, audits/family-gate-2026-10-02/B/box/metube-fresh.txt |
|
||||
| **R-772** | **A health probe that finds NO container to probe records `healthy: true` (P3-LOW)** **Reasoning kept:** «a not-run probe records `healthy: false, not_checked: true`, is looked at on the next tick» | CLOSED 2026-10-01 — controller v0.286.1 (not-run probe records healthy false, not_checked true; red-proofs RP-D1, RP-D1b). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/new-apps-2026-10-01/box/karakeep-768M/neg-and-crawl.txt, audits/visitors-2026-10-01/D/ |
|
||||
| **R-773** | **After a remove + restore, an app's sign-up route block (decision 47) is gone (P3-LOW)** **Reasoning kept:** «a REMOVED app restored from its backup gets the lock record (`opened_by: restore`) and the block written before anything starts» | CLOSED 2026-10-01 — controller v0.286.0 (restored app gets lock record `opened_by: restore` + block before start; red-proof RP-D2). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/new-apps-2026-10-01/box/karakeep/restore.txt, audits/visitors-2026-10-01/D/r773-live.txt |
|
||||
| **R-780** | **A permanent household gate with family accounts — the spike PASSED; the build waits for the operator's go (P2-MEDIUM)** **Reasoning kept:** «**Build requirement F1:** exceptions must be anchored (`PathPrefix(/api/v1/opds)` also matched `/api/v1/opdsx`).» | CLOSED 2026-10-02 — controller v0.287.0 (decision 64; internal/family, Család card, family_gate fields, catalog gate check-family-gate.py; Grimmory and MeTube published). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/permanent-gate-2026-10-01/VERDICT.md, audits/family-gate-2026-10-02/A/items.txt, audits/family-gate-2026-10-02/E/floor.txt |
|
||||
| **R-787** | **No catalog app's LICENCE has been checked against Felhom being a paid service (P2-MEDIUM)** **Reasoning kept:** «New apps carry their licence in record row 0.1.» | CLOSED 2026-10-02 — the read (decisions are R-784, R-789..R-795). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/licences-2026-10-02/TABLE.md |
|
||||
| **R-788** | **The volume-persistence gate reads an EMPTY declared volume as CLEAN (P3-LOW)** **Reasoning kept:** «an app that wrote nothing is UNDETERMINED (exit 2), never a pass.» | CLOSED 2026-10-02 — catalog (empty declared volume after the exercise is UNDETERMINED, red-proofed; APP_EXERCISE added). Bind mounts → R-805. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/family-gate-2026-10-02/C-metube-bench/C2-volume-persistence-metube.txt, audits/persistence-sweep-2026-10-02/A/RP-R788-empty-volume.txt |
|
||||
| **R-790** | **Emby is proprietary: a personal, non-commercial, non-transferable licence (P2-MEDIUM)** **Reasoning kept:** «KEPT — the household is the licensee; Felhom installs the official, unchanged image.» | CLOSED 2026-10-02 — operator ruling (kept), decision 66; part of the lawyer's review R-802. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/licences-2026-10-02/TABLE.md |
|
||||
| **R-791** | **n8n's Sustainable Use License allows own internal or personal use; distribution only free of charge for non-commercial purposes (P3-LOW)** **Reasoning kept:** «KEPT — the household is the user; the official, unchanged image.» | CLOSED 2026-10-02 — operator ruling (kept), decision 66; part of the lawyer's review R-802. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/licences-2026-10-02/TABLE.md |
|
||||
| **R-792** | **Plex is proprietary: the HOUSEHOLD is the licensee under its own Plex account (P3-LOW)** **Reasoning kept:** «KEPT — the household is the licensee under its own Plex account.» | CLOSED 2026-10-02 — operator ruling (kept), decision 66; part of the lawyer's review R-802. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/licences-2026-10-02/TABLE.md |
|
||||
| **R-795** | **recipe-importer, Felhom's own image, has no licence file (P3-LOW)** **Reasoning kept:** «Felhom's own code — no action unless it is shared with anyone.» | CLOSED 2026-10-02 — operator ruling (no action unless shared), decision 66. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/licences-2026-10-02/TABLE.md |
|
||||
| **R-800** | **[P2-MEDIUM] "Delete your data from the hard drive" keeps the app's files in the household's userdata folder — and neither the dialog nor the result says so.** | CLOSED 2026-10-03 (triage, verified) — controller v0.288.0 (`580b656`, decision 67) — the remove dialog and result name the kept userdata folder; live on 9202 (`0945332`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| **R-801** | **The volume-persistence gate never sent a request to ANY app: it read the routed port from label VALUES, not the label NAME (P2-MEDIUM)** | CLOSED 2026-10-02 — catalog (the fixed gate `routed_ports()` + the re-sweep of all 58: CLEAN 39 · UNDETERMINED 19 · BROKEN 0). Follow-ups R-805..R-807. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/family-gate-2026-10-02/B/volume-persistence-metube-exerciser-diag.txt, audits/family-gate-2026-10-02/B/RP-R801-routed-ports.txt, audits/persistence-sweep-2026-10-02/A/sweep/TABLE.md |
|
||||
| **R-803** | **papra cannot start under its own memory limit: a fresh install is OOM-killed in its migration and crash-loops (P1-HIGH)** | CLOSED 2026-10-02 — catalog (papra 768M, mem_request 256M; measured from birth, 0 kills, persistence CLEAN; no box ran papra). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/persistence-sweep-2026-10-02/A/sweep/papra-oom-diagnosis.txt, audits/persistence-sweep-2026-10-02/A/papra/SUMMARY.md |
|
||||
|
||||
**Rows that never had an `R-` id** (the 2026-07-28 table of campaign findings and watches):
|
||||
|
||||
| Row | What | Closed | Full text |
|
||||
|---|---|---|---|
|
||||
| E-2d | **Prove E-2 on a fresh VM — a real host-install 1.22.0 run, Case B, a claimable customer, add a drive and unplug it.** | CLOSED — PARTIALLY PROVEN (2026-07-29). C1, C2 proven; C3, C4 proven live; C5 FAILED → R-116 (the single named open leg; R-116 since CLOSED in CLOSED-ITEMS). Per Session-C runbook §9 a failed claim closes as partially proven, no re-run. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/E2D-fresh-vm-2026-07-29.md, audits/SESSION-C-2026-07-29.md |
|
||||
| — (watch, line 285) | **Storage Box **snapshots** on `storage-box-pool-1` — plan SET (daily 00:00, keep 7) but **0 taken yet**** | CLOSED 2026-10-03 (triage) — snapshots exist: R-429 recorded seven daily Storage Box snapshots (2026-09-01) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| — (watch, line 289) | **demo-felhom's next weekly PBS backup (newest is 2026-07-26)** | CLOSED 2026-10-03 (triage) — folded into R-91, whose trigger is exactly this backup | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| — (watch, line 290) | **demo-felhom's next restore-test (84 h cadence, last 2026-07-27 06:38 UTC)** | CLOSED 2026-10-03 (triage) — restore tests have run on demo-felhom on their cadence ever since (R-672, R-689 in `CLOSED-ITEMS.md`) | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-CRIT-2 | **~~A failed offsite backup left a phantom snapshot that RESET the tier's freshness clock — 7 days silent~~** **Reasoning kept:** «`NewestArchiveTime` now counts only plausibly-complete entries (measured 1 MiB floor; undecidable ⇒ not counted).» | SHIPPED + PROVEN-LIVE (agent v0.106.0, 2026-07-28). Campaign fault 2 replayed on demo-hp: phantom rejected + logged once, tier DUE and backed up, no thrash on the inverse. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-CRIT-1 | **~~An app that fails to restart after a quiesce never alarms on any channel~~** | SHIPPED + PROVEN-LIVE (controller v0.179.0, 2026-07-28). Live on demo-hp: alarmed 9s after grace expiry; a deliberate user stop stayed silent through 9 dead-app scans. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-A1 | **~~A restore-test in flight made a healthy backup report as FAILED (HTTP 409 read as a tier failure)~~** **Reasoning kept:** «409 → contention: tier stays DUE, dropped before anything stops (15m), and BLOCKED alarm if contention outlives the agent's 120m ceiling (3h).» | SHIPPED + PROVEN-LIVE (controller v0.179.0, 2026-07-28). Hub DB: 409 → 0 operator emails, real failure → 1. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| C9-F1 | **~~Tier-2 „Fájlok visszaállítása" is offered for apps whose copy has no restorable file leg; restores 0 files and reports all files present~~** **Reasoning kept:** «`Tier2RestoreCoverage` refuses UP FRONT without stopping the app and NAMES the working action; a run that proceeds claims only what it **examined** and discloses that the database and volumes are not covered.» | SHIPPED + PROVEN-LIVE (controller v0.183.0, 2026-07-28). Live on demo-felhom: bookstack refused without restart; paperless re-run byte-identical, 16/16 docs. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| C9-F2 | **~~An app in a Docker crash loop never alarms on any channel; `StateRestarting` is in no down-set~~** **Reasoning kept:** «`StateRestarting` deliberately NOT added to `IsDownState` (that alarms on every deploy fleet-wide)» | SHIPPED + PROVEN-LIVE (controller v0.183.0, 2026-07-28). Sustained restarting becomes down after `crashLoopAfter`=5m; dashboard counter uses the same predicate. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| C9-F3 → R-104 | **An **interrupted offsite run leaves an exclusive restic lock the self-heal cannot reach**: `resticStep` (`offbox.go:634-648`) has `unlock --remove-all`, but `ensureOffboxRepo`'s probe fails first, `classifyResticProbe` (`offbox.go:77-93`) has no lock case → …** | FOLDED into R-104 — the same finding; R-104 has its own open row | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| D5 | **~~Move app secrets into the LOCAL recovery unit so Tier-1/Tier-2 restore stop needing the guest~~** **Reasoning kept:** «**Operator ruling 2026-07-30: `type: secret` travels (45 fields), `type: password` NEVER (7) plus a code register (`vaultwarden/ADMIN_TOKEN`); plaintext.** The exclusion is what LICENSES the plaintext — coupled, not independent.» «the register is **code, not a catalog flag** (a boundary a catalog push can move is not a boundary — R-97a).» «**Precedence: the UNIT WINS** over the guest, because the unit's secrets were captured in the same run as the dumps beside them and therefore match the data being restored; pinned both directions.» | SHIPPED + PROVEN-LIVE (controller v0.188.0, 2026-07-30). Operator ruling 2026-07-30 on which secrets travel; manifest schema 2; live proof on scratch guest (AdventureLog secrets recovered 2/2, app read seeded row over TCP); 4 red-proofs. Related: R-127. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/D5-drive-alone-restore-2026-07-30.md |
|
||||
| F-DIAG | **~~Four distinct offsite failure causes collapse into two operator-visible strings~~** **Reasoning kept:** «Unclassifiable says so rather than being folded into a neighbour.» | SHIPPED (controller v0.182.0, 2026-07-28). `ClassifyOffsiteFailure` → quota / orphaned / no_repo / no_units / transport / unknown; redaction by the target's actual host/user/path. Unit-proven; not yet exercised by a live offsite failure of each class. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-OPS | **~~A manual `pct restore` inherits the source guest's bind mounts — during a real DR, on a different host, under pressure~~** **Reasoning kept:** «Docs only by design — the agent already neutralises binds on its own restore paths, and a second implementation would drift» | DOCUMENTED (2026-07-28) in `runbooks/RUNBOOK-manual-guest-restore.md` (mpN volumes vs binds, the mp9 source-VMID trap, strip-and-re-add, positive pre-start verification). | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: runbooks/RUNBOOK-manual-guest-restore.md |
|
||||
| F-REBOOT | **~~A guest rebooted during its backup does not come back — 9m47s outage with every alarm silent~~** **Reasoning kept:** «`onboot` is the deliberate-stop discriminator (already the stale-lock path's, and what `pve-guests` consults)» | SHIPPED + PROVEN-LIVE (agent v0.107.0, 2026-07-28). 60 s guest-power watchdog, retry 3x/1m-2m-4m then escalate. Live on demo-hp: 120 s unattended vs 587 s; `onboot:0` guest left stopped. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-LEAK | **~~A failed restore-test cannot destroy its own scratch guest (403 `VM.Allocate`); the 10-slot VMID band shrinks silently~~** **Reasoning kept:** «4th root-fenced exception, band enforced in sudoers **literally** (`pct destroy 99000[0-9] --purge`) + in code + at the caller; API destroy still tried first.» | SHIPPED + PROVEN-LIVE (agent v0.110.0 + host-install v1.21.0, 2026-07-28). Third attempt shipped: 4th root-fenced exception; live band permitted, 9201/9100/9999/990010/1 and `pct start 990000` refused. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| F-OBS | **~~`deadapp-check` leaves NO positive observable on a default (info-level) box~~** | SHIPPED + PROVEN-LIVE (controller v0.180.0 + agent v0.109.0, 2026-07-28). INFO summary every 20th scan; agent v0.109.0 fixes the same shape in the guest-power watchdog. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| E-2 | **~~Drive-role machinery around the moved vzdump target~~** | CLOSED — PARTIALLY PROVEN (Session C, 2026-07-29). C1/C2 proven in E-2d; C3, C4 proven live (R-114, R-112); C5 FAILED → R-116. Broken legs R-112, R-113, R-114, R-116 all since CLOSED (CLOSED-ITEMS). Arc's definition of done: R-106+R-109, R-108, D5. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md`; evidence: audits/SESSION-C-2026-07-29.md, audits/E2D-fresh-vm-2026-07-29.md |
|
||||
| E-2a | **~~The target move needs a root-fenced wrapper — the agent cannot do it~~** **Reasoning kept:** «the agent's PVE role was NOT widened.» «has NO storage-removal path (grep-assertable), is idempotent and refuses to repoint.» | SHIPPED + PROVEN-LIVE (agent v0.113.0 + host-install v1.22.0, 2026-07-29). `felhom-backup-target-apply` behind literal `FELHOM_BACKUPTARGET` sudoers alias; all five laws proven live as root on demo-hp, 0 stray storages. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| E-2b | **~~`NotifyStorageDisconnected`/`Reconnected` defined and called NOWHERE — a drive going absent emitted no event~~** | SHIPPED + PROVEN-LIVE (controller v0.184.1 + agent v0.112.0 + hub v0.81.0, 2026-07-29). Seam wired in `ReconcileDriveGates`; keying bug (guest path vs host MountPath) caught before deploy. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
| E-2c | **~~E-1 put the whole-guest backups on a drive `POST /disks/eject` would eject~~** **Reasoning kept:** «NOT a role reclassification — `RoleForStorage` untouched, because on both boxes that drive is ALSO the enrolled user-data drive» | SHIPPED + PROVEN-LIVE (agent v0.112.0, 2026-07-29). Eject + decommission refuse 409 on the backup-target mount; refused live on both boxes. | full text: `git show 9e2786c:documentation/backlog/OPEN-ITEMS.md` |
|
||||
|
||||
## 2026-09-17 — nothing decides by reading a Hungarian word (controller v0.251.0)
|
||||
|
||||
| Row | What | Closed | Full text |
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
> **Status 2026-10-03 (backlog triage): SHIPPED** — agent v0.84/v0.85 (CAMPAIGN-3). Kept here as history because
|
||||
> `CONTEXT.md`, `controller/network-storage-nas.md` and the controller's `CONTEXT.md` link to this path.
|
||||
|
||||
# FOLLOW-UP — NAS automount trigger does not survive a guest reboot (agent reassert gap)
|
||||
|
||||
**Opened:** 2026-07-11 · **Severity:** HIGH (data-safety-adjacent for media apps) · **Class:**
|
||||
|
||||
+266
-781
File diff suppressed because one or more lines are too long
@@ -1,27 +1,29 @@
|
||||
# documentation/backlog/
|
||||
|
||||
**`OPEN-ITEMS.md`** is the register of open work and the file to read first — it holds only what is
|
||||
open, one row per item, every row with a state and an owner. **`ROADMAP.md`** is the full history and
|
||||
reasoning behind the `R-n` IDs, including shipped and killed items; an ID is minted there, and a new
|
||||
instance of an existing item attaches to that ID rather than getting its own.
|
||||
open, one row per item, every row with a category, a severity, a state and an owner (the layout is at its
|
||||
top). **`CLOSED-ITEMS.md`** holds every finished row, compressed, with the commit that keeps its full text;
|
||||
a row moves there **in the same commit that closes it** (`scripts/closed_register_gate.py` RULE 3).
|
||||
**`ROADMAP.md`** holds intentions — features and spikes that do not exist yet — and **`ROADMAP-HISTORY.md`**
|
||||
the shipped and killed ones. An `R-n` id is minted once and never reused.
|
||||
|
||||
The rest of this folder: verified-LIVE findings with implementable fix plans that are **not yet
|
||||
implemented**. Preserved here
|
||||
(instead of on git branches) per the trunk-based, no-branches rule — the fix itself is implemented later
|
||||
**directly on `main`**, during a normal/supervised session.
|
||||
## The other files in this folder — verdict per file (2026-10-03 triage)
|
||||
|
||||
- **FIX-M18-NOTES.md** — dump re-validation runs every 5 min (perf). **FIXED** in controller v0.62.0 @
|
||||
`f8afe5c` (2026-06-14). (was on the deleted `felhom-controller` branch `fix/m18-dump-validation-cache`.)
|
||||
- **FIX-M19-NOTES.md** — `deriveStackName` misattribution edge (low-incidence correctness). **FIXED** in
|
||||
controller v0.62.0 @ `6bab68b` (2026-06-14). (was on the deleted branch `fix/m19-stackname-crossref`.)
|
||||
| File | Verdict |
|
||||
|---|---|
|
||||
| `SPEC-app-data-placement-2026-08-21.md` | **KEEP — still referenced by open rows** R-352 (placement still open) and R-368. |
|
||||
| `SPEC-r85-phase4-5-2026-07-26.md` | **KEEP as history — a spec of shipped work** (R-85, agent v0.104.0). `CONTEXT.md` and `architecture/00-capability-map.md` link here. |
|
||||
| `FOLLOWUP-nas-automount-guest-reboot-reassert.md` | **FINISHED, kept in place** — shipped in agent v0.84/v0.85 (CAMPAIGN-3). Not moved because `CONTEXT.md`, `controller/network-storage-nas.md` and `felhom-controller/CONTEXT.md` link to this path. |
|
||||
|
||||
- **FOLLOWUP-golden-default-controller-tag.md** — the golden bakes a stale controller (`:0.43.0` when
|
||||
queued; had rotted again to `:0.85.1` by resolution). **FIXED** in felhom-agent @ `ceca355`
|
||||
(2026-07-03): `build-golden.sh` v2.0.0 makes the controller tag a MANDATORY argument (a required arg
|
||||
cannot rot) and golden **0.98.3** was baked + clean-room-validated (bake → first-boot-current →
|
||||
self-manage → app deploy, on the drill VM — no supervised touch of live guests needed) + published +
|
||||
vouched. Evidence: `../audits/DRILL-golden-098-2026-07-03.md`.
|
||||
**Moved to `documentation/archive/` on 2026-10-03 (finished; nothing open refers to them):**
|
||||
|
||||
Related: the live-drive fixspec (`../audits/live-drive-fixspec-2026-06-14.md`) carries the **deferred
|
||||
supervised items** F9 (HDD provisioning/guest-attach), F20-BUG2 (durable_id scheme), F20-BUG3 (async
|
||||
mkfs) — to be implemented in the agent/golden supervised session.
|
||||
- `FIX-M18-NOTES.md` — dump re-validation every 5 min. **FIXED** in controller v0.62.0 @ `f8afe5c` (2026-06-14).
|
||||
- `FIX-M19-NOTES.md` — `deriveStackName` misattribution edge. **FIXED** in controller v0.62.0 @ `6bab68b` (2026-06-14).
|
||||
- `FOLLOWUP-golden-default-controller-tag.md` — the golden baked a stale controller. **FIXED** in felhom-agent
|
||||
@ `ceca355` (2026-07-03): `build-golden.sh` v2.0.0 makes the controller tag a mandatory argument. Evidence:
|
||||
`../audits/DRILL-golden-098-2026-07-03.md`.
|
||||
- `DIAGNOSIS-f9-storage-registration-gap-2026-06-14.md` — a read-only diagnosis of June 2026, superseded by the
|
||||
shipped F9 fixes (controller `CHANGELOG.md` cites it as history).
|
||||
|
||||
The archive also holds `OPEN-ITEMS-narratives-2026-10-03.md`: the campaign write-ups, rulings and ranking
|
||||
paragraphs that sat between the register's tables until the triage, word for word.
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
| R-7b | **Share backup EXECUTION** — put share data into the live tier-2 + offsite runs (the design fork reported by R-7 slice 1) | M | **SHIPPED (controller v0.145.0, 2026-07-18)** | **Viktor's ruling: Model B′ — a SIBLING shares source.** New, additive job/leg code reusing the proven primitives (tier-2 mirror seam, restic wrappers, soft-quota/enlargement gate, status recorders) while leaving **every per-app engine path byte-identical** — NOT a synthetic recovery unit (breaks on multi-drive shares, wraps 1 KB of JSON in dump machinery) and NOT engine-loop surgery. The B′ invariant is enforced by test in both tiers, red-proofed. Tier 2 → `RunSharesTier2` (legs grouped by SOURCE drive → `backups/secondary/_shares/<driveKey>/<share>`, payload at `_payload/`, layout marker LAST). Tier 3 → `runOffboxSharesLeg`: ONE extra `restic backup --tag felhom-offbox --tag _shares` placed after the app loop and BEFORE retention, so `forget --group-by host,tags` covers the new group with no flag change; a quota-blocked push degrades to the **manifest only, never to nothing**. Restore → „Megosztások" on `/backups/restore`: scratch, then a missing-only merge whose every destination is PREFIX-ASSERTED against live storage roots, definitions merged existing-wins, then `ReconcileSamba`, then the credential. The **payload** (`_shares-manifest.json` + a best-effort secret-bearing `passdb.tar`) is what makes DR return files + configuration + password rather than loose bytes. **Fold-in: samba joins the liveness set** — `EffectiveProtected` adds the CONTAINER `felhom-samba` exactly while sharing is on. **FULLY PROVEN-LIVE on demo (2026-07-18), all four legs.** (1) tier-2: real `/api/backup/tier2` trigger → `_shares` tree + marker + payload on the cross-drive target, mirrored file md5-identical, payload 0600 preserved. (2) offsite: Viktor's manual run 12:18:16Z → snapshot **`e0b9d723`** (tags `felhom-offbox,_shares`) with the payload dir + both share folders; a second run via the „Távoli mentés" button → **`4e2b15ec`**, containing `_shares-manifest.json` (418 B) AND `passdb.tar` (855 040 B), both 0600, share files with uid 1000 preserved. (3) restore round-trip: probe file + the `dokumentumok` DEFINITION deleted via the real endpoints, then „Megosztások" restore + place → `1 file(s), 1 definition(s) re-added, 1 kept, 0 refused, credential=true`; probe back md5-identical, the two pre-existing files NOT overwritten (missing-only proven on live data), definition back with its ORIGINAL flags and created_at, `smb.conf` re-rendered, `filmek` untouched. (4) liveness: samba stopped → `health_critical` pushed and hub-accepted (200) → self-healed. Remaining human leg: SMB positive auth with the real household password (never persisted by design). **Correction:** an earlier revision of this row and of the ship REPORT wrongly claimed the demo box had no offsite target — the verification read a guessed settings key (`offbox_target`) instead of the real one (`offbox`); root cause dissected in REPORT §7b. Findings: the reserved-name assumption was FALSE (`nbNameRe` accepted „_shares" as a share name — now refused); the alert/e-mail pipeline needed NO change and adds no new event type. Docs: `controller/sharing.md`; ship report `felhom-controller/REPORT.md`. |
|
||||
| R-8 | DLNA (**gate input now exists — R-6 spiked 2026-07-18: SSDP reaches LAN clients from host-net**): validate Jellyfin's built-in DLNA server first; only add minidlna to the catalog if Jellyfin-DLNA fails | S | idea (unblocked) | Don't add catalog weight before proving the cheap path. **R-6 confirmed the cheap path is physically viable — Jellyfin DLNA must run host-network (same multicast constraint as R-7)** |
|
||||
| R-9 | Uninstaller trio (from 07-15 Peti session): cluster-aware `felhom_guests` guard (node-local `pct list` deletes cluster-wide pveum objects); saferemove detection + time estimate + opt-in `--quick-remove` (never mutate `storage.cfg`); smarter `restore_storage` default for BYO clusters (shared storage, not local-lvm) | M | idea | Second item's rejected alternative (temp-disable-and-restore) stays rejected — crash window silently downgrades cluster wipe policy |
|
||||
| R-202 | **The orphan card promises recoverability unconditionally**, which after R-198 is true going forward and false for anything already orphaned | S | **gate hit 2026-08-04 — card untouched, sentence still live** | Blocked on knowing which escrow generation an orphaned repo belongs to (R-199/R-201). A single ACK boolean can say a retained recoverable blob EXISTS but not that one COVERS this repo; a conditional promise that can still be false is worse on that surface than a hedged one |
|
||||
| R-201 | **The wipe-and-recover drill** | L | **PREPARED, HALTED BEFORE THE WIPE (2026-08-04)** | Nothing irreversible done. Established live for the first time: a rebuilt box's off-site run REFUSES (the orphan card, not a silent fresh history — closes R-193 Q3), the orphan reset works move-aside-never-delete, and demo-hp's pre-rebuild key is permanently gone (superseded four hours before v0.93.0). Resume needs R-203 |
|
||||
| R-202 | **The orphan card promises recoverability unconditionally**, which after R-198 is true going forward and false for anything already orphaned | S | **CLOSED 2026-10-03 — the register row moved to `CLOSED-ITEMS.md` (triage)** — **gate hit 2026-08-04 — card untouched, sentence still live** | Blocked on knowing which escrow generation an orphaned repo belongs to (R-199/R-201). A single ACK boolean can say a retained recoverable blob EXISTS but not that one COVERS this repo; a conditional promise that can still be false is worse on that surface than a hedged one |
|
||||
| R-201 | **The wipe-and-recover drill** | L | **CLOSED 2026-10-03 — the register row moved to `CLOSED-ITEMS.md` (triage)** — **PREPARED, HALTED BEFORE THE WIPE (2026-08-04)** | Nothing irreversible done. Established live for the first time: a rebuilt box's off-site run REFUSES (the orphan card, not a silent fresh history — closes R-193 Q3), the orphan reset works move-aside-never-delete, and demo-hp's pre-rebuild key is permanently gone (superseded four hours before v0.93.0). Resume needs R-203 |
|
||||
| R-19 | Internet-outage customer-experience drill: pull WAN on demo, verify lan_resolver path, document what the customer actually sees/does | S | idea | Flips map row E "LAN access" IMPLEMENTED→PROVEN-LIVE |
|
||||
| R-34 | **Backup data lifecycle management.** An "inactive backups" section on „Távoli mentés": apps that have snapshots but no active backup — **disabled OR uninstalled** — listed with name / size / last snapshot / restorable, plus an explicit **double-confirmed per-app delete** via `restic forget --tag` + nightly prune. | M | idea | **RULING: the offsite toggle NEVER offers deletion — policy and destruction stay decoupled.** Turning backups off must never be a data-destroying act, and deletion must never hide behind a toggle. Origin: 2026-07-18 rehearsal. Pairs with R-32 (that one is the operator's view of dead bytes; this one is the customer's) |
|
||||
| R-27c | **Customer self-bind, slice 2 — console-passphrase bind.** Viktor's direction: bind using a passphrase shown on the box console, alongside (not instead of) the emailed capability link. | M | idea | **Security constraints from the session ruling, all load-bearing:** passphrase **issued at customer creation**; the global-lookup endpoint must be **spray-hardened** — per-appliance **and** per-IP caps, constant-time comparison, a **single generic failure** (no oracle), alerting on abuse; an **accent-free wordlist** (console keymaps are not Hungarian); the **web capability-link path is RETAINED**; **claim-by-email is RETAINED** as the delivery-channel proof. **Also under this item:** the self-bind email gains the **public universal-ISO download link + two-line instructions** (the DIY case). **Secret-bearing per-customer ISOs are ruled OUT.** Sibling of R-27b (second-box flow) — different axis, both build on the same `/bind/` page |
|
||||
@@ -167,5 +167,5 @@ Everything here is **remote-doable** — the N100 is packed, and none of it need
|
||||
## Absorbed / superseded notes in this folder
|
||||
|
||||
- `FOLLOWUP-nas-automount-guest-reboot-reassert.md` — **shipped** (agent v0.84/v0.85, CAMPAIGN-3); keep for history
|
||||
- `FOLLOWUP-golden-default-controller-tag.md` — verify against current golden flow; close or promote to an item
|
||||
- `FIX-M18-NOTES.md`, `FIX-M19-NOTES.md`, `DIAGNOSIS-f9-storage-registration-gap-2026-06-14.md` — historical diagnoses; superseded by shipped fixes
|
||||
- `FOLLOWUP-golden-default-controller-tag.md` — **FIXED** (agent `ceca355`); moved to `documentation/archive/` 2026-10-03
|
||||
- `FIX-M18-NOTES.md`, `FIX-M19-NOTES.md`, `DIAGNOSIS-f9-storage-registration-gap-2026-06-14.md` — historical diagnoses; superseded by shipped fixes; moved to `documentation/archive/` 2026-10-03
|
||||
@@ -1,3 +1,6 @@
|
||||
> **Status 2026-10-03 (backlog triage): SHIPPED — a spec of finished work, kept as history** (R-85: agent v0.104.0,
|
||||
> `ROADMAP-HISTORY.md`). `CONTEXT.md` and `architecture/00-capability-map.md` link to this path.
|
||||
|
||||
# SPEC — R-85 Phases 4 & 5: docs, then deploy
|
||||
|
||||
**Written** 2026-07-26 by Claude Code, at the operator's request, after Phases 0–3 shipped.
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
## The open register refuses a finished row (2026-10-03, backlog triage)
|
||||
|
||||
- `closed_register_gate.py` **RULE 3**: no row in `OPEN-ITEMS.md` may LEAD its `State` cell with a finished word
|
||||
(CLOSED, SHIPPED, FIXED, KILLED, DECIDED, ANSWERED, …). Why: `PROMPT-TEMPLATE.md` §N.7 said "move it" since
|
||||
2026-08-22 with no gate, and on 2026-10-03 the open register held 113 finished rows (a quarter of 824 KB). Seen
|
||||
red on the real register before the move (118 convicted) and by decoy.
|
||||
- New helper `register_table.py`: splits a row on pipes OUTSIDE backticks (measured: 437 of 442 rows then split to
|
||||
their declared width; the five that did not were real shape defects, repaired), and reads a cell BY THE COLUMN
|
||||
NAME in the table's own header, so a gate survives a column being added.
|
||||
- `instructions_gate.py`: reads the register's State by column name (the old positional scan would read the new
|
||||
title cell), and counts ids cited in the new narratives archive as history, not "a reference to nothing".
|
||||
- `test_gate_decoys.py`: two decoys — a finished row left in the open register (convicts; **without RULE 3 it
|
||||
passed — seen red**), and R-87's shape, a READY row whose prose says "closed" about another row (must pass).
|
||||
|
||||
## ISO build refuses a dirty or unpushed tree (2026-09-30, R-730)
|
||||
|
||||
- `iso/build-felhom-iso.sh`: a clean-tree gate right after argument parsing — any uncommitted or untracked change, or
|
||||
|
||||
@@ -2,11 +2,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""closed_register_gate.py — CLOSED-ITEMS.md holds closed work ONLY (R-405, 2026-08-31).
|
||||
|
||||
WHAT IT CONVICTS ON (a FAIL, exit 1), two rules and nothing else:
|
||||
WHAT IT CONVICTS ON (a FAIL, exit 1), three rules and nothing else:
|
||||
|
||||
RULE 1 — no row in `CLOSED-ITEMS.md` may carry an OPEN state word (READY, OPEN, BLOCKED,
|
||||
WATCHING, WAITING-ON-OPERATOR) as the LEADING VERDICT of its state cell.
|
||||
RULE 2 — no `R-` identifier may have a table row in BOTH `CLOSED-ITEMS.md` and `OPEN-ITEMS.md`.
|
||||
RULE 3 — (2026-10-03) no row in `OPEN-ITEMS.md` may carry a CLOSED-family word (CLOSED, SHIPPED,
|
||||
FIXED, KILLED, DECIDED, ANSWERED, SUPERSEDED, …; the list is `register_table.CLOSED_FAMILY`)
|
||||
as the LEADING VERDICT of its `State` cell. A finished row moves to `CLOSED-ITEMS.md` IN THE
|
||||
SAME COMMIT that finishes it.
|
||||
|
||||
WHY RULE 3, and the cost that bought it. RULES 1–2 guard the closed file and nothing guarded the open
|
||||
one. `PROMPT-TEMPLATE.md` §N.7 has said since 2026-08-22 "compress what this session closed … it moves
|
||||
to `CLOSED-ITEMS.md`", and it was followed only sometimes: on 2026-10-03 the open register held **113
|
||||
rows whose leading verdict was finished** (107 CLOSED, plus SHIPPED, FIXED, RULED, EXECUTED, ANSWERED,
|
||||
DECIDED) among 442 — a quarter of the file, at 824 KB. A rule without a gate is a wish. RULE 3 reads
|
||||
the `State` column BY NAME from the table's header (`register_table.rows`), never a fixed index, and
|
||||
it reads the LEADING verdict only — R-378's lesson applies here exactly as in RULE 1: the open row
|
||||
`READY — RE-RANKED UP (R-86 closed)` must pass, and does (decoy `closed-register/open-row-closed-word`).
|
||||
|
||||
WHY IT EXISTS, and the cost that bought it. The 2026-08-22 compression sweep (commit `ef6ac6f`,
|
||||
R-376..R-378) moved finished work out of `OPEN-ITEMS.md`. Its classifier matched a status word
|
||||
@@ -50,6 +63,9 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import register_table # noqa: E402 — the shared row reader (2026-10-03)
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
CLOSED = os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md")
|
||||
OPEN = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md")
|
||||
@@ -137,6 +153,19 @@ def main():
|
||||
for rid in sorted(closed_ids & set(open_ids), key=lambda r: (int(re.sub(r"\D", "", r)), r)):
|
||||
convicted.append((open_ids[rid], rid, "", "has a row in BOTH registers"))
|
||||
|
||||
# RULE 3 — a finished row left in the OPEN register (2026-10-03)
|
||||
finished_in_open = []
|
||||
for n, rid, columns, cells, _ in register_table.rows(OPEN):
|
||||
state = register_table.cell(columns, cells, "State")
|
||||
if state is None:
|
||||
continue # an unreadable row is register_shape_gate.py's conviction, not a pass here
|
||||
verdict = register_table.leading_verdict(state)
|
||||
if register_table.CLOSED_FAMILY.match(verdict):
|
||||
finished_in_open.append((n, rid, verdict))
|
||||
for n, rid, verdict in finished_in_open:
|
||||
convicted.append((n, rid, verdict, "OPEN-ITEMS.md row whose leading verdict is FINISHED — move "
|
||||
"it to CLOSED-ITEMS.md in the same commit that closes it"))
|
||||
|
||||
print("closed-register gate — %d closed rows with a readable state cell, %d open rows, "
|
||||
"%d convicted, %d warnings" % (checked, len(open_ids), len(convicted), len(warnings)))
|
||||
|
||||
@@ -145,7 +174,7 @@ def main():
|
||||
|
||||
if convicted:
|
||||
print()
|
||||
print("CONVICTED — CLOSED-ITEMS.md is for closed work only:")
|
||||
print("CONVICTED — CLOSED-ITEMS.md is for closed work only, OPEN-ITEMS.md for open work only:")
|
||||
for n, rid, verdict, why in convicted:
|
||||
print(" L%-5d %-7s %s%s" % (n, rid, why, (" — verdict %r" % verdict) if verdict else ""))
|
||||
print()
|
||||
|
||||
@@ -503,10 +503,29 @@ def register_state(workspace_root):
|
||||
closed_text = fh.read()
|
||||
|
||||
state = {}
|
||||
# 2026-10-03: the register declares its columns in each table's header, and since the triage of
|
||||
# that day its rows read `| ID | Category | Sev | What | State | … |` — the State is no longer
|
||||
# index 2, and the first bold cell is now the TITLE, whose words ("shipped", "closed") describe
|
||||
# the finding, not its state. `register_table` reads the State column BY NAME; the positional
|
||||
# scan below is kept only for a row in a table without a header (none today).
|
||||
by_name = {}
|
||||
try:
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import register_table
|
||||
for _n, rid, columns, cells, _line in register_table.rows(reg):
|
||||
st = register_table.cell(columns, cells, "State")
|
||||
if st is not None:
|
||||
by_name[rid] = ("closed" if register_table.CLOSED_FAMILY.match(
|
||||
register_table.leading_verdict(st)) else "open")
|
||||
except ImportError:
|
||||
by_name = {}
|
||||
for line in reg_text.split("\n"):
|
||||
m = re.match(r"\|\s*\*\*(R-\d+[a-z]?)\*\*\s*\|", line)
|
||||
if not m:
|
||||
continue
|
||||
if m.group(1) in by_name:
|
||||
state[m.group(1)] = by_name[m.group(1)]
|
||||
continue
|
||||
cells = [c.strip() for c in line.rstrip().strip("|").split("|")]
|
||||
# The State column is index 2 in BOTH table shapes here (`ID|What|State` and
|
||||
# `ID|What|State|Blocked on|Next action|Owner`). Scan from there rather than taking the
|
||||
@@ -536,6 +555,14 @@ def register_state(workspace_root):
|
||||
if m and m.group(1) not in state:
|
||||
state[m.group(1)] = "closed"
|
||||
|
||||
# 2026-10-03: the register's narrative sections (campaign write-ups, rulings, ranking
|
||||
# paragraphs) moved word for word to this archive. An id cited only there — R-153, named in the
|
||||
# 2026-08-02 intake paragraph and in felhom.eu/CLAUDE.md — is history, not "a reference to nothing".
|
||||
narr = os.path.join(workspace_root, "felhom.eu", "documentation", "archive",
|
||||
"OPEN-ITEMS-narratives-2026-10-03.md")
|
||||
if os.path.exists(narr):
|
||||
with io_open(narr) as fh:
|
||||
hist_text += "\n" + fh.read()
|
||||
mentioned = set(re.findall(r"\bR-\d+[a-z]?\b", reg_text)) | set(
|
||||
re.findall(r"\bR-\d+[a-z]?\b", hist_text)
|
||||
) | set(re.findall(r"\bR-\d+[a-z]?\b", closed_text))
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""register_table.py — read OPEN-ITEMS.md's rows the way a reader sees them (2026-10-03).
|
||||
|
||||
A helper, not a gate: `register_shape_gate.py` and `closed_register_gate.py` both import it, so the
|
||||
two gates cannot disagree about where a row's cells are.
|
||||
|
||||
WHY A HELPER EXISTS. Both gates used to split a row on every `|`. `register_shape_gate.py`'s own
|
||||
docstring records why that cannot count cells: register prose carries literal pipes. Measured on
|
||||
2026-10-03 against the register at `9305288` (442 rows): **every literal pipe but five sat inside
|
||||
inline code** (`--format '{{.Names}}|{{.Image}}'`, `owner: CC | …` inside backticks). A split that
|
||||
skips pipes inside backticks and escaped `\\|` gave the declared column count for 437 rows; the five
|
||||
it did not were genuine shape defects (a stray extra cell, two rows written five columns wide), and
|
||||
they were repaired in the same commit. So the cell count CAN be computed after all — by reading
|
||||
code spans the way a Markdown renderer does.
|
||||
|
||||
WHAT IT CANNOT SEE: a pipe in plain prose outside backticks still makes an extra cell. That is a
|
||||
real rendering defect (the table shows an extra column), so a gate convicting it is right.
|
||||
|
||||
THE COLUMN COMES FROM THE HEADER, never from a fixed index. Each register table declares its
|
||||
columns (`| ID | Category | Sev | What | State | … |`); `rows()` remembers the last header it saw
|
||||
and reports each row's cells by NAME. A gate that hard-codes "the state is cell 3" is wrong the day
|
||||
someone adds a column — the closed gate's original `cells[3]` assumption is exactly that.
|
||||
"""
|
||||
import io
|
||||
import re
|
||||
|
||||
ROW = re.compile(r"^\| \*\*(R-\d+[a-z]?)\*\* \|")
|
||||
HEADER = re.compile(r"^\|\s*ID\s*\|")
|
||||
# A verdict ends at the first separator. `READY — RE-RANKED UP (R-86 closed)` leads with READY.
|
||||
SEPARATOR = re.compile(u"[—–(:,.;]|\\s-\\s")
|
||||
|
||||
# THE TWO VOCABULARIES. A leading verdict is the first word of the state cell (emphasis stripped).
|
||||
# OPEN_STATES is the whole list a register row may lead with; anything else is either finished
|
||||
# (CLOSED_FAMILY — the row belongs in CLOSED-ITEMS.md) or unknown (a word nobody defined).
|
||||
OPEN_STATES = ("READY", "OPEN", "BLOCKED", "WATCHING", "WAITING-ON-OPERATOR", "NARROWED",
|
||||
"DEFERRED", "VERIFY")
|
||||
CLOSED_FAMILY = re.compile(r"^(CLOSED|SHIPPED|FIXED|KILLED|DONE|SUPERSEDED|OBSOLETE|WITHDRAWN|"
|
||||
r"MERGED|DISCHARGED|RULED|MOVED|BANKED|PROVEN-LIVE|EXECUTED|ANSWERED|"
|
||||
r"FOLDED|DECIDED|RESOLVED|DOCUMENTED|PASSED|COMPLETE|COMPLETED|✅)",
|
||||
re.I)
|
||||
|
||||
|
||||
def split_cells(line):
|
||||
"""The cells of a markdown table row, skipping pipes inside `code` and escaped `\\|`.
|
||||
|
||||
Returns the inner cells only (the empty strings outside the outer pipes are dropped), or None
|
||||
when the row does not end with `|` — such a row has lost its last cell.
|
||||
"""
|
||||
s = line.rstrip()
|
||||
if not s.endswith("|"):
|
||||
return None
|
||||
out, cur, code, i = [], [], False, 0
|
||||
while i < len(s):
|
||||
ch = s[i]
|
||||
if ch == "\\" and i + 1 < len(s) and s[i + 1] == "|":
|
||||
cur.append("\\|")
|
||||
i += 2
|
||||
continue
|
||||
if ch == "`":
|
||||
code = not code
|
||||
if ch == "|" and not code:
|
||||
out.append("".join(cur))
|
||||
cur = []
|
||||
else:
|
||||
cur.append(ch)
|
||||
i += 1
|
||||
out.append("".join(cur))
|
||||
return [c.strip() for c in out[1:-1]]
|
||||
|
||||
|
||||
def leading_verdict(cell):
|
||||
"""The verdict word(s) before the first separator, with Markdown emphasis stripped."""
|
||||
text = cell.replace("*", "").replace("~", "").replace("`", "").strip()
|
||||
return SEPARATOR.split(text)[0].strip()
|
||||
|
||||
|
||||
def first_word(cell):
|
||||
v = leading_verdict(cell)
|
||||
return v.split()[0].upper() if v.split() else ""
|
||||
|
||||
|
||||
def rows(path):
|
||||
"""Yield (line_no, id, columns, cells, line) for every register row.
|
||||
|
||||
`columns` is the header of the table the row sits in (a list of names, or None when no header
|
||||
has been seen yet); `cells` is split_cells(line) (None when the row lost its last pipe).
|
||||
"""
|
||||
columns = None
|
||||
for n, line in enumerate(io.open(path, encoding="utf-8"), 1):
|
||||
line = line.rstrip("\n")
|
||||
if HEADER.match(line):
|
||||
columns = split_cells(line)
|
||||
continue
|
||||
m = ROW.match(line)
|
||||
if not m:
|
||||
continue
|
||||
yield n, m.group(1), columns, split_cells(line), line
|
||||
|
||||
|
||||
def cell(columns, cells, name):
|
||||
"""The named cell of a row, or None when the table has no such column or the row is short."""
|
||||
if not columns or cells is None or name not in columns:
|
||||
return None
|
||||
i = columns.index(name)
|
||||
return cells[i] if i < len(cells) else None
|
||||
@@ -43,7 +43,9 @@ COVERS = {
|
||||
"observations": "R-419: prose SAYING it carries no marker, plus both genuine markers",
|
||||
"reuse-refs": "a cited .go path that does not exist; the .md hole is asserted as R-422",
|
||||
"golden-currency": "R-410: an empty directory with a perfect name, checked by what it COUNTED",
|
||||
"closed-register": "a verdict cell reading open, and a row with no state cell at all",
|
||||
"closed-register": ("a verdict cell reading open, a row with no state cell at all, and (RULE 3, "
|
||||
"2026-10-03) a FINISHED row left in OPEN-ITEMS.md — with R-87's shape, a READY "
|
||||
"row whose prose says 'closed' about another row, asserted to PASS"),
|
||||
"register-shape": ("R-627: a row whose state cell was EATEN (no trailing pipe), a DUPLICATED "
|
||||
"id, and a blank line splitting the table — each the exact shape the "
|
||||
"2026-09-21 append produced; plus the genuine article, which must pass"),
|
||||
@@ -210,6 +212,32 @@ decoy("closed-register/unreadable-row", "closed_register_gate.py",
|
||||
append_to(os.path.join(ROOT, "documentation", "backlog", "CLOSED-ITEMS.md"),
|
||||
u"\n| **R-905** | A row with no state cell at all. |\n"))
|
||||
|
||||
# --- closed-register RULE 3 (2026-10-03): a FINISHED row left in the OPEN register ---------------
|
||||
# On 2026-10-03 the open register held 113 rows whose leading verdict was finished — a quarter of the
|
||||
# file. The decoy is that exact shape: a row a session closed in place and never moved. The genuine
|
||||
# article beside it is R-87's shape from R-378: a READY row whose prose says "closed" about ANOTHER
|
||||
# row — it must pass, or the gate repeats the 2026-08-22 sweep's mistake one file over.
|
||||
# The row is built from the register's CURRENT header, so the decoy follows a column change rather
|
||||
# than silently testing a shape the file no longer has.
|
||||
_OPEN_REG = os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md")
|
||||
|
||||
|
||||
def _open_row(rid, state):
|
||||
cols = None
|
||||
for line in io.open(_OPEN_REG, encoding="utf-8"):
|
||||
if line.startswith("| ID |"):
|
||||
cols = [c.strip() for c in line.strip().strip("|").split("|")]
|
||||
fill = {"ID": "**%s**" % rid, "Category": "Process & tooling", "Sev": "P4",
|
||||
"What": "**A decoy finding.**", "State": state, "Owner": "CC"}
|
||||
return u"\n" + u"| " + u" | ".join(fill.get(c, u"—") for c in cols) + u" |\n"
|
||||
|
||||
|
||||
decoy("closed-register/finished-row-in-open", "closed_register_gate.py",
|
||||
append_to(_OPEN_REG, _open_row("R-906", "**CLOSED 2026-10-03 — shipped in v9.9.9**")))
|
||||
decoy("closed-register/open-row-closed-word (BY DESIGN)", "closed_register_gate.py",
|
||||
append_to(_OPEN_REG, _open_row("R-907", "**READY — RE-RANKED UP 2026-08-03 (R-86 closed)**")),
|
||||
expect="accept")
|
||||
|
||||
# --- hub-copy: an ENGLISH retrieval promise, planted in the bundle (R-558) -----------------------
|
||||
#
|
||||
# TWO SHAPES AT ONCE, and the second is why this decoy exists at all.
|
||||
|
||||
Reference in New Issue
Block a user