operator-plane: headless smoke + effective-config panel + ledger + unpin ruff
Step 6: tests/smoke_dashboard_headless.py renders the real page in a headless browser and fails on any JS console/pageerror (standalone script, not pytest-collected; CI non-blocking first). admin_api gains GET /api/config/effective (per-key masked value + source: overrides.env vs compose, missing .env.example keys flagged as drift) + an admin.html table (values XSS-escaped). dashboard.html gains the Experiment Ledger panel reading the plan-006 verdicts from the snapshot (empty-state until the tables exist). Removes the last CI ruff per-file pin (api/main.py:F821) now that os/hmac are imported; scoped ruff is green unpinned. Marks plan 007 DONE in plans/README.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
21687c97ff
commit
e2b3c1ceae
7 files changed
+244
-6
No files matched your search
@@ -86,6 +86,56 @@ def _editable(key: str) -> bool:
|
||||
and not any(m in key for m in SECRET_MARKERS))
|
||||
|
||||
|
||||
def _mask_value(key: str, value: str) -> str:
|
||||
"""Mask secret VALUES (*** + last 4). Uses the broader SECRET_MARKERS set so
|
||||
DATABASE_URL / *_WEBHOOK_URL are masked too — narrowing to KEY|SECRET|TOKEN|
|
||||
PASSWORD would leak the DB password in the config table."""
|
||||
if any(m in key.upper() for m in SECRET_MARKERS):
|
||||
tail = value[-4:] if value and len(value) >= 4 else ""
|
||||
return "***" + tail
|
||||
return value
|
||||
|
||||
|
||||
def _env_example_keys() -> set[str]:
|
||||
"""Keys documented in .env.example — the 'required' config surface used for
|
||||
drift detection (a documented key absent from the effective env is drift)."""
|
||||
path = os.path.join(os.path.dirname(__file__), ".env.example")
|
||||
keys: set[str] = set()
|
||||
try:
|
||||
with open(path) as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
keys.add(line.split("=", 1)[0].strip())
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return keys
|
||||
|
||||
|
||||
def effective_config() -> dict:
|
||||
"""Per-key effective value + source for the admin config table.
|
||||
|
||||
Merge order baked < compose < overrides.env (< learned). Sources we can prove
|
||||
at runtime: ``overrides.env`` (from data/overrides.env) vs ``compose`` (the
|
||||
process env — baked and compose are indistinguishable inside the container, so
|
||||
both surface as ``compose``); a documented .env.example key that is absent
|
||||
surfaces as ``missing`` (drift). Secret values are masked."""
|
||||
overrides = env_overrides.read_overrides()
|
||||
example = _env_example_keys()
|
||||
keys = set(example) | set(overrides) | {k for k in os.environ if _editable(k)}
|
||||
out: dict[str, dict] = {}
|
||||
for key in sorted(keys):
|
||||
value = os.environ.get(key)
|
||||
if value is None:
|
||||
out[key] = {"value_masked": None,
|
||||
"source": "missing" if key in example else "unset",
|
||||
"drift": key in example}
|
||||
continue
|
||||
source = "overrides.env" if key in overrides else "compose"
|
||||
out[key] = {"value_masked": _mask_value(key, str(value)), "source": source}
|
||||
return out
|
||||
|
||||
|
||||
# ---------- docker over the unix socket (stdlib only) ----------
|
||||
class _SockConn(http.client.HTTPConnection):
|
||||
def __init__(self):
|
||||
@@ -230,6 +280,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"overrides": _safe_overrides()})
|
||||
if path == "/api/profiles":
|
||||
return self._send(200, profiles())
|
||||
if path == "/api/config/effective":
|
||||
return self._send(200, {"config": effective_config()})
|
||||
return self._send(404, {"error": "unknown route"})
|
||||
|
||||
def do_POST(self): # noqa: N802
|
||||
|
||||
Reference in new issue
Block a user