Round 5 review of `feef9a5c`.

| Previous finding | Disposition | Evidence from revision 5 |
|---|---|---|
| R3-1. Expiry/install races | Resolved | §5.3 retains expiry checks before download and installation, with `genMu` serializing installation, expiry and cleanup. |
| R3-2. Unprotected console `Locate` | Resolved | §5.4 retains locked `Swappable.Locate` and puts the wrapper in `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 adds expected-box reconciliation through `control/edges/`, but fleet boxes explicitly skip that publisher: [edgesfile.go:698](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/host/edgesfile.go:698). Missing fleet heartbeats remain invisible, and those older than seven days are still discarded. Also, `systemBoxes`’s strict mode still uses `LoadBoxes`, which silently skips individual unreadable records: [apisystemupdate.go:258](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemupdate.go:258). Reconcile active registered fleet boxes through `ListFleetBoxes`, and use `LoadBoxesComplete` for every heartbeat prefix. |
| R4-1. Duplicate tunnel analytics | Partly resolved | §9 correctly suppresses the remote observer through `viaTunnelKey`, but §8.4 still requires a test that counts the country in the remote box’s analytics. Change that assertion to zero remote observations and exactly one aggregate observation at the hub. |
| R4-2. Publication deadline across backhaul | Resolved | §4.2 stages the upload, returns 202, and moves verification and publication into a polled job, with a backhaul timeout regression test. |

1. **P2 — §§4.1–4.2: the upload-and-job split leaves staging concurrency unspecified.** The revised protocol places the job under the publication mutex and refuses another upload while a *job* holds it. It does not reserve the slot while the first request is still uploading. Multiple slow uploads can therefore start before any job exists, each staging up to 1 GiB on the console’s disk. Serializing the subsequent jobs does not bound that disk consumption.

   **Evidence:** the staging and job-lock rules are at [design.md:305](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/docs/superpowers/specs/2026-10-06-platform-geoip-design.md:305). The existing job registry reserves its slot only when `startSystemJob` runs: [apisystemupdate.go:364](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/geoip-for-apps/internal/console/apisystemupdate.go:364). Reusing the registry after staging supplies no upload admission control.

   **Fix:** reserve one publication slot before reading any upload body, retain it through the handler-to-job handoff, and release it after job cleanup. Explicitly transfer ownership of the temporary file to the job. Test two concurrent uploads with the first body stalled: the second must receive 409 before its body is read.

Verdict: revise.