WARDEN-WP-0029: implement plan front door, org posture, desk, freshness
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

Ship posture-aware access planning: organization_posture=build (axis C),
catalog freshness warnings, warden plan verdicts, localhost founder desk,
and playbook/agent guidance that retire /tmp file-drop patterns.

Compose route catalog + handoff rather than a second routing layer.
This commit is contained in:
tegwick 2026-07-18 16:59:37 +02:00
parent 5c6b71b83b
commit 5149946a4c
18 changed files with 1690 additions and 102 deletions

View file

@ -365,6 +365,16 @@ def scorecard(
status_str = "[green]PASS[/green]" if r.passed else "[red]FAIL[/red]"
table.add_row(r.name, status_str, r.detail)
console.print(table)
# Always surface org posture in human scorecard (WP-0029 T02)
try:
from warden.posture import load_posture
org = load_posture().organization_posture
console.print(
f"\n[dim]organization_posture:[/dim] [bold]{org.id}[/bold] — {org.summary[:160]}"
)
except Exception: # noqa: BLE001
pass
console.print(
f"\nScore: {passed}/{total} "
+ ("[green]Operational[/green]" if passed == total else "[yellow]Needs attention[/yellow]")
@ -718,7 +728,11 @@ def route_list(
t = tag.lower()
entries = [e for e in entries if t in [k.lower() for k in e.need_keywords]]
freshness = catalog.freshness(stale_threshold_days=stale_days)
if output_json:
# Stable array of entries for agents. Freshness lives on human output +
# `warden plan --json` (`catalog` field); avoid breaking list parsers.
payload = []
for e in entries:
row = _entry_summary(e)
@ -729,6 +743,16 @@ def route_list(
print(json.dumps(payload, indent=2))
return
# Human path: always show catalog freshness (WP-0029 T05)
console.print(
f"[dim]catalog[/dim] source={freshness.source} "
f"hash={freshness.content_hash} "
f"reviewed={freshness.newest_reviewed or ''} "
f"entries={freshness.active_count}/{freshness.entry_count}"
)
for w in freshness.warnings:
console.print(f"[yellow]catalog warning:[/yellow] {w}")
if not entries:
if stale_only:
console.print(f"No stale routing entries (threshold: {stale_days} days since reviewed).")
@ -1414,14 +1438,16 @@ def _load_posture():
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."""
"""List posture axes: env, maturity, and organization lifecycle (WP-0029)."""
cat = _load_posture()
org = cat.organization_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,
"organization_posture": vars(org),
}, indent=2))
return
@ -1438,6 +1464,19 @@ def policy_list(
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)
org_table = Table(title="Axis C — organization lifecycle posture (WP-0029)")
org_table.add_column("ID")
org_table.add_column("Summary")
org_table.add_column("Relaxations")
org_table.add_column("Graduation triggers")
org_table.add_row(
org.id,
org.summary[:80] + ("" if len(org.summary) > 80 else ""),
", ".join(org.relaxations) or "",
", ".join(org.graduation_triggers) or "",
)
console.print(org_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]"
@ -1446,19 +1485,39 @@ def policy_list(
@policy_app.command("show")
def policy_show(
descriptor_id: Annotated[str, typer.Argument(help="An env posture (dev/test/prod) or maturity level (M0M3)")],
descriptor_id: Annotated[
str,
typer.Argument(
help="Env posture (dev/test/prod), maturity (M0M3), or organization posture id (build)"
),
],
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
) -> None:
"""Show one environment posture or maturity level."""
"""Show one environment posture, maturity level, or organization posture."""
cat = _load_posture()
env = cat.env(descriptor_id)
mat = cat.maturity(descriptor_id)
if env is None and mat is None:
org = cat.organization_posture if cat.organization_posture.id == descriptor_id else None
# Alias: `organization` always shows axis C
if descriptor_id in ("organization", "organization_posture", "org"):
org = cat.organization_posture
if env is None and mat is None and org is None:
err.print(
f"[red]Unknown descriptor {descriptor_id!r}.[/red] "
"Try `warden policy list`."
)
raise typer.Exit(1)
if org is not None and env is None and mat is None:
if output_json:
print(json.dumps({"axis": "organization_posture", **vars(org)}, indent=2))
return
console.print(f"[bold]{org.id}[/bold] ([cyan]organization lifecycle posture[/cyan])")
console.print(f" {'summary':14}: {org.summary}")
console.print(f" {'relaxations':14}: {', '.join(org.relaxations) or ''}")
console.print(
f" {'graduation':14}: {', '.join(org.graduation_triggers) or ''}"
)
return
obj = env or mat
if output_json:
print(json.dumps({"axis": "env_posture" if env else "maturity_level", **vars(obj)}, indent=2))
@ -1475,6 +1534,259 @@ def policy_show(
console.print(f" {'dataclass floor':14}: {', '.join(floor)} require this level")
# ---------------------------------------------------------------------------
# warden plan — policy decision front door (WARDEN-WP-0029 T01)
# ---------------------------------------------------------------------------
@app.command("plan")
def plan_cmd(
need: Annotated[str, typer.Argument(help="Free-text credential / access need")],
actor: Annotated[
Optional[str],
typer.Option("--actor", help="Optional actor id for audit context (e.g. agt-...)"),
] = None,
domain: Annotated[
Optional[str],
typer.Option("--domain", help="Optional domain substitution for path templates"),
] = None,
output_json: Annotated[bool, typer.Option("--json", help="Machine-readable plan")] = False,
include_draft: Annotated[
bool, typer.Option("--all", help="Include draft catalog lanes in matching")
] = False,
) -> None:
"""Policy decision front door: autonomous / founder_required / unroutable.
Composes the routing catalog + access handoff + organization posture. Never
holds secret values. Agents must call this before drafting founder credential steps.
"""
from warden.plan import build_plan
access_plan = build_plan(
need, actor=actor, domain=domain, include_draft=include_draft
)
# Metadata-only audit
try:
cfg = _load_cfg()
from warden.audit import record_event
record_event(
cfg.state_dir,
kind="plan",
action="plan",
subject=actor or "",
target=access_plan.lane_id or "",
outcome=access_plan.verdict,
need=need[:200],
organization_posture=access_plan.organization_posture,
lane_id=access_plan.lane_id or "",
)
except Exception: # noqa: BLE001 — audit must never block plan
pass
_record_memory_episode(
command="plan",
outcome=access_plan.verdict,
need=need,
route_id=access_plan.lane_id or "",
)
if output_json:
print(json.dumps(access_plan.to_dict(), indent=2))
return
verdict_style = {
"autonomous": "[green]autonomous[/green]",
"founder_required": "[yellow]founder_required[/yellow]",
"unroutable": "[red]unroutable[/red]",
}.get(access_plan.verdict, access_plan.verdict)
console.print(f"[bold]verdict[/bold]: {verdict_style}")
console.print(f"[bold]need[/bold]: {access_plan.need}")
console.print(
f"[bold]posture[/bold]: [cyan]{access_plan.organization_posture}[/cyan] "
f"policy_gate={access_plan.policy_gate}"
)
if access_plan.lane_id:
console.print(
f"[bold]lane[/bold]: {access_plan.lane_id}{access_plan.lane_title or ''}"
)
for reason in access_plan.reasons:
console.print(f"[dim]reason:[/dim] {reason}")
if access_plan.commands:
console.print("\n[bold]commands[/bold]")
for c in access_plan.commands:
console.print(f" {c}")
if access_plan.founder_act:
fa = access_plan.founder_act
console.print("\n[bold]founder act[/bold]")
console.print(f" kind: {fa.kind}")
console.print(f" summary: {fa.summary}")
for k, v in fa.details.items():
console.print(f" {k}: {v}")
console.print(
"\n[dim]Open the act surface:[/dim] "
f"warden desk --act {fa.kind}"
+ (f" --lane {access_plan.lane_id}" if access_plan.lane_id else "")
)
if access_plan.ccr_stub:
console.print("\n[bold]CCR stub[/bold] (unroutable — propose a lane)")
console.print(f" {access_plan.ccr_stub.get('title')}")
for step in access_plan.ccr_stub.get("steps") or []:
console.print(f" - {step}")
cat = access_plan.catalog
if cat:
console.print(
f"\n[dim]catalog source={cat.get('source')} hash={cat.get('content_hash')} "
f"bundled={cat.get('using_bundled')}[/dim]"
)
for w in cat.get("warnings") or []:
console.print(f"[yellow]catalog warning:[/yellow] {w}")
# ---------------------------------------------------------------------------
# warden desk — founder interaction surface (WARDEN-WP-0029 T03)
# ---------------------------------------------------------------------------
@app.command("desk")
def desk_cmd(
act: Annotated[
Optional[str],
typer.Option("--act", help="Founder act: approve | oidc_login | paste_once_provision"),
] = None,
summary: Annotated[
Optional[str],
typer.Option("--summary", help="Plain-language description of the act"),
] = None,
lane: Annotated[
Optional[str],
typer.Option("--lane", help="Catalog lane id for context"),
] = None,
path: Annotated[
Optional[str],
typer.Option("--path", help="Concrete OpenBao path (paste_once_provision)"),
] = None,
field: Annotated[
str,
typer.Option("--field", help="KV field name for paste_once_provision"),
] = "value",
oidc_command: Annotated[
Optional[str],
typer.Option("--oidc-command", help="Login command to display (oidc_login)"),
] = None,
plan_json: Annotated[
Optional[Path],
typer.Option("--plan-json", help="Path to warden plan --json output"),
] = None,
port: Annotated[
int,
typer.Option("--port", help="Port (0 = ephemeral)"),
] = 0,
no_browser: Annotated[
bool,
typer.Option("--no-browser", help="Do not open a browser"),
] = False,
dry_run: Annotated[
bool,
typer.Option("--dry-run", help="Do not call bao; simulate paste-once write"),
] = False,
) -> None:
"""Localhost founder interaction surface (build-phase: OS session trust).
Serves a short-lived page on 127.0.0.1 for approve / OIDC confirm / paste-once
provision into OpenBao. Secret values never appear in audit or CLI history.
"""
from warden.desk import (
DeskError,
load_plan_json,
new_session,
run_desk,
session_from_plan_dict,
)
try:
if plan_json is not None:
session = session_from_plan_dict(load_plan_json(plan_json))
# Allow CLI overrides on top of plan
if path:
session.path = path
if field:
session.kv_field = field
else:
if not act:
err.print(
"[red]desk requires --act or --plan-json.[/red] "
"Example: warden desk --act approve --summary 'enable policy'"
)
raise typer.Exit(2)
session = new_session(
act=act,
summary=summary or act,
lane_id=lane or "",
path=path or "",
kv_field=field,
oidc_command=oidc_command or "",
)
except DeskError as e:
err.print(f"[red]desk error:[/red] {e}")
raise typer.Exit(1)
try:
cfg = _load_cfg()
from warden.audit import record_event
record_event(
cfg.state_dir,
kind="desk",
action="desk_open",
subject="",
target=session.lane_id or session.act,
outcome="open",
act=session.act,
lane_id=session.lane_id,
)
except Exception: # noqa: BLE001
cfg = None
try:
finished = run_desk(
session,
port=port,
open_browser=not no_browser,
dry_run=dry_run,
)
except DeskError as e:
err.print(f"[red]desk error:[/red] {e}")
raise typer.Exit(1)
if cfg is not None:
try:
from warden.audit import record_event
record_event(
cfg.state_dir,
kind="desk",
action="desk_close",
subject="",
target=finished.lane_id or finished.act,
outcome=finished.result,
act=finished.act,
lane_id=finished.lane_id,
# message is metadata-only by construction for approve/login;
# for provision it must not include the secret (desk never puts it there)
detail=finished.message[:200] if finished.message else "",
)
except Exception: # noqa: BLE001
pass
console.print(
f"[bold]desk result:[/bold] {finished.result}"
+ (f"{finished.message}" if finished.message else "")
)
if finished.result in ("denied", "error"):
raise typer.Exit(1)
# ---------------------------------------------------------------------------
# warden worker — autonomous coordination worker (WP-0020 T1: dry-run scaffold)
# ---------------------------------------------------------------------------