Passa al contenuto principale

Algoritmo di Scoring

"Zenzic non scala i voti. Sicurezza e Governance non sono opzionali."

Il Documentation Quality Score (DQS) di Zenzic è un intero deterministico da 0 a 100 calcolato dai risultati di tutti i check attivi. Dato lo stesso stato del repository, l'algoritmo produce sempre lo stesso punteggio. Non ci sono euristiche di ponderazione, nessuna interpolazione e nessun arrotondamento fino al cast finale dell'intero.


Architettura Generale

La pipeline di scoring ha cinque stadi sequenziali:

1. Security Gate → Finding Z2xx? score = 0, early return.
2. Tabella Penalità → deduzioni per codice, cap per tier.
3. Governance Esc. → amplificazione esponenziale se Z6xx > 10.
4. Gravity Cap → brand score = 0 ⟹ totale ≤ 70.
5. Suppression Debt → sottrai ω_debt dal totale con cap.

Ogni stadio è descritto di seguito con la formula completa.


Stadio 1 — Security Override

Prima di qualsiasi calcolo del punteggio, il motore controlla la presenza di finding Z2xx:

Sfinal=0se cSnc>0S_{\text{final}} = 0 \quad \text{se } \sum_{c \in \mathcal{S}} n_c > 0

dove S={Z201,Z202,Z203,Z204}\mathcal{S} = \{Z201, Z202, Z203, Z204\}.

Si tratta di un early return incondizionale — nessun flag, nessuna opzione di configurazione e nessuna soppressione può bypassarlo. I quattro codici in S\mathcal{S} rappresentano condizioni di fallimento binarie:

CodiceNomeCondizione
Z201CREDENTIALPattern di credenziale rilevato nel documento
Z202PATH_TRAVERSALTentativo di escape di sistema in un link
Z203PATTERN_MATCHCorrispondenza di pattern vietato personalizzato
Z204FORBIDDEN_TERMPrivacy Gate — esposizione di termine riservato

Quando si attiva il Security Override, ScoreReport restituisce security_override=True e security_findings=N (conteggio totale Z2xx). Il flag --strict e la configurazione fail-on-error sono irrilevanti — il gate opera prima di entrambi.

:::danger I Codici di Sicurezza Non Sono Sopprimibili Nessun inline <!-- zenzic:ignore -->, nessun per_file_ignores e nessun excluded_dirs può sopprimere un finding Z2xx. Il finding si attiva comunque. Il punteggio è comunque 0. :::


Stadio 2 — Tabella Penalità e Cap per Tier

Se non sono presenti finding Z2xx, il motore calcola un punteggio per tier.

Matrice dei Pesi Zenzic (5-Tier)

TierCategoriaCodiciPesoCap
Security GateZ2xxscore = 0
StructuralstructuralZ1xx30%30 pts
NavigationnavigationZ3xx, Z4xx25%25 pts
ContentcontentZ5xx20%20 pts
GovernancebrandZ404, Z405, Z406, Z6xx25%25 pts

Formula per Categoria

Per ogni tier ii:

cat_ptsi=max ⁣(0,  wi×100ctieripenaltyc×nc)\text{cat\_pts}_i = \max\!\left(0,\; w_i \times 100 - \sum_{c \in \text{tier}_i} \text{penalty}_c \times n_c\right)

L'invariante del Category Cap garantisce che un singolo tier non possa trascinare il punteggio sotto il suo pavimento. Ad esempio, 1 000 occorrenze di Z505 (1 pt ciascuna) esauriscono il bucket content a −20 pts. Gli 80 pts rimanenti dagli altri tier non vengono toccati.

Punteggio Base

Sbase=i{structural, navigation, content, brand}cat_ptsiS_{\text{base}} = \sum_{i \in \{\text{structural, navigation, content, brand}\}} \text{cat\_pts}_i

Tabella di Riferimento delle Penalità

