| Previous finding | Disposition at `00428606` |
|---|---|
| R1-1 — Readiness without established database state | **Resolved.** Unknown pointers block readiness; off and expired pointers require empty mirrors. |
| R1-2 — `off` waits behind publication | **Resolved.** `TryLock` returns 409 while publication holds the slot. |
| R1-3 — Unbounded console pointer reads | **Resolved for reads.** Pointer reads are streamed and capped. |
| R1-4 — Recovery generation deleted after failed synchronization | **Resolved for the reported paths.** Failed installations preserve loaded and retained generations. |
| R1-5 — Rejected-upload test does not establish zero reads | **Resolved at the reservation boundary.** The panicking-reader test detects reads before reservation. |
| R1-6 — Countries overflow ordering | **Resolved.** `_other` remains intact until rendering and sorts last. |
| R2-1 — Feed status writes bypass the read cap | **Partly resolved; P2 remains.** `geoipfeed.go:174` still calls `Put`; `release/store.go:408` reads the entire existing object before checking its precondition. An oversized `feed.json` can exhaust memory. Apply the stat/streamed-hash checks used by `PutFrom`. |
| R2-2 — Timed-out checks never persist failures | **Resolved for download timeouts.** Status persistence gets a separate bounded context. |
| R2-3 — Unreadable feed status clears an alert | **Resolved.** Read and parse failures preserve established feed state. |
| R2-4 — Unusable directory prevents “not loaded” alert | **Resolved.** Pointer state is retained before directory preparation. |
| R2-5 — Published alert reasons never change while unhealthy | **Resolved.** Reason changes update `Value` and emit an event. |
| R3-1 — Console follower keeps serving an expired database | **Resolved.** Loaded-reader expiry is checked independently of local state and candidate loading. |
| R3-2 — Transient status-read failure erases feed history | **Resolved.** Unknown history is not overwritten. |
| R4-1 — Unbounded bucket operations block startup or publication | **Resolved for the reported operations.** Startup cleanup is asynchronous; maintenance and staging reads have deadlines. |
| R4-2 — Restart falsely clears a persisted GeoIP alert | **Resolved.** Unknown inputs cannot establish recovery. |
| R4-3 — Email delivery suppresses active reason changes | **Resolved.** Successful delivery records the last reason per recipient. |
| R4-4 — Region guesses carry another generation’s attribution | **Resolved.** Location and source are captured under one reader lock. |
| R5-1 — Unknown feed state suppresses confirmed expiry | **Resolved.** Established unhealthy conditions precede the unknown-state guard. |
| R6-1 — Failed startup deletion leaves a disabled feed alert firing indefinitely | **Resolved.** Deletion retries with bounded attempts and capped backoff. |
| R7-1 — Unbounded feed edition metadata makes control records unreadable | **Resolved for the reported input.** Editions are normalized HTTP dates; oversized serialized pointers are refused. |
| R7-2 — Unchanged-edition checks leave the feed reported as “not run yet” | **Resolved.** Confirmed editions update success history. |
| R8-1 — MMDB verification indefinitely holds the publish slot | **Resolved for the reported traversal attacks.** Verification runs in a child killed at its deadline; `Commit` defers staging cleanup and slot release. |
| R9-1 — Malformed MMDB metadata can crash the entire console | **Resolved for the reported arithmetic panic.** Parsing occurs in the child, whose failure refuses publication without replacing the pointer. |

New findings:

1. **P1 — The verifier child has no memory bound, so a tiny database can still exhaust the console’s memory.**  
   [internal/console/geoipverify.go:91](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/geoipverify.go:91)

   The child has a runtime deadline and capped output, but no independent memory limit. In the pinned `maxminddb-golang` decoder, the four-byte map header `FF FF FF FF` declares **16,843,036 entries**. The decoder allocates that map before checking whether its entries exist. Nested maps can therefore cause gigabytes of allocations from a tiny malformed data section, before verification or the country probe rejects it.

   The child inherits the console’s service cgroup. These allocations can trigger service-level or host-level out-of-memory handling, taking down the console or other processes before the five-minute timeout. Moving parsing to a child contains ordinary panics but does not contain this resource failure.

   **Fix:** enforce an allocation budget before invoking the unsafe decoder, or run verification under an independent hard memory limit sized to preserve console headroom. Add a regression using oversized collection declarations under a small enforced budget, asserting parent survival, slot release, staging cleanup, and preservation of the previous pointer.

2. **P2 — Retained recovery generations can survive their GeoLite2 expiry indefinitely.**  
   [internal/edge/geoipmirror.go:445](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/edge/geoipmirror.go:445), [internal/edge/geoipmirror.go:484](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/edge/geoipmirror.go:484)

   Start with GeoLite2 generation A, then publish DB-IP generation B. If the directory sync after selecting B fails once, A is retained in `unsynced`. Even after the filesystem recovers, subsequent polls return immediately because B is already loaded. They never retry the pending sync or clean up A. `ExpireTick` checks only B, which never expires, so A remains on disk beyond D1 indefinitely unless another publication, restart, or `off` occurs.

   **Fix:** retry pending synchronization when the pointer still names the loaded generation, then clean up retained files after synchronization succeeds. Also track retained generations’ expiry so expired GeoLite2 copies are removed even while synchronization remains unavailable. Test a single sync failure during a GeoLite2-to-DB-IP transition, followed by unchanged-pointer polls and advancement past A’s expiry.

Validation was static, including inspection of the pinned decoder. Targeted Go tests could not start because creating the build directory failed with “read-only file system”; runtime tests and cross-compilation remain unverified.

Verdict: revise.
