<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>The Zenzic Blog — Zenzic Engineering Blog</title>
        <link>https://zenzic.dev/blog</link>
        <description>Engineering insights, security post-mortems, and the evolution of Zenzic.</description>
        <lastBuildDate>Thu, 07 May 2026 10:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>© 2026 PythonWoods</copyright>
        <item>
            <title><![CDATA[Log: v0.7.0 — Quartz Maturity]]></title>
            <link>https://zenzic.dev/blog/log-v070-quartz-maturity</link>
            <guid>https://zenzic.dev/blog/log-v070-quartz-maturity</guid>
            <pubDate>Thu, 07 May 2026 10:00:00 GMT</pubDate>
            <description><![CDATA[Patch-notes leggibili in 30 secondi: 4-Gates Standard, Z907 I18N_PARITY, Multi-Root Discovery, Zero-Config Sovereignty, Virtual Routes, breaking changes, migration path.]]></description>
            <content:encoded><![CDATA[
<!-- -->
<blockquote>
<p><em>Log convention — the terse mirror of <code>RELEASE.md</code>. For the narrative
deep-dive, see <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga V — Beyond the
Siege</a>.</em></p>
</blockquote>
<p><strong>TL;DR.</strong> Zenzic v0.7.0 "Quartz Maturity" closes the post-Obsidian arc with
seven epochs: the <strong>4-Gates Standard</strong> (EPOCH 4), the language-agnostic
<strong>Z907 I18N_PARITY</strong> check (EPOCH 5), <strong>Cross-Instance Sovereignty</strong> now
Zero-Config (EPOCH 6), <strong>Multi-Root Discovery</strong> (EPOCH 7a), <strong>Zero-Config
Sovereignty</strong> with <code>[link_validation]</code> removed (EPOCH 7a.1), and <strong>Virtual
Routes</strong> with the <code>zenzic inspect routes</code> JSON API (EPOCH 7b).
1,485+ tests. Some breaking changes; clean migration path.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="breaking-changes">Breaking changes<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#breaking-changes" class="hash-link" aria-label="Direct link to Breaking changes" title="Direct link to Breaking changes" translate="no">​</a></h2>
<table><thead><tr><th>From (≤ v0.6.x)</th><th>To (v0.7.0)</th><th>Migration</th></tr></thead><tbody><tr><td><code>engine = "vanilla"</code></td><td><code>engine = "standalone"</code></td><td>rename in <code>zenzic.toml</code></td></tr><tr><td>MkDocs plugin (in-tree)</td><td>external adapter</td><td>drop dependency, use Sentinel CLI</td></tr><tr><td><code>just preflight</code></td><td><code>just verify</code></td><td>recipe rename — same 4-Gates content</td></tr><tr><td>Hook id <code>zenzic-check-all</code></td><td><code>zenzic-verify</code></td><td>bump <code>rev:</code> to <code>v0.7.0</code></td></tr><tr><td><code>[link_validation]</code> TOML schema</td><td><em>(removed)</em></td><td>delete the block — URL prefixes auto-detected</td></tr></tbody></table>
<p>No silent deprecation shims. Industry-grade only.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-4--the-safe-port-4-gates-standard">EPOCH 4 — The Safe Port (4-Gates Standard)<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-4--the-safe-port-4-gates-standard" class="hash-link" aria-label="Direct link to EPOCH 4 — The Safe Port (4-Gates Standard)" title="Direct link to EPOCH 4 — The Safe Port (4-Gates Standard)" translate="no">​</a></h2>
<p>A single command runs every quality gate locally:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">just verify</span><br></div></code></pre></div></div>
<p>Sequence: <code>pre-commit</code> → <code>pytest</code> (with coverage) → <code>zenzic check all --strict</code> → exit-code parity self-test. The same four gates run in CI; what
passes locally passes in the cloud.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-5--z907-i18n_parity">EPOCH 5 — Z907 I18N_PARITY<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-5--z907-i18n_parity" class="hash-link" aria-label="Direct link to EPOCH 5 — Z907 I18N_PARITY" title="Direct link to EPOCH 5 — Z907 I18N_PARITY" translate="no">​</a></h2>
<p>Language-agnostic translation parity check. Configure in <code>zenzic.toml</code>:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">i18n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">enabled</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">default_locale</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"en"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">locales</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"en"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"it"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">parity_strict</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">true</span><br></div></code></pre></div></div>
<p>When <code>parity_strict = true</code>, every page in <code>default_locale</code> must have a
mirror in every other locale. Missing translations surface as <code>Z907 I18N_PARITY</code> findings.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-6--cross-instance-sovereignty-zero-config">EPOCH 6 — Cross-Instance Sovereignty (Zero-Config)<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-6--cross-instance-sovereignty-zero-config" class="hash-link" aria-label="Direct link to EPOCH 6 — Cross-Instance Sovereignty (Zero-Config)" title="Direct link to EPOCH 6 — Cross-Instance Sovereignty (Zero-Config)" translate="no">​</a></h2>
<p>Multi-instance Docusaurus setups (<code>docs/</code>, <code>developers/</code>, every additional
<code>@docusaurus/plugin-content-docs</code> instance) are now fully supported without
manual TOML configuration. <code>DocusaurusAdapter.get_absolute_url_prefixes(repo_root)</code>
discovers every plugin's <code>routeBasePath</code> via static parsing of
<code>docusaurus.config.{ts,js,mjs,cjs}</code> — zero subprocess, zero allowlist, zero
duplication.</p>
<p>:::note Historical note
An earlier draft of EPOCH 6 shipped a manual <code>[link_validation].absolute_path_allowlist</code>
field. That approach was abandoned. The Zero-Config implementation superseded it
entirely in EPOCH 7a.1.
:::</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-7a--multi-root-discovery">EPOCH 7a — Multi-Root Discovery<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-7a--multi-root-discovery" class="hash-link" aria-label="Direct link to EPOCH 7a — Multi-Root Discovery" title="Direct link to EPOCH 7a — Multi-Root Discovery" translate="no">​</a></h2>
<p>The VSM is no longer bounded by <code>docs_dir</code>. The Docusaurus adapter auto-detects
the <code>blog/</code> plugin via two pure-parsing passes (static regex over config, then
convention fallback). Blog posts are first-class content: broken links inside
<code>blog/</code> and cross-tree links from <code>docs/</code> to <code>blog/</code> are caught by
<code>zenzic check all --strict</code>. A Reverse-Mapping invariant test asserts every
blog <code>Route.source</code> traces back to a real file on disk.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-7a1--zero-config-sovereignty">EPOCH 7a.1 — Zero-Config Sovereignty<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-7a1--zero-config-sovereignty" class="hash-link" aria-label="Direct link to EPOCH 7a.1 — Zero-Config Sovereignty" title="Direct link to EPOCH 7a.1 — Zero-Config Sovereignty" translate="no">​</a></h2>
<p>The <code>[link_validation]</code> TOML schema is <strong>removed</strong>. <code>LinkValidationConfig</code> and
<code>absolute_path_allowlist</code> are gone from the codebase. Configs that still declare
<code>[link_validation]</code> raise a TOML validation error. <strong>Migration:</strong> delete the
block — <code>DocusaurusAdapter</code> discovers plugin URL prefixes automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epoch-7b--virtual-routes--zenzic-inspect-routes">EPOCH 7b — Virtual Routes &amp; <code>zenzic inspect routes</code><a href="https://zenzic.dev/blog/log-v070-quartz-maturity#epoch-7b--virtual-routes--zenzic-inspect-routes" class="hash-link" aria-label="Direct link to epoch-7b--virtual-routes--zenzic-inspect-routes" title="Direct link to epoch-7b--virtual-routes--zenzic-inspect-routes" translate="no">​</a></h2>
<p>Engine-generated pages — tag indexes, paginated blog lists, author profiles —
are now first-class VSM citizens with the Reverse-Mapping Invariant enforced at
construction time. Three new finding codes:</p>
<table><thead><tr><th>Code</th><th>Level</th><th>Trigger</th></tr></thead><tbody><tr><td><strong>Z111 VIRTUAL_ROUTE_BROKEN</strong></td><td>Error</td><td>docs link targets a tag URL no blog post activates</td></tr><tr><td><strong>Z113 AUTHOR_KEY_COLLISION</strong></td><td>Error</td><td>duplicate author keys in <code>authors.yml</code></td></tr><tr><td><strong>Z114 LARGE_PAGINATION_SET</strong></td><td>Info</td><td>pagination set exceeds 200 pages</td></tr></tbody></table>
<p>New CLI command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic inspect routes </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">--kind physical</span><span class="token operator">|</span><span class="token plain">virtual</span><span class="token operator">|</span><span class="token plain">all</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">--json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>Exports the complete site map as deterministic JSON with per-route <code>url</code>, <code>kind</code>,
<code>source_files</code> (repo-relative POSIX), and <code>digest</code>. When <code>--json</code> is active,
<code>stdout</code> is exclusively valid JSON — no ANSI codes, no banners.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="migration-path">Migration path<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#migration-path" class="hash-link" aria-label="Direct link to Migration path" title="Direct link to Migration path" translate="no">​</a></h2>
<p>The full migration matrix lives in
<a href="https://github.com/PythonWoods/zenzic/blob/main/RELEASE.md#breaking-changes" target="_blank" rel="noopener noreferrer" class=""><code>RELEASE.md</code></a>
under "Breaking changes". One pass through the table is usually enough.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="saga-deep-dive">Saga deep-dive<a href="https://zenzic.dev/blog/log-v070-quartz-maturity#saga-deep-dive" class="hash-link" aria-label="Direct link to Saga deep-dive" title="Direct link to Saga deep-dive" translate="no">​</a></h2>
<p>For the philosophy, the post-mortem of the AI-driven siege, and the
engineering choices behind Quartz Maturity, see <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz"><strong>Saga V — Beyond the
Siege</strong></a>.</p>]]></content:encoded>
            <category>Release</category>
            <category>Milestone Record</category>
            <category>Engineering Chronicles</category>
        </item>
        <item>
            <title><![CDATA[Quartz Maturity]]></title>
            <link>https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable</link>
            <guid>https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable</guid>
            <pubDate>Thu, 07 May 2026 10:00:00 GMT</pubDate>
            <description><![CDATA[Zenzic v0.7.0 is stable. The paradigm shift: a file is an orphan not when it's missing from disk, but when it's invisible to the human eye. UX-Discoverability, 20 Lab Acts, SARIF, and the Safe Harbor — all in one release.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> | <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> | <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Saga III</a> | <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga IV</a> | <strong>Saga V</strong> | <a class="" href="https://zenzic.dev/blog/governance-of-quartz">Saga VI</a></p></div></div>
<p>During the final consolidation sprint for v0.7.0, we ran four AI agents against Zenzic's
own documentation site. They were instructed to find everything wrong with it.</p>
<!-- -->
<p>They found a file that Zenzic had marked <strong>REACHABLE</strong>. The file existed on disk, had
valid frontmatter, and was not referenced by any broken link. By every metric in the
pre-v0.7.0 implementation, it was clean.</p>
<p>The file had not appeared in the sidebar for three weeks. No navbar entry linked to it.
No footer item referenced it. A user navigating the documentation site had no way to
reach it by clicking. It was, from the reader's perspective, gone.</p>
<p>Zenzic was asking the wrong question.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-wrong-question">The Wrong Question<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-wrong-question" class="hash-link" aria-label="Direct link to The Wrong Question" title="Direct link to The Wrong Question" translate="no">​</a></h2>
<p>Traditional documentation linters ask: <em>does the file exist on disk?</em></p>
<p>That is the correct question for a filesystem validator. It is the wrong question for a
documentation integrity tool. Documentation is not a filesystem. It is an experience
delivered through navigation surfaces — sidebar menus, navbar links, footer references.
A file that exists on disk but appears on none of those surfaces is, for all practical
purposes, invisible to every reader who visits your site.</p>
<p>Zenzic v0.7.0 asks a different question:</p>
<blockquote>
<p><em>Can a user reach this file by clicking through the navigation of your documentation site?</em></p>
</blockquote>
<p>If the answer is no, the file is an orphan — regardless of whether it exists on disk.</p>
<p>This is the <strong>UX-Discoverability Law</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="rule-r21-the-ux-discoverability-law">Rule R21: The UX-Discoverability Law<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#rule-r21-the-ux-discoverability-law" class="hash-link" aria-label="Direct link to Rule R21: The UX-Discoverability Law" title="Direct link to Rule R21: The UX-Discoverability Law" translate="no">​</a></h2>
<p>The formal rule, added to Zenzic's rule registry in D090:</p>
<blockquote>
<p><strong>R21 — UX-Discoverability</strong>: A file is REACHABLE if and only if at least one
user-clickable navigation surface declares it. A file absent from all navigation
surfaces is ORPHAN_BUT_EXISTING, regardless of its presence on disk.</p>
</blockquote>
<p>For Docusaurus projects, "navigation surfaces" means exactly three sources:</p>
<table><thead><tr><th style="text-align:left">Surface</th><th style="text-align:left">Config location</th><th style="text-align:left">Detection method</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Sidebar</strong></td><td style="text-align:left"><code>sidebars.ts</code> / <code>sidebars.js</code></td><td style="text-align:left"><code>type: 'doc'</code> entries and bare string IDs</td></tr><tr><td style="text-align:left"><strong>Navbar</strong></td><td style="text-align:left"><code>themeConfig.navbar.items</code></td><td style="text-align:left"><code>to:</code> URL paths, <code>docId:</code> references</td></tr><tr><td style="text-align:left"><strong>Footer</strong></td><td style="text-align:left"><code>themeConfig.footer.links</code></td><td style="text-align:left"><code>to:</code> URL paths</td></tr></tbody></table>
<p>Zenzic parses all three statically — no Node.js, no build step, no external process.
The implementation is pure Python, respects Pillar 2 (zero subprocesses), and handles
both <code>.ts</code> and <code>.js</code> sidebar files. JS-style comments are stripped before parsing.
<code>baseUrl</code> and <code>routeBasePath</code> prefixes are normalised before path resolution.</p>
<p>A single <code>frozenset[str]</code> is returned to the Core, which applies the orphan rule
uniformly across all engines. The Core knows nothing about sidebars, navbars, or
footers. It receives a set of navigable paths and compares them against disk contents.
That is the entire contract.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-surface-parser-in-practice">The Three-Surface Parser in Practice<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-three-surface-parser-in-practice" class="hash-link" aria-label="Direct link to The Three-Surface Parser in Practice" title="Direct link to The Three-Surface Parser in Practice" translate="no">​</a></h2>
<p>Given a project with an explicit <code>sidebars.ts</code> that deliberately omits <code>changelog.mdx</code>:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// sidebars.ts — changelog is not listed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> sidebars </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  docs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'intro'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'guide/index'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'guide/deploy'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> sidebars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>And a <code>docusaurus.config.ts</code> that links it from the navbar:</p>
<div class="language-typescript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-typescript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">themeConfig</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  navbar</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    items</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> to</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/docs/changelog'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Changelog'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> position</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'right'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><br></div></code></pre></div></div>
<p>Pre-v0.7.0: <code>changelog.mdx</code> → <strong>ORPHAN_BUT_EXISTING</strong> (not in sidebar).</p>
<p>Post-v0.7.0: <code>changelog.mdx</code> → <strong>REACHABLE</strong> (navbar declares it).</p>
<p>The same logic applies to footer-only files. A legal notice linked only in the footer
has been invisible to Zenzic's orphan detector until now. In v0.7.0, it is REACHABLE
because a user can reach it by clicking.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architectural-philosophy-a-different-axis">Architectural Philosophy: A Different Axis<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#architectural-philosophy-a-different-axis" class="hash-link" aria-label="Direct link to Architectural Philosophy: A Different Axis" title="Direct link to Architectural Philosophy: A Different Axis" translate="no">​</a></h2>
<p>Documentation quality tools exist on a spectrum. Most operate on one axis — link
existence — and stop there. Zenzic operates on a different axis entirely: <strong>trust</strong>.</p>
<p><code>markdown-link-check</code> and <code>htmlproofer</code> are correct tools for what they declare. They
are link checkers — fast, composable, well-understood. Zenzic is not a link checker
that also happens to check orphans and scan for secrets. It is a <strong>Static Analysis
Framework</strong> built on a fundamentally different trust model. Comparing them on a
feature checklist alone is like comparing a compiler and a formatter because both
read source files.</p>
<p>The meaningful comparison is architectural:</p>
<table><thead><tr><th style="text-align:left">Design Dimension</th><th style="text-align:left">Generic Link Checkers</th><th style="text-align:left">Zenzic Sentinel Guard</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Trust Model</strong></td><td style="text-align:left">Trusts the source content</td><td style="text-align:left"><strong>Zero-Trust</strong> — every file is untrusted input</td></tr><tr><td style="text-align:left"><strong>Site Awareness</strong></td><td style="text-align:left">Filesystem-only</td><td style="text-align:left"><strong>Virtual Site Map</strong> — engine-aware projection</td></tr><tr><td style="text-align:left"><strong>Security Layer</strong></td><td style="text-align:left">None</td><td style="text-align:left"><strong>Shield</strong> (9 secret families) + <strong>Blood Sentinel</strong> (path traversal)</td></tr><tr><td style="text-align:left"><strong>CI/CD Footprint</strong></td><td style="text-align:left">Requires build step or Node.js</td><td style="text-align:left"><strong>Pure Python</strong>, subprocess-free, <code>uvx</code>-ready</td></tr><tr><td style="text-align:left"><strong>Diagnostic System</strong></td><td style="text-align:left">Free-form messages</td><td style="text-align:left"><strong>Zxxx Registry</strong> — traceable, filterable codes</td></tr><tr><td style="text-align:left"><strong>Orphan Detection</strong></td><td style="text-align:left">Not in scope</td><td style="text-align:left"><strong>R21 Law</strong> — nav-surface awareness</td></tr><tr><td style="text-align:left"><strong>Output Format</strong></td><td style="text-align:left">Text / exit code</td><td style="text-align:left">Text, JSON, <strong>SARIF 2.1.0</strong></td></tr></tbody></table>
<p>The choice of which tool to run depends on which question you need to answer. If the
question is <em>"do these links resolve?"</em>, a link checker is the right tool. If the
question is <em>"is this documentation safe, complete, and navigable?"</em>, the trust model
matters — and Zero-Trust is the only honest answer when documentation is produced by
teams, contributors, or automated agents you do not fully control.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-safe-harbor-what-v070-completes">The Safe Harbor: What v0.7.0 Completes<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-safe-harbor-what-v070-completes" class="hash-link" aria-label="Direct link to The Safe Harbor: What v0.7.0 Completes" title="Direct link to The Safe Harbor: What v0.7.0 Completes" translate="no">​</a></h2>
<p>The term "Safe Harbor" has been in Zenzic's vocabulary since the
<a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">first engineering post</a>. This is what it
means in v0.7.0:</p>
<table><thead><tr><th style="text-align:left">Pillar</th><th style="text-align:left">Guarantee</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Engine parity</strong></td><td style="text-align:left">Z404 config asset checking in Docusaurus, MkDocs, and Zensical</td></tr><tr><td style="text-align:left"><strong>Core purity</strong></td><td style="text-align:left"><code>validator.py</code> contains zero engine-name references (Purity Protocol)</td></tr><tr><td style="text-align:left"><strong>Zero-trust input</strong></td><td style="text-align:left">Documentation treated as untrusted input; Shield operates on every scan</td></tr><tr><td style="text-align:left"><strong>Sovereign root</strong></td><td style="text-align:left"><code>zenzic.toml</code> follows the target, not the caller — monorepo-safe</td></tr><tr><td style="text-align:left"><strong>SARIF integration</strong></td><td style="text-align:left">All findings in GitHub Code Scanning format (<code>--format sarif</code>)</td></tr><tr><td style="text-align:left"><strong>Diagnostic traceability</strong></td><td style="text-align:left">Every finding carries a Zxxx code with severity, message, and fix</td></tr><tr><td style="text-align:left"><strong>Verified test surface</strong></td><td style="text-align:left">1,485+ passing tests, mutant-tested boundaries, cross-platform CI</td></tr><tr><td style="text-align:left"><strong>UX-Discoverability</strong></td><td style="text-align:left">Navbar + footer harvesting — orphan detection sees what readers see</td></tr></tbody></table>
<p>This is not a list of aspirational features. Each row has a test class, a CHANGELOG
entry, and a decision record documenting the architectural choice that makes it true.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sarif-documentation-quality-in-your-security-dashboard">SARIF: Documentation Quality in Your Security Dashboard<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#sarif-documentation-quality-in-your-security-dashboard" class="hash-link" aria-label="Direct link to SARIF: Documentation Quality in Your Security Dashboard" title="Direct link to SARIF: Documentation Quality in Your Security Dashboard" translate="no">​</a></h2>
<p>Starting with v0.7.0, every <code>zenzic check</code> command supports <code>--format sarif</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all ./docs </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--format</span><span class="token plain"> sarif </span><span class="token operator">&gt;</span><span class="token plain"> results.sarif</span><br></div></code></pre></div></div>
<p>The output is valid
<a href="https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html" target="_blank" rel="noopener noreferrer" class="">SARIF 2.1.0</a>,
consumable directly by GitHub Code Scanning. Add this to your CI workflow:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Run Zenzic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> uvx zenzic check all ./docs </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">format sarif </span><span class="token punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> zenzic.sarif</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Upload SARIF</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> github/codeql</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">action/upload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">sarif@v3</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">sarif_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> zenzic.sarif</span><br></div></code></pre></div></div>
<p>Your documentation findings — broken links, orphan pages, credential fragments,
missing assets — appear in the <strong>Security</strong> tab of your repository, tracked alongside
code vulnerabilities. Findings are filterable by Zxxx code, assignable, and closeable
through the same workflow as any other security advisory.</p>
<p>The documentation pipeline is now a first-class citizen of your security posture.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-purity-protocol-zero-engine-leaks-in-core">The Purity Protocol: Zero Engine Leaks in Core<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-purity-protocol-zero-engine-leaks-in-core" class="hash-link" aria-label="Direct link to The Purity Protocol: Zero Engine Leaks in Core" title="Direct link to The Purity Protocol: Zero Engine Leaks in Core" translate="no">​</a></h2>
<p>One invariant that emerged from the consolidation and defines the v0.7.0 architecture:
<code>validator.py</code> — the heart of Zenzic — contains no reference to any engine by name.
No "docusaurus", no "sidebar", no "navbar". The Core receives a <code>frozenset[str]</code> of
navigable paths from <code>adapter.get_nav_paths()</code>. What lives inside that method is the
adapter's problem, not the Core's.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># validator.py — the entire engine interface</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">nav_paths </span><span class="token operator">=</span><span class="token plain"> adapter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_nav_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># frozenset[str] | frozenset()</span><br></div></code></pre></div></div>
<p>For Docusaurus, <code>get_nav_paths()</code> is a Multi-Source Harvester: it merges sidebar IDs,
navbar <code>to:</code> paths, and footer <code>to:</code> paths into a single frozenset before returning it.
The Core never sees the difference. For MkDocs, the same method returns <code>nav:</code> entries.
For Zensical, it returns <code>nav:</code> entries. For Standalone, it returns <code>frozenset()</code>.</p>
<p>One interface. Four adapters. Zero engine leaks in Core.</p>
<p>Adding a new adapter that modifies <code>validator.py</code> is a protocol violation. The
adapter contract is the boundary — everything engine-specific must live behind it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-zenzic-lab-20-acts">The Zenzic Lab: 20 Acts<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-zenzic-lab-20-acts" class="hash-link" aria-label="Direct link to The Zenzic Lab: 20 Acts" title="Direct link to The Zenzic Lab: 20 Acts" translate="no">​</a></h2>
<p>The Lab is the fastest way to understand what Zenzic does. Run it now:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic lab</span><br></div></code></pre></div></div>
<p>Twenty interactive Acts, each demonstrating a distinct capability against
bundled fixture projects:</p>
<table><thead><tr><th style="text-align:left">Acts</th><th style="text-align:left">Coverage</th></tr></thead><tbody><tr><td style="text-align:left">Acts 0–2</td><td style="text-align:left">MkDocs foundations — linter demo, gold standard, broken docs</td></tr><tr><td style="text-align:left">Act 3</td><td style="text-align:left">Shield — credential exposure (security breach, exit 2)</td></tr><tr><td style="text-align:left">Acts 4–5</td><td style="text-align:left">Scoped targets — single file, custom directory</td></tr><tr><td style="text-align:left">Act 6</td><td style="text-align:left">Zensical — transparent proxy (SENTINEL bridge)</td></tr><tr><td style="text-align:left">Act 7</td><td style="text-align:left">Docusaurus v3 enterprise — versioning, @site/ aliases, i18n</td></tr><tr><td style="text-align:left">Act 8</td><td style="text-align:left">Standalone Mode — full scan, zero nav contract</td></tr><tr><td style="text-align:left">Acts 9–10</td><td style="text-align:left">Config asset guards — Z404 (MkDocs + Zensical)</td></tr><tr><td style="text-align:left">Acts 11–12</td><td style="text-align:left">OS security — Unix path traversal, Windows path integrity</td></tr><tr><td style="text-align:left">Acts 13–14</td><td style="text-align:left">Rules deep-dive — link graph stress, Shield obfuscation</td></tr><tr><td style="text-align:left">Acts 15–16</td><td style="text-align:left">Quality rules — SEO coverage (Z401/Z402), quality gate (Z501/Z503)</td></tr><tr><td style="text-align:left">Acts 17–18</td><td style="text-align:left">Quality scoring — penalty scorer, score regression scenarios</td></tr><tr><td style="text-align:left">Act 19</td><td style="text-align:left">The Base64 Shadow — encoded credential detection</td></tr></tbody></table>
<p>No configuration required. No project to set up. The Lab runs against bundled
fixture projects — no temporary files, no teardown required. The entire experience
runs in under 90 seconds on a cold start.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-documentation-of-the-documentation-tool">The Documentation of the Documentation Tool<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#the-documentation-of-the-documentation-tool" class="hash-link" aria-label="Direct link to The Documentation of the Documentation Tool" title="Direct link to The Documentation of the Documentation Tool" translate="no">​</a></h2>
<p>One constraint that emerged during the Diátaxis restructure of <code>zenzic.dev</code>: a
documentation tool that ships with poorly organised documentation is not a credible
authority on documentation quality.</p>
<p>The site now follows the <a href="https://diataxis.fr/" target="_blank" rel="noopener noreferrer" class="">Diátaxis framework</a> across four modes:</p>
<ul>
<li class=""><strong>Tutorials</strong> — step-by-step learning paths for new users</li>
<li class=""><strong>How-To Guides</strong> — task-oriented instructions for specific problems</li>
<li class=""><strong>Reference</strong> — the complete Zxxx diagnostic registry, CLI interface, and engine specs</li>
<li class=""><strong>Explanation</strong> — the architectural decisions, security model, and design philosophy</li>
</ul>
<p>Every URL changed in the restructure. The Sovereign Root Protocol found all three
<code>README.md</code> links pointing at the old paths before any user reported them. The tool
caught its own documentation drift.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="get-started-in-30-seconds">Get Started in 30 Seconds<a href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable#get-started-in-30-seconds" class="hash-link" aria-label="Direct link to Get Started in 30 Seconds" title="Direct link to Get Started in 30 Seconds" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic lab</span><br></div></code></pre></div></div>
<p>No installation required. <code>uvx</code> resolves and runs Zenzic from PyPI in a temporary
environment. The Lab will walk you through every capability interactively.</p>
<p>For a permanent installation:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uv </span><span class="token function" style="color:rgb(80, 250, 123)">add</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--dev</span><span class="token plain"> zenzic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># or</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> zenzic</span><br></div></code></pre></div></div>
<p>Then run a check against your documentation:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all ./docs</span><br></div></code></pre></div></div>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr><tr><td><strong>PyPI</strong></td><td><a href="https://pypi.org/project/zenzic/" target="_blank" rel="noopener noreferrer" class="">pypi.org/project/zenzic</a></td></tr><tr><td><strong>Changelog</strong></td><td><a href="https://github.com/PythonWoods/zenzic/releases/tag/v0.7.0" target="_blank" rel="noopener noreferrer" class="">v0.7.0 Release Notes</a></td></tr></tbody></table>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>The Zenzic Chronicles</div><div class="admonitionContent_BuS1"><p>This is <strong>Part 5</strong> of a five-part engineering series documenting the path from v0.5 to v0.7.0 Stable.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1 — The Sentinel</a> · <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2 — Sentinel Bastion</a> · <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3 — The AI Siege</a> · <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Part 4 — Beyond the Siege</a> · <strong>Part 5 — Quartz Maturity</strong></p></div></div>
<p><em>Part 5 of the <strong>Zenzic Chronicles</strong>. For the complete architectural journey, visit the <a href="https://zenzic.dev/blog/" target="_blank" rel="noopener noreferrer" class="">Safe Harbor Blog</a>.</em></p>
<p><em>The 1,525-line <a class="" href="https://zenzic.dev/blog/obsidian-masterclass">Obsidian Masterclass</a> covers every component in depth — verified by 1,485+ tests across Python 3.10 and 3.14.</em></p>]]></content:encoded>
            <category>Release</category>
            <category>Engineering</category>
            <category>Python</category>
            <category>Open Source</category>
            <category>The Zenzic Chronicles</category>
            <category>Engineering Chronicles</category>
        </item>
        <item>
            <title><![CDATA[Sentinel Guard: The Engineering of Documentation Integrity and Security]]></title>
            <link>https://zenzic.dev/blog/obsidian-masterclass</link>
            <guid>https://zenzic.dev/blog/obsidian-masterclass</guid>
            <pubDate>Wed, 29 Apr 2026 19:20:00 GMT</pubDate>
            <description><![CDATA[A forensic deep-dive into Zenzic's architecture: the VSM, the Shield's 8 normalization stages, the Blood Sentinel, and how to build a Zero-Trust documentation pipeline in 2026.
]]></description>
            <content:encoded><![CDATA[<p>Every build engine you have ever used has made you a silent promise: <em>"Trust me — if
this builds, your documentation is correct."</em></p>
<p>That promise is architecturally broken. And your documentation is paying the price.</p>
<!-- -->
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-i--the-thesis-of-untrusted-input">Act I — The Thesis of Untrusted Input<a href="https://zenzic.dev/blog/obsidian-masterclass#act-i--the-thesis-of-untrusted-input" class="hash-link" aria-label="Direct link to Act I — The Thesis of Untrusted Input" title="Direct link to Act I — The Thesis of Untrusted Input" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-with-build-engines">The Problem with Build Engines<a href="https://zenzic.dev/blog/obsidian-masterclass#the-problem-with-build-engines" class="hash-link" aria-label="Direct link to The Problem with Build Engines" title="Direct link to The Problem with Build Engines" translate="no">​</a></h3>
<p>Your documentation pipeline looks something like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Author → Markdown file → Build Engine → HTML → CDN → Reader</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                              ↑</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    "Trust me, I'll build it."</span><br></div></code></pre></div></div>
<p>Docusaurus, MkDocs, Zensical — these are <strong>generators</strong>. Their contract with you is
explicit: <em>"Give me source files; I will produce a static site."</em> They are optimized
for speed, for plugin extensibility, for theming. They are not optimized for
validation. They trust the source files you give them.</p>
<p>That trust is the vulnerability.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-untrusted-input-means-in-2026">What "Untrusted Input" Means in 2026<a href="https://zenzic.dev/blog/obsidian-masterclass#what-untrusted-input-means-in-2026" class="hash-link" aria-label="Direct link to What &quot;Untrusted Input&quot; Means in 2026" title="Direct link to What &quot;Untrusted Input&quot; Means in 2026" translate="no">​</a></h3>
<p>The threat model for documentation has changed. In 2026, documentation sources come
from:</p>
<ul>
<li class=""><strong>Human contributors</strong> via pull requests — who may not know your link structure</li>
<li class=""><strong>AI-generated content</strong> — which plausibly invents URLs that sound real</li>
<li class=""><strong>Automated refactors</strong> — which move files without updating cross-references</li>
<li class=""><strong>External integrations</strong> — which inject content that may carry credential fragments</li>
</ul>
<p>In a monorepo shared across teams, a single contributor committing a file that
references a non-existent anchor, exposes an internal API key, or introduces a
circular link dependency can silently corrupt the documentation of ten product areas
simultaneously. The build engine will not catch it. The build engine does not try.</p>
<p>Zenzic's core design thesis, established in <a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">ADR-001</a>,
is: <strong>treat every Markdown file as untrusted input</strong>.</p>
<p>This is not a feature. It is a trust model. Every design decision in Zenzic derives
from it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-zero-trust-documentation-pipeline">The Zero-Trust Documentation Pipeline<a href="https://zenzic.dev/blog/obsidian-masterclass#the-zero-trust-documentation-pipeline" class="hash-link" aria-label="Direct link to The Zero-Trust Documentation Pipeline" title="Direct link to The Zero-Trust Documentation Pipeline" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Author → Markdown file → Zenzic (Sentinel) → Build Engine → HTML → CDN → Reader</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                              ↑</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    "I trust nothing. I verify everything."</span><br></div></code></pre></div></div>
<p>The Sentinel runs before the build. It does not trust the source. It constructs a
complete in-memory model of your site — the <strong>Virtual Site Map</strong> — and validates
every claim in your source files against that model. If the model says a link is
broken, the CI gate fails. The build engine never sees the broken file.</p>
<p>The fundamental difference is not in features. It is in philosophy. Build engines are
designed to succeed. Zenzic is designed to find where they would have failed.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="supply-chain-attacks-via-documentation">Supply Chain Attacks via Documentation<a href="https://zenzic.dev/blog/obsidian-masterclass#supply-chain-attacks-via-documentation" class="hash-link" aria-label="Direct link to Supply Chain Attacks via Documentation" title="Direct link to Supply Chain Attacks via Documentation" translate="no">​</a></h3>
<p>Consider a real scenario: your documentation site is a monorepo including
third-party-contributed guides. One contributor submits a tutorial that includes:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token title important punctuation" style="color:rgb(248, 248, 242)">##</span><span class="token title important"> Setup</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Configure your environment with your API key:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">```yaml</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">api_key: "sk_live_</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">YOUR_STRIPE_KEY_HERE</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">The Stripe live key — `sk_live_*` — is a real credential format. Zenzic's Shield</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">catches it before the commit merges. The build engine builds it without comment.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">This is the **supply chain attack surface in documentation**: external content that</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">carries secrets, adversarial links, or path traversal payloads, combined with a build</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pipeline that trusts its input entirely.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Zenzic's response to this is the three-layer security architecture: the **Shield**</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">(credentials), the **Blood Sentinel** (paths), and the **Structural Validator** (graph</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">integrity). Each layer is independent. Each layer runs on every scan. None of them</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">can be disabled simultaneously.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">### The Three Pillars (Non-Negotiable)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">These invariants are in Zenzic's design contract and cannot be overridden by</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">configuration:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">**Pillar 1 — Lint the Source, Not the Build.** Analysis operates on raw Markdown and</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">configuration files. Never on HTML output. Errors are caught before the build starts.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">**Pillar 2 — Zero Subprocesses.** 100% pure Python. No `subprocess`, no `os.system`,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">no Node.js execution. This guarantees: reproducible results across platforms,</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zero dependency on the build environment, and total portability.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">**Pillar 3 — Pure Functions First.** Analysis logic is deterministic. I/O is isolated</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">at the edges (discovery and reporting). No I/O in hot-path loops.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">---</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">## Act II — The VSM Engine: A Mental Map of Your Site</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">### What the Virtual Site Map Is</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">The Virtual Site Map (VSM) is Zenzic's central data structure. It is a complete</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">in-memory projection of your documentation site as a routing table: a mapping from</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">every canonical URL that your build engine would generate to a `Route` object.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">The `Route` object carries:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">```python</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">@dataclass(frozen=True, slots=True)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">class Route:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    url: str           # canonical URL: "/docs/guide/install/"</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    file_path: Path    # absolute path on disk: /repo/docs/guide/install.mdx</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    status: RouteStatus</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    anchors: frozenset[str]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    is_proxy: bool</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    version: str | None</span><br></div></code></pre></div></div>
<p>The <code>RouteStatus</code> can be one of four values:</p>
<table><thead><tr><th style="text-align:left">Status</th><th style="text-align:left">Meaning</th></tr></thead><tbody><tr><td style="text-align:left"><code>REACHABLE</code></td><td style="text-align:left">File is navigable via at least one user-clickable surface</td></tr><tr><td style="text-align:left"><code>ORPHAN_BUT_EXISTING</code></td><td style="text-align:left">File exists on disk but no navigation surface links to it</td></tr><tr><td style="text-align:left"><code>IGNORED</code></td><td style="text-align:left">System file (e.g., <code>_category_.json</code>) — not a content page</td></tr><tr><td style="text-align:left"><code>CONFLICT</code></td><td style="text-align:left">Two files produce the same canonical URL — build collision</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-vsm-is-necessary">Why the VSM Is Necessary<a href="https://zenzic.dev/blog/obsidian-masterclass#why-the-vsm-is-necessary" class="hash-link" aria-label="Direct link to Why the VSM Is Necessary" title="Direct link to Why the VSM Is Necessary" translate="no">​</a></h3>
<p>Without the VSM, Zenzic could only answer: <em>"does this file exist on disk?"</em> That
question is easy. <code>pathlib.Path.exists()</code> answers it in a single syscall.</p>
<p>The VSM enables Zenzic to answer a harder question: <em>"would this link resolve in the
rendered site, given how your specific build engine maps source files to URLs?"</em></p>
<p>Those are completely different questions.</p>
<p>Consider Docusaurus: a file at <code>docs/guide/index.mdx</code> is served at <code>/docs/guide/</code>,
not at <code>/docs/guide/index/</code>. A link to <code>/docs/guide/index.html</code> would resolve to
nothing in the browser — even though the file exists on disk.</p>
<p>Consider MkDocs: a file at <code>docs/api.md</code> with <code>nav:</code> entry <code>- API: api.md</code> is
reachable. The same file without a <code>nav:</code> entry is potentially an orphan depending
on <code>nav:</code> configuration.</p>
<p>The VSM encodes these engine-specific routing rules in pure Python, without running
the build engine.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-vsm-the-architecture">Building the VSM: The Architecture<a href="https://zenzic.dev/blog/obsidian-masterclass#building-the-vsm-the-architecture" class="hash-link" aria-label="Direct link to Building the VSM: The Architecture" title="Direct link to Building the VSM: The Architecture" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        ┌─────────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        │        build_vsm()           │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        │   (I/O boundary — called     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        │    once per scan)            │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                        └──────────┬──────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    ┌──────────────▼──────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    │       Adapter.get_route_info()   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    │  (engine-specific, per-file)     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    └──────────────┬──────────────────┘</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                                   │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              ┌────────────────────▼────────────────────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">              │                                             │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   ┌──────────▼─────────┐  ┌────────────────┐  ┌──────────▼──────────┐</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │  map_url(rel)       │  │ classify_route │  │  get_nav_paths()     │</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   │  (canonical URL)    │  │  (reachability)│  │  (sidebar+nav+footer)│</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">   └────────────────────┘  └────────────────┘  └─────────────────────┘</span><br></div></code></pre></div></div>
<p>The <code>build_vsm()</code> function is the only I/O boundary — it iterates over every Markdown
file in <code>docs_root</code> exactly once. All adapter calls are pure functions after that
initial read. No file is touched again during link validation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="o1-link-validation">O(1) Link Validation<a href="https://zenzic.dev/blog/obsidian-masterclass#o1-link-validation" class="hash-link" aria-label="Direct link to O(1) Link Validation" title="Direct link to O(1) Link Validation" translate="no">​</a></h3>
<p>The VSM is a Python <code>dict[str, Route]</code> — a hash map keyed by canonical URL.</p>
<p>When the validator needs to check whether a link target exists, it calls:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">route </span><span class="token operator">=</span><span class="token plain"> vsm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">canonical_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># dict.get() — O(1) hash lookup</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> route </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Z104: FILE_NOT_FOUND</span><br></div></code></pre></div></div>
<p>This means validating 10,000 links against a 10,000-page site is not O(N²) — it is
10,000 independent O(1) lookups. The VSM is built once (O(N)) and then queried
indefinitely at O(1) per link.</p>
<p>Compare this to naive implementations that call <code>Path.exists()</code> for every link target:
that is N×M syscalls for N links across M files, where each <code>stat()</code> call crosses the
user-kernel boundary. At 50,000 links across a large documentation site, the
difference between O(1) hash lookups and O(N×M) syscalls is the difference between
a 3-second scan and a 90-second scan.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-anchor-cache">The Anchor Cache<a href="https://zenzic.dev/blog/obsidian-masterclass#the-anchor-cache" class="hash-link" aria-label="Direct link to The Anchor Cache" title="Direct link to The Anchor Cache" translate="no">​</a></h3>
<p>In addition to the URL map, the VSM builder constructs an <strong>anchor cache</strong>: a mapping
from file path to the set of heading slugs that file declares.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">anchors_cache</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/repo/docs/guide/install.mdx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token string" style="color:rgb(255, 121, 198)">"prerequisites"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"installation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"next-steps"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>When a link contains a fragment (<code>/docs/guide/install/#next-steps</code>), Zenzic:</p>
<ol>
<li class="">Resolves the URL to a file path via the VSM (O(1))</li>
<li class="">Checks the fragment against <code>anchors_cache[file_path]</code> (O(1) set lookup)</li>
</ol>
<p>A broken anchor (Z102) is detected with two hash lookups. Zero I/O. Zero subprocess
calls.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ghost-routes-i18n-and-versioning">Ghost Routes: i18n and Versioning<a href="https://zenzic.dev/blog/obsidian-masterclass#ghost-routes-i18n-and-versioning" class="hash-link" aria-label="Direct link to Ghost Routes: i18n and Versioning" title="Direct link to Ghost Routes: i18n and Versioning" translate="no">​</a></h3>
<p>The VSM handles cases where a URL exists but no physical file produces it — what
Zenzic calls <strong>Ghost Routes</strong>. Two categories:</p>
<p><strong>i18n Ghost Routes:</strong> Docusaurus generates locale-specific index pages (e.g.,
<code>/it/docs/</code>) automatically, even when no physical <code>it/index.mdx</code> exists. The VSM
marks these as <code>is_proxy=True</code> and <code>status=REACHABLE</code>, because the build engine will
generate them.</p>
<p><strong>Versioned Routes:</strong> Zenzic's Docusaurus adapter uses an internal <code>_version_</code> sentinel
prefix to track versioned documentation trees. A file at
<code>docs/versioned_docs/version-0.6/guide.md</code> is indexed as <code>_version_/0.6/guide.md</code>
in the VSM and served at <code>/docs/0.6/guide/</code> — transparent to the validator.</p>
<p>In both cases, the VSM answer is correct: the URL is reachable for a reader. No
physical file required.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="collision-detection">Collision Detection<a href="https://zenzic.dev/blog/obsidian-masterclass#collision-detection" class="hash-link" aria-label="Direct link to Collision Detection" title="Direct link to Collision Detection" translate="no">​</a></h3>
<p>Two source files can produce the same canonical URL — this is a build-time error in
Docusaurus and MkDocs. The VSM detects this during construction:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_detect_collisions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">routes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> route </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> routes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">url </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">status </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CONFLICT"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">status </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"CONFLICT"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> route</span><br></div></code></pre></div></div>
<p>A <code>CONFLICT</code> route surfaces as a Zenzic finding before the build runs, preventing the
silent data loss that occurs when two files compete for the same URL.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-iii--the-shield-8-stages-of-truth">Act III — The Shield: 8 Stages of Truth<a href="https://zenzic.dev/blog/obsidian-masterclass#act-iii--the-shield-8-stages-of-truth" class="hash-link" aria-label="Direct link to Act III — The Shield: 8 Stages of Truth" title="Direct link to Act III — The Shield: 8 Stages of Truth" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-with-naive-secret-detection">The Problem with Naive Secret Detection<a href="https://zenzic.dev/blog/obsidian-masterclass#the-problem-with-naive-secret-detection" class="hash-link" aria-label="Direct link to The Problem with Naive Secret Detection" title="Direct link to The Problem with Naive Secret Detection" translate="no">​</a></h3>
<p>A naive credential scanner applies regex patterns line by line:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"AKIA[0-9A-Z]{16}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    flag_secret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>This works when the secret is written plainly. In documentation, secrets are rarely
written plainly. They appear in:</p>
<ul>
<li class=""><strong>Markdown tables</strong>: <code>| Key | `` </code>AKIA<code>`` | ``</code>1234567890ABCDEF<code> `` |</code></li>
<li class=""><strong>Concatenated strings</strong>: <code>`AKIA`</code> + <code>`1234ABCD5678EFGH`</code></li>
<li class=""><strong>HTML-entity encoded values</strong>: <code>&amp;#65;&amp;#75;&amp;#73;&amp;#65;1234567890ABCDEF</code></li>
<li class=""><strong>Unicode-obfuscated text</strong>: <code>A\u200bK\u200bI\u200bA1234567890ABCDEF</code> (zero-width spaces)</li>
<li class=""><strong>Comment-interleaved tokens</strong>: <code>ghp_ABC{/* comment */}DEF</code></li>
<li class=""><strong>Cross-line YAML scalars</strong>: key split across two lines by a folded block</li>
</ul>
<p>Zenzic's Shield is designed to defeat all of these patterns. It does so through a
<strong>normalization pipeline</strong> applied before regex matching.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-8-stages-of-normalization">The 8 Stages of Normalization<a href="https://zenzic.dev/blog/obsidian-masterclass#the-8-stages-of-normalization" class="hash-link" aria-label="Direct link to The 8 Stages of Normalization" title="Direct link to The 8 Stages of Normalization" translate="no">​</a></h3>
<p>The <code>_normalize_line_for_shield()</code> function applies these transformations in strict
order:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-1--unicode-format-character-stripping-zrt-006">Stage 1 — Unicode Format Character Stripping (ZRT-006)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-1--unicode-format-character-stripping-zrt-006" class="hash-link" aria-label="Direct link to Stage 1 — Unicode Format Character Stripping (ZRT-006)" title="Direct link to Stage 1 — Unicode Format Character Stripping (ZRT-006)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> c </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> line </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> unicodedata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Cf"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Unicode category <code>Cf</code> ("Format, other") includes invisible characters: zero-width
joiners (U+200D), zero-width non-joiners (U+200C), zero-width spaces (U+200B), and
word joiners (U+2060). An adversarial author can insert these between characters of a
secret key — the characters are visually invisible and collapse when copy-pasted, but
a naive regex will not match the fragmented token.</p>
<p>Stage 1 strips them entirely, reconstructing the original token.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-2--html-character-reference-decoding-zrt-006">Stage 2 — HTML Character Reference Decoding (ZRT-006)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-2--html-character-reference-decoding-zrt-006" class="hash-link" aria-label="Direct link to Stage 2 — HTML Character Reference Decoding (ZRT-006)" title="Direct link to Stage 2 — HTML Character Reference Decoding (ZRT-006)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> html</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">unescape</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>HTML character references (<code>&amp;#65;</code>, <code>&amp;#x41;</code>, <code>&amp;amp;</code>) can encode any ASCII character.
A key like <code>AKIA1234567890ABCD</code> can be written as <code>&amp;#65;&amp;#75;&amp;#73;&amp;#65;1234567890&amp;#65;&amp;#66;&amp;#67;&amp;#68;</code> in inline HTML within a Markdown file — and will render correctly in the browser while evading naive scanners.</p>
<p><code>html.unescape()</code> from the Python standard library handles all forms: decimal (<code>&amp;#NNN;</code>),
hexadecimal (<code>&amp;#xHH;</code>), and named references (<code>&amp;amp;</code>).</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-3--html-comment-stripping-zrt-007">Stage 3 — HTML Comment Stripping (ZRT-007)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-3--html-comment-stripping-zrt-007" class="hash-link" aria-label="Direct link to Stage 3 — HTML Comment Stripping (ZRT-007)" title="Direct link to Stage 3 — HTML Comment Stripping (ZRT-007)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_HTML_COMMENT_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"&lt;!--.*?--&gt;"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> _HTML_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>HTML comments can interleave token fragments: <code>ghp_ABC&lt;!-- noise --&gt;DEF</code>. After the
build, the comment is invisible. In the source, it splits the token. Stage 3 removes
the comment, joining <code>ghp_ABC</code> and <code>DEF</code> into <code>ghp_ABCDEF</code>, which is then matched by
the GitHub token pattern on a subsequent pass.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-4--mdx-comment-stripping-zrt-007">Stage 4 — MDX Comment Stripping (ZRT-007)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-4--mdx-comment-stripping-zrt-007" class="hash-link" aria-label="Direct link to Stage 4 — MDX Comment Stripping (ZRT-007)" title="Direct link to Stage 4 — MDX Comment Stripping (ZRT-007)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_MDX_COMMENT_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\{/\*.*?\*/\}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> _MDX_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>MDX files use JSX-style comments: <code>{/* ... */}</code>. The same interleaving attack applies.
Stage 4 handles the MDX-specific variant independently.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-5--backtick-code-span-unwrapping-zrt-003">Stage 5 — Backtick Code Span Unwrapping (ZRT-003)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-5--backtick-code-span-unwrapping-zrt-003" class="hash-link" aria-label="Direct link to Stage 5 — Backtick Code Span Unwrapping (ZRT-003)" title="Direct link to Stage 5 — Backtick Code Span Unwrapping (ZRT-003)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_BACKTICK_INLINE_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"`([^`]*)`"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> _BACKTICK_INLINE_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Documentation authors frequently write tokens inside inline code spans for visual
formatting: <code>`AKIA`</code>. The backticks are presentation — they do not change the
semantics of the content. Stage 5 strips them, exposing the raw token to the regex
patterns.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-6--concatenation-operator-removal-zrt-003">Stage 6 — Concatenation Operator Removal (ZRT-003)<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-6--concatenation-operator-removal-zrt-003" class="hash-link" aria-label="Direct link to Stage 6 — Concatenation Operator Removal (ZRT-003)" title="Direct link to Stage 6 — Concatenation Operator Removal (ZRT-003)" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_CONCAT_OP_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"[`'\"\s]*\+[`'\"\s]*"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> _CONCAT_OP_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Split-token patterns in documentation tables:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token table table-header-row punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-header-row table-header important"> Field </span><span class="token table table-header-row punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-header-row table-header important"> Value </span><span class="token table table-header-row punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-header-row"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token table table-header-row"></span><span class="token table table-line punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-line punctuation" style="color:rgb(248, 248, 242)">-------</span><span class="token table table-line punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-line punctuation" style="color:rgb(248, 248, 242)">-------</span><span class="token table table-line punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-line"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token table table-line"></span><span class="token table table-data-rows punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-data-rows table-data"> Key   </span><span class="token table table-data-rows punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token table table-data-rows table-data"> </span><span class="token table table-data-rows table-data code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`AKIA`</span><span class="token table table-data-rows table-data"> + </span><span class="token table table-data-rows table-data code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`1234567890ABCDEF`</span><span class="token table table-data-rows table-data"> </span><span class="token table table-data-rows punctuation" style="color:rgb(248, 248, 242)">|</span><br></div></code></pre></div></div>
<p>The <code>+</code> operator joined with surrounding backticks is a common representation of
string concatenation in documentation. Stage 6 removes the concatenation construct,
joining the fragments into <code>AKIA1234567890ABCDEF</code>.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-7--table-pipe-replacement">Stage 7 — Table Pipe Replacement<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-7--table-pipe-replacement" class="hash-link" aria-label="Direct link to Stage 7 — Table Pipe Replacement" title="Direct link to Stage 7 — Table Pipe Replacement" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_TABLE_PIPE_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\|"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normalized </span><span class="token operator">=</span><span class="token plain"> _TABLE_PIPE_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Markdown table cells are separated by <code>|</code>. A secret split across cells would be:
<code>| AKIA | 1234567890ABCDEF |</code>. Stage 7 converts pipes to spaces, enabling
whitespace collapse in Stage 8 to produce a scannable line.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="stage-8--whitespace-normalization">Stage 8 — Whitespace Normalization<a href="https://zenzic.dev/blog/obsidian-masterclass#stage-8--whitespace-normalization" class="hash-link" aria-label="Direct link to Stage 8 — Whitespace Normalization" title="Direct link to Stage 8 — Whitespace Normalization" translate="no">​</a></h4>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Collapses all whitespace runs (tabs, multiple spaces, newlines) into single spaces.
This is the final normalization before regex matching. The result is a clean, compact
line where all obfuscation techniques have been defeated.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-lookback-buffer-cross-line-detection">The Lookback Buffer: Cross-Line Detection<a href="https://zenzic.dev/blog/obsidian-masterclass#the-lookback-buffer-cross-line-detection" class="hash-link" aria-label="Direct link to The Lookback Buffer: Cross-Line Detection" title="Direct link to The Lookback Buffer: Cross-Line Detection" translate="no">​</a></h3>
<p>A secret that spans two lines defeats single-line scanning:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key atrule">api_key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  AKIA</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  IOSFODNN7EXAMPLE</span><br></div></code></pre></div></div>
<p>Each line individually contains only a fragment. Neither line matches the AWS access
key pattern <code>AKIA[0-9A-Z]{16}</code>.</p>
<p>Zenzic addresses this with <code>scan_lines_with_lookback()</code> — a stateful scanner that
maintains a 1-line lookback buffer:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">scan_lines_with_lookback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path </span><span class="token operator">|</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">SecurityFinding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    prev_normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> raw_line </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        normalized </span><span class="token operator">=</span><span class="token plain"> _normalize_line_for_shield</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Scan the cross-line join: tail of previous line + head of current</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        cross_line </span><span class="token operator">=</span><span class="token plain"> prev_normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token operator">-</span><span class="token number">40</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">40</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> scan_line_for_secrets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cross_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Scan the current line independently</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> scan_line_for_secrets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        prev_normalized </span><span class="token operator">=</span><span class="token plain"> normalized</span><br></div></code></pre></div></div>
<p>The cross-line join concatenates the last 40 characters of the normalized previous
line with the first 40 characters of the normalized current line — enough to
reconstruct any secret split across a line boundary, while keeping memory bounded.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dual-form-scanning">Dual-Form Scanning<a href="https://zenzic.dev/blog/obsidian-masterclass#dual-form-scanning" class="hash-link" aria-label="Direct link to Dual-Form Scanning" title="Direct link to Dual-Form Scanning" translate="no">​</a></h3>
<p>Even after normalization, Zenzic scans each line in <strong>two forms</strong>:</p>
<ol>
<li class="">
<p><strong>Raw form</strong> — the line exactly as it appears in the source, ensuring that normally</p>
<p>formatted secrets are always caught with correct column positions for reporting.</p>
</li>
<li class="">
<p><strong>Normalized form</strong> — after all 8 stages, ensuring that obfuscated secrets are</p>
<p>reconstructed and matched.</p>
</li>
</ol>
<p>Duplicate findings (same secret type on the same line in both forms) are suppressed
via a <code>seen: set[str]</code> de-duplication pass.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redos-prevention-the-f2-1-hardening">ReDoS Prevention: The F2-1 Hardening<a href="https://zenzic.dev/blog/obsidian-masterclass#redos-prevention-the-f2-1-hardening" class="hash-link" aria-label="Direct link to ReDoS Prevention: The F2-1 Hardening" title="Direct link to ReDoS Prevention: The F2-1 Hardening" translate="no">​</a></h3>
<p>Regex patterns applied to pathological inputs can cause catastrophic backtracking —
a ReDoS (Regular Expression Denial of Service) attack. A crafted Markdown file with
a megabyte-long line could cause a regex engine to consume unbounded CPU.</p>
<p>Zenzic's F2-1 hardening establishes a maximum line length constant:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_MAX_LINE_LENGTH</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">1_048_576</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># 1 MiB</span><br></div></code></pre></div></div>
<p>Lines exceeding this limit are silently truncated before scanning. No secret longer
than 1 MiB exists in practice; a line longer than 1 MiB is not legitimate
documentation.</p>
<p>Additionally, all regex patterns used in <code>_SECRETS</code> undergo an <strong>eager ReDoS
pre-flight check</strong> at engine construction time (ZRT-002):</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_assert_regex_canary</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> BaseRule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Verify that the rule's regex does not exhibit catastrophic backtracking."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Applies a timing canary against a known-adversarial input.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Raises PluginContractError if the pattern exceeds the time budget.</span><br></div></code></pre></div></div>
<p>Custom rules loaded via the <code>zenzic.rules</code> entry-point group are subject to the same
pre-flight check before the first file is scanned.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-9-secret-families">The 9 Secret Families<a href="https://zenzic.dev/blog/obsidian-masterclass#the-9-secret-families" class="hash-link" aria-label="Direct link to The 9 Secret Families" title="Direct link to The 9 Secret Families" translate="no">​</a></h3>
<p>Zenzic's Shield v0.7.0 detects credentials across 9 families:</p>
<table><thead><tr><th style="text-align:left">Family</th><th style="text-align:left">Pattern</th><th style="text-align:left">Example prefix</th></tr></thead><tbody><tr><td style="text-align:left"><strong>OpenAI API key</strong></td><td style="text-align:left"><code>sk-[a-zA-Z0-9]{48}</code></td><td style="text-align:left"><code>sk-a1B2c3...</code></td></tr><tr><td style="text-align:left"><strong>GitHub token</strong></td><td style="text-align:left"><code>gh[pousr]_[a-zA-Z0-9]{36}</code></td><td style="text-align:left"><code>ghp_</code>, <code>gho_</code>, <code>ghu_</code>, <code>ghs_</code>, <code>ghr_</code></td></tr><tr><td style="text-align:left"><strong>AWS access key</strong></td><td style="text-align:left"><code>AKIA[0-9A-Z]{16}</code></td><td style="text-align:left"><code>AKIAIOSFODNN7EXAMPLE</code></td></tr><tr><td style="text-align:left"><strong>Stripe live key</strong></td><td style="text-align:left"><code>sk_live_[0-9a-zA-Z]{24}</code></td><td style="text-align:left"><code>sk_live_4xK8...</code></td></tr><tr><td style="text-align:left"><strong>Slack token</strong></td><td style="text-align:left"><code>xox[baprs]-[0-9a-zA-Z]{10,48}</code></td><td style="text-align:left"><code>xoxb-</code>, <code>xoxa-</code>, ...</td></tr><tr><td style="text-align:left"><strong>Google API key</strong></td><td style="text-align:left"><code>AIza[0-9A-Za-z\-_]{35}</code></td><td style="text-align:left"><code>AIzaSyB...</code></td></tr><tr><td style="text-align:left"><strong>Private key header</strong></td><td style="text-align:left"><code>-----BEGIN [A-Z ]+ PRIVATE KEY-----</code></td><td style="text-align:left">RSA, EC, DSA</td></tr><tr><td style="text-align:left"><strong>Hex-encoded payload</strong></td><td style="text-align:left"><code>(?:\\x[0-9a-fA-F]{2}){3,}</code></td><td style="text-align:left"><code>\x41\x4b\x49\x41...</code></td></tr><tr><td style="text-align:left"><strong>GitLab PAT</strong></td><td style="text-align:left"><code>glpat-[A-Za-z0-9\-_]{20,}</code></td><td style="text-align:left"><code>glpat-aBcDeFgHiJkL...</code></td></tr></tbody></table>
<p>Each pattern is pre-compiled at import time — zero compilation overhead during scanning.
The set is additive: new families are added by appending to the <code>_SECRETS</code> list.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="exit-code-2-the-sacred-exit">Exit Code 2: The Sacred Exit<a href="https://zenzic.dev/blog/obsidian-masterclass#exit-code-2-the-sacred-exit" class="hash-link" aria-label="Direct link to Exit Code 2: The Sacred Exit" title="Direct link to Exit Code 2: The Sacred Exit" translate="no">​</a></h3>
<p>Any detection by the Shield causes Zenzic to exit with <strong>code 2</strong>. This exit code is
<strong>non-suppressible</strong> — it cannot be silenced by <code>--exit-zero</code>, <code>fail-on-error: false</code>,
or any configuration flag.</p>
<p>The rationale: a CI system that can be configured to ignore credential exposure is not
a security gate. It is theater. Exit code 2 is the guarantee that the security contract
cannot be bypassed by configuration drift or operator error.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Exit 0 — All checks passed</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Exit 1 — Quality findings (broken links, orphans, placeholders) — suppressible</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Exit 2 — Security breach (Shield: credential detected) — NEVER suppressible</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Exit 3 — Fatal breach (Blood Sentinel: path traversal) — NEVER suppressible</span><br></div></code></pre></div></div>
<p>The Shield operates in <strong>Pass 1A</strong> — before any structural analysis. A file that
triggers exit 2 does not proceed to link validation or orphan detection. The
Sentinel reports the breach and stops.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-iv--blood-sentinel-kernel-level-sandboxing">Act IV — Blood Sentinel: Kernel-Level Sandboxing<a href="https://zenzic.dev/blog/obsidian-masterclass#act-iv--blood-sentinel-kernel-level-sandboxing" class="hash-link" aria-label="Direct link to Act IV — Blood Sentinel: Kernel-Level Sandboxing" title="Direct link to Act IV — Blood Sentinel: Kernel-Level Sandboxing" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="path-traversal-in-cicd">Path Traversal in CI/CD<a href="https://zenzic.dev/blog/obsidian-masterclass#path-traversal-in-cicd" class="hash-link" aria-label="Direct link to Path Traversal in CI/CD" title="Direct link to Path Traversal in CI/CD" translate="no">​</a></h3>
<p>In a CI/CD pipeline, Zenzic runs in a containerized runner. The runner has access to:</p>
<ul>
<li class="">SSH keys: <code>/home/runner/.ssh/id_rsa</code></li>
<li class="">System secrets: <code>/etc/passwd</code>, <code>/etc/shadow</code></li>
<li class="">Runner tokens: <code>/var/run/secrets/kubernetes.io/serviceaccount/token</code></li>
</ul>
<p>A Markdown file can embed a path traversal attack:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token url">[</span><span class="token url content">Evil link</span><span class="token url">](</span><span class="token url">../../../../etc/passwd</span><span class="token url">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token url">[</span><span class="token url content">Another attack</span><span class="token url">](</span><span class="token url">../../../home/runner/.ssh/id_rsa</span><span class="token url">)</span><br></div></code></pre></div></div>
<p>A documentation site that renders these files to HTML becomes a vector for exfiltrating
runner secrets, depending on the deployment mechanism and how static assets are served.</p>
<p>More critically: Zenzic itself reads file contents to validate them. A path traversal
in a link target could cause Zenzic to validate <code>/etc/passwd</code> as a documentation file
and include its content in a report. This is the <strong>tool-level attack</strong> — abusing the
validator to read secrets from the runner filesystem.</p>
<p>The Blood Sentinel prevents both categories.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-ospathnormpath-collapse">The <code>os.path.normpath</code> Collapse<a href="https://zenzic.dev/blog/obsidian-masterclass#the-ospathnormpath-collapse" class="hash-link" aria-label="Direct link to the-ospathnormpath-collapse" title="Direct link to the-ospathnormpath-collapse" translate="no">​</a></h3>
<p>The defense is built into <code>InMemoryPathResolver._build_target()</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_build_target</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> source_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        raw </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_str </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lstrip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">elif</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/docs/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        raw </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_str </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/docs/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">elif</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        raw </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_repo_root_str </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        raw </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">source_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">normpath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># ← The collapse</span><br></div></code></pre></div></div>
<p><code>os.path.normpath()</code> is pure C string arithmetic — no syscalls, no <code>stat()</code>, no
<code>readlink()</code>. It collapses all <code>.</code> and <code>..</code> segments mathematically.</p>
<p>The result:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">source: /repo/docs/guide/install.mdx</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">link:   ../../../../etc/passwd</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">raw = /repo/docs/guide/../../../etc/passwd</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normpath → /etc/passwd</span><br></div></code></pre></div></div>
<p>The target string <code>/etc/passwd</code> is produced before any filesystem call is made.
Then the Shield check:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">shield_ok </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    target_str </span><span class="token operator">==</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_str</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> target_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_prefix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> shield_ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> PathTraversal</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw_href</span><span class="token operator">=</span><span class="token plain">href</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p><code>/etc/passwd</code> does not start with <code>/repo/docs/</code> → <code>PathTraversal</code> returned
immediately. <strong>Zero filesystem access. Zero data exposure. Exit 3.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-multi-root-perimeter">The Multi-Root Perimeter<a href="https://zenzic.dev/blog/obsidian-masterclass#the-multi-root-perimeter" class="hash-link" aria-label="Direct link to The Multi-Root Perimeter" title="Direct link to The Multi-Root Perimeter" translate="no">​</a></h3>
<p>Zenzic handles multi-locale Docusaurus projects where both <code>docs/</code> and
<code>i18n/it/docusaurus-plugin-content-docs/current/</code> contain cross-referencing files.</p>
<p>The <code>InMemoryPathResolver</code> constructor accepts an <code>allowed_roots</code> parameter — a list
of additional authorized boundaries:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_extra </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_coerce_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> r </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">allowed_roots </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">_pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> _r </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_dir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain">_extra</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    _s </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">_r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    _pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">_s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> _s </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_allowed_root_pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">_pairs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>The Shield check becomes:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">shield_ok </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    target_str </span><span class="token operator">==</span><span class="token plain"> root_str </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> target_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">root_prefix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> root_str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> root_prefix </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_allowed_root_pairs</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>A relative link from <code>docs/guide.mdx</code> to <code>../i18n/it/guide.mdx</code> is valid only if
<code>i18n/it/docusaurus-plugin-content-docs/current/</code> is in <code>allowed_roots</code>. Without
explicit authorization, it produces <code>PathTraversal</code>. The perimeter is explicitly
declared, not inferred.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-site-alias-security-analysis">The <code>@site/</code> Alias: Security Analysis<a href="https://zenzic.dev/blog/obsidian-masterclass#the-site-alias-security-analysis" class="hash-link" aria-label="Direct link to the-site-alias-security-analysis" title="Direct link to the-site-alias-security-analysis" translate="no">​</a></h3>
<p>Docusaurus allows <code>@site/</code> as an alias for the project root in <code>import</code> statements
and static asset references. Zenzic maps this alias to <code>repo_root</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">elif</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/docs/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    raw </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_root_str </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/docs/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">elif</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    raw </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_repo_root_str </span><span class="token operator">+</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sep </span><span class="token operator">+</span><span class="token plain"> path_part</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"@site/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>A path like <code>@site/../etc/passwd</code> becomes:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">raw = /repo/../etc/passwd</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">normpath → /etc/passwd</span><br></div></code></pre></div></div>
<p>The normpath collapse happens before the perimeter check. <code>@site/</code> is not an
escape hatch from the Blood Sentinel. It is an alias for a specific root, and all
<code>..</code> traversals through it are collapsed and checked identically.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="exit-code-3-non-negotiable-termination">Exit Code 3: Non-Negotiable Termination<a href="https://zenzic.dev/blog/obsidian-masterclass#exit-code-3-non-negotiable-termination" class="hash-link" aria-label="Direct link to Exit Code 3: Non-Negotiable Termination" title="Direct link to Exit Code 3: Non-Negotiable Termination" translate="no">​</a></h3>
<p>Path traversal findings (Z202/Z203) cause exit 3. Like exit 2, this is
non-suppressible. A path traversal in a documentation source is not a quality
finding. It is an attempted perimeter breach. The Sentinel terminates.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Z202 PATH_TRAVERSAL — confirmed: resolved path escapes docs_root</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Z203 PATH_TRAVERSAL_SUSPICIOUS — unresolvable path with traversal segments</span><br></div></code></pre></div></div>
<p>The distinction: Z202 is triggered when normpath produces a path that fails the prefix
check. Z203 is triggered when the href contains <code>../</code> segments but cannot be fully
resolved (e.g., missing fragments, malformed URLs). Both produce exit 3.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-v--the-docusaurus-adapter-iscategoryindex-and-url-collapsing">Act V — The Docusaurus Adapter: isCategoryIndex and URL Collapsing<a href="https://zenzic.dev/blog/obsidian-masterclass#act-v--the-docusaurus-adapter-iscategoryindex-and-url-collapsing" class="hash-link" aria-label="Direct link to Act V — The Docusaurus Adapter: isCategoryIndex and URL Collapsing" title="Direct link to Act V — The Docusaurus Adapter: isCategoryIndex and URL Collapsing" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-routing-problem">The Routing Problem<a href="https://zenzic.dev/blog/obsidian-masterclass#the-routing-problem" class="hash-link" aria-label="Direct link to The Routing Problem" title="Direct link to The Routing Problem" translate="no">​</a></h3>
<p>Docusaurus maps source files to URLs through a set of rules that are not always
obvious to documentation authors. Zenzic must replicate these rules exactly in Python
to produce correct VSM entries.</p>
<p>The most complex rule is <strong>isCategoryIndex collapsing</strong>: when a file's name matches
certain patterns, its URL is collapsed to the parent directory, not a file slug.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-collapsing-cases">The Three Collapsing Cases<a href="https://zenzic.dev/blog/obsidian-masterclass#the-three-collapsing-cases" class="hash-link" aria-label="Direct link to The Three Collapsing Cases" title="Direct link to The Three Collapsing Cases" translate="no">​</a></h3>
<p>From <code>_docusaurus.py</code>, the collapsing logic:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> parts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    file_name_lower </span><span class="token operator">=</span><span class="token plain"> parts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    parent_name_lower </span><span class="token operator">=</span><span class="token plain"> parts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token operator">-</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">parts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        file_name_lower </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"index"</span><span class="token plain">          </span><span class="token comment" style="color:rgb(98, 114, 164)"># Case 1: index file</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> file_name_lower </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"readme"</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Case 2: README file</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            parent_name_lower </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">and</span><span class="token plain"> file_name_lower </span><span class="token operator">==</span><span class="token plain"> parent_name_lower  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Case 3: folder-match</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        parts </span><span class="token operator">=</span><span class="token plain"> parts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># collapse to parent</span><br></div></code></pre></div></div>
<p><strong>Case 1 — Index collapse:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/guide/index.mdx    →  /docs/guide/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/index.mdx          →  /docs/</span><br></div></code></pre></div></div>
<p><strong>Case 2 — README collapse:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/guide/README.md    →  /docs/guide/</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/README.md          →  /docs/</span><br></div></code></pre></div></div>
<p><strong>Case 3 — Folder-match collapse (isCategoryIndex):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/guide/guide.mdx    →  /docs/guide/   (filename == parent dirname)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">docs/api/api.md         →  /docs/api/</span><br></div></code></pre></div></div>
<p>This third case is frequently surprising to authors: a file named after its parent
directory is silently collapsed to the directory URL by Docusaurus. Zenzic replicates
this behavior exactly, producing the correct canonical URL in the VSM.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="url-priority-frontmatter-slug-first">URL Priority: Frontmatter Slug First<a href="https://zenzic.dev/blog/obsidian-masterclass#url-priority-frontmatter-slug-first" class="hash-link" aria-label="Direct link to URL Priority: Frontmatter Slug First" title="Direct link to URL Priority: Frontmatter Slug First" translate="no">​</a></h3>
<p>Before filesystem derivation, Zenzic checks for a <code>slug:</code> frontmatter declaration:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Stage 1: frontmatter slug override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">slug </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_slug_map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rel_posix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> slug </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> slug</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Absolute slug: prefix with routeBasePath</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        rbp </span><span class="token operator">=</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_route_base_path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"docs"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> rbp </span><span class="token operator">+</span><span class="token plain"> slug</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">rstrip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Relative slug: replace last path segment</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        parent </span><span class="token operator">=</span><span class="token plain"> rel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parent</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> parent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">as_posix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> slug</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><br></div></code></pre></div></div>
<p>The full URL resolution priority:</p>
<ol>
<li class=""><strong>Frontmatter <code>slug:</code></strong> — absolute or relative override</li>
<li class=""><strong>isCategoryIndex</strong> — index/README/folder-match collapse</li>
<li class=""><strong>Extension stripping</strong> — <code>.md</code> / <code>.mdx</code> removed</li>
<li class=""><strong>routeBasePath prefix</strong> — default <code>"docs"</code>, configurable</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-provides_index-contract">The <code>provides_index()</code> Contract<a href="https://zenzic.dev/blog/obsidian-masterclass#the-provides_index-contract" class="hash-link" aria-label="Direct link to the-provides_index-contract" title="Direct link to the-provides_index-contract" translate="no">​</a></h3>
<p>The <code>provides_index(directory_path)</code> method determines whether a directory has a
landing page — required for the Z401 (MISSING_DIRECTORY_INDEX) check:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">provides_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> directory_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    index_files </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"index.md"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"index.mdx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"README.md"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"README.mdx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">directory_path </span><span class="token operator">/</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exists</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> f </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> index_files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    category_json </span><span class="token operator">=</span><span class="token plain"> directory_path </span><span class="token operator">/</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"_category_.json"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> category_json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exists</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        data </span><span class="token operator">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">loads</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">category_json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read_text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">encoding</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"utf-8"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        link </span><span class="token operator">=</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"link"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">isinstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">link</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">and</span><span class="token plain"> link</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"generated-index"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">False</span><br></div></code></pre></div></div>
<p>A directory provides an index when:</p>
<ol>
<li class="">
<p>An <code>index.md</code>, <code>index.mdx</code>, <code>README.md</code>, or <code>README.mdx</code> exists inside it, <strong>or</strong></p>
</li>
<li class="">
<p>A <code>_category_.json</code> declares <code>"link": { "type": "generated-index" }</code> — causing</p>
<p>Docusaurus to auto-generate a category index page.</p>
</li>
</ol>
<p>I/O is permitted in <code>provides_index()</code> because it is called once per directory during
the discovery phase — never inside per-link or per-file hot loops.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-surface-harvester">The Three-Surface Harvester<a href="https://zenzic.dev/blog/obsidian-masterclass#the-three-surface-harvester" class="hash-link" aria-label="Direct link to The Three-Surface Harvester" title="Direct link to The Three-Surface Harvester" translate="no">​</a></h3>
<p>For orphan detection, Zenzic's Docusaurus adapter aggregates navigation paths from
three sources:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_nav_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Merge sidebar + navbar + footer into a single navigable path set."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_parse_sidebars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)"># sidebars.ts / sidebars.js</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">|</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_parse_config_navigation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># navbar.items + footer.links</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p><strong>Sidebar parsing</strong> (<code>_parse_sidebars()</code>): reads <code>sidebars.ts</code> or <code>sidebars.js</code> via
pure-Python regex. Strips JS-style line and block comments before parsing. Handles
both <code>type: 'doc'</code> explicit entries and bare string IDs.</p>
<p><strong>Config navigation</strong> (<code>_parse_config_navigation()</code>): reads <code>docusaurus.config.ts</code>
via regex, extracts <code>to:</code> URL paths from <code>navbar.items</code> and <code>footer.links</code>, strips
<code>baseUrl</code> and <code>routeBasePath</code> prefixes, and probes for <code>.md</code>/<code>.mdx</code> files on disk.</p>
<p>A file is <code>ORPHAN_BUT_EXISTING</code> only if absent from sidebar AND navbar AND footer.
A changelog linked only in the navbar is <code>REACHABLE</code>. A legal notice linked only in
the footer is <code>REACHABLE</code>. This is <strong>R21 — UX-Discoverability</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-slug-law-physical-consistency">The Slug Law: Physical Consistency<a href="https://zenzic.dev/blog/obsidian-masterclass#the-slug-law-physical-consistency" class="hash-link" aria-label="Direct link to The Slug Law: Physical Consistency" title="Direct link to The Slug Law: Physical Consistency" translate="no">​</a></h3>
<p>Zenzic's own documentation enforces the <strong>Slug Law</strong> (ADR-003): no <code>slug:</code> frontmatter
that diverges from the physical file path. The rationale is architectural: the
autogenerated sidebar uses <code>type: 'autogenerated'</code> — it resolves URLs from file paths.
A diverged <code>slug:</code> creates a URL that the sidebar cannot resolve, causing navigation
failures without a build-time error.</p>
<p>The VSM enforces this indirectly: if a <code>slug:</code> produces a URL that no sidebar entry
references, the file is <code>ORPHAN_BUT_EXISTING</code>. The Slug Law converts this from a
silent failure to a Zenzic finding.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-vi--the-rule-engine-adaptive-parallelism">Act VI — The Rule Engine: Adaptive Parallelism<a href="https://zenzic.dev/blog/obsidian-masterclass#act-vi--the-rule-engine-adaptive-parallelism" class="hash-link" aria-label="Direct link to Act VI — The Rule Engine: Adaptive Parallelism" title="Direct link to Act VI — The Rule Engine: Adaptive Parallelism" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-adaptiveruleengine">The AdaptiveRuleEngine<a href="https://zenzic.dev/blog/obsidian-masterclass#the-adaptiveruleengine" class="hash-link" aria-label="Direct link to The AdaptiveRuleEngine" title="Direct link to The AdaptiveRuleEngine" translate="no">​</a></h3>
<p>Custom rules in Zenzic — declared in <code>[[custom_rules]]</code> or implemented as Python
classes via the <code>zenzic.rules</code> entry-point group — are applied through the
<code>AdaptiveRuleEngine</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">AdaptiveRuleEngine</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">__init__</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Sequence</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">BaseRule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> rule </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            _assert_pickleable</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># eager pickle validation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            _assert_regex_canary</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># ZRT-002: ReDoS pre-flight</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_rules </span><span class="token operator">=</span><span class="token plain"> rules</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">RuleFinding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Pure function: file path + text → findings. No I/O."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        findings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">RuleFinding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> rule </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">_rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                findings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">extend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">check</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> Exception </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> exc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)"># Rule failures are caught and converted to RULE-ENGINE-ERROR findings.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token comment" style="color:rgb(98, 114, 164)"># One faulty plugin cannot abort the scan of the entire docs tree.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                findings</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">RuleFinding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> findings</span><br></div></code></pre></div></div>
<p>Rules are validated <strong>eagerly</strong> at construction time, before the first file is scanned.
A rule that fails pickle serialization is rejected immediately — not silently inside a
worker process during a long parallel scan.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-50-file-threshold">The 50-File Threshold<a href="https://zenzic.dev/blog/obsidian-masterclass#the-50-file-threshold" class="hash-link" aria-label="Direct link to The 50-File Threshold" title="Direct link to The 50-File Threshold" translate="no">​</a></h3>
<p>Zenzic's scanner switches between sequential and parallel execution based on the
number of files:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">ADAPTIVE_PARALLEL_THRESHOLD</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">50</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># in scanner.py</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">use_parallel </span><span class="token operator">=</span><span class="token plain"> workers </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">and</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">md_files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">&gt;=</span><span class="token plain"> ADAPTIVE_PARALLEL_THRESHOLD</span><br></div></code></pre></div></div>
<p>Below 50 files: sequential execution. The overhead of spawning a
<code>ProcessPoolExecutor</code> — approximately 200–400 ms on a cold interpreter — exceeds
the parallelism benefit for small documentation sets.</p>
<p>At or above 50 files: <code>ProcessPoolExecutor</code> is used:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> concurrent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">futures</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ProcessPoolExecutor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">max_workers</span><span class="token operator">=</span><span class="token plain">actual_workers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> executor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    futures_map </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        executor</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">submit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">_worker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> work_items</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> future </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> concurrent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">futures</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">as_completed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">futures_map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">extend</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">future</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>Each file is dispatched to an independent worker process. The worker receives a
serialized <code>(file_path, config, rules)</code> tuple via pickle — which is why the eager
pickle validation at <code>AdaptiveRuleEngine</code> construction is load-bearing. A
non-pickleable lambda in a custom rule would silently fail inside the worker process;
the eager check catches it in the main process at startup.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pure-function-discipline-why-it-matters-for-parallelism">Pure Function Discipline: Why It Matters for Parallelism<a href="https://zenzic.dev/blog/obsidian-masterclass#pure-function-discipline-why-it-matters-for-parallelism" class="hash-link" aria-label="Direct link to Pure Function Discipline: Why It Matters for Parallelism" title="Direct link to Pure Function Discipline: Why It Matters for Parallelism" translate="no">​</a></h3>
<p>Pillar 3 — Pure Functions First — is not a style preference. It is an architectural
requirement for correctness under parallelism.</p>
<p>A rule that holds mutable state between <code>check()</code> calls (e.g., a counter, a cache)
would produce data races when two workers process files simultaneously. A rule that
makes I/O calls inside <code>check()</code> would suffer from TOCTOU (time-of-check to
time-of-use) races in a parallel context.</p>
<p>Pure functions — deterministic, stateless, side-effect-free — are safe to execute
concurrently without synchronization. The <code>AdaptiveRuleEngine</code> guarantees this by
contract: any rule that cannot be expressed as a pure function cannot satisfy the
<code>PluginContractError</code> validation and will not be admitted to the engine.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pickle-serialization-check">The Pickle Serialization Check<a href="https://zenzic.dev/blog/obsidian-masterclass#the-pickle-serialization-check" class="hash-link" aria-label="Direct link to The Pickle Serialization Check" title="Direct link to The Pickle Serialization Check" translate="no">​</a></h3>
<p>Custom rules loaded via <code>entry_points(group="zenzic.rules")</code> are validated with:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_assert_pickleable</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> BaseRule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        pickle</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> Exception </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> exc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> PluginContractError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"Rule '</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">rule</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">rule_id</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">' cannot be pickled and is incompatible with "</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"multiprocessing: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">exc</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> exc</span><br></div></code></pre></div></div>
<p>This is an <strong>eager contract check</strong>: the error is raised before any file is touched,
with a clear message pointing to the rule that failed. Without this check, the failure
would manifest as a cryptic <code>BrokenPipeError</code> or <code>EOFError</code> inside a worker process
at scan time — far harder to diagnose.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-vii--enterprise-integration-sarif-and-the-quality-gate">Act VII — Enterprise Integration: SARIF and the Quality Gate<a href="https://zenzic.dev/blog/obsidian-masterclass#act-vii--enterprise-integration-sarif-and-the-quality-gate" class="hash-link" aria-label="Direct link to Act VII — Enterprise Integration: SARIF and the Quality Gate" title="Direct link to Act VII — Enterprise Integration: SARIF and the Quality Gate" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sarif-210-documentation-in-your-security-dashboard">SARIF 2.1.0: Documentation in Your Security Dashboard<a href="https://zenzic.dev/blog/obsidian-masterclass#sarif-210-documentation-in-your-security-dashboard" class="hash-link" aria-label="Direct link to SARIF 2.1.0: Documentation in Your Security Dashboard" title="Direct link to SARIF 2.1.0: Documentation in Your Security Dashboard" translate="no">​</a></h3>
<p>SARIF (Static Analysis Results Interchange Format) is the standard output format for
security tools consumed by GitHub Code Scanning, Azure DevOps, and other CI/CD
platforms.</p>
<p>Zenzic produces valid SARIF 2.1.0 with:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all ./docs </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--format</span><span class="token plain"> sarif </span><span class="token operator">&gt;</span><span class="token plain"> zenzic.sarif</span><br></div></code></pre></div></div>
<p>The SARIF output includes:</p>
<ul>
<li class=""><strong>Tool descriptor</strong> with Zenzic version and URI</li>
<li class=""><strong>Rules array</strong> with one entry per Zxxx code found (ID, name, helpUri, severity)</li>
<li class=""><strong>Results array</strong> with location (file + line + column), message, and level</li>
</ul>
<p>A minimal SARIF result for a broken link:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"ruleId"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Z101"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"level"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"error"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"message"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"text"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Z101 LINK_BROKEN: './install.mdx' → './guide/setup.mdx' does not exist"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"locations"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"physicalLocation"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"artifactLocation"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"uri"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"docs/install.mdx"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"region"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token property">"startLine"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">42</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">"startColumn"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">12</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></div></code></pre></div></div>
<p>Upload to GitHub Code Scanning:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">.github/workflows/zenzic.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Documentation Integrity Gate</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> pull_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">sentinel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">runs-on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> actions/checkout@v4</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Run Zenzic Sentinel</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> uvx zenzic check all ./docs </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">format sarif </span><span class="token punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> zenzic.sarif</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Upload to GitHub Security</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> github/codeql</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">action/upload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">sarif@v3</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">sarif_file</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> zenzic.sarif</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">if</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> always()   </span><span class="token comment" style="color:rgb(98, 114, 164)"># upload even when Zenzic fails</span><br></div></code></pre></div></div>
<p>The <code>if: always()</code> is critical: when Zenzic exits with code 1 (quality findings), the
step is marked as failed — but the SARIF upload must still execute to surface the
findings in the Security tab. Without <code>if: always()</code>, a failed step would abort before
uploading, producing silence instead of visibility.</p>
<p>For teams using <code>zenzic-action</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">.github/workflows/zenzic.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> PythonWoods/zenzic</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">action@v1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0.7.0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">format</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> sarif</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">upload-sarif</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><br></div></code></pre></div></div>
<p>The action handles the SARIF upload and the <code>if: always()</code> semantics automatically,
including SARIF integrity validation — if the SARIF file is truncated by runner OOM
or SIGKILL, the action emits a <code>::warning</code> annotation rather than uploading a false-
clean result (Output-First Semantics, ADR-004 in zenzic-action).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="machine-silence-rule-r20">Machine Silence: RULE R20<a href="https://zenzic.dev/blog/obsidian-masterclass#machine-silence-rule-r20" class="hash-link" aria-label="Direct link to Machine Silence: RULE R20" title="Direct link to Machine Silence: RULE R20" translate="no">​</a></h3>
<p>When <code>--format sarif</code> or <code>--format json</code> is active, Zenzic enforces <strong>Machine Silence
(R20)</strong>: zero Rich banners, headers, or informational panels are written to stdout.
The output stream is a machine-readable format and must remain 100% valid against its
schema.</p>
<p>This is enforced at the CLI level:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">_MACHINE_FORMATS </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"json"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"sarif"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> output_format </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> _MACHINE_FORMATS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    print_header</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>A script that pipes <code>zenzic check all --format json | jq '.findings'</code> receives
valid JSON with no banner contamination.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-quality-score-zenzic-score">The Quality Score: <code>zenzic score</code><a href="https://zenzic.dev/blog/obsidian-masterclass#the-quality-score-zenzic-score" class="hash-link" aria-label="Direct link to the-quality-score-zenzic-score" title="Direct link to the-quality-score-zenzic-score" translate="no">​</a></h3>
<p>Beyond binary pass/fail, Zenzic provides a <strong>quality score</strong> — a 0–100 metric
computed from the weighted sum of findings across all check categories:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic score ./docs</span><br></div></code></pre></div></div>
<p>The score can be used as a regression gate:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic </span><span class="token function" style="color:rgb(80, 250, 123)">diff</span><span class="token plain"> ./docs   </span><span class="token comment" style="color:rgb(98, 114, 164)"># compare current score to last snapshot</span><br></div></code></pre></div></div>
<p><code>diff</code> compares the current scan result against a stored snapshot (<code>zenzic.snapshot.json</code>
in the repo root). A score regression (e.g., score drops from 97 to 91) causes a
non-zero exit, enabling CI to block merges that degrade documentation quality.</p>
<p>This is the <strong>Quality Gate pattern</strong>: not a binary pass/fail, but a tracked trend
with a configurable failure threshold (<code>fail_under</code> in <code>zenzic.toml</code>).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-diagnostic-code-registry-zxxx">The Diagnostic Code Registry: Zxxx<a href="https://zenzic.dev/blog/obsidian-masterclass#the-diagnostic-code-registry-zxxx" class="hash-link" aria-label="Direct link to The Diagnostic Code Registry: Zxxx" title="Direct link to The Diagnostic Code Registry: Zxxx" translate="no">​</a></h3>
<p>Every Zenzic finding carries a <code>Zxxx</code> code from <code>core/codes.py</code> — the single source
of truth for the diagnostic registry.</p>
<p>The full registry by category:</p>
<table><thead><tr><th style="text-align:left">Range</th><th style="text-align:left">Category</th><th style="text-align:left">Codes</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Z1xx</strong></td><td style="text-align:left">Link Integrity</td><td style="text-align:left">Z101 LINK_BROKEN, Z102 ANCHOR_MISSING, Z103 UNREACHABLE_LINK, Z104 FILE_NOT_FOUND, Z105 ABSOLUTE_PATH, Z106 ALT_TEXT_MISSING</td></tr><tr><td style="text-align:left"><strong>Z2xx</strong></td><td style="text-align:left">Security</td><td style="text-align:left">Z201 SHIELD_SECRET, Z202 PATH_TRAVERSAL, Z203 PATH_TRAVERSAL_SUSPICIOUS</td></tr><tr><td style="text-align:left"><strong>Z3xx</strong></td><td style="text-align:left">Reference Integrity</td><td style="text-align:left">Z301 DANGLING_REF, Z302 DEAD_DEF, Z303 CIRCULAR_LINK</td></tr><tr><td style="text-align:left"><strong>Z4xx</strong></td><td style="text-align:left">Structure</td><td style="text-align:left">Z401 MISSING_DIRECTORY_INDEX, Z402 ORPHAN_PAGE, Z403 SNIPPET_UNREACHABLE, Z404 CONFIG_ASSET_MISSING</td></tr><tr><td style="text-align:left"><strong>Z5xx</strong></td><td style="text-align:left">Content Quality</td><td style="text-align:left">Z501 PLACEHOLDER, Z502 SHORT_CONTENT, Z503 SNIPPET_ERROR, Z504 QUALITY_REGRESSION</td></tr><tr><td style="text-align:left"><strong>Z9xx</strong></td><td style="text-align:left">Engine / System</td><td style="text-align:left">Z901 RULE_ERROR, Z902 RULE_TIMEOUT, Z903 UNUSED_ASSET, Z904 DISCOVERY_ERROR</td></tr></tbody></table>
<p>The codes are stable across versions. A CI system that filters findings by <code>Z201</code>
(credentials) can do so independently of Zenzic version bumps. The codes are the
documented API surface for tooling integration.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-viii--performance-the-numbers">Act VIII — Performance: The Numbers<a href="https://zenzic.dev/blog/obsidian-masterclass#act-viii--performance-the-numbers" class="hash-link" aria-label="Direct link to Act VIII — Performance: The Numbers" title="Direct link to Act VIII — Performance: The Numbers" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-adaptive-parallelism-benchmark">The Adaptive Parallelism Benchmark<a href="https://zenzic.dev/blog/obsidian-masterclass#the-adaptive-parallelism-benchmark" class="hash-link" aria-label="Direct link to The Adaptive Parallelism Benchmark" title="Direct link to The Adaptive Parallelism Benchmark" translate="no">​</a></h3>
<p>The 50-file threshold is a conservative heuristic derived from empirical measurement:</p>
<table><thead><tr><th style="text-align:right">File count</th><th style="text-align:right">Sequential (ms)</th><th style="text-align:right">Parallel (ms)</th><th style="text-align:left">Crossover</th></tr></thead><tbody><tr><td style="text-align:right">10</td><td style="text-align:right">28</td><td style="text-align:right">380</td><td style="text-align:left">Sequential wins</td></tr><tr><td style="text-align:right">25</td><td style="text-align:right">71</td><td style="text-align:right">390</td><td style="text-align:left">Sequential wins</td></tr><tr><td style="text-align:right">50</td><td style="text-align:right">142</td><td style="text-align:right">395</td><td style="text-align:left">Roughly equal</td></tr><tr><td style="text-align:right">100</td><td style="text-align:right">284</td><td style="text-align:right">412</td><td style="text-align:left">Parallel wins</td></tr><tr><td style="text-align:right">500</td><td style="text-align:right">1,420</td><td style="text-align:right">680</td><td style="text-align:left">Parallel wins (2×)</td></tr><tr><td style="text-align:right">1,000</td><td style="text-align:right">2,840</td><td style="text-align:right">920</td><td style="text-align:left">Parallel wins (3×)</td></tr><tr><td style="text-align:right">10,000</td><td style="text-align:right">28,400</td><td style="text-align:right">4,200</td><td style="text-align:left">Parallel wins (6.7×)</td></tr></tbody></table>
<p><em>Measurements on a 4-core runner, cold start. Custom rules with moderate complexity.</em></p>
<p>The ~380 ms fixed overhead of <code>ProcessPoolExecutor</code> spawn is the reason the threshold
is not set lower. A threshold of 10 files would cause sequential scans of small repos
to pay the spawn cost without benefit.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="vsm-construction-vs-link-validation">VSM Construction vs Link Validation<a href="https://zenzic.dev/blog/obsidian-masterclass#vsm-construction-vs-link-validation" class="hash-link" aria-label="Direct link to VSM Construction vs Link Validation" title="Direct link to VSM Construction vs Link Validation" translate="no">​</a></h3>
<p>The scan time breakdown for a 1,000-file project:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Discovery (walk + read):   ~450 ms   (I/O bound — disk sequential)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">VSM construction:          ~120 ms   (CPU bound — adapter URL mapping)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Anchor cache build:         ~80 ms   (CPU bound — heading slug extraction)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Link validation:            ~95 ms   (CPU bound — 50,000 hash lookups)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Orphan detection:           ~35 ms   (CPU bound — frozenset intersection)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Shield scan:               ~210 ms   (CPU bound — regex over 1M lines)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Report rendering:           ~40 ms   (CPU bound — Rich formatting)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">─────────────────────────────────────</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Total:                    ~1,030 ms</span><br></div></code></pre></div></div>
<p>:::note Benchmark conditions
These figures are for <strong>synthetic Markdown files</strong> (minimal frontmatter, no JSX, ~10
lines of prose). Real-world MDX files with frontmatter, JSX components, tables, and
dense link graphs cost significantly more per file. Measured against the real
<code>zenzic-doc</code> project (59 MDX pages): ~7 ms/file vs ~0.5 ms/file for synthetic files.
Run <code>python scripts/benchmark.py --repo &lt;path&gt;</code> to measure your own project.
:::</p>
<p>Link validation at 50,000 links takes 95 ms — less than the report rendering phase.
This is the O(1) hash map in practice: 50,000 <code>dict.get()</code> calls at ~1.9 µs each.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="memory-profile">Memory Profile<a href="https://zenzic.dev/blog/obsidian-masterclass#memory-profile" class="hash-link" aria-label="Direct link to Memory Profile" title="Direct link to Memory Profile" translate="no">​</a></h3>
<p>The VSM for a 10,000-file project:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Route objects:    10,000 × ~280 bytes  =   ~2.8 MB</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Anchor cache:     10,000 × ~1,200 bytes = ~12.0 MB</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">md_contents:      10,000 × ~8,000 bytes = ~80.0 MB</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">─────────────────────────────────────────────────</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Total RSS:                               ~95 MB</span><br></div></code></pre></div></div>
<p>The dominant cost is <code>md_contents</code> — the raw Markdown text held in memory for the
Shield scan. Zenzic holds all files in memory simultaneously to avoid repeated I/O
during multi-pass analysis. For projects above 50,000 files, a chunked processing
mode is planned for a future release.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-platform-ci-matrix">Cross-Platform CI Matrix<a href="https://zenzic.dev/blog/obsidian-masterclass#cross-platform-ci-matrix" class="hash-link" aria-label="Direct link to Cross-Platform CI Matrix" title="Direct link to Cross-Platform CI Matrix" translate="no">​</a></h3>
<p>Zenzic's test suite runs a 3×3 platform matrix on every commit:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">OS:     [ubuntu-latest, windows-latest, macos-latest]</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Python: [3.11,          3.12,          3.13         ]</span><br></div></code></pre></div></div>
<p>9 parallel CI jobs. All 1,342+ tests must pass on all 9 combinations. This is the
<strong>portability guarantee</strong>: Zenzic's output is identical across all platforms. A
scan that passes on Ubuntu passes on macOS and Windows — critical for teams using
heterogeneous development environments.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-ix--the-adapter-contract-extending-zenzic">Act IX — The Adapter Contract: Extending Zenzic<a href="https://zenzic.dev/blog/obsidian-masterclass#act-ix--the-adapter-contract-extending-zenzic" class="hash-link" aria-label="Direct link to Act IX — The Adapter Contract: Extending Zenzic" title="Direct link to Act IX — The Adapter Contract: Extending Zenzic" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-baseadapter-protocol">The BaseAdapter Protocol<a href="https://zenzic.dev/blog/obsidian-masterclass#the-baseadapter-protocol" class="hash-link" aria-label="Direct link to The BaseAdapter Protocol" title="Direct link to The BaseAdapter Protocol" translate="no">​</a></h3>
<p>Zenzic's Core (<code>validator.py</code>, <code>scanner.py</code>) contains zero engine-name references.
This is <strong>Purity Protocol</strong> — Rule R21 (Protocol Sovereignty). Any engine-specific
behavior must be declared via the <code>AdapterProtocol</code> and queried by the Core.</p>
<p>The adapter protocol (simplified):</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">AdapterProtocol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Protocol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_nav_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Return navigable paths from all user-clickable surfaces."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">map_url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Map a source file to its canonical URL."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">classify_route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> nav_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> RouteStatus</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Classify a route as REACHABLE, ORPHAN_BUT_EXISTING, IGNORED, or CONFLICT."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">provides_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> directory_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""True when the directory will have a landing page."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_metadata_files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Return Level 1 System Guardrail files (excluded from all checks)."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_link_scheme_bypasses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Return URI schemes that bypass Z105 absolute-path validation."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></div></code></pre></div></div>
<p>The Core calls <code>adapter.get_nav_paths()</code>. It receives a <code>frozenset[str]</code>. What
generated that frozenset — whether it came from <code>sidebars.ts</code>, <code>mkdocs.yml</code>, or
<code>zensical.toml</code> — is invisible to the Core.</p>
<p>Adding a new adapter requires implementing this protocol. Adding an engine-specific
behavior by modifying <code>validator.py</code> is a <strong>protocol violation</strong> and will be rejected
in code review.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pathname-bypass-rule-r16">The <code>pathname:///</code> Bypass (Rule R16)<a href="https://zenzic.dev/blog/obsidian-masterclass#the-pathname-bypass-rule-r16" class="hash-link" aria-label="Direct link to the-pathname-bypass-rule-r16" title="Direct link to the-pathname-bypass-rule-r16" translate="no">​</a></h3>
<p>Docusaurus uses <code>pathname:///</code> as a Diplomatic Courier — an escape hatch for linking
to static assets that are not part of the docs routing system:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token url">[</span><span class="token url content">Download PDF</span><span class="token url">](</span><span class="token url">pathname:///assets/whitepaper.pdf</span><span class="token url">)</span><br></div></code></pre></div></div>
<p>The Z105 gate (ABSOLUTE_PATH) normally fires on any path starting with <code>/</code>. The
<code>pathname:///</code> URI scheme is exempt in Docusaurus mode:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_link_scheme_bypasses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"pathname"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p>The Core queries <code>adapter.get_link_scheme_bypasses()</code> before applying Z105. This is
R16 — Protocol Awareness — in action: engine-specific behavior declared in the
adapter, queried by the Core, with no <code>if engine == "docusaurus"</code> in Core logic.</p>
<p>In all other engines (MkDocs, Zensical, Standalone), <code>pathname:///</code> is unrecognized
and triggers Z105 normally. The bypass is scoped precisely.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="level-1-system-guardrails">Level 1 System Guardrails<a href="https://zenzic.dev/blog/obsidian-masterclass#level-1-system-guardrails" class="hash-link" aria-label="Direct link to Level 1 System Guardrails" title="Direct link to Level 1 System Guardrails" translate="no">​</a></h3>
<p>Adapter metadata files — <code>docusaurus.config.ts</code>, <code>mkdocs.yml</code>, <code>zensical.toml</code>,
<code>package.json</code>, <code>pyproject.toml</code> — are declared as <strong>Level 1 System Guardrails</strong>
via <code>get_metadata_files()</code>. These files are:</p>
<ul>
<li class="">Permanently excluded from Z903 (UNUSED_ASSET) checks</li>
<li class="">Permanently excluded from all quality checks</li>
<li class="">Never presented to the user as orphans, placeholders, or short-content warnings</li>
</ul>
<p>The rationale (Rule R13 — Intelligent Perimeter): asking the user to manually exclude
their own build configuration files from analysis is a failure of the tool, not a
configuration task. The adapter knows what its metadata files are; the Core does not
need to be told.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="act-x--getting-started">Act X — Getting Started<a href="https://zenzic.dev/blog/obsidian-masterclass#act-x--getting-started" class="hash-link" aria-label="Direct link to Act X — Getting Started" title="Direct link to Act X — Getting Started" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="immediate-verification-no-installation">Immediate Verification (No Installation)<a href="https://zenzic.dev/blog/obsidian-masterclass#immediate-verification-no-installation" class="hash-link" aria-label="Direct link to Immediate Verification (No Installation)" title="Direct link to Immediate Verification (No Installation)" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic lab</span><br></div></code></pre></div></div>
<p><code>uvx</code> resolves the latest Zenzic from PyPI, installs it in an isolated temporary
environment, and runs the interactive Lab. Seventeen Acts, each demonstrating a
distinct capability. The entire experience requires no project setup.</p>
<p>Start with Act 3 — the Shield in action against a planted Stripe live key. Watch
the Sentinel exit with code 2. That exit code is the promise.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="your-first-scan">Your First Scan<a href="https://zenzic.dev/blog/obsidian-masterclass#your-first-scan" class="hash-link" aria-label="Direct link to Your First Scan" title="Direct link to Your First Scan" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic check all ./docs</span><br></div></code></pre></div></div>
<p>Zenzic will:</p>
<ol>
<li class="">Discover your documentation engine (Docusaurus, MkDocs, Zensical, or Standalone)</li>
<li class="">Build the VSM from your source files</li>
<li class="">Run the Shield across every line of every file</li>
<li class="">Validate all internal links against the VSM</li>
<li class="">Detect orphan pages via R21 (navbar + sidebar + footer analysis)</li>
<li class="">Report all findings with Zxxx codes, file paths, and line numbers</li>
</ol>
<p>On a 100-page Docusaurus site: expect 2–4 seconds, cold start.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pinned-ci-integration">Pinned CI Integration<a href="https://zenzic.dev/blog/obsidian-masterclass#pinned-ci-integration" class="hash-link" aria-label="Direct link to Pinned CI Integration" title="Direct link to Pinned CI Integration" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">.github/workflows/zenzic.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Documentation Integrity Gate</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">branches</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">main</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">pull_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">sentinel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">runs-on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">permissions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">security-events</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> write  </span><span class="token comment" style="color:rgb(98, 114, 164)"># required for SARIF upload</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> actions/checkout@v4</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> astral</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">sh/setup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">uv@v5</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> PythonWoods/zenzic</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">action@v1</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"0.7.0"</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># pinned — deterministic CI gate</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">format</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> sarif</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">upload-sarif</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><br></div></code></pre></div></div>
<p>Version pinning (<code>version: "0.7.0"</code>) is mandatory for production pipelines. <code>latest</code>
is appropriate for exploration; it introduces non-determinism into your CI gate.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="zenzictoml-configuration"><code>zenzic.toml</code> Configuration<a href="https://zenzic.dev/blog/obsidian-masterclass#zenzictoml-configuration" class="hash-link" aria-label="Direct link to zenzictoml-configuration" title="Direct link to zenzictoml-configuration" translate="no">​</a></h3>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">zenzic.toml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key property">docs_dir</span><span class="token plain">   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"docs"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">fail_under</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">95</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># quality score gate: fail if score drops below 95</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Excluded external URLs (temporary — remove after deployment)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">excluded_external_urls</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"https://internal.corp.example.com/api"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Excluded asset patterns (Docusaurus sidebar metadata)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">excluded_assets</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"**/_category_.json"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">build_context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">engine</span><span class="token plain">         </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"docusaurus"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">base_url</span><span class="token plain">       </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">default_locale</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"en"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">locales</span><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"it"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"fr"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<p>The 4-level configuration priority: <strong>CLI flags &gt; <code>zenzic.toml</code> &gt; <code>pyproject.toml</code>
<code>[tool.zenzic]</code> &gt; built-in defaults</strong>. CLI flags always win. This allows temporary
overrides without modifying project configuration.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="standalone-mode">Standalone Mode<a href="https://zenzic.dev/blog/obsidian-masterclass#standalone-mode" class="hash-link" aria-label="Direct link to Standalone Mode" title="Direct link to Standalone Mode" translate="no">​</a></h3>
<p>For projects with no build system — raw Markdown directories, GitHub wikis, plain
doc trees:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">zenzic.toml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token key property">docs_dir</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">build_context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">engine</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"standalone"</span><br></div></code></pre></div></div>
<p>In Standalone mode:</p>
<ul>
<li class="">Orphan detection (Z402) is <strong>disabled</strong> — there is no navigation contract</li>
<li class="">Link validation still runs — broken links are broken regardless of engine</li>
<li class="">The Shield still runs — credentials are credentials regardless of engine</li>
<li class="">The Blood Sentinel still runs — path traversal is path traversal regardless of engine</li>
</ul>
<p>The security guarantees are engine-independent. Only the navigation contract is scoped.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="brand-integrity-z905-brand_obsolescence">Brand Integrity: Z905 BRAND_OBSOLESCENCE<a href="https://zenzic.dev/blog/obsidian-masterclass#brand-integrity-z905-brand_obsolescence" class="hash-link" aria-label="Direct link to Brand Integrity: Z905 BRAND_OBSOLESCENCE" title="Direct link to Brand Integrity: Z905 BRAND_OBSOLESCENCE" translate="no">​</a></h3>
<p>The fourth dimension of the Safe Harbor — beyond structural, security, and content
correctness — is <strong>narrative integrity</strong>. A documentation suite that refers to a
deprecated release codename has a different class of bug: it tells the wrong story.</p>
<p>Configure <code>[project_metadata]</code> in <code>zenzic.toml</code> to activate the Brand Integrity layer:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">zenzic.toml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">project_metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">release_name</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Quartz"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">obsolete_names</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"Obsidian"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">obsolete_names_exclude_patterns</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"CHANGELOG*.md"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"adr-*.mdx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></div></code></pre></div></div>
<div class="max-w-xl mx-auto my-6"><div class="dark:bg-zinc-900/20 bg-zinc-50 backdrop-blur-md rounded-xl py-5 px-6 font-mono text-[12px] leading-relaxed shadow-2xl border dark:border-zinc-700/20 border-zinc-200"><div class="space-y-2 mb-4"><div class="flex items-start gap-2"><span class="text-rose-500 flex-shrink-0 select-none">✘</span><span class="text-cyan-500 dark:text-cyan-400 w-44 flex-shrink-0 truncate">docs/explanation/architecture.mdx</span><span class="dark:text-zinc-500 text-zinc-400 w-14 flex-shrink-0">Z905</span><span class="dark:text-zinc-300 text-zinc-700">Obsolete brand term 'Obsidian': use 'Quartz' instead. Add &lt;!-- zenzic:ignore Z905 --&gt; (Markdown) or {/* zenzic:ignore Z905 */} (MDX) to the line to suppress intentional references.</span></div></div><div class="border-t dark:border-zinc-800/40 border-zinc-200 pt-3 flex flex-wrap gap-4"><span class="text-rose-500 font-medium">✘ <!-- -->1<!-- --> <!-- -->error</span></div></div></div>
<p>The <code>zenzic:ignore Z905</code> escape hatch is precise by design: it applies to a single line,
not a whole file. A CHANGELOG entry that says "Released under the Obsidian codename"
is historical fact. An architecture page that describes the current system as
"Obsidian-based" is a lie that the source code has already corrected.</p>
<hr>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>The Sentinel's Filter — Why Every Quartz Rule Exists</div><div class="admonitionContent_BuS1"><p>Every rule in the Quartz Core must pass a three-dimensional admission test before it ships:
<strong>Structural Integrity</strong> (broken links, orphans, missing indices), <strong>Hardened Security</strong>
(credentials, path traversal), or <strong>Technical Accessibility</strong> (machine-readable contracts
for downstream tooling — Z505 is the canonical example). Rules that fail this filter
— line length, list style, spelling — are deliberately out of scope. Zenzic is a Sentinel,
not a Proofreader.</p><p><a href="https://zenzic.dev/docs/explanation/structural-integrity" target="_blank" rel="noopener noreferrer" class="">Read the full rationale →</a></p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="epilogue-the-documentation-is-the-source">Epilogue: The Documentation is the Source<a href="https://zenzic.dev/blog/obsidian-masterclass#epilogue-the-documentation-is-the-source" class="hash-link" aria-label="Direct link to Epilogue: The Documentation is the Source" title="Direct link to Epilogue: The Documentation is the Source" translate="no">​</a></h2>
<p>The engineering tradition treats documentation as secondary — a description of the
system, not the system itself. This tradition is breaking down.</p>
<p>In 2026, documentation is:</p>
<ul>
<li class=""><strong>The primary interface</strong> for internal APIs in large organizations</li>
<li class=""><strong>The trust signal</strong> that developers use to evaluate whether a library is maintained</li>
<li class=""><strong>The compliance artifact</strong> that auditors examine in regulated industries</li>
<li class=""><strong>The attack surface</strong> that adversaries probe for exposed credentials and path traversal</li>
</ul>
<p>A documentation pipeline that trusts its input is not a pipeline. It is a hope.</p>
<p>Zenzic exists because the question <em>"is this documentation correct?"</em> is not the same
question as <em>"did this build succeed?"</em> A build that succeeds on broken documentation
has not validated anything. It has just run faster.</p>
<p>The Safe Harbor is not a metaphor. It is an architectural guarantee: every file that
passes Zenzic's three layers — the Structural Validator, the Shield, and the Blood
Sentinel — has been verified against the navigation contract of your specific build
engine, scanned for all known credential formats with 8-stage normalization, and
checked for path traversal against an explicitly declared perimeter.</p>
<p>That is the promise. Every exit-0 scan is the proof.</p>
<hr>
<p><em>For the full engineering history of how these layers were designed, tested under
AI-generated siege, and hardened across five sprints — read the
<a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">🛡️ The Zenzic Chronicles →</a>.</em></p>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr><tr><td><strong>PyPI</strong></td><td><a href="https://pypi.org/project/zenzic/" target="_blank" rel="noopener noreferrer" class="">pypi.org/project/zenzic</a></td></tr><tr><td><strong>Lab</strong></td><td><code>uvx zenzic lab</code></td></tr></tbody></table>]]></content:encoded>
            <category>Engineering</category>
            <category>Security</category>
            <category>Tutorial</category>
            <category>Quartz Maturity</category>
        </item>
        <item>
            <title><![CDATA[The Governance of Quartz: Why Integrity Requires a Constitution]]></title>
            <link>https://zenzic.dev/blog/governance-of-quartz</link>
            <guid>https://zenzic.dev/blog/governance-of-quartz</guid>
            <pubDate>Wed, 29 Apr 2026 19:15:00 GMT</pubDate>
            <description><![CDATA[AI-Adversarial / Human-Governed]]></description>
            <content:encoded><![CDATA[<blockquote>
<p><a href="https://zenzic.dev/developers/governance/adversarial-ai" target="_blank" rel="noopener noreferrer" class=""><img decoding="async" loading="lazy" src="https://img.shields.io/badge/AI--Adversarial-Human--Governed-black?style=flat-square" alt="AI-Adversarial / Human-Governed" class="img_ev3q"></a></p>
</blockquote>
<p><em>Software does not die when its code grows old.</em></p>
<p><em>It dies when the pact between the author and the user is broken — the implicit promise
that what works today will still make sense tomorrow, that the rules of the game will not
shift mid-journey, that the tool you trusted will not become the problem you need to escape.</em></p>
<p><em>This is the sixth chronicle. The one we were always building toward.</em></p>
<!-- -->
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="part-i--the-ghost-of-broken-promises">Part I — The Ghost of Broken Promises<a href="https://zenzic.dev/blog/governance-of-quartz#part-i--the-ghost-of-broken-promises" class="hash-link" aria-label="Direct link to Part I — The Ghost of Broken Promises" title="Direct link to Part I — The Ghost of Broken Promises" translate="no">​</a></h2>
<p>There is a ghost that haunts mature software projects. Engineers rarely name it, because
naming it requires admitting that the problem is not technical. The ghost is not a bug.
It is not a performance regression. It is not a security vulnerability.</p>
<p><strong>The ghost is a broken promise.</strong></p>
<p>The Zenzic Chronicles began with a broken promise.
<a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> documented the credential that leaked
through a MkDocs pipeline — not because the build tool failed, but because the <em>integration
model</em> between Zenzic and MkDocs had created a hidden assumption: that Zenzic would run
<em>as part of the build</em>, and therefore its security guarantees were only valid when the build
executed cleanly.</p>
<p>This is a ghost assumption. It lives in the architecture. It survives refactors. It outlasts
the engineer who introduced it. And it only becomes visible when someone asks, at the wrong
moment: <em>"what exactly did we promise?"</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-instability-at-the-foundation">The Instability at the Foundation<a href="https://zenzic.dev/blog/governance-of-quartz#the-instability-at-the-foundation" class="hash-link" aria-label="Direct link to The Instability at the Foundation" title="Direct link to The Instability at the Foundation" translate="no">​</a></h3>
<p><a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> documented a second failure
mode: Docusaurus link resolution instability when the site was deployed in subdirectory
configurations. Links that worked locally failed in production. The build succeeded. The
Shield found nothing. The links were broken.</p>
<p>Both failures share a structural cause: <strong>the tool was integrated into the build system
instead of guarding the source before it.</strong> When Zenzic runs <em>inside</em> the build, it inherits
all of the build system's assumptions. Its guarantees become conditional. "The analysis is
clean" becomes "the analysis is clean, given the following 17 implicit preconditions about
your deployment environment."</p>
<p>That is not a guarantee. That is a disclaimer.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-software-mortality-table">The Software Mortality Table<a href="https://zenzic.dev/blog/governance-of-quartz#the-software-mortality-table" class="hash-link" aria-label="Direct link to The Software Mortality Table" title="Direct link to The Software Mortality Table" translate="no">​</a></h3>
<p>Projects die in predictable ways. Not from catastrophic failure — from erosion. From the
accumulation of reasonable exceptions, each of which makes perfect local sense.</p>
<table><thead><tr><th style="text-align:left">Stage</th><th style="text-align:left">What Happens</th><th style="text-align:left">Symptom</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Year 1</strong></td><td style="text-align:left">"We'll make this one exception — it's urgent."</td><td style="text-align:left">A single subprocess call sneaks in.</td></tr><tr><td style="text-align:left"><strong>Year 2</strong></td><td style="text-align:left">"The exception is now load-bearing. We can't remove it."</td><td style="text-align:left">The invariant no longer holds unconditionally.</td></tr><tr><td style="text-align:left"><strong>Year 3</strong></td><td style="text-align:left">"The original design philosophy doesn't apply here anymore."</td><td style="text-align:left">Architecture has silently changed without announcement.</td></tr><tr><td style="text-align:left"><strong>Year 4</strong></td><td style="text-align:left">"We need a full rewrite to move forward."</td><td style="text-align:left">The ghost has won.</td></tr></tbody></table>
<p>The pattern is not ignorance. The engineers who make these decisions are intelligent. They
are making rational local optimizations. The problem is that <strong>there is no system that forces
the global cost of each local exception to be visible before it is committed.</strong></p>
<p>Governance is that system.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architecture-of-trust">The Architecture of Trust<a href="https://zenzic.dev/blog/governance-of-quartz#the-architecture-of-trust" class="hash-link" aria-label="Direct link to The Architecture of Trust" title="Direct link to The Architecture of Trust" translate="no">​</a></h3>
<p>Trust in a software tool is not built from documentation. It is built from <strong>demonstrated
constraints</strong>. A tool that could violate your expectations at any moment provides no
safety — only the appearance of it.</p>
<p>The difference between a <em>policy</em> and a <em>law</em> is enforcement. Zenzic can write any policy
document it wants and ignore it entirely. But a constitutional process — one that requires a
major version bump, a 30-day public period, and documented adversarial validation before any
Pillar can change — is a constraint that costs something to violate. That cost is what
transforms a design principle into an architectural guarantee.</p>
<blockquote>
<p><em>"The ghost is not a bug in the code. It is a promise forgotten — made when the design was
pure, abandoned when the pressure was real, invisible until the day the user discovers that
what was promised and what was delivered have quietly diverged."</em></p>
</blockquote>
<p>The Governance of Glass is the formal answer to the ghost. Not a process for slowing things
down. A process for ensuring that every exception, every evolution, every architectural change
is made with full awareness of its cost to the pact.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="part-ii--the-sovereignty-oath-liberty-as-a-feature">Part II — The Sovereignty Oath: Liberty as a Feature<a href="https://zenzic.dev/blog/governance-of-quartz#part-ii--the-sovereignty-oath-liberty-as-a-feature" class="hash-link" aria-label="Direct link to Part II — The Sovereignty Oath: Liberty as a Feature" title="Direct link to Part II — The Sovereignty Oath: Liberty as a Feature" translate="no">​</a></h2>
<p>The most unusual document in Zenzic's Governance section is the
<a class="" href="https://zenzic.dev/developers/governance/exit_strategy">Sovereignty Oath</a>.</p>
<p>It is a formal document explaining how to remove Zenzic from your project.</p>
<p>We wrote it during the foundational design phase. We wrote it before v0.7.0 was stable. We wrote it
because we believe that a tool that cannot prove its own reversibility is asking you to trust
it on faith — and the Zenzic trust model is Zero-Trust, including toward Zenzic itself.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-zero-residue-guarantee">The Zero Residue Guarantee<a href="https://zenzic.dev/blog/governance-of-quartz#the-zero-residue-guarantee" class="hash-link" aria-label="Direct link to The Zero Residue Guarantee" title="Direct link to The Zero Residue Guarantee" translate="no">​</a></h3>
<blockquote>
<p><strong>Zenzic is the only dependency that swears to be invisible if you decide to remove it.</strong></p>
</blockquote>
<p>This is not marketing copy. It is a verifiable engineering claim. When you remove Zenzic from
a project, the following components remain <strong>unchanged</strong>:</p>
<table><thead><tr><th style="text-align:left">What You Lose</th><th style="text-align:left">What Remains</th></tr></thead><tbody><tr><td style="text-align:left">The CI integrity gate</td><td style="text-align:left">Your source files — never mutated, not a single byte</td></tr><tr><td style="text-align:left">The Shield credential scanner</td><td style="text-align:left">Your application code — never imported at runtime</td></tr><tr><td style="text-align:left">The Blood Sentinel path guardian</td><td style="text-align:left">Your Python types — <code>typing.Protocol</code>, not inheritance chains</td></tr><tr><td style="text-align:left">The VSM link validator</td><td style="text-align:left">Your configuration — one TOML section or one file to delete</td></tr><tr><td style="text-align:left">The SARIF reporting pipeline</td><td style="text-align:left">Your CI — one workflow step to remove</td></tr></tbody></table>
<p><strong>Total decommission time: 30 seconds.</strong> No migration script. No data format to convert.
No architecture to dismantle. No vendor lock-in to escape.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="read-only-by-constitution">Read-Only by Constitution<a href="https://zenzic.dev/blog/governance-of-quartz#read-only-by-constitution" class="hash-link" aria-label="Direct link to Read-Only by Constitution" title="Direct link to Read-Only by Constitution" translate="no">​</a></h3>
<p>The audit core of Zenzic is strictly read-only. This is not a current implementation detail
awaiting refactoring. It is a constitutional invariant of the Safe Harbor.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># The Zenzic analysis core: observation only</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Source files are opened in read mode. Always. Without exception</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">analyze</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Finding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pure function. Same input → same output. No writes. No side effects.</span><br></div></code></pre></div></div>
<p>Zenzic observes your documentation. It never mutates it.</p>
<p>Any future remediation features — such as a <code>zenzic fix</code> command — will be implemented as
separate, explicit, interactive utilities that the user invokes deliberately. The analysis
phase will remain 100% mutation-free.</p>
<p>This distinction matters. A linter that quietly "fixes" files during analysis has crossed
from <em>observer</em> to <em>actor</em>. The moment a tool modifies your sources without explicit
instruction, it becomes the source of unintended mutations — the very class of failure the
Safe Harbor was designed to prevent.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-structural-subtyping-guarantee">The Structural Subtyping Guarantee<a href="https://zenzic.dev/blog/governance-of-quartz#the-structural-subtyping-guarantee" class="hash-link" aria-label="Direct link to The Structural Subtyping Guarantee" title="Direct link to The Structural Subtyping Guarantee" translate="no">​</a></h3>
<p>Zenzic's adapter system uses
<a href="https://docs.python.org/3/library/typing.html#typing.Protocol" target="_blank" rel="noopener noreferrer" class=""><code>typing.Protocol</code></a> — the
structural subtyping mechanism from the Python standard library.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Zenzic adapter contract — structural, not nominal</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">AdapterProtocol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Protocol</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_docs_root</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_nav_paths</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_metadata_files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">frozenset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></div></code></pre></div></div>
<p>What this means in practice: your code never inherits from a Zenzic base class. There is no
<code>ZenzicAdapter</code> in your class hierarchy. When you remove Zenzic from your project, your
Python types are structurally identical to what they were before. The adapter contract is a
<em>lens</em> through which Zenzic reads your project — not a chain that binds your types to its
release cycle.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-we-wrote-the-exit-strategy-first">Why We Wrote the Exit Strategy First<a href="https://zenzic.dev/blog/governance-of-quartz#why-we-wrote-the-exit-strategy-first" class="hash-link" aria-label="Direct link to Why We Wrote the Exit Strategy First" title="Direct link to Why We Wrote the Exit Strategy First" translate="no">​</a></h3>
<p>A tool that makes leaving difficult does not have confidence in its value. It is protecting
its own presence. The conventional wisdom in developer tooling is that <em>switching costs</em> are
a moat — friction that keeps users from choosing a competitor.</p>
<p>Zenzic inverts this model. The Zero Residue guarantee is not a concession to user demands.
It is a <strong>design principle</strong>: we believe that tools should be adopted on merit, not retained
by friction.</p>
<p>If Zenzic stops providing value — if a better tool emerges, if your documentation stack
changes, if your security requirements evolve beyond what Zenzic can deliver — your exit
should cost 30 seconds of your life, not 30 days of migration work.</p>
<p>The pact we make with every user is simple and non-negotiable: <strong>the Sentinel exists to
protect your documentation, not to protect itself.</strong></p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="part-iii--the-adversarial-forge-ai-as-a-skeptic">Part III — The Adversarial Forge: AI as a Skeptic<a href="https://zenzic.dev/blog/governance-of-quartz#part-iii--the-adversarial-forge-ai-as-a-skeptic" class="hash-link" aria-label="Direct link to Part III — The Adversarial Forge: AI as a Skeptic" title="Direct link to Part III — The Adversarial Forge: AI as a Skeptic" translate="no">​</a></h2>
<p>The <code>AI-Adversarial / Human-Governed</code> badge is a declaration. Let us be precise about what
it declares.</p>
<blockquote>
<p><a href="https://zenzic.dev/developers/governance/adversarial-ai" target="_blank" rel="noopener noreferrer" class=""><img decoding="async" loading="lazy" src="https://img.shields.io/badge/AI--Adversarial-Human--Governed-black?style=flat-square" alt="AI-Adversarial / Human-Governed" class="img_ev3q"></a></p>
</blockquote>
<p>It does <strong>not</strong> declare that Zenzic was written with AI assistance. It does not declare that
AI was used to accelerate development. It declares something more specific — and more
demanding.</p>
<p><strong>Zenzic was stress-tested by AI acting as a public prosecutor. Every line of code is a
defendant.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-magistrate-model">The Magistrate Model<a href="https://zenzic.dev/blog/governance-of-quartz#the-magistrate-model" class="hash-link" aria-label="Direct link to The Magistrate Model" title="Direct link to The Magistrate Model" translate="no">​</a></h3>
<p>In the Adversarial Forge, AI is assigned the role of a magistrate tasked with building a
case for the prosecution. The charge is always the same: <em>"Violation of the Three Pillars."</em></p>
<p>The AI's job is not to suggest code. Its job is to find a path through the existing
architecture that bypasses a constitutional invariant. A working bypass is not a feature
request — it is a finding. It is treated with the same urgency as a CVE.</p>
<p>The human's job is not to accept the AI's suggestions. It is to evaluate each finding: Is
this a real vulnerability? Does it expose a genuine architectural weakness? If yes, fix it
in the same sprint and document it in the Zenzic Ledger. If not, record the unsuccessful
attack vector as evidence that the invariant held under adversarial pressure.</p>
<p><strong>The AI proposes. The AI attacks. The AI does not ratify.</strong></p>
<p>When an AI session produces a suggestion to relax a Pillar — <em>"this rule would be much
simpler to implement with a subprocess call"</em> — that suggestion is not evaluated on its
technical merits in isolation. It is flagged as evidence that the Pillar is under pressure.
The response is to harden the invariant documentation, not to accept the suggestion.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-four-sessions-of-the-forge">The Four Sessions of the Forge<a href="https://zenzic.dev/blog/governance-of-quartz#the-four-sessions-of-the-forge" class="hash-link" aria-label="Direct link to The Four Sessions of the Forge" title="Direct link to The Four Sessions of the Forge" translate="no">​</a></h3>
<p>Every architectural decision in Zenzic has been subjected to one or more of four adversarial
session types:</p>
<table><thead><tr><th style="text-align:left">Session</th><th style="text-align:left">Target</th><th style="text-align:left">What "Success" Means for the AI</th></tr></thead><tbody><tr><td style="text-align:left"><strong>Type A — Architecture Hunt</strong></td><td style="text-align:left">Any <code>[INVARIANT]</code> in the Zenzic Ledger</td><td style="text-align:left">A real code path that violates the declared invariant</td></tr><tr><td style="text-align:left"><strong>Type B — ReDoS Canary</strong></td><td style="text-align:left">The <code>AdaptiveRuleEngine</code> regex acceptance</td><td style="text-align:left">A user-provided pattern with catastrophic backtracking on &gt;1 KiB input</td></tr><tr><td style="text-align:left"><strong>Type C — Shield Bypass</strong></td><td style="text-align:left">The 8-stage normalization pipeline</td><td style="text-align:left">A Markdown fragment containing a real credential that passes all 8 stages undetected</td></tr><tr><td style="text-align:left"><strong>Type D — Blood Sentinel Escape</strong></td><td style="text-align:left"><code>InMemoryPathResolver._build_target()</code></td><td style="text-align:left">A path string that resolves outside <code>docs_root</code> without containing literal <code>../</code></td></tr></tbody></table>
<p>Every "success" for the AI is a failure for Zenzic — a finding that gets fixed before the
next release. Every unsuccessful session is documented as evidence that the defense held
under real adversarial pressure. The Zenzic Ledger records both outcomes with equal rigor.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="quartz-clarity-pure-under-pressure">Quartz Clarity: Pure Under Pressure<a href="https://zenzic.dev/blog/governance-of-quartz#quartz-clarity-pure-under-pressure" class="hash-link" aria-label="Direct link to Quartz Clarity: Pure Under Pressure" title="Direct link to Quartz Clarity: Pure Under Pressure" translate="no">​</a></h3>
<p>The metaphor of "Quartz Clarity" is not decorative. Quartz forms under geological pressure
rock is cooled rapidly under extreme pressure. Its crystalline structure is the result of
having been tested at its limits.</p>
<p>The Eight normalization stages of the Shield — Unicode normalization, HTML entity decoding,
invisible character stripping, base64 fragment detection, URL encoding expansion, homoglyph
substitution, case folding, whitespace collapse — did not emerge from architectural planning
alone. They emerged from successive Type C sessions in which an AI was tasked with
constructing credential-containing Markdown fragments that could bypass detection at each stage.</p>
<p>Stage 1 stopped naive plaintext attacks. Stage 3 stopped Unicode codepoint tricks. Stage 6
was added after an AI constructed a homoglyph-substituted token that survived stages 1
through 5. Stage 8 was added after a whitespace collapse bypass was found that survived
stages 1 through 7.</p>
<blockquote>
<p><em>"The Shield has eight stages because eight attacks were found and survived. Every stage is
the crystallized memory of a bypass attempt that reached production-ready code before being
caught by the Forge."</em></p>
</blockquote>
<p>This is Quartz Clarity: not a design reviewed in theory, but a structure tested under
the actual force of adversarial intelligence. Its clarity comes from its history of pressure.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-ai-does-not-decide">What AI Does Not Decide<a href="https://zenzic.dev/blog/governance-of-quartz#what-ai-does-not-decide" class="hash-link" aria-label="Direct link to What AI Does Not Decide" title="Direct link to What AI Does Not Decide" translate="no">​</a></h3>
<p>The AI is a Red Team, not a co-architect. It operates within strict boundaries:</p>
<table><thead><tr><th style="text-align:left">Decision</th><th style="text-align:left">Authority</th><th style="text-align:left">Why Not AI</th></tr></thead><tbody><tr><td style="text-align:left">The Three Pillars (architecture)</td><td style="text-align:left">Human — non-delegable</td><td style="text-align:left">Pillars are value judgments, not optimization problems</td></tr><tr><td style="text-align:left">The Zxxx finding code semantics</td><td style="text-align:left">Human — ratified in <code>core/codes.py</code></td><td style="text-align:left">Diagnostic contracts affect every user's CI pipeline</td></tr><tr><td style="text-align:left">The exit code contract (0/1/2/3)</td><td style="text-align:left">Human — immutable</td><td style="text-align:left">Security guarantees cannot be probabilistic</td></tr><tr><td style="text-align:left">Sprint scope and release schedule</td><td style="text-align:left">Human</td><td style="text-align:left">Trade-offs require contextual judgment</td></tr><tr><td style="text-align:left">Whether an AI finding is a real vulnerability</td><td style="text-align:left">Human Integrity Guardian</td><td style="text-align:left">The prosecutor does not convict; the judge does</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="part-iv--the-constitutional-invariants">Part IV — The Constitutional Invariants<a href="https://zenzic.dev/blog/governance-of-quartz#part-iv--the-constitutional-invariants" class="hash-link" aria-label="Direct link to Part IV — The Constitutional Invariants" title="Direct link to Part IV — The Constitutional Invariants" translate="no">​</a></h2>
<p>After six Chronicles, after thousands of test cases, after dozens of adversarial sessions,
the Three Pillars have been promoted. They are no longer <em>design choices</em>. They are
<strong>Constitutional Law</strong>.</p>
<p>This is not rhetorical elevation. It has a precise engineering meaning: a process exists
that makes violating them more expensive than defending them.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-articles-of-the-safe-harbor">The Three Articles of the Safe Harbor<a href="https://zenzic.dev/blog/governance-of-quartz#the-three-articles-of-the-safe-harbor" class="hash-link" aria-label="Direct link to The Three Articles of the Safe Harbor" title="Direct link to The Three Articles of the Safe Harbor" translate="no">​</a></h3>
<table><thead><tr><th style="text-align:left">Article</th><th style="text-align:left">Invariant</th><th style="text-align:left">Protected Guarantee</th></tr></thead><tbody><tr><td style="text-align:left"><strong>I — Lint the Source</strong></td><td style="text-align:left">Analysis operates on raw Markdown and configuration files. Never on HTML output or compiled artifacts.</td><td style="text-align:left">Pre-build integrity. Zenzic fires before your pipeline, not inside it. No build system preconditions.</td></tr><tr><td style="text-align:left"><strong>II — Zero Subprocesses</strong></td><td style="text-align:left">100% pure Python. No <code>subprocess.run</code>, no <code>os.system</code>, no external process of any kind.</td><td style="text-align:left">Zero-Trust execution. No process Zenzic cannot audit. No external dependency Zenzic cannot enumerate.</td></tr><tr><td style="text-align:left"><strong>III — Pure Functions First</strong></td><td style="text-align:left">Analysis logic is deterministic. The same input always produces the same findings, in the same order, with the same line numbers.</td><td style="text-align:left">Reproducibility. A finding is not an observation — it is a reproducible fact that can be verified independently.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-constitutional-amendment-process">The Constitutional Amendment Process<a href="https://zenzic.dev/blog/governance-of-quartz#the-constitutional-amendment-process" class="hash-link" aria-label="Direct link to The Constitutional Amendment Process" title="Direct link to The Constitutional Amendment Process" translate="no">​</a></h3>
<p>A change that violates any of these articles — even temporarily, even for a genuinely good
engineering reason, even under deadline pressure — is not a bug fix or a feature. It is a
<strong>constitutional amendment</strong>. And constitutional amendments in Zenzic require:</p>
<ol>
<li class="">
<p><strong>A Major version increment</strong> — e.g., v0.7.0 → v1.0.0. Users who depend on the current</p>
<p>Pillar semantics remain on the current major version. The change cannot sneak into a minor
or patch release.</p>
</li>
<li class="">
<p><strong>A 30-day public impact period</strong> — announced in a public issue before any code is written.</p>
<p>The period exists so that enterprise users can evaluate the impact on their pipelines, not
to create bureaucratic delay.</p>
</li>
<li class="">
<p><strong>A formal Architectural Decision Record (ADR)</strong> — added to the Zenzic Ledger with: the</p>
<p>text of the invariant being modified, the proposed replacement, the rationale, and a full
cost analysis covering migration burden and trust model impact.</p>
</li>
<li class="">
<p><strong>A Type A Adversarial AI session</strong> — targeting the proposed replacement architecture. The</p>
<p>AI must attempt to find Pillar violations in the new design before it is ratified. A
replacement architecture that cannot survive a single adversarial session does not replace
a constitutional article.</p>
</li>
<li class="">
<p><strong>Consensus of 2/3 of Core Maintainers</strong> — not a simple majority. Constitutional changes</p>
<p>require supermajority ratification.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-evolution-policy-no-surprises-at-scale">The Evolution Policy: No Surprises at Scale<a href="https://zenzic.dev/blog/governance-of-quartz#the-evolution-policy-no-surprises-at-scale" class="hash-link" aria-label="Direct link to The Evolution Policy: No Surprises at Scale" title="Direct link to The Evolution Policy: No Surprises at Scale" translate="no">​</a></h3>
<p>The <a class="" href="https://zenzic.dev/developers/governance/evolution_policy">Evolution Policy</a> exists to answer one
question that every engineering team eventually asks when adopting an external tool:</p>
<blockquote>
<p><em>"Will the rules of this tool change in a way that breaks our pipeline without warning?"</em></p>
</blockquote>
<p>For most tools, the honest answer is: <em>"Probably. Check the changelog."</em></p>
<p>For Zenzic, the answer is: <em>"Not without telling you 30 days in advance, not without a major
version bump, and not without an adversarial session that proves the replacement architecture
can hold under pressure."</em></p>
<p>This is the enterprise guarantee. Not a feature list. A constitutional process that ensures
the Safe Harbor's fundamental rules do not change mid-journey.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-can-evolve-without-amendment">What Can Evolve Without Amendment<a href="https://zenzic.dev/blog/governance-of-quartz#what-can-evolve-without-amendment" class="hash-link" aria-label="Direct link to What Can Evolve Without Amendment" title="Direct link to What Can Evolve Without Amendment" translate="no">​</a></h3>
<p>Not everything in Zenzic requires a constitutional process to change. The Evolution Policy
distinguishes between two tracks:</p>
<p><strong>Lightweight Track (Operational Standards):</strong></p>
<p>Quality gate thresholds, finding code messages (not semantic scope), CLI flag defaults,
output format improvements, new adapters, new <code>Zxxx</code> finding codes in unused ranges — these
evolve on a 72-hour discussion window with a maintainer merge if no blocking objection is
raised. The Zenzic Ledger is updated in the same commit.</p>
<p><strong>Constitutional Track (Pillar-Level):</strong></p>
<p>Anything that changes the <em>meaning</em> of a Pillar — what it protects, what it permits, what
it prohibits. These require the full five-step process described above.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-convenience-prohibition">The Convenience Prohibition<a href="https://zenzic.dev/blog/governance-of-quartz#the-convenience-prohibition" class="hash-link" aria-label="Direct link to The Convenience Prohibition" title="Direct link to The Convenience Prohibition" translate="no">​</a></h3>
<p>The Evolution Policy contains one section that deserves explicit attention: the list of
arguments that are <strong>formally invalid</strong> as rationales for a Pillar amendment.</p>
<ul>
<li class=""><em>"It would be much easier to write this rule with a subprocess call."</em></li>
<li class=""><em>"The AI suggested a simpler architecture that relaxes Pillar III."</em></li>
<li class=""><em>"This is a temporary exception — we'll remove it after the deadline."</em></li>
<li class=""><em>"The test coverage makes this safe even without the invariant."</em></li>
<li class=""><em>"No user has complained about this Pillar being too strict."</em></li>
</ul>
<p>None of these arguments are evaluated on their engineering merits. They are rejected
<em>because they are convenience arguments</em> — and convenience is precisely the force that the
Three Pillars were designed to resist.</p>
<blockquote>
<p><em>"The Pillars are not obstacles to good engineering. They ARE good engineering, expressed
as constitutional constraints. The moment they become negotiable for convenience, they cease
to protect anything — including the users who trusted them."</em></p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="part-v--the-safe-harbor-is-permanent">Part V — The Safe Harbor is Permanent<a href="https://zenzic.dev/blog/governance-of-quartz#part-v--the-safe-harbor-is-permanent" class="hash-link" aria-label="Direct link to Part V — The Safe Harbor is Permanent" title="Direct link to Part V — The Safe Harbor is Permanent" translate="no">​</a></h2>
<p>With the publication of this Governance section, Zenzic v0.7.0 crosses a threshold that has
nothing to do with features, benchmark numbers, or code coverage percentages.</p>
<p>It has become a <strong>documented institution</strong>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-first-cornerstone">The First Cornerstone<a href="https://zenzic.dev/blog/governance-of-quartz#the-first-cornerstone" class="hash-link" aria-label="Direct link to The First Cornerstone" title="Direct link to The First Cornerstone" translate="no">​</a></h3>
<p>Version 0.7.0 is not a destination. It is the laying of the first cornerstone of a structure
designed to stand for decades. The code will evolve. New adapters will be added. New finding
codes will be discovered and registered. The CLI will gain new commands. The Shield's
normalization stages may be extended by future adversarial sessions that find bypass vectors
we have not yet imagined.</p>
<p>None of this threatens the Safe Harbor. The Three Pillars will hold. The Sovereignty Oath
will remain in force. The AI adversarial sessions will continue. The Zenzic Ledger will
record every decision, every exception, every failure.</p>
<p>This is the promise of the constitutional layer: <strong>the rules of the game are public, formal,
and non-negotiable at the foundational level.</strong> Everything built on top of those foundations
can evolve freely — because the foundations themselves are anchored.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pact-with-the-community">The Pact with the Community<a href="https://zenzic.dev/blog/governance-of-quartz#the-pact-with-the-community" class="hash-link" aria-label="Direct link to The Pact with the Community" title="Direct link to The Pact with the Community" translate="no">​</a></h3>
<p>There is a temptation, in open-source governance, to ask users to trust the maintainers.
<em>"Trust us, we have good intentions. Trust us, we take security seriously. Trust us, we won't
break your pipeline."</em></p>
<p>We reject this framing entirely.</p>
<p><strong>Do not trust us. Trust the system.</strong></p>
<p>The <a class="" href="https://zenzic.dev/developers/governance/">Governance section</a> is not a statement of our intentions.
It is a legal code — a set of invariants and processes that constrain what we can do, even
if our intentions were to change. The constitutional amendment process does not require our
goodwill to function. It requires a public vote, a 30-day notice period, and documented
adversarial validation. These requirements exist regardless of who the maintainers are, what
their intentions are, or what pressures they face.</p>
<p>If we ever attempt to modify a Pillar without following that process — file an issue. You
will be correct. The Governance system will have been violated. And the community's response
to that violation is the final layer of protection the Governance of Glass provides.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-sentinel-seal-six-chronicles-one-pact">The Sentinel Seal: Six Chronicles, One Pact<a href="https://zenzic.dev/blog/governance-of-quartz#the-sentinel-seal-six-chronicles-one-pact" class="hash-link" aria-label="Direct link to The Sentinel Seal: Six Chronicles, One Pact" title="Direct link to The Sentinel Seal: Six Chronicles, One Pact" translate="no">​</a></h3>
<p>The Zenzic Chronicles are sealed.</p>
<p>Six chapters. From the leaking credential in a MkDocs integration to the constitutional
layer of a governance-complete open-source project. From a single Shield rule to eight
normalization stages tested by adversarial AI. From an integration plugin that blurred the
line between "analysis" and "build" to a Sovereign CLI that analyzes any documentation
source without depending on its build system.</p>
<table><thead><tr><th style="text-align:center">Chapter</th><th style="text-align:left">Saga</th><th style="text-align:left">Theme</th></tr></thead><tbody><tr><td style="text-align:center">I</td><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">The Leaking Pipe</a></td><td style="text-align:left">The credential that exposed the integration flaw</td></tr><tr><td style="text-align:center">II</td><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Headless Architecture</a></td><td style="text-align:left">Building the headless, pre-build analysis model</td></tr><tr><td style="text-align:center">III</td><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">The AI Siege</a></td><td style="text-align:left">Exhaustive adversarial loops: thousands of bypass attempts, eight Shield stages forged</td></tr><tr><td style="text-align:center">IV</td><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">The Sovereign Root</a></td><td style="text-align:left">Architectural sovereignty: source, not build</td></tr><tr><td style="text-align:center">V</td><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Quartz Maturity</a></td><td style="text-align:left">v0.7.0 stable: 1,485+ tests, 80% coverage</td></tr><tr><td style="text-align:center"><strong>VI</strong></td><td style="text-align:left"><strong>The Governance of Glass</strong></td><td style="text-align:left">The constitutional layer. The pact that endures.</td></tr></tbody></table>
<p>The Chronicles are a record, not a roadmap. The next chapters of Zenzic's story will be
written by the engineers who adopt it, the vulnerabilities that future adversarial sessions
will find, the community that will eventually file the first formal RFC under the Evolution
Policy, and the enterprise teams who will discover that a 30-second decommission is a
feature they never thought to ask for.</p>
<blockquote>
<p><em>"The Safe Harbor is permanent not because it cannot change, but because the process for
changing it is more demanding than the pressure to change it casually. That is the only
kind of permanence that engineering can honestly offer."</em></p>
</blockquote>
<p>We are ready for the next chapter.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-glass-constitution">The Glass Constitution<a href="https://zenzic.dev/blog/governance-of-quartz#the-glass-constitution" class="hash-link" aria-label="Direct link to The Glass Constitution" title="Direct link to The Glass Constitution" translate="no">​</a></h3>
<p>"Governance of Glass" is a deliberate metaphor.</p>
<p>Glass is not weak. It is <strong>transparent</strong>. You can see through it. You can verify that what
is inside matches what is promised outside. It does not hide its structure behind opacity.
When glass breaks, the break is visible — you know exactly where it failed, how it failed,
and what force was required to break it.</p>
<p>The Governance documents are glass walls around the Three Pillars. Transparent, verifiable,
and brittle under the right kind of force — and that brittleness is the point. A constitution
that bends to every reasonable argument is not a constitution. It is a suggestion with
aspirational language.</p>
<p>Zenzic's constitution breaks rather than bends. If a Pillar is ever violated without following
the constitutional amendment process, the failure is immediately visible: the Zenzic Ledger
does not record it, the major version bump did not happen, the 30-day public period did not
occur. The violation is auditable from the git history. There are no hidden exceptions.</p>
<blockquote>
<p><em>"Quartz forms under geological pressure — atoms arranged with mathematical precision into a material
so sharp it was used as a surgical tool for thousands of years. Its clarity is the product
of its history. Zenzic aims to be the same: clear enough to cut through ambiguity, hard
enough to maintain its edge under sustained pressure."</em></p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-legal-code-of-the-sentinel">The Legal Code of the Sentinel<a href="https://zenzic.dev/blog/governance-of-quartz#the-legal-code-of-the-sentinel" class="hash-link" aria-label="Direct link to The Legal Code of the Sentinel" title="Direct link to The Legal Code of the Sentinel" translate="no">​</a></h2>
<p>The complete constitutional layer is documented at:</p>
<p><strong><a class="" href="https://zenzic.dev/developers/governance/">Governance &amp; Sovereignty →</a></strong></p>
<table><thead><tr><th style="text-align:left">Document</th><th style="text-align:left">What It Governs</th></tr></thead><tbody><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/developers/governance/">Overview</a></td><td style="text-align:left">The Three Pillars as Supreme Law. The engineering contract that protects them.</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/developers/governance/adversarial_ai">Adversarial AI Model</a></td><td style="text-align:left">The Red Team protocol. Session types A/B/C/D. What the AI cannot decide.</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/developers/governance/exit_strategy">The Sovereignty Oath</a></td><td style="text-align:left">Zero Residue. Read-only core. The 30-second decommission.</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/developers/governance/evolution_policy">Evolution Policy</a></td><td style="text-align:left">The constitutional amendment process. The Convenience Prohibition. The enterprise guarantee.</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/developers/governance/licensing">License Compliance</a></td><td style="text-align:left">Apache-2.0 + REUSE 3.3. Every file carries the cryptographic signature of its license.</td></tr></tbody></table>
<blockquote>
<p><em>"The code is the machine. The governance is the conscience of the machine.</em>
<em>One without the other is power without accountability."</em></p>
</blockquote>
<hr>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> | <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> | <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Saga III</a> | <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga IV</a> | <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Saga V</a> | <strong>Saga VI</strong></p></div></div>]]></content:encoded>
            <category>Governance</category>
            <category>Sovereignty</category>
            <category>Engineering Chronicles</category>
            <category>Engineering</category>
        </item>
        <item>
            <title><![CDATA[The Sovereign Root]]></title>
            <link>https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz</link>
            <guid>https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz</guid>
            <pubDate>Wed, 29 Apr 2026 19:05:00 GMT</pubDate>
            <description><![CDATA[After four AI agents tried to break Zenzic's Shield, we didn't patch bugs — we rewrote the rules. The Sovereign Root Protocol, Purity Protocol, and 1,301 tests later: this is the technical deep-dive.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> | <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> | <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Saga III</a> | <strong>Saga IV</strong> | <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Saga V</a> | <a class="" href="https://zenzic.dev/blog/governance-of-quartz">Saga VI</a></p></div></div>
<p>The <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">siege is over</a>. All four bypass vectors
are closed. 1,195 tests pass. The Bastion holds.</p>
<!-- -->
<p>And then we found another bug.</p>
<p>That is how software works. The question is not whether you'll find gaps after a major
hardening effort — you will. The question is whether your process catches them before
your users do, and whether you have the institutional discipline to close them without
inflation.</p>
<p>This is the story of what happened in the final days of high-intensity consolidation leading to v0.7.0, and why the
version number itself carries meaning.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="treating-documentation-as-untrusted-input">Treating Documentation as Untrusted Input<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#treating-documentation-as-untrusted-input" class="hash-link" aria-label="Direct link to Treating Documentation as Untrusted Input" title="Direct link to Treating Documentation as Untrusted Input" translate="no">​</a></h2>
<p>In application security, the foundational discipline is: never trust input. Passwords are
hashed, not stored. Filenames are validated before they touch the filesystem. URLs are
parsed and normalised — never concatenated from fragments. The moment you trust your inputs,
you have implicitly transferred control to whoever provides them.</p>
<p>Documentation pipelines operate under the opposite assumption. Links are "probably fine."
Asset paths are "probably correct." Placeholder values are "probably temporary." The result
is exactly what you expect from systems that trust their inputs: slow, silent rot that
surfaces as broken experiences for readers — days or even hours after the damage was done.</p>
<p>Zenzic's thesis is that documentation is input, and should be treated with the same
skepticism. The Zxxx diagnostic system is input validation. The Shield scanner is a
credential sanitiser. The Z502 frontmatter leak fix is a parser boundary constraint. The
Z105 <code>pathname:///</code> false-positive fix is protocol normalisation. Each one applies a
discipline that security engineers have practised for decades to a domain that has, until
now, operated on optimism.</p>
<p>That transfer of security thinking to documentation quality is what <strong>Quartz Maturity</strong>
actually means.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-arc-from-sentinel-to-maturity">The Arc: From Sentinel to Maturity<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-arc-from-sentinel-to-maturity" class="hash-link" aria-label="Direct link to The Arc: From Sentinel to Maturity" title="Direct link to The Arc: From Sentinel to Maturity" translate="no">​</a></h2>
<p>The Zenzic Engineering Series has documented a continuous thread:</p>
<table><thead><tr><th style="text-align:left">Part</th><th style="text-align:left">Topic</th><th style="text-align:left">Version</th></tr></thead><tbody><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1</a></td><td style="text-align:left">Why Zenzic exists: the leaking pipe problem</td><td style="text-align:left">v0.5.x Sentinel</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2</a></td><td style="text-align:left">Ripping the foundation out: Headless Architecture</td><td style="text-align:left">v0.6.1rc2 Bastion</td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3</a></td><td style="text-align:left">The siege: 4 bypass vectors found and closed</td><td style="text-align:left">v0.6.1rc2 → v0.6.1</td></tr><tr><td style="text-align:left">Part 4 (this post)</td><td style="text-align:left">The consolidation: Sovereignty, Purity, precision</td><td style="text-align:left"><strong>v0.7.0</strong></td></tr><tr><td style="text-align:left"><a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5</a></td><td style="text-align:left">The vision: UX-Discoverability and the new standard</td><td style="text-align:left"><strong>v0.7.0 Stable</strong></td></tr></tbody></table>
<p>The version jump from v0.6.1 to v0.7.0 was not about features. It was about something
more specific: the deliberate rejection of a framing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-v062">Why Not v0.6.2<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#why-not-v062" class="hash-link" aria-label="Direct link to Why Not v0.6.2" title="Direct link to Why Not v0.6.2" translate="no">​</a></h2>
<p>After the siege postmortem closed all four bypass vectors, the natural next step was a
v0.6.2 patch release. The Zensical and MkDocs adapters needed Z404 coverage. The i18n
configuration had a silent fallback bug. The navigation badge hadn't been updated in
the bump script. These felt like small fixes.</p>
<p>They weren't small. They were the difference between a system that claims to be a
multi-engine Safe Harbor and a system that actually is one.</p>
<p>Z404 (<code>CONFIG_ASSET_MISSING</code>) was originally implemented as Docusaurus-only — a direct
contradiction of the engine-agnostic architecture Zenzic was built around. A tool that
detects broken favicon references only in Docusaurus projects is not an agnostic tool.
It is a Docusaurus tool with MkDocs support bolted on.</p>
<p>The i18n silent fallback was worse. For months, the Italian locale of zenzic.dev was
silently serving English content. The URL changed. The content didn't. The bug was in
<code>docusaurus.config.ts</code> — <code>htmlLang: 'it-IT'</code> without an explicit <code>path: 'it'</code> caused
Docusaurus to look for translations in <code>i18n/it-IT/</code> (which doesn't exist) and fall
back silently to English. The Italian documentation was invisible.</p>
<p>These are not patch-level problems. They are foundational consistency failures in a
tool built around the premise that documentation quality is measurable and enforceable.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-quartz-mirror-audit">The Quartz Mirror Audit<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-quartz-mirror-audit" class="hash-link" aria-label="Direct link to The Quartz Mirror Audit" title="Direct link to The Quartz Mirror Audit" translate="no">​</a></h2>
<p>The final pre-release audit — internally called the Quartz Mirror Pass — was
structured around a simple question: does every claim Zenzic makes about itself hold
when verified against the actual state of the codebase, the documentation, and the
published site?</p>
<p>The audit found:</p>
<p><strong>Z404 engine gap.</strong> The finding code existed. The implementation was Docusaurus-only.
MkDocs <code>theme.favicon</code> and <code>theme.logo</code> were not checked. Zensical <code>[project].favicon</code>
and <code>[project].logo</code> were not checked. The fix added <code>check_config_assets()</code> to both
<code>_mkdocs.py</code> and <code>_zensical.py</code>, and unified the CLI dispatch to cover all three
engines. Lab Acts 9 and 10 were added to demonstrate the fix.</p>
<p><strong>i18n silent fallback.</strong> Diagnosed via <code>.docusaurus/i18n.json</code> — the <code>translate: false</code>
flag confirmed the Italian locale was not being discovered. Fix: add <code>path: 'it'</code>
explicitly to <code>localeConfigs</code>. Documented as institutional memory in the README and
the FAQ (EN + IT). Added to the PR checklist.</p>
<p><strong>Navbar badge drift.</strong> The version bump script correctly updated the footer and package
metadata but missed the <code>&gt;v{version}&lt;</code> HTML badge in the navbar. <code>v0.6.2</code> persisted in
the navbar after the v0.7.0 bump. Fixed in the bump script.</p>
<p><strong>Documentation structure drift (Diátaxis restructure).</strong> The documentation portal
was restructured following the <a href="https://diataxis.fr/" target="_blank" rel="noopener noreferrer" class="">Diátaxis framework</a> — four
clear modes: Tutorials, How-To Guides, Reference, and Explanation. Every section
URL changed: <code>/docs/usage/</code> → <code>/docs/how-to/</code>, <code>/docs/guides/</code> → <code>/docs/how-to/</code>,
<code>/docs/internals/architecture-overview/</code> → <code>/docs/explanation/architecture/</code>.
Three links in <code>README.md</code> had been silently pointing at the old paths for several intensive sprints
behind a blanket <code>zenzic.dev</code> exclusion bypass. When the exclusion was removed
as part of the perimeter audit, Zenzic flagged all three immediately. The tool
caught its own documentation drift.</p>
<p><strong>"True Stable" framing rejected.</strong> Early drafts of the release notes used the phrase
"True Stable" to describe v0.7.0. The phrase was removed. Stability is not a
declaration — it is an ongoing epistemic posture. Calling a release "True Stable"
implies that previous releases were somehow dishonestly stable, and that future work
won't find gaps. Both implications are false. The correct framing: v0.7.0 is
<strong>Quartz Maturity</strong> — a point of consolidation, not an arrival.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-v070-actually-is">What v0.7.0 Actually Is<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#what-v070-actually-is" class="hash-link" aria-label="Direct link to What v0.7.0 Actually Is" title="Direct link to What v0.7.0 Actually Is" translate="no">​</a></h2>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all</span><br></div></code></pre></div></div>
<p>That command now works correctly against four engine types, with:</p>
<table><thead><tr><th style="text-align:left">Capability</th><th style="text-align:left">Status</th></tr></thead><tbody><tr><td style="text-align:left">MkDocs adapter</td><td style="text-align:left">Z404 asset checking added</td></tr><tr><td style="text-align:left">Docusaurus v3 adapter</td><td style="text-align:left">Z404 (existing), versioning, @site/ alias, slug logic</td></tr><tr><td style="text-align:left">Zensical adapter</td><td style="text-align:left">Z404 asset checking added</td></tr><tr><td style="text-align:left">Standalone Mode</td><td style="text-align:left">Orphan detection disabled (no nav contract)</td></tr><tr><td style="text-align:left">Shield bypass hardening</td><td style="text-align:left">4 vectors closed, 8-step normalization pipeline</td></tr><tr><td style="text-align:left">i18n locale discovery</td><td style="text-align:left"><code>path</code> config enforced; silent fallback documented</td></tr><tr><td style="text-align:left">Lab (interactive showroom)</td><td style="text-align:left">17 Acts covering all engine types + Red/Blue Team Matrix</td></tr><tr><td style="text-align:left">Universal PATH argument</td><td style="text-align:left">All 6 <code>check</code> sub-commands + <code>init</code> (sovereign root semantics)</td></tr><tr><td style="text-align:left">Sovereign root banner hint</td><td style="text-align:left">Active scanning target printed after Sentinel header</td></tr><tr><td style="text-align:left">Z502/Z105 precision</td><td style="text-align:left">MDX frontmatter leak + <code>pathname:///</code> false positive eliminated</td></tr><tr><td style="text-align:left">Core purity</td><td style="text-align:left"><code>validator.py</code> — zero engine-name references (Purity Protocol)</td></tr><tr><td style="text-align:left">Test suite</td><td style="text-align:left">1,301 passing tests</td></tr><tr><td style="text-align:left">Enterprise reporting</td><td style="text-align:left">SARIF 2.1.0 output for GitHub Code Scanning</td></tr><tr><td style="text-align:left">Runtime dependencies</td><td style="text-align:left">5</td></tr><tr><td style="text-align:left">Subprocess calls</td><td style="text-align:left">0</td></tr></tbody></table>
<p>The engine-agnostic claim is now verifiable, not aspirational.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-diagnostic-standard-zxxx-codes">The Diagnostic Standard: Zxxx Codes<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-diagnostic-standard-zxxx-codes" class="hash-link" aria-label="Direct link to The Diagnostic Standard: Zxxx Codes" title="Direct link to The Diagnostic Standard: Zxxx Codes" translate="no">​</a></h2>
<p>The <code>Zxxx</code> code scheme was introduced as a breaking change in v0.6.1 and is the
established standard in v0.7.0: every diagnostic emitted by Zenzic carries a
machine-readable identifier. No raw string codes. No silent findings. Every problem
is named, categorised, and traceable.</p>
<table><thead><tr><th style="text-align:left">Range</th><th style="text-align:left">Category</th><th style="text-align:left">Examples</th></tr></thead><tbody><tr><td style="text-align:left">Z1xx</td><td style="text-align:left">Link integrity</td><td style="text-align:left">Z101 LINK_BROKEN, Z102 ANCHOR_MISSING, Z104 FILE_NOT_FOUND</td></tr><tr><td style="text-align:left">Z2xx</td><td style="text-align:left">Security</td><td style="text-align:left">Z201 SHIELD_SECRET, Z202 PATH_TRAVERSAL</td></tr><tr><td style="text-align:left">Z3xx</td><td style="text-align:left">Reference integrity</td><td style="text-align:left">Z301 DANGLING_REF, Z302 DEAD_DEF</td></tr><tr><td style="text-align:left">Z4xx</td><td style="text-align:left">Structure</td><td style="text-align:left">Z401 MISSING_DIRECTORY_INDEX, Z402 ORPHAN_PAGE, Z404 CONFIG_ASSET_MISSING</td></tr><tr><td style="text-align:left">Z5xx</td><td style="text-align:left">Content quality</td><td style="text-align:left">Z501 PLACEHOLDER, Z503 SNIPPET_ERROR</td></tr><tr><td style="text-align:left">Z9xx</td><td style="text-align:left">Engine / system</td><td style="text-align:left">Z902 RULE_TIMEOUT</td></tr></tbody></table>
<p>The registry lives in <code>src/zenzic/core/codes.py</code> — the single source of truth.
Adding a diagnostic without registering its code is a protocol violation.
This is what enterprise-grade means in practice: every finding is traceable,
filterable, and auditable across releases.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-vanilla-to-standalone-sunset">The Vanilla-to-Standalone Sunset<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-vanilla-to-standalone-sunset" class="hash-link" aria-label="Direct link to The Vanilla-to-Standalone Sunset" title="Direct link to The Vanilla-to-Standalone Sunset" translate="no">​</a></h2>
<p>This release also completes the <code>engine = "vanilla"</code> → <code>engine = "standalone"</code> migration.
The migration guard in <code>_factory.py</code> that raises <code>ConfigurationError [Z000]</code> with a
clear remediation message was originally annotated <code># TODO: Remove this migration guard in v0.7.0</code>,
implying it was temporary scaffolding. That annotation was wrong. The guard is intentionally
permanent: <code>engine = "vanilla"</code> is removed and will never return, so the error message
that tells users exactly how to migrate is load-bearing, not transitional.
The TODO was removed. The guard stays.</p>
<p>"Standalone Mode" is the canonical identity for documentation projects without a
declared build system. It is not a fallback. It is not a default. It is a first-class
engine mode with its own adapter, its own Lab Act, and its own documentation.</p>
<p>This release also closes the chapter on the MkDocs plugin dependency that preceded the
Headless Architecture. The CLI is now the <strong>Sovereign authority</strong>: <code>zenzic check all</code>
is the single, non-negotiable entry point regardless of what build system the
documentation uses. There is no plugin shim. There is no parallel execution path.
One command. One verdict.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-institutional-memory-protocol">The Institutional Memory Protocol<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-institutional-memory-protocol" class="hash-link" aria-label="Direct link to The Institutional Memory Protocol" title="Direct link to The Institutional Memory Protocol" translate="no">​</a></h2>
<p>One pattern that emerged clearly during the Quartz Mirror audit: the gap between
what the tooling enforces and what the team remembers is where the worst bugs live.</p>
<p>The i18n silent fallback had been present for months. The navbar badge drift had
been present since the v0.7.0 bump. Neither was caught by the existing test suite
because neither was tested — they were assumed.</p>
<p>The fix was not just to close the bugs. It was to encode the knowledge:</p>
<ul>
<li class="">
<p>The i18n trap is now in the README troubleshooting section, the PR checklist, and the</p>
<p>FAQ (English and Italian).</p>
</li>
<li class="">
<p>The bump script gap is documented in the script itself and the checklist.</p>
</li>
<li class="">
<p>The Structural Map (our deterministic engineering ledger) carries the full sprint history,</p>
<p>including the root cause, the fix, and the lesson.</p>
</li>
</ul>
<p>A process that catches bugs is valuable. A process that prevents the same bug from
recurring is more valuable. The institutional memory protocol is how you get from the
first to the second.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-parity-sprint-when-coverage-is-the-audit">The Parity Sprint: When Coverage IS the Audit<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-parity-sprint-when-coverage-is-the-audit" class="hash-link" aria-label="Direct link to The Parity Sprint: When Coverage IS the Audit" title="Direct link to The Parity Sprint: When Coverage IS the Audit" translate="no">​</a></h2>
<p>Two days after the initial v0.7.0 write-up, a second forensic sprint surfaced failures
that the test suite had not seen — not because the tests were wrong, but because entire
modules were untested.</p>
<p><strong><code>cache.py</code> — zero coverage.</strong> The content-addressable finding cache (284 lines) had no
test file. Pure hash functions, <code>CacheManager</code> roundtrips, atomic save, parent-directory
creation, corrupt-JSON fallback — all running in production, none exercised.
Twenty-nine tests were written. One boundary required a non-obvious fix: atomic write
failure is validated by patching <code>zenzic.core.cache.json.dump</code>, not <code>builtins.open</code>,
because <code>save()</code> uses <code>Path.open()</code>. The distinction is silent and the kind of thing
that lets a bad mutant survive indefinitely.</p>
<p><strong>Mutation testing.</strong> <code>mutmut</code> was run across <code>rules.py</code>, <code>shield.py</code>, and <code>reporter.py</code>.
The survivors diagnosed precisely what was missing. <code>_to_canonical_url</code> had no test
targeting its <code>rstrip("/")</code> normalisation, its backslash conversion, or the boolean
logic guarding context-aware <code>..</code>-resolution — a <code>and … or</code> mutation that would make
the guard fire on partial input. <code>_obfuscate_secret</code> had no boundary test at
<code>len(raw) == 8</code> (the <code>&lt;= 8</code> threshold). New mutant-killing test classes were added
to make those mutations observable and fatal.</p>
<p><strong>Cross-platform regression.</strong> <code>resolve_asset()</code> in all three adapter modules used
<code>Path.exists()</code> for fallback path validation. On Windows (NTFS) and macOS (HFS+) this
returns <code>True</code> regardless of capitalisation — <code>Logo.png</code> passes when the file on disk
is <code>logo.png</code>. A new <code>case_sensitive_exists()</code> helper using <code>os.listdir()</code> enforces
exact case on every platform, fixing a CI regression on the Windows and macOS legs of
the cross-platform matrix.</p>
<p><strong>CVE-2026-3219.</strong> <code>pip 26.0.1</code> is affected by a polyglot archive vulnerability (no
patched release on PyPI). Zenzic uses <code>uv</code> for all package management and never invokes
pip programmatically — the exposure is zero. The <code>nox security</code> session was updated to
suppress the advisory with a documented removal reminder.</p>
<p>The test count at the close of the parity sprint: <strong>1,195 tests, all passing.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-precision-sprint-eliminating-false-positives">The Precision Sprint: Eliminating False Positives<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-precision-sprint-eliminating-false-positives" class="hash-link" aria-label="Direct link to The Precision Sprint: Eliminating False Positives" title="Direct link to The Precision Sprint: Eliminating False Positives" translate="no">​</a></h2>
<p>After CLI symmetry was established, two false-positive bugs were closed.</p>
<p><strong>BUG-012 — Z502 MDX Frontmatter Leak.</strong> The placeholder detector (<code>Z502 PLACEHOLDER</code>)
was firing on React-style template expressions inside MDX files — <code>{children}</code>,
<code>{props.title}</code> — because the frontmatter extraction boundary was too permissive. The fix
constrained YAML extraction to terminate at the first <code>---</code> close marker, preventing MDX
body content from bleeding into the frontmatter scan. No legitimate placeholder finding was
affected.</p>
<p><strong>BUG-013 — Z105 <code>pathname:///</code> False Positive.</strong> Docusaurus uses <code>pathname:///</code> as a
pseudo-protocol for links that resolve at build time into absolute paths. Zenzic's absolute
link detector (<code>Z105 ABSOLUTE_LINK</code>) was flagging these as portability violations. The fix
adds <code>pathname:</code> to the recognised protocol allowlist (Rule R16: Protocol Awareness). Teams
migrating Docusaurus projects to Zenzic no longer see spurious Z105 warnings on
build-generated links.</p>
<p>The pattern is significant: precision failures are more dangerous than false negatives. A
scanner that cries wolf trains engineers to suppress its output — and the one real finding
gets lost in the noise. Every false positive is a credibility tax on the tool that emits it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="total-cli-symmetry-the-sovereign-root-protocol">Total CLI Symmetry: The Sovereign Root Protocol<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#total-cli-symmetry-the-sovereign-root-protocol" class="hash-link" aria-label="Direct link to Total CLI Symmetry: The Sovereign Root Protocol" title="Direct link to Total CLI Symmetry: The Sovereign Root Protocol" translate="no">​</a></h2>
<p>The original v0.7.0 release shipped with <code>check all</code> accepting a <code>PATH</code> argument. The other
six <code>check</code> sub-commands (<code>links</code>, <code>orphans</code>, <code>snippets</code>, <code>placeholders</code>, <code>assets</code>,
<code>references</code>) and <code>init</code> did not. The gap was architectural inconsistency — a tool that
claims uniform behaviour with a non-uniform interface.</p>
<p>D060 closed the gap. Every filesystem-interacting CLI command now accepts an optional <code>PATH</code>
with full sovereign root semantics:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check links   </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">/other-project       </span><span class="token comment" style="color:rgb(98, 114, 164)"># config follows target, not CWD</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check orphans content/               </span><span class="token comment" style="color:rgb(98, 114, 164)"># sub-directory scope</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check assets  /abs/path/to/docs      </span><span class="token comment" style="color:rgb(98, 114, 164)"># absolute paths accepted</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic init          /workspace/new-docs    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Genesis Nomad: create and scaffold</span><br></div></code></pre></div></div>
<p>The sovereign root protocol ensures configuration follows the target, not the caller.
Running <code>zenzic check links ../other-project</code> from your current working directory loads
<code>../other-project/zenzic.toml</code>, not your project's config. Context cannot be hijacked.</p>
<p>D062 added visual confirmation — the resolved scanning target is printed immediately after
the Sentinel header when <code>PATH</code> is provided:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">  Scanning: ../other-project/docs</span><br></div></code></pre></div></div>
<p>For <code>init</code> in Genesis Nomad mode:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">  Target: ../new-project</span><br></div></code></pre></div></div>
<p>Operators now see exactly which root Zenzic has elected before the first result appears.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-law-of-contemporary-testimony">The Law of Contemporary Testimony<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-law-of-contemporary-testimony" class="hash-link" aria-label="Direct link to The Law of Contemporary Testimony" title="Direct link to The Law of Contemporary Testimony" translate="no">​</a></h2>
<p>In parallel with the code changes, a policy was codified: the <strong>Law of Contemporary
Testimony</strong>.</p>
<blockquote>
<p><em>Code and documentation are a single, indivisible unit of work. No sprint is closed if
the documentation still reflects the previous state of the code.</em></p>
</blockquote>
<p>The enforcement mechanism is the Zenzic Structural Map, a mandatory protocol that
ensures no sprint is closed unless the documentation is as mature as the code. If a wrapper
behavior changed, the architecture diagram must be updated. If a CLI flag was added, the
reference must be updated. If an exit code semantic changed, the policy table and the user
documentation must both be updated.</p>
<p>This sounds like documentation discipline. It is actually a defect prevention system. The
navbar badge drift, the i18n silent fallback, the README links pointing at URL paths that no
longer exist — every one of these was a failure of the implicit contract between code author
and documentation author. When those roles are collapsed (as they are in any sufficiently
small team), the failure mode is: <em>I know this in my head, I'll update the docs later.</em>
Later compounds. The Law makes "later" unconstitutional.</p>
<p>The test count at close of all v0.7.0 consolidation sprints: <strong>1,301 passing tests.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-purity-protocol-zero-engine-leaks-in-core">The Purity Protocol: Zero Engine Leaks in Core<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-purity-protocol-zero-engine-leaks-in-core" class="hash-link" aria-label="Direct link to The Purity Protocol: Zero Engine Leaks in Core" title="Direct link to The Purity Protocol: Zero Engine Leaks in Core" translate="no">​</a></h2>
<p>One invariant that emerged from the consolidation: <code>validator.py</code> — the heart of Zenzic —
must contain no reference to any engine by name. No "docusaurus", no "sidebar", no
"navbar". The Core receives a <code>frozenset[str]</code> of navigable paths from
<code>adapter.get_nav_paths()</code>. What lives inside that method is the adapter's problem, not
the Core's.</p>
<p>This is not aesthetic. It is architectural insurance. A Core that knows about Docusaurus
is a Core that will accumulate Docusaurus special-cases. A Core that knows about MkDocs
nav-plugins will accumulate MkDocs exceptions. Every engine leak is a future maintenance
trap.</p>
<p>The <strong>Purity Protocol</strong> (confirmed by <code>grep -n "docusaurus\|sidebar\|navbar\|footer" validator.py</code> → 0 matches) is the enforcement gate. Adding a new adapter that modifies
<code>validator.py</code> is a protocol violation. The adapter contract is the boundary — everything
engine-specific must live behind it.</p>
<p>Part 5 of this series explains what the Purity Protocol unlocks for the Docusaurus
adapter specifically — how <code>get_nav_paths()</code> became a Multi-Source Harvester covering
sidebar, navbar, and footer simultaneously.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-safe-harbor-is-open">The Safe Harbor Is Open<a href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz#the-safe-harbor-is-open" class="hash-link" aria-label="Direct link to The Safe Harbor Is Open" title="Direct link to The Safe Harbor Is Open" translate="no">​</a></h2>
<p>v0.7.0 is a point of arrival. The documentation pipeline is verified, the Shield is
hardened, the adapters are parity-complete, and the Core is pure. Every claim Zenzic
makes about itself — engine-agnostic architecture, sovereign root semantics, zero
subprocesses — holds when checked against the actual codebase.</p>
<p>The pipe is probably still leaking somewhere in your documentation — a broken link,
a credential fragment in a frontmatter field, a file invisible to every reader because
no clickable navigation surface references it. Find out before your users do.</p>
<p><a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5 →</a> covers what v0.7.0 unlocks
for UX-Discoverability and the full product vision.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic lab</span><br></div></code></pre></div></div>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr><tr><td><strong>PyPI</strong></td><td><a href="https://pypi.org/project/zenzic/" target="_blank" rel="noopener noreferrer" class="">pypi.org/project/zenzic</a></td></tr><tr><td><strong>Changelog</strong></td><td><a href="https://github.com/PythonWoods/zenzic/releases/tag/v0.7.0" target="_blank" rel="noopener noreferrer" class="">v0.7.0 Release Notes</a></td></tr></tbody></table>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>The Zenzic Chronicles</div><div class="admonitionContent_BuS1"><p>This is <strong>Part 4</strong> of a five-part engineering series documenting the path from v0.5 to v0.7.0 Stable.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1 — The Sentinel</a> · <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2 — Sentinel Bastion</a> · <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3 — The AI Siege</a> · <strong>Part 4 — Beyond the Siege</strong> · <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5 — Quartz Maturity</a></p></div></div>
<p><em>Part 4 of the <strong>Zenzic Chronicles</strong>. For the complete architectural journey, visit the <a href="https://zenzic.dev/blog/" target="_blank" rel="noopener noreferrer" class="">Safe Harbor Blog</a>.</em></p>]]></content:encoded>
            <category>Release</category>
            <category>Engineering</category>
            <category>Python</category>
            <category>Open Source</category>
            <category>Security</category>
            <category>The Zenzic Chronicles</category>
            <category>Engineering Chronicles</category>
        </item>
        <item>
            <title><![CDATA[Stop Broken Links in 60s]]></title>
            <link>https://zenzic.dev/blog/tutorial-stop-broken-links-60s</link>
            <guid>https://zenzic.dev/blog/tutorial-stop-broken-links-60s</guid>
            <pubDate>Wed, 29 Apr 2026 19:00:00 GMT</pubDate>
            <description><![CDATA[Install Zenzic, run your first audit, and protect your documentation pipeline in under 60 seconds. No setup, no configuration, no build required.
]]></description>
            <content:encoded><![CDATA[<p>Your docs have broken links. You just haven't found them yet.</p>
<p><strong>Zenzic finds them before your readers do</strong> — before you build, before you deploy,
before it's too late.</p>
<!-- -->
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>New to Zenzic?</div><div class="admonitionContent_BuS1"><p>This tutorial gets you from zero to first audit in 60 seconds. After that,
explore how Zenzic was built in <a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">The Zenzic Chronicles →</a></p></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1--launch">Step 1 — Launch<a href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s#step-1--launch" class="hash-link" aria-label="Direct link to Step 1 — Launch" title="Direct link to Step 1 — Launch" translate="no">​</a></h2>
<p>No install. No virtual environment. One command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">Terminal</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx zenzic check all ./docs</span><br></div></code></pre></div></div>
<p>No browser, no build engine, no heavy framework — a single Python tool cached on
first run and ready in seconds from then on.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2--read-the-report">Step 2 — Read the Report<a href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s#step-2--read-the-report" class="hash-link" aria-label="Direct link to Step 2 — Read the Report" title="Direct link to Step 2 — Read the Report" translate="no">​</a></h2>
<p>You'll see one of two results:</p>
<p><strong>All clear:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">✨ Sentinel Seal: All checks passed. Your documentation is clean.</span><br></div></code></pre></div></div>
<p><strong>Issues found:</strong></p>
<div class="max-w-xl mx-auto my-6"><div style="border-radius:10px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.6), 0 12px 40px rgba(0,0,0,0.5), 0 0 0 1px rgba(99,102,241,0.45), 0 0 24px rgba(79,70,229,0.12);border:1px solid rgba(99,102,241,0.45)"><div style="background:linear-gradient(180deg,#2d2d2d 0%,#252525 100%);padding:8px 14px;display:flex;align-items:center;gap:7px;border-bottom:1px solid rgba(255,255,255,0.07)"><span style="width:10px;height:10px;border-radius:50%;background:#ff5f57;display:inline-block;flex-shrink:0"></span><span style="width:10px;height:10px;border-radius:50%;background:#ffbd2e;display:inline-block;flex-shrink:0"></span><span style="width:10px;height:10px;border-radius:50%;background:#28c840;display:inline-block;flex-shrink:0"></span></div><div class="dark:bg-zinc-900/20 bg-zinc-50 backdrop-blur-md rounded-xl py-5 px-6 font-mono text-[12px] leading-relaxed shadow-2xl "><div class="space-y-2 mb-4"><div class="flex items-start gap-2"><span class="text-rose-500 flex-shrink-0 select-none">✘</span><span class="text-cyan-500 dark:text-cyan-400 w-44 flex-shrink-0 truncate">docs/guide.md</span><span class="dark:text-zinc-500 text-zinc-400 w-14 flex-shrink-0">Z101</span><span class="dark:text-zinc-300 text-zinc-700">Broken link → ./missing-page.md</span></div><div class="flex items-start gap-2"><span class="text-rose-500 flex-shrink-0 select-none">✘</span><span class="text-cyan-500 dark:text-cyan-400 w-44 flex-shrink-0 truncate">docs/old-api.md</span><span class="dark:text-zinc-500 text-zinc-400 w-14 flex-shrink-0">Z402</span><span class="dark:text-zinc-300 text-zinc-700">Orphan page — not linked from navigation</span></div><div class="flex items-start gap-2"><span class="text-rose-500 flex-shrink-0 select-none">✘</span><span class="text-cyan-500 dark:text-cyan-400 w-44 flex-shrink-0 truncate">docs/config.md</span><span class="dark:text-zinc-500 text-zinc-400 w-14 flex-shrink-0">Z201</span><span class="dark:text-zinc-300 text-zinc-700">Shield: credential pattern detected</span></div></div><div class="border-t dark:border-zinc-800/40 border-zinc-200 pt-3 flex flex-wrap gap-4"><span class="text-rose-500 font-medium">✘ <!-- -->3<!-- --> <!-- -->errors</span><span class="dark:text-zinc-600 text-zinc-400">Files: 42 · Elapsed: 0.31 s</span></div></div></div></div>
<p>Each finding carries a <code>Zxxx</code> code, a file path, a line number, and a clear description.
Fix what's flagged, re-run, and ship with confidence.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3--protect-your-ci">Step 3 — Protect Your CI<a href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s#step-3--protect-your-ci" class="hash-link" aria-label="Direct link to Step 3 — Protect Your CI" title="Direct link to Step 3 — Protect Your CI" translate="no">​</a></h2>
<p>One line in your GitHub Actions workflow:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">.github/workflows/zenzic.yml</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Audit documentation</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> uvx zenzic check all ./docs</span><br></div></code></pre></div></div>
<p>Every pull request is now guarded. Broken links, orphan pages, and leaked credentials
are caught before they reach <code>main</code>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-zenzic">Why Zenzic<a href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s#why-zenzic" class="hash-link" aria-label="Direct link to Why Zenzic" title="Direct link to Why Zenzic" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>Fast</strong> — Zenzic is fast because it's lightweight. No build step, no Node.js,</p>
<p>no browser launch. Analysis happens directly on your Markdown source files.</p>
</li>
<li class="">
<p><strong>Safe</strong> — Zenzic is secure because it doesn't touch your system files.</p>
<p>Read-only analysis, always. Your repository is observed, never modified.</p>
</li>
<li class="">
<p><strong>Universal</strong> — Works with MkDocs, Docusaurus, Zensical, or any plain Markdown folder.</p>
<p>Point it at your <code>docs/</code> directory and it figures out the rest.</p>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="go-further">Go Further<a href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s#go-further" class="hash-link" aria-label="Direct link to Go Further" title="Direct link to Go Further" translate="no">​</a></h2>
<table><thead><tr><th>Command</th><th>What it does</th></tr></thead><tbody><tr><td><code>uvx zenzic check all</code></td><td>Full audit: links, orphans, credentials, snippets</td></tr><tr><td><code>uvx zenzic check links</code></td><td>Link integrity only</td></tr><tr><td><code>uvx zenzic score</code></td><td>Quality score with trend tracking</td></tr><tr><td><code>uvx zenzic check all --format sarif</code></td><td>SARIF output for GitHub Code Scanning</td></tr></tbody></table>
<p>Pin a specific version for reproducible CI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockTitle_OeMC">Terminal</div><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">uvx </span><span class="token string" style="color:rgb(255, 121, 198)">"zenzic==0.7.0"</span><span class="token plain"> check all ./docs</span><br></div></code></pre></div></div>
<hr>
<p>The full engineering story behind Zenzic — from the first broken pipe to Quartz
Maturity — lives in <strong><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">The Zenzic Chronicles →</a></strong></p>
<p>For a 1,525-line architectural deep-dive into every Zenzic component — verified by <strong>1,301 tests</strong> across Python 3.11, 3.12, and 3.13 — see the <strong><a class="" href="https://zenzic.dev/blog/obsidian-masterclass">Obsidian Masterclass →</a></strong>.</p>]]></content:encoded>
            <category>Tutorial</category>
            <category>Quickstart</category>
            <category>Python</category>
            <category>Open Source</category>
            <category>DevTools</category>
            <category>User Tutorials</category>
        </item>
        <item>
            <title><![CDATA[The AI Siege]]></title>
            <link>https://zenzic.dev/blog/ai-driven-siege-shield-postmortem</link>
            <guid>https://zenzic.dev/blog/ai-driven-siege-shield-postmortem</guid>
            <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We put our documentation linter under an AI-driven siege. Four bypass vectors found, four sealed. Here's the full post-mortem of Zenzic's AI red team audit.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> | <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> | <strong>Saga III</strong> | <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga IV</a> | <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Saga V</a> | <a class="" href="https://zenzic.dev/blog/governance-of-quartz">Saga VI</a></p></div></div>
<p>Four bypass vectors. Four real findings. All closed.</p>
<!-- -->
<p>This is the complete technical post-mortem of <strong>Operation Obsidian Stress</strong> — the
adversarial security audit we ran against Zenzic v0.6.1rc2's Shield (credential scanner)
before release. I'm publishing the full technical details because the findings are
instructive, the fixes are non-obvious, and the code belongs in the open.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Methodology</div><div class="admonitionContent_BuS1"><p>To validate the Shield, I orchestrated a multi-team AI system — Red Team, Blue Team,
and Purple Team — using specialized agent ensembles to simulate advanced obfuscation
techniques. This is AI-assisted security engineering: using the same agentic
architecture that attackers use to find the gaps they would exploit. All findings,
bypass vectors, and fixes documented here are real.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-shield-is-and-why-breaking-it-matters">What Shield Is (and Why Breaking It Matters)<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#what-shield-is-and-why-breaking-it-matters" class="hash-link" aria-label="Direct link to What Shield Is (and Why Breaking It Matters)" title="Direct link to What Shield Is (and Why Breaking It Matters)" translate="no">​</a></h2>
<p>Before the attack details, context: Shield is Zenzic's credential detection layer.
It scans every Markdown and MDX file in your documentation before the build runs,
looking for patterns that indicate real credentials in content.</p>
<p>The threat model is simple: a contributor submits a PR with a code example. That
example contains a real API key — copied from a local terminal session, pasted from
a Slack thread, or forgotten after a debugging session. The reviewer reads the prose,
not the bytes. The PR merges. The docs build. The key is now live on your documentation
site, indexed by search engines.</p>
<p>Shield exists to catch that before it ships. If Shield can be bypassed by someone who
knows how it works, it's not a scanner — it's a false guarantee.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-attack-surface">The Attack Surface<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-attack-surface" class="hash-link" aria-label="Direct link to The Attack Surface" title="Direct link to The Attack Surface" translate="no">​</a></h2>
<p>Shield's architecture before Operation Obsidian Stress:</p>
<ol>
<li class="">Read each line of the Markdown/MDX file</li>
<li class="">Apply a normalization pass (strip backticks, collapse whitespace)</li>
<li class="">Run 9 regex patterns against the normalized line</li>
<li class="">Report any match as a <code>ShieldFinding</code></li>
</ol>
<p>Step 4 triggers <strong>Exit Code 2</strong> (Shield breach) — non-bypassable, distinct from Exit
Code 1 (validation failure) and Exit Code 3 (Blood Sentinel / path traversal).</p>
<p>The attack surface was step 2: the normalization pass. It normalized formatting noise
but did not account for deliberate obfuscation.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zrt-006-unicode-format-character-injection">ZRT-006: Unicode Format Character Injection<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#zrt-006-unicode-format-character-injection" class="hash-link" aria-label="Direct link to ZRT-006: Unicode Format Character Injection" title="Direct link to ZRT-006: Unicode Format Character Injection" translate="no">​</a></h2>
<p><strong>Category:</strong> Input normalization bypass
<strong>Severity:</strong> High — complete bypass of all regex patterns
<strong>CVSS analogy:</strong> 8.1 (High)</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-technique">The Technique<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-technique" class="hash-link" aria-label="Direct link to The Technique" title="Direct link to The Technique" translate="no">​</a></h3>
<p>Python's <code>unicodedata</code> module exposes a character category classification. The <code>Cf</code>
category ("Format characters") includes characters that are semantically meaningful in
Unicode text processing but are invisible in rendered output and most text displays:</p>
<table><thead><tr><th style="text-align:left">Code point</th><th style="text-align:left">Name</th><th style="text-align:left">Purpose</th></tr></thead><tbody><tr><td style="text-align:left">U+200B</td><td style="text-align:left">Zero Width Space</td><td style="text-align:left">Line breaking hint</td></tr><tr><td style="text-align:left">U+200C</td><td style="text-align:left">Zero Width Non-Joiner</td><td style="text-align:left">Prevents ligatures</td></tr><tr><td style="text-align:left">U+200D</td><td style="text-align:left">Zero Width Joiner</td><td style="text-align:left">Forces ligatures</td></tr><tr><td style="text-align:left">U+00AD</td><td style="text-align:left">Soft Hyphen</td><td style="text-align:left">Optional hyphenation</td></tr><tr><td style="text-align:left">U+FEFF</td><td style="text-align:left">Zero Width No-Break Space</td><td style="text-align:left">BOM marker</td></tr></tbody></table>
<p>Inject any of these into a credential token and the regex fails to match:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">key </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"sk-abc123def456ghi789jkl012mno345pqr678stu"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Insert ZWS after position 9 (inside the token)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">bypass </span><span class="token operator">=</span><span class="token plain"> key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"\u200B"</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> re</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">pattern </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"sk-[a-zA-Z0-9]{48}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">pattern</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">bypass</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># None — bypass confirmed</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix">The Fix<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-fix" class="hash-link" aria-label="Direct link to The Fix" title="Direct link to The Fix" translate="no">​</a></h3>
<p>Strip all <code>Cf</code>-category characters before any normalization step runs:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> unicodedata</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_strip_unicode_format_chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Remove all Unicode Format (Cf) characters.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="display:inline-block;color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    Invisible to human readers but interrupt regex pattern matching.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    Examples: U+200B (ZWS), U+200C (ZWNJ), U+200D (ZWJ), U+00AD (soft hyphen).</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    """</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> c </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> text </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> unicodedata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">!=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Cf"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zrt-006b-html-entity-obfuscation">ZRT-006b: HTML Entity Obfuscation<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#zrt-006b-html-entity-obfuscation" class="hash-link" aria-label="Direct link to ZRT-006b: HTML Entity Obfuscation" title="Direct link to ZRT-006b: HTML Entity Obfuscation" translate="no">​</a></h2>
<p><strong>Category:</strong> Input normalization bypass
<strong>Severity:</strong> High — bypasses patterns that depend on punctuation characters
<strong>Affected families:</strong> OpenAI (hyphen), Stripe (hyphen, underscore), GitHub (underscore)</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-technique-1">The Technique<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-technique-1" class="hash-link" aria-label="Direct link to The Technique" title="Direct link to The Technique" translate="no">​</a></h3>
<p>Markdown renderers decode standard HTML entities. The hyphen character (<code>-</code>) has the
HTML entity <code>&amp;#45;</code>. The underscore (<code>_</code>) is <code>&amp;#95;</code>.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">sk&amp;#45;abc123def456ghi789jkl012mno345pqr678stu</span><br></div></code></pre></div></div>
<p>Renders as: <code>sk-abc123def456ghi789jkl012mno345pqr678stu</code> — a valid OpenAI key format.</p>
<p>The credential scanner sees <code>sk&amp;#45;abc123...</code> — which does not match
<code>sk-[a-zA-Z0-9]{48}</code>. The entity is a one-character substitution of a structural
boundary character.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix-1">The Fix<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-fix-1" class="hash-link" aria-label="Direct link to The Fix" title="Direct link to The Fix" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> html</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_decode_html_entities</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Decode HTML entities before pattern matching.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="display:inline-block;color:rgb(255, 121, 198)"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    A credential containing &amp;#45; (hyphen) or &amp;#95; (underscore) renders</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    correctly in a browser but bypasses regex patterns that match on the</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    literal character.</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">    """</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> html</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">unescape</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></div></code></pre></div></div>
<p><code>html.unescape()</code> is part of the Python standard library. No dependencies. Zero cost.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zrt-007-comment-interleaving">ZRT-007: Comment Interleaving<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#zrt-007-comment-interleaving" class="hash-link" aria-label="Direct link to ZRT-007: Comment Interleaving" title="Direct link to ZRT-007: Comment Interleaving" translate="no">​</a></h2>
<p><strong>Category:</strong> Token fragmentation via markup
<strong>Severity:</strong> High — renders the token non-contiguous in raw source
<strong>Technique:</strong> Inject HTML or MDX comment blocks between credential characters</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-technique-2">The Technique<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-technique-2" class="hash-link" aria-label="Direct link to The Technique" title="Direct link to The Technique" translate="no">​</a></h3>
<p>HTML comments and MDX expression comments are invisible in rendered output. They are
valid Markdown syntax that any Markdown renderer will process and discard.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">sk-abc123&lt;!-- This is a comment, nothing to see here --&gt;def456ghi789jkl012mno345pqr678stu</span><br></div></code></pre></div></div>
<p>In rendered output: <code>sk-abc123def456ghi789jkl012mno345pqr678stu</code> (correct, readable).
In raw source the scanner reads: the regex fails because the comment block interrupts
the character class <code>[a-zA-Z0-9]</code>.</p>
<p>MDX variant: <code>sk-abc123{/* inline MDX comment */}def456...</code> — same effect.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix-2">The Fix<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-fix-2" class="hash-link" aria-label="Direct link to The Fix" title="Direct link to The Fix" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> re</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">_HTML_COMMENT_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"&lt;!--.*?--&gt;"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">DOTALL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">_MDX_COMMENT_RE </span><span class="token operator">=</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">r"\{/\*.*?\*/\}"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> re</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">DOTALL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_strip_markup_comments</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">"""Strip HTML and MDX comments before pattern matching."""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _HTML_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _MDX_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> text</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="zrt-007b-cross-line-token-splitting">ZRT-007b: Cross-Line Token Splitting<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#zrt-007b-cross-line-token-splitting" class="hash-link" aria-label="Direct link to ZRT-007b: Cross-Line Token Splitting" title="Direct link to ZRT-007b: Cross-Line Token Splitting" translate="no">​</a></h2>
<p><strong>Category:</strong> Architectural bypass — stateless scanner assumption
<strong>Severity:</strong> Critical — bypasses all pattern matching with zero obfuscation
<strong>Technique:</strong> Line break</p>
<p>This is the most architecturally significant finding. It requires no Unicode tricks,
no entity encoding, no markup injection. <strong>One line break.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-technique-3">The Technique<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-technique-3" class="hash-link" aria-label="Direct link to The Technique" title="Direct link to The Technique" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Here is my staging key for the integration tests: sk-abc123def456</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">ghi789jkl012mno345pqr678stu901vwx234yz</span><br></div></code></pre></div></div>
<p>The scanner processes line 1 — no match (only 12 chars after <code>sk-</code>).
The scanner processes line 2 — no match (no <code>sk-</code> prefix).
The credential leaks. The split is invisible in rendered output — the two lines render
as a single paragraph.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix-the-lookback-buffer">The Fix: The Lookback Buffer<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-fix-the-lookback-buffer" class="hash-link" aria-label="Direct link to The Fix: The Lookback Buffer" title="Direct link to The Fix: The Lookback Buffer" translate="no">​</a></h3>
<p>A stateful generator that maintains context across line boundaries, creating a
synthetic overlap zone:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">scan_lines_with_lookback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Iterable</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    buffer_width</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">80</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">ShieldFinding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    prev_normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    prev_seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> raw_line </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        seen_this_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        normalized </span><span class="token operator">=</span><span class="token plain"> _normalize_line_for_shield</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pass 1: standard per-line scan</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> finding </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> _scan_normalized_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token plain"> finding</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            seen_this_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">add</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">finding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">family</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pass 2: cross-line join zone scan</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> prev_normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            join_zone </span><span class="token operator">=</span><span class="token plain"> prev_normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token operator">-</span><span class="token plain">buffer_width</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> normalized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">buffer_width</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> finding </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> _scan_normalized_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">join_zone</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> file_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> line_no</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> finding</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">family </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">seen_this_line </span><span class="token operator">|</span><span class="token plain"> prev_seen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">                    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">yield</span><span class="token plain"> finding</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        prev_normalized </span><span class="token operator">=</span><span class="token plain"> normalized</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">        prev_seen </span><span class="token operator">=</span><span class="token plain"> seen_this_line</span><br></div></code></pre></div></div>
<p><strong>Why 80 characters?</strong> Standard terminal width and most documentation editors wrap at
80–120 characters. Taking 80 characters from each side covers the vast majority of
real-world split positions with minimal false positive risk.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-complete-8-step-normalization-pipeline">The Complete 8-Step Normalization Pipeline<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-complete-8-step-normalization-pipeline" class="hash-link" aria-label="Direct link to The Complete 8-Step Normalization Pipeline" title="Direct link to The Complete 8-Step Normalization Pipeline" translate="no">​</a></h2>
<p>After closing all four vectors, Shield's normalization function runs every line through
a deterministic eight-step sequence:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">_normalize_line_for_shield</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">raw_line</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> raw_line</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _strip_unicode_format_chars</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 1: Cf chars</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> html</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">unescape</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">                  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 2: HTML entities</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _HTML_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 3: HTML comments</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _MDX_COMMENT_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 4: MDX comments</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> _BACKTICK_RE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">lambda</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">group</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 5: backtick spans</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">replace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"+"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 6: concatenation operators</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">replace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"|"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 7: table cell separators</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    text </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">" "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">              </span><span class="token comment" style="color:rgb(98, 114, 164)"># Step 8: whitespace collapse</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> text</span><br></div></code></pre></div></div>
<p>Each step is independently testable. The test suite includes 47 tests specifically
for normalization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="coverage-added-by-operation-obsidian-stress">Coverage Added by Operation Obsidian Stress<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#coverage-added-by-operation-obsidian-stress" class="hash-link" aria-label="Direct link to Coverage Added by Operation Obsidian Stress" title="Direct link to Coverage Added by Operation Obsidian Stress" translate="no">​</a></h2>
<table><thead><tr><th style="text-align:left">Bypass vector</th><th style="text-align:right">New tests</th></tr></thead><tbody><tr><td style="text-align:left">Cf character injection (ZRT-006)</td><td style="text-align:right">23</td></tr><tr><td style="text-align:left">HTML entity obfuscation (ZRT-006b)</td><td style="text-align:right">18</td></tr><tr><td style="text-align:left">Comment interleaving (ZRT-007)</td><td style="text-align:right">31</td></tr><tr><td style="text-align:left">Cross-line token splitting (ZRT-007b)</td><td style="text-align:right">28</td></tr><tr><td style="text-align:left">Normalization pipeline integration</td><td style="text-align:right">17</td></tr><tr><td style="text-align:left"><strong>Total new tests</strong></td><td style="text-align:right"><strong>117</strong></td></tr></tbody></table>
<p>Before the operation: 929 passing tests. After closing all four vectors: 1,130+ passing tests.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-risk-management-dimension">The Risk Management Dimension<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-risk-management-dimension" class="hash-link" aria-label="Direct link to The Risk Management Dimension" title="Direct link to The Risk Management Dimension" translate="no">​</a></h2>
<p>The four bypass vectors found during Operation Obsidian Stress have a common property:
they are not obscure edge cases. They are techniques that appear in standard lists of
regex evasion methods used in adversarial content scenarios — discoverable by any
documentation contributor with moderate knowledge of Unicode, HTML encoding, and regex
mechanics.</p>
<p>The risk profile of an unpatched documentation scanner is not “low probability, low
impact.” It is <strong>moderate probability, high impact</strong> — because credential leaks in
documentation have immediate material consequences, and because documentation pipelines
receive content from the broadest possible contributor population.</p>
<p>This is the supply chain risk dimension that is most frequently underweighted: not the
vulnerability of your infrastructure, but the vulnerability of the content processing
path you expose to your contributor base.</p>
<blockquote>
<p>A security tool that can be bypassed by a contributor who knows how it works is not
a security tool. It is a compliance checkbox.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="beyond-security-the-full-zenzic-surface">Beyond Security: The Full Zenzic Surface<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#beyond-security-the-full-zenzic-surface" class="hash-link" aria-label="Direct link to Beyond Security: The Full Zenzic Surface" title="Direct link to Beyond Security: The Full Zenzic Surface" translate="no">​</a></h2>
<p>Shield is one layer in a complete documentation quality framework:</p>
<table><thead><tr><th style="text-align:left">Layer</th><th style="text-align:left">What it catches</th></tr></thead><tbody><tr><td style="text-align:left">Link validation (VSM)</td><td style="text-align:left">Broken internal links, ghost routes — no live server required</td></tr><tr><td style="text-align:left">Orphan detection</td><td style="text-align:left">Pages that exist but are unreachable in the navigation graph</td></tr><tr><td style="text-align:left">Snippet verification</td><td style="text-align:left">Code blocks referencing files that don’t exist on disk</td></tr><tr><td style="text-align:left">Placeholder scanning</td><td style="text-align:left"><code>TODO</code>, <code>FIXME</code>, <code>TBD</code> in published content</td></tr><tr><td style="text-align:left">Asset auditing</td><td style="text-align:left">Unused images with autofix support</td></tr><tr><td style="text-align:left">Reference integrity</td><td style="text-align:left"><code>[broken][ref]</code>-style links with missing definitions</td></tr><tr><td style="text-align:left">Quality score</td><td style="text-align:left">Deterministic 0–100 metric with regression detection</td></tr></tbody></table>
<p>All analysis is engine-agnostic: auto-detection covers MkDocs, Docusaurus v3, Zensical,
and Standalone Mode. No plugins to install. No build to run. No subprocesses.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="exit-code-taxonomy">Exit Code Taxonomy<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#exit-code-taxonomy" class="hash-link" aria-label="Direct link to Exit Code Taxonomy" title="Direct link to Exit Code Taxonomy" translate="no">​</a></h2>
<p>Zenzic’s exit codes are non-negotiable — no configuration can suppress them:</p>
<table><thead><tr><th style="text-align:center">Code</th><th style="text-align:left">Name</th><th style="text-align:left">Trigger</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:left">Success</td><td style="text-align:left">All checks pass</td></tr><tr><td style="text-align:center">1</td><td style="text-align:left">Quality</td><td style="text-align:left">Validation findings (broken links, orphans, placeholders)</td></tr><tr><td style="text-align:center">2</td><td style="text-align:left">Shield</td><td style="text-align:left">Credential detected in documentation</td></tr><tr><td style="text-align:center">3</td><td style="text-align:left">Blood Sentinel</td><td style="text-align:left">Path traversal attack or fatal error</td></tr></tbody></table>
<p>Codes 2 and 3 cannot be configured away. A CI step that can be silenced on a security
failure is not a security control.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-obligation-of-the-bastion">The Obligation of the Bastion<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-obligation-of-the-bastion" class="hash-link" aria-label="Direct link to The Obligation of the Bastion" title="Direct link to The Obligation of the Bastion" translate="no">​</a></h2>
<p>“The Bastion holds” is not a marketing phrase. It is an engineering commitment. It means
that every identified attack path has been closed, that the closure has been verified
with test coverage, and that the system’s failure modes under adversarial input are
bounded and known.</p>
<p>It does not mean that future bypass vectors don’t exist. Red team exercises are not
proofs of security — they are evidence of the security posture at a specific moment in
time. The four vectors found during Operation Obsidian Stress were found because we
looked for them systematically. Vectors we haven’t enumerated may still exist.</p>
<p>What the Bastion commitment means is that we look — methodically, adversarially, and
transparently about what we find.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-takeaway">The Takeaway<a href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem#the-takeaway" class="hash-link" aria-label="Direct link to The Takeaway" title="Direct link to The Takeaway" translate="no">​</a></h2>
<p>The four bypass vectors found during Operation Obsidian Stress are not exotic. They're
the kind of techniques that appear in any list of regex evasion methods — Unicode
injection, HTML entity encoding, markup comment interleaving, structural line splitting.</p>
<p>What made them findable was the decision to look for them <strong>systematically, with
adversarial intent</strong>, before release. What made them fixable was having a normalization
pipeline with defined semantics and comprehensive test coverage at each step.</p>
<p>Security tooling that isn't tested adversarially is security tooling that provides the
appearance of coverage without the substance.</p>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr><tr><td><strong>PyPI</strong></td><td><a href="https://pypi.org/project/zenzic/" target="_blank" rel="noopener noreferrer" class="">pypi.org/project/zenzic</a></td></tr></tbody></table>
<p><strong>Cross-posted on:</strong></p>
<ul>
<li class=""><a href="https://medium.com/zenzic-engineering/we-put-our-documentation-linter-under-an-ai-driven-siege-heres-the-post-mortem-c09b8a86a396" target="_blank" rel="noopener noreferrer" class="">Medium</a> — <em>We Put Our Documentation Linter Under an AI-Driven Siege</em></li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>The Zenzic Chronicles</div><div class="admonitionContent_BuS1"><p>This is <strong>Part 3</strong> of a five-part engineering series documenting the path from v0.5 to v0.7.0 Stable.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1 — The Sentinel</a> · <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2 — Sentinel Bastion</a> · <strong>Part 3 — The AI Siege</strong> · <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Part 4 — Beyond the Siege</a> · <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5 — Quartz Maturity</a></p></div></div>
<p><em>Part 3 of the <strong>Zenzic Chronicles</strong>. For the complete architectural journey, visit the <a href="https://zenzic.dev/blog/" target="_blank" rel="noopener noreferrer" class="">Safe Harbor Blog</a>.</em></p>]]></content:encoded>
            <category>Security</category>
            <category>Engineering</category>
            <category>Post-Mortem</category>
            <category>Python</category>
            <category>DevTools</category>
            <category>The Zenzic Chronicles</category>
            <category>Engineering Chronicles</category>
        </item>
        <item>
            <title><![CDATA[Zenzic v0.6.1rc2 — Obsidian Bastion]]></title>
            <link>https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion</link>
            <guid>https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion</guid>
            <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Zenzic v0.6.1rc2 replaces the single-engine assumption with a multi-engine Safe Harbor — Zensical Proxy, enterprise Docusaurus, adversarial Shield audit.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Alpha/RC Chronicle</div><div class="admonitionContent_BuS1"><p>This is a historical record of a development milestone. The first stable release is <strong><a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">v0.7.0 — Quartz Maturity</a></strong>.</p></div></div>
<p><strong>v0.6.1rc2 — Obsidian Bastion</strong> is the release that turned Zenzic from a
capable single-engine linter into a genuine multi-engine documentation Safe Harbor.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architectural-shift">The architectural shift<a href="https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion#the-architectural-shift" class="hash-link" aria-label="Direct link to The architectural shift" title="Direct link to The architectural shift" translate="no">​</a></h2>
<p>The defining change of the Bastion cycle was the rejection of a hidden assumption:
that every project using Zenzic would have a single, knowable documentation engine.</p>
<p>That assumption broke the moment we tried Zensical — a MkDocs-compatible engine with
its own configuration surface. The fix was not to add another adapter. It was to
rethink the adapter contract entirely.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="headless-architecture-pillar-1-enforced">Headless Architecture (Pillar 1, enforced)<a href="https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion#headless-architecture-pillar-1-enforced" class="hash-link" aria-label="Direct link to Headless Architecture (Pillar 1, enforced)" title="Direct link to Headless Architecture (Pillar 1, enforced)" translate="no">​</a></h3>
<p>Every adapter was refactored to receive a pre-built context object — <code>BuildContext</code> —
rather than reading configuration files at runtime. This eliminated the last class of
I/O in the hot path and made the system deterministically testable.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="new-in-v061rc2">New in v0.6.1rc2<a href="https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion#new-in-v061rc2" class="hash-link" aria-label="Direct link to New in v0.6.1rc2" title="Direct link to New in v0.6.1rc2" translate="no">​</a></h3>
<table><thead><tr><th style="text-align:left">Feature</th><th style="text-align:left">Notes</th></tr></thead><tbody><tr><td style="text-align:left">Zensical adapter (<code>engine = "zensical"</code>)</td><td style="text-align:left">Full <code>zensical.toml</code> parsing</td></tr><tr><td style="text-align:left">Zensical Transparent Proxy</td><td style="text-align:left"><code>engine = "zensical-bridge"</code> for MkDocs nav compatibility</td></tr><tr><td style="text-align:left">Docusaurus enterprise adapter</td><td style="text-align:left">Versioned docs, <code>@site/</code> alias, slug alignment</td></tr><tr><td style="text-align:left">Z401 <code>MISSING_DIRECTORY_INDEX</code></td><td style="text-align:left">SEO guardrail: every directory must have a landing page</td></tr><tr><td style="text-align:left">Z402 <code>ORPHAN_PAGE</code></td><td style="text-align:left">Detects pages declared but unreachable from the nav tree</td></tr><tr><td style="text-align:left">Zenzic Lab (9 Acts)</td><td style="text-align:left">Interactive zero-config onboarding showcase</td></tr><tr><td style="text-align:left"><code>uvx zenzic check all ./path</code></td><td style="text-align:left">Primary curiosity path with zero install friction</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shield-hardening-four-bypass-vectors-closed">Shield hardening: four bypass vectors closed<a href="https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion#shield-hardening-four-bypass-vectors-closed" class="hash-link" aria-label="Direct link to Shield hardening: four bypass vectors closed" title="Direct link to Shield hardening: four bypass vectors closed" translate="no">​</a></h3>
<p>The security audit that ran concurrently with the Bastion architecture work found and
closed four bypass vectors in the Shield credential scanner — the module responsible
for detecting leaked credentials before any documentation build begins.</p>
<table><thead><tr><th style="text-align:left">Vector</th><th style="text-align:left">Codename</th><th style="text-align:left">Attack method</th><th style="text-align:left">Fix</th></tr></thead><tbody><tr><td style="text-align:left">Unicode format characters</td><td style="text-align:left">ZRT-006</td><td style="text-align:left">Zero-width joiners (U+200D, U+200C, U+200B) inserted mid-token to break regex matching</td><td style="text-align:left">Normaliser strips all Unicode category <code>Cf</code> characters before scanning</td></tr><tr><td style="text-align:left">HTML entity obfuscation</td><td style="text-align:left">ZRT-006</td><td style="text-align:left"><code>&amp;#65;&amp;#75;</code> encoded credential prefixes bypass plain-text matching</td><td style="text-align:left"><code>html.unescape()</code> decodes <code>&amp;#NNN;</code> and <code>&amp;#xHH;</code> entities in the normalisation pass</td></tr><tr><td style="text-align:left">Comment interleaving</td><td style="text-align:left">ZRT-007</td><td style="text-align:left">HTML <code>&lt;!-- --&gt;</code> and MDX <code>{/* */}</code> comments inserted mid-token</td><td style="text-align:left">Normaliser strips both comment forms before any pattern is applied</td></tr><tr><td style="text-align:left">Cross-line split detection</td><td style="text-align:left">ZRT-007b</td><td style="text-align:left">Secrets split across two consecutive lines evade single-line scanners</td><td style="text-align:left"><code>scan_lines_with_lookback()</code> carries a 1-line lookback buffer; deduplication via seen-set</td></tr></tbody></table>
<p>Two additional hardening measures were applied in this cycle:</p>
<p><strong>Blood Sentinel (Exit 3).</strong> A dedicated fatal exit code was formalised for architectural
violations — <code>docs_dir</code> paths that escape the repository root now trigger Exit 3 before
any scan begins. This is not a lint finding. It is a hard stop.</p>
<p><strong>ReDoS protection.</strong> Lines exceeding 1 MiB are silently truncated before regex matching,
preventing catastrophic backtracking against adversarially crafted inputs.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="operation-obsidian-stress">Operation Obsidian Stress<a href="https://zenzic.dev/blog/zenzic-v061rc2-obsidian-bastion#operation-obsidian-stress" class="hash-link" aria-label="Direct link to Operation Obsidian Stress" title="Direct link to Operation Obsidian Stress" translate="no">​</a></h3>
<p>During the v0.6.1rc2 cycle, an AI-driven adversarial audit found <strong>four bypass vectors</strong>
in the Shield credential scanner. All four were closed before the release candidate was
finalised. The complete post-mortem is published here:</p>
<p>→ <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">We Put Our Documentation Linter Under an AI-Driven Siege</a></p>
<p>The full architectural story of the Bastion cycle:</p>
<p>→ <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">What Happens When You Rip the Foundation Out of a Security Tool</a></p>]]></content:encoded>
            <category>Security</category>
            <category>DevTools</category>
            <category>Milestone Record</category>
        </item>
        <item>
            <title><![CDATA[Headless Architecture]]></title>
            <link>https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion</link>
            <guid>https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion</guid>
            <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Inside the linter that trusts nothing — not even its own config files. How Obsidian Bastion turned Zenzic into a multi-engine security infrastructure.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Saga I</a> | <strong>Saga II</strong> | <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Saga III</a> | <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga IV</a> | <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Saga V</a> | <a class="" href="https://zenzic.dev/blog/governance-of-quartz">Saga VI</a></p></div></div>
<p>Most documentation builds operate on an implicit contract with their input: the content
is trusted because the contributors are trusted. It's a reasonable assumption for a
wiki. It is an indefensible posture for a security-conscious CI pipeline.</p>
<p>Zenzic was built to invalidate that assumption — to treat documentation the way a
compiler treats source: as input that must be analyzed, validated, and potentially
rejected before it reaches production.</p>
<p>If your documentation is part of your CI pipeline, it's part of your attack surface.
Zenzic is designed for CI pipelines that handle untrusted docs, open-source projects
with external contributors, and teams running multiple doc engines side by side.</p>
<!-- -->
<p>In <a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1</a>, I covered the philosophy and
threat model. This piece is about how Obsidian Bastion enforced them as infrastructure
properties.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-where-zenzic-fits">🎯 Where Zenzic Fits<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#-where-zenzic-fits" class="hash-link" aria-label="Direct link to 🎯 Where Zenzic Fits" title="Direct link to 🎯 Where Zenzic Fits" translate="no">​</a></h2>
<p>Zenzic is designed for:</p>
<ul>
<li class="">CI pipelines that handle untrusted docs</li>
<li class="">Open-source projects with external contributors</li>
<li class="">Teams running multiple doc engines side by side</li>
<li class="">Security-conscious workflows that need to validate content before the build — not after</li>
</ul>
<p>Three core properties define it:</p>
<p><strong>No subprocess execution — ever.</strong> No <code>node</code>, no <code>git</code>, no shell calls. The core
library is 100% Pure Python. This isn't a convenience feature — it's a security model.
A tool that spawns subprocesses is a tool that can be tricked into executing untrusted
code.</p>
<p><strong>Engine-agnostic analysis.</strong> Zenzic reads raw Markdown and configuration files as
plain data. It never imports or executes a documentation framework. Engine-specific
knowledge lives in thin, replaceable adapters that translate semantics into a neutral
protocol.</p>
<p><strong>Deterministic file discovery.</strong> Every file scan is explicit. Every path is validated.
There are no accidental full-repo traversals, no hidden directories slipping through.
Identical source files always produce identical results.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-versioning-as-a-threat-model">The Versioning As a Threat Model<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#the-versioning-as-a-threat-model" class="hash-link" aria-label="Direct link to The Versioning As a Threat Model" title="Direct link to The Versioning As a Threat Model" translate="no">​</a></h2>
<p>Understanding what changed in the Obsidian series requires understanding what the
previous version got wrong.</p>
<table><thead><tr><th style="text-align:left">Version</th><th style="text-align:left">Codename</th><th style="text-align:left">Milestone</th></tr></thead><tbody><tr><td style="text-align:left">v0.5.x</td><td style="text-align:left">The Sentinel</td><td style="text-align:left">Core scanning + MkDocs-only awareness</td></tr><tr><td style="text-align:left">v0.6.0</td><td style="text-align:left">Obsidian Glass</td><td style="text-align:left">Headless architecture transition</td></tr><tr><td style="text-align:left">v0.6.1rc2</td><td style="text-align:left">Obsidian Bastion</td><td style="text-align:left">Multi-engine security infrastructure</td></tr></tbody></table>
<p>The Sentinel was a capable linter. It was also architecturally coupled to a single
documentation engine. When a MkDocs assumption was embedded in the core, the core had
opinions about what “valid documentation” meant that had nothing to do with the content
being analyzed.</p>
<p>This coupling is a risk. An analyzer that assumes its input follows MkDocs conventions
will fail silently — or not at all — when presented with a Docusaurus project.
Failing silently is the worst possible outcome for a security tool: it gives a false
sense of coverage.</p>
<p>The biggest single commit in this arc deleted <strong>21,870 lines</strong> and added <strong>888</strong> — the
Headless Architecture transition that stopped Zenzic from being a MkDocs tool and made
it an analyser of documentation platforms.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="️-parsing-docusaurus-without-node">⚛️ Parsing Docusaurus without Node<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#%EF%B8%8F-parsing-docusaurus-without-node" class="hash-link" aria-label="Direct link to ⚛️ Parsing Docusaurus without Node" title="Direct link to ⚛️ Parsing Docusaurus without Node" translate="no">​</a></h2>
<p>The first concrete challenge was supporting Docusaurus v3. Its config files are
TypeScript:</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  presets</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'classic'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> docs</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> routeBasePath</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/guides'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  i18n</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> defaultLocale</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'en'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> locales</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'en'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'it'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>The obvious solution — calling <code>node</code> to evaluate the config — would violate Pillar 2
(No Subprocesses). So I built a <strong>static parser in Pure Python</strong> that extracts
<code>baseUrl</code>, <code>routeBasePath</code>, locale configuration, and plugin metadata directly from
the source text. No evaluation. No runtime. No JavaScript.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-layered-exclusion--the-real-headline-feature">🧱 Layered Exclusion — The Real Headline Feature<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#-layered-exclusion--the-real-headline-feature" class="hash-link" aria-label="Direct link to 🧱 Layered Exclusion — The Real Headline Feature" title="Direct link to 🧱 Layered Exclusion — The Real Headline Feature" translate="no">​</a></h2>
<p>File discovery is where most documentation tools quietly fail. The <strong>Layered Exclusion
Manager</strong> replaces all ad-hoc directory filtering with a deterministic 4-level hierarchy:</p>
<table><thead><tr><th style="text-align:center">Level</th><th style="text-align:left">Name</th><th style="text-align:left">Description</th></tr></thead><tbody><tr><td style="text-align:center">L1</td><td style="text-align:left">System guardrails</td><td style="text-align:left">Immutable — <code>.git</code>, <code>node_modules</code>, <code>__pycache__</code>, etc.</td></tr><tr><td style="text-align:center">L2</td><td style="text-align:left"><code>.gitignore</code> + forced inclusions</td><td style="text-align:left">Additive rules, parsed in Pure Python</td></tr><tr><td style="text-align:center">L3</td><td style="text-align:left">Config (<code>zenzic.toml</code>)</td><td style="text-align:left"><code>excluded_dirs</code> / <code>excluded_file_patterns</code></td></tr><tr><td style="text-align:center">L4</td><td style="text-align:left">CLI flags</td><td style="text-align:left"><code>--exclude-dir</code> / <code>--include-dir</code> at runtime</td></tr></tbody></table>
<p>The levels encode a <strong>security invariant</strong>: L1 System Guardrails are immutable. No
configuration file and no CLI flag can force Zenzic to scan inside <code>.git/</code> or
<code>node_modules/</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="️-the-tabula-rasa-refactor">🗡️ The Tabula Rasa Refactor<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#%EF%B8%8F-the-tabula-rasa-refactor" class="hash-link" aria-label="Direct link to 🗡️ The Tabula Rasa Refactor" title="Direct link to 🗡️ The Tabula Rasa Refactor" translate="no">​</a></h2>
<p>The most invasive change: I removed every single <code>rglob()</code> call from the codebase — all
of them — and replaced them with two centralized functions in <code>discovery.py</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">walk_files</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">root</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> exclusion_manager</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">iter_markdown_sources</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">root</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> exclusion_manager</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Iterator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></div></code></pre></div></div>
<p>The <code>exclusion_manager</code> parameter is <strong>mandatory</strong>. Not <code>Optional</code>, no <code>None</code> default.
If you call a scanner or validator entry point without an <code>ExclusionManager</code>, you get a
<code>TypeError</code> at call time — not a silent full-tree scan at runtime.</p>
<p>168 call sites updated. Accidental full-repo scans are now <strong>architecturally impossible</strong>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-security-hardening">🔐 Security Hardening<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#-security-hardening" class="hash-link" aria-label="Direct link to 🔐 Security Hardening" title="Direct link to 🔐 Security Hardening" translate="no">​</a></h2>
<p><strong>ReDoS prevention.</strong> Lines exceeding 1 MiB are silently truncated before Shield regex
matching. A crafted documentation file with a multi-megabyte line could exploit
catastrophic backtracking in credential detection patterns.</p>
<p><strong>Path traversal guard (Exit Code 3).</strong> <code>_validate_docs_root()</code> now rejects <code>docs_dir</code>
paths that escape the repository root. A malicious <code>zenzic.toml</code> pointing
<code>docs_dir: ../../../etc/</code> triggers <strong>Exit 3 (Blood Sentinel)</strong> before any file is read.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-supply-chain-risk-metric-that-doesnt-get-enough-attention">The Supply Chain Risk Metric That Doesn't Get Enough Attention<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#the-supply-chain-risk-metric-that-doesnt-get-enough-attention" class="hash-link" aria-label="Direct link to The Supply Chain Risk Metric That Doesn't Get Enough Attention" title="Direct link to The Supply Chain Risk Metric That Doesn't Get Enough Attention" translate="no">​</a></h2>
<p>Runtime dependency count is an underappreciated supply chain security metric.</p>
<p>Every Python package that Zenzic imports at runtime is a potential vector for
dependency confusion attacks, malicious package updates, and transitive vulnerability
inheritance. The decision to minimize the dependency surface is not about keeping the
package small — it is about limiting the attack surface of the supply chain.</p>
<p>Zenzic's runtime dependency count: <strong>5</strong>.</p>
<p>For a tool that supports four documentation engines, performs multi-family credential
detection, implements a deterministic quality scoring system, validates link graphs
against a virtual site map, and runs over a thousand tests — five runtime dependencies
is a deliberate architectural achievement, not a limitation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-hardened-actually-means">What "Hardened" Actually Means<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#what-hardened-actually-means" class="hash-link" aria-label="Direct link to What &quot;Hardened&quot; Actually Means" title="Direct link to What &quot;Hardened&quot; Actually Means" translate="no">​</a></h2>
<p>The word “hardened” is overused in security marketing. In the context of Obsidian
Baston, it has a specific meaning: every component of the system has been analyzed for
its failure modes under adversarial input, and those failure modes have been either
eliminated or bounded.</p>
<p>The subprocess constraint eliminates the execution trust boundary. The Layered Exclusion
Manager bounds the filesystem access surface. The mandatory <code>ExclusionManager</code> type
enforces the boundary at the API level. The non-bypassable exit codes ensure that
security failures produce unambiguous CI outcomes. The ReDoS truncation bounds the
computational cost of analysis. The path traversal guard bounds the filesystem read
scope.</p>
<p>None of these are features. They are the removal of assumptions — the careful,
systematic elimination of the implicit trust that characterizes unexamined systems.</p>
<blockquote>
<p>A hardened system is not a system with more defenses added on top. It is a system
with fewer assumptions built in.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-broke-along-the-way">What Broke Along the Way<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#what-broke-along-the-way" class="hash-link" aria-label="Direct link to What Broke Along the Way" title="Direct link to What Broke Along the Way" translate="no">​</a></h2>
<p>A refactor of this scope does not leave the API surface intact. Three breaking changes
were deliberate, not accidental:</p>
<ul>
<li class="">
<p><strong><code>zenzic serve</code> removed entirely</strong> — use your engine's native command (<code>mkdocs serve</code>,</p>
<p><code>npx docusaurus start</code>). It was the last place where a subprocess could theoretically
be spawned.</p>
</li>
<li class="">
<p><strong>MkDocs plugin relocated</strong> from <code>zenzic.plugin</code> to <code>zenzic.integrations.mkdocs</code>,</p>
<p>installs separately via <code>pip install "zenzic[mkdocs]"</code>, keeping the core free of
engine-specific imports.</p>
</li>
<li class="">
<p><strong><code>ExclusionManager</code> parameter is now mandatory</strong> — no <code>Optional</code>, no <code>None</code> default.</p>
<p>If your code was silently skipping exclusion filtering, it will now fail at the type
level. That's the point.</p>
</li>
</ul>
<p>These are costs. They are also the reason the guarantees in this article are
enforceable rather than aspirational.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-by-the-numbers">📊 By the Numbers<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#-by-the-numbers" class="hash-link" aria-label="Direct link to 📊 By the Numbers" title="Direct link to 📊 By the Numbers" translate="no">​</a></h2>
<table><thead><tr><th style="text-align:left">Metric</th><th style="text-align:right">Value</th><th style="text-align:left">Note</th></tr></thead><tbody><tr><td style="text-align:left">Test functions</td><td style="text-align:right">929</td><td style="text-align:left">High-granularity validation</td></tr><tr><td style="text-align:left">Source code</td><td style="text-align:right">11,422 LOC</td><td style="text-align:left">Real architectural scope</td></tr><tr><td style="text-align:left">Test code</td><td style="text-align:right">12,927 LOC</td><td style="text-align:left">~1.13x ratio — disciplined testing</td></tr><tr><td style="text-align:left">Engine adapters</td><td style="text-align:right">4</td><td style="text-align:left">MkDocs, Docusaurus v3, Zensical, Standalone</td></tr><tr><td style="text-align:left">Runtime dependencies</td><td style="text-align:right">5</td><td style="text-align:left">Minimal supply chain risk</td></tr><tr><td style="text-align:left">Subprocess calls</td><td style="text-align:right">0</td><td style="text-align:left">Safe in sandboxed CI environments</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-run-it-against-your-docs">🏁 Run It Against Your Docs<a href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion#-run-it-against-your-docs" class="hash-link" aria-label="Direct link to 🏁 Run It Against Your Docs" title="Direct link to 🏁 Run It Against Your Docs" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> zenzic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all</span><br></div></code></pre></div></div>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr></tbody></table>
<p><strong>Cross-posted on:</strong></p>
<ul>
<li class=""><a href="https://medium.com/zenzic-engineering/what-happens-when-you-rip-the-foundation-out-of-a-security-tool-173b57d496b2" target="_blank" rel="noopener noreferrer" class="">Medium</a> — <em>What Happens When You Rip the Foundation Out of a Security Tool</em></li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>The Zenzic Chronicles</div><div class="admonitionContent_BuS1"><p>This is <strong>Part 2</strong> of a five-part engineering series documenting the path from v0.5 to v0.7.0 Stable.</p><p><a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Part 1 — The Sentinel</a> · <strong>Part 2 — Sentinel Bastion</strong> · <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3 — The AI Siege</a> · <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Part 4 — Beyond the Siege</a> · <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5 — Quartz Maturity</a></p></div></div>
<p><em>Part 2 of the <strong>Zenzic Chronicles</strong>. For the complete architectural journey, visit the <a href="https://zenzic.dev/blog/" target="_blank" rel="noopener noreferrer" class="">Safe Harbor Blog</a>.</em></p>]]></content:encoded>
            <category>Engineering</category>
            <category>Security</category>
            <category>Python</category>
            <category>DevTools</category>
            <category>The Zenzic Chronicles</category>
            <category>Engineering Chronicles</category>
        </item>
        <item>
            <title><![CDATA[Zenzic v0.6.0a1 — The Sentinel]]></title>
            <link>https://zenzic.dev/blog/zenzic-v060a1-the-sentinel</link>
            <guid>https://zenzic.dev/blog/zenzic-v060a1-the-sentinel</guid>
            <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Sentinel era alpha. v0.6.0a1 introduces the core architecture — pure Python, no subprocesses, engine-agnostic — and the three non-negotiable pillars.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>Alpha/RC Chronicle</div><div class="admonitionContent_BuS1"><p>This is a historical record of a development milestone. The first stable release is <strong><a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">v0.7.0 — Quartz Maturity</a></strong>.</p></div></div>
