#!/usr/bin/env python3 """Effective-config path resolver (ATLAS-WP-0004-T01). Given a configuration-surface entry, compute the *override path*: the ordered contributing layers, which layer wins, what each overrode, the validating schema, and the owner. This renders the PATH only -- never a resolved or live value, and never a secret value (config-atlas stores the map, not the value; PRD FR-5). The resolver is deterministic and order-independent: the result depends only on each source's layer position in the canonical L0-L9 ordering, not on the order sources happen to appear in the entry. """ from __future__ import annotations import re from dataclasses import dataclass, field from pathlib import Path try: import yaml except ImportError as exc: # pragma: no cover raise SystemExit(f"setup error: missing PyYAML ({exc}). pip install pyyaml") # Canonical L0-L9 ordering (must match schemas/surface-entry.schema.json $defs/layer). LAYER_ORDER = [ "product-default", "company", "platform", "environment", "region", "installation", "tenant", "group", "user", "agent", "emergency", ] LAYER_INDEX = {name: i for i, name in enumerate(LAYER_ORDER)} ROOT = Path(__file__).resolve().parent.parent SURFACES_DIR = ROOT / "registry" / "surfaces" FRONTMATTER = re.compile(r"^---\n(.*?)\n---\n", re.S) # Roles that contribute no orderable layer (linked authority, not an overlay). NON_LAYER_ROLES = {"feature-control-key"} @dataclass class Contribution: """One source's contribution to the override path.""" role: str layer: str | None # canonical layer, or None for non-layer roles rank: int # position in LAYER_ORDER, or -1 ref: str # repo:path / endpoint reference (never a value) overrides: str | None = None # the lower layer this one overrides, if any winning: bool = False @dataclass class OverridePath: surface_id: str name: str kind: str owner: str mutability: str security_class: str default_layer: str | None allowed_layers: list[str] validator: str | None contributions: list[Contribution] = field(default_factory=list) consumers: list[str] = field(default_factory=list) secret_deps: list[str] = field(default_factory=list) related: list[str] = field(default_factory=list) notes: list[str] = field(default_factory=list) @property def winner(self) -> Contribution | None: return next((c for c in self.contributions if c.winning), None) def _layer_for_role(role: str) -> str | None: """Map a source role (e.g. 'environment-overlay') to a canonical layer. Strategy: longest canonical layer name that the role starts with or contains. Non-layer roles (feature-control-key) return None. """ if role in NON_LAYER_ROLES: return None for layer in sorted(LAYER_ORDER, key=len, reverse=True): if role == layer or role.startswith(layer + "-") or layer in role.split("-"): return layer return None def load_entry(path: Path) -> dict: m = FRONTMATTER.match(path.read_text()) if not m: raise ValueError(f"{path}: missing YAML frontmatter") data = yaml.safe_load(m.group(1)) if not isinstance(data, dict): raise ValueError(f"{path}: frontmatter is not a mapping") return data def find_entry(surface_id: str) -> dict: path = SURFACES_DIR / f"{surface_id}.md" if not path.exists(): raise FileNotFoundError(f"no surface entry for id '{surface_id}' at {path}") return load_entry(path) def resolve_path(entry: dict) -> OverridePath: """Compute the override path for a surface entry. Pure / deterministic.""" scope = entry.get("scope", {}) or {} schema = entry.get("schema", {}) or {} relations = entry.get("relations", {}) or {} op = OverridePath( surface_id=entry["id"], name=entry.get("name", entry["id"]), kind=entry.get("kind", "?"), owner=entry.get("owner", "?"), mutability=entry.get("mutability", "?"), security_class=entry.get("security_class", "?"), default_layer=scope.get("default_layer"), allowed_layers=list(scope.get("allowed_layers", [])), validator=schema.get("validator"), consumers=list(relations.get("consumed_by", [])), secret_deps=list(relations.get("depends_on_secret", [])), related=list(relations.get("related_to", [])), ) contribs: list[Contribution] = [] for src in entry.get("sources", []) or []: role = src.get("role", "?") ref = src.get("repo", "") + (":" if src.get("repo") and src.get("path") else "") + (src.get("path") or src.get("endpoint") or "") layer = _layer_for_role(role) rank = LAYER_INDEX.get(layer, -1) if layer else -1 contribs.append(Contribution(role=role, layer=layer, rank=rank, ref=ref or role)) # Deterministic, order-independent: sort by layer rank, then ref for ties. layered = sorted([c for c in contribs if c.rank >= 0], key=lambda c: (c.rank, c.ref)) non_layered = [c for c in contribs if c.rank < 0] # Each higher layer overrides the previous lower one; the last (most specific) wins. for i, c in enumerate(layered): if i > 0: c.overrides = layered[i - 1].layer if layered: layered[-1].winning = True op.contributions = layered + non_layered if non_layered: op.notes.append( "linked authority (no overlay ordering): " + ", ".join(f"{c.role} -> {c.ref}" for c in non_layered) ) if op.security_class == "secret-ref" or op.secret_deps: op.notes.append("secret values are referenced only, never resolved here") op.notes.append("no values shown — config-atlas maps the surface, not the value") return op if __name__ == "__main__": # pragma: no cover - smoke check import sys sid = sys.argv[1] if len(sys.argv) > 1 else "surface.infotech.state-hub.api-config" p = resolve_path(find_entry(sid)) w = p.winner print(p.surface_id, "->", "winning:", w.layer if w else "(none)")