| 1 | | Previous finding | Disposition | Evidence from revision 9 at `ca3f3536` | |
| 2 | |---|---|---| |
| 3 | | R3-1. Expiry/install races | Resolved | §5.3 retains expiry checks before download and installation, with `genMu` serializing selection, expiry and cleanup. | |
| 4 | | R3-2. Unprotected console `Locate` | Resolved | §5.4 retains locked `Swappable.Locate` and assigns the wrapper to `Config.GeoIP`. | |
| 5 | | R3-3. Coordinate-only results | Resolved | §8.1a accepts coordinate-only hits; §9 counts their country as `_unknown`. | |
| 6 | | R3-4. Heartbeat freshness | Resolved | §5.5 retains `status.BoxStale` and the jittered-heartbeat test. | |
| 7 | | R1-4, carried forward: orphan destruction | Resolved | §3 retains startup and daily orphan sweeps, followed by §15’s noncurrent-version expiration. | |
| 8 | | 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.” | |
| 9 | | R4-1. Duplicate tunnel analytics | Resolved | §§8.4 and 9 retain zero remote observations and exactly one at the hub. | |
| 10 | | R4-2. Publication deadline across backhaul | Resolved | §4.2 retains staging followed by a 202 response and a polled publication job. | |
| 11 | | R5-1. Concurrent upload staging | Resolved | §4.2 retains mutex reservation before reading the body and ownership through job cleanup. | |
| 12 | | R6-1. Plaintext tunnel cache mismatch | Resolved | D2 and §§8–9, 16 consistently require the transport-independent tunnel mark. | |
| 13 | | R6-2. Abandoned upload staging | Resolved | §4.2 retains the dedicated staging directory, startup cleanup and restart tests. | |
| 14 | | R7-1. Publication of expired candidates | Resolved | §4.1 rejects expired candidates, rechecks before pointer replacement and forbids a force override. | |
| 15 | | R7-2. Empty sanitized source | Resolved | §6 retains the valid fallback identifier `mmdb`. | |
| 16 | | R7-3. Attribution lost through overflow | Resolved | §9 exempts attributed sources from folding into `_other`. | |
| 17 | | R7-4. Interface discrepancies | Resolved | §§4.1 and 6 retain the corrected signatures and Unix-seconds conversion. | |
| 18 | | R8-1. Box identity namespaces | Resolved | §5.5 requires `(namespace, label)` throughout inventory, heartbeat matching and blockers. | |
| 19 | | 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. | |
| 20 | | R8-3. Existing-blob verification | Resolved | §3 now requires both SHA-256 and size to match before replacing the pointer. | |
| 21 | |
| 22 | 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. |
| 23 | |
| 24 | **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. |
| 25 | |
| 26 | **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. |
| 27 | |
| 28 | 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. |
| 29 | |
| 30 | **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). |
| 31 | |
| 32 | **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. |
| 33 | |
| 34 | Verdict: revise. |