diff --git a/pep-stance.yaml b/pep-stance.yaml index 2be4dac..01e1614 100644 --- a/pep-stance.yaml +++ b/pep-stance.yaml @@ -94,7 +94,7 @@ on_apply: # a register that cannot detect its own staleness is only as good as the last # re-measure, and this one was stale by eight lanes before the test caught it. classification_coverage: - measured: "2026-09-21" + measured: "2026-09-28" attribution: self-measured signing_targets: resolved: 0 diff --git a/src/warden/cli.py b/src/warden/cli.py index dcea9c2..2dce2db 100644 --- a/src/warden/cli.py +++ b/src/warden/cli.py @@ -1212,6 +1212,7 @@ def _access_proxy( goes to stderr so --fetch stdout carries only the secret. """ from warden.proxy import ( + AttendedLoginError, ProxyError, build_wrapped_fetch, caller_auth_present, @@ -1350,6 +1351,8 @@ def _access_proxy( f"[dim]proxy {action}: {entry.id} → {entry.owner_repo} " f"(caller identity; value not persisted)[/dim]" ) + rc = 10 if is_login else 5 + outcome = "login_failed" if is_login else "error" try: if is_login: rc = proxy_attended_login_exec(resolved, child_argv=child_argv) @@ -1381,9 +1384,12 @@ def _access_proxy( rc = 0 else: rc = proxy_fetch(resolved) + outcome = "ok" if rc == 0 else "error" except ProxyError as e: + if isinstance(e, AttendedLoginError): + rc, outcome = e.exit_code, e.outcome err.print(f"[red]{e}[/red]") - raise typer.Exit(5) + raise typer.Exit(rc) finally: try: write_audit( @@ -1393,6 +1399,8 @@ def _access_proxy( domain=domain, action=action, decision_id=decision_id, + exit_code=rc, + outcome=outcome, ) except OSError as e: err.print(f"[yellow]audit write failed:[/yellow] {e}") @@ -1564,6 +1572,10 @@ def access( f" {label:<8} : [dim]{proxy} --exec -- [/dim] " "[yellow](contained login + command; output suppressed)[/yellow]" ) + console.print( + " contract : reviewed command must be silent; any stdout/stderr " + "fails closed. Redirect both streams inside the reviewed command." + ) else: console.print( f" {label:<8} : [dim]{proxy} --fetch[/dim] " diff --git a/src/warden/proxy.py b/src/warden/proxy.py index ab451a9..2780eb2 100644 --- a/src/warden/proxy.py +++ b/src/warden/proxy.py @@ -61,6 +61,15 @@ class ProxyError(Exception): """Raised when a proxy fetch cannot be performed safely.""" +class AttendedLoginError(ProxyError): + """Value-free phase result for the attended command envelope.""" + + def __init__(self, message: str, *, exit_code: int, outcome: str): + super().__init__(message) + self.exit_code = exit_code + self.outcome = outcome + + def _has_shell_pipe(cmd: str) -> bool: """True when ``cmd`` contains an unquoted shell pipe operator.""" in_single = in_double = False @@ -144,6 +153,7 @@ def write_audit( action: str, decision_id: Optional[str], exit_code: Optional[int] = None, + outcome: Optional[str] = None, ) -> Path: """Append a metadata-only audit record. Never contains a secret value (G2).""" state_dir.mkdir(parents=True, exist_ok=True) @@ -157,6 +167,7 @@ def write_audit( "subject": os.environ.get("WARDEN_POLICY_SUBJECT", "").strip() or "operator", "policy_decision_id": decision_id, "exit_code": exit_code, + "outcome": outcome or ("ok" if exit_code in (None, 0) else "error"), } record.update(recording_time()) with log_path.open("a") as f: @@ -171,10 +182,11 @@ def write_audit( subject=record["subject"], target=need_id, decision_id=decision_id, - outcome="ok" if exit_code in (None, 0) else "error", + outcome=record["outcome"], source="access", owner_repo=owner_repo, domain=domain, + exit_code=exit_code, ) except Exception: pass @@ -443,9 +455,10 @@ def proxy_attended_login_exec( resolved.argv[0], env=env, possible_output=login_output ) status = "revoked" if revoked else "revocation could not be confirmed" - raise ProxyError( + raise AttendedLoginError( "attended login failed closed before command handoff; any possible " - f"issued session was contained and {status}" + f"issued session was contained and {status}", + exit_code=10, outcome="login_failed", ) try: @@ -455,23 +468,33 @@ def proxy_attended_login_exec( resolved.argv[0], env=env, possible_output=b"" ) status = "revoked" if revoked else "revocation could not be confirmed" - raise ProxyError( - "attended command could not start; the login session was " + status + raise AttendedLoginError( + "attended command could not start; the login session was " + status, + exit_code=11, outcome="child_failed", ) from exc child_output = _output_bytes(child) revoked = _revoke_contained( resolved.argv[0], env=env, possible_output=child_output ) - if child.returncode != 0 or child_output.strip(): + # Even whitespace is output. The separator added by _output_bytes is not. + if child.stdout or child.stderr: status = "revoked" if revoked else "revocation could not be confirmed" - raise ProxyError( - "attended command failed closed because it returned a failure or " - f"unexpected output; the login session was {status}" + raise AttendedLoginError( + "attended command produced output; reviewed commands must remain " + f"silent (redirect both streams); the login session was {status}", + exit_code=12, outcome="child_output", + ) + if child.returncode != 0: + status = "revoked" if revoked else "revocation could not be confirmed" + raise AttendedLoginError( + f"attended command exited non-zero; the login session was {status}", + exit_code=11, outcome="child_failed", ) if not revoked: - raise ProxyError( - "attended command completed but session revocation could not be confirmed" + raise AttendedLoginError( + "attended command completed but session revocation could not be confirmed", + exit_code=13, outcome="revoke_unconfirmed", ) return 0 finally: @@ -480,7 +503,11 @@ def proxy_attended_login_exec( if root_created: root.rmdir() except OSError as exc: - raise ProxyError("attended login private storage cleanup failed") from exc + raise AttendedLoginError( + "attended login private storage cleanup failed; inspect the prior phase " + "before retrying the reviewed command", + exit_code=14, outcome="cleanup_failed", + ) from exc def _capture_value(resolved: ResolvedFetch) -> str: diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 34f961e..76d7beb 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -330,6 +330,76 @@ def test_cli_login_lane_rejects_persistent_fetch(monkeypatch, tmp_path): assert "requires --exec" in r.output +@pytest.mark.parametrize( + "phase,code,outcome", + [ + ("success", 0, "ok"), + ("missing_helper", 10, "login_failed"), + ("login_failed", 10, "login_failed"), + ("login_start", 10, "login_failed"), + ("child_start", 11, "child_failed"), + ("child_failed", 11, "child_failed"), + ("child_output", 12, "child_output"), + ("child_whitespace", 12, "child_output"), + ("revoke_failed", 13, "revoke_unconfirmed"), + ("cleanup_failed", 14, "cleanup_failed"), + ], +) +def test_cli_attended_result_and_both_audits(monkeypatch, tmp_path, phase, code, outcome): + _proxy_env(monkeypatch, tmp_path) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + calls = [] + sentinel = b"hvs.NONPRODUCTION_RESULT_SENTINEL" + + def fake_run(argv, **kw): + calls.append(argv) + helper = Path(kw["env"]["HOME"]) / ".vault-token" + if argv[:2] == ["bao", "login"]: + if phase == "login_start": + raise OSError("client unavailable") + if phase != "missing_helper": + helper.write_bytes(sentinel) + return subprocess.CompletedProcess( + argv, 1 if phase == "login_failed" else 0, sentinel, b"" + ) + if argv == ["reviewed-child"]: + if phase == "child_start": + raise OSError("child unavailable") + output = {"child_output": sentinel, "child_whitespace": b" \n"}.get(phase, b"") + return subprocess.CompletedProcess( + argv, 9 if phase == "child_failed" else 0, b"", output + ) + assert argv == ["bao", "token", "revoke", "-self"] + return subprocess.CompletedProcess( + argv, 1 if phase == "revoke_failed" else 0, b"", b"" + ) + + monkeypatch.setattr("warden.proxy.subprocess.run", fake_run) + if phase == "cleanup_failed": + def fail_cleanup(*args): + raise OSError("cleanup unavailable") + monkeypatch.setattr("warden.proxy.shutil.rmtree", fail_cleanup) + result = runner.invoke( + app, ["access", "login oidc", "--domain", "coulomb_social", + "--exec", "--", "reviewed-child"] + ) + assert result.exit_code == code, result.output + assert sentinel.decode() not in result.output + if code == 10: + assert ["reviewed-child"] not in calls + if phase not in ("login_start", "cleanup_failed"): + assert calls[-1] == ["bao", "token", "revoke", "-self"] + if phase != "cleanup_failed": + assert not (tmp_path / ".warden-attended-login").exists() + for name in ("access-audit.log", "audit.jsonl"): + raw = (tmp_path / "state" / name).read_text() + assert sentinel.decode() not in raw + records = [json.loads(line) for line in raw.splitlines()] + login = next(record for record in records if record["action"] == "login") + assert login["exit_code"] == code + assert login["outcome"] == outcome + + def test_attended_login_refuses_read_only_home_before_auth(monkeypatch, tmp_path): monkeypatch.setattr(Path, "home", lambda: tmp_path) monkeypatch.setattr( diff --git a/wiki/playbooks/openbao-platform-admin-login.md b/wiki/playbooks/openbao-platform-admin-login.md index c1c86ae..fbe547b 100644 --- a/wiki/playbooks/openbao-platform-admin-login.md +++ b/wiki/playbooks/openbao-platform-admin-login.md @@ -33,8 +33,10 @@ workload KV-read lane and it does not provision a secret value. and remain silent. Warden self-revokes the session and removes the helper on every success or failure path. - Safety does not rely on `-no-print`. Any client or child output, helper - persistence defect, non-zero exit, or revocation/cleanup defect fails closed. + Safety does not rely on `-no-print`. Login output stays contained and is + accepted only after successful helper persistence. Any child output (including + whitespace), persistence defect, non-zero exit, or revocation/cleanup defect + fails closed. Captured bytes are never returned, logged, excerpted, hashed, or fingerprinted. Do not paste a token into chat, State Hub, a shell argument, or a handoff file. Root is offline break-glass authority, not a fallback for failure. @@ -53,6 +55,34 @@ mount, `platform-admin` role, and allowed callback with `railiance-platform` and `key-cape`. Do not retry with a workload-specific OIDC role: it is intentionally incapable of OpenBao control-plane administration. +## Exit status and audit + +After argument/configuration validation, the contained envelope returns: + +| Exit | Audit outcome | Meaning | +| --- | --- | --- | +| 0 | `ok` | Login, silent child, self-revocation and cleanup succeeded. | +| 10 | `login_failed` | Login or private-home preflight failed; child did not run. | +| 11 | `child_failed` | Child could not start or exited non-zero. | +| 12 | `child_output` | Child emitted stdout/stderr, even whitespace; takes precedence over child exit failure. | +| 13 | `revoke_unconfirmed` | Child succeeded silently, but self-revocation was not confirmed. | +| 14 | `cleanup_failed` | Private storage cleanup failed; overrides the prior phase. | + +Both `access-audit.log` and `audit.jsonl` record the Warden exit code and phase +outcome, never captured bytes. Revocation warnings accompany child failures too. +Codes 11–14 do not establish that the child made no changes: check its permitted +metadata receipt before retrying. A zero exit from `bao token revoke -self` is +Warden's revocation confirmation; no post-revoke lookup is used. An arbitrary +lookup failure would not prove revocation. + +On the WSL workstation, use the ops-bridge `openbao-ui-railiance01` tunnel at +`http://127.0.0.1:18200` for `BAO_ADDR` and `VAULT_ADDR`, with the founder's TTY +and a working browser opener (`xdg-open`, or the configured OpenBao browser). +`bao.coulomb.social` serves a public notice, not OpenBao. The captured OIDC output +is not a browser fallback. Check the opener before starting an attended login. +The reviewed child must redirect both streams if its tools normally print success +messages; it should write only approved, value-free evidence to its own receipt. + ## Authority - OpenBao policy and role owner: `railiance-platform/docs/openbao.md` diff --git a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md index 8b1a634..d9af961 100644 --- a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md +++ b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md @@ -4,14 +4,14 @@ type: workplan title: "Tamper-resistant credential governance + mass rotation/lockdown (Strand B)" domain: infotech repo: ops-warden -status: active +status: blocked flavor: implementation owner: codex topic_slug: custodian planning_priority: medium planning_order: 27 created: "2026-07-16" -updated: "2026-08-23" +updated: "2026-09-28" state_hub_workstream_id: "21528e8d-a049-523d-9ae1-da7a27cb8bbf" --- @@ -28,7 +28,7 @@ Strand A makes accidental disclosure structurally hard and gives every lane layer: turning that advisory guidance into one-command action and hardening policy governance against silent drift or malicious change. -## Status: active, narrowly on T02 +## Status: blocked on T02 (reviewed 2026-09-28) Activated by the operator on 2026-08-22 after the second activation gate became concrete. `RAILIANCE-WP-0024-T03` now requires an approved recovery window, @@ -40,7 +40,7 @@ heavyweight machinery. Activation is deliberately narrow. No mass-disclosure incident triggers T01, and no fleet policy currently mandates T03's signed policy-manifest reconcile. Those tasks remain `cancel`; cancellation here continues to mean deferred, not -abandoned. T02 alone is `progress`. +abandoned. T02 alone was activated; it now waits on a fresh attended drill. ## Activation gate (promote to `ready` only when ≥1 holds) @@ -89,7 +89,7 @@ each verified capabilities-safe, taint cleared only on success. ```task id: WARDEN-WP-0027-T02 -status: progress +status: wait priority: medium state_hub_task_id: "cae498ee-6307-5d32-9f1b-a471cfcc2536" ``` @@ -287,3 +287,12 @@ manifest, and an attended reconcile can restore it. approved ops-bridge unattended-signing design may still re-evaluate it cert_command cutover - `secrets-engine` `SECRETS-WP-0004` — the parked AppRole apply/handoff + +### 2026-09-28 loose-end review + +T02 now waits and the workplan is blocked. Source containment acceptance is +already complete in railiance-platform's archived RPF-WP-0017; it is not a live +drill receipt. The terminal August scenario remains consumed. Completion still +requires a fresh platform-driven attended emergency seal/unseal scenario, fresh +platform/infra/master receipts and a new founder GO. The cancelled T01/T03 stay +cancelled. No ceremony, rotation or production mutation was performed. diff --git a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md index 9758916..dd4d6bc 100644 --- a/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md +++ b/workplans/WARDEN-WP-0034-layer-model-v07-conformance.md @@ -4,7 +4,7 @@ type: workplan title: "Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule" domain: infotech repo: ops-warden -status: active +status: blocked flavor: implementation depends_on: - WARDEN-WP-0030 @@ -14,7 +14,7 @@ planning_priority: P1 depends_on_workplans: - WARDEN-WP-0030 created: "2026-08-29" -updated: "2026-09-21" +updated: "2026-09-28" state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef" --- @@ -336,3 +336,12 @@ layer-conformance script, and the declaration-route CLI smoke check pass. - `security-layer-model_v0.7.md` §3.4, §6.4, §9.6, §9.7, §11, §13.1 - `net-kingdom/SECURITY-COMPANION.md` v0.2 - `ADR-0002`, `ADR-0003`, `ADR-0004`, `ADR-0005`, `ADR-0009`, `ADR-0010` + +### 2026-09-28 loose-end review + +T05 remains wait; the workplan is blocked on the two outstanding owner answers. +The local ops-mason checkout still has no published stance map, and the available +railiance-infra workplans/docs contain no KRL-versus-TTL acceptance or refusal. +The flex-auth decision-lifetime answer remains accepted. Warden's declaration, +TTL-bound tests and fresh-check behavior are complete; inventing an owner answer +would not satisfy T05. No certificate lifetime or revocation policy was changed. diff --git a/workplans/WARDEN-WP-0036-attended-login-openbao-output.md b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md index 594b5ae..44c3461 100644 --- a/workplans/WARDEN-WP-0036-attended-login-openbao-output.md +++ b/workplans/WARDEN-WP-0036-attended-login-openbao-output.md @@ -8,7 +8,7 @@ status: finished owner: codex topic_slug: attended-login-openbao-output created: "2026-09-01" -updated: "2026-09-01" +updated: "2026-09-28" state_hub_workstream_id: "d844c96e-152d-53fa-bff6-e072125ef66c" --- @@ -41,3 +41,28 @@ OpenBao platform-admin operation with deterministic self-revocation. Completed 2026-09-01. The installed CLI completed the governed Policy Nexus Forgejo source bootstrap with all child output contained, then revoked and removed its isolated helper session. + +### 2026-09-28 contained-result correction (existing T01/T02) + +Addressed the September 21 attended-login failure/audit report under the existing +handoff repair and verification tasks. Failed envelopes now use distinct +non-zero codes 10–14 for login, child failure, child output, unconfirmed revocation +and cleanup failure. Both audit files retain the actual Warden exit code and +phase outcome. Any child bytes, including whitespace, fail closed; the access +advisory now states the silent-child requirement and the playbook documents codes, +retry limits and the WSL tunnel/browser requirements. + +The pre-change checkout already raised exit 5 on ProxyError, so the reported +historical exit-zero failure was not reproduced here. The actual reproduced defect +was unconditional success auditing; the new CLI regression covers failed login +with returncode zero but missing persistence as well as non-zero login, start +failures, child output/failure, revocation and cleanup. Revocation still checks +revoke-self's exit status; it does not misinterpret an arbitrary failed lookup as +proof. The September 23 platform return confirms revoke-self is already granted. + +Validation: focused proxy suite 48 passed; full suite 483 passed, 4 integration +tests deselected by repository default; changed Python files pass Ruff. No live +OIDC, credential read or production operation was used. The earlier September 1 +live operation remains historical evidence, not a live validation of this change. +Automated endpoint/browser preflight and OIDC URL presentation remain optional +inbox suggestions, outside these completed handoff acceptance criteria. diff --git a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md index 58f0372..2bf8391 100644 --- a/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md +++ b/workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md @@ -4,12 +4,12 @@ type: workplan title: "Repoint the whynot-design npm lane to Forgejo" domain: infotech repo: ops-warden -status: active +status: blocked flavor: planning owner: codex topic_slug: whynot-design-forgejo-npm-lane created: "2026-09-04" -updated: "2026-09-27" +updated: "2026-09-28" state_hub_workstream_id: "42a097db-1c24-558e-a724-030bb2b4443e" --- @@ -174,3 +174,12 @@ T03 remains wait and the founder's no-rotation hold remains effective. Resume only on the SECRETS-WP-0006-T06 migration receipt, then rotate through the dedicated package lane and prove a fresh publish plus exact npm view. Do not treat the September 16 OpenRouter key-check receipt as npm acceptance. + +### 2026-09-28 loose-end review + +Workplan state corrected to blocked to match T03's wait and the September 27 +owner return. The governed catalog field and path are already correct. The +founder's no-rotation hold remains binding until SECRETS-WP-0006-T06 supplies +native governed-consumer migration evidence; a dedicated token rotation and a +fresh package publish/exact-version lookup then remain. No credential fetch, +rotation or publication was attempted. diff --git a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md index 54d3528..46f06df 100644 --- a/workplans/WARDEN-WP-0039-explicit-policy-refusal.md +++ b/workplans/WARDEN-WP-0039-explicit-policy-refusal.md @@ -9,7 +9,7 @@ flavor: residual owner: codex topic_slug: custodian created: "2026-09-08" -updated: "2026-09-08" +updated: "2026-09-28" origin: residual origin_ref: HFACT-WP-0001 state_hub_workstream_id: "ae44a935-6fca-514c-a385-4550dd2b1fe8" @@ -106,3 +106,13 @@ wrong-caller, wrong-owner and wrong-tenant negatives. No grant was requested. `secrets-engine` was told this bears on SECRETS-WP-0007-T04 and on how many lanes stay proxied; `gate-house` was told it may reach how the signing lane's approval-consume is built (GH-DEC-2026-005). + +### 2026-09-28 loose-end review + +T03 remains wait and the workplan remains blocked. Re-read the owner's finished +FLEX-WP-0026: no admitted delegated-read binding exists, and its native OpenRouter +contract explicitly does not close WARDEN-WP-0039-T03. FLEX-DEC-2026-015 also +confirms resource.system is runtime policy vocabulary, not a repository rename. +Retain the explicit refusal. A native owner route's success does not grant this +interim proxy general delegated-read authority; exact positive and caller/owner/ +tenant negative evidence is still required for the route that replaces it. diff --git a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md index 79b1a54..44f629d 100644 --- a/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md +++ b/workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md @@ -4,7 +4,7 @@ type: workplan title: "Adopt unknown -> fail_closed behind signing-target classification coverage" domain: infotech repo: ops-warden -status: proposed +status: blocked flavor: planning depends_on: - WARDEN-WP-0032 @@ -16,7 +16,7 @@ depends_on_workplans: - WARDEN-WP-0032 - WARDEN-WP-0034 created: "2026-09-09" -updated: "2026-09-09" +updated: "2026-09-28" state_hub_workstream_id: "c8ee441e-1be1-5219-910c-e79ff23cc9ec" --- @@ -43,7 +43,7 @@ So: coverage first, then the cell. That ordering is the whole holding of ```task id: WARDEN-WP-0040-T01 -status: todo +status: wait priority: high state_hub_task_id: "611f0901-9fb5-5954-8dd0-a860c5f0cbec" ``` @@ -65,7 +65,7 @@ outcome this workplan exists to prevent. ```task id: WARDEN-WP-0040-T02 -status: todo +status: wait priority: high state_hub_task_id: "54ad4864-7bcf-592e-a187-9239a81a0b11" ``` @@ -114,7 +114,7 @@ or the test fails — which is the property that makes the map worth publishing. ```task id: WARDEN-WP-0040-T04 -status: todo +status: done priority: medium state_hub_task_id: "222a8c5d-0c0f-5a1d-98d8-02be7dca3358" ``` @@ -154,3 +154,30 @@ adopted. T01–T03 are unchanged and still gate the conversion. Coverage is now measured rather than asserted (`scripts/report_coverage.py`), so T02's reporting obligation has a tool behind it and the published figure cannot drift from the register's. + +### 2026-09-28 loose-end review + +T04 is done: GH-DEC-2026-011 already answered both transition asks on September 9; +the accepted coverage column and declared-gap disposition are recorded above. +No additional response is required to meet that task's acceptance criterion. + +T01/T02 now wait; the workplan is blocked. Re-ran both coverage reports: +signing targets = 0 resolved / 3 unknown / 1 not-applicable; routing lanes = +3 resolved / 20 unknown / 15 not-applicable. Refreshed pep-stance.yaml's measured +date. These figures describe the checked-in registry and local owner declarations, +not a live PDP query. No zone membership was inferred or changed. + +The exact missing workload resolutions are `ops-bridge-tunnel` for +`agt-state-hub-bridge`, `codex-interhub-bootstrap` for +`agt-codex-interhub-bootstrap`, and `backup-daily` for `atm-backup-daily`. +The seed inventory's `adm-example` is not an admitted repair actor. +Warden's own workload declaration cannot classify those target workloads. +T01 needs an owner-declared continuity actor/target and its authoritative +z2-continuity resolution; T02 retains the owner-routing/declaration follow-up +(ops-bridge, the Inter-Hub execution owner and the backup execution owner). +Without it, changing unknown to fail_closed would deny their issuance during a +PDP outage. Coverage reporting is complete, but owner coordination is not. + +T03 also remains gated on standard acceptance: the local authoritative +net-kingdom/canon/standards/security-layer-model_v0.8.md still says proposed. +No superseding ADR or runtime stance change is warranted before these gates.