Architettura
Questa pagina descrive il design interno di Zenzic per contributor e utenti avanzati che devono capire come funziona lo strumento internamente. Per configurazione e uso, consulta Configuration Reference e Checks Reference.
Integrità Oltre il Codice
Zenzic estende il determinismo dell'analisi statica anche all'infrastruttura di build.
La stessa regola ingegneristica vale a livello repository: se il contesto di esecuzione non è deterministico, i risultati dell'analisi non sono affidabili. Per questo i controlli CI/CD sono parte del contratto architetturale, non un dettaglio operativo secondario.
| Controllo | Ruolo architetturale |
|---|---|
| GitHub Actions pin-nate a SHA | Impedisce il drift dei tag mutabili e blocca il workflow su commit revisionati |
Sync lockfile frozen (uv.lock) | Garantisce grafo dipendenze deterministico in CI e in release build |
| Attestazioni di provenienza build | Fornisce metadati verificabili sull'origine degli artefatti distribuiti |
| Dependabot per le actions | Automatizza il refresh degli SHA preservando il modello di pinning immutabile |
Questo è l'equivalente infrastrutturale dell'analisi statica: vincolare gli input di esecuzione, preservare la riproducibilità e rendere auditabile l'evidenza di sicurezza per contributor e utenti finali.
Pipeline a Tre Fasi
Il motore principale di analisi opera come una Pipeline a Tre Fasi sull'insieme dei file di documentazione. Ogni fase ha una responsabilità distinta e viene eseguita in ordine deterministico.
Passata 1 — Raccolta e Scansione
La prima passata attraversa tutti i file .md e .mdx sotto docs/ ed esegue tre operazioni coordinate:
| Stream | Cosa legge | Scopo |
|---|---|---|
| Credential scanner stream | Ogni riga, incluse frontmatter e blocchi di codice fenced | Rilevare credenziali esposte |
| Content stream | Righe fuori dai blocchi fenced (frontmatter saltato) | Raccogliere definizioni reference e rilevare immagini |
| Reference URL secret scan | URL raccolte dalle definizioni reference | Rieseguire la scansione delle URL normalizzate per credenziali |
Lo stream del credential scanner usa enumerate() grezzo: nessuna riga è mai invisibile al credential scanner. Lo stream dei contenuti usa una macchina a stati consapevole dei blocchi fenced che salta le righe dentro fence ``` o ~~~, prevenendo falsi positivi di definizioni reference dagli esempi di codice.
Durante la Passata 1, il ReferenceScanner popola una ReferenceMap per file.
Gli eventi di harvest includono:
| Evento | Dati | Significato |
|---|---|---|
DEF | (norm_id, url) | Definizione reference accettata |
DUPLICATE_DEF | (norm_id, url) | ID duplicato (il primo vince per CommonMark 4.7) |
IMG | (alt_text, url) | Immagine con alt text trovata |
MISSING_ALT | url | Immagine senza alt text |
SECRET | SecurityFinding | Credenziale rilevata dal credential scanner |
Se viene prodotto un evento SECRET, il file viene marcato come compromesso ed escluso dai percorsi di output insicuri, mentre la Passata 2 continua a essere eseguita per la coerenza strutturale.
Passata 2 — Cross-Check e Validazione dei Link
La Passata 2 risolve i link stile-reference ([text][id]) contro la ReferenceMap popolata. Riesamina il content stream per trovare tutti i riferimenti a link e shortcut references. Ogni utilizzo viene risolto contro le definizioni raccolte nella Passata 1. Gli ID non definiti producono risultati DANGLING_REF.
Passata 3 — Report di Integrità
La Passata 3 calcola il punteggio di integrità per file e consolida tutti i risultati:
Il report include:
- Riferimenti dangling (errori) dalla Passata 2
- Definizioni morte (warning) — definite ma mai referenziate
- Definizioni duplicate (warning) — stesso ID definito due volte
- Risultati del credential scanner dalla Passata 1
- Risultati delle regole dall'Adaptive Rule Engine (se configurato)
Pipeline di Validazione dei Link
Il validatore di link (validate_links_async) opera indipendentemente con la propria struttura multifase:
Fase 1 — Legge tutti i file .md/.mdx in memoria, estrae link inline e reference link, calcola gli slug degli heading per file. Costruisce InMemoryPathResolver una volta sola dalla mappa completa dei file.
Fase 1.5 — Costruisce il grafo di adiacenza dei link e applica il rilevamento cicli DFS iterativo. Il registro dei cicli è un frozenset[str] — lookup O(1) nella Fase 2. Complessità totale: Θ(V+E).
Fase 2 — Valida ogni link contro il resolver, la VSM e il registro dei cicli. I link interni vengono risolti interamente in memoria (nessun I/O su disco). I link esterni vengono raccolti per la Fase 3.
Fase 3 (solo modalità strict) — Validazione HTTP HEAD concorrente degli URL esterni via httpx. Fino a 20 connessioni simultanee. Ogni URL univoco viene verificato esattamente una volta indipendentemente da quanti file vi fanno riferimento.
Credential Scanner
Il credential scanner di Zenzic è un motore di rilevamento credenziali integrato nella Passata 1. Opera come middleware: ogni riga passa dal credential scanner prima che qualsiasi altro parser la veda.
Normalizzatore Pre-scansione
Il normalizzatore applica più passate di normalizzazione prima della corrispondenza regex per rilevare credenziali offuscate — coprendo caratteri formato Unicode, riferimenti carattere HTML, interleaving di commenti, backtick span, operatori concat e pipe di tabella. Vengono scansionate sia la forma grezza che quella normalizzata; se lo stesso tipo di segreto viene rilevato in entrambe le forme, viene emesso un solo risultato.
Middleware I/O: safe_read_line
Per l'estrazione di metadati (parsing del frontmatter per slug, tag, stato draft), ogni riga passa attraverso safe_read_line(). Se viene rilevato un segreto, viene sollevata immediatamente un'eccezione CredentialViolation — la riga non viene mai restituita al chiamante, impedendo al segreto di entrare in qualsiasi parser.
Fondamenti di Sicurezza Enterprise-Grade
Questa sezione documenta le funzionalità di hardening della sicurezza. Queste proprietà sono verificate dalla suite di test e applicate dalla guardia _validate_docs_root e dal recinto I/O safe_read_line.
F2-1 — Troncamento Anti-ReDoS delle righe
Il credential scanner applica un limite rigido di 1 MiB per riga prima di qualsiasi motore regex per prevenire vulnerabilità ReDoS (hardening F2-1). Le righe che superano questo limite vengono silenziosamente troncate — una credenziale che inizia entro il primo 1 MiB sarà comunque rilevata; solo il contenuto oltre il limite è invisibile allo scanner.
F4-1 — Validazione Anti-Jailbreak del Percorso
La funzione _validate_docs_root() in cli/_shared.py eleva il path traversal guard (Codice di Uscita 3) da un controllo a tempo di link a una barriera pre-scansione del filesystem.
Modello di minaccia: Un .zenzic.toml malevolo o mal configurato contenente docs_dir = "../../etc" causerebbe la scansione da parte di Zenzic di directory di sistema OS, potenzialmente divulgando contenuti di file sensibili attraverso i risultati del rilevamento credenziali o esponendo la struttura delle directory nei messaggi di errore.
Mitigazione: resolve() espande tutti i symlink e i componenti .. prima del confronto, quindi docs_dir = "repo/../../../etc" viene catturato incondizionatamente. Il controllo viene eseguito prima di qualsiasi fase I/O e non può essere aggirato da flag CLI.
Il Codice di Uscita 3 non viene mai soppresso da --exit-zero o exit_zero = true. Se viene rilevato un tentativo di jailbreak, il processo termina immediatamente dopo la stampa del messaggio diagnostico del path traversal guard.
| Scenario | Valore docs_dir | Risultato |
|---|---|---|
| Progetto normale | "docs" | Risolve dentro la radice del repo → consentito |
| Radice del repo come docs | "." | Risolve alla radice del repo → consentito |
| Escape dal parent | "../../etc" | Risolve fuori dalla radice del repo → Exit 3 |
| Escape tramite symlink | "docs-link" (symlink a /tmp) | resolve() espande → Exit 3 |
Protocollo Adapter
Gli adapter sono il meccanismo che permette a Zenzic di supportare diversi motori di build senza accoppiarsi a nessuno di essi.
BaseAdapter
BaseAdapter è un Abstract Base Class (ABC) che ogni adapter deve soddisfare. Metodi chiave:
| Metodo | Responsabilità |
|---|---|
has_engine_config() | Guard: restituisce True se l'adapter ha trovato un file di configurazione del motore. Quando False, i controlli nav-dipendenti vengono saltati |
get_route_info() | API Metadata-Driven Routing. Restituisce tutti i metadati di routing in una singola chiamata: URL canonico, stato della route, slug opzionale, route base path e flag proxy. |
get_nav_paths() | Restituisce l'insieme dei percorsi .md dichiarati nella navigazione del sito |
get_ignored_patterns() | Pattern fnmatch che l'adapter tratta come ignorati (ad esempio README.md per alcuni motori) |
is_locale_dir(name) | Determina se una directory è un albero di localizzazione |
resolve_asset(path, docs_root) | Risolve un asset con fallback i18n |
resolve_anchor(file, anchor, cache, docs_root) | Risolve un'ancora con fallback i18n |
provides_index(directory_path) | Hook I/O della fase di discovery. Restituisce True se il motore genererà una landing page per questa directory. Unico metodo del protocollo che può eseguire I/O su disco (Path.exists()). |
RouteMetadata
I metadati di routing unificati restituiti da get_route_info():
@dataclass(slots=True)
class RouteMetadata:
canonical_url: str # URL canonico servito dal motore (es. "/guide/install/")
status: RouteStatus # REACHABLE, ORPHAN_BUT_EXISTING, IGNORED, CONFLICT
slug: str | None = None # Override slug dal frontmatter
route_base_path: str = "/" # Prefisso URL dal preset del docs plugin
is_proxy: bool = False # True per route generate dal build senza file sorgente
version: str | None = None # Etichetta versione opzionale (supporto Docusaurus)
Virtual Site Map (VSM)
La Virtual Site Map è l'unica fonte di verità per il routing di Zenzic. È una struttura dati pura (una mappa tra stringhe canonical_url e oggetti Route) costruita dal VSMBuilder combinando la conoscenza degli adapter con la scoperta del filesystem.
Versioning e Supporto Multi-Doc
Zenzic, la VSM è consapevole delle versioni. Per gli adapter che supportano la documentazione multi-versione (DocusaurusAdapter), il VSM builder:
- Identifica i confini di versione tramite la scoperta estesa della root dell'adapter.
- Etichetta le route con la rispettiva versione in
RouteMetadata. - Risolve i cross-link dando priorità al contesto della stessa versione, prevenendo la "version-skew" nella validazione dei link.
Le route versionate sono spesso trattate come Ghost Routes — sono marcate come REACHABLE anche se non appaiono nel file di navigazione principale, poiché si assume che il motore di build gestisca automaticamente le sidebar specifiche per versione.
Modalità Offline e Risoluzione URL Flat
Il flag --offline innesca un cambiamento architetturale globale nel modo in cui la VSM risolve gli URL. Quando attivo:
offline_modeviene impostato aTruenelBuildContext.- Gli adapter forzano
use_directory_urls = False, sovrascrivendo qualsiasi configurazione specifica del motore. Gli adapter passano alla risoluzione URL piatta (es.guida/install.md→/guida/install.html) invece di slug in stile directory.
Questo garantisce che Zenzic rimanga un Custode Strutturale anche per la documentazione distribuita su filesystem dove la risoluzione dell'indice di directory (es. /pagina/ → /pagina/index.html) non è disponibile.
Adapter disponibili
| Adapter | Engine | Configurazione rilevata | Funzionalità |
|---|---|---|---|
MkDocsAdapter | mkdocs | mkdocs.yml | Risoluzione nav completa, modalità i18n folder/suffix, fallback locale |
ZensicalAdapter | zensical | zensical.toml | Config nativa TOML, lettura nativa di mkdocs.yml |
DocusaurusAdapter | docusaurus | docusaurus.config.js/ts | Parsing statico config JS, supporto slug frontmatter, route base path |
StandaloneAdapter | standalone | (nessuna) | Adapter agnostico per progetti Markdown puri; orphan check saltato |
Sovranità del Protocollo
Regola R21 (D080): il Core (validator.py, scanner.py) non deve mai codificare
nomi di engine come condizioni per la logica di validazione. Il comportamento
engine-specifico viene dichiarato nell'adapter e interrogato dal Core tramite metodi
del protocollo.
Il pattern canonico è get_link_scheme_bypasses() -> frozenset[str]. Se un engine
utilizza uno schema URI non standard per i link interni, il suo adapter restituisce
quel nome di schema e il validator esonera gli URL corrispondenti dal controllo Z105:
| Adapter | get_link_scheme_bypasses() | Motivo |
|---|---|---|
DocusaurusAdapter | frozenset({"pathname"}) | I link pathname:/// referenziano asset static/ tramite l'escape hatch del React router |
MkDocsAdapter | frozenset() | Nessun bypass engine-specifico richiesto |
ZensicalAdapter | frozenset() | Nessun bypass engine-specifico richiesto |
StandaloneAdapter | frozenset() | Nessun bypass engine-specifico richiesto |
Invariante architetturale: aggiungere un nuovo adapter che necessita di un bypass
per lo schema dei link richiede zero modifiche a validator.py. Implementa
get_link_scheme_bypasses() nell'adapter — il Core lo interroga a runtime.
Parità di Validazione Cross-Engine
Il Core di Zenzic è un algoritmo puro — non ha conoscenza di quale engine ha prodotto la documentazione che sta ispezionando. Le quattro categorie principali di controllo si attivano in modo identico per lo stesso contenuto indipendentemente dall'adapter attivo:
| Categoria | Regola | Dipendenza engine |
|---|---|---|
| Rilevamento segreti | Z201 | Nessuna — scansione raw del frontmatter |
| Link con percorso assoluto | Z105 | Solo schemi di bypass dichiarati dall'adapter |
| Contenuto breve | Z502 | Nessuna — conteggio parole dopo strip del frontmatter |
| Indice directory mancante | Z401 | adapter.provides_index() — uniforme tra engine |
La directory examples/matrix/ in questo repository contiene la prova vivente:
vettori di validazione avversariale identici producono findings identici su engine standalone,
mkdocs e zensical. I fixture integrity-baseline producono un Zenzic Audit Badge identico su tutti
e tre. Zero asimmetrie.
Risoluzione Link e Mapping degli Slug
Gli adapter che supportano override slug nel frontmatter (DocusaurusAdapter) mappano gli slug nella Virtual Site Map per la validazione della raggiungibilità: una pagina con slug: /quick-start all'URL /docs/quick-start viene correttamente marcata REACHABLE anche se il suo percorso file è docs/guides/getting-started.mdx.
Tuttavia, la validazione dell'integrità dei link di Zenzic (link rotti, percorsi assoluti) risolve i percorsi relativi dalla posizione nel filesystem, non dall'URL dello slug. Questo significa che una divergenza marcata tra slug e percorso file può causare una risoluzione diversa dei link relativi in Zenzic (basata sui file) rispetto al build engine (basata sugli URL).
Invariante architetturale: mantieni la gerarchia del filesystem allineata alla gerarchia degli URL desiderata. Se un file viene spostato in una nuova directory, lascia che l'URL segua naturalmente piuttosto che usare slug per bloccare il vecchio URL. Questo garantisce che i link ../ si risolvano in modo identico sia nel linter che nel generatore di siti statici.
Mapping degli Alias in InMemoryPathResolver
InMemoryPathResolver non è un mero indice di file. Implementa uno strato di Mapping degli Alias che traduce prefissi di percorso virtuali in percorsi fisici del filesystem prima di qualsiasi validazione dei link.
Il resolver viene inizializzato una volta durante la Fase 1 con una mappa completa dei file in memoria. All'inizializzazione registra anche tutti i prefissi alias noti per l'adapter attivo. Alias supportati:
| Prefisso alias | Risolve in | Engine |
|---|---|---|
@site/ | repo_root/ | Docusaurus |
Quando il target di un link usa @site/static/img/logo.png, il resolver rimuove il prefisso virtuale e rimappa il percorso in repo_root/static/img/logo.png prima di verificare l'esistenza del file. Questo previene errori spuri PATH_TRAVERSAL che altrimenti si attiverebbero per riferimenti Docusaurus relativi al progetto perfettamente validi.
Proprietà chiave: la risoluzione degli alias avviene interamente in memoria. Non viene eseguito alcun I/O su disco; il resolver consulta solo l'indice dei file costruito durante la Fase 1. Questo preserva l'invariante Zero-I/O nel percorso critico (Legge Core 1).
Factory degli engine
La factory get_adapter() interroga il gruppo entry-point zenzic.adapters e ricade su StandaloneAdapter se non trova un adapter per il motore richiesto. Gli adapter di terze parti si registrano via pyproject.toml — vedi la guida all'implementazione degli adapter.
Guardia has_engine_config
Quando un adapter viene istanziato ma non trova il file di configurazione del motore (es. MkDocsAdapter senza mkdocs.yml), la factory ricade su StandaloneAdapter. Questo garantisce che i controlli nav-dipendenti vengano saltati in modo pulito invece di produrre falsi positivi.
LayeredExclusionManager — Interni
Il LayeredExclusionManager viene costruito una sola volta per invocazione CLI e passato lungo l'intera pipeline. Incapsula tutti e quattro i livelli di esclusione in forma pre-compilata.
Costruzione
Il manager viene costruito una sola volta per invocazione CLI e incapsula tutti e quattro i livelli di esclusione. Se respect_vcs_ignore è true, i file .gitignore vengono analizzati alla costruzione e uniti in un singolo VCSIgnoreParser unificato.
VCS Ignore Parser
Il VCSIgnoreParser implementa la specifica gitignore completa, incluse negazione (!), ancoraggio al percorso e wildcards glob.
should_exclude_dir
Chiamato da walk_files() per ogni directory durante os.walk(). Restituisce True per potare la directory dalla visita — la directory e tutti i suoi discendenti non vengono mai esplorati.
should_exclude_file
Esegue una valutazione completa a 5 livelli inclusi i controlli sui componenti del percorso contro tutti i livelli di esclusione.
Protocollo Sovereign Root
Ogni comando CLI che interagisce con il filesystem accetta un argomento PATH opzionale.
Quando fornito, Zenzic applica il Protocollo Sovereign Root: la configurazione e la
scansione seguono la destinazione, non la directory corrente del chiamante.
Il Problema: Context Hijacking
Senza questo protocollo, eseguire zenzic check links ../altro-progetto dall'interno di
progetto-A caricherebbe il .zenzic.toml di progetto-A, userebbe il suo adapter e
applicherebbe le sue regole di esclusione — alla documentazione di altro-progetto.
Questo è il Context Hijacking: l'ambiente del chiamante sovrascrive silenziosamente
quello della destinazione.
La Soluzione: Sovranità in Tre Passi
Il protocollo risolve config e perimetro dal percorso della destinazione (non da cwd): trova il .zenzic.toml della destinazione, calibra docs_root alla directory o file di destinazione, e applica la Guardia Sandbox per ancorare il controllo di path traversal alla destinazione — non alla posizione del chiamante.
Caso Speciale init
zenzic init <percorso> è un caso speciale: crea un progetto invece di verificarlo.
Il percorso indicato diventa direttamente il repo_root, e la directory viene creata
(mkdir -p) se non esiste. Il rilevamento automatico dell'engine opera sulla directory
di destinazione (anche vuota). La CWD del chiamante non viene mai modificata.
Il contratto di risoluzione del path significa che la CWD del chiamante è sempre pulita — nessun effetto collaterale sulla directory invocante.
Invarianti
| Invariante | Garanzia |
|---|---|
| Isolamento della config | Il .zenzic.toml della destinazione viene caricato; la config del chiamante non viene mai consultata |
| Guardia sandbox | Il perimetro del path traversal guard è ancorato a docs_root, non a cwd |
| Nessuna mutazione della CWD | init scrive nella destinazione; la directory di lavoro del chiamante non viene modificata |
| Visualizzazione dell'hint | Il banner mostra il percorso assoluto risolto della destinazione per la fiducia dell'operatore |
Codici di uscita
Zenzic usa quattro codici di uscita, ognuno con una semantica precisa:
| Codice | Nome | Significato | Sopprimibile con --exit-zero? |
|---|---|---|---|
| 0 | Pulito | Nessun problema trovato (o --exit-zero attivo per problemi non-sicurezza) | — |
| 1 | Risultati | Errori di qualità trovati (link rotti, orfani, snippet, segnaposto, asset, riferimenti) | Sì |
| 2 | Credential Scanner | Credenziale rilevata dallo Zenzic credential scanner | No |
| 3 | Path Traversal Guard | Traversamento path verso directory di sistema OS (/etc/, /root/, /var/, /proc/, /sys/, /usr/) | No |
Exit Code 0 — Pulito
Tutti i controlli sono stati superati. Nessun errore, nessun warning (o --exit-zero è attivo e sono stati trovati solo risultati non di sicurezza).
Exit Code 1 — Risultati
Sono stati rilevati problemi di qualità della documentazione: link rotti, pagine orfane, snippet non validi, pagine segnaposto, asset non utilizzati, riferimenti dangling o definizioni morte (in modalità strict).
Può essere soppresso da exit_zero = true nella configurazione o --exit-zero nella CLI.
Exit Code 2 — Credential Scanner
Una credenziale esposta è stata rilevata dallo Zenzic credential scanner. Questo codice di uscita non viene mai soppresso da --exit-zero o exit_zero = true. La credenziale deve essere ruotata immediatamente.
Exit Code 3 — Path Traversal Guard
Un link nella documentazione risolve verso un percorso di sistema OS (/etc/, /root/, /var/, /proc/, /sys/, /usr/). Viene classificato come PATH_TRAVERSAL_SUSPICIOUS — un incidente di sicurezza che indica una potenziale iniezione template, toolchain compromessa o divulgazione di infrastruttura.
Il codice di uscita 3 ha la priorità massima. Se esistono risultati sia di credential scanner che di path traversal guard nella stessa esecuzione, il codice 3 vince.
Il path traversal guard scatta anche quando docs_dir stesso risolve fuori dalla radice del repository (protezione jailbreak F4-1).
Garanzia DFA — Il Motore RE2
Zenzic utilizza esclusivamente un motore DFA (Deterministic Finite Automaton) per tutti i pattern regex forniti dall'utente. Questa è un vincolo architetturale rigido, non un'opzione di configurazione.
Cosa significa
Ogni pattern dichiarato in [[custom_rules]] all'interno di .zenzic.toml viene compilato al
momento del caricamento dalla libreria RE2 (tramite google-re2).
RE2 implementa un vero DFA: elabora la stringa di input in un'unica passata da sinistra a destra,
senza backtracking.
Questo garantisce che ogni validazione regex avvenga in tempo lineare rispetto alla lunghezza dell'input:
dove è la lunghezza della riga scansionata. La complessità temporale è limitata indipendentemente dalla struttura del pattern. Il ReDoS (Regular Expression Denial of Service) è matematicamente impossibile con questo motore.
Cosa RE2 rifiuta
RE2 rifiuta i pattern che richiedono backtracking NFA. Se un pattern in [[custom_rules]] usa uno
di questi costrutti, Zenzic genera un PluginContractError all'avvio — prima che venga scansionato
qualsiasi file:
| Costrutto | Esempio | Motivo |
|---|---|---|
| Backreference | (\w+)\1 | Richiede memoria di una cattura precedente — non regolare |
| Lookahead positivo | foo(?=bar) | Richiede scansione speculativa in avanti |
| Lookahead negativo | foo(?!bar) | Richiede scansione speculativa in avanti |
| Lookbehind | (?<=foo)bar | Richiede scansione all'indietro |
Cosa RE2 accetta
RE2 è un superset della sintassi POSIX ERE standard. Pattern come questi si compilano e vengono eseguiti correttamente:
| Funzionalità | Esempio |
|---|---|
| Testo letterale | internal\.corp\.example\.com |
| Alternazione | DRAFT|WIP|TODO |
| Ripetizione | [0-9]{3}-[0-9]{4} |
| Flag inline | (?i)\bDRAFT\b (case-insensitive), (?m)^todo (multiline) |
| Gruppi nominati | (?P<code>Z\d{3}) |
| Quantificatori classici "pericolosi" | (a+)+ — sicuro in RE2, gira in O(n) |
Il Contratto di Purezza DFA
Ogni pattern in [[custom_rules]] viene compilato con RE2 al momento del caricamento. Un pattern si compila — ed è quindi -safe — oppure non si compila, e l'avvio fallisce con un messaggio PluginContractError azionabile. Non esiste un canary a runtime, nessun timer SIGALRM e nessuna divergenza tra piattaforme.
Complessità Algoritmica
L'architettura di Zenzic separa gli intenti computazionali applicando limiti algoritmici specifici a ciascun dominio:
-
Topologia (Knowledge Graph): La validazione dei link esegue una DFS iterativa sul grafo di adiacenza della Virtual Site Map. Con una rappresentazione tramite liste di adiacenza, la complessità della traversata è , dove rappresenta pagine e asset ed i collegamenti. I registri di ciclicità utilizzano hash set per fornire lookup medi nelle passate successive.
-
Scansione Semantica: Il credential scanner e le regole personalizzate utilizzano il motore regex
google-re2, che evita il backtracking catastrofico tipico di alcuni motori regex tradizionali. Ciò garantisce una complessità lineare rispetto all'input analizzato ed elimina le vulnerabilità ReDoS basate sul backtracking esponenziale. -
I/O Discovery: L'ingestione dei file mantiene una complessità rispetto al volume totale dei dati elaborati. Quando viene raggiunta una determinata soglia operativa, l'elaborazione può essere distribuita tramite process pool paralleli per ridurre il wall-time, mantenendo invariata la complessità computazionale complessiva.
Motore Adattivo Ibrido
La pipeline di scansione seleziona automaticamente tra esecuzione sequenziale e parallela in base al numero di file:
| Condizione | Modalità | Dettaglio |
|---|---|---|
workers=1 (default) oppure file < 50 | Sequenziale | Zero overhead di spawn processi. I/O completo O(N). |
workers != 1 e file >= 50 | Parallelo | ProcessPoolExecutor con distribuzione per-file |
La soglia di 50 file è un'euristica conservativa: sotto questa soglia, l'overhead di spawn dei processi supera il beneficio del parallelismo. I risultati sono sempre ordinati per file_path indipendentemente dalla modalità di esecuzione.
CLI Sovrana — Nessun Layer di Integrazioni
Zenzic opera come CLI standalone con analisi esterna guidata da Adapter. I build engine non vengono mai estesi a runtime da componenti Zenzic.
Perché: un hook embedded nel motore accoppia il ciclo di rilascio di Zenzic all'API di uno strumento di build esterno. Inoltre impedisce una superficie di enforcement uniforme tra motori. La pipeline CLI fornisce hardening completo di credential scanner e path traversal guard. Una CLI Sovrana disaccoppia il quality gate dallo strumento di build e rende ogni punto di applicazione engine-agnostico.
Esegui Zenzic in CI come quality gate esterno tramite zenzic check all --strict in uno step del workflow. Questo produce lo stesso enforcement con VSM completa, credential scanner (ZRT-006/007) e path traversal guard attivi — senza alcuna integrazione runtime all'interno del motore di build.
Vedi Integrazione CI/CD per esempi di workflow.
Pattern di Estensione
Gli strumenti esterni che devono invocare i check di Zenzic in modo programmatico possono usare direttamente la Python API pubblica (zenzic.core.scanner, zenzic.models.config). Tutta l'intelligenza risiede in zenzic.core. La CLI è un sottile strato di dispatch sulle stesse funzioni — non esiste logica nascosta che richieda il percorso subprocess.
Layer CLI
La CLI è strutturata come un package (src/zenzic/cli/), non come un modulo monolitico. Questa separazione applica la responsabilità singola a livello di modulo e rende la pipeline di output visivo auditabile come una preoccupazione di primo piano.
Struttura del package
src/zenzic/cli/
├── __init__.py # Superficie di re-export pubblica per main.py — nessuna logica
├── _shared.py # Custode dello Stato Visivo: singleton console, singleton _ui, utility
├── _check.py # sub-app check_app + 7 comandi check
├── _clean.py # sub-app clean_app + pulizia asset
├── _config_explain.py # comando explain + superficie introspezione configurazione
├── _governance.py # sub-app config_app + comandi profili governance
├── _guard.py # sub-app guard_app + comandi secret-guard scan/init
├── _inspect.py # sub-app inspect_app + comando capabilities
├── _lab.py # comando lab — showcase interattivo esempi
├── _metadata.py # SSOT dei metadati top-level comandi e pannelli help
└── _standalone.py # comandi score, diff, init
Custode dello Stato Visivo
_shared.py è il solo proprietario di tutto lo stato console e UI. Espone due getter dinamici:
| Getter | Restituisce |
|---|---|
get_console() | Il singleton rich.console.Console corrente |
get_ui() | Il singleton ZenzicUI corrente (avvolge la console) |
configure_console() — chiamata dal callback Typer --no-color / --force-color in main.py — sostituisce entrambi i singleton atomicamente. Poiché ogni comando chiama get_ui() / get_console() al momento dell'invocazione anziché all'importazione, ricevono sempre l'istanza che riflette i flag colore dell'utente.
Invariante: force_terminal sulla Console a livello di modulo è sempre None (auto-detect via sys.stdout.isatty()). Passare force_terminal=False disabilita silenziosamente il colore anche nei terminali interattivi — questo è un pattern di bug latente che l'architettura esplicitamente protegge.
Flusso del banner di avvio
Il banner scrive sempre su stdout (il _shared.console condiviso), quindi usa lo stesso stream di rilevamento colore dell'output successivo del comando. Il callback Typer viene eseguito dopo il banner, il che è accettabile — la console a livello di modulo già usa force_terminal=None (auto-detect) all'avvio.
_SUBAPPS_WITH_MENU in cli_main() copre le sub-app che usano no_args_is_help=True: invocare zenzic check, zenzic clean, o zenzic inspect senza ulteriori argomenti mostra la pagina di help Typer; il banner viene anteposto dall'esplicito controllo di invocazione nuda.
Punti di estensione
| Obiettivo | Azione |
|---|---|
| Aggiungere un comando a una sub-app esistente | Aggiungere @check_app.command() (o altra app) nel _*.py rilevante — nessuna modifica a __init__.py o main.py |
| Aggiungere una nuova sub-app top-level | Creare _miafeature.py, esportare da __init__.py, registrare in main.py, aggiungere a _SUBAPPS_WITH_MENU se no_args_is_help=True |
| Aggiungere una utility condivisa | Aggiungere in _shared.py e importare via from . import _shared — non istanziare mai Console o ZenzicUI localmente |
Identità Visiva — zenzic.core.ui
ZenzicPalette, ZenzicUI, make_banner, emoji e SUPPORTS_COLOR risiedono in
src/zenzic/core/ui.py. Il layer core possiede l'identità visiva in modo che ZenzicReporter
(anch'esso in core/) possa importarli senza guardare verso l'alto. Il layer CLI importa da
zenzic.core.ui direttamente. Lo stub di compatibilità in src/zenzic/ui.py riesporta tutto
per qualsiasi codice di terze parti che importava già dal vecchio percorso.
zenzic-doc — Banco di Prova Vivente
Il sito di documentazione Zenzic (zenzic-doc) non è un artefatto passivo — è un
partecipante attivo nella pipeline di qualità. Ogni commit esegue zenzic check all --strict
contro sé stesso prima di poter essere inviato (Sovereign Parity, ZRT-010).
Oltre all'audit standard di Zenzic, zenzic-doc impone un secondo invariante unico al suo
ruolo di documentazione di un linter: ogni codice di finding Zxxx presente in docs/ deve
avere un'entità registrata in src/zenzic/core/codes.py nel pacchetto Core — e viceversa.
Questa parità bidirezionale è applicata dalla sessione Nox verify-codes-parity tramite
Risoluzione Sovrana (Fail-Closed):
- Author environment:
ZENZIC_CORE_PATHimpostato →uv run --project <percorso>sul sorgente locale. - Core non trovato: la sessione fallisce in modalità fail-closed; fallback PyPI proibito.
Eseguire just verify in zenzic-doc avvia tutti e quattro i gate in sequenza. I contributori
devono fornire un path locale al core Zenzic (ZENZIC_CORE_PATH, ./_zenzic_core o ../zenzic).
Lo Standard a 4 Cancelli
Ogni repository dell'ecosistema Zenzic impone la qualità in quattro checkpoint progressivi:
| Cancello | Trigger | Strumento | Cosa rileva |
|---|---|---|---|
| Cancello 1 — IDE | In tempo reale, al salvataggio | Estensioni editor (Pylance, ESLint) | Errori di tipo, problemi sintattici |
| Cancello 2 — Pre-commit | git commit | Hook pre-commit | Lint, credenziali, formattazione, REUSE |
| Cancello 3 — Pre-push | git push | just verify (via pre-commit -t pre-push) | Suite di validazione completa identica alla CI |
| Cancello 4 — CI Remota | Pull Request / push | GitHub Actions | just verify identico su un runner Ubuntu pulito |
I Cancelli 3 e 4 eseguono lo stesso comando (just verify) — locale e remoto non possono
mai divergere. Questo è il principio di Sovereign Parity (ZRT-010).
Per la configurazione operativa (installazione hook, YAML del workflow), vedi la guida CI/CD Integration.
Scegliere il Modello Giusto
| Scenario | Approccio consigliato |
|---|---|
| Pipeline CI per qualsiasi motore | zenzic check all — aggiungi uno step, nessun plugin necessario |
| Gate pre-commit per le credenziali | zenzic check references — si registra come hook pre-commit |
| Motore custom non supportato | Scrivi un Adapter — pubblica come pacchetto separato, registra via entry-point zenzic.adapters |
| Migrazione da MkDocs a Docusaurus | Usa zenzic check all con engine = "mkdocs" sulla sorgente, engine = "docusaurus" sulla destinazione |
Vedi Integrazione CI/CD per workflow passo dopo passo corrispondenti a ogni scenario.
Integrità del Brand
Il modello Exclusion Zone va oltre la correttezza strutturale. Una base di codice o una suite di documentazione che contiene identificatori di brand obsoleti porta un tipo di debito diverso: debito narrativo. Una pagina che fa ancora riferimento a un codename contraddice il contratto che cerca di documentare.
Zenzic gestisce questo attraverso il blocco di configurazione [governance] e il finding Z601 BRAND_OBSOLESCENCE. La soppressione per-riga format-aware è disponibile per i riferimenti storici intenzionali utilizzando la sintassi di commento appropriata per il tipo di file (.md vs. .mdx).