fix(echo): adversarial-review fix wave (task 17a) master
Five-stream review returned needs-work; two streams independently found the same correctness bug and codex judged the patch incorrect. All four HIGH findings fixed test-first and bite-proofed. H1 — Classify descended into TARGETS, not just payloads. `object` is the payload only for wrapper verbs; for Like/Dislike/Delete it is the activity's TARGET, so the walk reached the target of a GENUINE remote activity and condemned the envelope. A Lemmy user's vote on a post we federated out was dropped before ApplyVote (native tallies would sit at zero forever), and a Lemmy moderator's removal of a native post could never fire — the feature 17c exists to deliver. Reached genuine traffic on 3 of 4 routes; the 4th survived only because it has no guard. The drop counter was scoring discarded genuine votes as successful suppressions. Fixed with a payload-verb ALLOWLIST (ap has no TypeFlag/TypeBlock/TypeRemove constants, and an unknown verb must fail toward not condemning). The suite missed this because every "genuine remote" fixture targeted LEMMY-origin content; the breaking shape is a remote actor acting ON our object. H2 — the community backfill bypassed both guards, reaching the materializer directly. A community outbox holds our own posts once native users participate, and MaterializePost calls EnsureActor BEFORE reading any mapping, so an ordinary admin re-backfill was a mint oracle minting a bridged_actors row for our own persona — plus 3 dereferences of our own origin and an uncounted drop. The item then died writing into a repo we do not host, so every trigger reported "1 of 2 items failed" and the community never looked cleanly backfilled. Guarded before resolveEmbedded (resolving is what dereferences) as a skip, not an error, so runs stay clean. H3 — two guards whose removal the suite could not detect: the authority-spoof check (its only negative cases exited via NotFound and never reached it) and the absence of any POSITIVE vanity-origin case, so an implementation comparing against one configured origin — what decision 10 forbids — passed everything. normalizeHost was an untested hand-copy. All three now bite. H4 — classification weighed a bare id string, and our ids are public. Now corroborated against the activity we actually sent: contradiction disqualifies, absence does not. Verb vs stored Kind, carried object vs the stored payload, disqualifying only when the substituted object is not ours (swapping one of our objects cannot lose anyone's content; a Lemmy human's note is the forgery). Never byte-equality — a real echo returns reserialized, and byte-comparison would refuse every genuine one. Structural (four reviewers converged): Echo is now REQUIRED in both ingest.NewHandler and NewBackfill and is an interface, so the dispatcher's fail-safe is finally exercisable — a classification that cannot be MADE parks and retries, never poisons and never materializes, with no counter moved (a failure is not a suppression). echo.New validates its four stores. expvar names underscored for Prometheus. identifyActor compares scheme. Two load-bearing doc comments corrected where the code contradicted them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>