Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ destroys the evidence needed to draw a better one later.
| Tier | Table | Produced by | Grain | Status |
|---|---|---|---|---|
| 0 Raw | S3 JSONL | OONI collector (external) | measurement | built |
| 1 Observation | `obs_web`, `obs_web_ctrl`, `obs_http_middlebox` | `make_observations` | measurement × endpoint | built |
| 2 Evidence / judgment | `analysis_web_measurement` | `make_analysis` | measurement × target | built |
| 1 Observation | `obs_web`, `obs_web_ctrl`, `obs_http_middlebox`, `obs_tunnel` | `make_observations` | measurement × endpoint (× phase for tunnels) | built; `obs_tunnel` **[mvp]** |
| 1 External report | `external_reports` | collector (measurement type `vpnext`) | provider report (per-connection or aggregated) | **[later]** |
| 2 Evidence / judgment | `analysis_web_measurement`, `analysis_tunnel_measurement` | `make_analysis`, `make_tunnel_analysis` | measurement × target | built; tunnel **[mvp]** |
| 3 Cell state | *(a view, not a table)* | `GROUP BY` over tier 2 | target × network × layer × hour | **[mvp]** |
| 4 Changepoint | `event_detector_changepoints`, `event_detector_cusums` | `make_detector` | series × signal | built |
| 5 Event | `events` | n/a | cc × target-set × time | **[mvp]** |
Expand All @@ -55,6 +56,13 @@ computing its own hourly aggregate inline, which is why measurement counts are
discarded and why a national event emits one alert per
`(cc, asn, domain, layer)` rather than one alert per event.

`analysis_tunnel_measurement` deliberately mirrors `analysis_web_measurement`'s
shape (per measurement × target: blocked/down/ok plus rule id, keyed on phase
instead of layer) rather than inventing a parallel structure, so tier 3, the
detector and the alert feed consume both through one shape. `external_reports`
sits at tier 1 but outside this chain entirely: it joins no control set and
feeds only event grading at tier 5, never tiers 2 to 4 (ontology.md §5.2).

Tier 3 is deliberately **a view rather than a table** while its grain is still
being learned. It holds per-layer histograms of rule firings, from which
consensus, sample size and ambiguity all derive ([ontology.md](ontology.md) §9).
Expand Down Expand Up @@ -85,20 +93,31 @@ graph TD
OGEN --> OW[(obs_web)]
OGEN --> OWC[(obs_web_ctrl)]
OGEN --> OHM[(obs_http_middlebox)]
OGEN --> OT[(obs_tunnel)]

OW --> AN[[make_analysis]]
OWC --> AN
FPD[(fingerprints_dns)] --> AN
RULES[analysis/rules.py<br/>rule registry] --> AN
AN --> AWM[(analysis_web_measurement)]

OT --> TAN[[make_tunnel_analysis]]
BASE[(tunnel baseline aggregate<br/>cross-network rollup)] --> TAN
TAN --> ATM[(analysis_tunnel_measurement)]

AWM --> DET[[make_detector]]
ATM --> DET
CLB[(citizenlab)] --> DET
DET --> CP[(event_detector_changepoints)]
DET --> CUS[(event_detector_cusums)]
CP --> SLACK[Slack alerts]

EXT[External report collector<br/>type vpnext] --> ER[(external_reports)]
ER -.corroboration only.-> EVT[[event grading]]
CP --> EVT

AWM --> API[[FastAPI]]
ATM --> API
OW --> API
API --> USERS[Explorer / researchers]