CodiceNomePenalità / occorrenzaTier
Z101LINK_BROKEN8.0 ptsStructural
Z102ANCHOR_MISSING5.0 ptsStructural
Z103ORPHAN_LINK2.0 ptsStructural
Z104FILE_NOT_FOUND8.0 ptsStructural
Z105ABSOLUTE_PATH2.0 ptsStructural
Z107CIRCULAR_ANCHOR1.0 ptsStructural
Z106CIRCULAR_LINK0.0 ptsInformativo (nessun impatto sul DQS)
Z108EMPTY_LINK_TEXT1.0 ptsStructural
Z111VIRTUAL_ROUTE_BROKEN8.0 ptsStructural
Z113AUTHOR_KEY_COLLISION2.0 ptsStructural
Z301DANGLING_REF4.0 ptsNavigation
Z302DEAD_DEF1.0 ptsNavigation
Z303DUPLICATE_DEF3.0 ptsNavigation
Z402ORPHAN_PAGE4.0 ptsNavigation
Z401MISSING_DIRECTORY_INDEX2.0 ptsNavigation
Z501PLACEHOLDER2.0 ptsContent
Z502SHORT_CONTENT1.0 ptsContent
Z503SNIPPET_ERROR10.0 ptsContent
Z505UNTAGGED_CODE_BLOCK1.0 ptsContent
Z403MISSING_ALT1.0 ptsContent
Z405UNUSED_ASSET3.0 ptsGovernance
Z404CONFIG_ASSET_MISSING3.0 ptsGovernance
Z406NAV_CONTRACT2.0 ptsGovernance
Z601BRAND_OBSOLESCENCE2.0 ptsGovernance

::: note Z106 — Telemetria del Knowledge Graph, non un difetto Z106 è escluso dalla tabella delle penalità per scelta progettuale. Elevarlo a finding con punteggio sottrarrebbe punti al Quality Score, spingendo gli ingegneri a rimuovere link utili per soddisfare il linter — un incentivo perverso che degrada la qualità reale della documentazione. I link circolari in un Knowledge Graph sono dati strutturali, non difetti. Z106 viene emesso come telemetria topologica; ispezionalo con --show-info. :::

:::note Z602 non è incluso nel punteggio Z602 (I18N_PARITY) è un gate di Governance che si attiva come finding autonomo. Non contribuisce a nessun bucket DQS e quindi non ha un valore di penalità nella tabella sopra. :::


Stadio 3 — Governance Escalation

Le violazioni Z6xx rappresentano decadimento del brand e della governance — il tipo che si accumula silenziosamente. Oltre 10 occorrenze, il motore applica un amplificatore esponenziale alle deduzioni del bucket Governance:

deductionbrand=min ⁣(capbrand,  deductionbrand×2nexcess/5)\text{deduction}_{\text{brand}}' = \min\!\left(\text{cap}_{\text{brand}},\; \text{deduction}_{\text{brand}} \times 2^{\lfloor n_{\text{excess}} / 5 \rfloor}\right)

dove nexcess=nZ6xx10n_{\text{excess}} = n_{Z6xx} - 10.

La deduzione è limitata al massimo del tier Governance (25 pts) per evitare overflow. L'effetto pratico: un repository con 20 violazioni Z601 (10 in eccesso → moltiplicatore =22=4= 2^2 = 4) subisce quattro volte il normale impatto sulla governance.


Stadio 4 — Gravity Cap

Se il bucket Governance viene completamente azzerato dalle deduzioni:

Sbase=min ⁣(Sbase,  70)se cat_ptsbrand=0S_{\text{base}} = \min\!\left(S_{\text{base}},\; 70\right) \quad \text{se } \text{cat\_pts}_{\text{brand}} = 0

Motivazione (ADR-031): un insieme di documentazione con violazioni di governance incontrollate — decadimento del brand, asset stantii, contratti di navigazione rotti — non può essere considerato un prodotto di alta qualità, indipendentemente dal grafo dei link. Il Gravity Cap impone questo vincolo strutturalmente.


Stadio 5 — Suppression Debt

Ogni soppressione attiva è un'assunzione consapevole di responsabilità. Il modello flat-cost deduce esattamente 1 punto per soppressione, indipendentemente da quante soppressioni siano presenti. Non esiste una franchigia gratuita.

suppression_cap (default: 30) è una soglia di hard-fail, non un limite di franchigia. Quando suppression_count > suppression_cap, zenzic score termina con codice 1 immediatamente, prima della valutazione della soglia di punteggio. La formula della penaltà è indipendente dal cap:

