feat: complete local layer model v0.7 conformance work
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
This commit is contained in:
tegwick 2026-09-05 01:19:48 +02:00
parent 89b117f640
commit 00145d705e
13 changed files with 279 additions and 27 deletions

View file

@ -5,15 +5,15 @@ title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent session
domain: infotech
repo: ops-warden
status: accepted
version: "1.0"
revision: "1"
version: "1.1"
revision: "2"
owner: ops-warden
binds: "ops-warden; any agent runtime calling warden access"
created: "2026-07-20"
updated: "2026-08-18"
last_reviewed: "2026-08-18"
updated: "2026-09-04"
last_reviewed: "2026-09-04"
review_interval: 6m
enforced_by: "src/warden/access.py (exit 7); OpenBao policy agent-high-risk-boundary"
enforced_by: "src/warden/access.py + src/warden/cli.py (exit 7); OpenBao policy agent-high-risk-boundary"
supersedes: ""
successor: ""
---
@ -48,6 +48,13 @@ refuses to stream the raw value and exits 7.** The agent is not blocked from doi
its work — `--out`, `--exec`, `--wrap` and `--fingerprint` all remain available.
It is blocked from doing its work *in a way that writes the secret into a transcript*.
**Revision 2 also recognizes the issued coding-agent subject.** When
`WARDEN_POLICY_SUBJECT` is the KeyCape-issued
`service:codex:railiance-platform` identity, the same refusal applies without
`WARDEN_AGENT_ID`. The legacy variable remains a fail-toward-safety fallback.
Ops-warden reads this identity marker; it does not validate the token or decide
whether the principal is authorized.
**The boundary is enforced at the credential store as well as at the CLI.** The
OpenBao policy `agent-high-risk-boundary` denies data-read on those paths for agent
tokens, allowing metadata and capabilities only. A control that lives solely in our
@ -73,10 +80,12 @@ retry correctly. Changing it is a breaking change to every agent runtime.
**`risk: high` becomes a load-bearing catalog field** rather than documentation.
Classifying a lane now changes runtime behaviour, so it must be set deliberately.
**We accept that `--unsafe-stdout` still exists for humans.** The boundary keys on
`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known
limit: this ADR raises the floor for cooperating runtimes and hardens the store
behind them; it does not claim to stop a determined caller.
**We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the
issued subject when the runtime presents it and otherwise relies on
`WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This
ADR raises the floor for cooperating runtimes and hardens the store behind them; it
does not claim to stop a determined caller. The OpenBao
`agent-high-risk-boundary` policy is the enforced control.
## Related

View file

@ -1,6 +1,6 @@
# ops-warden — NetKingdom security layer declaration
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.4.md
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md
# Assent: docs/adr/ADR-0010 (ops-warden's own voice, per §11 "who must declare")
# Validate: python3 scripts/check_layer_conformance.py
#
@ -14,7 +14,7 @@
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.4"
standard_version: "0.7"
repository: ops-warden
layer: staff
declared_by: docs/adr/ADR-0010

View file

@ -1,6 +1,6 @@
# ops-warden — PEP unreachable-engine stance map
#
# Framework: net-kingdom/canon/standards/security-layer-model_v0.6.md §6.4, §9.3
# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md §6.4, §9.3, §9.7
# Rule of record: docs/adr/ADR-0009
# Validate: pytest tests/test_layer_conformance.py -k stance
#
@ -18,7 +18,7 @@
schema_version: "0.1"
framework: netkingdom-security-layer-model
standard_version: "0.6"
standard_version: "0.7"
repository: ops-warden
pep_shape: true
declared_by: docs/adr/ADR-0009
@ -67,3 +67,38 @@ input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebu
reconstructability:
bound: "§9.6 attributive — completeness is not claimed"
declared_at: wiki/AuditTrail.md
# §9.7.2 — the certificate-verification PEP has no active revocation channel.
# A previously allowed certificate remains usable until its enforced TTL expires,
# so the visibility deadline is exactly the maximum issued lifetime per actor type.
revocation_visibility:
boundary: "SSH certificate verification at the target host"
mechanism: ttl_expiry
revocation_channel: none
deadline_hours:
adm: 48
agt: 24
atm: 8
note: >-
No CRL or KRL distribution exists. Host-side authorization belongs to
railiance-infra; shortening or actively recalling this window is a joint
design question, not an implied capability of ops-warden.
# §9.6 — signing evidence is attributive, not load-bearing. Measurement as of
# 2026-09-04 found three signature records across 79 calendar days, concentrated
# on only two active days (1 event on 2026-06-17; 2 within 40 minutes on
# 2026-08-22). That bursty operator-driven sample cannot support a useful rate
# threshold, so cadence is explicitly deferred instead of fabricated.
emission_cadence:
classification: attributive
status: deferred
measured_as_of: "2026-09-04"
observed_window:
first: "2026-06-17T23:18:33Z"
last: "2026-09-04T21:21:30Z"
signature_records: 3
active_signature_days: 2
daily_counts:
"2026-06-17": 1
"2026-08-22": 2
reason: "operator-driven issuance is too sparse and bursty for a meaningful rate threshold"