Expand All @@ -107,7 +126,9 @@ graph TD
```

Note `fastpath` is the *v4* pipeline's table, not produced here. The two data
quality jobs read it; nothing else in v5 depends on it.
quality jobs read it; nothing else in v5 depends on it. The dotted edge
from `external_reports` marks that it feeds event grading and nothing
upstream of it.

---

Expand Down Expand Up @@ -166,27 +187,47 @@ an oversight.
**Only web_connectivity produces this.**
- `obs_http_middlebox`: HIRL/HFM results. Different shape, hence a different
table.
- `obs_tunnel` **[mvp]**: one row per (measurement, endpoint, phase) for
tunnel nettests (`openvpn` today). No control counterpart; see
[ontology.md](ontology.md) §2, §3.1.

**External report tier** **[later]**

- `external_reports`: provider-submitted connection reports,
- per-connection or provider-aggregated. Outside the tiered chain:
joins no control set. [ontology.md](ontology.md) §5.2.
- should consider aligning the semantics with [MANTA client metrics](https://0xacab.org/leap/manta/-/blob/no-masters/2026-06-09-client-metrics.md)

**Judgment tier**

- `analysis_web_measurement`: per measurement: `(blocked, down, ok)` per layer,
the top failure per layer, and `top_{dns,tcp,tls}_rule_id`.
- `analysis_tunnel_measurement` **[mvp]**: per measurement: `(blocked, down,
ok)` per phase and the driving rule id, mirroring the web table's shape.

**State tier** **[mvp]**

- Cell state: a view over tier 2, not a table. §3.1 below; semantics in
[ontology.md](ontology.md) §9.
[ontology.md](ontology.md) §9. Extends to tunnel targets at the same grain,
keyed on phase instead of layer.

**Detection tier**

- `event_detector_cusums`: per `(cc, asn, domain)` CUSUM accumulator state.
Tunnel series extend the key to `(cc, asn, tunnel_target, phase)`
([ontology.md](ontology.md) §11); no new table, same accumulator shape.
- `event_detector_changepoints`: emitted transitions.

**Reference data**: externally maintained, refreshed by updater DAGs:
`fingerprints_dns`, `fingerprints_http`, `citizenlab`, `citizenlab_flip`,
`asnmeta`. The fingerprint tables are `EmbeddedRocksDB` and are swapped in
atomically via `EXCHANGE TABLES`.

**Tunnel baseline aggregate** **[mvp]**: a per-(tunnel target, window)
success-by-network rollup, standing in for the control web targets get from
`obs_web_ctrl`. New infrastructure, not a byproduct of an existing job;
§3.2 below states the cost reasoning.

**Data quality**: `faulty_measurements`, written by the volume and
time-inconsistency jobs.

Expand Down Expand Up @@ -229,6 +270,23 @@ closed window into a plain table. **Not** an insert-time `AggregatingMergeTree`
materialized view: MVs fire per insert and never observe `ReplacingMergeTree`
replacement, so nightly re-analysis would double-count every re-scored row.

### 3.2 Tunnel baseline aggregate **[mvp]**

Tunnel nettests have no test helper, so scoring needs a substitute control:
whether the same target succeeded elsewhere in the window
([ontology.md](ontology.md) §5.1). Unlike the web control, this cannot be
computed inline per run. §4.4 already gives the cost reason for why the web
control window stays narrow: scanning a trailing 24h or 7d of raw
observations on every hourly run is not affordable. The same arithmetic
applies here, with no narrow-window option to fall back on, since there is no
test helper to ask instead.

The aggregate is therefore precomputed: per `(tunnel_target, window)`, a
success/failure rollup by network, rebuilt per closed window like tier 3.
Small by construction (the watchlist of registered targets, not the full
measurement volume), and this is the one piece of standing infrastructure
the tunnel work adds beyond extending existing tiers.

## 4. Tradeoffs

Each closes with the requirement conflict it embodies, in
Expand Down
58 changes: 55 additions & 3 deletions docs/implementation-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ likelihood-ratio fits remain winner-censored.
| Corpus thin, fits winner-censored | First labels are drawn (two adjudicators) and the LR fit runs, but volume is far below the targets that make per-rule LRs informative, and only the *winning* rule is persisted, so fits are conditional on cascade position. | V1, V2 |
| Detector watchlist is narrow | Citizenlab global `GRP` plus `twitter.com`; country-specific news blocking is structurally invisible. | priority 5, D1 |
| `resolver_transport` unused | `dns_engine IN ('getaddrinfo','system')` excludes DoT/DoH/UDP answers from the answer set, so `dnscheck`'s central comparison is dropped. | E1, M1 |
| ~28 spec nettests have no transformer | Notably `openvpn`, actively collected with no analysis path at all. | M1 |
| ~28 spec nettests have no transformer | `openvpn` is addressed by §3.14-3.16 below; the remaining ~27 stay deferred (§4). | M1, M8 |
| Reference data is unversioned | Fingerprints and test lists join as of run time; no verdict names the corpus state that produced it, and a rebuild cannot reproduce old output for validation. | P3, A5 |
| Alerts are not reconstructible | Changepoints reference no measurement set, tier-2 rows mutate under them, and the recovery procedure regenerates history under current code. | P5, P4 |
| Late data never reaches detection | The daily DAG has no detector task; the CUSUM never revisits a closed hour. Late uploads correlate with censored networks (C7). | D2 |
Expand Down Expand Up @@ -106,8 +106,11 @@ would re-derive (§6.1 there).
| Provenance and reproducibility | 3.9 (versioned scoring inputs) |
| Unreconstructible alerts, late data, backfill hazard | 3.10 (stateless detection) |
| Silent degradation in production | 3.11 (health checks) |
| No circumvention-tool analysis or alerting | 3.14 (observation tier), 3.15 (analysis path), 3.16 (series and alerting) |

Roughly eight to ten weeks. Everything else is §4.
Roughly eight to ten weeks for 3.1-3.13; 3.14-3.16 add a further three to
five weeks, riding 3.6/3.7/3.10 rather than duplicating them. Everything else
is §4.

---

Expand Down Expand Up @@ -488,6 +491,53 @@ controls the strictest admission standard in the system.
**Effort:** days for the floor and sanity checks; revalidation waits on the
certificate-storage decision. **Serves:** A4, A2.

### 3.14 Tunnel observation tier

Land `obs_tunnel` ([ontology.md](ontology.md) §2), converging the OpenVPN
observation models already drafted in
[ooni/data#63](https://github.com/ooni/data/pull/63) with the shape that
entry specifies: one row per (measurement, endpoint, phase), the protocol
stack, and no control counterpart. Add the phase columns (§3.1) and wire
`make_observations` for `openvpn`.

Additive: a new table and transformer, no change to existing rows. The PR
exists and is open for review; this item is convergence and test coverage,
not new design. **Effort:** the PR's scope is most of it; convergence with
the unsupported-nettests review (#48) and tests, days. **Serves:** M8.

### 3.15 Tunnel analysis path

Register tunnel targets (`provider/protocol+obfuscation`,
[ontology.md](ontology.md) §4.1), build the cross-network baseline aggregate
(architecture.md §3.2) that stands in for the missing test helper, and score
`analysis_tunnel_measurement` from it: a rule cascade keyed on
`(target, phase)`, mirroring the web judgment tier's shape.

Rules ship **weightless** until 3.8 gains a tunnel stratum: this is the same
V2 discipline the web rules follow ("no unmeasured changes"), and skipping it
here would mean the alert feed's first tool-reachability alerts inherit
exactly the uncalibrated-constant problem the labelled corpus exists to
retire. **Effort:** the baseline aggregate is the real work; 1 to 2 weeks.
**Depends on:** 3.14. **Serves:** M8, C9 (aggregate-only publication is
enforced at this layer, not bolted on at presentation).

### 3.16 Tunnel series and alerting

Extend series to `(cc, asn, tunnel_target, phase)`
([ontology.md](ontology.md) §11), through the same cell state, detector and
alert feed as web series, not a parallel system. Events carry both the
provider ladder and the protocol ladder (§4.1) in their scope, so an alert
can say "RiseupVPN" or "openvpn+obfs4 generally" depending on what the
evidence supports.

Rides 3.6 (cell state), 3.7 (event correlation) and 3.10 (stateless
detection) rather than duplicating any of them: once those land for web
series, extending them to a second series kind is the marginal cost.
**Effort:** days once 3.6/3.7/3.10 land. **Depends on:** 3.15, 3.6, 3.7,
3.10. **Serves:** M8, D1 (endpoint rotation must not enter the series key,
which matters more here than for web: a provider's own discovery system can
rotate a pool on a schedule unrelated to any network condition).

---

## 4. Later
Expand All @@ -513,7 +563,9 @@ blocked on it.
| Full `locus` axis with inference (E7) | 3.5 stabilises series; naming *who* is responsible needs cross-network resolver checks that do not exist. Blockpage-fingerprint `scope` already attributes what it matches, and the planned middlebox and transit detectors widen that set. See [ontology.md](ontology.md) A.2. |
| Dempster-Shafer verdicts / pipeline-side mechanism inference / target hierarchy / cert store | Ontology Appendix A, each with its trigger. (The mechanism *taxonomy* was promoted into ontology §12; what stays deferred is the pipeline emitting mechanism labels as output.) |
| Widening the detector watchlist (priority 5) | Needs 3.7 first, and the stale hardcoded entry fixed (D1). |
| Coverage of ~28 unsupported nettests | `openvpn` first (actively collected, no analysis path); then the low-level probes, which map onto existing observation shapes. |
| Coverage of remaining ~27 unsupported nettests | `openvpn` moved into scope (3.14-3.16); the low-level probes remain, mapping onto existing observation shapes. |
| External reports tier (M8, C9) | `external_reports` table and `vpnext` transformer, corroboration join at event grading only ([ontology.md](ontology.md) §5.2, Appendix A.7). Trigger: a signed provider agreement under the published report format, with PR1's retention, access and aggregation-floor decisions settled first. This is where a companion project's provider-telemetry relationship (if one exists) would arrive pipeline-side: that project owns the collection and the relationship, this plan owns what the data may and may not mean once it does. |
| Tunnel mechanism taxonomy leaves (`tunnel.*`) | Reserved, not scored ([ontology.md](ontology.md) §12.3). Trigger: first adjudicated tunnel labels (3.8-style corpus extension) or alignment with an external taxonomy effort. |

---

Expand Down
24 changes: 24 additions & 0 deletions docs/label-corpus-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,20 @@ labels drawn either side are not one population.
with a wrong predicate is worse than an absent one, because it silently deflates
every LR denominator.

`screen_tunnel` is proposed, **not implemented**, and gated on
[implementation-plan.md](implementation-plan.md) §3.15 landing first: there is
nothing to draw from until `analysis_tunnel_measurement` exists. Once it does,
the same reasoning that motivates the layer strata applies with more force,
since tunnel evidence has no test-helper control at all to lean on: a uniform
draw would oversample whichever provider or protocol has the most traffic, so
draw explicitly per protocol stack, with `screen_negative`'s role (bounding
what the pipeline misses) played here by targets that tested reachable
everywhere. **This stratum must exist, populated, before any tunnel rule
weight ships** ([implementation-plan.md](implementation-plan.md) §3.15's V2
gate): a tunnel rule cascade running on unlabelled data is the same mistake
web scoring made before this corpus existed, repeated on a target class OONI
has less experience judging.

---

## Part 2: How an analyst should think about it
Expand Down Expand Up @@ -342,6 +356,16 @@ build a corpus of any size.
Controls: `B` blocked, `D` down, `O` ok, `U` can't call it, `X` unusable, then
confidence, mechanism chips, and a rationale field. Commit advances.

**Tunnel adjudication: not implemented.** The queue and the Request/Observation/
Control panel layout above assume a web measurement (DNS/TCP/TLS/HTTP,
control diffed against `obs_web_ctrl`). A tunnel row has no control panel to
diff against (ontology.md §5.1's cross-network comparison is not a
side-by-side view); the Observation panel would need to show phase reached
and the sibling-endpoint comparison instead. `tunnel.*` mechanism chips stay
greyed out (ontology.md §12.3 marks the nodes `[reserved]`) until this lands.
**Trigger:** [implementation-plan.md](implementation-plan.md) §3.15 landing,
which is also what `screen_tunnel` (§1.4 above) is gated on.

### 3.2 The event editor: implemented

[event-labeler.html](event-labeler.html). A form over the event schema, four
Expand Down
Loading
Loading