Read-only ActivityPub → atproto bridge for the threadiverse using Coves lexicons

fix(reconcile): stop the report claiming things it cannot prove (17e review) master

A seven-stream review of the reconciliation sweep. The sub-run's premise is that a class name, a metric name and a Detail string are CLAIMS — and the review found the sweep asserting several the data does not support, plus one outright false positive that would have grown without bound. THE CRITICAL ONE: every successfully re-cast vote was reported, forever. A vote flip is an in-place upsert — current_activity_id moves to the new activity, delivered_state resets to pending, and NO Undo is enqueued. So once the new vote delivered, the OLD delivered activity satisfied neither exclusion: the ledger named the new id, and no Undo existed. outbound_activities is append-only, so the finding was permanent, and the class grew with every vote change the bridge had ever federated — on the highest-volume table in the system. The Detail told the operator the peer held a vote whose delivery never landed. Both halves were false, and acting on one would have retracted the vote the user holds right now. A third exclusion fixes it: a later DELIVERED Like/Dislike for the same (actor, subject) supersedes an earlier one exactly as a delivered Undo does, compared as a (created_at, activity_id) tuple because created_at is the TRANSACTION timestamp and two activities in one transaction tie on it. Worth recording how it survived the original gate. Both existing exclusions were individually bitten and confirmed load-bearing, by controls built so that only one could suppress each fixture — that discipline was right and it was done deliberately. It cannot prove a set is COMPLETE. Biting answers "does removing this change an answer I already test?", and says nothing about answers no fixture contains. The same blind spot hid two more: adm.status = 'accepted' could be deleted with the whole suite green (so a REJECTED post — same row shape — was reported as one the community accepted), and DISTINCT ON ... seq DESC could be flipped to ASC likewise. HONESTY FIXES, which are the rest of it. Four poison classes never touch the wire (parent_unaccepted, parent_poisoned, cross_authority, signer) and were being filed under "we sent this and got no answer", inflating the one number whose worth depends on staying small; they are known non-delivery and are now excluded by the same rule that already excluded cancelled. The acceptance classes asserted definite non-delivery when accepted_at IS NULL proves only that settlement was never RECORDED — the definite wording now survives for cancelled alone. The re-cast Detail asserted a cause the query does not establish, since a poisoned Undo and purge residue reach that class too. OBSERVABILITY, where the old code could publish health from a sweep that measured nothing. Counts are now derived from the gauge registry so the two key sets are one set by construction — replacing a comment that claimed Go checks map-literal exhaustiveness, which it does not, and which was repeated in this project's own commit history on my authority. A missing count now publishes the unswept sentinel and logs an error rather than a plausible zero. Publication is atomic under a mutex with a generation taken at sweep START, so a slow background pass can no longer overwrite a newer on-demand one and walk the gauges backwards while an operator refreshes. And a sweep that fails forever is no longer invisible: a last-successful-sweep age gauge and a failure counter, the freshness precedent consume/metrics.go already set. BOUNDS. The 500-entry cap did not bound memory — no query carried a LIMIT, so every divergent row was materialized before the cap applied, putting the spike on the admin endpoint an operator hits BECAUSE the process is struggling. The bound now reaches the database, with separate exact counts so Counts stays a true unbounded measurement, and each comparison's FROM/WHERE is one shared constant so the list and the count cannot describe different populations. Examples are dealt round-robin so a bulk class cannot leave later classes with a count and no example to investigate. A two-minute sweep timeout bounds one pass, because the connection pool is 25 for the whole application and a pathological sweep could otherwise starve federation. ENFORCEMENT, replacing two comments with mechanisms. worker.go now consumes the store's poison-class constants instead of repeating literals, so the correspondence between "decided before any POST" and "excluded from unknown" is compile-checked; changing one constant reds a test in internal/outbound and another in internal/store. And every read runs in BEGIN ... READ ONLY — one transaction per read, so no pass-wide snapshot fights the timeout — which makes Postgres itself refuse a write, the only enforcement of decision 19 that survives a future contributor. Also: an unmappable delivery state now aborts the pass instead of vanishing from Entries AND Counts; DIVERGENCE_ACCEPTANCE_STALE_AFTER is wired rather than documented-but-unreachable; the 500 body is redacted like its siblings; community_did joins the acceptance comparison; and the FOLLOWUPS perf entry is corrected — the acceptance query's JSONB join is a SECOND unservable full scan, confirmed by a planner that still refuses an index with enable_seqscan off. Tests: whole-package -count=2 across store/ingest/votes/outbound/consume/config, full suite 21/21, e2e 280s. Every fix bite-proofed, including the controls that were vacuous until their implementation landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>