-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmethodology.html
More file actions
326 lines (293 loc) · 32.6 KB
/
Copy pathmethodology.html
File metadata and controls
326 lines (293 loc) · 32.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Metodologia di scoperta MX — Osservatorio Nazionale Sovranità Digitale · Sovranità digitale della posta della PA</title>
<meta name="description" content="Metodologia di scoperta del provider email reale della PA italiana via analisi DNS: record MX, SPF, CNAME, DKIM e gateway look-through. MxMap Italia.">
<meta name="keywords" content="MxMap Italia, MXMap Italia, mxmap.it, sovranità digitale, posta elettronica PA, CLOUD Act, IndicePA, provider email pubblica amministrazione">
<meta name="application-name" content="Osservatorio Nazionale Sovranità Digitale">
<meta name="robots" content="index, follow, max-image-preview:large">
<link rel="canonical" href="https://mxmap.it/methodology.html">
<link rel="icon" type="image/svg+xml" href="brand/favicon.svg">
<link rel="apple-touch-icon" href="brand/apple-touch-icon.png">
<meta name="theme-color" content="#0066CC">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Osservatorio Nazionale Sovranità Digitale">
<meta property="og:title" content="MxMap.it — Metodologia di scoperta del provider email">
<meta property="og:image" content="https://mxmap.it/brand/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/png">
<meta name="twitter:image" content="https://mxmap.it/brand/og-image.png">
<meta name="twitter:card" content="summary_large_image">
<style>
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:900px;margin:0 auto;padding:32px 24px;line-height:1.6;color:#222}
h1{font-size:26px;border-bottom:2px solid #009246;padding-bottom:8px}
h2{font-size:20px;margin-top:36px;color:#005a2a;border-bottom:1px solid #ddd;padding-bottom:4px}
h3{font-size:16px;margin-top:24px;color:#444}
.method-card{border:1px solid #ddd;border-left:4px solid #009246;border-radius:6px;padding:16px 20px;margin:18px 0;background:#fafafa}
.method-card h3{margin-top:0;color:#005a2a}
.tag{display:inline-block;background:#e8f5ee;color:#005a2a;padding:2px 8px;border-radius:3px;font-family:ui-monospace,Menlo,monospace;font-size:12px;font-weight:600}
.gate{background:#fff8e1;border-left:3px solid #ffa000;padding:8px 12px;margin:8px 0;font-size:14px}
.fail{background:#fef0f0;border-left:3px solid #d42e2e;padding:8px 12px;margin:8px 0;font-size:14px}
.example{background:#f0f7ff;border-left:3px solid #1976d2;padding:8px 12px;margin:8px 0;font-size:13px;font-family:ui-monospace,Menlo,monospace}
table.stats{border-collapse:collapse;width:100%;max-width:520px;margin:12px 0;font-size:14px}
table.stats th,table.stats td{padding:6px 12px;border-bottom:1px solid #eee;text-align:left}
table.stats th{background:#f4f4f4;font-weight:600}
table.stats td.n{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}
nav.toc{background:#f4f8f6;border-radius:6px;padding:14px 20px;margin:20px 0}
nav.toc ul{margin:0;padding-left:20px}
nav.toc a{color:#005a2a;text-decoration:none}
nav.toc a:hover{text-decoration:underline}
.intro{background:#fafafa;border:1px solid #ddd;border-radius:6px;padding:16px 20px;margin:18px 0}
code{background:#f4f4f4;padding:1px 6px;border-radius:3px;font-size:13px}
.principle{background:#fff8e1;border:1px solid #ffd54f;border-radius:6px;padding:16px 20px;margin:24px 0}
.principle strong{color:#bf8800}
</style>
</head>
<body>
<p style="font-size:13px"><a href="index.html">← Torna alla mappa</a></p>
<h1>Metodologia di scoperta MX</h1>
<p style="font-size:13px;color:#666;margin-top:-8px">Osservatorio sulla Sovranità Digitale della Posta Elettronica della Pubblica Amministrazione Italiana</p>
<p>Come abbiamo determinato chi gestisce la posta di ciascun ente pubblico italiano (PEC esclusa).</p>
<div class="principle">
<strong>Principio guida.</strong> Meglio un ente classificato come <em>non risolto</em> che un ente classificato in modo fuorviante.
Nessuna euristica permissiva: ogni metodo di scoperta segue regole esplicite e verificabili, validate dal modulo
<code>is_legit_email_domain</code>. Quando una regola non si attiva, l'ente resta <strong>unknown</strong> —
mai assegnato a un MX "plausibile ma non provato".
</div>
<div class="principle">
<strong>Limite della fonte (dipendenza core).</strong> IndicePA è l'anagrafe ufficiale degli enti,
ma <strong>non è una base dati pulita da cui inferire in modo immediato e senza rielaborazioni
il dominio di posta "normale" (non-PEC) di ciascun ente</strong>: i domini email vi sono spesso
incoerenti, incompleti o assenti. L'intera pipeline qui descritta esiste proprio per
<em>rielaborare</em> IndicePA (estrazione, correzione, validazione, arricchimento) — i nostri dati
<strong>non sono una lettura diretta</strong> della fonte. La bonifica continua di IndicePA è una
<strong>dipendenza funzionale core</strong>, tracciata come progetto dedicato:
<a href="https://github.com/mxmap-it/mxmap.it/issues/2" target="_blank" rel="noopener">mxmap.it#2 —
Software per un IndicePA ben manutenuto e bonificato</a> (misura autonoma della qualità del dato +
cicli di segnalazione, anche via PEC, verso enti e AgID).
</div>
<nav class="toc">
<strong>Indice</strong>
<ul>
<li><a href="#pipeline">Pipeline di scoperta — panoramica</a></li>
<li><a href="#validator">Validatore <code>is_legit_email_domain</code></a></li>
<li><a href="#methods">Metodi di scoperta (taxonomia)</a></li>
<li><a href="#stats">Statistiche di scoperta — distribuzione</a></li>
<li><a href="#failure">Cosa significa "non risolto"</a></li>
</ul>
</nav>
<h2 id="pipeline">Pipeline di scoperta — panoramica</h2>
<div class="intro">
<p>Ogni ente attraversa una catena di tentativi. Il primo che produce un MX accettato dal validatore vince:</p>
<ol>
<li><strong>Seed-time</strong> (<code>fetch_indicepa.py</code>): si parte dal campo <code>Sito_istituzionale</code> di IndicePA, con sei tier di correzione automatica (override manuale, enrichment LLM curato, enrichment PEC-only Wikidata, AOO/UO Tier-6).</li>
<li><strong>Preprocess</strong> (<code>preprocess.py</code>): risoluzione DNS (MX/SPF/DKIM/CNAME/ASN/tenant) sul dominio risultante.</li>
<li><strong>Recover</strong> (<code>recover_it_unknowns.py</code>): per ogni ente ancora <code>unknown</code> con <code>domain_fallbacks</code> (mail non-PEC del record IndicePA), si tentano i fallback uno a uno, gated da <code>is_legit_email_domain</code>.</li>
<li><strong>Postprocess</strong> (<code>postprocess.py</code>): banner SMTP + scraping del sito (gated).</li>
<li><strong>Finalize</strong> (<code>finalize_it_unknowns.py</code>): cinque strategie sugli ultimi unknowns — Tier-6 AOO/UO, PEC pubblica, Wikidata P856, scraping homepage, ricerca DuckDuckGo + scraping (tutte gated).</li>
</ol>
<p>Ogni stage scrive il campo <code>mx_discovery_method</code> con la tag canonica del metodo applicato. Il tag viene mostrato nel popup dell'ente con tooltip esplicativo e link a questa pagina.</p>
</div>
<h2 id="validator">Validatore <code>is_legit_email_domain</code></h2>
<p>Nucleo della pipeline. Per ogni dominio candidato proveniente da scraping, fallback IndicePA o ricerca web, decide se è legittimamente associato all'ente. <strong>Default: REJECT.</strong> Accetta solo se scatta una di queste regole esplicite:</p>
<ol>
<li><strong>Match esatto</strong> — il dominio candidato è identico al dominio seed dell'ente.</li>
<li><strong>Override manuale</strong> — coppia (codice_ipa → dominio) hand-curata.</li>
<li><strong>PA-shared NAZIONALE</strong> — il candidato è un dominio di infrastruttura PA cross-territoriale (<code>garr.it</code>, <code>sogei.it</code>, consorzi ASMEL); accettato per qualsiasi ente.</li>
<li><strong>PA-shared REGIONALE</strong> — il candidato è una piattaforma di una specifica regione (<code>lepida.it</code> = Emilia-Romagna, <code>regione.vda.it</code> = Valle d'Aosta, <code>ruparpiemonte.it</code> = Piemonte, ecc.). Accettato solo se: (a) l'ente è una PA locale (presenza di marker <code>comune.</code>, <code>provincia.</code>, codice di provincia, ecc.); (b) <em>e</em> l'ente è geograficamente in quella regione, verificato tramite mappatura province (110 codici) + capoluoghi (107 nomi). <strong>Rigetto automatico</strong> per ministeri/PA centrali (dominio <code>*.gov.it</code>).</li>
<li><strong>Relazione di sottodominio</strong> — il candidato è ancestor o discendente del dominio seed.</li>
<li><strong>Intersezione di label significativi</strong> — dopo aver rimosso TLD comuni, codici provincia e prefissi strutturali (<code>comune.</code>, <code>mail.</code>, <code>www.</code>, ecc.), le label residue dei due domini condividono almeno un elemento (≥3 caratteri).</li>
<li><strong>Fuzzy match Damerau-Levenshtein ≤ 1</strong> (rule 6.5) — esiste una coppia di label significativi (uno per parte, entrambi di lunghezza ≥ 6 caratteri) la cui distanza di edit è al massimo 1. Cattura typo singoli reali del dataset come <code>consorfarm.it</code> ↔ <code>consofarm.it</code>, <code>consorziolagodibracciano.it</code> ↔ <code>consorziolagodibraciano.it</code>, hyphenation come <code>aslroma1.it</code> ↔ <code>asl-roma1.it</code>. La soglia DL=1 + min-length 6 esclude false positive su parole brevi (es. <em>roma</em>/<em>noma</em>).</li>
<li><strong>Label concatenation</strong> (rule 6.6) — un singolo label del candidato (≥ 5 caratteri) contiene come substring 2 o più label dell'ente (ciascuno ≥ 3 caratteri), con copertura non-sovrapposta ≥ 80% del label candidato. Cattura il pattern molto comune dell'IndicePA dove l'ente è registrato come <code>{città}.aci.it</code> ma il sito reale è <code>aci{città}.it</code>: ad esempio <code>aciarezzo.it</code> ↔ <code>arezzo.aci.it</code> dove <em>aciarezzo</em> = <em>arezzo</em> + <em>aci</em> (copertura 100%). Recupera ~16 enti del cluster ACI provinciali + ordini professionali.</li>
<li><strong>Rigetto PEC</strong> — domini di provider PEC (<code>legalmail.it</code>, <code>arubapec.it</code>, <code>postecert.it</code>, ecc.) sono sempre rigettati come base di classificazione MX (per design: la PEC non è considerata posta "operativa" per scopo di sovranità).</li>
</ol>
<p>Codice: <a href="https://github.com/mxmap-it/mxmap.it/blob/main/src/mail_sovereignty/scrape_validator.py" target="_blank" rel="noopener">src/mail_sovereignty/scrape_validator.py</a>. Test: <a href="https://github.com/mxmap-it/mxmap.it/blob/main/scripts/_test_scrape_validator.py" target="_blank" rel="noopener">scripts/_test_scrape_validator.py</a>.</p>
<h2 id="methods">Metodi di scoperta — taxonomia completa</h2>
<p>Ogni voce mostra: la tag stabile (citabile come anchor di questa pagina), una descrizione estesa, la regola formale che la attiva, le sorgenti di evidenza, e il comportamento in caso di fallimento.</p>
<div class="method-card" id="method-seed_primary_mx">
<h3>1. <span class="tag">seed_primary_mx</span> — Dominio IndicePA (diretto)</h3>
<p>Il dominio dichiarato come <code>Sito_istituzionale</code> nel record IndicePA dell'ente ha record MX validi. Nessuna correzione, nessun recupero. È il caso ideale e il più frequente per i Comuni con presenza digitale matura.</p>
<div class="gate"><strong>Regola:</strong> <code>lookup_mx(seed.domain)</code> restituisce almeno un MX.</div>
<div class="example">Esempio: <code>comune.milano.it</code> → <code>p-milano-mx-001.it-milano.local</code> (su infrastruttura proprietaria).</div>
</div>
<div class="method-card" id="method-domain_guess">
<h3>2. <span class="tag">domain_guess</span> — Dominio dedotto dal nome</h3>
<p>L'ente non ha dominio in IndicePA. Il sistema genera candidati dal nome (rimozione diacritici, prefissi <code>comune-</code>/<code>provincia-</code>, TLD nazionale <code>.it</code>) e accetta il primo con MX risolvibile.</p>
<div class="gate"><strong>Regola:</strong> generatore deterministico in <code>preprocess.guess_domains()</code> + verifica MX.</div>
<div class="fail"><strong>Failure:</strong> nessun candidato genera MX → entry passa allo stadio successivo.</div>
</div>
<div class="method-card" id="method-manual_override">
<h3>3. <span class="tag">manual_override</span> — Override manuale</h3>
<p>Dominio corretto a mano dai mantenitori del dataset. Usato per fixare typo IndicePA (<code>castefranco</code> → <code>castelfranco</code>), domini <code>*.gov.it</code> defunti migrati a <code>comune.{nome}.{prov}.it</code>, o casi che la pipeline automatica non riesce a recuperare correttamente.</p>
<div class="gate"><strong>Regola:</strong> presenza in <code>IT_MANUAL_DOMAIN_OVERRIDES</code> (dict <code>codice_ipa → dominio</code>) in <code>scripts/fetch_indicepa.py</code>. Ogni voce ha un commento di giustificazione.</div>
<p style="font-size:13px;color:#666">Numero limitato (≪100). Crescita lenta, una voce per bug confermato.</p>
</div>
<div class="method-card" id="method-manual_llm_enrichment">
<h3>4. <span class="tag">manual_llm_enrichment</span> — Curatela LLM (revisione umana)</h3>
<p>Per gli enti che IndicePA segnala con sola PEC e dove né Wikidata né Wikipedia hanno informazioni, generiamo un prompt strutturato (lista di codici IPA + nomi ufficiali) e lo sottomettiamo a una sessione LLM. Le risposte vengono validate a mano e committate in <code>data/manual_llm_enrichment.json</code>.</p>
<div class="gate"><strong>Regola:</strong> presenza in <code>data/manual_llm_enrichment.json</code> + verifica sintattica del hostname + MX risolvibile a runtime.</div>
<p style="font-size:13px;color:#666">Generazione non riproducibile (LLM-mediata), consumo deterministico. Vedi <code>scripts/generate_llm_enrichment_prompt.py</code>.</p>
</div>
<div class="method-card" id="method-pec_only_enrichment">
<h3>5. <span class="tag">pec_only_enrichment</span> — Enrichment PEC-only (Wikidata + Wikipedia)</h3>
<p>Quando l'ente IndicePA espone solo indirizzi PEC, lo script <code>enrich_pec_only.py</code> tenta automaticamente di recuperare il dominio istituzionale tramite proprietà P856 di Wikidata indicizzata sul codice ISTAT o sul nome, con fallback al titolo Wikipedia. La verifica MX è obbligatoria prima di accettare il dominio.</p>
<div class="gate"><strong>Regola:</strong> Wikidata P856 valido → hostname normalizzato → MX risolvibile. In assenza di MX la voce è scartata, non promossa a fallback.</div>
</div>
<div class="method-card" id="method-aoo_uo_tier6">
<h3>6. <span class="tag">aoo_uo_tier6</span> — AOO/UO IndicePA (Tier-6)</h3>
<p>IndicePA pubblica oltre al dataset <code>enti</code> anche due dataset ausiliari: <em>Aree Organizzative Omogenee</em> (AOO) e <em>Unità Organizzative</em> (UO). Questi record contengono email non-PEC dei responsabili (<code>mail_resp</code>, <code>mail1..3</code> con <code>tipo_mail*</code> ≠ Pec). Per ministeri e PA centrali è spesso l'unica fonte di domini reali (il record principale ha solo PEC).</p>
<div class="gate"><strong>Regola:</strong> per ogni codice IPA, raccogliamo tutti i domini non-PEC dalle AOO+UO collegate; <strong>ogni</strong> dominio passa per <code>is_legit_email_domain(dominio, seed.domain, codice_ipa=...)</code>; vengono accettati solo i superstiti. I rigetti vengono loggati per audit (vedi <code>data/indicepa_extended_emails.json</code> → <code>filtered_out</code>).</div>
<div class="example">Caso <code>m_it</code> (Min. Interno): 283 record AOO. Domini scoperti: <code>interno.it</code> ✓, <code>vigilfuoco.it</code> ✗ (rigettato — ente separato), <code>regione.vda.it</code> ✗ (rigettato — piattaforma regionale fuori scope per un ministero nazionale).</div>
</div>
<div class="method-card" id="method-domain_fallback">
<h3>7. <span class="tag">domain_fallback</span> — Mail IndicePA non-PEC (fallback ente)</h3>
<p>Il record <code>enti</code> stesso espone fino a 5 campi <code>Mail*</code>. Il fetch ne estrae i domini non-PEC distinti dal primario e li salva in <code>seed.domain_fallbacks</code>. Se il dominio primario non risolve, <code>recover_it_unknowns.py</code> prova ciascun fallback in ordine.</p>
<div class="gate"><strong>Regola:</strong> per ogni fallback <code>fb</code>, <code>is_legit_email_domain(fb, seed.domain, codice_ipa=...)</code> deve restituire True. Solo allora si esegue la classificazione DNS. Rigetti loggati in <code>data/reports/recover_it_unknowns_rejections.json</code>.</div>
<div class="fail"><strong>Failure:</strong> il fallback più frequente che fallisce questa regola è <code>istruzione.it</code> per scuole — gestito tramite la regola specifica <code>istruzione_miur_tenant</code> (sotto).</div>
</div>
<div class="method-card" id="method-istruzione_miur_tenant">
<h3>8. <span class="tag">istruzione_miur_tenant</span> — Tenant centrale MIM (istruzione.it)</h3>
<p>Le scuole statali italiane (categoria IndicePA <code>L33</code>) non hanno un proprio tenant Microsoft 365: la posta dei dirigenti scolastici, segreterie e docenti abilitati è ospitata sul tenant centrale del Ministero dell'Istruzione e del Merito (MIM) all'indirizzo <code>istruzione.it</code>. Questa dipendenza è una realtà istituzionale, non una misattribuzione — ma è importante segnalarla distintamente da una scuola che gestisce un proprio tenant.</p>
<div class="gate"><strong>Regole (TUTTE E TRE obbligatorie):</strong>
<ul>
<li><strong>R1:</strong> <code>seed.ipa_codice_categoria == "L33"</code> (nessuna euristica su codice_ipa).</li>
<li><strong>R2:</strong> il fallback proposto è esattamente <code>istruzione.it</code>.</li>
<li><strong>R3:</strong> la risoluzione DKIM su <code>istruzione.it</code> deve contenere <code>miuristruzione.onmicrosoft.com</code> (prova crittografica del tenant MIM).</li>
</ul>
</div>
<div class="fail"><strong>Failure di anche una sola regola:</strong> entry rigettata, marcata come <code>miur_tenant_unverified</code> nel report, ente resta <code>unknown</code>. Nessuna assunzione "questo sembra una scuola, sarà MIM".</div>
<div class="example">Caso <code>iccaroberlingieri.edu.it</code> (I.C. Caro-Berlingieri): categoria L33 ✓, fallback <code>istruzione.it</code> ✓, DKIM <code>selector1-istruzione-it._domainkey.miuristruzione.onmicrosoft.com</code> ✓ → tag <code>istruzione_miur_tenant</code>.</div>
</div>
<div class="method-card" id="method-wikidata_p856">
<h3>9. <span class="tag">wikidata_p856</span> — Wikidata sito ufficiale</h3>
<p>Per i comuni che fallirebbero tutti i tentativi precedenti, una query SPARQL batch su Wikidata recupera la proprietà <code>P856</code> (sito ufficiale) indicizzata sul codice ISTAT 6-cifre del comune. Cattura tipici typo IndicePA e migrazioni <code>*.gov.it</code> → <code>comune.{nome}.{prov}.it</code>.</p>
<div class="gate"><strong>Regola:</strong> <code>SELECT ?web WHERE { ?city wdt:P3829 "{istat6}"; wdt:P856 ?web }</code> + verifica MX. Solo se il dominio Wikidata <em>differisce</em> dal primario IndicePA viene considerato (idempotenza).</div>
</div>
<div class="method-card" id="method-public_pec_inference">
<h3>10. <span class="tag">public_pec_inference</span> — Inferenza da PEC pubblica</h3>
<p>Alcuni enti utilizzano come PEC ufficiale infrastrutture pubblicamente operate (non provider commerciali): <code>cert.ruparpiemonte.it</code> (CSI Piemonte, ICT regionale sovrano), <code>asmepec.it</code> (consorzio comuni ASMEL). Quando il record IndicePA mostra solo PEC di questo tipo, classifichiamo l'ente come <code>regional-public</code> — anche senza un MX proprio risolto.</p>
<div class="gate"><strong>Regola:</strong> presenza di Mail* su uno dei provider PEC pubblici whitelistati, e <em>assenza</em> di MX validi sul dominio primario.</div>
</div>
<div class="method-card" id="method-homepage_scrape">
<h3>11. <span class="tag">homepage_scrape</span> — Scraping sito istituzionale</h3>
<p>Quando i metodi precedenti falliscono, scarichiamo l'homepage (e alcune sotto-pagine: <code>/contatti</code>, <code>/amministrazione-trasparente</code>, ecc.) ed estraiamo gli indirizzi email visibili (incluso il decifrato dei mailto TYPO3 cifrati con Caesar). Ogni dominio estratto passa per il validatore.</p>
<div class="gate"><strong>Regola:</strong> per ogni email scrapata, <code>is_legit_email_domain(email_host, ente_domain)</code> deve restituire True. Se rigettata, la coppia (host, ragione) viene loggata in <code>data/reports/cleanup_invalid_mx_attributions.json</code>.</div>
<div class="fail"><strong>Failure storico (ora bloccato):</strong> molti siti comunali pubblicano in homepage email di terze parti (eventi, partner, scuole ospitate); l'attribuzione automatica del loro MX al comune era la causa del bug "Min. Interno → MX Comune di Roma" che ha motivato l'introduzione del validatore.</div>
</div>
<div class="method-card" id="method-search_engine_scrape">
<h3>12. <span class="tag">search_engine_scrape</span> — Ricerca DDG + scraping</h3>
<p>Ultima risorsa per enti con dominio primario defunto e nessuna voce Wikidata. Query DuckDuckGo HTML (no JS, no rate limit aggressivo) per il nome dell'ente; candidati URL filtrati per somiglianza al nome e dominio plausibile; scraping dell'homepage del candidato; validazione is_legit del dominio mail estratto.</p>
<div class="gate"><strong>Regola:</strong> stessa di <code>homepage_scrape</code> (validatore is_legit obbligatorio).</div>
</div>
<div class="method-card" id="method-smtp_banner">
<h3>13. <span class="tag">smtp_banner</span> — Banner SMTP</h3>
<p>Per gli enti già classificati come <code>independent</code> (MX proprio, nessun match con i grandi provider), apriamo una connessione SMTP al primo MX e leggiamo il banner EHLO. Stringhe come <em>"Postfix (Ubuntu)"</em>, <em>"Exchange 2019"</em>, <em>"Plesk SMTP server"</em> arricchiscono il record con il software identificato.</p>
<div class="gate"><strong>Regola:</strong> match keyword del banner contro <code>SMTP_BANNER_KEYWORDS</code> in <code>constants.py</code>. Concorrenza limitata (5) per non sembrare scanner.</div>
</div>
<div class="method-card" id="method-unknown">
<h3>14. <span class="tag">unknown</span> — Non risolto</h3>
<p>Nessuno dei 13 metodi sopra ha prodotto un MX accettabile. L'ente resta <code>unknown</code> e <em>non viene assegnato a un provider</em>. Le motivazioni di rigetto del validatore sono comunque archiviate per consentire revisione manuale futura.</p>
<div class="gate"><strong>Promemoria del principio:</strong> non classifichiamo mai per riempire la riga. Il numero di <code>unknown</code> è una metrica di onestà del dataset, non un fallimento.</div>
</div>
<h2 id="stats">Statistiche di scoperta — distribuzione corrente</h2>
<p id="stats-loading"><em>Caricamento statistiche…</em></p>
<table class="stats" id="stats-table" style="display:none">
<thead><tr><th>Metodo</th><th>Enti</th><th>% sul totale IT</th></tr></thead>
<tbody></tbody>
</table>
<p style="font-size:13px;color:#666;margin-top:14px">Aggiornato automaticamente a ogni build del frontend (vedi <code>scripts/build_frontend.py</code>).</p>
<h2 id="failure">Cosa significa "non risolto"</h2>
<p>Un ente classificato come <code>unknown</code> può esserlo per varie ragioni — tutte riconducibili al principio "meglio onesto che fuorviante":</p>
<ul>
<li><strong>Dominio defunto.</strong> Il sito IndicePA non risponde più; non esiste P856 Wikidata; la ricerca web non trova un sostituto plausibile.</li>
<li><strong>Nessun fallback non-PEC.</strong> IndicePA espone solo PEC, Wikidata non ha P856, nessun record AOO/UO collegato.</li>
<li><strong>Tutti i candidati cross-tenant.</strong> Lo scraping ha trovato email, ma <em>tutte</em> appartenevano ad altri enti (errore comune nei siti comunali con riferimenti a scuole/enti convenzionati). Il validatore le ha respinte.</li>
<li><strong>Ente recentemente costituito o estinto.</strong> Soppressioni, fusioni di comuni, ridenominazioni post-riforma: latenza tra IndicePA e realtà.</li>
</ul>
<p>Le regole 6.5 (fuzzy Damerau-Levenshtein ≤ 1) e 6.6 (label concatenation) sono già attive — catturano typo singoli e pattern del tipo <code>aciarezzo.it</code> ↔ <code>arezzo.aci.it</code>. Restano <code>unknown</code> i casi che richiedono override manuale (mismatch semantici non risolvibili automaticamente senza assunzioni rischiose).</p>
<h2 id="confidence">Affidabilità della classificazione (confidence)</h2>
<p>Ogni ente porta un punteggio di <strong>affidabilità</strong> della classificazione (0–100%): quanto è solida l'evidenza DNS che sostiene il provider attribuito. Il modello è un <strong>port fedele del classificatore <a href="https://github.com/mxmap/esorics2026" target="_blank" rel="noopener">ESORICS 2026</a></strong> di David Huser e colleghi (citazione completa nei <a href="#riferimenti">Riferimenti</a>), adattato alla nostra architettura: da noi il provider è già deciso per keyword sui record DNS, qui calcoliamo <em>quanto fidarsene</em> e la <em>giurisdizione</em> dell'infrastruttura di posta.</p>
<p>Il punteggio nasce da un set di <strong>7 regole</strong>: si scorre dall'evidenza più forte alla più debole e si prende la prima che combacia. Solo i tre segnali di <em>routing/autorizzazione</em> (MX, SPF, DKIM) determinano la regola di base; gli altri (tenant MS365, autodiscover) contribuiscono solo come <em>boost</em> (+0,02 ciascuno, cap a 1,0). Razionale upstream: avere un tenant Microsoft 365 (es. per Teams) non prova che la <em>posta</em> sia ospitata lì.</p>
<table class="stats">
<thead><tr><th>regola</th><th>segnali richiesti</th><th>gateway</th><th>base</th></tr></thead>
<tbody>
<tr><td><code>mx_spf</code></td><td>MX + SPF</td><td>—</td><td>0,90</td></tr>
<tr><td><code>mx_only</code></td><td>MX</td><td>—</td><td>0,80</td></tr>
<tr><td><code>spf_gw</code></td><td>SPF</td><td>sì</td><td>0,70</td></tr>
<tr><td><code>dkim_gw</code></td><td>DKIM</td><td>sì</td><td>0,65</td></tr>
<tr><td><code>dkim_spf</code></td><td>DKIM + SPF</td><td>—</td><td>0,60</td></tr>
<tr><td><code>spf_only</code></td><td>SPF</td><td>—</td><td>0,50</td></tr>
<tr><td><code>fallback</code></td><td>— (catch-all)</td><td>—</td><td>0,40</td></tr>
</tbody>
</table>
<h3 style="margin-top:28px">Sovranità: domestic / foreign / mixed</h3>
<p>Per gli enti senza un backend cloud riconosciuto (posta self-hosted o provider minore) non basta dire "indipendente": li qualifichiamo per <strong>giurisdizione dell'IP del server MX</strong> (paese dell'ASN via Team Cymru). La confidenza usa tabelle dedicate a base piatta (nessun boost, perché i segnali cloud sono irrilevanti alla classificazione per paese):</p>
<table class="stats">
<thead><tr><th>caso</th><th>🇮🇹 domestico (IT)</th><th>🌍 estero</th></tr></thead>
<tbody>
<tr><td>MX + SPF</td><td><code>dom_mx_spf</code> 0,80</td><td><code>frgn_mx_spf</code> 0,60</td></tr>
<tr><td>solo MX</td><td><code>dom_mx_only</code> 0,70</td><td><code>frgn_mx_only</code> 0,50</td></tr>
<tr><td>solo evidenza secondaria</td><td><code>dom_secondary</code> 0,20</td><td><code>frgn_secondary</code> 0,10</td></tr>
<tr><td>niente</td><td><code>dom_none</code> 0,00</td><td><code>frgn_none</code> 0,00</td></tr>
</tbody>
</table>
<p>L'<strong>etichetta di sovranità</strong> nel popup (🇮🇹 MX sovrano / 🌍 MX estero / misto) deriva da <code>mx_countries</code>: <em>domestic</em> se tutti gli MX sono in IT, <em>foreign</em> se nessuno, <em>mixed</em> se alcuni.</p>
<div class="method-card" id="method-domestic_mx_override">
<h3><span class="tag">domestic MX override</span> — Teams ≠ posta</h3>
<p>Caso insidioso: un ente risulta <code>microsoft</code>/<code>google</code> per via del <em>tenant</em> o del <em>DKIM</em>, ma il suo record MX punta a un server <strong>self-hosted in Italia</strong> (non a <code>*.protection.outlook.com</code>). Significa che il cloud serve Teams/SharePoint, mentre la <strong>posta in entrata</strong> resta sovrana. In questi casi riclassifichiamo l'ente per giurisdizione invece che come cloud estero.</p>
<div class="gate"><strong>Regola (come ESORICS):</strong> scatta solo se <em>non</em> c'è gateway <em>e</em> l'MX non combacia coi pattern cloud del provider. Dietro un gateway antispam il verdetto cloud viene dal look-through (il DKIM prova il backend reale) ed è legittimo → nessun override.</div>
</div>
<h3 style="margin-top:28px">Anticipazione: validazione via bounce</h3>
<p>La confidenza è anche una <strong>mappa di dove verificare</strong>. Gli enti a confidenza bassa (< 0,60) pur essendo classificati sono i candidati prioritari per la futura validazione attiva via <em>bounce-probing</em> (invio a indirizzo inesistente + analisi del messaggio di ritorno NDR, che rivela il MTA reale del backend). Il <strong>report completo</strong> — distribuzione aggregata, confidenza per provider, regole attivate, sovranità e lista dei candidati bounce — è rigenerato a ogni run:</p>
<div class="gate"><strong>Report:</strong> <a href="https://github.com/mxmap-it/mxmap.it/blob/main/data/reports/confidence_report.md" target="_blank" rel="noopener">confidence_report.md</a> (leggibile) · <a href="data/reports/confidence_report.json" target="_blank" rel="noopener">confidence_report.json</a> (machine-readable). Codice: <code>src/mail_sovereignty/classification_confidence.py</code> (port + test di fedeltà), <code>scripts/compute_confidence.py</code>, <code>scripts/report_confidence.py</code>.</div>
<h2 id="riferimenti">Riferimenti</h2>
<p>Questo osservatorio è un fork del lavoro di <strong>David Huser</strong> e colleghi, a cui va il credito per il metodo di classificazione e per il modello di confidence qui adottato.</p>
<ul style="font-size:14px;line-height:1.7">
<li><strong>Paper di ricerca</strong> (modello confidence + sovranità) — David Huser, Mario Bischof, Ronald Petrlic, Alexej Ochsner, <em>«Email Provider Dependencies and Email Security in Municipalities Across Germany, Austria, and Switzerland»</em>, ESORICS 2026. Codice e dati: <a href="https://github.com/mxmap/esorics2026" target="_blank" rel="noopener">github.com/mxmap/esorics2026</a> · sito: <a href="https://mxmap.github.io/esorics2026/" target="_blank" rel="noopener">mxmap.github.io/esorics2026</a>. Il nostro classificatore (le 7 regole, il modello domestic/foreign, il domestic-MX-override) è un port fedele del loro modulo <code>provider_classification</code>.</li>
<li><strong>Progetto originale</strong> — <strong>David Huser</strong>, <em>mxmap.ch</em>: mappe interattive di dove i comuni svizzeri ospitano la posta e quanto dipendono dagli hyperscaler statunitensi. <a href="https://mxmap.ch" target="_blank" rel="noopener">mxmap.ch</a> · <a href="https://github.com/davidhuser/mxmap" target="_blank" rel="noopener">github.com/davidhuser/mxmap</a> (MIT). <em>mxmap.it</em> ne è un fork ispirato, adattato alla Pubblica Amministrazione italiana.</li>
</ul>
<hr style="margin-top:48px;border:none;border-top:1px solid #ddd">
<p style="font-size:12px;color:#888;text-align:center">
Osservatorio Sovranità Digitale PA — <a href="https://github.com/mxmap-it/mxmap.it" target="_blank" rel="noopener">codice e dati su GitHub</a> · Licenza dati: ODbL-1.0 + CC-BY-4.0 · Codice: MIT · Metodo: <a href="https://github.com/mxmap/esorics2026" target="_blank" rel="noopener">mxmap/ESORICS 2026</a>
</p>
<script>
// Carica e visualizza le statistiche di scoperta MX
fetch('data/reports/mx_discovery_stats.json')
.then(r => r.ok ? r.json() : null)
.then(stats => {
if (!stats || !stats.by_method) return;
const total = stats.total || 0;
const labels = {
seed_primary_mx: 'Dominio IndicePA (diretto)',
domain_guess: 'Dominio dedotto dal nome',
manual_override: 'Override manuale',
manual_llm_enrichment: 'Curatela LLM',
pec_only_enrichment: 'Enrichment PEC-only (Wikidata)',
aoo_uo_tier6: 'AOO/UO IndicePA (Tier-6)',
domain_fallback: 'Mail IndicePA non-PEC (fallback)',
istruzione_miur_tenant: 'Tenant centrale MIM',
wikidata_p856: 'Wikidata P856',
public_pec_inference: 'Inferenza da PEC pubblica',
homepage_scrape: 'Scraping sito',
search_engine_scrape: 'Ricerca + scraping',
smtp_banner: 'Banner SMTP',
unknown: 'Non risolto'
};
const tbody = document.querySelector('#stats-table tbody');
const rows = Object.entries(stats.by_method).sort((a, b) => b[1] - a[1]);
for (const [m, n] of rows) {
const tr = document.createElement('tr');
const pct = total ? (100 * n / total).toFixed(1) : '0';
tr.innerHTML = `<td><a href="#method-${m}">${labels[m] || m}</a> <span style="color:#888;font-size:11px;font-family:ui-monospace,Menlo,monospace">${m}</span></td><td class="n">${n.toLocaleString('it')}</td><td class="n">${pct}%</td>`;
tbody.appendChild(tr);
}
document.getElementById('stats-loading').style.display = 'none';
document.getElementById('stats-table').style.display = '';
})
.catch(() => { document.getElementById('stats-loading').innerHTML = '<em>Statistiche non disponibili.</em>'; });
</script>
</body>
</html>