feat(analytics): cross-log verification with a manager-set time tolerance (checklist C) #9
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/crosslog-verification"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements checklist item C (C1-C6): advisory cross-log verification for a Monthly Contest.
Decisions (owner, 2026-10-25)
verification_settingsstores it: default 5 minutes, bounds 0-120, editable in the app, audited. The engine never hard-codes it.What changed
feat(analytics):app/analytics/crosslog.py— pairs on callsign+band (never serial, so a busted serial is a mismatch instead of a NIL), then compares received-vs-sent serial, received-vs-sent locator and log time. Pure package: no web/i18n/persistence imports.feat(db):verification_settingssingleton + migration0003, seeded idempotently byapp.bootstrap.feat(manager):/manager/contest/{id}/crosslogreport grouped per entrant, andPOST /manager/verification-settings(audited asverification.settings; out-of-range changes nothing).test(crosslog): 17 new tests (199 total).docs: checklist C1-C6 ticked with the decisions, future-work/design/HANDOVER updated (next item = D, which needs the D1 scope decision).Verification
- GET /manager/contest/{id}/crosslog: advisory findings grouped per entrant, systematic patterns marked, informational rows kept visually separate, plus the current tolerance. - POST /manager/verification-settings: stores the tolerance through record_audit (action verification.settings); an out-of-range value changes nothing and is not audited. - Dashboard links to the report per Monthly Contest. - New msgids in both catalogs; finding labels and column headers included.Review round 1 — addressed in
2dadf53An independent review returned CHANGES NEEDED (0 blockers, 2 major) and an independent functional tester returned 10/10 PASS. The two majors were real design flaws, not polish:
1. Section E (SWL) entrant were being branded as fraud. The engine ignored
sectionentirely. An SWL hears stations that never log the SWL, so every claim became a NIL, and three NILs crossed the systematic threshold — every active SWL would have carried the "Systematic" marker. Fixed: Section E logs are no longer cross-checked; the report says the entry is not cross-checkable, which is the honest answer.2. A band disagreement was escalated to NIL. One side logging 2m where the other logged 70cm — an ordinary logger-default error — produced a NIL under both entrants, and three of them made an entrant look systematic. Fixed:
FindingKind.BAND_MISMATCH; only NIL / SERIAL_MISMATCH / LOCATOR_MISMATCH can make an entrant systematic now.Also fixed from the review:
record_audit), and the action follows the codebase'snoun.verbvocabulary (verification_settings.update);next_urlis confined to/manager(no open redirect);reasonthe Audit Log expects;server_default+ a CHECK constraint socreate_alland the migration agree;.informationalrows are actually styled now;nl.posays "inzending" for the stored record, per CONTEXT.md;Verification after the fixes: 209 tests pass. On the live stack: an SWL log uploads as Section E and shows the not-cross-checkable note with no NIL and no systematic marker; the newest audit row is
verification_settings.updatewith the manager's reason andtarget_id=1; an out-of-range POST redirects withskew_error=1and the page explains it with HTTP 400; the corpus still reproduces exactly one NIL, one serial mismatch and one non-submitter row.Review round 2 — addressed in
9a25b4aSecond pass returned APPROVE WITH NOTES and the live tester 9/9 PASS. The notes were legitimate, so they are closed too:
next_urlwas a bare prefix check (/managerX,/manager/../..//evil.examplepassed through). It now requires a real/managerpath boundary, and the dashboard renders the out-of-range notice too, so the message is not dropped when the dashboard is the redirect target.0003revision chain plus its emitted DDL (the migration chain had no test past0001).Suite: 211 passed. PR comment is documentation of the round only — nothing is merged until the owner says so.
One genuine policy question for the owner, surfaced by this round: TIME_SKEW is no longer part of the "systematic" classification (it was before PR review). A station with a slipping clock produces many time rows without inventing anything, so it is noise — but if you would rather see a repeating time pattern flagged as systematic, that is a one-line change to
FRAUD_SHAPED_KINDS.