generated from coulomb/repo-seed
feat(WARDEN-WP-0015): T2 — machine-readable posture descriptors + warden policy
Adds registry/policy/security-posture.yaml (Axis A env postures, Axis B maturity levels M0-M3, dataclass_floor, lattice rule — no secret material) and src/warden/posture.py: typed loader with validation (unique/contiguous ranks, floor references known levels) and the pure can_deliver() lattice helper (no-write-down: prod posture + workload maturity >= secret required_maturity + dataclass floor). New `warden policy list|show` read-only lookup mirroring `warden route`. tests/test_posture.py covers load, the allow/deny lattice matrix, validation rejections, and CLI. 184 passed, lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,11 @@ route_app = typer.Typer(
|
||||
no_args_is_help=True,
|
||||
)
|
||||
app.add_typer(route_app, name="route")
|
||||
policy_app = typer.Typer(
|
||||
help="Look up Workload Security Posture descriptors (read-only; env posture + maturity)",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
app.add_typer(policy_app, name="policy")
|
||||
|
||||
console = Console()
|
||||
err = Console(stderr=True)
|
||||
@@ -979,3 +984,81 @@ def access(
|
||||
f"Obtain it from [bold]{entry.owner_repo}[/bold] as shown — "
|
||||
"warden advises, the owner vends."
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# warden policy — read-only Workload Security Posture lookup (WP-0015 T2)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _load_posture():
|
||||
from warden.posture import PostureError, load_posture
|
||||
try:
|
||||
return load_posture()
|
||||
except PostureError as e:
|
||||
err.print(f"[red]Posture descriptor error:[/red] {e}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@policy_app.command("list")
|
||||
def policy_list(
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
) -> None:
|
||||
"""List both posture axes: environment postures and workload maturity levels."""
|
||||
cat = _load_posture()
|
||||
if output_json:
|
||||
print(json.dumps({
|
||||
"env_postures": [vars(e) for e in cat.env_postures],
|
||||
"maturity_levels": [vars(m) for m in cat.maturity_levels],
|
||||
"dataclass_floor": cat.dataclass_floor,
|
||||
"requires_env_posture": cat.requires_env_posture,
|
||||
}, indent=2))
|
||||
return
|
||||
|
||||
env_table = Table(title="Axis A — environment posture")
|
||||
for col in ("ID", "rank", "backend", "real values", "user data", "audit"):
|
||||
env_table.add_column(col)
|
||||
for e in sorted(cat.env_postures, key=lambda x: x.rank):
|
||||
env_table.add_row(e.id, str(e.rank), e.backend, e.real_values, e.real_user_data, e.audit)
|
||||
console.print(env_table)
|
||||
|
||||
mat_table = Table(title="Axis B — workload maturity")
|
||||
for col in ("ID", "rank", "phase", "max dataclass", "promotion gate"):
|
||||
mat_table.add_column(col)
|
||||
for m in sorted(cat.maturity_levels, key=lambda x: x.rank):
|
||||
mat_table.add_row(m.id, str(m.rank), m.phase, m.max_dataclass, ", ".join(m.promotion_gate) or "—")
|
||||
console.print(mat_table)
|
||||
console.print(
|
||||
f"\n[dim]lattice: deliver iff env=={cat.requires_env_posture} and "
|
||||
"workload.maturity >= secret.required_maturity (and the dataclass floor).[/dim]"
|
||||
)
|
||||
|
||||
|
||||
@policy_app.command("show")
|
||||
def policy_show(
|
||||
descriptor_id: Annotated[str, typer.Argument(help="An env posture (dev/test/prod) or maturity level (M0–M3)")],
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
) -> None:
|
||||
"""Show one environment posture or maturity level."""
|
||||
cat = _load_posture()
|
||||
env = cat.env(descriptor_id)
|
||||
mat = cat.maturity(descriptor_id)
|
||||
if env is None and mat is None:
|
||||
err.print(
|
||||
f"[red]Unknown descriptor {descriptor_id!r}.[/red] "
|
||||
"Try `warden policy list`."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
obj = env or mat
|
||||
if output_json:
|
||||
print(json.dumps({"axis": "env_posture" if env else "maturity_level", **vars(obj)}, indent=2))
|
||||
return
|
||||
axis = "environment posture" if env else "workload maturity level"
|
||||
console.print(f"[bold]{obj.id}[/bold] ([cyan]{axis}[/cyan])")
|
||||
for k, v in vars(obj).items():
|
||||
if k == "id":
|
||||
continue
|
||||
console.print(f" {k:14}: {', '.join(v) if isinstance(v, list) else v}")
|
||||
if mat:
|
||||
floor = [dc for dc, lvl in cat.dataclass_floor.items() if lvl == mat.id]
|
||||
if floor:
|
||||
console.print(f" {'dataclass floor':14}: {', '.join(floor)} require this level")
|
||||
|
||||
Reference in New Issue
Block a user