generated from coulomb/repo-seed
Some checks failed
validate-registry / validate (push) Has been cancelled
Activate WP-0003 and WP-0004. Add tools/effective_config.py (deterministic, order-independent override-path resolver — path only, never a value) and tools/config_explain.py + `make explain` to render the layer path, winning layer, validator, owner, consumers, and secret references for any surface.*. Verified on all 4 seeded surfaces; order-independent; no values/secrets leak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
162 lines
6.0 KiB
Python
162 lines
6.0 KiB
Python
#!/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)")
|