Adapter Internals — Pedagogical Comparison
This section walks through two concrete adapter methods side-by-side.
The contrast between DocusaurusAdapter and StandaloneAdapter shows how the
adapter protocol enables engine-agnostic Core logic.
provides_index() — Does this directory have a landing page
The Core calls provides_index(directory_path) once per directory during orphan
detection. It answers: "Will the engine generate a browsable index for this
directory, so that files inside it are not structurally orphaned?"
DocusaurusAdapter.provides_index() — full engine awareness:
def provides_index(self, directory_path: Path) -> bool:
# Physical index files — Docusaurus serves these directly.
index_files = ("index.md", "index.mdx", "README.md", "README.mdx")
if any((directory_path / f).exists() for f in index_files):
return True
# _category_.json with "generated-index" link — Docusaurus auto-generates
# a category landing page even without a physical index file.
category_json = directory_path / "_category_.json"
if category_json.exists():
try:
import json as _json
data = _json.loads(category_json.read_text(encoding="utf-8"))
link = data.get("link", {})
return isinstance(link, dict) and link.get("type") == "generated-index"
except Exception:
return True # conservative: assume it provides an index
return False
StandaloneAdapter.provides_index() — zero engine assumptions:
def provides_index(self, directory_path: Path) -> bool:
# No engine config — only a plain index.md signals a landing page.
return (directory_path / "index.md").exists()
Key difference: DocusaurusAdapter knows about _category_.json and
README.mdx because those are Docusaurus conventions. StandaloneAdapter
makes no assumptions — it recognises only the universal index.md convention.
get_nav_paths() — What files are discoverable
get_nav_paths() returns the set of file paths reachable via the site's
navigation UI. A file absent from this set is a candidate for Z402
(ORPHAN_BUT_EXISTING).
DocusaurusAdapter.get_nav_paths() — three-source aggregation:
def get_nav_paths(self) -> frozenset[str]:
if self._sidebar_path is not None:
sidebar_paths = _parse_sidebars(self._sidebar_path, self._docs_root)
if sidebar_paths is not None:
# Explicit sidebar: merge with navbar paths.
# A file is REACHABLE if it appears in the sidebar OR the navbar.
return sidebar_paths | self._navbar_paths
# Autogenerated or no sidebar: all files are already REACHABLE.
return frozenset()
self._navbar_paths is populated by _parse_config_navigation() from
docusaurus.config.* — it extracts to: URL paths and docId: attributes
from navbar and footer items. A file linked only in the footer is still
considered discoverable (UX-Discoverability Law, Rule R21).
StandaloneAdapter.get_nav_paths() — intentionally empty:
def get_nav_paths(self) -> frozenset[str]:
"""Empty frozenset — no engine config means no declared nav."""
return frozenset()
When get_nav_paths() returns an empty frozenset, get_route_info() treats
all files as REACHABLE. This is intentional: in Standalone mode there is no
navigation contract, so orphan detection (Z402) is disabled.
_classify_route() — Internal route classification
_classify_route(rel, nav_paths) is a private helper that get_route_info() calls internally
to map a source file path to its route status.
DocusaurusAdapter._classify_route() — four classification rules:
def _classify_route(self, rel: Path, nav_paths: frozenset[str]) -> RouteStatus:
# Rule 1: Private/meta files (e.g. _category_.json) → IGNORED
non_sentinel_parts = [p for p in rel.parts if p != "_version_"]
if any(part.startswith("_") for part in non_sentinel_parts):
return "IGNORED"
# Version Ghost Routes: files under _version_/<label>/ are always REACHABLE.
if len(rel.parts) >= 2 and rel.parts[0] == "_version_":
return "REACHABLE"
# Rule 2: No explicit nav → autogenerated sidebar → all REACHABLE
if not nav_paths:
return "REACHABLE"
# Rule 3: Explicit nav match (sidebar or navbar)
if rel.as_posix() in nav_paths:
return "REACHABLE"
# Locale shadows inherit nav membership
if self.is_shadow_of_nav_page(rel, nav_paths):
return "REACHABLE"
# Ghost Routes: locale entry points (e.g. it/index.mdx)
if rel.name in ("index.md", "index.mdx") and len(rel.parts) == 2:
if rel.parts[0] in self._locale_dirs:
return "REACHABLE"
# Rule 4: File exists but is not discoverable via any UI entry point
return "ORPHAN_BUT_EXISTING"
StandaloneAdapter.get_route_info() — always reachable (no _classify_route() helper):
def get_route_info(self, rel: Path) -> RouteMetadata:
"""Return route metadata derived purely from the filesystem.
StandaloneAdapter has no engine config, no nav, no slug support.
Every file is ``REACHABLE`` with a filesystem-derived URL.
"""
from zenzic.core.adapters._base import RouteMetadata
return RouteMetadata(
canonical_url=self._map_url(rel),
status="REACHABLE",
)
The contrast is stark. DocusaurusAdapter implements a four-rule priority
cascade in _classify_route(), which get_route_info() calls internally.
StandaloneAdapter has no _classify_route() helper — get_route_info() returns
REACHABLE directly because with no engine there is no navigation contract.
get_link_scheme_bypasses() — Engine-specific URI schemes
Rule R21 (Protocol Sovereignty) mandates that the Core never hardcodes engine names in validation logic. Engine-specific URI schemes are declared by the adapter and queried by the Core.
DocusaurusAdapter.get_link_scheme_bypasses():
def get_link_scheme_bypasses(self) -> frozenset[str]:
# Docusaurus uses pathname:/// links to reference static/ files that
# bypass the React router. The leading / is a URI convention artifact,
# not a server-absolute path — suppress Z105 for these links.
return frozenset({"pathname"})
StandaloneAdapter.get_link_scheme_bypasses():
def get_link_scheme_bypasses(self) -> frozenset[str]:
"""Standalone projects have no engine-specific link-scheme bypass."""
return frozenset()
When a link like pathname:///assets/brand.html is encountered in a Docusaurus
project, the Core checks adapter.get_link_scheme_bypasses(). It finds
"pathname" in the returned set and skips the Z105 (absolute path) check.
In a Standalone project, the same link triggers Z105 — correctly, because
pathname:/// is a Docusaurus-specific escape hatch with no meaning in a generic
Markdown project.