Passa al contenuto principale

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.


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.