Voidly creates incidents through three deterministic pipelines:
scripts/create-voidly-incidents.py (Voidly
probe blocks), scripts/ingest-ooni-incidents.py
(OONI ground-truth signals), and the IODA ingest's
promote_to_incidents() function (which produces
noisy disruption labels, not censorship). Each of these is a thin rule over
one upstream source. None of them looks at the joint signal across sources,
and none of them looks at our second-opinion DBSCAN unsupervised anomaly
model. If a country-day looks like censorship but the three rules miss it,
the only way it makes it into the incident table is if a human notices.
scripts/auto-incident-watchdog.py runs every
six hours under cron and proposes draft incidents only when
four independent gates fire together:
is_anomaly = true on the leaderboard).
When all four hold, the watchdog writes a JSON draft to
/opt/voidly-ai/data/auto_incidents_queue/<slug>.json
with status = "draft",
created_by = "auto-watchdog",
the Bayesian posterior under
evidence_corroboration_posterior,
the DBSCAN anomaly score, the IDs of every linked confirmed-source
evidence row, and a template narrative. The drafts are exposed at
GET /v1/atlas/auto-incidents-pending for an
editor to review.
incidents table.
Drafts sit only in the queue dir. The 343-citable-censorship headline
on the index page is unchanged by every run of the watchdog.atlasFindings.ts.Four DBSCAN flips on the leaderboard: MA, EG, IQ, IN on 2026-05-20. All four failed the Bayesian corroboration gate (posteriors 0.37, 0.37, 0.00, 0.18 — well below the 0.5 floor). All four also have existing incidents within +/- 7 days, so even with a high posterior they would have been deduped. Net: 0 drafts written. That is the honest baseline — on a normal day, the deterministic pipelines cover the obvious cases, and the watchdog stays quiet.
0 */6 * * * /usr/bin/python3 /opt/voidly-ai/scripts/auto-incident-watchdog.py/var/log/voidly-auto-incident-watchdog.log/opt/voidly-ai/data/auto_incidents_queue/
(one JSON per draft, plus _manifest.json
with per-run counts and skip reasons).GET /v1/atlas/auto-incidents-pending
(supports ?country=,
?min_posterior=,
?limit=, and
?include_body=true for the full template HTML).GET /v1/atlas/auto-incidents-pending/<slug>.auto_incidents_queue/_archive/ on the next
watchdog run.
Every draft is a single JSON object with a stable
slug, the trigger numbers, and the linked
evidence row IDs. To promote a draft, an editor confirms the underlying
numbers (each draft's raw_links field
points at the live API endpoints the trigger was computed from), then
inserts a real row into the incidents table
by copying the draft's fields into one of the existing creator scripts
(create-voidly-incidents.py or
ingest-ooni-incidents.py) with
status = "active". To dismiss a draft, the
editor deletes the JSON — no database rollback is needed because the
watchdog never touched the database.
source_data but preserves the original
first_seen so editor-facing timestamps
are stable.