ωdebt=n\omega_{\text{debt}} = n

dove:

  • nn = soppressioni attive totali (inline zenzic:ignore + voci per_file_ignores)

Il punteggio finale è:

Sfinal=max ⁣(0,  Sbasen)S_{\text{final}} = \max\!\left(0,\; S_{\text{base}} - n\right)

Riferimento Costo Soppressioni

Numero soppressioniCosto per soppressioneNote
ncapn \leq \text{cap}1 pt ciascunaPostura gestita — ogni soppressione ha un costo
n>capn > \text{cap}1 pt ciascunaHard-fail: zenzic score termina con codice 1

:::info Condizione al Contorno — Invariante di Configurazione Poiché ogni soppressione deduce 1 punto, il punteggio massimo raggiungibile per un repository è:

Punteggio Massimo Raggiungibile=100Fs\text{Punteggio Massimo Raggiungibile} = 100 - |F_s|

dove Fs|F_s| è il conteggio totale delle soppressioni attive. Configurare fail_under > 100 - suppression_cap crea una contraddizione matematica: il gate di punteggio si attiva per il debito da soppressioni prima che venga raggiunto il cap di governance. Regola di configurazione sicura:

fail_under100suppression_cap\texttt{fail\_under} \leq 100 - \texttt{suppression\_cap} :::

Architettura Dual-Gate — Vincoli Ortogonali

fail_under e suppression_cap operano come vincoli ortogonali valutati indipendentemente dalla pipeline CI. Un'esecuzione della pipeline fallisce se una delle due condizioni viene soddisfatta:

  • Score gate: score < fail_underzenzic score termina con codice 1
  • Governance cap: |F_s| > suppression_capzenzic score termina con codice 1

Questa architettura dual-gate consente un bounding asimmetrico del debito tecnico. Una politica ibrida come fail_under = 90, suppression_cap = 30 impone: "La qualità complessiva non deve mai scendere sotto 90/100, e indipendentemente dal punteggio, non più di 30 difetti soppressi sono mai tollerati."

:::tip Il Suppression Debt nella CLI Esegui zenzic score per vedere la postura corrente delle soppressioni:

Suppression Audit: 8/30 (inline: 5, per-file: 3)

Esegui zenzic check all --audit per vedere i finding senza applicare soppressioni. :::

Interpretazione dei Risultati

1) Finding Informativi

I finding informativi sono diagnostica non bloccante usata per visibilita e osservabilita.

  • Non riducono il DQS.
  • Non attivano mai il Security Override.
  • In SARIF vengono emessi al livello note.

Esempi tipici includono Z106 (CIRCULAR_LINK) e superfici informative come Z114 o Z906.

2) Audit delle Soppressioni

Il Suppression Audit e telemetria di governance, non un semplice contatore di failure.

  • Riporta il totale delle soppressioni attive (direttive inline + voci per_file_ignores).
  • Esplicita il perimetro di debito tecnico autorizzato.
  • L'impatto sul punteggio si applica a ogni soppressione attiva (flat-cost: 1 pt ciascuna).

3) Semantica Label: [MANAGED DEBT] e [EXTENDED DEBT]

  • [MANAGED DEBT]: sono presenti soppressioni attive e il progetto resta nel profilo cap sovrano (suppression_cap <= 30).
  • [EXTENDED DEBT]: sono presenti soppressioni attive con profilo cap esteso (suppression_cap > 30).

Queste label descrivono la postura di governance. Sono complementari alla matematica del punteggio e aiutano i revisori a tracciare la crescita delle soppressioni nel tempo.

:::warning Breaking Change Le soppressioni non sono allowance-based. Ogni soppressione deduce 1 punto (modello flat-cost), mentre suppression_cap resta una soglia di hard-fail indipendente. I progetti con soppressioni attive vedranno sempre il punteggio massimo raggiungibile ridotto del numero di soppressioni. :::


Formula Completa

Assemblando tutti e cinque gli stadi:

