feat(mvp): working secrets-engine CLI for the whynot-design npm publish lane

Implements SECRETS-WP-0002 end to end as a uv-managed Python package:

- catalog: non-secret lane registry + strict validator (build/test/prod)
- stage roles + OpenBao ACL policies; guards refuse wildcards, sys/, identity/,
  admin names, and cross-stage paths before any backend call
- plan/apply: dry-run-first, idempotent policy + approle apply, decision-gated
- decisions: State Hub lookup with local-fixture fallback; non-secret evidence
  to JSONL + hub progress, scrubbed of any value
- provision/verify: mode-0600 file import + generated test values; positive/
  negative checks that never print the value
- exec delivery: `exec --catalog ... -- npm publish` injects the token via a
  temp .npmrc for the child only, cleaned up on exit/failure/interrupt
- ops-warden routing contract + hardening backlog docs
- 34 tests incl. live OpenBao integration; scripts/demo-e2e.sh runs the full
  chain against a throwaway bao dev server

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-06-28 12:28:45 +02:00
parent 58c24cff53
commit a852d3f1ff
47 changed files with 3743 additions and 122 deletions

View file

@ -0,0 +1,116 @@
"""Non-secret evidence writer.
Every privileged or noteworthy action emits an evidence record to a local
append-only JSONL log and, best-effort, to the State Hub progress API. Records
are scrubbed of anything that looks like a secret value before they are written.
"""
from __future__ import annotations
import json
import os
import urllib.error
import urllib.request
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from secrets_engine.redact import looks_secret, redact_text
# Keys that must never carry a value into evidence regardless of nesting.
_FORBIDDEN_VALUE_KEYS = {"value", "secret", "token", "password", "raw"}
def _scrub(obj: Any) -> Any:
"""Recursively drop secret-looking keys and redact token shapes in strings."""
if isinstance(obj, dict):
out = {}
for k, v in obj.items():
if k.lower() in _FORBIDDEN_VALUE_KEYS or looks_secret(k):
out[k] = "<omitted: non-secret evidence only>"
else:
out[k] = _scrub(v)
return out
if isinstance(obj, list):
return [_scrub(v) for v in obj]
if isinstance(obj, str):
return redact_text(obj)
return obj
@dataclass
class EvidenceWriter:
evidence_dir: Path
hub_url: str = ""
topic_id: str = ""
workstream_id: str = ""
author: str = "secrets-engine"
actor: str = field(default_factory=lambda: os.environ.get("USER", "unknown"))
def __post_init__(self) -> None:
self.evidence_dir = Path(self.evidence_dir)
def _log_path(self) -> Path:
self.evidence_dir.mkdir(parents=True, exist_ok=True)
day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
return self.evidence_dir / f"evidence-{day}.jsonl"
def record(
self,
action: str,
*,
result: str,
catalog_id: str = "",
stage: str = "",
decision_id: str = "",
detail: dict[str, Any] | None = None,
hub: bool = True,
) -> dict[str, Any]:
"""Append one non-secret evidence record. Returns the stored record."""
record = {
"ts": datetime.now(timezone.utc).isoformat(),
"action": action,
"result": result,
"actor": self.actor,
"catalog_id": catalog_id,
"stage": stage,
"decision_id": decision_id,
"detail": _scrub(detail or {}),
}
path = self._log_path()
with path.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(record, sort_keys=True) + "\n")
if hub and self.hub_url:
self._post_hub(action, result, catalog_id, stage, decision_id)
return record
def _post_hub(
self, action: str, result: str, catalog_id: str, stage: str, decision_id: str
) -> None:
"""Best-effort progress note to State Hub. Never raises; never sends values."""
if not self.topic_id:
return
summary = f"secrets-engine {action}: {result}"
if catalog_id:
summary += f" [{catalog_id}{'/' + stage if stage else ''}]"
payload: dict[str, Any] = {
"topic_id": self.topic_id,
"event_type": "note",
"summary": summary,
"author": self.author,
}
if self.workstream_id:
payload["workstream_id"] = self.workstream_id
if decision_id:
payload["detail"] = {"decision_id": decision_id, "catalog_id": catalog_id}
try:
req = urllib.request.Request(
self.hub_url.rstrip("/") + "/progress/",
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req, timeout=3).read()
except (urllib.error.URLError, OSError, ValueError):
# Hub being offline must never block secret work or leak anything.
pass