View file

@ -40,6 +40,24 @@
version: 1
entries:
- id: netkingdom-layer-declaration
title: NetKingdom layer declaration and PEP stance-map route
risk: standard
workload_ref:
applicability: not-applicable
reason: "Repository conformance guidance, not a workload credential or runtime action."
need_keywords: [how, declare, layer, declaration, netkingdom, security, companion, pep, pep-shaped, stance, conformance, tooling, staff, engine]
owner_repo: ops-warden
subsystem: NetKingdom layer-conformance routing
warden_executes: false
wiki_ref: wiki/playbooks/netkingdom-layer-declaration.md#worker-checklist
canon_ref: net-kingdom/canon/standards/security-layer-model_v0.7.md
reviewed: "2026-09-04"
status: active
delegation:
mode: permanent
reviewed: "2026-09-04"
- id: ssh-cert-host-access
title: Short-lived SSH certificate for host / ops reachability
# Emits a signed certificate — a public artifact. The private key never leaves the caller (WARDEN-WP-0032-T05).

View file

@ -12,13 +12,21 @@ reuses `expand_handoff` to build the command it runs as the caller.
"""
from __future__ import annotations
import os
from dataclasses import dataclass
from typing import Optional
from typing import Mapping, Optional
from warden.config import ConfigError, load_config
from warden.routing.models import RouteEntry
# KeyCape KEY-WP-0009-T03 is the issued coding-agent identity available today.
# This is intentionally an exact subject allowlist, not a guess based on every
# ``service:*`` identity. OpenBao validates and enforces the credential; this
# module only recognizes the already-issued subject for the advisory CLI guard.
ISSUED_AGENT_SUBJECTS = frozenset({"service:codex:railiance-platform"})
@dataclass
class ExpandedHandoff:
"""Handoff templates with `<domain>` substituted when a domain is supplied.
@ -34,6 +42,25 @@ class ExpandedHandoff:
exec_capable: bool
def agent_read_boundary_identity(
environ: Mapping[str, str] | None = None,
) -> str | None:
"""Return the issued/fallback agent marker used by the advisory read guard.
``WARDEN_POLICY_SUBJECT`` carries the principal identity used by the policy
request. When it names an issued coding-agent subject, prefer it over the
self-declared legacy marker. This function does not validate a token or
render an authorization decision; OpenBao's agent policy is the enforced
boundary. ``WARDEN_AGENT_ID`` remains a fail-toward-safety fallback.
"""
env = os.environ if environ is None else environ
issued_subject = str(env.get("WARDEN_POLICY_SUBJECT") or "").strip()
if issued_subject in ISSUED_AGENT_SUBJECTS:
return issued_subject
fallback = str(env.get("WARDEN_AGENT_ID") or "").strip()
return fallback or None
def _sub_domain(value: Optional[str], domain: Optional[str]) -> Optional[str]:
if value and domain:
return value.replace("<domain>", domain)

View file

