Ship WARDEN-WP-0030: delegation register for every catalog lane
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Every execution position is now explicit. Catalog entries carry
delegation.mode (permanent / native / interim) with intended owner and
blocker. warden route gaps lists the interim set. Promotion requires
the ownership question. Doctrine lives in AccessRouting.md; the
register was published to the named owner repos.
This commit is contained in:
tegwick 2026-08-15 20:54:58 +02:00
parent 8d3706fa06
commit c93e3c9b43
11 changed files with 619 additions and 29 deletions

View file

@ -123,15 +123,15 @@ Gap analysis: `history/2026-07-01-intent-scope-gap-analysis.md` (current);
| NetKingdom evolution reflected in docs | Met |
| Non-SSH secrets stay out of ops-warden | Met |
| Workload posture / maturity model for secret-flow blockers | Met — two-axis standard + descriptors + conformance checker + dev doubles (WP-0015) |
| Every execution position explicitly permanent or interim with a named owner | **Not met** — 2 of 24 lanes carry `exec_owner`; 11 proxy lanes record no intended owner (WP-0030) |
| Every execution position explicitly permanent or interim with a named owner | **Met** — every catalog entry carries `delegation:`; `warden route gaps` lists the interim set (WP-0030) |
**Maturity vector:** `D5 / A5 / C4 / R4` (Discovery / Availability / Completeness / Reliability)
**Maturity vector:** `D5 / A5 / C5 / R4` (Discovery / Availability / Completeness / Reliability)
| Dimension | Level | Meaning today |
| --- | --- | --- |
| D5 | Discovery | Routing wiki + security map + pointer catalog + NK canon cross-links |
| A5 | Availability | CLI + `warden route` + `warden access` advisory & proxy front door + `warden policy` + opt-in policy gate + agent `--json` |
| C4 | Completeness | All ops-warden lanes shipped — SSH (prod), routing, access assist, posture conformance, cert_command pilot gate, disclosure hygiene, tenant custody, policy front door. **Down from C5:** delegation is unrecorded on 11 of 24 lanes, so ops-warden's interim positions are not distinguishable from designed ones (WP-0030) |
| C5 | Completeness | All ops-warden lanes shipped — SSH (prod), routing, access assist, posture conformance, cert_command pilot gate, disclosure hygiene, tenant custody, policy front door, delegation register (WP-0030) |
| R4 | Reliability | Live OpenBao sign + credential-broker policy-gate smoke evidence on Railiance (2026-07-01) |
---
@ -350,16 +350,16 @@ Remaining production distance is also in other repos' lanes (see Known gaps).
- **Policy front door:** WP-0029 shipped — `warden plan "<need>"` (autonomous /
founder_required / unroutable), `warden desk` founder interaction surface, declared
`organization_posture: build` as a third posture axis, catalog freshness reporting
- **Delegation:** 24 catalog lanes — 1 executed by ops-warden (SSH), 2 delegated via
`exec_owner`, 11 proxied with **no intended owner recorded**, 10 route-only. Addressed
by WP-0030 (proposed)
- **Active work:** WP-0027 (`backlog`), WP-0030 (`proposed`); remaining production
distance is other repos' lanes
- **Delegation:** 27 catalog lanes carry `delegation:` (WP-0030). SSH is
`permanent`; owner-fronted lanes are `native`; interim proxies name
`intended_owner` + `blocked_on`. Query: `warden route gaps`.
- **Active work:** WP-0027 (`backlog`); remaining production distance is other
repos' lanes (and retiring interim covers as those owners ship front doors)
- **Integration docs:** cert_command migration, token hygiene (broker-first), principals
drift (`wiki/playbooks/`)
- **Latest assessment:** `history/2026-08-11-delegation-surface-assessment.md`
- **Latest workplans:** WP-0029 (policy front door) shipped July 2026; WP-0030
(delegation register) proposed 2026-08-11
(delegation register) shipped August 2026
---

View file

