Skip to main content

Architecture

This page describes the internal design of Zenzic for contributors and advanced users who need to understand how the tool works under the hood. For configuration and usage, see the Configuration Reference and Checks Reference.

Integrity Beyond Code

Zenzic extends static-analysis determinism to its build infrastructure.

The same engineering rule applies at repository level: if execution context is not deterministic, analysis results are not trustworthy. For this reason, CI/CD controls are treated as part of the architecture contract, not as operational afterthoughts.

ControlArchitectural role
SHA-pinned GitHub ActionsPrevents mutable tag drift and locks workflow behavior to reviewed commits
Frozen lockfile sync (uv.lock)Ensures deterministic dependency graph during CI and release builds
Build provenance attestationsProvides verifiable origin metadata for distributed artifacts
Dependabot for actionsAutomates SHA refresh while preserving immutable pinning model

This is the infrastructure-layer equivalent of static analysis: constrain execution inputs, preserve reproducibility, and make security evidence auditable for contributors and downstream users.


Three-Phase Pipeline

The core analysis engine operates as a Three-Phase Pipeline over the documentation file set. Each phase has a distinct responsibility and runs in deterministic order.

Zenzic Three-Phase Analysis Pipeline: File Set → Pass 1 (credential scanner + Content streams) → Pass 2 (Cross-Check) → Pass 3 (Integrity Report) → Quality ReportZenzic Three-Phase Analysis Pipeline: File Set → Pass 1 (credential scanner + Content streams) → Pass 2 (Cross-Check) → Pass 3 (Integrity Report) → Quality Report

Pass 1 -- Harvest and Credential Scan

Pass 1 reads every .md and .mdx file under docs/ and performs three coordinated operations:

StreamWhat it readsPurpose
Credential scanner streamEvery line including frontmatter and fenced code blocksDetect leaked credentials
Content streamLines outside fenced blocks (frontmatter skipped)Harvest reference definitions, detect images
Reference URL secret scanURLs harvested from reference definitionsRe-scan normalized URLs for embedded credentials

