fa9362f36f
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
69 lines
5.3 KiB
Markdown
69 lines
5.3 KiB
Markdown
# REPORT — controller v0.105.0: fork-4 offsite password custody (hand-off + atomicity + DR inject + coord)
|
|
|
|
**Date:** 2026-07-09 · **Class:** implementation (controller). **Baseline:** `main` @ `bde43f3` (v0.104.0)
|
|
→ `0b09a79` (v0.105.0). Pairs with agent v0.77.0. Design validated by the custody spike (`febdc56`).
|
|
|
|
## What shipped
|
|
- **Hand-off** — `agentapi.Client.StageEscrowSecret` pushes the repo password to the agent's
|
|
`POST /escrow/stage-secret` (authenticated pinned channel, value never logged). Enable flow
|
|
(`offboxConfigHandler`) reads the 0600 password via `Manager.PushOffboxPasswordForEscrow` (the handler
|
|
never sees the value) and sets `EscrowState="pending"`; a stage-push failure is surfaced, not marked escrowed.
|
|
- **Atomicity gate** — `Manager.OffboxRunnable()`/`offboxEscrowed()`; `RunOffboxBackup` (→ the daily
|
|
scheduler and the run handler) **refuses to run until `EscrowState=="escrowed"`** — no un-recoverable
|
|
offsite ciphertext can exist. `OffboxConfigured()` unchanged (config/UI still work). New additive
|
|
`settings.OffboxTarget.EscrowState` (`""|"pending"|"escrowed"`, preserved across config edits).
|
|
- **Confirm + DR inject** — `POST /backup/offbox/confirm-escrow` → escrowed (operator, after the ceremony);
|
|
`POST /backup/offbox/inject-password` → `Manager.InjectOffboxPassword` pre-places a recovered 64-hex
|
|
password 0600 (tmp+rename), refusing to clobber without `force`; a subsequent `WriteOffboxSecrets` uses it.
|
|
`/backups` shows a pending-escrow notice + "Letét megerősítése" button.
|
|
- **DR recipe** — `DRRecipeAppHalf.OffsiteRestic *DRResticCoord {host,user,port,repo_path}` (coordinates
|
|
ONLY — password escrowed, SFTP key regenerable), populated from `Manager.OffboxCoord()`.
|
|
|
|
## Files changed (commit `0b09a79`)
|
|
`internal/agentapi/client.go`, `internal/settings/settings.go`, `internal/backup/offbox.go`,
|
|
`internal/backup/offbox_test.go`, `internal/web/offbox_handlers.go`, `internal/web/offbox_escrow_test.go`
|
|
(new), `internal/web/server.go`, `internal/web/templates/backups.html`, `internal/report/dr_recipe.go`,
|
|
`internal/report/dr_recipe_test.go`, `internal/report/builder.go`, `CHANGELOG.md`, `CONTEXT.md`,
|
|
`controller/README.md`.
|
|
|
|
## Tests + companion red-proofs
|
|
Green gate `go build ./... && go vet ./... && go test ./...` — **ALL-GREEN**. UI gates (`template_id_gate`,
|
|
`emoji_gate`) pass. Offbox suite extended (harness defaults to `EscrowState="escrowed"` so behavioral run
|
|
tests exercise the run path). New:
|
|
- `TestOffbox_PendingEscrowBlocksRun` (A) — a toggled app with a present unit is NOT backed up while pending
|
|
(0 `restic backup` calls); `OffboxConfigured` true, `OffboxRunnable` false.
|
|
**Companion red-proof (run + reverted):** disabling the gate (`if false && …`) → the run backed up while
|
|
pending → the test FAILED (`NO offsite backup may run while escrow is pending, got […/backups/primary/app1]`).
|
|
Proves the atomicity gate is load-bearing.
|
|
- `TestOffbox_ConfirmEscrowEnablesRun` (B) — pending blocks; after `EscrowState="escrowed"` the run proceeds (1 backup).
|
|
- `TestOffbox_InjectPasswordPrePlaced` (C) — an injected recovered password survives `WriteOffboxSecrets`;
|
|
refuse-clobber without force; force overwrites; invalid rejected.
|
|
**Companion `TestOffbox_NoInjectGeneratesDifferentPassword`:** without inject, `WriteOffboxSecrets`
|
|
generates a DIFFERENT 64-hex password (which could never open the existing repo) — proves inject is load-bearing.
|
|
- `TestOffbox_CoordForDR` — `OffboxCoord` returns the coords; ok=false when unconfigured.
|
|
- `report.TestDRResticCoord_NoSecrets` (E) — the serialized `OffsiteRestic` coord clears the secret-name
|
|
regex and emits host/user/port/repo_path with no password/key.
|
|
- web: `TestOffboxWeb_RunGatedUntilConfirm` (run refused with the escrow-wait flash while pending; confirm →
|
|
escrowed + runnable), `TestOffboxWeb_InjectPassword` (endpoint pre-places 0600; invalid → error flash).
|
|
|
|
## Deploy verification (guest 9201)
|
|
Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.105.0` (digest `sha256:413d46a9…c0`), deployed
|
|
via the bootstrap mechanism. `docker ps`: `:0.105.0 Up (healthy)`. Startup clean (settings loaded, scheduler
|
|
registered, logscanner errors=0). **Atomicity gate live:** the standing demo offbox has `escrow_state` unset
|
|
→ it is now correctly **gated** (`/backups` renders the "…letétbe helyezésére vár" notice + "Letét
|
|
megerősítése" button; scheduled/manual runs skip). Per the STOP boundary, NO ceremony/confirm/gated run was
|
|
performed on the demo.
|
|
|
|
## NOT yet live-validated
|
|
The supervised escrow ceremony end-to-end (enable → stage → `--selftest=escrow-create` with a real R →
|
|
confirm → gated run against the box) is the **operator-run** follow-up — pending. Unit tests are this task's proof.
|
|
|
|
## Observations (noticed, not acted on)
|
|
- The standing demo offbox (configured pre-fork-4) is now gated `pending` — this is the intended fail-safe,
|
|
but it means the demo's daily offsite run will skip until an operator runs the ceremony + confirm. Flagged
|
|
for the supervised follow-up.
|
|
- `confirm-escrow` currently trusts the operator (pilot). The provisioning task should replace it with a
|
|
hub-verified auto-confirm (the operator-forgets/operator-lies footgun) — noted in code + CONTEXT.
|
|
- Pre-existing Tier-2 `TestBackupTier2Restore_DoubleClickRefused` flake (documented in v0.104.0 REPORT) is
|
|
unchanged/untouched.
|