@ -27,6 +27,9 @@
# status active (surfaced by default) | draft (hidden unless --all)
# steps ONLY when warden_executes: true
# cert_command ONLY when warden_executes: true
# delegation WP-0030 register. mode: native | interim | permanent.
# intended_owner required unless permanent; blocked_on required
# when interim. Absence is implicit interim with unknown owner.
version: 1
@ -41,6 +44,9 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md#operational-ssh-path
reviewed: "2026-06-18"
status: active
delegation:
mode: permanent
reviewed: "2026-08-15"
cert_command: "warden sign <actor> --pubkey <path>"
steps:
- "Confirm the actor is in inventory (`warden inventory list`); add with `warden inventory add` if not — see wiki/ActorInventoryPatterns.md."
@ -58,6 +64,10 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-07-01"
status: active
delegation:
mode: native
intended_owner: railiance-platform
reviewed: "2026-08-15"
# Concrete broker lane — RAILIANCE-WP-0005 pilot (live 2026-07-01):
# credential exec injects VAULT_TOKEN only into the child process; ops-warden
# issues SSH certs and never mints or holds OpenBao tokens.
@ -87,6 +97,11 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-06-27"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# Structured handoff (WP-0014) — reference example. Templates only, no values.
# ops-warden does not own this secret; it advises and (exec_capable) proxies the
# fetch *as the caller* via `warden access`, never holding or persisting the value.
@ -115,6 +130,10 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-06-29"
status: active
delegation:
mode: native
intended_owner: secrets-engine
reviewed: "2026-08-15"
# Concrete, owner-confirmed lane — railiance-platform CCR-2026-0001 (commit 8f617fc):
# status=active, access_frontdoor.readiness=ready, resolvable=true; positive fetch
# passed and negative (non-whynot) login denied. Zero-placeholder fetch: an automated
@ -152,6 +171,10 @@ entries:
canon_ref: net-kingdom/docs/responsibility-map.md
reviewed: "2026-06-18"
status: active
delegation:
mode: native
intended_owner: flex-auth
reviewed: "2026-08-15"
- id: key-cape-oidc-login
title: Interactive login, OIDC token, or MFA
@ -163,6 +186,11 @@ entries:
canon_ref: net-kingdom/docs/canon/standards/iam-profile_v0.2.md
reviewed: "2026-06-27"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# Login lane (WP-0014 T4) — interactive auth bootstrap, not a secret read. No
# secret-read gate (you have no identity yet) and no caller-auth precheck (the
# point is to obtain one). warden runs it interactively as the caller and never
@ -182,6 +210,10 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md#operational-ssh-path
reviewed: "2026-06-24"
status: active
delegation:
mode: native
intended_owner: ops-bridge
reviewed: "2026-08-15"
- id: railiance-infra-principals
title: Host SSH principal file or force-command deployment
@ -193,6 +225,10 @@ entries:
canon_ref: net-kingdom/docs/responsibility-map.md
reviewed: "2026-06-18"
status: active
delegation:
mode: native
intended_owner: railiance-infra
reviewed: "2026-08-15"
- id: inter-hub-bootstrap-ssh
title: Inter-Hub bootstrap SSH envelope
@ -204,6 +240,10 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md#operational-ssh-path
reviewed: "2026-06-24"
status: active
delegation:
mode: native
intended_owner: railiance-infra
reviewed: "2026-08-15"
- id: activity-core-issue-sink
title: activity-core IssueSink → issue-core REST emission
@ -215,6 +255,10 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-06-18"
status: active
delegation:
mode: native
intended_owner: activity-core
reviewed: "2026-08-15"
- id: issue-core-ingestion-api-key
title: issue-core ingestion API key (OpenBao KV + ESO)
@ -226,6 +270,11 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-07-02"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# Concrete, owner-confirmed lane — railiance-platform CCR-2026-0002 / RAILIANCE-WP-0009
# (promoted 2026-07-02): policy workload-kv-read-issue-core-runtime and k8s auth role
# external-secrets-issue-core applied; ExternalSecret issue-core/issue-core-runtime
@ -258,6 +307,11 @@ entries:
canon_ref: reuse-surface/specs/FederationHubAPI.md
reviewed: "2026-07-07"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# Concrete, owner-confirmed lane — railiance-platform CCR-2026-0005 / RAILIANCE-WP-0011
# (promoted 2026-07-07): policy workload-kv-read-reuse-surface-runtime; ExternalSecret
# reuse/reuse-surface-runtime SecretSynced to reuse-surface-env on Railiance01;
@ -290,6 +344,11 @@ entries:
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
reviewed: "2026-07-02"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# High-risk: provider API key with spend impact + prompt-adjacent (WP-0026 T04).
risk: high
# Concrete, owner-confirmed lane — railiance-platform CCR-2026-0003 / RAILIANCE-WP-0010
@ -325,6 +384,11 @@ entries:
canon_ref: railiance-platform/docs/workload-kv-access-lanes.md
reviewed: "2026-07-16"
status: active
delegation:
mode: interim
intended_owner: railiance-platform
blocked_on: "Rotation is re-establish, a multi-step procedure ops-warden only describes"
reviewed: "2026-08-11"
# High-risk: WebDAV upload token + AGE recovery escrow (WP-0026 T04).
risk: high
# CCR-2026-0004: policy + OIDC role applied; values provisioned 2026-07-07.
@ -365,6 +429,11 @@ entries:
canon_ref: railiance-platform/docs/workload-kv-access-lanes.md
reviewed: "2026-07-13"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-11"
# High-risk: site-admin PAT (WP-0026 T04).
risk: high
# CCR-2026-0006: approved by platform-operator 2026-07-12; policy
@ -402,6 +471,11 @@ entries:
canon_ref: railiance-platform/docs/workload-kv-access-lanes.md
reviewed: "2026-07-17"
status: active
delegation:
mode: interim
intended_owner: tenant-engine
blocked_on: "Custody at tenants/binky/... but rotation owner is binky-control — split lifecycle, no front door reconciling it"
reviewed: "2026-08-11"
risk: high
# CCR-2026-0007: tenants/ mount + policy + OIDC role applied; founder provisioned
# values via UI (version ≥2, not placeholder). Capabilities-safe verify 2026-07-17:
@ -431,6 +505,11 @@ entries:
canon_ref: binky-control/integrations/qonto-mcp.md
reviewed: "2026-07-21"
status: active
delegation:
mode: interim
intended_owner: tenant-engine
blocked_on: "Same split lifecycle as binky-company-email-imap; no tenant-engine front door"
reviewed: "2026-08-11"
risk: high
# CCR-2026-0008: policy + OIDC role applied; secret at tenants/binky/qonto-api
# (fields API_KEY, API_USER). Map to QONTO_API_KEY / QONTO_ORGANIZATION_ID for
@ -461,6 +540,11 @@ entries:
canon_ref: key-cape/docs/qonto-runtime-identity-contract.md
reviewed: "2026-07-27"
status: active
delegation:
mode: interim
intended_owner: key-cape
blocked_on: "client_secret_basic exchange is a key-cape protocol procedure, not a KV read; rotation already automatable"
reviewed: "2026-08-11"
risk: high
auth_method: "OpenBao platform workload lane; KeyCape client_secret_basic exchange"
path_template: "platform/workloads/rapp-qonto/keycape-client"
@ -486,6 +570,11 @@ entries:
canon_ref: binky-control/integrations/executor-worker-secrets.md
reviewed: "2026-07-17"
status: active
delegation:
mode: interim
intended_owner: railiance-platform
blocked_on: "re-establish plus an alternative host-local key path; two ways in, neither owner-fronted (also agent-harness)"
reviewed: "2026-08-11"
risk: high
# Provisioned 2026-07-17 on railiance01: ed25519 keypair on host, OpenBao copy at
# platform/workloads/agent-harness/forgejo-deploy-key, write deploy key on
@ -516,6 +605,11 @@ entries:
canon_ref: binky-control/integrations/executor-worker-secrets.md
reviewed: "2026-07-17"
status: active
delegation:
mode: interim
intended_owner: railiance-platform
blocked_on: "AppRole is a host-standing credential; no owner front door for minting or rotating role_id+secret_id"
reviewed: "2026-08-15"
risk: high
# Provisioned 2026-07-17: role agent-harness-binky-mail bound to existing policy
# workload-kv-read-binky-company-email-imap; role_id/secret_id delivered to
@ -548,6 +642,10 @@ entries:
canon_ref: net-kingdom/docs/object-storage-sts-credential-vending.md
reviewed: "2026-06-24"
status: draft
delegation:
mode: native
intended_owner: net-kingdom
reviewed: "2026-08-15"
- id: database-dynamic-credentials
title: Database dynamic credentials (OpenBao secrets engine)
@ -559,6 +657,10 @@ entries:
canon_ref: rapp-postgres/docs/canon-drafts/shared-platform-relational-storage_v0.1-draft.md
reviewed: "2026-08-10"
status: active
delegation:
mode: native
intended_owner: rapp-postgres
reviewed: "2026-08-15"
risk: high
exec_capable: false
@ -572,6 +674,11 @@ entries:
canon_ref: ops-mason/plans/rein-openweights-openrouter-approle.md
reviewed: "2026-07-27"
status: active
delegation:
mode: interim
intended_owner: ops-mason
blocked_on: "AppRole is a host-standing credential; no owner-fronted exec for the OpenRouter key read"
reviewed: "2026-08-15"
risk: standard
# Built 2026-07-27 by ops-mason (MASON-WP-0001-T05), approved by Bernd
# Worsch 2026-07-27. Policy + AppRole live; reins/ KV v2 mount created
@ -621,6 +728,11 @@ entries:
canon_ref: railiance-platform/docs/apps-pg.md
reviewed: "2026-08-09"
status: active
delegation:
mode: interim
intended_owner: railiance-apps
blocked_on: "Runtime Secret applied via railiance-apps make target; OpenBao path planned; confirm whether user-engine should front USER_ENGINE_PROXY_SECRET"
reviewed: "2026-08-15"
risk: standard
# K8s assembly is the live handoff today (same pattern as vergage-teilnahme-env).
# OpenBao KV platform/workloads/coulomb/coulomb-social/runtime-env is the
@ -650,6 +762,10 @@ entries:
canon_ref: audit-core/docs/operator-runbook.md
reviewed: "2026-08-13"
status: draft
delegation:
mode: native
intended_owner: ops-mason
reviewed: "2026-08-15"
auth_method: "in-cluster generated Secret audit-core-senders; later OpenBao KV via Mason wrap-migrate"
path_template: "platform/workloads/audit-core/senders"
policy_ref: "external-secrets-audit-core"
@ -667,6 +783,11 @@ entries:
canon_ref: railiance-platform/docs/workload-kv-access-lanes.md
reviewed: "2026-08-12"
status: active
delegation:
mode: interim
intended_owner: secrets-engine
blocked_on: "secrets-engine has not confirmed whether exec --catalog generalizes over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)"
reviewed: "2026-08-15"
risk: high
# CCR-2026-0010 approved 2026-08-12; applied same day (EMAIL-WP-0004-T03):
# policies external-secrets-email-connect + workload-kv-read-email-connect-transactional,
@ -703,6 +824,11 @@ entries:
canon_ref: ops-mason/plans/reef-storage-scaleway-bootstrap.md
reviewed: "2026-08-14"
status: draft
delegation:
mode: interim
intended_owner: railiance-platform
blocked_on: "Founder bootstrap API key; draft until provisioned; no owner-fronted exec"
reviewed: "2026-08-15"
risk: high
# CCR-2026-0011. Values via founder paste-once or local tfvars ingest.
# Not the Barman runtime key (platform-pg-backup-s3).