The credential scanner stream uses raw enumerate() -- no line is ever invisible to the credential scanner. The content stream uses a fenced-block-aware state machine that skips lines inside ``` or ~~~ fences, preventing false-positive reference definitions from code examples.

During Pass 1, the ReferenceScanner populates a ReferenceMap per file.

Harvest events include:

EventDataMeaning
DEF(norm_id, url)Reference definition accepted
DUPLICATE_DEF(norm_id, url)Duplicate ID (first wins per CommonMark 4.7)
IMG(alt_text, url)Image with alt text found
MISSING_ALTurlImage without alt text
SECRETSecurityFindingCredential detected by credential scanner

If any SECRET event is yielded, the file is flagged as compromised and excluded from insecure output paths, while Pass 2 analysis still executes for structural consistency.

Pass 2 -- Cross-Check and Link Validation

Pass 2 resolves reference-style links ([text][id]) against the populated ReferenceMap. This pass re-reads the content stream to find all reference link usages and shortcut references. Each usage is resolved against the definitions collected in Pass 1. Undefined IDs produce DANGLING_REF findings.

Pass 3 -- Integrity Report

Pass 3 computes the per-file integrity score and consolidates all findings:

The report includes:

  • Dangling references (errors) from Pass 2
  • Dead definitions (warnings) -- defined but never referenced
  • Duplicate definitions (warnings) -- same ID defined twice
  • Security findings from Pass 1
  • Rule findings from the Adaptive Rule Engine (if configured)

The link validator (validate_links_async) operates independently with its own multi-pass structure:

Pass 1 -- Read all .md/.mdx files into memory, extract inline links and reference links, compute heading anchor slugs per file. Construct the InMemoryPathResolver once from the complete file map.

Pass 1.5 -- Build the link adjacency graph and run iterative DFS cycle detection. The cycle registry is a frozenset[str] -- O(1) membership checks in Pass 2. Total complexity: Theta(V+E).

Pass 2 -- Validate each link against the resolver, VSM, and cycle registry. Internal links are resolved entirely in memory (no disk I/O). External links are collected for Pass 3.

Pass 3 (strict mode only) -- Concurrent HTTP HEAD validation of external URLs via httpx. Up to 20 simultaneous connections. Each unique URL is pinged exactly once regardless of how many files reference it.


Credential Scanner

The Zenzic credential scanner is a credential detection engine integrated into Pass 1. It operates as middleware: every line passes through the credential scanner before any other parser sees it.

Zenzic credential scanner normalization algorithm: 8-step pipeline from Raw Line through ZRT-006/007/003 normalization passes to dual raw/normalized scan, deduplication, and SecurityFinding outputZenzic credential scanner normalization algorithm: 8-step pipeline from Raw Line through ZRT-006/007/003 normalization passes to dual raw/normalized scan, deduplication, and SecurityFinding output

Pre-scan Normalizer

The normalizer applies multiple normalization passes before regex matching to detect obfuscated credentials — covering Unicode format characters, HTML character references, comment interleaving, backtick spans, concat operators, and table pipes. Both the raw and normalized forms are scanned; if the same secret type is detected in both forms, only one finding is emitted.

IO Middleware: safe_read_line

For metadata extraction (frontmatter parsing for slugs, tags, draft status), every line passes through safe_read_line(). If a secret is detected, a CredentialViolation exception is raised immediately -- the line is never returned to the caller, preventing the secret from entering any parser.


Enterprise-Grade Security Foundations

This section documents the security hardening features. These properties are verified by the test suite and enforced by the _validate_docs_root guard and the safe_read_line I/O fence.

F2-1 — Anti-ReDoS Line Truncation

The credential scanner applies a hard 1 MiB per-line limit before any regex engine to prevent ReDoS vulnerabilities (F2-1 hardening). Lines exceeding this limit are silently truncated — a credential that begins within the first 1 MiB will still be detected; only content beyond the cap is invisible to the scanner.

F4-1 — Anti-Jailbreak Path Validation

The _validate_docs_root() function in cli/_shared.py elevates the path traversal guard (Exit Code 3) from a link-time check to a pre-scan filesystem barrier.

Threat model: A malicious or misconfigured .zenzic.toml containing docs_dir = "../../etc" would cause Zenzic to scan OS system directories, potentially leaking sensitive file contents through credential detection findings or exposing the directory structure in error messages.

Mitigation: resolve() expands all symlinks and .. components before the comparison, so docs_dir = "repo/../../../etc" is caught unconditionally. The check runs before any I/O phase and cannot be bypassed by CLI flags.

Exit Code 3 is never suppressed by --exit-zero or exit_zero = true. If a jailbreak attempt is detected, the process terminates immediately after printing the path traversal guard diagnostic.

Scenariodocs_dir valueOutcome
Normal project"docs"Resolves inside repo root → allowed
Repo root as docs"."Resolves to repo root → allowed
Parent escape"../../etc"Resolves outside repo root → Exit 3
Symlink escape"docs-link" (symlink to /tmp)resolve() expands → Exit 3

Adapter Protocol

Zenzic is engine-agnostic. It works with MkDocs, Zensical, Docusaurus, or no documentation engine at all. This is achieved through the Adapter Protocol — an Abstract Base Class (ABC) that defines the contract between the core pipeline and engine-specific path resolution.

BaseAdapter

Every adapter must extend BaseAdapter. Key methods:

MethodDescription
has_engine_config()Guard: returns True when the adapter found an engine configuration file. When False, nav-dependent checks are skipped.
get_route_info()Metadata-Driven Routing API. Returns all routing metadata in a single call: canonical URL, route status, optional slug, route base path, and proxy flag.
get_nav_paths()Returns the set of .md paths declared in the site navigation.
get_ignored_patterns()fnmatch patterns the adapter treats as ignored (e.g. README.md for some engines).
is_locale_dir(name)Determines whether a directory is a locale tree.
resolve_asset(path, docs_root)Resolves an asset with i18n fallback.
resolve_anchor(file, anchor, cache, docs_root)Resolves an anchor with i18n fallback.
provides_index(directory_path)Discovery-phase I/O hook. Returns True when the engine will generate a landing page for this directory. The only protocol method that may perform disk I/O (Path.exists()).

RouteMetadata

The unified routing metadata returned by get_route_info():

@dataclass(slots=True)
class RouteMetadata:
canonical_url: str # URL path the engine serves (e.g. "/guide/install/")
status: RouteStatus # REACHABLE, ORPHAN_BUT_EXISTING, IGNORED, CONFLICT
slug: str | None = None # Frontmatter slug override
route_base_path: str = "/" # URL prefix from docs plugin preset
is_proxy: bool = False # True for build-generated routes with no source file
version: str | None = None # Optional version label (Docusaurus support)

Virtual Site Map (VSM)

The Virtual Site Map is Zenzic's single source of truth for routing. It is a pure-data structure (a mapping of canonical_url string to Route objects) constructed by the VSMBuilder by combining adapter knowledge with filesystem discovery.

Zenzic Virtual Site Map: projection from physical filesystem files through VSMBuilder and Adapter Protocol to a route catalog with REACHABLE, ORPHAN, and GHOST route statusesZenzic Virtual Site Map: projection from physical filesystem files through VSMBuilder and Adapter Protocol to a route catalog with REACHABLE, ORPHAN, and GHOST route statuses

Versioning & Multi-Doc Support

Zenzic, the VSM is version-aware. For adapters that support multi-version documentation (DocusaurusAdapter), the VSM builder:

  1. Identifies version boundaries via the adapter's extended root discovery.
  2. Tags routes with their respective version label in RouteMetadata.
  3. Resolves cross-links within the same version context first, preventing version-skew in link validation.

Versioned routes are often treated as Ghost Routes — they are marked REACHABLE even if they do not appear in the primary navigation file, as the build engine is assumed to manage version-specific sidebars automatically.

Offline Mode & Flat URL Resolution

The --offline flag triggers a global architectural shift in how the VSM resolves URLs. When active:

  1. offline_mode is set to True in the BuildContext.
  2. Adapters force use_directory_urls = False, overriding any engine-specific configuration. Adapters switch to flat URL resolution (e.g., guide/install.md/guide/install.html) instead of directory-style slugs.

This ensures that Zenzic remains a Structural Custodian for documentation distributed on filesystems where directory-index resolution (e.g., /page//page/index.html) is unavailable.

Built-in Adapters

AdapterEngineConfig fileFeatures
MkDocsAdaptermkdocsmkdocs.ymlFull nav resolution, i18n folder/suffix mode, locale fallback
ZensicalAdapterzensicalzensical.tomlNative TOML-based config, reads mkdocs.yml natively
DocusaurusAdapterdocusaurusdocusaurus.config.js/tsStatic JS config parsing, frontmatter slug support, route base path
StandaloneAdapterstandalone(none)Engine-agnostic adapter for plain Markdown projects; orphan check skipped

Protocol Sovereignty

Rule R21 (D080): the Core (validator.py, scanner.py) must never hardcode engine names as conditions for validation logic. Engine-specific behaviour is declared in the adapter and queried by the Core via protocol methods.

The canonical pattern is get_link_scheme_bypasses() -> frozenset[str]. If an engine uses a non-standard URI scheme for internal links, its adapter returns that scheme name and the validator exempts matching URLs from the Z105 absolute-path check:

Adapterget_link_scheme_bypasses()Reason
DocusaurusAdapterfrozenset({"pathname"})pathname:/// links reference static/ assets via the React router escape hatch
MkDocsAdapterfrozenset()No engine-specific bypass required
ZensicalAdapterfrozenset()No engine-specific bypass required
StandaloneAdapterfrozenset()No engine-specific bypass required

Architectural invariant: adding a new engine adapter that needs a link-scheme bypass requires zero changes to validator.py. Implement get_link_scheme_bypasses() in the adapter alone — the Core queries it at runtime.

Cross-Engine Validation Parity

Zenzic's Core is a pure algorithm — it has no knowledge of which engine produced the docs it is inspecting. The four primary check categories fire identically for the same content regardless of the active adapter:

CategoryRuleEngine dependency
Secret detectionZ201None — raw frontmatter scan
Absolute path linksZ105Adapter-declared bypass schemes only
Short contentZ502None — word count after frontmatter strip
Missing directory indexZ401adapter.provides_index() — uniform across engines

The examples/matrix/ directory in this repository contains the living proof: identical adversarial-validation vectors produce identical findings across standalone, mkdocs, and zensical engines. The integrity-baseline fixtures produce an identical Zenzic Audit Badge on all three. Zero asymmetries.

Adapters that support frontmatter slug overrides (DocusaurusAdapter) map slugs into the Virtual Site Map for reachability validation: a page with slug: /quick-start at URL /docs/quick-start is correctly marked REACHABLE even though its file path is docs/guides/getting-started.mdx.

However, Zenzic's link integrity validation (broken links, absolute paths) resolves relative paths from the filesystem location, not the slug URL. This means a heavy divergence between slug and file path can cause a page's relative links to resolve differently in Zenzic (file-based) vs the build engine (URL-based).

Architectural invariant: keep the filesystem hierarchy aligned with the intended URL hierarchy. If a file is moved to a new directory, let the URL follow naturally rather than using slug to pin the old URL. This ensures ../ links resolve identically in both the linter and the static-site generator.

Alias Mapping in InMemoryPathResolver

The InMemoryPathResolver is not a simple file-lookup table. It implements an Alias Mapping layer that translates virtual path prefixes into physical filesystem paths before any link validation takes place.

The resolver is initialised once during Pass 1 with a complete in-memory file map. At initialisation it also registers all known alias prefixes for the active adapter. Supported aliases:

Alias prefixResolves toEngine
@site/repo_root/Docusaurus

When a link target uses @site/static/img/logo.png, the resolver strips the virtual prefix and remaps the path to repo_root/static/img/logo.png before checking for file existence. This prevents spurious PATH_TRAVERSAL errors that would otherwise fire for valid Docusaurus project-relative references.

Key property: alias resolution happens entirely in memory. No disk I/O is performed; the resolver consults only the file index built during Pass 1. This preserves the Zero-I/O hot-path invariant (Core Law 1).

Engine Factory

The get_adapter() factory queries the zenzic.adapters entry-point group and falls back to StandaloneAdapter if no adapter is found. Third-party adapters register via pyproject.toml — see the Adapter implementation guide.

has_engine_config Guard

When an adapter is instantiated but finds no engine config file (e.g. MkDocsAdapter with no mkdocs.yml), the factory falls back to StandaloneAdapter. This ensures nav-dependent checks are skipped cleanly rather than producing false positives.


Layered Exclusion Manager Internals

The LayeredExclusionManager is constructed once per CLI invocation and passed through the entire pipeline. It encapsulates all four exclusion levels in pre-compiled form.

Construction

The manager is constructed once per CLI invocation and encapsulates all four exclusion levels. If respect_vcs_ignore is true, .gitignore files are parsed at construction time and merged into a single unified VCSIgnoreParser.

VCS Ignore Parser

The VCSIgnoreParser implements the full gitignore specification, including negation (!), path anchoring, and glob wildcards.

should_exclude_dir

Called by walk_files() for each directory during os.walk(). Returns True to prune the directory from the walk -- the directory and all its descendants are never entered.

should_exclude_file

Performs full 5-layer evaluation including path-component checks against all exclusion layers.


Sovereign Root Protocol

Every CLI command that interacts with the filesystem accepts an optional PATH argument. When provided, Zenzic applies the Sovereign Root Protocol: configuration and scanning follow the target, not the caller's current working directory.

The Problem: Context Hijacking

Without this protocol, running zenzic check links ../other-project from inside project-A would load project-A's .zenzic.toml, use project-A's engine adapter, and apply project-A's exclusion rules — to other-project's documentation. This is Context Hijacking: the caller's environment silently overrides the target's.

The Solution: Three-Step Sovereignty

The protocol resolves config and scope from the target path (not from cwd): it finds the target's .zenzic.toml, calibrates docs_root to the target directory or file, and applies the Sandbox Guard to anchor the path traversal check at the target — not the caller's location.

init Special Case

zenzic init <path> is a special case: it creates a project rather than auditing one. The given path becomes the repo_root directly, and the directory is created (mkdir -p) if it does not exist. Engine auto-detection runs on the (possibly empty) target directory. The caller's CWD is never written to.

The path resolution contract means the caller's CWD is always clean — no side effects on the invoking directory.

Invariants

InvariantGuarantee
Config isolationTarget's .zenzic.toml is loaded; caller's config is never consulted
Sandbox guardPath traversal guard scan scope is anchored at docs_root, not cwd
No CWD mutationinit writes to the target; caller's working directory is untouched
Hint displayBanner shows the resolved target path for operator confidence

Exit Codes

Zenzic uses a structured exit code contract:

CodeNameMeaningSuppressed by --exit-zero?
0CleanNo issues found, or --exit-zero activeN/A
1FindingsDocumentation quality issues detectedYes
2Credential ScannerLeaked credential detected by Zenzic credential scannerNo
3Path Traversal GuardPath traversal to OS system directory detectedNo

Exit Code 0 -- Clean

All checks passed. No errors, no warnings (or --exit-zero is active and only non-security findings were found).

Exit Code 1 -- Findings

Documentation quality issues were detected: broken links, orphan pages, invalid snippets, placeholder pages, unused assets, dangling references, or dead definitions (in strict mode).

Can be suppressed by exit_zero = true in config or --exit-zero on the CLI.

Exit Code 2 -- Credential Scanner

A leaked credential was detected by the Zenzic credential scanner. This exit code is never suppressed by --exit-zero or exit_zero = true. The credential must be rotated immediately.

Exit Code 3 -- Path Traversal Guard

A documentation link resolves to an OS system path (/etc/, /root/, /var/, /proc/, /sys/, /usr/). This is classified as PATH_TRAVERSAL_SUSPICIOUS -- a security incident that indicates a potential template injection, compromised toolchain, or infrastructure disclosure.

Exit code 3 has the highest priority. If both credential scanner and path traversal guard findings exist in the same run, exit code 3 wins.

The path traversal guard also fires when docs_dir itself resolves outside the repository root (F4-1 jailbreak protection).


DFA Guarantee — The RE2 Engine

Zenzic uses exclusively a DFA (Deterministic Finite Automaton) engine for all user-supplied regex patterns. This is a hard architectural constraint, not a configuration option.

What this means

Every pattern declared in [[custom_rules]] inside .zenzic.toml is compiled at load time by the RE2 library (via google-re2). RE2 implements a true DFA: it processes the input string in a single left-to-right pass, with no backtracking.

This guarantees that every regex validation runs in time linear in the length of the input:

O(n)O(n)

where nn is the length of the line being scanned. The time complexity is bounded regardless of the pattern structure. ReDoS (Regular Expression Denial of Service) is mathematically impossible under this engine.

What RE2 rejects

RE2 rejects patterns that require NFA backtracking. If a [[custom_rules]] pattern uses any of these constructs, Zenzic raises a PluginContractError at startup — before any file is scanned:

ConstructExampleReason
Backreferences(\w+)\1Requires memory of a previous capture — non-regular
Positive lookaheadfoo(?=bar)Requires speculative forward scanning
Negative lookaheadfoo(?!bar)Requires speculative forward scanning
Lookbehind(?<=foo)barRequires backward scanning

What RE2 accepts

RE2 is a superset of standard POSIX ERE syntax. Patterns like these compile and run correctly:

FeatureExample
Literal textinternal\.corp\.example\.com
AlternationDRAFT|WIP|TODO
Repetition[0-9]{3}-[0-9]{4}
Inline flags(?i)\bDRAFT\b (case-insensitive), (?m)^todo (multiline)
Named groups(?P<code>Z\d{3})
Classic "dangerous" quantifiers(a+)+ — safe under RE2, runs in O(n)

The DFA Purity Contract

Every [[custom_rules]] pattern is compiled with RE2 at load time. A pattern either compiles — and is therefore O(n)O(n) safe — or it does not, and the startup fails with an actionable PluginContractError message. There is no runtime canary, no SIGALRM timer, and no platform-specific divergence.


Algorithmic Complexity

Zenzic's architecture separates computational intents by applying well-defined algorithmic bounds to each domain:

  • Topology (Knowledge Graph): Link validation runs an iterative DFS on the Virtual Site Map adjacency graph. Using an adjacency-list representation, traversal complexity is Θ(V+E)\Theta(V+E), where VV represents pages/assets and EE represents links. Cycle registries are stored as hash sets, providing average O(1)O(1) lookups during subsequent validation passes.

  • Semantic Scanning: Credential scanning and custom rules use the google-re2 linear-time regex engine instead of Python's standard re module. This ensures O(N)O(N) evaluation without catastrophic backtracking, eliminating ReDoS vulnerabilities based on exponential backtracking behavior.

  • I/O Discovery: File ingestion operates in O(N)O(N) complexity relative to the total volume of processed data. Wall-time can be reduced through parallel process pools when processing large file sets, without changing the underlying computational complexity.


Hybrid Adaptive Engine

The scan engine automatically selects sequential or parallel execution based on the number of files:

ConditionModeBehaviour
workers = 1 (default) or file count < 50SequentialZero process-spawn overhead. Full O(N) I/O.
workers != 1 and file count >= 50ParallelProcessPoolExecutor with per-file distribution

The threshold (50 files) is a conservative heuristic: below it, process-spawn overhead exceeds the parallelism benefit. Results are always sorted by file_path regardless of execution mode.


Sovereign CLI — No Integrations Layer

Zenzic operates as a standalone CLI with adapter-driven external analysis. Build engines are never extended at runtime by Zenzic components.

Why: An embedded engine hook couples Zenzic's release cycle to an external build tool API. It also prevents a single, uniform enforcement surface across engines. The CLI pipeline provides full credential scanner and path traversal hardening for every adapter. A Sovereign CLI decouples quality gating from build tooling and makes every enforcement point engine-agnostic.

Run Zenzic in CI as an external quality gate via zenzic check all --strict in a workflow step. This produces identical enforcement with the full VSM, credential scanner (ZRT-006/007), and path traversal guard active — without any runtime integration inside the build engine.

See CI/CD Integration for workflow examples.

Extension Pattern

External tools that need to invoke Zenzic checks programmatically can use the public Python API directly (zenzic.core.scanner, zenzic.models.config). All intelligence lives in zenzic.core. The CLI is a thin dispatch layer over the same functions — there is no hidden logic that requires the subprocess path.


CLI Layer

The CLI is structured as a package (src/zenzic/cli/), not a monolithic module. This separation enforces single-responsibility at the module level and makes the visual output pipeline auditable as a first-class concern.

Package layout


src/zenzic/cli/
├── __init__.py # Public re-export surface for main.py — no logic
├── _shared.py # Visual State Guardian: console singleton, _ui singleton, utilities
├── _check.py # check_app sub-app + 7 check commands
├── _clean.py # clean_app sub-app + clean assets
├── _config_explain.py # explain command + config introspection surface
├── _governance.py # config_app sub-app + governance profile commands
├── _guard.py # guard_app sub-app + secret-guard scan/init commands
├── _inspect.py # inspect_app sub-app + capabilities command
├── _lab.py # lab command — interactive example showcase
├── _metadata.py # SSOT for top-level command metadata and help panels
└── _standalone.py # score, diff, init commands

UI State Manager

_shared.py is the sole owner of all console and UI state. It exposes two dynamic getters:

GetterReturns
get_console()The current rich.console.Console singleton
get_ui()The current ZenzicUI singleton (wraps the console)

configure_console() — called by the --no-color / --force-color Typer callback in main.pyreplaces both singletons atomically. Because every command calls get_ui() / get_console() at invocation time rather than import time, they always receive the instance that reflects the user's color flags.

Invariant: force_terminal on the module-level Console is always None (auto-detect via sys.stdout.isatty()). Passing force_terminal=False silently disables color even in interactive terminals — this is a latent bug pattern the architecture explicitly guards against.

Startup banner flow

The banner always writes to stdout (the shared _shared.console) so it uses the same color-detection stream as the subsequent command output. The Typer callback runs after the banner, which is acceptable — the module-level console already uses force_terminal=None (auto-detect) at startup.

_SUBAPPS_WITH_MENU in cli_main() covers sub-apps that use no_args_is_help=True: invoking zenzic check, zenzic clean, or zenzic inspect with no further arguments shows the Typer help page; the banner is prepended by the explicit bare-invocation check.

Extension points

GoalAction
Add a command to an existing sub-appAdd @check_app.command() (or other app) in the relevant _*.py — no changes to __init__.py or main.py
Add a new top-level sub-appCreate _myfeature.py, export from __init__.py, register in main.py, add to _SUBAPPS_WITH_MENU if no_args_is_help=True
Add a shared utilityAdd to _shared.py and import via from . import _shared — never instantiate Console or ZenzicUI locally

Visual identity — zenzic.core.ui

ZenzicPalette, ZenzicUI, make_banner, emoji, and SUPPORTS_COLOR live in src/zenzic/core/ui.py. The core layer owns the visual identity so ZenzicReporter (which is also in core/) can import them without looking upward. The CLI layer imports from zenzic.core.ui directly. The compatibility stub at src/zenzic/ui.py re-exports everything for any third-party code that was already importing from the old path.


zenzic-doc — Living Test Bench

The Zenzic documentation site (zenzic-doc) is not a passive artifact — it is an active participant in the quality pipeline. Every commit runs zenzic check all --strict against itself before it can be pushed (Sovereign Parity, ZRT-010).

Beyond the standard Zenzic audit, zenzic-doc enforces a second invariant unique to its role as documentation for a linter: every Zxxx finding code present in docs/ must have a registered entry in src/zenzic/core/codes.py in the Core package — and vice versa. This bidirectional parity is enforced by the verify-codes-parity Nox session via Sovereign Resolution (Fail-Closed):

  • Author environment: ZENZIC_CORE_PATH set → uv run --project <path> against local source.
  • Core path not found: the session fails closed; PyPI fallback is prohibited.

Running just verify in zenzic-doc executes the full lifecycle-gate flow with one entry-point. Contributors must provide a local checkout path for Zenzic Core (ZENZIC_CORE_PATH, ./_zenzic_core, or ../zenzic).


The 4-Lifecycle-Gates Standard

Every repository in the Zenzic ecosystem enforces quality at four progressive checkpoints:

GateTriggerToolWhat it catches
Gate 1 — IDEReal-time, on saveEditor extensions (Pylance, ESLint)Type errors, syntax issues
Gate 2 — Pre-commitgit commitpre-commit hooksLint, credentials, formatting, REUSE
Gate 3 — Pre-pushgit pushjust verify (via pre-commit -t pre-push)Full validation suite identical to CI
Gate 4 — Remote CIPull Request / pushGitHub ActionsIdentical just verify on a clean Ubuntu runner

Gates 3 and 4 execute the same command (just verify) — local and remote are never allowed to drift. This is the Sovereign Parity principle (ZRT-010).

For operational setup (installing hooks, workflow YAML), see the CI/CD Integration guide.


Choosing the Right Model

ScenarioRecommended approach
CI pipeline for any enginezenzic check all — add a step, no plugin needed
Pre-commit credential gatezenzic check references — registers as a pre-commit hook
Custom engine not yet supportedWrite an Adapter — ship as a separate package, register via zenzic.adapters entry-point
Migrate from MkDocs to DocusaurusUse zenzic check all with engine = "mkdocs" on the source, engine = "docusaurus" on the target

See CI/CD Integration for step-by-step workflows corresponding to each scenario.


Brand Integrity

The Exclusion Zone model extends beyond structural correctness. A codebase or documentation suite that contains stale brand identifiers carries a different kind of debt: narrative debt. A page that still refers to a codename contradicts the contract it is trying to document.

Zenzic addresses this through the [governance] configuration block and the Z601 BRAND_OBSOLESCENCE finding. Format-aware, per-line suppression is available for intentional historical references using the appropriate comment syntax for the file type (.md vs. .mdx).