generated from coulomb/repo-seed
feat(explain): implement ATLAS-WP-0004 T01+T02 — effective-config resolver + config explain
Some checks failed
validate-registry / validate (push) Has been cancelled
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>
This commit is contained in:
@@ -20,6 +20,9 @@ python3 tools/validate_registry.py # schema + index consistency only
|
||||
git diff --check # whitespace / conflict markers
|
||||
reuse-surface validate --root . # capability federation (from reuse-surface checkout)
|
||||
|
||||
# Explain a surface's effective-config override path (no values shown)
|
||||
make explain SURFACE=surface.infotech.state-hub.api-config
|
||||
|
||||
# After workplan or registry edits — from ~/state-hub
|
||||
make fix-consistency REPO=config-atlas
|
||||
```
|
||||
|
||||
9
Makefile
9
Makefile
@@ -1,7 +1,12 @@
|
||||
# config-atlas — registry validation gate (ATLAS-WP-0002-T06)
|
||||
# Markdown-first repo: no build/run, only validation.
|
||||
# Markdown-first repo: no build/run, only validation and the explain tool.
|
||||
|
||||
.PHONY: validate validate-schema validate-reuse validate-whitespace
|
||||
.PHONY: validate validate-schema validate-reuse validate-whitespace explain
|
||||
|
||||
# Effective-config override path for a surface (ATLAS-WP-0004).
|
||||
# make explain SURFACE=surface.infotech.state-hub.api-config
|
||||
explain:
|
||||
@python3 tools/config_explain.py $(SURFACE)
|
||||
|
||||
# Full gate run by agents and CI.
|
||||
validate: validate-schema validate-whitespace validate-reuse
|
||||
|
||||
65
tools/config_explain.py
Normal file
65
tools/config_explain.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""`config explain <surface-id>` (ATLAS-WP-0004-T02).
|
||||
|
||||
Render the effective-config override PATH for a configuration surface as a
|
||||
human/agent-readable view. Shows which layer wins, what it overrode, the
|
||||
validating schema, owner, and consumers -- never a resolved or secret value.
|
||||
|
||||
Usage:
|
||||
python3 tools/config_explain.py surface.infotech.state-hub.api-config
|
||||
make explain SURFACE=surface.infotech.state-hub.api-config
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from effective_config import OverridePath, find_entry, resolve_path
|
||||
|
||||
|
||||
def render(op: OverridePath) -> str:
|
||||
lines: list[str] = []
|
||||
lines.append(f"config explain {op.surface_id}")
|
||||
lines.append("")
|
||||
lines.append(f" {op.name}")
|
||||
lines.append(f" kind: {op.kind} owner: {op.owner} mutability: {op.mutability} class: {op.security_class}")
|
||||
if op.allowed_layers:
|
||||
lines.append(f" allowed layers: {', '.join(op.allowed_layers)} default: {op.default_layer}")
|
||||
lines.append("")
|
||||
lines.append(" effective layer path (most-specific wins):")
|
||||
if not op.contributions:
|
||||
lines.append(" (no sources declared)")
|
||||
for c in op.contributions:
|
||||
layer = c.layer if c.layer else f"[{c.role}]"
|
||||
marker = " <== winning" if c.winning else ""
|
||||
over = f" (overrides {c.overrides})" if c.overrides else ""
|
||||
lines.append(f" {layer:<16} {c.ref}{over}{marker}")
|
||||
lines.append("")
|
||||
if op.validator:
|
||||
lines.append(f" validated by: {op.validator}")
|
||||
if op.consumers:
|
||||
lines.append(f" consumed by: {', '.join(op.consumers)}")
|
||||
if op.secret_deps:
|
||||
lines.append(f" depends on secret (ref): {', '.join(op.secret_deps)}")
|
||||
if op.related:
|
||||
lines.append(f" related: {', '.join(op.related)}")
|
||||
for note in op.notes:
|
||||
lines.append(f" · {note}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if len(argv) != 1:
|
||||
print(__doc__)
|
||||
return 2
|
||||
surface_id = argv[0]
|
||||
try:
|
||||
entry = find_entry(surface_id)
|
||||
except FileNotFoundError as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
print(render(resolve_path(entry)))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
161
tools/effective_config.py
Normal file
161
tools/effective_config.py
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/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)")
|
||||
@@ -4,11 +4,11 @@ type: workplan
|
||||
title: "Discovery connectors"
|
||||
domain: infotech
|
||||
repo: config-atlas
|
||||
status: ready
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: custodian
|
||||
created: "2026-06-26"
|
||||
updated: "2026-06-26"
|
||||
updated: "2026-06-27"
|
||||
state_hub_workstream_id: "e4400d9c-021a-4e44-8e9b-719f94a9561a"
|
||||
---
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ type: workplan
|
||||
title: "Effective-config explain and graph"
|
||||
domain: infotech
|
||||
repo: config-atlas
|
||||
status: ready
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: custodian
|
||||
created: "2026-06-26"
|
||||
updated: "2026-06-26"
|
||||
updated: "2026-06-27"
|
||||
state_hub_workstream_id: "fbfdbf2b-ca6b-450e-a654-a61c5939f068"
|
||||
---
|
||||
|
||||
@@ -44,11 +44,18 @@ T04 (blast-radius). T01 and T03 may start in parallel.
|
||||
|
||||
```task
|
||||
id: ATLAS-WP-0004-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "cee293aa-b407-4b97-a462-b67d7aa0f170"
|
||||
```
|
||||
|
||||
Result 2026-06-27: Added `tools/effective_config.py` — a pure, deterministic
|
||||
resolver that orders a surface's `sources[]` by canonical L0-L9 layer rank and
|
||||
emits the override path (contributing layers, winning layer, what each overrode,
|
||||
validator, owner). Renders the PATH only, never a value; non-layer roles
|
||||
(feature-control-key) are listed as linked authority. Verified order-independent
|
||||
(8x shuffle -> identical path).
|
||||
|
||||
Implement a static resolver that, from a surface's `sources[]` (with layer `role`),
|
||||
the L0–L9 ordering, and the explicit merge rules, produces the **override path**:
|
||||
ordered contributing layers, the winning layer, what each overrode, the validating
|
||||
@@ -63,11 +70,17 @@ the `config explain` shape in [`wiki/ConfigLayering.md`](../wiki/ConfigLayering.
|
||||
|
||||
```task
|
||||
id: ATLAS-WP-0004-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5d66a8c1-74bd-4c6f-9ea6-839e884ad103"
|
||||
```
|
||||
|
||||
Result 2026-06-27: Added `tools/config_explain.py` and `make explain
|
||||
SURFACE=...`. Renders the resolver output (layer path, overrides, winning layer,
|
||||
validator, owner, consumers, secret refs) for any `surface.*` id. Verified on all
|
||||
4 seeded surfaces; no values or secret values appear in output. Documented in
|
||||
`.claude/rules/stack-and-commands.md`.
|
||||
|
||||
Add a `tools/` command (e.g. `config_explain.py`) that takes a `surface.*` id and
|
||||
renders the resolver output as the human/agent-readable `config explain` view.
|
||||
Reuse the existing `tools/` + Makefile pattern (`make explain SURFACE=...`).
|
||||
|
||||
Reference in New Issue
Block a user