From c67a4d3f35750d0d06534b2a9bd8cd28940b04ea Mon Sep 17 00:00:00 2001 From: Minseong Choi Date: Wed, 8 Jul 2026 05:56:59 +0900 Subject: [PATCH] =?UTF-8?q?docs(changes):=20close=20=C2=A7B4=20with=20the?= =?UTF-8?q?=20S3=20archive=20backend=20deferred=20by=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operational break-glass ops (Owner provision, OP create, halt, Sync backup) are built and oracle-verified. The fourth §B4 line item — the tarS3 archive backend — is recorded as a deliberate deferral, not a silent gap: config.Load fail-closes store=tarS3 (frozen by TestLoadRejectsUnimplementedArchiveStore), tarLocal is the tested baseline every backup/restore path uses today, and offsite/cross-cluster DR is opt-in future work. Supersedes the "S3 remains open" note in the phase-2b peer doc. --- .../2026-07-08-b4-close-s3-deferred.md | 57 +++++++++++++++++++ docs/changes/INDEX.md | 1 + 2 files changed, 58 insertions(+) create mode 100644 docs/changes/2026-07-08-b4-close-s3-deferred.md diff --git a/docs/changes/2026-07-08-b4-close-s3-deferred.md b/docs/changes/2026-07-08-b4-close-s3-deferred.md new file mode 100644 index 0000000..316ccce --- /dev/null +++ b/docs/changes/2026-07-08-b4-close-s3-deferred.md @@ -0,0 +1,57 @@ +# §B4 phase close — S3 archive backend deferred by design (decision record) + +- **Type:** decision / scope record (no code changed) +- **Date:** 2026-07-08 +- **Area:** `internal/config`, `internal/backup` — the archive-store backend selection +- **Task:** #31 Phase B4 break-glass ops — **closes the phase**, superseding the phase-2b + note in [break-glass-backup-peer](2026-07-07-break-glass-backup-peer.md) ("S3 remains + open in B4; this does not close the phase"). + +## Decision + +The three operational B4 break-glass ops are built, wired into the recovery console menu, +and oracle-verified: + +| Op | Menu enum | Commit | +|---|---|---| +| Provision/reset Owner | `bgProvisionOwner` | (Phase B1 lineage) | +| Add Operator ("OP create") | `bgAddOperator` | recovery-console menu | +| Halt a running server | `bgHaltServer` | `c2ee21a` | +| Back up a world now ("Sync") | `bgSyncBackup` | `7a7c0d5` / `f2fc57c` / `fc748d3` | + +The fourth B4 line item — **S3 archive backend (`tarS3`)** — is **deferred by design**, not +left as a silent gap. It is closed as a documented deferral and **#31 is done**. + +## Why deferring is safe (not a loose end) + +- **Fail-closed at config load, frozen by a test.** `config.Load` rejects + `store = "tarS3"` (and `volumeSnapshot`, `longhorn`) with an error that points the + operator at the `tarLocal` remediation. `TestLoadRejectsUnimplementedArchiveStore` + freezes exactly this: a config naming an unimplemented backend fails at load, so + felis-api can never boot green while the reaper CronJob fails every run and restore + silently 503s. tarS3 cannot be selected into a broken state. +- **Peer to two other deferred backends.** `tarS3` sits beside `volumeSnapshot` and + `longhorn` as recognized-but-unimplemented store names. The spec's own phasing is + tarLocal-first ("起步 `tarLocal` … 要异地/跨集群 → `tarS3`"): the baseline single-node + path is `tarLocal` (tar → backup PVC), which is implemented, tested, and the backend + every built backup/restore path uses today. +- **Offsite/cross-cluster DR is the only capability gap**, and it is opt-in future work, + not a correctness hole in the shipped baseline. + +## The build path, when offsite DR is wanted + +`minio-go/v7` is already vendored (the modpack upload lane's `internal/submit/s3store.go`), +so tarS3 adds no dependency. A future build is bounded: + +1. `internal/backup/tars3.go` — a `WorldArchiver` reusing the existing package-level + `writeTarGz`/`readTarGz`/`pruneToManifest`, streaming the tar to an object via + `PutObject` (size −1, multipart) and reading it back via `GetObject`, mirroring + `s3store.go`'s fakeable-client testability. +2. Store-selection factory in the backup/reaper entrypoint (`store = "tarS3"` → construct + the minio-backed archiver) + remove tarS3 from the config fail-closed list (update + `TestLoadRejectsUnimplementedArchiveStore` to keep only volumeSnapshot/longhorn). +3. Inject the S3 Secret into the backup/reaper Job Pods (integration wiring, like the + Kaniko S3-context credential path). + +The live-S3 upload + Secret-into-Pod would be integration-only verified, exactly as the +modpack S3 backend and the pgrepo SQL are. diff --git a/docs/changes/INDEX.md b/docs/changes/INDEX.md index 9890b23..c2ed069 100644 --- a/docs/changes/INDEX.md +++ b/docs/changes/INDEX.md @@ -249,3 +249,4 @@ primary record. | 096d597 | 2026-07-07 | docs(changes): backfill detail docs for pre-ledger functional commits | | 5a7cd5a | 2026-07-07 | docs(changes): fold 346ec68 cloudflare-edge walkthrough into its detail doc | | 4626ab5 | 2026-07-07 | docs(changes): mutation-audit the ledger's "unit-tested" safety claims | +| 729bd7b | 2026-07-07 | docs(changes): index the mutation audit and two lagging ledger rows |