| Previous finding | Disposition | Evidence from revision 9 at `ca3f3536` |
|---|---|---|
| R3-1. Expiry/install races | Resolved | §5.3 retains expiry checks before download and installation, with `genMu` serializing selection, expiry and cleanup. |
| R3-2. Unprotected console `Locate` | Resolved | §5.4 retains locked `Swappable.Locate` and assigns the wrapper to `Config.GeoIP`. |
| R3-3. Coordinate-only results | Resolved | §8.1a accepts coordinate-only hits; §9 counts their country as `_unknown`. |
| R3-4. Heartbeat freshness | Resolved | §5.5 retains `status.BoxStale` and the jittered-heartbeat test. |
| R1-4, carried forward: orphan destruction | Resolved | §3 retains startup and daily orphan sweeps, followed by §15’s noncurrent-version expiration. |
| R1-16, carried forward: rollout verification | Partly resolved | §5.5 now excludes retired rows from blocking, but its blanket incomplete-read rule still lets an unreadable retired heartbeat block readiness. Scope record-level blockers to expected identities; also change the final “every listed box” requirement to “every expected box.” |
| R4-1. Duplicate tunnel analytics | Resolved | §§8.4 and 9 retain zero remote observations and exactly one at the hub. |
| R4-2. Publication deadline across backhaul | Resolved | §4.2 retains staging followed by a 202 response and a polled publication job. |
| R5-1. Concurrent upload staging | Resolved | §4.2 retains mutex reservation before reading the body and ownership through job cleanup. |
| R6-1. Plaintext tunnel cache mismatch | Resolved | D2 and §§8–9, 16 consistently require the transport-independent tunnel mark. |
| R6-2. Abandoned upload staging | Resolved | §4.2 retains the dedicated staging directory, startup cleanup and restart tests. |
| R7-1. Publication of expired candidates | Resolved | §4.1 rejects expired candidates, rechecks before pointer replacement and forbids a force override. |
| R7-2. Empty sanitized source | Resolved | §6 retains the valid fallback identifier `mmdb`. |
| R7-3. Attribution lost through overflow | Resolved | §9 exempts attributed sources from folding into `_other`. |
| R7-4. Interface discrepancies | Resolved | §§4.1 and 6 retain the corrected signatures and Unix-seconds conversion. |
| R8-1. Box identity namespaces | Resolved | §5.5 requires `(namespace, label)` throughout inventory, heartbeat matching and blockers. |
| R8-2. Shutdown propagation bound | Partly resolved | §5.3 adds pointer revalidation and pass-start scheduling, but its eleven-minute guarantee still excludes pointer-read time. An eleven-minute polling wait followed by a successful nine-minute read takes twenty minutes. Include read duration and qualify behavior during read failures. |
| R8-3. Existing-blob verification | Resolved | §3 now requires both SHA-256 and size to match before replacing the pointer. |

1. **P2 — §§4.2, 5.5: status can report `ready` without establishing database convergence.** The readiness contract does not distinguish an unreadable pointer from confirmed absence. The implementation records `pointer_error` but passes a nil pointer to the verdict, disabling the hash comparison. Healthy heartbeats reporting different generations can therefore produce `ready: true`. Confirmed absence or expiry also disables that comparison, allowing readiness while mirrors still report a loaded database.

   **Evidence:** [apisystemgeoip.go:137](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemgeoip.go:137) leaves `ptr` nil after a read failure; [apisystemgeoip.go:157](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemgeoip.go:157) still computes readiness. At [apisystemgeoip.go:299](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemgeoip.go:299), mismatches block only when the desired SHA is nonempty.

   **Fix:** specify distinct unknown, off, live and expired pointer states. Unknown must block readiness. Off and expired must require expected mirrors to report no loaded generation. Test pointer-read failure with otherwise healthy heartbeats, and deletion while a mirror still reports the previous SHA.

2. **P2 — §§3, 4.2: `off` can time out waiting for the publication mutex.** An upload reserves the mutex before reading its body and retains it through the publication job. A concurrent synchronous `off` therefore waits behind an operation allowed to take minutes. The caller or backhaul times out before the pointer is deleted, leaving the operator without a completed shutdown. The spec defines serialization but omits admission and response behavior for this case.

   **Evidence:** [geoippublish.go:290](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/geoippublish.go:290) uses blocking `Lock`; [apisystemgeoip.go:106](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemgeoip.go:106) waits synchronously for `Off`. The ordinary CLI client has a thirty-second timeout at [auth.go:404](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/auth.go:404), and backhaul has the same response-header limit at [edge.go:2710](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/edge/edge.go:2710).

   **Fix:** require immediate 409 when the publication slot is occupied, or make `off` a polled job with an independent lifetime. Preserve serialization through cleanup. Test `off` during both a stalled upload and an active publication job, including through backhaul.

Verdict: revise.