Passa al contenuto principale

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.

ControlloRuolo architetturale
GitHub Actions pin-nate a SHAImpedisce 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 buildFornisce metadati verificabili sull'origine degli artefatti distribuiti
Dependabot per le actionsAutomatizza 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.

Zenzic — Pipeline a Tre Fasi: File Set → Passata 1 (credential scanner + Content stream) → Passata 2 (Cross-Check) → Passata 3 (Integrity Report) → Quality ReportZenzic — Pipeline a Tre Fasi: File Set → Passata 1 (credential scanner + Content stream) → Passata 2 (Cross-Check) → Passata 3 (Integrity Report) → Quality Report

Passata 1 — Raccolta e Scansione

La prima passata attraversa tutti i file .md e .mdx sotto docs/ ed esegue tre operazioni coordinate:

StreamCosa leggeScopo
Credential scanner streamOgni riga, incluse frontmatter e blocchi di codice fencedRilevare credenziali esposte
Content streamRighe fuori dai blocchi fenced (frontmatter saltato)Raccogliere definizioni reference e rilevare immagini
Reference URL secret scanURL raccolte dalle definizioni referenceRieseguire 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:

EventoDatiSignificato
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_ALTurlImmagine senza alt text
SECRETSecurityFindingCredenziale 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)

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.

Zenzic credential scanner — algoritmo di normalizzazione in 8 passaggi: dalla riga grezza attraverso ZRT-006/007/003 fino alla scansione duale e al SecurityFindingZenzic credential scanner — algoritmo di normalizzazione in 8 passaggi: dalla riga grezza attraverso ZRT-006/007/003 fino alla scansione duale e al SecurityFinding

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.

ScenarioValore docs_dirRisultato
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:

MetodoResponsabilità
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.

Zenzic Virtual Site Map — proiezione dal filesystem attraverso VSMBuilder e Adapter Protocol verso un catalogo di route con stati REACHABLE, ORPHAN e GHOSTZenzic Virtual Site Map — proiezione dal filesystem attraverso VSMBuilder e Adapter Protocol verso un catalogo di route con stati REACHABLE, ORPHAN e GHOST

Versioning e Supporto Multi-Doc

Zenzic, la VSM è consapevole delle versioni. Per gli adapter che supportano la documentazione multi-versione (DocusaurusAdapter), il VSM builder:

  1. Identifica i confini di versione tramite la scoperta estesa della root dell'adapter.
  2. Etichetta le route con la rispettiva versione in RouteMetadata.
  3. 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:

  1. offline_mode viene impostato a True nel BuildContext.
  2. 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

AdapterEngineConfigurazione rilevataFunzionalità
MkDocsAdaptermkdocsmkdocs.ymlRisoluzione nav completa, modalità i18n folder/suffix, fallback locale
ZensicalAdapterzensicalzensical.tomlConfig nativa TOML, lettura nativa di mkdocs.yml
DocusaurusAdapterdocusaurusdocusaurus.config.js/tsParsing statico config JS, supporto slug frontmatter, route base path
StandaloneAdapterstandalone(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:

Adapterget_link_scheme_bypasses()Motivo
DocusaurusAdapterfrozenset({"pathname"})I link pathname:/// referenziano asset static/ tramite l'escape hatch del React router
MkDocsAdapterfrozenset()Nessun bypass engine-specifico richiesto
ZensicalAdapterfrozenset()Nessun bypass engine-specifico richiesto
StandaloneAdapterfrozenset()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:

CategoriaRegolaDipendenza engine
Rilevamento segretiZ201Nessuna — scansione raw del frontmatter
Link con percorso assolutoZ105Solo schemi di bypass dichiarati dall'adapter
Contenuto breveZ502Nessuna — conteggio parole dopo strip del frontmatter
Indice directory mancanteZ401adapter.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.

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 aliasRisolve inEngine
@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

InvarianteGaranzia
Isolamento della configIl .zenzic.toml della destinazione viene caricato; la config del chiamante non viene mai consultata
Guardia sandboxIl perimetro del path traversal guard è ancorato a docs_root, non a cwd
Nessuna mutazione della CWDinit scrive nella destinazione; la directory di lavoro del chiamante non viene modificata
Visualizzazione dell'hintIl 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:

CodiceNomeSignificatoSopprimibile con --exit-zero?
0PulitoNessun problema trovato (o --exit-zero attivo per problemi non-sicurezza)
1RisultatiErrori di qualità trovati (link rotti, orfani, snippet, segnaposto, asset, riferimenti)
2Credential ScannerCredenziale rilevata dallo Zenzic credential scannerNo
3Path Traversal GuardTraversamento 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:

O(n)O(n)

dove nn è 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:

CostruttoEsempioMotivo
Backreference(\w+)\1Richiede memoria di una cattura precedente — non regolare
Lookahead positivofoo(?=bar)Richiede scansione speculativa in avanti
Lookahead negativofoo(?!bar)Richiede scansione speculativa in avanti
Lookbehind(?<=foo)barRichiede 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 letteraleinternal\.corp\.example\.com
AlternazioneDRAFT|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 O(n)O(n)-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 è Θ(V+E)\Theta(V+E), dove VV rappresenta pagine e asset ed EE i collegamenti. I registri di ciclicità utilizzano hash set per fornire lookup medi O(1)O(1) 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 O(N)O(N) rispetto all'input analizzato ed elimina le vulnerabilità ReDoS basate sul backtracking esponenziale.

  • I/O Discovery: L'ingestione dei file mantiene una complessità O(N)O(N) 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:

CondizioneModalitàDettaglio
workers=1 (default) oppure file < 50SequenzialeZero overhead di spawn processi. I/O completo O(N).
workers != 1 e file >= 50ParalleloProcessPoolExecutor 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:

GetterRestituisce
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.pysostituisce 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

ObiettivoAzione
Aggiungere un comando a una sub-app esistenteAggiungere @check_app.command() (o altra app) nel _*.py rilevante — nessuna modifica a __init__.py o main.py
Aggiungere una nuova sub-app top-levelCreare _miafeature.py, esportare da __init__.py, registrare in main.py, aggiungere a _SUBAPPS_WITH_MENU se no_args_is_help=True
Aggiungere una utility condivisaAggiungere 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_PATH impostato → 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:

CancelloTriggerStrumentoCosa rileva
Cancello 1 — IDEIn tempo reale, al salvataggioEstensioni editor (Pylance, ESLint)Errori di tipo, problemi sintattici
Cancello 2 — Pre-commitgit commitHook pre-commitLint, credenziali, formattazione, REUSE
Cancello 3 — Pre-pushgit pushjust verify (via pre-commit -t pre-push)Suite di validazione completa identica alla CI
Cancello 4 — CI RemotaPull Request / pushGitHub Actionsjust 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

ScenarioApproccio consigliato
Pipeline CI per qualsiasi motorezenzic check all — aggiungi uno step, nessun plugin necessario
Gate pre-commit per le credenzializenzic check references — si registra come hook pre-commit
Motore custom non supportatoScrivi un Adapter — pubblica come pacchetto separato, registra via entry-point zenzic.adapters
Migrazione da MkDocs a DocusaurusUsa 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).