@ -2,7 +2,6 @@
from __future__ import annotations
import json
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Annotated, List, Optional
@ -1309,7 +1308,9 @@ def _access_proxy(
# T04 — agent identity on a high-risk lane: never stream raw secret data.
# Agents may use sanctioned transports (--out / --exec / --wrap / --fingerprint).
agent_id = os.environ.get("WARDEN_AGENT_ID", "").strip()
from warden.access import agent_read_boundary_identity
agent_id = agent_read_boundary_identity()
raw_value_stream = (
not is_login and not do_exec and not wrap and not out_path and not fingerprint
)

View file

@ -6,7 +6,7 @@ from pathlib import Path
from typer.testing import CliRunner
from warden.access import expand_handoff, policy_gate_status
from warden.access import agent_read_boundary_identity, expand_handoff, policy_gate_status
from warden.cli import app
from warden.routing.models import RouteEntry
@ -64,6 +64,33 @@ def test_policy_gate_status_no_config(monkeypatch, tmp_path):
assert "advisory" in policy_gate_status()
def test_issued_coding_agent_subject_drives_read_boundary_without_legacy_marker():
env = {"WARDEN_POLICY_SUBJECT": "service:codex:railiance-platform"}
assert agent_read_boundary_identity(env) == "service:codex:railiance-platform"
def test_legacy_agent_marker_remains_fail_toward_safety_fallback():
env = {"WARDEN_AGENT_ID": "grok"}
assert agent_read_boundary_identity(env) == "grok"
def test_non_agent_policy_subject_does_not_manufacture_agent_identity():
env = {"WARDEN_POLICY_SUBJECT": "iam:bernd"}
assert agent_read_boundary_identity(env) is None
def test_explicit_empty_environment_does_not_inherit_process_identity(monkeypatch):
monkeypatch.setenv("WARDEN_AGENT_ID", "codex")
monkeypatch.setenv("WARDEN_POLICY_SUBJECT", "service:codex:railiance-platform")
assert agent_read_boundary_identity({}) is None
assert agent_read_boundary_identity() == "service:codex:railiance-platform"
def test_operator_subject_does_not_override_legacy_agent_marker():
env = {"WARDEN_POLICY_SUBJECT": "iam:bernd", "WARDEN_AGENT_ID": "codex"}
assert agent_read_boundary_identity(env) == "codex"
# --- CLI -------------------------------------------------------------------
def test_access_advisory_output(monkeypatch):

View file

@ -30,6 +30,7 @@ class TestDeclaration:
d = _decl()
assert d["repository"] == "ops-warden"
assert d["layer"] == "staff"
assert d["standard_version"] == "0.7"
# §11: "only the repository's own file, in its own voice, conforms."
assert d["declared_by"] == "docs/adr/ADR-0010"
@ -131,3 +132,21 @@ class TestPepStanceMap:
"""§6.4 obligation 2: caching an input claim is permitted; caching the
answer is a second decision point deciding early (§6.1)."""
assert self._stance()["verdict_caching"] == "none"
def test_revocation_visibility_deadline_equals_enforced_ttl_policy(self):
"""§9.7.2: a published replay window must not drift from issuance."""
from warden.models import ActorType, MAX_TTL_HOURS
published = self._stance()["revocation_visibility"]
expected = {actor.value: MAX_TTL_HOURS[actor] for actor in ActorType}
assert published["deadline_hours"] == expected
assert published["mechanism"] == "ttl_expiry"
assert published["revocation_channel"] == "none"
def test_attributive_emission_cadence_deferral_carries_measurement(self):
cadence = self._stance()["emission_cadence"]
assert cadence["classification"] == "attributive"
assert cadence["status"] == "deferred"
assert cadence["observed_window"]["signature_records"] == 3
assert cadence["observed_window"]["active_signature_days"] == 2
assert cadence["reason"]

View file

@ -590,6 +590,27 @@ def test_access_agent_high_risk_raw_stream_refused(tmp_path, monkeypatch):
assert "agent read-boundary" in r.output.lower() or "risk=high" in r.output.lower()
def test_access_issued_agent_subject_refuses_raw_stream_without_legacy_marker(
tmp_path, monkeypatch
):
"""WP-0034 T02: the issued KeyCape subject is sufficient for exit 7."""
_proxy_env(monkeypatch, tmp_path)
monkeypatch.setenv("VAULT_TOKEN", "caller-token")
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
monkeypatch.setenv(
"WARDEN_POLICY_SUBJECT", "service:codex:railiance-platform"
)
r = runner.invoke(
app,
[
"access", "railiance-backup-offsite-lane",
"--fetch", "--unsafe-stdout",
],
)
assert r.exit_code == 7, r.output
assert "service:codex:railiance-platform" in r.output
def test_access_agent_high_risk_fingerprint_allowed(tmp_path, monkeypatch):
"""Agents may use --fingerprint on high-risk lanes (no raw value)."""
_proxy_env(monkeypatch, tmp_path)

View file

@ -143,6 +143,18 @@ def test_route_find_vault_token_ops_warden_prefers_broker_lane():
assert matches[0].id == "ops-warden-warden-sign-token"
def test_route_find_layer_declaration_resolves_to_reference_playbook():
catalog = load_catalog(_repo_catalog())
entry = catalog.find("how do I declare my layer", limit=1)[0]
assert entry.id == "netkingdom-layer-declaration"
assert entry.owner_repo == "ops-warden"
assert entry.wiki_ref.endswith(
"netkingdom-layer-declaration.md#worker-checklist"
)
assert entry.canon_ref.endswith("security-layer-model_v0.7.md")
assert entry.exec_capable is False
def test_whynot_design_npm_lane_is_concrete_and_resolvable():
"""The provisioned Forgejo npm lane is exact, concrete, and resolvable."""
catalog = load_catalog(_repo_catalog())

View file

@ -14,9 +14,10 @@ def test_repo_catalog_uses_only_explicit_workload_references():
assert report["ok"] is True
assert len(report["resolved"]) == 3
assert len(report["unknown"]) == 18
# 11 since WARDEN-WP-0033: the two NetKingdom SSO lanes (c374d41) are
# provider/control-plane credentials, not workload delivery lanes.
assert len(report["not_applicable"]) == 11
# 12 since WARDEN-WP-0034: the layer-declaration route is repository
# conformance guidance, not a workload delivery lane. The two NetKingdom SSO
# lanes (c374d41) remain provider/control-plane credentials as well.
assert len(report["not_applicable"]) == 12
assert {row["lane"] for row in report["resolved"]} == {
"ops-warden-warden-sign-token",
"issue-core-ingestion-api-key",

View file

@ -0,0 +1,37 @@
# NetKingdom layer declaration route
Date: 2026-09-04
Catalog: `netkingdom-layer-declaration`
Doctrine owner: `gate-house`
Path steward: `ops-warden`
This page is a route from the accepted NetKingdom security companion to the
reference declaration artifacts. It does not redefine the layer model.
## Worker checklist
1. Read `net-kingdom/SECURITY-COMPANION.md`, then use the accepted
`net-kingdom/canon/standards/security-layer-model_v0.7.md` for the normative
layer, Tooling-contact, and PEP obligations.
2. Use ops-warden's `layer.yaml` as the machine-readable declaration reference.
Adapt its repository, layer, contacts, and ownership facts; do not copy
ops-warden-specific claims as your own.
3. If the repository is PEP-shaped, use `pep-stance.yaml` as the stance-map
reference and publish the resulting path in the repository's layer declaration.
4. Adapt `scripts/check_layer_conformance.py` and
`tests/test_layer_conformance.py`, then run both checks in the declaring repo.
5. Send the declaration and any stance-map inventory pointer to `gate-house` for
doctrine/register review. Route credential or operational-lane questions back
through `warden route`; do not place doctrine in the routing catalog.
Reference checks in this checkout:
```bash
python3 scripts/check_layer_conformance.py
pytest tests/test_layer_conformance.py
```
## Ownership boundary
`gate-house` owns what the model requires. Each repository owns the truth of its
own declaration. `ops-warden` owns only this discoverable path to those sources.

View file

@ -4,14 +4,14 @@ 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: ready
status: active
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0030
created: "2026-08-29"
updated: "2026-08-29"
updated: "2026-09-05"
state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef"
---
@ -36,7 +36,7 @@ completed directly; none of these qualify.
```task
id: WARDEN-WP-0034-T01
status: todo
status: done
priority: high
state_hub_task_id: "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484"
```
@ -60,9 +60,16 @@ of a revocation channel is stated rather than left to inference.
**Not in scope:** shortening it. Whether 48h is acceptable is a joint question with
`railiance-infra` (KRL distribution) and is T05's to raise, not this task's to decide.
Completed 2026-09-04. `pep-stance.yaml` now publishes TTL expiry as the only
revocation mechanism, explicitly records that no CRL/KRL channel exists, and
states `adm=48h`, `agt=24h`, and `atm=8h`. The conformance test derives the
expected map from `ActorType`/`MAX_TTL_HOURS`, so declaration and issuance
policy cannot drift independently. The layer and stance declarations now name
the accepted v0.7 standard.
```task
id: WARDEN-WP-0034-T02
status: todo
status: done
priority: high
state_hub_task_id: "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55"
```
@ -90,9 +97,18 @@ enforced/advisory split is written down.
identity claim, adjacent to deciding, and `ADR-0002`/§6 both point away from it. Read
the identity; do not adjudicate it.
Completed 2026-09-04. The advisory CLI guard now prefers the exact issued
KeyCape subject `service:codex:railiance-platform` from
`WARDEN_POLICY_SUBJECT`, then falls back to `WARDEN_AGENT_ID`. Tests prove the
issued subject triggers exit 7 without the legacy marker, the fallback remains,
and an operator subject does not manufacture an agent identity. ADR-0004
revision 2 records the enforced/advisory split: Warden reads the marker and
does not validate a token; OpenBao's `agent-high-risk-boundary` is the real
enforcement.
```task
id: WARDEN-WP-0034-T03
status: todo
status: done
priority: medium
state_hub_task_id: "a891b32c-b0a7-59f6-a5cd-977be65c09ca"
```
@ -111,9 +127,16 @@ that as the finding with the distribution that shows it, and declare the deferra
**Done when:** either a declared cadence with its derivation, or a declared deferral
carrying the data that justifies it.
Completed 2026-09-04 with an explicit measured deferral. The signatures log
contains three issuances across the 79-day observation window, on only two
active days: one on 2026-06-17 and two within 40 minutes on 2026-08-22. The
unified audit carries the latter two events. `pep-stance.yaml` records the
counts, dates, attributive classification, and reason that this sparse,
operator-driven burst cannot support a meaningful rate threshold.
```task
id: WARDEN-WP-0034-T04
status: todo
status: done
priority: medium
state_hub_task_id: "94e73daa-f74d-51fd-8639-68896a4066ee"
```
@ -138,9 +161,15 @@ to copy — `layer.yaml`, `pep-stance.yaml`, `check_layer_conformance.py`,
playbook is reachable from the catalog. **Not** a restatement of the companion — a
pointer layer, per `ADR-0001`.
Completed 2026-09-04. Catalog entry `netkingdom-layer-declaration` resolves the
exact query to `wiki/playbooks/netkingdom-layer-declaration.md`. The short
playbook points at the accepted companion/statute, the four reference artifacts
and checks, and the gate-house review path while explicitly leaving doctrine
with gate-house and declaration truth with each repository.
```task
id: WARDEN-WP-0034-T05
status: todo
status: wait
priority: low
state_hub_task_id: "7d1b3c82-9b96-5087-a53a-496212909029"
```
@ -162,8 +191,24 @@ fix them (`ADR-0003`, `ADR-0005`).
**Done when:** both are routed with reasoning, and the answers recorded either way —
including a refusal, which is an equally good answer.
Raised 2026-09-04 and waiting for answers. Ops-mason received the stance-map
reference and classification question (message `6ddd6cc7`). Railiance-infra
received the KRL-versus-TTL question (message `3bfa306e`). Flex-auth, as the
current access-engine implementation, received the decision-lifetime semantics
question (message `02c99a74`). No implementation or policy decision was assigned
to another owner by these messages.
## Related
Review 2026-09-05: reviewed the pending implementation and fixed the identity
helper so an explicitly empty environment does not inherit the process identity.
Regression coverage also proves an operator subject cannot override the legacy
agent marker. Refreshed the generated high-risk path artifact after the catalog
changes. T05 remains waiting: no corresponding reply was present in the inbox,
and the checked owner workplans contained no resolution of these questions.
Validation: 415 unit tests and four SSH integration tests pass; Ruff, the
layer-conformance script, and the declaration-route CLI smoke check pass.
- `history/2026-08-29-v07-scope-intent-assessment.md` — the gap analysis behind this plan
- `history/2026-08-29-layer-model-v04-review.md`, `-v06-review.md` — the two prior reviews
- `security-layer-model_v0.7.md` §3.4, §6.4, §9.6, §9.7, §11, §13.1