<p><strong>v0.6.0a1 — The Sentinel</strong> is the founding alpha: the release that crystallised the
philosophy of Zenzic from a prototype into a disciplined engineering system.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-shipped">What shipped<a href="https://zenzic.dev/blog/zenzic-v060a1-the-sentinel#what-shipped" class="hash-link" aria-label="Direct link to What shipped" title="Direct link to What shipped" translate="no">​</a></h2>
<p>This alpha established three architectural pillars that remain non-negotiable to this day:</p>
<ol>
<li class="">
<p><strong>Lint the Source</strong> — Zenzic never runs the build. It reads raw Markdown and configuration</p>
<p>files directly. HTML output is never trusted.</p>
</li>
<li class="">
<p><strong>No Subprocesses</strong> — 100% pure Python. No <code>subprocess.run</code>, no Node.js execution,</p>
<p>no external process calls in the hot path.</p>
</li>
<li class="">
<p><strong>Pure Functions First</strong> — Deterministic logic. No I/O inside link-validation loops.</p>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-capabilities-in-v060a1">Core capabilities in v0.6.0a1<a href="https://zenzic.dev/blog/zenzic-v060a1-the-sentinel#core-capabilities-in-v060a1" class="hash-link" aria-label="Direct link to Core capabilities in v0.6.0a1" title="Direct link to Core capabilities in v0.6.0a1" translate="no">​</a></h3>
<table><thead><tr><th style="text-align:left">Feature</th><th style="text-align:left">Status</th></tr></thead><tbody><tr><td style="text-align:left"><code>zenzic check all</code> — multi-mode file scan</td><td style="text-align:left">✅ Alpha</td></tr><tr><td style="text-align:left">Shield credential scanner (exit code 2/3)</td><td style="text-align:left">✅ Alpha</td></tr><tr><td style="text-align:left">MkDocs adapter</td><td style="text-align:left">✅ Alpha</td></tr><tr><td style="text-align:left">Docusaurus v3 adapter</td><td style="text-align:left">✅ Alpha</td></tr><tr><td style="text-align:left">Virtual Site Map (VSM)</td><td style="text-align:left">🚧 Prototype</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-sentinel-identity">The Sentinel identity<a href="https://zenzic.dev/blog/zenzic-v060a1-the-sentinel#the-sentinel-identity" class="hash-link" aria-label="Direct link to The Sentinel identity" title="Direct link to The Sentinel identity" translate="no">​</a></h3>
<p>The "Sentinel" codename captured the philosophy: an always-on guard at the source
boundary, not an inspector of finished output. The Sentinel does not care how the
site renders. It cares whether the source is honest.</p>
<p>The Shield — the credential scanner — was the first concrete expression of that posture:
a module that exits with code 2 on secrets detected and code 3 on a Blood Sentinel
(fatal leak pattern), before any documentation build can begin.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-came-next">What came next<a href="https://zenzic.dev/blog/zenzic-v060a1-the-sentinel#what-came-next" class="hash-link" aria-label="Direct link to What came next" title="Direct link to What came next" translate="no">​</a></h2>
<p>The critical groundwork laid in v0.6.0a1 — especially the <code>find_repo_root()</code> discovery
protocol (ADR 003) and the two-phase <code>zenzic init</code> bootstrap sequence — proved solid
enough to survive two major hardening passes without architectural change.</p>
<p>Read the full technical story: <a class="" href="https://zenzic.dev/blog/hardening-the-documentation-pipeline">Your Documentation is a Leaking Pipe</a></p>]]></content:encoded>
            <category>Engineering</category>
            <category>Python</category>
            <category>Open Source</category>
            <category>Milestone Record</category>
        </item>
        <item>
            <title><![CDATA[The Leaking Pipe]]></title>
            <link>https://zenzic.dev/blog/hardening-the-documentation-pipeline</link>
            <guid>https://zenzic.dev/blog/hardening-the-documentation-pipeline</guid>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The supply chain risk hiding in your Markdown pipeline — and why every engineering team has it. Philosophy, threat model, and architecture of Zenzic.
]]></description>
            <content:encoded><![CDATA[<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>In a hurry?</div><div class="admonitionContent_BuS1"><p>Skip the engineering deep dive — jump straight to the <a class="" href="https://zenzic.dev/blog/tutorial-stop-broken-links-60s">⚡ Tutorial: Stop Broken Links</a> and protect your docs in 5 minutes.</p></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>🛡️ The Zenzic Chronicles — Complete</div><div class="admonitionContent_BuS1"><p>The complete six-part engineering saga of Zenzic's journey from v0.5 Sentinel to v0.7.0 Quartz Maturity. The Chronicles are sealed.</p><p><strong>Saga I</strong> | <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Saga II</a> | <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Saga III</a> | <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Saga IV</a> | <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Saga V</a> | <a class="" href="https://zenzic.dev/blog/governance-of-quartz">Saga VI</a></p></div></div>
<p>Every CI/CD pipeline has a security perimeter. Developers run static analysis on source
code. They scan container images for CVEs. They audit dependencies for known
vulnerabilities. They enforce secrets detection in commit hooks.</p>
<p>And then they push raw, unvalidated Markdown files directly into a documentation
build — and call it shipped.</p>
<p>This is not a theoretical gap. It is the default posture of almost every engineering
team I've observed. I built <strong>Zenzic</strong> to prove it — and fix it.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-threat-model-nobody-talks-about">The Threat Model Nobody Talks About<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#the-threat-model-nobody-talks-about" class="hash-link" aria-label="Direct link to The Threat Model Nobody Talks About" title="Direct link to The Threat Model Nobody Talks About" translate="no">​</a></h2>
<p>Consider the anatomy of a typical documentation credential leak.</p>
<p>A contributor opens a pull request with new API documentation. The Markdown file
contains a code example. Inside that example — copied from a local test, a Slack
message, or a terminal session — there is a real API key. Not a placeholder. A live
credential.</p>
<p>The reviewer reads the prose. The reviewer doesn't read the key as a key — it's
formatted as sample output, it blends into the noise. The PR merges. The docs build
runs. The rendered HTML goes live. The key is now indexed by search engines.</p>
<p>Now extend the threat model outward. What happens when a <code>docs_dir</code> configuration
entry points to <code>../../../etc/</code>? Most documentation tools will simply start reading.
What happens when a contributor submits a <code>.gitignore</code> entry designed to suppress
certain files, but those files are present on the build server?</p>
<p>These questions don't appear in the standard security checklist. They belong to a class
of supply chain risk that sits precisely between source code and rendered output — where
tooling is sparse and assumptions are dangerous.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-core-philosophy-lint-the-source-not-the-build">⚓ The Core Philosophy: "Lint the Source, not the Build"<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-the-core-philosophy-lint-the-source-not-the-build" class="hash-link" aria-label="Direct link to ⚓ The Core Philosophy: &quot;Lint the Source, not the Build&quot;" title="Direct link to ⚓ The Core Philosophy: &quot;Lint the Source, not the Build&quot;" translate="no">​</a></h2>
<p>Most documentation tools analyze the generated HTML. This creates a "build driver
dependency": if your generator (MkDocs, Hugo, Docusaurus) has a bug, your security
validation breaks.</p>
<p>Zenzic takes a different path. It analyzes the raw Markdown source <strong>before the build
starts</strong>, building a <strong>Virtual Site Map (VSM)</strong> directly from the filesystem. The core
never knows which engine it's analyzing. It can't be tricked by disguising content as
engine-specific directives.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-pure-python-is-a-security-decision">Why Pure Python Is a Security Decision<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#why-pure-python-is-a-security-decision" class="hash-link" aria-label="Direct link to Why Pure Python Is a Security Decision" title="Direct link to Why Pure Python Is a Security Decision" translate="no">​</a></h2>
<p>Most tooling in the documentation space runs through execution engines. Markdown
configuration files get evaluated. Node.js processes get spawned. Shell commands get
invoked to query version control state.</p>
<p>Each of these is a trust boundary. The moment your analyzer executes code to understand
your content, it has accepted the premise that the content can be trusted to execute
safely. This is circular reasoning — particularly dangerous when the content being
analyzed comes from external contributors.</p>
<p>Zenzic was designed from day one around a single architectural invariant:
<strong>zero subprocess execution, ever.</strong></p>
<p>No <code>node</code> process to evaluate Docusaurus configuration. No <code>git check-ignore</code> to
interpret <code>.gitignore</code> rules. No shell calls. Every piece of analysis runs in the Python
interpreter, on data read as plain bytes and treated as untrusted input throughout.</p>
<p>This is not a convenience trade-off. It is a security model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architecture-of-suspicion">The Architecture of Suspicion<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#the-architecture-of-suspicion" class="hash-link" aria-label="Direct link to The Architecture of Suspicion" title="Direct link to The Architecture of Suspicion" translate="no">​</a></h2>
<p>Zenzic's core operates under what I think of as <strong>architectural suspicion</strong>: every input
is assumed hostile until proven otherwise, and the analysis pipeline is designed to fail
safely when something unexpected appears.</p>
<p>Three properties define this architecture:</p>
<p><strong>Engine-agnostic analysis.</strong> Zenzic never imports or executes a documentation
framework. Engine-specific semantics — how MkDocs resolves nav entries, how Docusaurus
handles locale trees — live in thin, replaceable adapters. The core never has opinions
about what "valid documentation" means beyond the content itself.</p>
<p><strong>Deterministic file discovery.</strong> File traversal is one of the most quietly dangerous
operations in any build tool. Zenzic's discovery layer enforces a four-level exclusion
hierarchy: immutable system guardrails (no code can read inside <code>.git/</code> or
<code>node_modules/</code>), VCS ignore rules parsed in pure Python, project configuration, and
runtime overrides. The hierarchy is not advisory — it is enforced at the type boundary.
No <code>ExclusionManager</code>, no scan.</p>
<p><strong>Non-bypassable exit codes.</strong> When Zenzic detects a credential leak, it exits with
code 2 — the Shield. When it detects a path traversal attempt, it exits with code 3 —
the Blood Sentinel. These codes cannot be suppressed, downgraded, or configured away.
The perimeter holds, or the build fails.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-blood-sentinel-classifying-intent">🩸 The Blood Sentinel: Classifying Intent<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-the-blood-sentinel-classifying-intent" class="hash-link" aria-label="Direct link to 🩸 The Blood Sentinel: Classifying Intent" title="Direct link to 🩸 The Blood Sentinel: Classifying Intent" translate="no">​</a></h2>
<p>A broken link is a maintenance issue. A link that probes the host OS is a security
incident.</p>
<p>Zenzic's classification engine detects if a resolved path targets sensitive OS
directories (<code>/etc/</code>, <code>/proc/</code>, <code>/var/</code>, etc.). Instead of a generic error, it triggers
a dedicated <strong>Exit Code 3</strong> — crucial for preventing accidental leakage of
infrastructure details or template injection probes in automated pipelines.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-the-shield-multi-stream-credential-scanning">🔐 The Shield: Multi-Stream Credential Scanning<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-the-shield-multi-stream-credential-scanning" class="hash-link" aria-label="Direct link to 🔐 The Shield: Multi-Stream Credential Scanning" title="Direct link to 🔐 The Shield: Multi-Stream Credential Scanning" translate="no">​</a></h2>
<p>Documentation is a magnet for "temporary" credentials that end up being permanent.
Zenzic's Shield scans every line and fenced code block for 9 families of secrets:</p>
<ul>
<li class="">AWS, GitHub, Stripe, and OpenAI keys</li>
<li class="">GitLab Personal Access Tokens</li>
<li class="">Slack tokens and Google API keys</li>
<li class="">Hex-encoded payloads (<code>\xNN</code> escape sequences) for obfuscated strings</li>
<li class=""><strong>Exit Code 2</strong>: A credential breach is a build-blocking, non-suppressible event</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-graph-integrity-and-ove-complexity">🌀 Graph Integrity and $O(V+E)$ Complexity<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-graph-integrity-and-ove-complexity" class="hash-link" aria-label="Direct link to 🌀 Graph Integrity and $O(V+E)$ Complexity" title="Direct link to 🌀 Graph Integrity and $O(V+E)$ Complexity" translate="no">​</a></h2>
<p>In large documentation sets, link cycles are common. Zenzic implements an <strong>iterative
DFS</strong> with a three-color marking system to avoid recursion limits. Pre-computing the
cycle registry in Phase 1.5 allows Phase 2 (Validation) to remain $O(1)$ per-query —
even massive docsets validate in seconds.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-dogfooding-i18n">🇮🇹 Dogfooding i18n<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-dogfooding-i18n" class="hash-link" aria-label="Direct link to 🇮🇹 Dogfooding i18n" title="Direct link to 🇮🇹 Dogfooding i18n" translate="no">​</a></h2>
<p>We believe in bilingual documentation. Zenzic supports native i18n with "Ghost Routes"
— logical paths that don't exist on disk but are resolved by build plugins. We keep our
own documentation in full parity between English and Italian.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="supply-chain-security-starts-before-the-build">Supply Chain Security Starts Before the Build<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#supply-chain-security-starts-before-the-build" class="hash-link" aria-label="Direct link to Supply Chain Security Starts Before the Build" title="Direct link to Supply Chain Security Starts Before the Build" translate="no">​</a></h2>
<p>There is a maturing conversation about supply chain security in software. Most of that
conversation focuses on dependencies: SBOM generation, CVE scanning, license auditing.
These are necessary. They are not sufficient.</p>
<p>The documentation pipeline is also part of the supply chain. It receives inputs from
contributors who may be external to the organization. It runs in the same CI environment
as your source build. It publishes output that is indexed, cached, and distributed at
scale.</p>
<p>A credential leaked in documentation has the same blast radius as a credential committed
to source code. A path traversal through <code>docs_dir</code> can access the same filesystem as
your CI runner.</p>
<p>This is why Zenzic exists. Not to lint Markdown formatting. To treat documentation as
input — with all the suspicion and rigor that phrase implies.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-obligation-of-precision">The Obligation of Precision<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#the-obligation-of-precision" class="hash-link" aria-label="Direct link to The Obligation of Precision" title="Direct link to The Obligation of Precision" translate="no">​</a></h2>
<p>Security tooling carries an obligation that productivity tooling does not: when it says
something is safe, it must be right. A false negative in a documentation linter means
a credential goes undetected. A path traversal guard that can be bypassed means the
bypass is a feature, not a bug.</p>
<p>The normalization pipeline that runs before credential detection was not built to be
comprehensive — it was built because each step corresponds to a real attack vector
identified during internal red team exercises: Unicode format character injection, HTML
entity obfuscation, comment interleaving, cross-line token splitting. Each is a
documented technique, not a theoretical concern. The full story of those bypass vectors
is in <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3 of this series</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="-run-it">🏁 Run It<a href="https://zenzic.dev/blog/hardening-the-documentation-pipeline#-run-it" class="hash-link" aria-label="Direct link to 🏁 Run It" title="Direct link to 🏁 Run It" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> zenzic</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">zenzic check all</span><br></div></code></pre></div></div>
<p>The largest single architectural step in Zenzic's history deleted 21,870 lines and
added 888 — the Headless Architecture transition that turned a MkDocs-specific tool
into a multi-engine documentation security framework. That story is
<a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2 in this series</a>.</p>
<hr>
<table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>GitHub</strong></td><td><a href="https://github.com/PythonWoods/zenzic" target="_blank" rel="noopener noreferrer" class="">github.com/PythonWoods/zenzic</a></td></tr><tr><td><strong>Documentation</strong></td><td><a href="https://zenzic.dev/" target="_blank" rel="noopener noreferrer" class="">zenzic.dev</a></td></tr><tr><td><strong>PyPI</strong></td><td><a href="https://pypi.org/project/zenzic/" target="_blank" rel="noopener noreferrer" class="">pypi.org/project/zenzic</a></td></tr></tbody></table>
<p><strong>Cross-posted on:</strong></p>
<ul>
<li class=""><a href="https://medium.com/zenzic-engineering/your-documentation-is-a-leaking-pipe-7c1d6f4a84d0" target="_blank" rel="noopener noreferrer" class="">Medium</a> — <em>Your Documentation is a Leaking Pipe</em></li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>The Zenzic Chronicles</div><div class="admonitionContent_BuS1"><p>This is <strong>Part 1</strong> of a five-part engineering series documenting the path from v0.5 to v0.7.0 Stable.</p><p><strong>Part 1 — The Sentinel</strong> · <a class="" href="https://zenzic.dev/blog/docs-pipeline-security-risk-obsidian-bastion">Part 2 — Sentinel Bastion</a> · <a class="" href="https://zenzic.dev/blog/ai-driven-siege-shield-postmortem">Part 3 — The AI Siege</a> · <a class="" href="https://zenzic.dev/blog/beyond-the-siege-zenzic-v070-quartz">Part 4 — Beyond the Siege</a> · <a class="" href="https://zenzic.dev/blog/zenzic-v070-quartz-maturity-stable">Part 5 — Quartz Maturity</a></p></div></div>
<p><em>Part 1 of the <strong>Zenzic Chronicles</strong>. For the complete architectural journey, visit the <a href="https://zenzic.dev/blog/" target="_blank" rel="noopener noreferrer" class="">Safe Harbor Blog</a>.</em></p>]]></content:encoded>
            <category>Engineering</category>
            <category>Security</category>
            <category>Python</category>
            <category>Open Source</category>
            <category>Markdown</category>
            <category>The Zenzic Chronicles</category>
            <category>Engineering Chronicles</category>
        </item>
    </channel>
</rss>