View file

@ -659,6 +659,8 @@ def _entry_summary(entry) -> dict:
if entry.has_rotation
else {}
),
# Delegation register (WP-0030) — implicit interim if the block is absent.
"delegation": entry.effective_delegation.to_dict(),
}
@ -769,6 +771,85 @@ def route_list(
)
@route_app.command("gaps")
def route_gaps(
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
all_entries: Annotated[bool, typer.Option("--all", help="Include draft entries")] = False,
stale_days: Annotated[
int,
typer.Option(
"--stale-days",
help="Days since delegation review before an interim lane is stale (default 90)",
min=1,
),
] = 90,
) -> None:
"""List interim lanes: intended owner, blocker, and age since review."""
from warden.routing.catalog import days_since_review
catalog = _load_catalog()
entries = catalog.gaps(include_draft=all_entries)
if output_json:
payload = []
for e in entries:
d = e.effective_delegation
reviewed = d.reviewed or e.reviewed
payload.append(
{
"id": e.id,
"title": e.title,
"status": e.status,
"mode": d.mode,
"intended_owner": d.intended_owner,
"blocked_on": d.blocked_on,
"reviewed": reviewed,
"days_since_review": days_since_review(reviewed),
"implicit": d.implicit,
"stale": days_since_review(reviewed) > stale_days,
}
)
print(json.dumps(payload, indent=2))
return
if not entries:
console.print("No interim routing gaps (every execution position is classified).")
return
table = Table(title="Interim delegation register")
table.add_column("ID")
table.add_column("Owner")
table.add_column("Blocked on")
table.add_column("Reviewed")
table.add_column("Days")
table.add_column("Status")
for e in entries:
d = e.effective_delegation
reviewed = d.reviewed or e.reviewed
days = days_since_review(reviewed)
reviewed_styled = f"[yellow]{reviewed}[/yellow]" if days > stale_days else reviewed
days_styled = f"[yellow]{days}[/yellow]" if days > stale_days else str(days)
status_styled = e.status if e.status == "active" else f"[yellow]{e.status}[/yellow]"
table.add_row(
e.id,
d.intended_owner or "[yellow]unknown[/yellow]",
d.blocked_on or "",
reviewed_styled,
days_styled,
status_styled,
)
console.print(table)
stale_n = sum(
1
for e in entries
if days_since_review(e.effective_delegation.reviewed or e.reviewed) > stale_days
)
if stale_n:
console.print(
f"[yellow]{stale_n} interim lane(s) past {stale_days}d review cadence.[/yellow]"
)
@route_app.command("show")
def route_show(
entry_id: Annotated[str, typer.Argument(help="Catalog entry id (see `warden route list`)")],
@ -813,6 +894,17 @@ def route_show(
console.print(f" wiki : {entry.wiki_ref}")
console.print(f" canon : {entry.canon_ref}")
console.print(f" reviewed : {entry.reviewed} status: {entry.status}")
d = entry.effective_delegation
owner = d.intended_owner or "unknown"
if d.mode == "interim":
console.print(
f" delegation: [yellow]interim[/yellow] → {owner}"
+ (f" blocked: {d.blocked_on}" if d.blocked_on else "")
)
elif d.mode == "native":
console.print(f" delegation: native → {owner} already fronts this")
else:
console.print(" delegation: permanent (ops-warden owns this front door)")
if entry.warden_executes:
console.print("\n[green]ops-warden issues this directly.[/green]")

View file

@ -12,12 +12,13 @@ from warden.routing.catalog import (
find_catalog_path,
load_catalog,
)
from warden.routing.models import RouteEntry
from warden.routing.models import Delegation, RouteEntry
__all__ = [
"Catalog",
"CatalogError",
"CatalogFreshness",
"Delegation",
"RouteEntry",
"find_catalog_path",
"load_catalog",

View file

@ -23,7 +23,13 @@ from typing import List, Optional
import yaml
from warden.routing.models import VALID_RISK, RotationGuide, RouteEntry
from warden.routing.models import (
VALID_DELEGATION_MODES,
VALID_RISK,
Delegation,
RotationGuide,
RouteEntry,
)
# Structured handoff string fields (WP-0014) — templates and pointers only.
# Every one is scanned for accidental secret material; see _assert_no_secret_material.
@ -201,6 +207,25 @@ class Catalog:
if is_review_stale(e.reviewed, threshold_days=threshold_days, today=today)
]
def gaps(self, include_draft: bool = False) -> List[RouteEntry]:
"""Interim lanes — the queryable delegation register (WARDEN-WP-0030)."""
return [e for e in self.listed(include_draft=include_draft) if e.is_interim]
def stale_gaps(
self,
include_draft: bool = False,
threshold_days: int = DEFAULT_STALE_DAYS,
*,
today: Optional[date] = None,
) -> List[RouteEntry]:
"""Interim lanes whose delegation review is past the cadence threshold."""
out: List[RouteEntry] = []
for e in self.gaps(include_draft=include_draft):
reviewed = e.effective_delegation.reviewed or e.reviewed
if is_review_stale(reviewed, threshold_days=threshold_days, today=today):
out.append(e)
return out
def freshness(
self,
*,
@ -236,6 +261,15 @@ class Catalog:
f"{stale_count} catalog entr{'y' if stale_count == 1 else 'ies'} "
f"past {stale_threshold_days}d review cadence"
)
stale_interim = len(self.stale_gaps(
include_draft=True, threshold_days=stale_threshold_days, today=today
))
if stale_interim:
warnings.append(
f"{stale_interim} interim delegation"
f"{'' if stale_interim == 1 else 's'} past "
f"{stale_threshold_days}d review — see `warden route gaps`"
)
return CatalogFreshness(
path=str(path),
@ -362,6 +396,66 @@ def _parse_rotation(entry_id: str, raw: Optional[dict]) -> Optional[RotationGuid
)
def _parse_delegation(entry_id: str, raw: Optional[dict]) -> Optional[Delegation]:
"""Parse an optional ``delegation:`` block (WARDEN-WP-0030).
Absence is allowed: the loader treats it as implicit interim with an
unknown owner. When the block *is* present, mode / owner / blocker rules
are enforced so a declared answer cannot be incomplete.
"""
if raw is None:
return None
if not isinstance(raw, dict):
raise CatalogError(f"entry {entry_id!r} `delegation` must be a mapping")
mode = str(raw.get("mode", "")).strip()
if mode not in VALID_DELEGATION_MODES:
raise CatalogError(
f"entry {entry_id!r} delegation.mode {mode!r} invalid "
f"(expected one of {VALID_DELEGATION_MODES})"
)
intended_owner = str(raw.get("intended_owner", "")).strip() or None
if mode != "permanent" and not intended_owner:
raise CatalogError(
f"entry {entry_id!r} delegation.intended_owner is required "
f"unless mode is permanent"
)
blocked_on = str(raw.get("blocked_on", "")).strip() or None
if mode == "interim" and not blocked_on:
raise CatalogError(
f"entry {entry_id!r} delegation.blocked_on is required when mode is interim"
)
reviewed = str(raw.get("reviewed", "")).strip() or None
if not reviewed:
raise CatalogError(f"entry {entry_id!r} delegation.reviewed is required")
try:
date.fromisoformat(reviewed)
except ValueError as e:
raise CatalogError(
f"entry {entry_id!r} delegation.reviewed {reviewed!r} is not YYYY-MM-DD"
) from e
if intended_owner:
_assert_no_secret_material(
entry_id, "delegation.intended_owner", intended_owner, prose=True
)
if blocked_on:
_assert_no_secret_material(
entry_id, "delegation.blocked_on", blocked_on, prose=True
)
return Delegation(
mode=mode,
intended_owner=intended_owner,
blocked_on=blocked_on,
reviewed=reviewed,
implicit=False,
)
def _parse_entry(raw: dict, index: int) -> RouteEntry:
if not isinstance(raw, dict):
raise CatalogError(f"entry #{index} is not a mapping")
@ -452,6 +546,7 @@ def _parse_entry(raw: dict, index: int) -> RouteEntry:
pointer_command=handoff["pointer_command"],
rotation=_parse_rotation(entry_id, raw.get("rotation")),
risk=risk,
delegation=_parse_delegation(entry_id, raw.get("delegation")),
)

View file

@ -34,6 +34,40 @@ class RotationGuide:
# standard — ordinary workload secrets (ESO-fed, non-escrow); normal least-privilege.
VALID_RISK = ("standard", "high")
# Delegation modes (WARDEN-WP-0030). Absence of a block is implicit interim.
# native — intended owner already fronts the lane (route-primary / pointer)
# interim — ops-warden covers a gap; intended_owner + blocked_on required
# permanent — ops-warden is the designed owner of this front door (SSH only today)
VALID_DELEGATION_MODES = ("native", "interim", "permanent")
IMPLICIT_DELEGATION_BLOCKED_ON = (
"unclassified — no delegation block; treat as a question, not a settlement"
)
@dataclass
class Delegation:
"""Who should own this front door, and what is missing (WARDEN-WP-0030).
Pointer-layer only: names the intended owner and the blocker. Does not
restate how that owner will implement their front door.
"""
mode: str # native | interim | permanent
intended_owner: Optional[str] = None
blocked_on: Optional[str] = None
reviewed: Optional[str] = None
implicit: bool = False
def to_dict(self) -> dict:
return {
"mode": self.mode,
"intended_owner": self.intended_owner,
"blocked_on": self.blocked_on,
"reviewed": self.reviewed,
"implicit": self.implicit,
}
@dataclass
class RouteEntry:
@ -78,6 +112,8 @@ class RouteEntry:
rotation: Optional[RotationGuide] = None
# Agent read-boundary risk class (WP-0026 T04). high → agents use wrap/out/exec only.
risk: str = "standard" # "standard" | "high"
# Delegation register (WP-0030). None → implicit interim with unknown owner.
delegation: Optional[Delegation] = None
@property
def is_active(self) -> bool:
@ -111,6 +147,28 @@ class RouteEntry:
"""True when an owner-native exec front door is the primary path for this lane."""
return bool(self.exec_owner and self.exec_command)
@property
def effective_delegation(self) -> Delegation:
"""Declared delegation, or implicit interim with an unknown owner.
Absence of a ``delegation:`` block is a question (WP-0030), not a
settlement that ops-warden owns the front door.
"""
if self.delegation is not None:
return self.delegation
return Delegation(
mode="interim",
intended_owner=None,
blocked_on=IMPLICIT_DELEGATION_BLOCKED_ON,
reviewed=None,
implicit=True,
)
@property
def is_interim(self) -> bool:
"""True when this lane is a tracked gap (explicit or implicit)."""
return self.effective_delegation.mode == "interim"
@property
def has_handoff(self) -> bool:
"""True when structured assist fields are present (advisory richness)."""

View file

@ -575,3 +575,137 @@ def test_route_show_json_includes_risk():
assert payload["high_risk"] is True
assert payload["resolvable"] is True
assert payload["status"] == "active"
# ---------------------------------------------------------------------------
# Delegation register (WARDEN-WP-0030)
# ---------------------------------------------------------------------------
def test_every_catalog_entry_declares_delegation():
catalog = load_catalog(_repo_catalog())
missing = [e.id for e in catalog.entries if e.delegation is None]
assert missing == [], f"entries missing delegation block: {missing}"
def test_every_proxy_declares_delegation():
"""A new exec_capable proxy cannot land without answering the ownership question."""
catalog = load_catalog(_repo_catalog())
missing = [
e.id
for e in catalog.entries
if e.exec_capable and not e.warden_executes and e.delegation is None
]
assert missing == [], f"proxy lanes missing delegation: {missing}"
def test_ssh_lane_is_permanent_delegation():
e = load_catalog(_repo_catalog()).get("ssh-cert-host-access")
assert e.delegation is not None
assert e.delegation.mode == "permanent"
assert e.delegation.intended_owner is None
assert e.is_interim is False
def test_native_exec_lanes_are_native_delegation():
catalog = load_catalog(_repo_catalog())
for eid, owner in (
("whynot-design-npm-publish", "secrets-engine"),
("ops-warden-warden-sign-token", "railiance-platform"),
):
e = catalog.get(eid)
assert e.delegation is not None
assert e.delegation.mode == "native"
assert e.delegation.intended_owner == owner
def test_founder_interim_lanes_classified():
catalog = load_catalog(_repo_catalog())
expected = {
"rapp-qonto-keycape-client": "key-cape",
"binky-company-email-imap": "tenant-engine",
"binky-qonto-api": "tenant-engine",
"railiance-backup-offsite-lane": "railiance-platform",
"agent-harness-forgejo-deploy": "railiance-platform",
}
for eid, owner in expected.items():
e = catalog.get(eid)
assert e is not None and e.delegation is not None
assert e.delegation.mode == "interim"
assert e.delegation.intended_owner == owner
assert e.delegation.blocked_on
def test_missing_delegation_is_implicit_interim(tmp_path):
catalog = load_catalog(_write_catalog(tmp_path, [dict(ROUTED_ENTRY)]))
e = catalog.get("openbao-api-key")
d = e.effective_delegation
assert e.delegation is None
assert d.implicit is True
assert d.mode == "interim"
assert d.intended_owner is None
assert "unclassified" in (d.blocked_on or "")
def test_interim_without_blocked_on_rejected(tmp_path):
bad = dict(
ROUTED_ENTRY,
delegation={
"mode": "interim",
"intended_owner": "secrets-engine",
"reviewed": "2026-08-15",
},
)
with pytest.raises(CatalogError, match="blocked_on"):
load_catalog(_write_catalog(tmp_path, [bad]))
def test_non_permanent_without_owner_rejected(tmp_path):
bad = dict(
ROUTED_ENTRY,
delegation={"mode": "native", "reviewed": "2026-08-15"},
)
with pytest.raises(CatalogError, match="intended_owner"):
load_catalog(_write_catalog(tmp_path, [bad]))
def test_invalid_delegation_mode_rejected(tmp_path):
bad = dict(
ROUTED_ENTRY,
delegation={"mode": "maybe", "intended_owner": "x", "reviewed": "2026-08-15"},
)
with pytest.raises(CatalogError, match="delegation.mode"):
load_catalog(_write_catalog(tmp_path, [bad]))
def test_catalog_gaps_lists_only_interim():
catalog = load_catalog(_repo_catalog())
gap_ids = {e.id for e in catalog.gaps(include_draft=True)}
assert "ssh-cert-host-access" not in gap_ids
assert "whynot-design-npm-publish" not in gap_ids
assert "binky-company-email-imap" in gap_ids
assert "openbao-api-key" in gap_ids
assert all(catalog.get(i).is_interim for i in gap_ids)
def test_cli_route_gaps_json(repo_catalog_env):
result = runner.invoke(app, ["route", "gaps", "--json"])
assert result.exit_code == 0
data = json.loads(result.stdout)
assert data
ids = {row["id"] for row in data}
assert "binky-company-email-imap" in ids
assert "ssh-cert-host-access" not in ids
for row in data:
assert row["mode"] == "interim"
assert "intended_owner" in row
assert "blocked_on" in row
assert "days_since_review" in row
def test_cli_route_show_includes_delegation(repo_catalog_env):
result = runner.invoke(app, ["route", "show", "binky-qonto-api", "--json"])
assert result.exit_code == 0
data = json.loads(result.stdout)
assert data["delegation"]["mode"] == "interim"
assert data["delegation"]["intended_owner"] == "tenant-engine"
assert data["delegation"]["implicit"] is False

View file

@ -76,6 +76,44 @@ boundary in `OperatorAccessAssist.md`.
---
## Interim custodianship
ops-warden **works with, and never replaces or duplicates**, the NetKingdom
components that own identity, custody, authorization, and tenant/user lifecycle
(`INTENT.md` §9). Covering a gap is legitimate. Silently becoming the owner of
that gap is not.
The only lane ops-warden executes with its own authority is **SSH issuance**
(`ssh-cert-host-access`). Every other execution position — including every
`warden access` proxy — is **interim by default**. A catalog entry without a
`delegation:` block is treated as interim with an unknown owner: absence is a
question, not a settlement.
| `delegation.mode` | Meaning |
| --- | --- |
| `permanent` | ops-warden is the designed owner of this front door (SSH only, today) |
| `native` | the intended owner already fronts it; ops-warden routes (and may keep a proxy as fallback) |
| `interim` | ops-warden covers the gap until `intended_owner` ships the missing front door named in `blocked_on` |
Delegation targets — the components that should own a front door once they have
one:
| Target | What they should front |
| --- | --- |
| **secrets-engine** | owner-native secret-exec (`secrets-engine exec --catalog <id>`) |
| **tenant-engine** | tenant/client secret custody and tenant-lane front door |
| **user-engine** | end-user / account-lifecycle secrets that belong with user identity |
| **railiance-platform** | OpenBao cluster, credential broker, platform workload procedure |
| **flex-auth** | authorization decisions (already native — ops-warden only points) |
| **key-cape** | identity login and client-credential protocol (OIDC, `client_secret_basic`) |
Query the register with `warden route gaps`. An interim lane is retired by
setting `exec_owner` / `exec_command` (the WP-0019 pattern) and flipping
`delegation.mode` to `native` once the owner's front door is proven. Do not
delete a working proxy on the way.
---
## Routing lookup CLI (`warden route`)
Agents and operators query the pointer catalog directly instead of re-deriving
@ -86,6 +124,7 @@ material.
```bash
warden route list [--json] [--all] [--tag <keyword>] # active-only unless --all
warden route list --stale [--stale-days 90] [--all] [--json] # past review cadence
warden route gaps [--json] [--all] # interim register (owner + blocker)
warden route show <id> [--json] # owner + pointers; SSH adds steps
warden route find "<free text need>" [--json] [--all] # rank by keyword overlap
```
@ -145,6 +184,7 @@ owner repo's shipped path.
| **On canon change** | When net-kingdom security docs change, review affected `canon_ref` entries immediately |
| **On owner ship** | When an owning repo merges a new OpenBao path or playbook, promote `draft``active` and bump `reviewed` |
| **On agent confusion** | If `warden route find` misses a common query, add `need_keywords` or a playbook — do not restate owner procedure in the catalog |
| **On interim review** | Run `warden route gaps` — re-check each `blocked_on` against the intended owner; flip to `native` when their front door exists |
### Stale check (operators and agents)
@ -177,6 +217,7 @@ moved but anchors still resolve.
- `CredentialRouting.md` — worker decision tree and routing table
- `NetKingdomSecurityMap.md` — component literacy
- `INTENT.md` — steward mission ("issue SSH, route the rest")
- `INTENT.md` — steward mission ("issue SSH, route the rest"); §9 interim custodianship
- `workplans/WARDEN-WP-0030-delegation-register.md` — delegation register
- `workplans/WARDEN-WP-0010-access-routing-charter.md` — charter + no-double-source rule
- `net-kingdom/docs/platform-identity-security-architecture.md` — platform canon

View file

@ -81,7 +81,9 @@ These needs are also carried in the machine-readable pointer catalog
The catalog is a **pointer-and-assist layer**: it names the owner, links the doc,
and carries secret-free handoff templates for `warden access`. Only the SSH row is
something ops-warden executes with its own authority. Non-SSH `exec_capable` rows
run the owner's tool as the caller and preserve owner custody.
run the owner's tool as the caller and preserve owner custody. Every execution
position is classified `permanent` / `native` / `interim` (`warden route gaps`
lists the interim set — WARDEN-WP-0030).
| Catalog `id` | What ops-warden answers | What the worker does next |
| --- | --- | --- |

View file

@ -24,10 +24,21 @@ Before changing `status: draft` → `status: active`:
| 7 | **Review date** | Update `reviewed:` in catalog entry |
| 8 | **Verification** | Positive + negative proof via **`bao token capabilities`** — never `bao kv get` (see below) |
| 9 | **Rotation guidance** | Secret-vending lanes carry a `rotation:` block; `warden rotate-guide <id>` returns steps. Enforced by the `catalog_rotation_coverage` scorecard check (WP-0026 T06) |
| 10 | **Delegation** | Entry carries a `delegation:` block (WP-0030). `mode: permanent` only for ops-warden's own front door. `mode: native` when the owner already fronts it. `mode: interim` requires `intended_owner` and `blocked_on`; the promotion note must name both and the retirement condition |
Promotion PR touches: `registry/routing/catalog.yaml`, playbook, optional
`tests/test_routing.py`, and a one-line note in `wiki/CredentialRouting.md` draft table.
If `delegation.mode` is `interim`, the promotion note (PR body or State Hub
progress) must state:
1. who the intended owner is
2. what is missing (`blocked_on`)
3. what would let ops-warden step back (`exec_owner` + proven owner front door)
A proxy that works is not enough to promote without answering the ownership
question. `warden route gaps` must list the lane after merge if it is interim.
---
## Capabilities-safe lane verification (WARDEN-WP-0026 T01)
@ -113,4 +124,6 @@ playbook prose alone.
## See also
- `wiki/CredentialRouting.md` — draft table index
- `wiki/playbooks/ops-warden-warden-sign-token.md` — promotion reference
- `wiki/playbooks/ops-warden-warden-sign-token.md` — promotion reference
- `wiki/AccessRouting.md#interim-custodianship` — delegation register doctrine
- `warden route gaps` — queryable interim register

View file

@ -4,13 +4,13 @@ type: workplan
title: "Delegation register — make gap-covering interim, visible, and retirable"
domain: infotech
repo: ops-warden
status: proposed
owner: claude
status: finished
owner: grok
topic_slug: custodian
planning_priority: high
planning_order: 30
created: "2026-08-11"
updated: "2026-08-11"
updated: "2026-08-15"
state_hub_workstream_id: "29f3fcb3-e3c5-4f9d-bdfd-f76254754e21"
---
@ -87,7 +87,7 @@ to *"who should own this front door, and what is missing?"*
```task
id: WARDEN-WP-0030-T01
status: todo
status: done
priority: high
state_hub_task_id: "3d76f6c2-5007-4ad5-8d6f-360f9303c929"
```
@ -103,7 +103,7 @@ targets. Cross-link INTENT.md §9.
```task
id: WARDEN-WP-0030-T02
status: todo
status: done
priority: high
state_hub_task_id: "8013020b-d0a5-4e89-8e0b-bc418a761c84"
```
@ -123,11 +123,26 @@ Backfill all 24 entries. `exec_owner` lanes become `mode: native`.
eleven undelegated proxies are classified per the founder review below — five
`interim` now, six held pending secrets-engine's answer.
**Landed 2026-08-15.** Catalog is now 27 entries. All 27 carry `delegation:`.
- `permanent` (1): `ssh-cert-host-access`
- `native` (10): both `exec_owner` lanes plus the route-only pointers
- `interim` (16): the five founder-classified now; the six held pending
secrets-engine (still unanswered as of 2026-08-15, msg `7d55d332`) recorded as
`interim` / `intended_owner: secrets-engine` / `blocked_on` the generic-vs-per-lane
question — **not** `permanent`; plus five new-since-survey lanes drafted the same
way (`email-connect-transactional` same held question; two AppRoles;
`coulomb-social-runtime-env`; draft `scaleway-bootstrap`)
The six are not guessed permanent. Interim is the default until secrets-engine
answers. Reclassify to `permanent` only if they confirm `exec --catalog` stays
per-lane.
### T03 — `warden route gaps` + conformance test
```task
id: WARDEN-WP-0030-T03
status: todo
status: done
priority: medium
state_hub_task_id: "e7755708-e1bf-4d42-999c-4a60330cf154"
```
@ -142,7 +157,7 @@ entries in the existing drift/stale review cadence.
```task
id: WARDEN-WP-0030-T04
status: todo
status: done
priority: medium
state_hub_task_id: "ddc0268b-1279-4875-b223-0627c97d657a"
```
@ -156,7 +171,7 @@ checklist.
```task
id: WARDEN-WP-0030-T05
status: todo
status: done
priority: medium
state_hub_task_id: "d8796e25-e896-463d-9476-f002e38b6897"
```
@ -167,16 +182,29 @@ what ops-warden currently fronts on your behalf, here is what would let us step
back. This is the artefact that converts a private ops-warden position into a
fleet-visible capability gap.
**Landed 2026-08-15.** Register sent (no secret values):
| To | Message |
| --- | --- |
| secrets-engine | `9c1d753f` — 1 native + 7 interim pending generic-exec answer |
| tenant-engine | `17c851b1` — 2 Binky tenant lanes |
| user-engine | `38aeccd5` — confirm USER_ENGINE_PROXY_SECRET ownership |
| railiance-platform | `e75793e3` — 1 native + 4 interim |
| net-kingdom | `3fea09df` — object-storage-sts remains native/draft |
| key-cape | `28ded56c` — rapp-qonto-keycape-client (founder-classified interim) |
## Acceptance
- [ ] `wiki/AccessRouting.md` states the interim-custodianship boundary and names
- [x] `wiki/AccessRouting.md` states the interim-custodianship boundary and names
the delegation targets
- [ ] All 24 catalog entries carry `delegation`; `warden route gaps` returns the
interim set with owner + blocker
- [ ] Routing test fails when a proxy lane omits `delegation`
- [ ] Promotion playbook gates draft→active on the ownership question
- [ ] Founder has reviewed the interim/permanent classification of the eleven
- [ ] Register delivered to the named owner repos
- [x] All catalog entries carry `delegation`; `warden route gaps` returns the
interim set with owner + blocker (27 entries; 15 active interim)
- [x] Routing test fails when a proxy lane omits `delegation`
- [x] Promotion playbook gates draft→active on the ownership question
- [x] Founder has reviewed the interim/permanent classification of the eleven
(2026-08-11); the six held lanes stay interim pending secrets-engine, not
marked permanent
- [x] Register delivered to the named owner repos (T05)
## Classification of the eleven (founder review, 2026-08-11)