Sfinal={0se cSnc>0(Security Override)max ⁣(0,  Sgravityn)altrimentiS_{\text{final}} = \begin{cases} 0 & \text{se } \sum_{c \in \mathcal{S}} n_c > 0 \quad \text{(Security Override)} \\[6pt] \max\!\left(0,\; S_{\text{gravity}} - n\right) & \text{altrimenti} \end{cases}

dove nn è il conteggio totale delle soppressioni attive e:

Sgravity={min ⁣(Sbase,  70)se cat_ptsbrand=0SbasealtrimentiS_{\text{gravity}} = \begin{cases} \min\!\left(S_{\text{base}},\; 70\right) & \text{se } \text{cat\_pts}_{\text{brand}} = 0 \\ S_{\text{base}} & \text{altrimenti} \end{cases}

Esempio Pratico

Scenario: Un repository ha 2 link rotti (Z101), 3 pagine orfane (Z402), 5 blocchi di codice senza tag (Z505) e 15 violazioni Z601 brand, con 8 soppressioni attive (cap = 30).

Stadio 1 — Security Gate: Nessun finding Z2xx → continua.

Stadio 2 — Tabella Penalità:

TierCapDeduzionecat_pts
Structural302 × 8.0 = 16.014.0
Navigation253 × 4.0 = 12.013.0
Content205 × 1.0 = 5.015.0
Governance2515 × 2.0 = 30.0 → cap a 250.0

Sbase=14+13+15+0=42S_{\text{base}} = 14 + 13 + 15 + 0 = 42

Stadio 3 — Governance Escalation: 15 violazioni Z601 → nexcess=5n_{\text{excess}} = 5 → moltiplicatore =25/5=2.0= 2^{5/5} = 2.0 → deduzione =30×2=60min(60,25)=25= 30 \times 2 = 60 \to \min(60, 25) = 25. Bucket brand = 0.

Stadio 4 — Gravity Cap: cat_ptsbrand=0\text{cat\_pts}_{\text{brand}} = 0Sgravity=min(42,70)=42S_{\text{gravity}} = \min(42, 70) = 42.

Stadio 5 — Suppression Debt: n=8n = 8 soppressioni → flat-cost: ωdebt=8\omega_{\text{debt}} = 8.

Sfinal=max(0,  428)=34S_{\text{final}} = \max(0,\; 42 - 8) = \mathbf{34}


Lettura dell'Output CLI

Eseguendo zenzic score viene visualizzato un Ledger Quality Breakdown che esplicita ogni passo aritmetico: dalle penalità raw per tier al cap applicato, all'aggiustamento del Gravity Cap, al debito da soppressioni e al punteggio finale.

✨ Quality Score: 65/100

╭─ Quality Breakdown ──────────────────────────────────────╮
│ Category Issues Weight Raw Pts Applied Pts │
├──────────────────────────────────────────────────────────┤
│ ✓ structural 0 30% 0 0 │
│ ✓ navigation 0 25% 0 0 │
│ ✗ content 2 20% -4 -4 │
│ ✗ brand 15 25% -30 -25 (CAPPED) │
├──────────────────────────────────────────────────────────┤
│ Σ Subtotal 71 │
╰──────────────────────────────────────────────────────────╯
! Technical Debt (6 suppressions) -6 pts
= Final Quality Score 65 / 100

Guida alle colonne:

ColonnaSignificato
Raw PtsDeduzione post-escalation prima del cap di categoria, mostrata come valore negativo (o 0).
Applied PtsDeduzione effettivamente applicata, limitata al massimo del tier.
(CAPPED)La deduzione raw ha superato il cap del tier ed è stata troncata.
Σ SubtotalSomma di tutti i cat_pts residui prima del Gravity Cap e del Suppression Debt.

Quando scatta il Gravity Cap (bucket Brand = 0), compare una riga extra tra Σ Subtotal e Technical Debt:

│ Σ Subtotal 75 │
╰──────────────────────────────────────────────────────────╯
! Gravity Cap Enforcement (Brand = 0) -5 pts
! Technical Debt (0 suppressions) 0 pts
= Final Quality Score 70 / 100

L'aritmetica è sempre esplicita: Σ Subtotal − Gravity Cap − Suppression Debt = Final Score.


Vedi Anche