Skip to main content

ADR 009: Path Sovereignty — Configuration Follows the Target

Status: Active Decider: Architecture Lead Date: 2026-04-12 (v0.7.0 sprint, CEO-052)


Context

find_repo_root() originally searched upward from os.getcwd() — the invoking shell's current working directory. This worked correctly for the standard case where the user runs Zenzic from inside the repository they want to analyse.

It failed for any scenario where the caller's working directory differed from the target repository:

# CWD = /home/user/my-tools
# Target = /home/user/another-project/docs
zenzic check all /home/user/another-project/docs

In this case, find_repo_root() would walk upward from /home/user/my-tools, find that repository's zenzic.toml, and load that repository's configuration — including its engine, docs_dir, excluded_dirs, and custom rules. The analysis target was another-project, but the configuration applied was from my-tools. This is Context Hijacking.


Decision

"The configuration follows the target, not the caller."

When an explicit PATH argument is provided to any filesystem-interacting CLI command, find_repo_root() is called with search_from=target_path — walking upward from the target, not the CWD:

# core/scanner.py
def find_repo_root(
search_from: Path | None = None,
fallback_to_cwd: bool = False,
) -> Path:
start = search_from or Path.cwd()
for parent in [start, *start.parents]:
if (parent / ".git").exists() or (parent / "zenzic.toml").exists():
return parent
if fallback_to_cwd:
return Path.cwd()
raise RuntimeError(...)

_apply_target() in cli/_check.py orchestrates the recalibration: after deriving docs_root from the user-provided target, it calls find_repo_root(search_from=docs_root) to load the correct zenzic.toml, then re-derives docs_dir from the target repo's configuration.


The _apply_target() Invariant

When target == repo_root (the user points directly at a repo root, not a subdirectory), docs_dir is preserved from the config rather than overridden to ".". This prevents a subtle regression: a user running zenzic check all /path/to/repo should respect that repo's docs_dir = "docs" setting, not flatten it to the root.

# _apply_target() — canonical logic
if resolved_target == repo_root:
# Target IS the repo root: honour the config's docs_dir.
docs_root = repo_root / config.docs_dir
else:
# Target is a subdirectory: treat it as the docs root directly.
docs_root = resolved_target

Rationale

1. The Principle of Contextual Integrity

A configuration file belongs to the project it lives in. Loading a foreign zenzic.toml because of a coincidence of working directory is a configuration supply chain vulnerability — the analysis is secretly governed by rules the user did not intend to apply.

2. CI/CD Correctness

In CI pipelines, the working directory is often the runner's home, a workspace root, or a tool directory — not the documentation repository. Path Sovereignty ensures that zenzic check all $DOCS_PATH in CI always applies the correct project-specific rules, regardless of the runner's $PWD.

3. Symmetry with ADR-007

ADR-007 (Sovereign Sandbox) established that the perimeter follows the target. ADR-009 completes the picture: the configuration also follows the target. Together they guarantee that every aspect of an analysis — what is scanned, what rules apply, and what escapes are forbidden — is determined solely by the target repository.


Scope

Path Sovereignty applies to every CLI command that accepts an optional positional PATH argument (Rule R18 — Total CLI Symmetry):

CommandPATH semantics
zenzic check all [PATH]Sovereign root: find_repo_root(search_from=PATH)
zenzic score [PATH]Same
zenzic diff [PATH]Same; snapshot path derived from resolved repo_root
zenzic init [PATH]Genesis Nomad: PATH is the repo_root directly; created if absent
zenzic lab, zenzic inspectNo PATH argument — exempt

Consequences

  • Running Zenzic from any directory now produces identical results to running it

    from inside the target repository — no surprises for CI operators.

  • Contributors implementing new CLI commands that accept a PATH argument

    must call find_repo_root(search_from=resolved_path) and invoke _apply_target(). This is now a documented invariant in the contribution guide.

  • The fallback_to_cwd=True parameter of find_repo_root() is reserved

    exclusively for the init command (Genesis Fallback — see ADR-003). No other command may use it.