From d3e17e9b2e194d31b9f48fbf4c6a13db74b84a7a Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 8 Oct 2026 14:49:10 +0200 Subject: [PATCH] CHANGELOG/README/REUSE/REPORT: the decision sheet D1, D3, D4, D8 (unreleased) Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ REPORT.md | 35 +++++++++++++++-------------------- REUSE.md | 3 +++ controller/README.md | 28 ++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b3acb..7a735fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## Unreleased (2026-10-08, evening) — the operator's decision sheet: operator actions (D1), sign-ins survive a restart (D4), health issues in the household's language (D3), a failed off-site restore holds the app (D8) — ships with tomorrow's release + +**MinAgent: 0.131.0** (unchanged — no agent change). Operator rulings 2026-10-08 14:16, `09` §3 decisions 185–194. +New wire fields (additive, the hub of the same day carries their twins): reply `operator_actions`, report +`operator_action_results`. **This controller needs the hub of the same day for D1** (an older hub sends no actions; +nothing breaks). + +- **D1 — operator actions in the report reply (R-314, R-279; R-177 folded).** `internal/report/opactions.go`: a CLOSED + switch in the running process — `offsite_backup_now` (the household's four checks, then the same run), + `abandon_stop`, `abandon_extend` (1–30 days, never earlier than the current date, refused once the deletion is the + hub's), `run_job` with `fill-watch`, `offsite-integrity`, `offsite-proof`, `disk-health-check` (new + `scheduler.RunNow(name)`, refuses unknown or running jobs; `scheduler.OnDemand(ctx)`). Anything else answers + `refused` and calls nothing. Once per id (in memory; every action is safe to repeat after a restart). The result + rides the next report. `TestOpActions_ClosedList` fails if an action is added. **Fixed on the way (small, no row):** + `ExtendAbandon` could move a deletion EARLIER and could edit the box date during the hub phase (both refused now); + `StopAbandon` reported success while the hub's deletion stayed pending (error now); the reply read limit 4 KiB → + 64 KiB (a larger reply dropped every field). +- **D4 — dashboard sign-ins survive the controller's own restart (R-35, option C).** `internal/web/session_store.go`: + `/dashboard-sessions.json` (0600, tmp+fsync+rename) holds sha256(cookie) → expiry + CSRF token, never the + cookie. Logout and a password change end the session on disk too. **Security review fixes:** the file carries a + fingerprint of the password hash in force, so rows written under another password are dropped at load (a password + change revokes even if its save failed); a revoking save that fails removes the file. Tests `TestR35_*` (8). + Small fix on the way: `TestR650_NoBareDockerExec` skipped a non-`.go` file that vanished mid-walk (a race with a + parallel stacks test). +- **D3, dashboard half — the last six health producers carry their sentence (R-79 option A).** Docker unreachable, + protected container down, storage unavailable / not separate / usage high / almost full: a `MsgRef` each; the hub + wire text is unchanged byte for byte (wire golden green). Six keys `health.*` (hu+en); the four storage Hungarian + values equal the frozen literals. Tests `TestR79_RemainingProducersCarryTheirDashboardSentence`, + `TestR79_HealthBannersFollowTheHousehold`. +- **D8, first half — a failed off-site replay that follows a moved definition or a replaced volume HOLDS the app + (R-893 option C).** `offbox_reconstitute.go`: the live definition is kept before the snapshot's is written and + written back on failure; the stack stays stopped with a new hold `restore_mixed` (operator-only to clear); the + household reads that it needs our help (keys `err.backup.db_restore_failed_held_mixed`, `note.reconstitute.held_mixed`); + the operator is told through the existing `backup_run_failures` event (leg `restore-hold-mixed`, no new hub type). + No version change and no volume replaced → today's behaviour (`TestR379_ScenarioA` green). Tests `TestR893_*`. + Known limit: placed files are not undone (the second half, „put back exactly as it was", is its own row). + ## Unreleased (2026-10-08, afternoon) — three dashboard layout fixes: the Apps card header (R-909), the launcher on a phone (R-907), the empty „+5 more warnings" (R-906) — ships with tomorrow's release **MinAgent: 0.131.0** (unchanged — nothing on the wire, nothing the agent sees). Hungarian text: not one string diff --git a/REPORT.md b/REPORT.md index cdd292e..b397397 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,26 +1,21 @@ -# REPORT — controller, 2026-10-08 (day + afternoon): R-899, R-304 (+ the operator mail) on main, unreleased +# REPORT — controller, 2026-10-08 (evening): the operator's decision sheet D1, D3, D4, D8 on main, unreleased -**No release, no delivery today** (kernel night 8→9; `09` §3 decision 178). Both ship with tomorrow's release. +**No release, no delivery today** (kernel night 8→9). Everything ships with tomorrow's release, together with the hub of +the same day (D1's wire fields). MinAgent unchanged (0.131.0). Rulings: `09` §3 decisions 185–194. -| Item | Commit | Tests | Red-proof | +| Part | What | Tests | Red-proof seen | |---|---|---|---| -| R-899 — a daytime „Mentés most" press never cancels the night's whole-guest backup (decision 177) | `6d07ca2` | `TestR899_*` (6, quiesce), `TestR899_PressCarriesTriggerManual` (agentapi) | rule off → the 2026-10-07 replay started nothing on night 2 (`started=[local local]`), plus 2 more failures; without the owed-tier valve guard an owed night ran outside the window (`started=[local local]` at 15:00) | -| R-304 — „we do not know whether your code is wrong" when earlier packages were not all checked (agent 424) | `75b3b39` | `TestR304_OlderUnchecked_IsNotAWrongCode` (hu + en), `TestR304_Classify424` | without the handler case the page fell to the safe default „A művelet nem fejeződött be…" in both languages | +| D1 (R-314, R-279) | Operator actions in the report reply — a closed list of four; anything else `refused` | `TestOpActions_*`, `TestOpActions_ClosedList`, `scheduler` `RunNow` tests | stop not applied in the running manager; same id ran 3×; unknown action called jobs; an added action fails the closed-list test | +| D4 (R-35) | Sign-ins survive a restart; only sha256(cookie) on disk; password fingerprint; a failed revoking save removes the file | `TestR35_*` (8) | restart lost the cookie; file held the cookie; password change / failed logout save revived the session | +| D3 (R-79, dashboard) | Six health producers carry a bundle key; wire text unchanged | `TestR79_*` | all 6 keys empty; one key removed → English household saw Hungarian | +| D8 (R-893, first half) | Failed off-site replay after a moved definition / replaced volume → live definition back, app held (`restore_mixed`), honest sentence, operator line | `TestR893_*` (2) | the app was started on a mixed state; definition not written back | -| R-304 option C — operator mail when a code opens / may open an older package, once a day (decision 183) | `a40729a` | `TestR304_OlderPackageMail_*` | without the calls `events = []`; without the day check `sent 2 events, want still 1` | -| R-298 side fix — no eject/format button on a backup-target drive; R-717 two stale comments | `a40729a` | parity fixtures `storage_full`/`storage_empty` regenerated (only the new JS lines differ) | — (JS; not verified in a browser) | +Small fixes without a row: `ExtendAbandon` could move a deletion earlier or edit the box date in the hub phase; +`StopAbandon` reported success while the hub's deletion stayed pending; the reply read limit 4 KiB → 64 KiB; +`TestR650_NoBareDockerExec` raced a vanishing temp file. -**Needs the hub of the same day:** `recovery_older_package` is allowlisted only on hub main (`5beedcce`); against an older -hub the push is refused (400) and only logged. A contract test caught a variable severity in the first draft -(`TestR329_EveryEmittedSeverityIsInTheHubVocabulary`) — fixed to a literal. +Security review of the session's commits (automatic): two findings on D4 (a failed save could revive an ended +session) — fixed and red-proved (`TestR35_PasswordChangeRevokesEvenIfSaveFailed`, `TestR35_FailedLogoutSaveRemovesFile`). -**Design:** `07` §6.1 (R-899 paragraph), `09` §3 decisions 177–178. The ledger is -`/whole-guest-ledger.json`, successes only, read only to decide due-ness. **MinAgent unchanged (0.131.0).** - -**Gates:** `go build/vet/test ./...` rc 0; `controller_gates.py --fast` rc 0. **CI:** job 1528 (`6d07ca2`) success; job -1531 (`75b3b39`) success; job 1544 (`a40729a`) success. - -**Seen, filed (not fixed):** the recovery screen's other 14 messages are Hungarian-only Go literals and show in Hungarian -on an English page — appended to R-516. - -**Not done:** no live validation (no delivery today); the first night after a press is read back after tomorrow's release. +Gates: `go build/vet/test ./...` green; `controller_gates.py --fast` green. Machines: none touched. +Live proofs (tomorrow, scratch 9202, after the releases): see `felhom.eu/REPORT-day3-2026-10-08.md`. diff --git a/REUSE.md b/REUSE.md index 833bc2b..4f3727a 100644 --- a/REUSE.md +++ b/REUSE.md @@ -269,6 +269,9 @@ | Symbol | File | Short signature | Use for | Gotchas | |---|---|---|---|---| +| `Scheduler.RunNow` / `scheduler.OnDemand` | controller/internal/scheduler/scheduler.go | `RunNow(name) error`; `OnDemand(ctx) bool` | Run a registered job once now (operator action `run_job`, decision 185) | Refuses unknown and already-running jobs; a job that checks due-ness reads `OnDemand(ctx)` to skip that check on demand | +| `report.OperatorActions` | controller/internal/report/opactions.go | `Reconcile(reply)` → results in the next report | Hub-requested actions in the report reply — the `selftail.go` pattern plus results | The action table is CLOSED and pinned by `TestOpActions_ClosedList`; once per id in memory; every entry must be safe to repeat | +| `web.sessionFingerprint` / `loadSessions` / `saveSessionsLocked` / `saveSessionsRevokingLocked` | controller/internal/web/session_store.go | dashboard sessions on disk (R-35) | Any session lookup | The map is keyed by sha256(cookie), NEVER the cookie — hash with `sessionFingerprint` first; a path that ENDS sessions uses the revoking save | | `Scheduler.Every` / `Daily` | controller/internal/scheduler/scheduler.go | `(name, interval/"HH:MM", fn)` | ALL background jobs | Daily is Europe/Budapest, DST-safe (`nextDailyRun` avoids Add(24h)); register in main.go block (§5) | | `Scheduler.UpdateDaily` | controller/internal/scheduler/scheduler.go | `(name, "HH:MM") bool` | Retime a daily job at runtime (no restart) | Per-job buffered `resched` chan + select case in `runDailyJob`; false (WARN) on invalid time / unknown-or-non-daily name; read `Schedule` under the mutex in the loop | | `backupwindow.*` (LegTimes / GateWindow / EffectiveWindow / ParseHHMM / FmtHHMM / Valid) | controller/internal/backupwindow/backupwindow.go | pure `string`↔`int` | Backup-window arithmetic (v0.168.0) | Offsets (W+60m/W+105m, gate W+2h..W+6h) are CONSTANTS — derived, never stored; wrap-safe modulo 1440; `EffectiveWindow(settings, yaml)` = settings>yaml>"02:30" | diff --git a/controller/README.md b/controller/README.md index 8b11d9c..f29a6f9 100644 --- a/controller/README.md +++ b/controller/README.md @@ -913,6 +913,13 @@ Each app can define rich metadata in `.felhom.yml`: `--clear-restore-hold `, **which requires a controller restart**. `--single-transaction` on the Postgres import is a belt only; MariaDB DDL is not transactional, which is why the rollback is the fix. + - **Off-site restore of one app: a failed replay after a moved definition or a replaced volume HOLDS the app + (R-893 option C, `09` §3 decision 192).** The rollback puts back the database rows only; the snapshot's files, + volumes and older definition would stay. So when the snapshot's definition was written or a named volume was + replaced, the live definition is written back, the app is held stopped (`restore_mixed`, operator-cleared like + a restore hold), the household reads that it needs our help, and the operator gets a `backup_run_failures` + line (leg `restore-hold-mixed`). Otherwise the ladder above is unchanged. „Put back exactly as it was" is the + next slice. - **Where an off-site restore puts the data (v0.219.0, R-356).** `ReconstituteFromOffsite` and `PlaceOffsiteRestore` resolve the destination with `Manager.GetAppDrivePath` — **the same resolver `CaptureRecoveryUnit` wrote the snapshot with**: the app's `HDD_PATH` if it declares @@ -2612,6 +2619,10 @@ Continuously monitors registered storage paths for disconnection/reconnection (p | Protected containers | — | not running | | Storage paths | not a mount point (data on SSD), drive disconnected | path inaccessible, disk >= 95% | +Every issue and warning carries a bundle key beside its frozen wire text (R-516 item 10; the last six producers — +Docker, protected containers, the four storage lines — since R-79, `09` §3 decision 187), so the dashboard banner shows +it in the household's language while the hub report keeps its bytes. + Backup destination validation (`CheckBackupDestination`) has tiered checks: - Path doesn't exist → critical/blocked - Not writable → critical/blocked @@ -3231,8 +3242,25 @@ Five sections: --- +#### Sign-ins survive a restart (R-35, `09` §3 decision 188) + +Dashboard sessions are kept in `/dashboard-sessions.json` (0600, atomic write): only sha256(cookie) → expiry + +CSRF token, never the cookie, plus a fingerprint of the password hash in force. A settings push, an update or a crash +restart no longer signs the household out. Expiry is unchanged (7 days). Logout and a password change end the session +on disk too; rows written under another password are dropped at load; a revoking save that fails removes the file. +Pinned by `internal/web/session_store_test.go` (`TestR35_*`). + ### 9. Central Hub Reporting +#### Operator actions (`internal/report/opactions.go`, `09` §3 decision 185) + +The hub may ask the running controller, in the report reply (`operator_actions`), for one of a CLOSED list: +`offsite_backup_now`, `abandon_stop`, `abandon_extend` (1–30 days; never earlier than the current date; refused once +the deletion is the hub's), `run_job` (`fill-watch`, `offsite-integrity`, `offsite-proof`, `disk-health-check`). +Anything else is answered `refused` and calls nothing. Each id runs once per process; the result rides the next report +(`operator_action_results: [{id, outcome, message}]`, outcome `done` / `refused` / `failed`). No action deletes data, +starts a countdown or shortens one (`TestOpActions_ClosedList`). The box's log records every action it receives. + #### Report Push (`internal/report/`) Periodic JSON push (default every 15 min) to the central felhom-hub service: