This commit is contained in:
+56
@@ -7,6 +7,62 @@
|
||||
>
|
||||
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
|
||||
|
||||
Last updated: 2026-08-02 (v0.189.0 — R-166 / D-b: the box stops guessing what the customer wanted)
|
||||
|
||||
> **2026-08-02 — v0.189.0 (R-166, operator decision D-b).** When an app was not running the box had
|
||||
> to work out *why*, and it did so **by counting containers**: zero meant "the customer stopped it",
|
||||
> some meant "something broke". A **power cut mid-compose** and an **interrupted deploy** also leave
|
||||
> zero containers, so both were read as deliberate stops and stranded **silently** (R-157 mechanism
|
||||
> B) — and a backup that stopped an app and died left it stopped with **nothing on disk** recording
|
||||
> that it was owed a restart. The settling fact — what the customer asked for — **was written down
|
||||
> nowhere**: `app.yaml` recorded *installed*, never *meant to be running*.
|
||||
>
|
||||
> **DECISION — one owner: the customer's action, and nothing else.** A census found **14 callers of
|
||||
> `StartStack`/`StopStack`, of which exactly 2 are the customer**; the rest are quiesce, the volume
|
||||
> dump, offbox reconstitution, app export/restore, the storage gate, migration and the boot
|
||||
> reconciler. So the primitives are deliberately **not** writers — intent there would make a nightly
|
||||
> backup indistinguishable from the customer pressing Stop. Writers: the API action switch,
|
||||
> `DeployStack`, `UpdateOptionalConfig`'s redeploy branch, the `.fab` import. Intent is written
|
||||
> **BEFORE** the act and a failed write **REFUSES** the act.
|
||||
>
|
||||
> **DECISION — absent means UNKNOWN, never "running", and this is the whole safety property.** Every
|
||||
> `app.yaml` on every box predates the field, so absent is what the fleet reads on upgrade; reading
|
||||
> it as running would start every deliberately-stopped app on the first boot after the upgrade. The
|
||||
> legacy branch of `isBootOrphan` keeps the old container-count rule **byte-for-byte**, and its test
|
||||
> asserts BOTH legacy rows together because the safety property is the pair. Backfill is
|
||||
> **running-only** — "zero containers ⇒ stopped" IS the defect, so an ambiguous app stays ambiguous.
|
||||
>
|
||||
> **Part 2 — `backup.AppStopGuard`**, a persisted marker over every stop→work→start window (volume
|
||||
> dump, offbox reconstitute, `.fab` export), in its **own** file (one file, one writer). Written
|
||||
> before the stop, cleared only after a restart that **succeeded**, kept when one fails. `Recover`
|
||||
> **returns** its outcome instead of using a notifier seam, because it must complete before the boot
|
||||
> reconciler (`main.go` ~236) while the notifier is not built until ~307 — a seam wired after the
|
||||
> fact is a seam that never fires.
|
||||
>
|
||||
> **THE TEST LESSON, and it is the one worth carrying:** Scenario E's first version called
|
||||
> `appStop.Begin` itself, and **survived the red-proof that deleted the production call**. It proved
|
||||
> the marker type, not that `DumpAppVolumesSafe` uses it. Rewritten to drive the real function with a
|
||||
> simulated hard abort (an unwind that skips the restart statement, since a `defer` is not
|
||||
> crash-safety — Campaign 8 fault 10). **A test that constructs the thing it is meant to prove the
|
||||
> caller constructs is hollow, and its red-proof will say so if you run it.**
|
||||
>
|
||||
> **FOUND EN ROUTE — `SaveAppConfig` rebuilt `AppConfig` field-by-field**, the R-100 shape (v0.181.0
|
||||
> shipped two live instances). The literal named five fields, so `desired_state` would have been
|
||||
> dropped on **every** save across nine call sites — a customer's Stop erased by the next unrelated
|
||||
> `app.yaml` write. Copy-and-overlay (`saveCfg := *cfg`) is safe by construction. **Generalise it:
|
||||
> treat any field-by-field struct rebuild in a save path as a defect on sight.** Measured, not
|
||||
> assumed: `app.yaml` does NOT round-trip YAML keys the struct does not model (pinned by test).
|
||||
>
|
||||
> **R-157: mechanism B closed, mechanism A untouched** (the sweep observes ~5 s after start and never
|
||||
> re-checks) — and B's fix makes A cost more, since the sweep now has more it could recover.
|
||||
> **NEW R-170:** `shouldRecreateOnBoot` (`internal/web/intermediary.go:131`) still infers a Stop from
|
||||
> `hasContainers` — the same defect one gate over, for drive-backed apps. Left deliberately.
|
||||
>
|
||||
> **Live on 9201, three flows** (stop survives a restart; a zero-container `running` app recovered by
|
||||
> name; a legacy app.yaml skipped and never inferred stopped). The **interrupted-operation half is
|
||||
> IMPLEMENTED, not PROVEN-LIVE** — nobody killed the controller mid-backup on metal. 27/27 packages;
|
||||
> 7 red-proofs observed FAIL then restored. Detail: `REPORT.md`.
|
||||
|
||||
Last updated: 2026-07-28 (v0.182.0 — R-101 + F-DIAG: the restore dialog names the last SUCCESSFUL copy)
|
||||
|
||||
> **2026-07-28 — v0.182.0 (R-101 + F-DIAG).** `Tier2LastRun` is the ATTEMPT clock (written on failure)
|
||||
|
||||
Reference in New Issue
Block a user