generated from coulomb/repo-seed
feat(WARDEN-WP-0014): T2 — warden access advisory front door
Adds `warden access <need> [--domain X] [--json]`: resolves a credential need against the routing catalog and renders the structured handoff (owner, auth method, path template, command skeleton, policy gate status, proxy hint). SSH lane points at `warden sign`; routed lanes end "warden advises, the owner vends". New pure warden/access.py module (expand_handoff, policy_gate_status) reused by the T3 proxy lane. JSON output is stable and secret-free. tests/test_access.py added. 157 passed, lint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
76
src/warden/access.py
Normal file
76
src/warden/access.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""Operator access assist — render structured handoff for a credential need.
|
||||
|
||||
The `warden access` front door (WP-0014) resolves a need to a `RouteEntry` and
|
||||
renders its **structured handoff**: how the caller authenticates to the owning
|
||||
subsystem, the owner-side path template, the command skeleton to run *as the
|
||||
caller*, and the policy check the fetch path gates on.
|
||||
|
||||
This module is **pure**: it expands templates and reports gate status. It never
|
||||
fetches, holds, or logs a secret value — that boundary is the whole point of the
|
||||
assist layer. Proxy execution (`--fetch`/`--exec`) lives in the CLI/T3 lane and
|
||||
reuses `expand_handoff` to build the command it runs as the caller.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from warden.config import ConfigError, load_config
|
||||
from warden.routing.models import RouteEntry
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExpandedHandoff:
|
||||
"""Handoff templates with `<domain>` substituted when a domain is supplied.
|
||||
|
||||
Remaining placeholders (`<workload>`, `<bundle>`, `<FIELD>`) are intentionally
|
||||
left for the caller/owner to fill — ops-warden does not invent owner-side names.
|
||||
"""
|
||||
|
||||
auth_method: Optional[str]
|
||||
path_template: Optional[str]
|
||||
fetch_command: Optional[str]
|
||||
policy_ref: Optional[str]
|
||||
exec_capable: bool
|
||||
|
||||
|
||||
def _sub_domain(value: Optional[str], domain: Optional[str]) -> Optional[str]:
|
||||
if value and domain:
|
||||
return value.replace("<domain>", domain)
|
||||
return value
|
||||
|
||||
|
||||
def expand_handoff(entry: RouteEntry, domain: Optional[str] = None) -> ExpandedHandoff:
|
||||
"""Expand an entry's handoff templates for display or proxy.
|
||||
|
||||
The catalog `fetch_command` may reference the literal token ``<path_template>``;
|
||||
we inline the entry's ``path_template`` so the rendered command is self-contained,
|
||||
then substitute ``<domain>`` across every field when a domain is given.
|
||||
"""
|
||||
path = entry.path_template
|
||||
fetch = entry.fetch_command
|
||||
if fetch and path and "<path_template>" in fetch:
|
||||
fetch = fetch.replace("<path_template>", path)
|
||||
|
||||
return ExpandedHandoff(
|
||||
auth_method=_sub_domain(entry.auth_method, domain),
|
||||
path_template=_sub_domain(path, domain),
|
||||
fetch_command=_sub_domain(fetch, domain),
|
||||
policy_ref=_sub_domain(entry.policy_ref, domain),
|
||||
exec_capable=entry.exec_capable,
|
||||
)
|
||||
|
||||
|
||||
def policy_gate_status() -> str:
|
||||
"""One-line description of whether the flex-auth gate is enforced for fetches.
|
||||
|
||||
Advisory output only — never raises. The proxy lane (T3) is what actually runs
|
||||
the gate before fetching; here we just report the configured posture.
|
||||
"""
|
||||
try:
|
||||
cfg = load_config()
|
||||
except ConfigError:
|
||||
return "advisory — no warden.yaml (caller identity; gate not enforced)"
|
||||
if cfg.policy.enabled:
|
||||
return f"enforced — flex-auth at {cfg.policy.flex_auth_url}"
|
||||
return "advisory — policy.enabled=false (gate ships with flex-auth deploy)"
|
||||
@@ -708,3 +708,108 @@ def route_find(
|
||||
)
|
||||
return
|
||||
_print_entry_table(matches, f"Matches for {query!r}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# warden access — operator front door (advisory; proxy lands in T3)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _access_json(entry, expanded, gate: str, domain: Optional[str]) -> dict:
|
||||
"""Stable, secret-free JSON shape for agentic operators. WP-0014 T2."""
|
||||
payload = _entry_summary(entry)
|
||||
payload["domain"] = domain
|
||||
payload["policy_gate"] = gate
|
||||
payload["handoff"] = {
|
||||
"auth_method": expanded.auth_method,
|
||||
"path_template": expanded.path_template,
|
||||
"fetch_command": expanded.fetch_command,
|
||||
"policy_ref": expanded.policy_ref,
|
||||
"exec_capable": expanded.exec_capable,
|
||||
}
|
||||
if entry.warden_executes:
|
||||
payload["next_action"] = "ops-warden issues this directly — see cert_command"
|
||||
payload["cert_command"] = entry.cert_command
|
||||
else:
|
||||
payload["next_action"] = (
|
||||
f"obtain from {entry.owner_repo} ({entry.subsystem}); "
|
||||
"ops-warden holds no value"
|
||||
)
|
||||
return payload
|
||||
|
||||
|
||||
@app.command("access")
|
||||
def access(
|
||||
need: Annotated[str, typer.Argument(help="Free-text need, e.g. 'npm token', 'db password'")],
|
||||
domain: Annotated[
|
||||
Optional[str],
|
||||
typer.Option("--domain", help="Substitute <domain> in path/auth templates, e.g. coulomb_social"),
|
||||
] = None,
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON (stable, secret-free)")] = False,
|
||||
all_entries: Annotated[bool, typer.Option("--all", help="Include draft entries")] = False,
|
||||
) -> None:
|
||||
"""Operator front door: how to obtain any credential, gated and audited.
|
||||
|
||||
Advisory by default — renders the owner, auth method, path template, command
|
||||
skeleton, and policy gate status for the best-matching need. ops-warden issues
|
||||
the SSH lane directly and **routes every other need to its owner** — it never
|
||||
holds or vends the secret value. (Proxy fetch arrives in WP-0014 T3.)
|
||||
"""
|
||||
from warden.access import expand_handoff, policy_gate_status
|
||||
|
||||
catalog = _load_catalog()
|
||||
matches = catalog.find(need, include_draft=all_entries, limit=1)
|
||||
if not matches:
|
||||
err.print(
|
||||
f"[red]No access match for {need!r}.[/red] "
|
||||
"Try `warden route list --all` to browse, or rephrase the need."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
entry = matches[0]
|
||||
expanded = expand_handoff(entry, domain)
|
||||
gate = policy_gate_status()
|
||||
|
||||
if output_json:
|
||||
print(json.dumps(_access_json(entry, expanded, gate, domain), indent=2))
|
||||
return
|
||||
|
||||
console.print(f"[bold]{entry.title}[/bold] ([cyan]{entry.id}[/cyan])")
|
||||
console.print(f" owner : {entry.owner_repo} ({entry.subsystem})")
|
||||
|
||||
if entry.warden_executes:
|
||||
console.print("\n[green]ops-warden issues this directly.[/green]")
|
||||
console.print(f" run : [bold]{entry.cert_command}[/bold]")
|
||||
if entry.steps:
|
||||
for i, step in enumerate(entry.steps, 1):
|
||||
console.print(f" {i}. {step}")
|
||||
return
|
||||
|
||||
if expanded.auth_method:
|
||||
console.print(f" auth : {expanded.auth_method}")
|
||||
if expanded.path_template:
|
||||
console.print(f" path : {expanded.path_template}")
|
||||
if expanded.fetch_command:
|
||||
console.print(f" fetch : {expanded.fetch_command}")
|
||||
if expanded.policy_ref:
|
||||
console.print(f" policy : {expanded.policy_ref} [dim]({gate})[/dim]")
|
||||
console.print(f" wiki : {entry.wiki_ref}")
|
||||
console.print(f" canon : {entry.canon_ref}")
|
||||
|
||||
if expanded.exec_capable:
|
||||
proxy = f"warden access {need!r}"
|
||||
if domain:
|
||||
proxy += f" --domain {domain}"
|
||||
console.print(
|
||||
f" proxy : [dim]{proxy} --fetch[/dim] "
|
||||
"[yellow](exec_capable; proxy ships in WP-0014 T3)[/yellow]"
|
||||
)
|
||||
if expanded.path_template and "<" in expanded.path_template:
|
||||
console.print(
|
||||
" note : remaining <…> placeholders are owner-confirmed names "
|
||||
f"(coordinate with {entry.owner_repo})."
|
||||
)
|
||||
console.print(
|
||||
f"\n[yellow]ops-warden does not hold this secret.[/yellow] "
|
||||
f"Obtain it from [bold]{entry.owner_repo}[/bold] as shown — "
|
||||
"warden advises, the owner vends."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user