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,115 @@
"""Decision integration.
Privileged actions require an approved decision (or approved CCR) unless the lane
is explicitly `bootstrap-only` or the caller passes --dry-run. Decisions are
looked up from State Hub by id; when the hub has no record yet (common during the
pilot) a local approval fixture under `.decisions/<ref>.yaml` can stand in, so the
end-to-end chain is testable before the canonical hub decision object exists.
No secret values are ever read from or written to a decision.
"""
from __future__ import annotations
import json
import urllib.error
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
from secrets_engine.catalog import CatalogEntry
from secrets_engine.errors import DecisionError
APPROVED_STATUSES = {"resolved", "approved", "accepted"}
@dataclass
class Decision:
id: str
title: str
status: str
superseded_by: str | None
source: str # "hub" | "local-fixture"
review_url: str = ""
raw: dict[str, Any] | None = None
def is_approved(self) -> bool:
return self.status.lower() in APPROVED_STATUSES and not self.superseded_by
def _hub_get(hub_url: str, decision_id: str) -> dict[str, Any] | None:
url = hub_url.rstrip("/") + f"/decisions/{decision_id}"
try:
with urllib.request.urlopen(url, timeout=3) as resp:
return json.loads(resp.read())
except (urllib.error.URLError, OSError, ValueError):
return None
def _local_fixture(repo_root: Path, ref: str) -> dict[str, Any] | None:
path = repo_root / ".decisions" / f"{ref}.yaml"
if not path.exists():
return None
try:
return yaml.safe_load(path.read_text(encoding="utf-8")) or None
except yaml.YAMLError as e:
raise DecisionError(f"{path}: invalid decision fixture: {e}") from e
def resolve_decision(
*,
hub_url: str,
repo_root: Path,
decision_ref: str,
) -> Decision:
"""Resolve a decision by id (hub) or slug (local fixture). Raises if absent."""
if not decision_ref:
raise DecisionError("no decision reference provided")
doc = _hub_get(hub_url, decision_ref)
if doc:
return Decision(
id=doc.get("id", decision_ref),
title=doc.get("title", ""),
status=doc.get("status", "unknown"),
superseded_by=doc.get("superseded_by"),
source="hub",
review_url=f"{hub_url.rstrip('/')}/decisions/{doc.get('id', decision_ref)}",
raw=doc,
)
fixture = _local_fixture(repo_root, decision_ref)
if fixture:
return Decision(
id=fixture.get("id", decision_ref),
title=fixture.get("title", decision_ref),
status=fixture.get("status", "unknown"),
superseded_by=fixture.get("superseded_by"),
source="local-fixture",
review_url=fixture.get("review_url", ""),
raw=fixture,
)
raise DecisionError(
f"decision '{decision_ref}' not found in State Hub or local fixtures"
)
def require_approved(entry: CatalogEntry, decision: Decision | None) -> None:
"""Enforce the lane's approval model. Raises DecisionError if not satisfied."""
if not entry.approval_required():
return # bootstrap-only lane
if decision is None:
raise DecisionError(
f"lane '{entry.id}' requires an approved decision; none resolved"
)
if decision.superseded_by:
raise DecisionError(
f"decision '{decision.id}' is superseded by '{decision.superseded_by}'"
)
if not decision.is_approved():
raise DecisionError(
f"decision '{decision.id}' is not approved (status='{decision.status}')"
)