Skip to main content

ADR 011: Cross-Instance Absolute Path Allowlist

Status: Accepted (May 2026) Decider: Tech Lead Date: 2026-05-03 (v0.7.0 "Quartz Maturity" / "Quarzo")


Context

The v0.7.0 documentation restructure introduced a multi-instance Docusaurus architecture: /docs/* (User area) and /developers/* (Developer area) are served by two separate @docusaurus/plugin-content-docs instances. This split is a discoverability win — search, sidebar, and breadcrumbs no longer mix user-level and engineering content — but it creates a structural friction with the Zenzic core validator.

Each Zenzic adapter analysis pass operates on one Virtual Site Map (VSM) at a time. Cross-plugin links (e.g. a User how-to that links to a Developer reference) cannot be relative — Docusaurus refuses to resolve relative paths across plugin boundaries. They must be absolute (/developers/how-to/...). But absolute paths are exactly what Z105 ABSOLUTE_PATH was designed to forbid: they break portability when a site is hosted in a subdirectory, and they are environment-dependent. The validator, lacking knowledge of the sibling plugin's VSM, sees a legitimate cross-plugin link as a broken absolute reference.

The options examined were:

  • Option A — Implement a manual allowlist in core configuration.
  • Option B — Force the use of JSX components (e.g. <Link>), binding the source to the build engine (violates Pillar 1: Lint the Source).
  • Option C — Auto-detect cross-instance routes via multiple scans (computationally expensive, risks Pillar 2: Zero Subprocesses).

Decision

We adopt Option A: a [link_validation] absolute_path_allowlist key in zenzic.toml. The validator honors listed prefixes as Trusted Ghost Routes — absolute paths whose targets are project-internal but live outside the current VSM.

# zenzic.toml — declarative cross-plugin contract
[link_validation]
absolute_path_allowlist = ["/developers/", "/api/"]

The check runs immediately before the Z105 emission in validator.py: if the parsed path starts with any allowlisted prefix, the link is treated as valid and skipped — no other resolution is attempted, no error is recorded.

Rationale

This decision is governed by the Transparency Invariant:

  • Explicit Declaration. Instead of silencing errors with inline noqa comments scattered through Markdown, the architect declares — once, in config — which absolute prefixes the project owns. The configuration is the cross-instance map.
  • Linter Integrity. Zenzic still does its job: an absolute link that is neither on disk nor in the allowlist still fails the push. The allowlist narrows the scope of trust; it does not weaken Z105 itself.
  • Engine Agnosticism. Markdown source remains agnostic of Docusaurus, MkDocs, or any future multi-instance engine. No <Link> import, no JSX prelude — the same .mdx file would work in a single-instance migration.

Option B was rejected because JSX imports bleed engine-specific syntax into content (Pillar 1 violation). Option C was rejected because it would require either subprocess delegation to the build tool (Pillar 2 violation) or duplicating Docusaurus's plugin-resolution logic in Python (maintenance burden, parity drift).

Invariants

These constraints are permanent consequences of ADR-0011:

  1. Allowlist entries must start with /. Relative entries are nonsense (relative paths never trigger Z105) and would silently broaden the bypass.
  2. Match semantics are startswith only. No globbing, no regex, no wildcards. The semantics must remain inspectable at a glance.
  3. The check runs before Z105 emission, not after. Allowlisted links must never appear in the findings stream — not even as suppressed info — because they represent intentional architectural contracts, not silenced problems.
  4. Allowlist entries are not validated for existence. Z108 STALE_ALLOWLIST_ENTRY (config hygiene) is intentionally deferred to v0.8.0 to preserve Pillar 3: Pure Functions (no aggregate cross-worker state). See Technical Debt Ledger.

Consequences

Pros

  • Pillar 1 preserved. Markdown source stays engine-agnostic.
  • Pillar 2 preserved. Validation remains deterministic, no extra processes or network scans.
  • Audit Trail. The zenzic.toml becomes a documented map of inter- instance dependencies — readable by humans, parseable by tools, versioned in git.
  • Reversible. Removing an entry restores Z105 enforcement on that prefix; the architect can always re-tighten the perimeter.

Cons

  • Manual Maintenance. If a satellite route changes (e.g. /developers//dev/), the allowlist in the core repo must be updated by hand. The validator cannot detect a renamed route through the allowlist alone.
  • Scope Discipline Required. A reckless allowlist (["/"]) would silently disable Z105 entirely. Code review of zenzic.toml changes is the protection.

Transparency Analysis

The allowlist transforms a potential blind spot into a conscious choice. Zenzic's stance is unambiguous: we prefer the developer to write

"Zenzic, I know /developers/ is not in this VSM — trust me."

over hiding the same fact behind an inline suppression comment that degrades the global Quality Score without explaining the system topology. The first form is documentation; the second is technical debt disguised as silence.

This ADR establishes the precedent for how Zenzic will handle expansion toward micro-site architectures: every cross-boundary trust must be declared, named, and reviewable.

Suppression vs Configuration

Zenzic offers two distinct primitives for telling the linter "this is intentional." They are orthogonal and must not be conflated:

PrimitiveScopeUse when
[link_validation] absolute_path_allowlistProject-wide structural contractThe fact is a systemic truth of the architecture (e.g. multi-instance routing, satellite domain prefix).
<!-- zenzic:ignore Zxxx --> / {/* zenzic:ignore Zxxx */}One source lineThe rule is correct in general; this specific occurrence is a documented, local exception (e.g. a code sample that looks like a credential).

The allowlist is a contract: it changes Z105's domain of validity by declaring premises about the project's URL space. The validator still evaluates the link — the evaluation simply has different inputs.

The inline ignore is surgery: it suppresses an emitted finding on a single line, leaves an audit comment in the source, and is reviewed at the diff level.

Anti-pattern (forbidden in v0.7.0+). Cross-plugin links must never be handled with <!-- zenzic:ignore Z105 -->. Doing so would tacitly admit that the routing is "broken and accepted"; in fact the routing is correct by design, and that correctness deserves promotion to the project's structural configuration. Inline suppression of cross-plugin links also fragments the truth: a future contributor reading zenzic.toml would see no record of the cross-boundary dependency.

Decision rule. If the same suppression would be needed in two or more files, it is no longer a local exception — it is a systemic truth and belongs in zenzic.toml. Promote it.