CHANGELOG/README/REUSE/REPORT: the decision sheet D1, D3, D4, D8 (unreleased)
gates / gates (push) Successful in 1m0s
gates / gates (push) Successful in 1m0s
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:
@@ -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`:
|
||||
`<data>/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
|
||||
|
||||
@@ -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
|
||||
`<data>/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`.
|
||||
|
||||
@@ -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" |
|
||||
|
||||
@@ -913,6 +913,13 @@ Each app can define rich metadata in `.felhom.yml`:
|
||||
`--clear-restore-hold <app>`, **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 `<data>/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:
|
||||
|
||||
Reference in New Issue
Block a user