Compare commits
57 commits
mason/stat
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b1a508610 | |||
| a942ce805d | |||
| 66db87e6ae | |||
| 9c01a8a212 | |||
|
|
d8791a9296 | ||
| 3aedd8f844 | |||
| 1e3eb8df2a | |||
| 83fdd08f88 | |||
| 8e1b621116 | |||
| df48ee96e0 | |||
| 3a01b8f1b5 | |||
|
|
084bca783b | ||
|
|
589d9067da | ||
| 541cd9043f | |||
| 19bb75c614 | |||
| 31d9b6671c | |||
| 11e5e8be0f | |||
|
|
89a8dea2a1 | ||
| b9ca0cc3f5 | |||
| c1330049d9 | |||
| 96dbb22aab | |||
| 37b8f39bd0 | |||
| afc50354df | |||
| 00145d705e | |||
| 89b117f640 | |||
| 81f28ff814 | |||
|
|
efabe526d8 | ||
|
|
60cf826c43 | ||
| 8afcc9c321 | |||
|
|
529feeac49 | ||
| 9770d6ad66 | |||
|
|
31694ab64d | ||
| eddb5d426b | |||
| 4fee839b11 | |||
| 8f01eefb1e | |||
| b4c1d3900a | |||
|
|
4e267179db | ||
|
|
d18d18e313 | ||
| 2e3ff772f4 | |||
| fd08950231 | |||
| 94f32bd160 | |||
| ec625873fb | |||
| f815bb35ca | |||
| 57d39ede0f | |||
|
|
f289465b90 | ||
|
|
a45280f30d | ||
| 61c992923c | |||
| ee94c18938 | |||
| d7f4ebcfe0 | |||
|
|
f766400563 | ||
| 70d8b503f0 | |||
| 85d3078bae | |||
|
|
467635e84b | ||
| 15730bb650 | |||
| 347e47ce8e | |||
| 37c387bd34 | |||
| 030362085a |
59 changed files with 6599 additions and 146 deletions
|
|
@ -16,6 +16,8 @@ distinction that matters here.
|
||||||
| `ADR-0006` | Enforcement is zone-scoped, never a global flag |
|
| `ADR-0006` | Enforcement is zone-scoped, never a global flag |
|
||||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
||||||
| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after |
|
| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after |
|
||||||
|
| `ADR-0009` | Adopt security-zones v0.1 as a consumer; membership is compiled, never inferred |
|
||||||
|
| `ADR-0010` | ops-warden is Staff: it owns access lanes, never access rules; doctrine belongs to gate-house |
|
||||||
|
|
||||||
### Owned versus inherited — check `owner:` before changing anything
|
### Owned versus inherited — check `owner:` before changing anything
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,16 @@
|
||||||
# Custodian Brief — ops-warden
|
# Custodian Brief — ops-warden
|
||||||
|
|
||||||
**Domain:** infotech
|
**Domain:** infotech
|
||||||
**Last synced:** 2026-08-25 18:17 UTC
|
**Last synced:** 2026-09-09 14:41 UTC
|
||||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||||
|
|
||||||
## Active Workstreams
|
## Active Workstreams
|
||||||
|
|
||||||
### Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed
|
### Layer model v0.7 conformance — state the deadline, bind the agent boundary, steward the estate's newest rule
|
||||||
Progress: 5/6 done | workplan_id: `4627d89b-4b00-562a-81e9-76e96f90fa7e`
|
Progress: 4/5 done | workplan_id: `ae3ff76f-883d-5e2f-b6aa-144d61e8fdef`
|
||||||
|
|
||||||
**Open tasks:**
|
**Open tasks:**
|
||||||
- ! Tasks `5acac140`
|
- ! Tasks `7d1b3c82`
|
||||||
|
|
||||||
### Tamper-resistant credential governance + mass rotation/lockdown (Strand B)
|
### Tamper-resistant credential governance + mass rotation/lockdown (Strand B)
|
||||||
Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
|
Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
|
||||||
|
|
@ -19,11 +19,18 @@ Progress: 2/3 done | workplan_id: `21528e8d-a049-523d-9ae1-da7a27cb8bbf`
|
||||||
**Open tasks:**
|
**Open tasks:**
|
||||||
- ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee`
|
- ► Task: Graded lockdown / break-glass with explicit trust-root `cae498ee`
|
||||||
|
|
||||||
## Inbox Hygiene
|
### Repoint the whynot-design npm lane to Forgejo
|
||||||
|
Progress: 2/3 done | workplan_id: `42a097db-1c24-558e-a724-030bb2b4443e`
|
||||||
|
|
||||||
**Stale unread:** 9 message(s) older than 3 day(s) — triage at session start.
|
**Open tasks:**
|
||||||
**Missing thread_id:** 7 unread message(s) lack supersession chains.
|
- ! Prove routing and publication `a8b1b855`
|
||||||
- ! secrets-engine: Re: Need the warden-sign live apply/handoff (FLEX-WP-0007 T4) `80456912`
|
|
||||||
|
### Preserve explicit policy caller refusals before credential and CA effects
|
||||||
|
Progress: 2/3 done | workplan_id: `ae44a935-6fca-514c-a385-4550dd2b1fe8`
|
||||||
|
|
||||||
|
**Open tasks:**
|
||||||
|
- ! Resolve the credential proxy's admitted policy binding `8ca28b63`
|
||||||
|
*(wait: The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership.)*
|
||||||
|
|
||||||
---
|
---
|
||||||
## MCP Orientation (when available)
|
## MCP Orientation (when available)
|
||||||
|
|
|
||||||
2360
.repo-manager/index.json
Normal file
2360
.repo-manager/index.json
Normal file
File diff suppressed because it is too large
Load diff
13
AGENTS.md
13
AGENTS.md
|
|
@ -221,6 +221,19 @@ Install bundled memory: `make install-all` then `make check-memory` from
|
||||||
|
|
||||||
## Workplan Convention (ADR-001)
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
|
### State Hub reads and caller identity
|
||||||
|
|
||||||
|
Use `GET /workplans/{id}` and `GET /tasks/?workplan_id=<id>` for reads.
|
||||||
|
Do not send the legacy `workstream_id` query parameter or call `/workstreams/`
|
||||||
|
routes (410 Gone). Keep `state_hub_workstream_id` in file frontmatter unchanged.
|
||||||
|
Send `X-StateHub-Component: ops-warden` on direct HTTP requests so operational
|
||||||
|
reviews can attribute callers. Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsS -H 'X-StateHub-Component: ops-warden' \
|
||||||
|
'http://127.0.0.1:8000/tasks/?workplan_id=<workplan-uuid>'
|
||||||
|
```
|
||||||
|
|
||||||
Work items originate as files in this repo — not in the hub. The hub is a
|
Work items originate as files in this repo — not in the hub. The hub is a
|
||||||
read/cache/index layer that rebuilds from files.
|
read/cache/index layer that rebuilds from files.
|
||||||
|
|
||||||
|
|
|
||||||
120
INTENT.md
120
INTENT.md
|
|
@ -1,5 +1,68 @@
|
||||||
|
---
|
||||||
|
layer: Staff
|
||||||
|
role: null # Engines only: PDP | PIP | Evidence | Lifecycle
|
||||||
|
pep_shaped: true # §6.4 — issuing a certificate is a protected side effect
|
||||||
|
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
|
||||||
|
standard_version: "0.7"
|
||||||
|
companion: net-kingdom/SECURITY-COMPANION.md
|
||||||
|
declaration: layer.yaml
|
||||||
|
pep_stance: pep-stance.yaml
|
||||||
|
assent: docs/adr/ADR-0010
|
||||||
|
---
|
||||||
|
|
||||||
# INTENT
|
# INTENT
|
||||||
|
|
||||||
|
> **ops-warden is Staff, and PEP-shaped.** Declared here in its own voice, per
|
||||||
|
> `security-layer-model_v0.7` §11 — a layer stated *about* a repository by another
|
||||||
|
> repository is not a declaration. The standard is **accepted**; its operative form
|
||||||
|
> is `net-kingdom/SECURITY-COMPANION.md`. ops-warden's assent is `ADR-0010`, and its
|
||||||
|
> reviews of v0.4, v0.6 and v0.7 are in `history/`.
|
||||||
|
>
|
||||||
|
> **Staff** because ops-warden's core function is judgement, not computation: it
|
||||||
|
> decides which lane a need belongs to and stewards the paths through the estate's
|
||||||
|
> rules. Its artifacts are runbooks, routing decisions, workplans. **PEP-shaped**
|
||||||
|
> because issuing a certificate is a protected side effect — a shape, not a layer
|
||||||
|
> (§6.4). ops-warden renders no authorization decision and never will; it consumes
|
||||||
|
> them from `access-engine`.
|
||||||
|
>
|
||||||
|
> **The estate's front door for paths.** The companion routes the whole estate here:
|
||||||
|
> *"For how to get something done — which lane, which credential, which route — ask
|
||||||
|
> `ops-warden`. This document says what the rules are; ops-warden stewards the paths
|
||||||
|
> through them."* That is a standing obligation, not a compliment: every rule
|
||||||
|
> gate-house writes needs a path someone can actually walk, and ops-warden owes the
|
||||||
|
> estate that path.
|
||||||
|
>
|
||||||
|
> **The declarations are files, not this note** — prose cannot distinguish a
|
||||||
|
> declaration from a transcribed review (§11):
|
||||||
|
>
|
||||||
|
> | File | Declares | Enforced by |
|
||||||
|
> | --- | --- | --- |
|
||||||
|
> | `layer.yaml` | every Tooling contact, mapped to a §5.1/§5.2/§5.3 shape, plus the non-Tooling clients so the check is total | `scripts/check_layer_conformance.py`, `tests/test_layer_conformance.py` |
|
||||||
|
> | `pep-stance.yaml` | the unreachable-engine stance map (§6.4 obl. 3), total per zone | test asserts the published map **equals** the shipped default |
|
||||||
|
> | `tenancy.yaml` | tenancy posture, and `z1-operational` zone membership | `ADR-0009` |
|
||||||
|
>
|
||||||
|
> Both are cited in the standard as the estate's reference forms (§11, §6.4, §13.1).
|
||||||
|
>
|
||||||
|
> **Two declared engine gaps (§5.3), tracked non-conformance and not conformance.**
|
||||||
|
> `VaultCA` signs over a direct OpenBao client and `warden desk` shells `bao kv put`.
|
||||||
|
> Intended owner **secrets-engine**; blocked on no engine exposing an SSH-CA or
|
||||||
|
> attended-provisioning surface; reviewed quarterly; registered in statute §13.
|
||||||
|
> ops-warden keeps signing while the gap is open — refusing would remove production
|
||||||
|
> host access to close a documentation gap.
|
||||||
|
>
|
||||||
|
> **The agent principal (§3.4).** ops-warden is operated by agents as well as people,
|
||||||
|
> and they share the layer but not the blast radius. No standing credential; tool use
|
||||||
|
> is a conduit or an engine API and there is no third route; **tool availability is
|
||||||
|
> not permission**, which is exactly what `ADR-0004`'s read-boundary enforces; agent
|
||||||
|
> memory is not a state plane. Session semantics belong to `glas-harness`, not here.
|
||||||
|
>
|
||||||
|
> **Evidence (§9.6).** ops-warden's audit trail is **attributive**, not load-bearing:
|
||||||
|
> no control branches on the presence of a signing record. Emission is deliberately
|
||||||
|
> non-atomic so an audit-store failure cannot remove production host access — a trade
|
||||||
|
> the standard sanctions, declared in `wiki/AuditTrail.md`, registered in §13. If any
|
||||||
|
> future control ever gates on this trail, that trade must be revisited before it
|
||||||
|
> ships.
|
||||||
|
|
||||||
> This file captures **why this repository exists**, the **direction it is
|
> This file captures **why this repository exists**, the **direction it is
|
||||||
> moving toward**, and the **kind of system it is meant to become**.
|
> moving toward**, and the **kind of system it is meant to become**.
|
||||||
> It is intentionally **aspirational and stable**, not a description of
|
> It is intentionally **aspirational and stable**, not a description of
|
||||||
|
|
@ -77,30 +140,52 @@ owns one lane and points at the rest:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## NetKingdom Security Literacy
|
## Lane routing — who owns which need
|
||||||
|
|
||||||
ops-warden should be fluent in the platform architecture documented in
|
**This is a runbook, not doctrine.** Security doctrine, the authority model, and the
|
||||||
`net-kingdom` — especially:
|
security curriculum are **gate-house's** (`security-layer-model_v0.7` §8).
|
||||||
|
ops-warden references them and does not restate them. What follows is lane
|
||||||
|
stewardship: which subsystem owns which need, and what ops-warden does about it.
|
||||||
|
|
||||||
| Plane / component | Role in access | ops-warden relationship |
|
The machine-readable form is `registry/routing/catalog.yaml`, and the executable form
|
||||||
| --- | --- | --- |
|
is `warden plan "<need>"` / `warden route find`. Prefer either over this table — it is
|
||||||
| **key-cape / Keycloak** | Identity — who is the actor, MFA, IAM Profile claims | Instruct identity path; do not re-implement OIDC |
|
orientation, and the catalog is the source of truth (`ADR-0001`).
|
||||||
| **flex-auth + Topaz** | Authorization — may this actor perform this action | Caller-side policy gate shipped (opt-in); production flip is flex-auth's |
|
|
||||||
| **OpenBao** | Runtime secrets — API keys, dynamic creds, leases, audit | Instruct custody paths; SSH engine is signing backend only; proxy reads as caller when `exec_capable` |
|
| Component | Layer | Owns | ops-warden relationship |
|
||||||
| **secrets-engine** | Owner-native secret-exec (`secrets-engine exec`) | Route provisioned exec lanes (e.g. npm publish); ops-warden does not hold tokens |
|
| --- | --- | --- | --- |
|
||||||
| **railiance-platform** (credential broker) | Scoped lease grants (`credential exec`) | Route `warden-sign` token needs; ops-warden does not mint OpenBao tokens |
|
| **gate-house** | Staff | Security doctrine, invariants, authority ceilings, authority context, conformance review, curriculum | **Route doctrine and authority-model questions here.** Not policy decisions — those go to access-engine |
|
||||||
| **tenant-engine** | Tenant/client secret custody and front door | Route tenant lanes once fronted; ops-warden's current tenant proxies are interim (§9) |
|
| **access-engine** (`flex-auth`) | Engine | **The policy decision** — whether an actor may act. The only decision point in NetKingdom | Consume decisions; caller-side pre-sign gate. ops-warden never renders or caches one |
|
||||||
| **user-engine** | End-user identity and account lifecycle | No ops-warden lane today; route rather than absorb |
|
| **key-cape / Keycloak** | Tooling | Identity — who the actor is, MFA, IAM Profile claims | Instruct the identity path; do not re-implement OIDC |
|
||||||
| **ops-warden** | Operational SSH certificates — short-lived host access | **Own and issue** this lane |
|
| **OpenBao** | Tooling | Runtime secrets — API keys, dynamic creds, leases, audit | Instruct custody paths; proxy reads as the caller when `exec_capable`. Direct client use is the declared exception above |
|
||||||
| **ops-bridge** | Tunnel transport — consumes certs via `cert_command` | Primary consumer; document integration |
|
| **secrets-engine** | Engine | Credential abstraction, custody, lifecycle; owner-native exec | Route provisioned exec lanes (e.g. npm publish). **Intended owner of the SSH-CA surface** |
|
||||||
| **railiance-infra** | Host principals, force-command, SSH hardening | Instruct host-side deployment; do not own Ansible |
|
| **tenant-engine** | Engine | Tenant/client secret custody and front door | Route tenant lanes once fronted; current tenant proxies are interim (section 9) |
|
||||||
| **railiance-platform** (deploy) | OpenBao/K8s/platform service deployment | Instruct production endpoints; do not deploy clusters |
|
| **user-engine** | Engine | Users, accounts, memberships | No ops-warden lane today; route rather than absorb |
|
||||||
|
| **zone-engine** | Engine | Zone identity and membership | Consume compiled membership; ops-warden declares `z1-operational` (`ADR-0009`) |
|
||||||
|
| **railiance-platform** (broker) | — | Scoped lease grants (`credential exec`) | Route `warden-sign` token needs; ops-warden does not mint OpenBao tokens |
|
||||||
|
| **ops-mason** | Staff | Building and tearing down access routes and perimeters | Peer lane owner; same lane/rule demarcation applies |
|
||||||
|
| **ops-warden** | Staff | **Operational access lanes** — short-lived SSH certificates, routing, stewardship, runbooks | **Own and issue** the SSH lane |
|
||||||
|
| **ops-bridge** | Staff | Tunnel transport — consumes certs via `cert_command` | Primary consumer; document integration |
|
||||||
|
| **railiance-infra** | — | Host principals, force-command, SSH hardening | Instruct host-side deployment; do not own Ansible |
|
||||||
|
| **kings-guard** | Staff | Adaptive defence, observation, containment; publishes posture | Posture may reduce authority, never manufacture it |
|
||||||
|
|
||||||
|
### Access lane versus access rule
|
||||||
|
|
||||||
|
Normative, per `security-layer-model_v0.7` §8 and assented to in `ADR-0010`:
|
||||||
|
|
||||||
|
- **access lane** — ops-warden and ops-mason. *How* a worker reaches a host.
|
||||||
|
- **access rule** — access-engine. *Whether* they may.
|
||||||
|
|
||||||
|
ops-warden owns the route and never the decision. A question about whether an actor
|
||||||
|
may do something is not an ops-warden question, however it arrives.
|
||||||
|
|
||||||
Canonical references:
|
Canonical references:
|
||||||
|
|
||||||
|
- `net-kingdom/SECURITY-COMPANION.md` — the operative form; start here
|
||||||
|
- `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted; §5 shapes, §6.4 PEP, §8 vocabulary)
|
||||||
- `net-kingdom/docs/platform-identity-security-architecture.md`
|
- `net-kingdom/docs/platform-identity-security-architecture.md`
|
||||||
- `net-kingdom/docs/responsibility-map.md`
|
- `net-kingdom/docs/responsibility-map.md`
|
||||||
- `wiki/AccessManagementDirective.md` (ops SSH actor model)
|
- `wiki/AccessManagementDirective.md` (ops SSH actor model)
|
||||||
|
- `.claude/rules/credential-routing.md` (agent-facing runbook — stays inline by design)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -123,7 +208,8 @@ Canonical references:
|
||||||
| Need | Route to |
|
| Need | Route to |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| OIDC login, MFA, human identity claims | key-cape / Keycloak (NetKingdom IAM Profile) |
|
| OIDC login, MFA, human identity claims | key-cape / Keycloak (NetKingdom IAM Profile) |
|
||||||
| Policy decision — may actor X access resource Y | flex-auth |
|
| Security doctrine, invariants, authority model | gate-house |
|
||||||
|
| Policy decision — may actor X access resource Y | access-engine (`flex-auth`) |
|
||||||
| API keys, provider secrets, DB creds, object-storage STS | OpenBao (+ flex-auth policy where required) |
|
| API keys, provider secrets, DB creds, object-storage STS | OpenBao (+ flex-auth policy where required) |
|
||||||
| Inter-Hub operator keys, LLM provider credentials | OpenBao or approved operator secret store |
|
| Inter-Hub operator keys, LLM provider credentials | OpenBao or approved operator secret store |
|
||||||
| Tunnel lifecycle, port forwarding | ops-bridge |
|
| Tunnel lifecycle, port forwarding | ops-bridge |
|
||||||
|
|
|
||||||
4
Makefile
4
Makefile
|
|
@ -24,12 +24,12 @@ sync:
|
||||||
uv sync
|
uv sync
|
||||||
|
|
||||||
install-warden:
|
install-warden:
|
||||||
uv tool install . --force
|
uv tool install . --force --reinstall-package ops-warden
|
||||||
|
|
||||||
install-memory:
|
install-memory:
|
||||||
@test -d "$(PHASE_MEMORY_REPO)" || (echo "ERROR: phase-memory not found at $(PHASE_MEMORY_REPO). Clone it or set PHASE_MEMORY_REPO."; exit 1)
|
@test -d "$(PHASE_MEMORY_REPO)" || (echo "ERROR: phase-memory not found at $(PHASE_MEMORY_REPO). Clone it or set PHASE_MEMORY_REPO."; exit 1)
|
||||||
@test -f "$(PHASE_MEMORY_REPO)/pyproject.toml" || (echo "ERROR: $(PHASE_MEMORY_REPO) is not a phase-memory checkout."; exit 1)
|
@test -f "$(PHASE_MEMORY_REPO)/pyproject.toml" || (echo "ERROR: $(PHASE_MEMORY_REPO) is not a phase-memory checkout."; exit 1)
|
||||||
uv tool install . --with-editable "$(PHASE_MEMORY_REPO)" --force
|
uv tool install . --with-editable "$(PHASE_MEMORY_REPO)" --force --reinstall-package ops-warden
|
||||||
|
|
||||||
install: install-warden
|
install: install-warden
|
||||||
|
|
||||||
|
|
|
||||||
29
SCOPE.md
29
SCOPE.md
|
|
@ -85,6 +85,28 @@ cutover, flex-auth publishing the zone-aware pre-sign stance package,
|
||||||
the owner-driven WP-0015 canon landing, and — newly named — the missing owner front
|
the owner-driven WP-0015 canon landing, and — newly named — the missing owner front
|
||||||
doors that keep ops-warden holding interim lanes (secrets-engine, tenant-engine).
|
doors that keep ops-warden holding interim lanes (secrets-engine, tenant-engine).
|
||||||
|
|
||||||
|
### Layer-model conformance (v0.7, accepted)
|
||||||
|
|
||||||
|
ops-warden declares **Staff**, **PEP-shaped**, in `INTENT.md` frontmatter and in its
|
||||||
|
own voice — `security-layer-model_v0.7` §11. Shipped declaration artifacts, both
|
||||||
|
cited in the standard as the estate's reference forms:
|
||||||
|
|
||||||
|
| Artifact | Declares | Status |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `layer.yaml` | 5 Tooling contacts mapped to §5.1/§5.2/§5.3 shapes + non-Tooling clients so the check is total | shipped; named reference form (§11) |
|
||||||
|
| `pep-stance.yaml` | unreachable-engine stance map, total per zone | shipped; registered in statute §13.1 (§6.4 obl. 3) |
|
||||||
|
| `scripts/check_layer_conformance.py` | every direct Tooling client maps to a declared shape | shipped; CI-enforced |
|
||||||
|
| `tests/test_layer_conformance.py` | the §5.2 no-authority property, and published stance map **equals** shipped default | shipped, 11 tests |
|
||||||
|
|
||||||
|
Conformance state under §11: **declared gap** — tracked non-conformance, not
|
||||||
|
conformance. Two §5.3 contacts (`VaultCA` signing write, `warden desk` `bao kv put`),
|
||||||
|
intended owner `secrets-engine`, registered in statute §13.
|
||||||
|
|
||||||
|
Four ops-warden findings have been adopted into the standard: §9.1's two marks
|
||||||
|
(`pending` vs `declared-gap`), §5's Tooling scope rule, §6.4 obligation 1's second
|
||||||
|
limb, and §13.1's existence. Reviews: `history/2026-08-29-layer-model-v04-review.md`,
|
||||||
|
`-v06-review.md`, `-v07-scope-intent-assessment.md`.
|
||||||
|
|
||||||
### Issue vs route
|
### Issue vs route
|
||||||
|
|
||||||
ops-warden executes exactly one lane with its own authority and routes/assists the rest.
|
ops-warden executes exactly one lane with its own authority and routes/assists the rest.
|
||||||
|
|
@ -156,6 +178,7 @@ superseding ADR, never an in-place edit.
|
||||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade |
|
||||||
| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after |
|
| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after |
|
||||||
| `ADR-0009` | Adopt security-zones v0.1 and compile explicit workload membership; PEP failure mode is per zone |
|
| `ADR-0009` | Adopt security-zones v0.1 and compile explicit workload membership; PEP failure mode is per zone |
|
||||||
|
| `ADR-0010` | ops-warden is Staff and PEP-shaped — it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap, not an exemption |
|
||||||
|
|
||||||
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
|
Rules we follow but do not own — NetKingdom canon, the IAM profile, the
|
||||||
credential-management standard, the-custodian's ADR-001 workplan convention — are
|
credential-management standard, the-custodian's ADR-001 workplan convention — are
|
||||||
|
|
@ -433,11 +456,12 @@ Downstream: `ops-bridge` (primary), kaizen agents, CI automations, human operato
|
||||||
|
|
||||||
| Repo | Relationship |
|
| Repo | Relationship |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
| `gate-house` | Owns the security layer model, doctrine, invariants, authority context, and conformance review. ops-warden routes doctrine questions there, and the companion routes the estate's *path* questions back to ops-warden (`ADR-0010`) |
|
||||||
| `net-kingdom` | Canonical security architecture; ops-warden aligns to it |
|
| `net-kingdom` | Canonical security architecture; ops-warden aligns to it |
|
||||||
| `ops-bridge` | Primary cert_command consumer |
|
| `ops-bridge` | Primary cert_command consumer |
|
||||||
| `railiance-infra` | Host-side SSH principals and hardening |
|
| `railiance-infra` | Host-side SSH principals and hardening |
|
||||||
| `railiance-platform` | OpenBao deployment and platform secrets |
|
| `railiance-platform` | OpenBao deployment and platform secrets |
|
||||||
| `flex-auth` | Authorization; policy package shipped (FLEX-WP-0006); runtime deploy FLEX-WP-0007 |
|
| `flex-auth` | Authorization — ruled name `access-engine`; the only policy decision point. Policy package shipped (FLEX-WP-0006); runtime deploy FLEX-WP-0007 |
|
||||||
| `key-cape` | Identity / IAM Profile lightweight mode |
|
| `key-cape` | Identity / IAM Profile lightweight mode |
|
||||||
| `secrets-engine` | Owner-native secret-exec front door (`secrets-engine exec/route`); ops-warden routes provisioned secret lanes to it (WP-0019) and holds 6 more as interim proxies pending its front doors |
|
| `secrets-engine` | Owner-native secret-exec front door (`secrets-engine exec/route`); ops-warden routes provisioned secret lanes to it (WP-0019) and holds 6 more as interim proxies pending its front doors |
|
||||||
| `tenant-engine` | Intended owner of tenant/client secret front doors; ops-warden holds 3 tenant lanes as interim proxies (WP-0028 pattern, WP-0030 register) |
|
| `tenant-engine` | Intended owner of tenant/client secret front doors; ops-warden holds 3 tenant lanes as interim proxies (WP-0028 pattern, WP-0030 register) |
|
||||||
|
|
@ -486,6 +510,9 @@ keywords: [access, credential, secret, npm, token, api-key, openbao, key-cape, l
|
||||||
| `wiki/CredentialRouting.md` | Which subsystem for each credential need |
|
| `wiki/CredentialRouting.md` | Which subsystem for each credential need |
|
||||||
| `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage |
|
| `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage |
|
||||||
| `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog |
|
| `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog |
|
||||||
|
| `net-kingdom/SECURITY-COMPANION.md` | **The estate's operative security rules — start here** |
|
||||||
|
| `layer.yaml` | Layer declaration: every Tooling contact and its §5 shape |
|
||||||
|
| `pep-stance.yaml` | Unreachable-engine stance map (§6.4); equals shipped behaviour by test |
|
||||||
| `tenancy.yaml` | Declared tenancy posture (`I1 A1 E0 P n/a R n/a V0`) and why each axis sits where it does |
|
| `tenancy.yaml` | Declared tenancy posture (`I1 A1 E0 P n/a R n/a V0`) and why each axis sits where it does |
|
||||||
| `wiki/NetKingdomSecurityMap.md` | Platform security component map |
|
| `wiki/NetKingdomSecurityMap.md` | Platform security component map |
|
||||||
| `examples/warden.production.example.yaml` | Production warden.yaml template |
|
| `examples/warden.production.example.yaml` | Production warden.yaml template |
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@
|
||||||
|
|
||||||
| Kind | ID | Status | Lane | Source |
|
| Kind | ID | Status | Lane | Source |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| workplan | ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
|
| workplan | WARDEN-WP-ADHOC-2026-06-27 | finished | — | workplans/ADHOC-2026-06-27.md |
|
||||||
| workplan | ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
|
| workplan | WARDEN-WP-ADHOC-2026-06-29 | finished | — | workplans/ADHOC-2026-06-29.md |
|
||||||
| workplan | ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md |
|
| workplan | WARDEN-WP-ADHOC-2026-08-11 | finished | — | workplans/ADHOC-2026-08-11.md |
|
||||||
| workplan | ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md |
|
| workplan | WARDEN-WP-ADHOC-2026-08-17 | finished | — | workplans/ADHOC-2026-08-17.md |
|
||||||
|
| workplan | WARDEN-WP-ADHOC-2026-09-08 | finished | — | workplans/ADHOC-2026-09-08.md |
|
||||||
| workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
| workplan | WARDEN-WP-0016 | finished | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
||||||
| workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
|
| workplan | WARDEN-WP-0017 | finished | — | workplans/WARDEN-WP-0017-access-front-door-discoverability.md |
|
||||||
| workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
|
| workplan | WARDEN-WP-0018 | finished | — | workplans/WARDEN-WP-0018-whynot-design-npm-lane-activation.md |
|
||||||
|
|
@ -29,16 +30,29 @@
|
||||||
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
||||||
| workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
| workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
| workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md |
|
| workplan | WARDEN-WP-0032 | finished | — | workplans/WARDEN-WP-0032-security-zones.md |
|
||||||
| workplan | WARDEN-WP-0033 | active | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| workplan | WARDEN-WP-0033 | finished | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
|
| workplan | WARDEN-WP-0034 | active | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
| task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
|
| workplan | WARDEN-WP-0035 | finished | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
|
||||||
| task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
|
| workplan | WARDEN-WP-0036 | finished | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
|
||||||
| task | ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md |
|
| workplan | WARDEN-WP-0037 | active | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
|
||||||
| task | ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md |
|
| workplan | WARDEN-WP-0038 | finished | — | workplans/WARDEN-WP-0038-plan-mutation-intent.md |
|
||||||
| task | ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md |
|
| workplan | WARDEN-WP-0039 | blocked | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
|
||||||
| task | ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md |
|
| workplan | WARDEN-WP-0040 | proposed | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
|
||||||
| task | ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md |
|
| task | WARDEN-WP-ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
|
||||||
| task | ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md |
|
| task | WARDEN-WP-ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-11-T02 | done | — | workplans/ADHOC-2026-08-11.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-11-T03 | done | — | workplans/ADHOC-2026-08-11.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-17-T01 | done | — | workplans/ADHOC-2026-08-17.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-17-T02 | done | — | workplans/ADHOC-2026-08-17.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-17-T03 | done | — | workplans/ADHOC-2026-08-17.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-08-17-T04 | done | — | workplans/ADHOC-2026-08-17.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T01 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T02 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T03 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T04 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T05 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
|
| task | WARDEN-WP-ADHOC-2026-09-08-T06 | done | — | workplans/ADHOC-2026-09-08.md |
|
||||||
| task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
| task | WARDEN-WP-0016-T01 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
||||||
| task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
| task | WARDEN-WP-0016-T02 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
||||||
| task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
| task | WARDEN-WP-0016-T03 | done | — | workplans/WARDEN-WP-0016-ops-bridge-tunnel-cert-pilot.md |
|
||||||
|
|
@ -126,6 +140,27 @@
|
||||||
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T01 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T02 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T03 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | WARDEN-WP-0033-T04 | wait | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T04 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T05 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
| task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
| task | WARDEN-WP-0033-T06 | done | — | workplans/WARDEN-WP-0033-native-lane-handoff.md |
|
||||||
|
| task | WARDEN-WP-0034-T01 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
|
| task | WARDEN-WP-0034-T02 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
|
| task | WARDEN-WP-0034-T03 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
|
| task | WARDEN-WP-0034-T04 | done | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
|
| task | WARDEN-WP-0034-T05 | wait | — | workplans/WARDEN-WP-0034-layer-model-v07-conformance.md |
|
||||||
|
| task | WARDEN-WP-0035-T01 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
|
||||||
|
| task | WARDEN-WP-0035-T02 | done | — | workplans/WARDEN-WP-0035-policy-nexus-forgejo-source-read-route.md |
|
||||||
|
| task | WARDEN-WP-0036-T01 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
|
||||||
|
| task | WARDEN-WP-0036-T02 | done | — | workplans/WARDEN-WP-0036-attended-login-openbao-output.md |
|
||||||
|
| task | WARDEN-WP-0037-T01 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
|
||||||
|
| task | WARDEN-WP-0037-T02 | done | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
|
||||||
|
| task | WARDEN-WP-0037-T03 | wait | — | workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md |
|
||||||
|
| task | WARDEN-WP-0039-T01 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
|
||||||
|
| task | WARDEN-WP-0039-T02 | done | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
|
||||||
|
| task | WARDEN-WP-0039-T03 | wait | — | workplans/WARDEN-WP-0039-explicit-policy-refusal.md |
|
||||||
|
| task | WARDEN-WP-0040-T01 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
|
||||||
|
| task | WARDEN-WP-0040-T02 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
|
||||||
|
| task | WARDEN-WP-0040-T03 | wait | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
|
||||||
|
| task | WARDEN-WP-0040-T04 | todo | — | workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md |
|
||||||
|
| intake | WARDEN-IN-0001 | closed | — | intakes/intakes.md |
|
||||||
|
| intake | WARDEN-IN-0002 | open | — | intakes/intakes.md |
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@ title: "ADR-0004 — High-risk lanes refuse raw value streaming to agent session
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: ops-warden
|
repo: ops-warden
|
||||||
status: accepted
|
status: accepted
|
||||||
version: "1.0"
|
version: "1.1"
|
||||||
revision: "1"
|
revision: "2"
|
||||||
owner: ops-warden
|
owner: ops-warden
|
||||||
binds: "ops-warden; any agent runtime calling warden access"
|
binds: "ops-warden; any agent runtime calling warden access"
|
||||||
created: "2026-07-20"
|
created: "2026-07-20"
|
||||||
updated: "2026-08-18"
|
updated: "2026-09-04"
|
||||||
last_reviewed: "2026-08-18"
|
last_reviewed: "2026-09-04"
|
||||||
review_interval: 6m
|
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: ""
|
supersedes: ""
|
||||||
successor: ""
|
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.
|
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*.
|
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
|
**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
|
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
|
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.
|
**`risk: high` becomes a load-bearing catalog field** rather than documentation.
|
||||||
Classifying a lane now changes runtime behaviour, so it must be set deliberately.
|
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
|
**We accept that `--unsafe-stdout` still exists for humans.** The CLI recognizes the
|
||||||
`WARDEN_AGENT_ID`, so an agent that does not set it is not caught. That is a known
|
issued subject when the runtime presents it and otherwise relies on
|
||||||
limit: this ADR raises the floor for cooperating runtimes and hardens the store
|
`WARDEN_AGENT_ID`; both markers remain caller-visible and therefore advisory. This
|
||||||
behind them; it does not claim to stop a determined caller.
|
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
|
## Related
|
||||||
|
|
||||||
|
|
|
||||||
109
docs/adr/ADR-0010-ops-warden-is-staff.md
Normal file
109
docs/adr/ADR-0010-ops-warden-is-staff.md
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
---
|
||||||
|
id: ops-warden-adr-0010
|
||||||
|
type: adr
|
||||||
|
title: "ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: accepted
|
||||||
|
version: "1.0"
|
||||||
|
revision: "1"
|
||||||
|
owner: ops-warden
|
||||||
|
binds: "ops-warden"
|
||||||
|
created: "2026-08-28"
|
||||||
|
updated: "2026-08-28"
|
||||||
|
last_reviewed: "2026-08-28"
|
||||||
|
review_interval: 3m
|
||||||
|
enforced_by: "INTENT.md layer declaration; docs/adr/ADR-0002; docs/adr/ADR-0003; docs/adr/ADR-0005; registry/routing/catalog.yaml delegation fields"
|
||||||
|
supersedes: ""
|
||||||
|
successor: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-0010 — ops-warden is Staff: lanes, not rules, and one declared engine gap
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Accepted 2026-08-28, answering intake `WARDEN-IN-0001` from gate-house, which
|
||||||
|
carries decision `GH-DEC-2026-001`. The standard being adopted —
|
||||||
|
`net-kingdom/canon/standards/security-layer-model_v0.1.md` — is `proposed`, and was
|
||||||
|
proposed pending assent from flex-auth, kings-guard, and ops-warden. This ADR is
|
||||||
|
ops-warden's half of that assent.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The estate acquired overlapping claims to the same responsibility, most visibly two
|
||||||
|
repositories describing themselves as the authorization control plane. The layer
|
||||||
|
model resolves the overlap by layering repositories on determinism — Taxonomy,
|
||||||
|
Tooling, Engines, Staff — and by two rules: Staff never touches Tooling directly
|
||||||
|
(§5), and `access-engine` is the only policy decision point (§6).
|
||||||
|
|
||||||
|
ops-warden is assigned Staff. Two demarcations follow that touch this repository:
|
||||||
|
the security curriculum it had been carrying belongs to gate-house, and the words
|
||||||
|
*access lane* and *access rule* are bound to different owners.
|
||||||
|
|
||||||
|
Full reasoning: `history/2026-08-28-security-layer-model-assent.md`.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
**1. ops-warden is Staff and declares it.** `INTENT.md` carries the layer label and
|
||||||
|
the §5 invariant. ops-warden holds no state another layer depends on at runtime and
|
||||||
|
renders no authorization decision — it consumes them.
|
||||||
|
|
||||||
|
**2. Lanes, not rules.** ops-warden owns *how* a worker reaches a host: SSH
|
||||||
|
certificate issuance, the routing catalog, `warden access`, `warden plan`,
|
||||||
|
`cert_command`. It never owns *whether* a worker may — that is `access-engine`
|
||||||
|
(today `flex-auth`), and ops-warden neither renders nor caches that decision. This
|
||||||
|
restates what `ADR-0002` and `ADR-0005` already bind; it is recorded here because
|
||||||
|
the demarcation is now normative estate-wide and other repositories rely on
|
||||||
|
ops-warden holding to it. The ruled rename `flex-auth` → `access-engine` is assented
|
||||||
|
to; ops-warden asks only for a window in which both names resolve.
|
||||||
|
|
||||||
|
**3. Doctrine goes to gate-house; runbooks stay here.** ops-warden does not restate
|
||||||
|
security doctrine, the authority model, or the curriculum. It references
|
||||||
|
gate-house's. It keeps everything operational about the lanes it stewards: which
|
||||||
|
subsystem owns which need, how to obtain a credential lane by lane, and conformance
|
||||||
|
evidence for its own lanes. `.claude/rules/credential-routing.md` is runbook, not
|
||||||
|
curriculum, and stays inlined in this and every other repository.
|
||||||
|
|
||||||
|
**4. One declared engine gap, not an exemption.** `src/warden/vault.py` (`VaultCA`)
|
||||||
|
is a direct OpenBao client performing a write from a Staff repository. It is a §5
|
||||||
|
non-conformance. ops-warden declares it rather than arguing it away:
|
||||||
|
|
||||||
|
- **intended owner:** `secrets-engine` (credential abstraction, custody, lifecycle)
|
||||||
|
- **blocked on:** no engine exposes an SSH certificate signing surface
|
||||||
|
- **review:** with this ADR, every 3 months
|
||||||
|
|
||||||
|
Until that surface exists, ops-warden continues to sign — refusing to would remove
|
||||||
|
production host access to close a documentation gap — and reports the position as
|
||||||
|
open. `warden desk`'s `bao kv put` is declared on the same terms. `taint.py` is
|
||||||
|
metadata-only observation, declared under §5's read-only allowance. `proxy.py`
|
||||||
|
supplies no authority of its own: it runs the owner's tool under the caller's
|
||||||
|
identity and is governed by `ADR-0002`.
|
||||||
|
|
||||||
|
This is `ADR-0003` turned inward. ops-warden has required an intended owner and a
|
||||||
|
blocker on 27 catalog lanes it holds for other repositories; it holds itself to the
|
||||||
|
same record.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
ops-warden's conformance under §10 is *declared non-conformant with a tracked
|
||||||
|
closure path*, not clean. That is the accurate state and it is the state that gets
|
||||||
|
fixed, because it names an owner who can fix it.
|
||||||
|
|
||||||
|
An amendment to §5 has been offered to gate-house — a second sanctioned shape
|
||||||
|
alongside read-only diagnostics: a declared engine gap carrying intended owner,
|
||||||
|
blocker, and review date, machine-readable so §10 can tell a tracked gap from an
|
||||||
|
undeclared violation. It is offered, not assumed; §5 stays gate-house's to write. If
|
||||||
|
gate-house declines it, ops-warden's position is a plain non-conformance and is
|
||||||
|
reported as one.
|
||||||
|
|
||||||
|
The `NetKingdom Security Literacy` section stops being a prose second source for
|
||||||
|
`registry/routing/catalog.yaml`, which `ADR-0001` had already ruled against for
|
||||||
|
catalog procedure.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed, gate-house)
|
||||||
|
- `gate-house/decisions/decisions.md` — `GH-DEC-2026-001`
|
||||||
|
- `history/2026-08-28-security-layer-model-assent.md`
|
||||||
|
- `ADR-0001`, `ADR-0002`, `ADR-0003`, `ADR-0005`, `ADR-0009`
|
||||||
|
- `WARDEN-IN-0001`
|
||||||
|
|
@ -71,3 +71,4 @@ and the publication is a defect.
|
||||||
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden |
|
| `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden |
|
||||||
| `ADR-0008` | A lane's risk grade covers every field its path discloses | ops-warden |
|
| `ADR-0008` | A lane's risk grade covers every field its path discloses | ops-warden |
|
||||||
| `ADR-0009` | Adopt security-zones v0.1; compile explicit membership and select PEP failure mode per zone | ops-warden |
|
| `ADR-0009` | Adopt security-zones v0.1; compile explicit membership and select PEP failure mode per zone | ops-warden |
|
||||||
|
| `ADR-0010` | ops-warden is Staff: it owns access lanes, never access rules; the direct OpenBao client is a declared engine gap | ops-warden, and gate-house as the standard's owner |
|
||||||
|
|
|
||||||
171
history/2026-08-28-security-layer-model-assent.md
Normal file
171
history/2026-08-28-security-layer-model-assent.md
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
# Security layer model — ops-warden's assent (WARDEN-IN-0001)
|
||||||
|
|
||||||
|
**Date:** 2026-08-28
|
||||||
|
**Intake:** `WARDEN-IN-0001`
|
||||||
|
**Requested by:** gate-house, ratified as `GH-DEC-2026-001`
|
||||||
|
**Standard:** `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed)
|
||||||
|
**Outcome:** assent to all three items; one declared non-conformance and one
|
||||||
|
proposed amendment to the standard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What was asked
|
||||||
|
|
||||||
|
gate-house asked ops-warden to assent to three boundary items:
|
||||||
|
|
||||||
|
1. **ops-warden is Staff**, bound by §5 — Staff acts only through Engine APIs and
|
||||||
|
never holds a direct Tooling client.
|
||||||
|
2. **Doctrine versus runbook** — the security curriculum moves to gate-house; the
|
||||||
|
`NetKingdom Security Literacy` section in `INTENT.md` becomes lane-specific
|
||||||
|
runbooks that reference gate-house doctrine rather than restating it.
|
||||||
|
3. **The access lane / access rule demarcation** (§8) — ops-warden and ops-mason own
|
||||||
|
*lanes* (how a worker reaches a host); access-engine owns *rules* (whether they
|
||||||
|
may). This is the condition attached to renaming flex-auth to access-engine, so
|
||||||
|
ops-warden effectively holds a veto on that name.
|
||||||
|
|
||||||
|
Plus: add gate-house to the literacy and routing tables, and say so if moving the
|
||||||
|
curriculum out leaves ops-warden unable to instruct its own workers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item 1 — Staff, and the §5 problem it exposes
|
||||||
|
|
||||||
|
**Assent to the layer.** Staff is the right assignment and not a demotion.
|
||||||
|
ops-warden's artifacts are exactly what §3.4 describes: routing decisions,
|
||||||
|
workplans, runbooks, an audit trail. Its one production lane is non-deterministic
|
||||||
|
in the sense that matters — it is an operator front door, not a contract.
|
||||||
|
|
||||||
|
**But §5 is violated today, and by the one lane ops-warden permanently owns.**
|
||||||
|
The rule is deliberately greppable, so grepping is the honest response:
|
||||||
|
|
||||||
|
| Path | Tooling contact | Kind | Whose credential |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `src/warden/vault.py` (`VaultCA.sign`) | `POST <bao>/v1/<mount>/sign/<role>` | **write** | broker-issued token held by ops-warden's process |
|
||||||
|
| `src/warden/desk.py` | `bao kv put` (paste-once provisioning) | **write** | founder's, at the desk |
|
||||||
|
| `src/warden/taint.py` | `bao kv metadata get` | read (metadata only, never data) | caller's |
|
||||||
|
| `src/warden/proxy.py` (`warden access --fetch/--exec/--wrap`) | catalog `fetch_command` | read | **the caller's own** |
|
||||||
|
|
||||||
|
Two of these are not really ops-warden's clients. `proxy.py` runs the owner's tool
|
||||||
|
under the caller's identity and supplies no authority of its own — that is
|
||||||
|
`ADR-0002`, conduit not broker, and it is arguably outside §5's target. `taint.py`
|
||||||
|
reads metadata only, and fits §5's read-only-observation allowance once declared.
|
||||||
|
|
||||||
|
**`VaultCA` does not have that defence.** It is a direct OpenBao client, in a Staff
|
||||||
|
repository, performing a write, presenting a token from its own environment. It is
|
||||||
|
production-verified and it is the SSH lane — the single thing ops-warden owns
|
||||||
|
permanently. Under §5 as written, adopting this standard puts ops-warden's core
|
||||||
|
lane in violation on the day it is adopted.
|
||||||
|
|
||||||
|
The escape hatch §5 offers does not fit: it covers *read-only observation for
|
||||||
|
diagnostics*, and signing is a write. The route §5 prescribes does fit —
|
||||||
|
|
||||||
|
> *A Staff repository needing a capability no engine exposes MUST raise that as an
|
||||||
|
> engine gap, not solve it locally.*
|
||||||
|
|
||||||
|
— and no engine exposes SSH certificate signing. `secrets-engine` owns credential
|
||||||
|
abstraction, custody and lifecycle, which is the layer this belongs in, but it
|
||||||
|
fronts no SSH-CA API today.
|
||||||
|
|
||||||
|
**So ops-warden assents and declares the non-conformance rather than negotiating an
|
||||||
|
exemption.** `VaultCA` is recorded in `INTENT.md` as a declared §5 exception with a
|
||||||
|
named intended owner (`secrets-engine`), a blocker (no SSH-CA engine surface), and a
|
||||||
|
review date. That is `ADR-0003` — cover gaps, never silently own them — applied to
|
||||||
|
ops-warden itself instead of to someone else's lane.
|
||||||
|
|
||||||
|
### Proposed amendment to the standard
|
||||||
|
|
||||||
|
§5 has exactly one shape for a Staff repository that legitimately touches Tooling:
|
||||||
|
read-only diagnostics. That shape is too narrow to describe the estate as it exists,
|
||||||
|
and a rule with no lane for a real, sanctioned case gets satisfied by relabelling
|
||||||
|
rather than by closing the gap.
|
||||||
|
|
||||||
|
Recommend §5 gain a second shape: a **declared engine gap** — a Staff repository MAY
|
||||||
|
hold a Tooling client for a capability no engine exposes, provided it is declared in
|
||||||
|
`INTENT.md` with an intended owner, the blocker, and a review date, and provided the
|
||||||
|
declaration is machine-readable so the conformance check in §10 can distinguish a
|
||||||
|
tracked gap from an undeclared violation.
|
||||||
|
|
||||||
|
ops-warden already runs this machinery for other repositories' lanes: 27 catalog
|
||||||
|
entries carry `delegation:` with `intended_owner` and `blocked_on`, and
|
||||||
|
`warden route gaps` lists them (WP-0030). It is offered, not imposed — the standard
|
||||||
|
is gate-house's.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item 2 — Doctrine versus runbook
|
||||||
|
|
||||||
|
**Assent.** The `NetKingdom Security Literacy` section is what gate-house says it is:
|
||||||
|
evidence that the curriculum had no owner, so it accreted in whatever `INTENT.md`
|
||||||
|
was open. That is the same failure `risk-nexus` names for findings and the same one
|
||||||
|
`ADR-0001` prevents for catalog procedure. ops-warden has argued this rule twice
|
||||||
|
against other repositories; it applies here.
|
||||||
|
|
||||||
|
The boundary, drawn precisely:
|
||||||
|
|
||||||
|
| Moves to gate-house | Stays with ops-warden |
|
||||||
|
| --- | --- |
|
||||||
|
| Why the planes are separated; the authority model | Which subsystem owns which credential need |
|
||||||
|
| What "posture", "zone", "authority ceiling" mean | How to obtain a cert, a lease, a login — per lane |
|
||||||
|
| The security curriculum a worker is taught | The runbook a worker executes |
|
||||||
|
| Doctrine a lane must conform to | Evidence of conformance for ops-warden's lanes |
|
||||||
|
|
||||||
|
**gate-house's test question, answered: no, it does not leave ops-warden unable to
|
||||||
|
instruct its workers — and the reason is worth recording.** What actually instructs
|
||||||
|
an ops-warden worker is not the prose in `INTENT.md`. It is `warden plan "<need>"`,
|
||||||
|
`warden route find`, and `.claude/rules/credential-routing.md`, which is inlined into
|
||||||
|
every repository's agent instructions precisely because credential routing is
|
||||||
|
high-frequency and high-risk. That surface is executable, lane-specific, and
|
||||||
|
unambiguously runbook. It does not depend on the literacy table, and moving doctrine
|
||||||
|
out does not weaken it.
|
||||||
|
|
||||||
|
If anything the move improves it: the literacy table has been a second, prose copy of
|
||||||
|
what `registry/routing/catalog.yaml` states machine-readably, which is the
|
||||||
|
double-source failure `ADR-0001` exists to stop.
|
||||||
|
|
||||||
|
**One thing must not move with it.** `.claude/rules/credential-routing.md` stays
|
||||||
|
inline in this repository and in every other. It is not doctrine and not a
|
||||||
|
curriculum; it is the anti-pattern list an agent needs *before* it acts, and a
|
||||||
|
reference to a document in another repository would not be read in time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item 3 — Access lane versus access rule
|
||||||
|
|
||||||
|
**Assent, unconditionally, and the veto on `access-engine` is not exercised.**
|
||||||
|
|
||||||
|
ops-warden is already built this way. `ADR-0005` implements one lane narrowly and
|
||||||
|
routes everything else; `ADR-0002` makes it a conduit that never decides; `ADR-0009`
|
||||||
|
has ops-warden compile membership attributes and apply a zone's failure mode while
|
||||||
|
flex-auth owns the stance. ops-warden consumes decisions; it has never rendered one.
|
||||||
|
The demarcation costs nothing because it describes what is already true.
|
||||||
|
|
||||||
|
`access-engine` is also the better name. ops-warden's own routing table has had to
|
||||||
|
say "authorization" for the decision and "access" for the route for a year, and the
|
||||||
|
collision is visible in every playbook.
|
||||||
|
|
||||||
|
**One operational condition, on execution rather than on the ruling.** The rename is
|
||||||
|
598 references across 82 files in this repository alone — catalog `owner:` fields,
|
||||||
|
`registry/flex-auth/`, `src/warden/policy.py`, the production registry snapshot
|
||||||
|
builder, playbooks, and the `.claude/rules/` files that other repositories inline.
|
||||||
|
Ops-warden asks for a deprecation window in which both names resolve, rather than a
|
||||||
|
flag day; ops-warden will do its own migration inside that window. This is a request
|
||||||
|
about sequencing, not a reservation about the name.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Item 4 — gate-house is missing from every table
|
||||||
|
|
||||||
|
Correct, and fixed in this pass. gate-house is added to the literacy/routing table
|
||||||
|
in `INTENT.md` as the owner of doctrine, invariants, authority context, and
|
||||||
|
conformance review — with the routing rule stated explicitly: **doctrine and
|
||||||
|
authority-model questions go to gate-house; policy decisions continue to go to
|
||||||
|
access-engine.** Those are different questions and the distinction is the whole
|
||||||
|
point of §6.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recorded as
|
||||||
|
|
||||||
|
- `ADR-0010` — ops-warden is Staff; lanes not rules; the declared §5 exception
|
||||||
|
- `INTENT.md` — layer declaration, reworked routing table, gate-house row
|
||||||
|
- `WARDEN-IN-0001` — closed, outcome `assented`
|
||||||
136
history/2026-08-29-layer-model-v04-review.md
Normal file
136
history/2026-08-29-layer-model-v04-review.md
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
# Security Layer Model v0.4 — ops-warden's review
|
||||||
|
|
||||||
|
**Date:** 2026-08-29
|
||||||
|
**Reviewed:** `net-kingdom/canon/standards/security-layer-model_v0.4.md` (accepted)
|
||||||
|
**Prior position:** `ADR-0010`, assent to v0.1 (`WARDEN-IN-0001`)
|
||||||
|
**Outcome:** no objection to the ruling; three findings, one of them against ops-warden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What v0.4 did with ops-warden's amendment
|
||||||
|
|
||||||
|
Both §5 asks from `ADR-0010` were adopted.
|
||||||
|
|
||||||
|
**§5.3 declared engine gap** is the amendment ops-warden offered, adopted with the
|
||||||
|
four fields intact (`capability`, `intended_owner`, `blocked_on`, `review`), the
|
||||||
|
rationale preserved — *a rule offering no lane for a real sanctioned case gets
|
||||||
|
satisfied by relabelling rather than by closing the gap* — and the framing that
|
||||||
|
matters most kept explicit: **a declared gap is tracked non-conformance, not
|
||||||
|
conformance**. ops-warden's delegation machinery is cited as prior art.
|
||||||
|
|
||||||
|
**§5.2 conduit** resolves the question ops-warden flagged rather than assumed. The
|
||||||
|
test is the supplied-authority property, which is the right test: it turns on what
|
||||||
|
the repository presents, not on what it touches. *"A conduit that presents its own
|
||||||
|
token is not a conduit"* is a sharper statement of `ADR-0002` than `ADR-0002` makes.
|
||||||
|
|
||||||
|
**This created an obligation ops-warden had not met.** §5.3 requires the fields
|
||||||
|
*machine-readably* and §11 makes the mapping a mechanical check; ops-warden's
|
||||||
|
declaration was prose in `INTENT.md`. Fixed in this pass: `layer.yaml`,
|
||||||
|
`scripts/check_layer_conformance.py`, `tests/test_layer_conformance.py`. The
|
||||||
|
checker found three undeclared modules on first run, all false positives — help
|
||||||
|
text, a docstring, and the doubles library that *simulates* `bao` — which is why
|
||||||
|
it now matches invocation shapes rather than the word.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 1 — §9.1 and §5.3 disagree, and ops-warden's §4 row is the instance
|
||||||
|
|
||||||
|
§9.1: *a Staff repository MUST NOT be catalogued in §4 as owning a capability that
|
||||||
|
requires a Tooling contact no engine exposes*; where intended but unbuilt, the
|
||||||
|
entry **MUST be marked pending** and the gap declared under §5.3.
|
||||||
|
|
||||||
|
ops-warden's §4 row reads `operational access lanes, stewardship, runbooks; SSH
|
||||||
|
certificate issuance` — with no pending mark. And §13 lists *SSH-CA signing write
|
||||||
|
(`VaultCA`, `bao kv put`) — declared by ops-warden — intended owner secrets-engine*.
|
||||||
|
|
||||||
|
So the catalog asserts ownership of a capability that requires a Tooling contact no
|
||||||
|
engine exposes, unmarked. By §9.1's own text that is a defect. But the available
|
||||||
|
fix is worse than the defect: **marking it pending would be false.** SSH issuance is
|
||||||
|
production-verified and in daily use. `pending` would tell a reader ops-warden does
|
||||||
|
not yet do the one thing it demonstrably does.
|
||||||
|
|
||||||
|
The root cause is that §9.1 collapses two different states:
|
||||||
|
|
||||||
|
| State | Example | Capability today |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| No route exists at all | kings-guard containment (§9.2) | **zero** |
|
||||||
|
| Route exists via a declared §5.3 gap | ops-warden SSH issuance | **working, tracked** |
|
||||||
|
|
||||||
|
§5.3 exists precisely to sanction the second. §9.1 was written for the first — it
|
||||||
|
was raised by kings-guard, about containment, and correctly fixed *for that case*.
|
||||||
|
Applied to the adjacent case it produces a false catalog.
|
||||||
|
|
||||||
|
**Recommendation:** give §9.1 two marks rather than one — `pending` where no route
|
||||||
|
exists, and `declared-gap` where the capability is discharged under §5.3 and
|
||||||
|
registered in §13. Both are honest; today's binary forces a choice between a false
|
||||||
|
label and an unmarked violation.
|
||||||
|
|
||||||
|
This is the §12 loop working as designed, and §12 already says so: a finding that a
|
||||||
|
rule is unsatisfiable is a success of the loop.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 2 — §5's scope is undefined for infrastructure §4 does not catalogue
|
||||||
|
|
||||||
|
§5 forbids *a direct client for a Tooling-layer system*. §4 catalogues the security
|
||||||
|
estate, and only `key-cape` and `OpenBao` are Tooling rows.
|
||||||
|
|
||||||
|
ops-warden holds an HTTP client for the **State Hub** and for **llm-connect**
|
||||||
|
(`src/warden/worker.py`). Neither appears in §4. Both are infrastructure a Staff
|
||||||
|
repository holds a direct client for.
|
||||||
|
|
||||||
|
The question is not rhetorical, because the answers diverge sharply:
|
||||||
|
|
||||||
|
- **If they are Tooling**, then every Staff repository in the estate is in
|
||||||
|
undeclared violation on adoption day — they all write progress events — and
|
||||||
|
§11's second mechanical check fails estate-wide.
|
||||||
|
- **If they are not**, §5 should say so, because *"a Tooling-layer system"* reads
|
||||||
|
considerably broader than *"a repository in the §4 Tooling rows"*.
|
||||||
|
|
||||||
|
ops-warden has recorded both under `non_tooling_clients` in `layer.yaml` with the
|
||||||
|
reasoning stated, rather than resolving it unilaterally. The scope is gate-house's
|
||||||
|
to set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 3 — §9.6 lands on ops-warden, and ops-warden does not satisfy it
|
||||||
|
|
||||||
|
This is the one against us, and it is the most consequential item in the review.
|
||||||
|
|
||||||
|
§9.6 consequence 1: *any system whose evidence is load-bearing MUST make emission
|
||||||
|
atomic with the state change it records. An archive cannot retrofit completeness.*
|
||||||
|
|
||||||
|
**ops-warden's audit emission is deliberately non-atomic.** `src/warden/ca.py:90`
|
||||||
|
carries `pass # audit must not block signing`, and `wiki/AuditTrail.md` states the
|
||||||
|
trail *"never blocks the primary action"*. If the audit append fails, the
|
||||||
|
certificate is still issued and the event is simply lost — a suppressed event that
|
||||||
|
leaves the chain perfectly intact, which is the exact failure §9.6 describes.
|
||||||
|
|
||||||
|
That was a considered availability choice: an audit-disk problem should not remove
|
||||||
|
production host access. §9.6 now makes it a conformance question, and the trade is
|
||||||
|
real in both directions:
|
||||||
|
|
||||||
|
- make emission atomic → an audit write failure fails the sign, and the estate's
|
||||||
|
operational access lane acquires a new dependency on its own evidence store;
|
||||||
|
- leave it → signing evidence cannot be treated as complete, and anything reasoning
|
||||||
|
from *"there is no record of a sign"* is unsound.
|
||||||
|
|
||||||
|
**ops-warden has not changed it, and is not going to decide this alone** — §9.6 is
|
||||||
|
estate doctrine and the question is whether SSH signing evidence is load-bearing in
|
||||||
|
gate-house's sense. What ops-warden can say is that the second horn is currently
|
||||||
|
true and undocumented: `wiki/AuditTrail.md` does not warn that absence of a record
|
||||||
|
is not evidence of absence. That correction is ops-warden's regardless of the
|
||||||
|
ruling, and is the smaller half of the fix.
|
||||||
|
|
||||||
|
Note also that §5.2 requires a conduit action to be *"reconstructable as the
|
||||||
|
caller's action in audit"* — an audit-dependent claim, and therefore bounded by
|
||||||
|
§9.6. Worth a cross-reference so the two rules do not drift apart.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Offered
|
||||||
|
|
||||||
|
`layer.yaml` + `check_layer_conformance.py` + `test_layer_conformance.py` is a
|
||||||
|
working reference implementation of §5.3 and of §11's second mechanical check. Eight
|
||||||
|
of fifteen estate repositories have yet to declare (§14). If it is useful as a
|
||||||
|
pattern to point them at, it is offered — as the delegation machinery was.
|
||||||
156
history/2026-08-29-layer-model-v06-review.md
Normal file
156
history/2026-08-29-layer-model-v06-review.md
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
# Security Layer Model v0.6 — ops-warden's review
|
||||||
|
|
||||||
|
**Date:** 2026-08-29
|
||||||
|
**Reviewed:** `security-layer-model_v0.6.md` (proposed), plus v0.5 and the companion
|
||||||
|
**Prior positions:** `ADR-0010` (v0.1 assent); `history/2026-08-29-layer-model-v04-review.md`
|
||||||
|
**Outcome:** no objection; one conformance action taken, two findings, one accepted SHOULD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Disposition of ops-warden's v0.4 findings
|
||||||
|
|
||||||
|
All three were acted on, two of them exactly as recommended.
|
||||||
|
|
||||||
|
| Finding | Outcome |
|
||||||
|
| --- | --- |
|
||||||
|
| §9.1 forces a false `pending` onto working capability | **Adopted** — v0.5 split it into `pending` and `declared-gap`, credited to ops-warden |
|
||||||
|
| §5 scope undefined for uncatalogued infrastructure | **Adopted** — "Tooling-layer system" now means a §4 Tooling row; the State Hub case is recorded, not policed |
|
||||||
|
| §9.6 atomicity lands on ops-warden's signing lane | **Ruled** — the load-bearing / attributive distinction, with ops-warden's `# audit must not block signing` named as the estate's live example |
|
||||||
|
|
||||||
|
The §9.6 ruling deserves a note, because it went in ops-warden's favour and that is
|
||||||
|
a reason to check it rather than accept it. The test is *"no control branches on
|
||||||
|
its presence"*. Verified: the only consumer of `audit.jsonl` is `warden activity`
|
||||||
|
(`cli.py`), which displays. Nothing gates on a signing record — not the agent
|
||||||
|
read-boundary, not `warden plan`, not the scorecard. The lane is genuinely
|
||||||
|
attributive and the trade is legitimate on the standard's own terms. The two
|
||||||
|
obligations that attach — declare it, never claim completeness — were already met
|
||||||
|
in `wiki/AuditTrail.md`, now updated to record the ruling rather than the open
|
||||||
|
question. **If a future ops-warden control ever gates on this trail, the trade has
|
||||||
|
to be revisited before that ships**, and that is recorded there.
|
||||||
|
|
||||||
|
`layer.yaml` is named in §11 as the estate's reference declaration form, including
|
||||||
|
the "record non-Tooling clients so the check is total" property. Offered again to
|
||||||
|
the repositories that have yet to declare.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conformance action taken — the stance map was not published
|
||||||
|
|
||||||
|
§6.4 obligation 3 requires a declared unreachable-engine stance that is total, per
|
||||||
|
zone, with no implicit default, *"published rather than held in code comments"* —
|
||||||
|
and §6.4 requires **every** PEP-shaped consumer to publish its map so the maps can
|
||||||
|
be inventoried. `ADR-0009` is named as the reference shape.
|
||||||
|
|
||||||
|
ops-warden was not doing this. The map lived in `PolicyConfig.failure_modes`, a
|
||||||
|
dataclass default in `src/warden/config.py`. That is not a code *comment*, but it
|
||||||
|
is not published either — it is merely written down, and a consumer of the estate
|
||||||
|
had no way to read ops-warden's stance without reading ops-warden's source.
|
||||||
|
|
||||||
|
Published as `pep-stance.yaml`, with the property that makes publishing worth
|
||||||
|
anything: `tests/test_layer_conformance.py` asserts the published map is **equal to
|
||||||
|
the shipped default**. A published map that may drift from the code is worse than
|
||||||
|
no map, because it invites reliance it cannot support. The file also records the
|
||||||
|
obligation-2 position (verdict never cached; input claims cached under their own
|
||||||
|
freshness rules) and the obligation-4 bound (§9.6 attributive).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 1 — §6.4 obligation 1 contradicts obligation 3, and ops-warden is the instance
|
||||||
|
|
||||||
|
> **1. No side effect without a decision record.** A PEP MUST NOT perform the
|
||||||
|
> protected action unless it holds a decision from `access-engine` identifying the
|
||||||
|
> request it was rendered for.
|
||||||
|
|
||||||
|
> **3. A declared unreachable-engine stance (§9.3):** total, per zone... `ops-warden`
|
||||||
|
> `ADR-0009` is the reference shape.
|
||||||
|
|
||||||
|
These cannot both be absolute. ops-warden's declared stance — blessed by §9.3 as
|
||||||
|
*"the only thing left"* when there is no engine to ask — is `fail_open` for `z0`–`z2`
|
||||||
|
and `unknown`. Applying it means issuing a certificate **without holding a
|
||||||
|
decision**, which obligation 1 forbids without qualification.
|
||||||
|
|
||||||
|
So the same section names ops-warden as the reference shape for obligation 3 while
|
||||||
|
obligation 1 makes ops-warden's shipped behaviour a violation. §9.3 settled the
|
||||||
|
substance; §6.4 restates it in a form that takes it back.
|
||||||
|
|
||||||
|
**Recommendation.** Bound obligation 1 by obligation 3:
|
||||||
|
|
||||||
|
> A PEP MUST NOT perform the protected action unless it holds a decision from
|
||||||
|
> `access-engine` identifying the request it was rendered for, **or its declared
|
||||||
|
> §9.3 stance for the applicable scope permits proceeding without one and the
|
||||||
|
> application of that stance is recorded in place of the decision**.
|
||||||
|
|
||||||
|
This is not a weakening. It is stricter than today's text in the case that matters:
|
||||||
|
it makes the *recorded application of the stance* mandatory, rather than leaving
|
||||||
|
"no decision record" as a silent state. ops-warden already does this — `ca.py`
|
||||||
|
writes `policy_zone`, `policy_failure_mode` and `policy_decision_id` (present only
|
||||||
|
where a decision was rendered) into both the signatures log and `audit.jsonl`, per
|
||||||
|
`ADR-0009` rule 4: *a fail-open signing result is metadata, not silence*.
|
||||||
|
|
||||||
|
This is the same shape as the v0.4 §9.1 finding: a rule written for the clean case,
|
||||||
|
correct there, producing a false result on the adjacent case the standard has
|
||||||
|
already sanctioned elsewhere.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Finding 2 — §6.4 creates a register that §13 does not implement
|
||||||
|
|
||||||
|
§6.4: *"Every PEP-shaped consumer MUST publish its stance map, and those maps MUST
|
||||||
|
be inventoried — in `maturity-engine` once it exists, **in §13 until then**."*
|
||||||
|
|
||||||
|
§13 contains no stance-map rows. It records declared contacts and unowned
|
||||||
|
capabilities; there is no column, row, or section for a PEP stance. So the
|
||||||
|
obligation names a register that does not exist yet, and the failure mode §6.4
|
||||||
|
itself warns about — *"`z0`–`z2` and unknown fail open" becoming the estate's real
|
||||||
|
policy without anyone having compiled it* — is exactly what the missing register
|
||||||
|
permits.
|
||||||
|
|
||||||
|
**Recommendation.** Either add a stance-map table to §13 with the same
|
||||||
|
state/owner-status discipline the gap table has, or state that the inventory waits
|
||||||
|
for `maturity-engine` and mark the obligation pending under §9.1's own logic — a
|
||||||
|
requirement whose register does not exist is a capability catalogued without a
|
||||||
|
surface. ops-warden's row is ready to paste:
|
||||||
|
|
||||||
|
| PEP | Protected action | Scope | Stance | Published |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `ops-warden` | SSH certificate issuance | security-zone | open `z0`–`z2`+unknown, closed `z3`/n-a | `ops-warden/pep-stance.yaml` |
|
||||||
|
|
||||||
|
The second half matters more than the first: **ops-warden is currently the only
|
||||||
|
PEP that has published one**, so an inventory today would contain one row and that
|
||||||
|
is itself the finding. `ops-mason` is named PEP-shaped in the same paragraph.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accepted, not yet done — §9.6 emission cadence
|
||||||
|
|
||||||
|
§9.6: *"A source SHOULD declare an expected emission cadence, and a drop below it
|
||||||
|
SHOULD become a finding in its own right."*
|
||||||
|
|
||||||
|
ops-warden declares none. This is a genuine SHOULD and the reasoning behind it is
|
||||||
|
sound — it converts the suppression blind spot into something detectable without
|
||||||
|
any Tooling contact, because the source publishes its own stream. It is not done
|
||||||
|
here because a cadence asserted without evidence is worse than none: ops-warden's
|
||||||
|
signing volume is operator-driven and bursty, and a fabricated baseline would
|
||||||
|
generate findings that mean nothing. Deriving one from the existing trail is
|
||||||
|
tractable and is recorded as ops-warden's to do, not gate-house's to chase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## On the pace
|
||||||
|
|
||||||
|
Six versions in two days, with four repositories' findings absorbed and credited,
|
||||||
|
is the §12 loop working at a rate the estate has not seen before. Two cautions,
|
||||||
|
offered as an interested consumer rather than as objections:
|
||||||
|
|
||||||
|
1. **§13 already says it should not be statute, and it is right.** The register has
|
||||||
|
grown every version. Moving it to `maturity-engine` is the stated plan; until
|
||||||
|
that exists, each version of the standard is also a snapshot of a backlog, and
|
||||||
|
the two have very different review intervals.
|
||||||
|
2. **The standard is `proposed` again at v0.6**, and the four repositories that
|
||||||
|
assented did so to v0.1. ops-warden's `ADR-0010` assent covers the three
|
||||||
|
boundary items, and nothing in v0.2–v0.6 has disturbed them — the layer, the
|
||||||
|
lane/rule demarcation, and doctrine-versus-runbook all stand. But the
|
||||||
|
`assented_by` list carries assent forward across five revisions, and a reader
|
||||||
|
could take it as assent to the current text. Worth distinguishing *assented to
|
||||||
|
the boundary* from *reviewed the current revision*; ops-warden has now done
|
||||||
|
both, and this note is the second.
|
||||||
157
history/2026-08-29-v07-scope-intent-assessment.md
Normal file
157
history/2026-08-29-v07-scope-intent-assessment.md
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
# v0.7 conformance — INTENT vs SCOPE gap assessment
|
||||||
|
|
||||||
|
**Date:** 2026-08-29
|
||||||
|
**Standard:** `security-layer-model_v0.7.md` (**accepted**) + `SECURITY-COMPANION.md` v0.2
|
||||||
|
**Prior:** `ADR-0010`; v0.4 and v0.6 reviews in `history/`
|
||||||
|
**Method:** each v0.7 obligation checked against shipped code, not against intent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
ops-warden is **conformant on every obligation it can discharge alone except three**,
|
||||||
|
and holds two declared §5.3 gaps that are tracked, registered and owned elsewhere.
|
||||||
|
The three genuine gaps are §9.7.2 (no stated revocation visibility deadline — a MUST),
|
||||||
|
§3.4 rule 1 (the agent read-boundary keys on an honour-system marker rather than an
|
||||||
|
issued identity), and §9.6's cadence, which is a SHOULD for an attributive source and
|
||||||
|
remains undone for an honest reason.
|
||||||
|
|
||||||
|
One new obligation is not a rule at all but a role: the companion routes the entire
|
||||||
|
estate to ops-warden for *how to get something done*. Nothing in the repo answers a
|
||||||
|
layer or declaration question today, and that is now a discoverability gap.
|
||||||
|
|
||||||
|
Four ops-warden findings were adopted into the standard between v0.4 and v0.7 — §9.1's
|
||||||
|
two marks, §5's Tooling scope rule, §6.4 obligation 1's second limb, and §13.1's
|
||||||
|
existence. That is the conformance loop working; it is not a reason to assume the next
|
||||||
|
pass finds nothing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Obligation-by-obligation
|
||||||
|
|
||||||
|
### Conformant, shipped, evidenced
|
||||||
|
|
||||||
|
| Obligation | Evidence |
|
||||||
|
| --- | --- |
|
||||||
|
| §11 declare layer in own voice, machine-readably | `INTENT.md` frontmatter (`layer: Staff`, `pep_shaped: true`) + `layer.yaml` — cited in §11 as the estate's reference form |
|
||||||
|
| §5 every Tooling contact maps to a shape; non-Tooling recorded so the check is total | `layer.yaml` 5 contacts + 2 exclusions; `scripts/check_layer_conformance.py` |
|
||||||
|
| §5.2 conduit supplies no authority | `proxy.py::_caller_env`; `tests/test_layer_conformance.py::test_conduit_supplies_no_authority_of_its_own` |
|
||||||
|
| §6.4 obl. 1 no side effect without a decision **or a recorded stance** | `ca.py` writes `policy_zone`, `policy_failure_mode`, `policy_decision_id` (present only where rendered). ops-warden is the named reference for limb two |
|
||||||
|
| §6.4 obl. 2 no verdict recaching | `policy.py` caches nothing — verified by inspection, not by claim |
|
||||||
|
| §6.4 obl. 3 stance map published, at a path named in the declaration, equal to shipped behaviour, asserted by test | `pep-stance.yaml`, named in `layer.yaml`; test asserts equality with `PolicyConfig().failure_modes`; registered in statute §13.1 |
|
||||||
|
| §9.3 stance total, per zone, no implicit default | 7 rows covering every zone plus `unknown` and `not-applicable` |
|
||||||
|
| §9.6 evidence claims bounded | `wiki/AuditTrail.md` declares the attributive trade and states absence is not evidence of absence |
|
||||||
|
| §9.7.1 every allow has an explicit lifetime | TTL enforced per `ActorType` — `adm` 48h, `agt` 24h, `atm` 8h |
|
||||||
|
| §3.4 rule 2 tool use is a conduit or engine API | `warden access` is the conduit; `ADR-0004` enforces that tool availability is not permission |
|
||||||
|
|
||||||
|
### Declared gaps — tracked non-conformance, owned elsewhere
|
||||||
|
|
||||||
|
Both registered in statute §13, intended owner `secrets-engine`, reviewed quarterly.
|
||||||
|
Neither is closable by ops-warden: closing them means another repository shipping a
|
||||||
|
surface.
|
||||||
|
|
||||||
|
- **`VaultCA` signing write** — no engine exposes SSH-CA signing.
|
||||||
|
- **`warden desk` `bao kv put`** — no engine exposes attended provisioning.
|
||||||
|
|
||||||
|
Nothing in v0.7 changes their status. The right ops-warden behaviour is to keep them
|
||||||
|
declared, keep the review dates honest, and not quietly grow a third.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The three real gaps
|
||||||
|
|
||||||
|
### G1 — §9.7.2: no stated revocation visibility deadline (MUST)
|
||||||
|
|
||||||
|
> *A **PEP** has one boundary and MUST state one deadline… an unstated deadline is
|
||||||
|
> an unbounded replay window.*
|
||||||
|
|
||||||
|
ops-warden states none, and the honest answer is uncomfortable: **the effective
|
||||||
|
window is the certificate TTL — up to 48 hours.** A certificate issued under an allow
|
||||||
|
stays valid for its full TTL even if the decision that authorized it is revoked or
|
||||||
|
superseded the next minute. ops-warden has no revocation channel for an issued cert:
|
||||||
|
there is no CRL, no KRL distribution, and host-side `auth_principals` is
|
||||||
|
`railiance-infra`'s.
|
||||||
|
|
||||||
|
This is not a documentation gap. It is a design property that has never been written
|
||||||
|
down, and §9.7.2 exists precisely to force it into the open. Two things follow:
|
||||||
|
|
||||||
|
1. The deadline must be **stated** — `adm` 48h / `agt` 24h / `atm` 8h — in
|
||||||
|
`pep-stance.yaml`, as what it is rather than as an aspiration.
|
||||||
|
2. Whether 48h is *acceptable* is a separate question, and it is partly
|
||||||
|
`railiance-infra`'s (KRL distribution) and partly ours (TTL policy). Stating it is
|
||||||
|
ours and is cheap; shortening it is a joint change.
|
||||||
|
|
||||||
|
Stating a bad number is better than stating none: an unstated deadline is an
|
||||||
|
unbounded replay window, and this one is bounded and already implemented.
|
||||||
|
|
||||||
|
### G2 — §3.4 rule 1: the agent boundary rests on an honour-system marker
|
||||||
|
|
||||||
|
> *No standing credential. Authority is issued per task, time-bounded under §9.7,
|
||||||
|
> and attributable to the principal on whose behalf it acts.*
|
||||||
|
|
||||||
|
`ADR-0004`'s read-boundary triggers when `WARDEN_AGENT_ID` is set — an environment
|
||||||
|
variable the agent sets **about itself**. An agent that does not set it is not
|
||||||
|
recognised as an agent. ops-warden has known this (`WARDEN-WP-0033-T04` recorded it
|
||||||
|
as "an honour-system marker on the ops-warden side"), and it was tolerable while no
|
||||||
|
issued agent identity existed.
|
||||||
|
|
||||||
|
One now does. `key-cape` accepted issuance ownership in `KEY-WP-0009-T03`:
|
||||||
|
`codex-railiance-platform`, subject `service:codex:railiance-platform`, role
|
||||||
|
`coding-agent`, scope `openbao:login`, 15-minute lifetime. The OpenBao side is
|
||||||
|
enforced by `railiance-platform`'s policy, which is the half that actually holds.
|
||||||
|
|
||||||
|
So the gap is narrower than it looks and worth stating precisely: **the OpenBao-side
|
||||||
|
boundary is real; the ops-warden-side boundary is advisory.** ops-warden should key
|
||||||
|
its read-boundary on the issued identity where one is present, and treat
|
||||||
|
`WARDEN_AGENT_ID` as a fallback that fails *toward* the boundary rather than away
|
||||||
|
from it. That is a change in this repo and does not need another repo to move.
|
||||||
|
|
||||||
|
### G3 — §9.6 emission cadence (SHOULD, for an attributive source)
|
||||||
|
|
||||||
|
Unchanged from the v0.6 review and still honest: ops-warden declares no expected
|
||||||
|
cadence because its signing volume is operator-driven and bursty, and a fabricated
|
||||||
|
baseline generates findings that mean nothing. v0.7 makes cadence a **MUST for
|
||||||
|
load-bearing sources**; ops-warden's trail is attributive, so it remains a SHOULD.
|
||||||
|
|
||||||
|
Deriving a real baseline from the existing trail is tractable and is ops-warden's to
|
||||||
|
do. It should be derived and declared, or explicitly deferred with a reason — not
|
||||||
|
left silent, which is what it is today.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The role the companion assigns, and what it costs
|
||||||
|
|
||||||
|
> *"For how to get something done in NetKingdom — which lane, which credential, which
|
||||||
|
> route — ask `ops-warden`. This document says what the rules are; ops-warden stewards
|
||||||
|
> the paths through them."*
|
||||||
|
|
||||||
|
This is the largest change in ops-warden's INTENT surface and it is not a rule, so it
|
||||||
|
does not appear in any conformance check. The estate has been told to come here.
|
||||||
|
|
||||||
|
**Today the repo answers credential questions and no others.** `warden route` and
|
||||||
|
`warden plan` cover lanes, owners and acts. Nothing answers *"which layer am I"*,
|
||||||
|
*"how do I declare"*, *"I am PEP-shaped, what do I owe"* — the questions the companion
|
||||||
|
and the standard's adoption status (eight of fifteen repositories undeclared) actually
|
||||||
|
generate.
|
||||||
|
|
||||||
|
ops-warden has already built the reference artifacts those repositories need, and the
|
||||||
|
standard points at them by name in §11 and §6.4. What is missing is the path: a
|
||||||
|
discoverable route from *"I read the companion"* to *"here is the file to copy and the
|
||||||
|
check to run"*. That is exactly the stewardship ops-warden claims, applied to the
|
||||||
|
estate's newest rule rather than to its credential lanes.
|
||||||
|
|
||||||
|
Also worth noting, and not ops-warden's to fix: §13.1's register has one row, and
|
||||||
|
`ops-mason` — catalogued PEP-shaped in the same paragraph — has published nothing.
|
||||||
|
The standard says one row is itself the finding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What does not need doing
|
||||||
|
|
||||||
|
- **No new ADR.** `ADR-0010` holds: Staff, lanes not rules, declared gaps not
|
||||||
|
exemptions. v0.2–v0.7 refined the rules around it and disturbed none of its three
|
||||||
|
positions. The reviews extend it; a superseding record would add ceremony without
|
||||||
|
changing a decision.
|
||||||
|
- **No change to the two §5.3 gaps.** They are correctly declared and owned elsewhere.
|
||||||
|
- **No re-assent.** ops-warden assented to the boundary in `ADR-0010` and has now
|
||||||
|
reviewed three revisions on their merits, which is the stronger position.
|
||||||
175
history/2026-09-09-layer-model-v08-review.md
Normal file
175
history/2026-09-09-layer-model-v08-review.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# security-layer-model v0.8 — ops-warden review
|
||||||
|
|
||||||
|
**Date:** 2026-09-09
|
||||||
|
**Reviewing:** `net-kingdom/canon/standards/security-layer-model_v0.8.md` (status
|
||||||
|
`proposed`, net-kingdom@66eeaba), circulated for assent by gate-house
|
||||||
|
**Prior reviews:** `2026-08-29-layer-model-v04-review.md`, `-v06-review.md`,
|
||||||
|
`-v07-scope-intent-assessment.md`
|
||||||
|
|
||||||
|
## The cell in question
|
||||||
|
|
||||||
|
v0.8 §6.4 obligation 3 adds: *unknown is not a zone and MUST resolve to
|
||||||
|
`fail_closed`.* `pep-stance.yaml` declares `unknown: fail_open` under `ADR-0009`.
|
||||||
|
§13.1 records the cell as non-conformant.
|
||||||
|
|
||||||
|
## Finding 1 — assent. The rule is right and our falsifier check fails.
|
||||||
|
|
||||||
|
gate-house wrote the falsifier into GH-DEC-2026-009's reversal: a scope that is
|
||||||
|
genuinely unknown **and** genuinely low-consequence, where failing closed costs
|
||||||
|
availability with no security gain. Their expectation is that any such path is a
|
||||||
|
§5.1 read-only diagnostic carrying no PEP obligation at all.
|
||||||
|
|
||||||
|
We looked for one on our side and do not have it. The stance map governs exactly
|
||||||
|
one protected action — `warden sign`, SSH certificate issuance. That is a
|
||||||
|
credential-issuing side effect, not a diagnostic; §5.1 does not reach it. So the
|
||||||
|
predicted escape hatch is absent here, and the argument stands unrebutted on its
|
||||||
|
own terms:
|
||||||
|
|
||||||
|
- §9.3 licenses trading availability for openness **per zone**, knowingly, for a
|
||||||
|
named scope. Where the scope is unknown that trade was never made for the
|
||||||
|
request in hand, so resolving permissively does not extend a considered
|
||||||
|
decision — it invents one.
|
||||||
|
- Unknown is the cheapest state to induce. Our own `unknown` reasons are
|
||||||
|
`workload_resolution_absent` and `catalog_applicability_absent` — an
|
||||||
|
unregistered workload or a missing declaration, neither of which requires a
|
||||||
|
credential to arrange.
|
||||||
|
|
||||||
|
We accept that being unclassifiable must not buy permissiveness. The cell is
|
||||||
|
wrong as doctrine.
|
||||||
|
|
||||||
|
## Finding 2 — the rule is right and adopting it today would be a global fail-closed flag in all but name
|
||||||
|
|
||||||
|
This is the finding we would like recorded, and it is about adoption cost, not
|
||||||
|
about the rule.
|
||||||
|
|
||||||
|
The stance map applies only when the engine is unreachable, so the cell's bite is
|
||||||
|
proportional to how much of the target population sits in `unknown`. Measured
|
||||||
|
today:
|
||||||
|
|
||||||
|
| Population | Resolved to a zone | `unknown` | `not-applicable` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Signing targets (actor resources in the flex-auth snapshot) | **0** | **3** | 1 |
|
||||||
|
| Routing catalog lanes | 3 | 18 | 12 |
|
||||||
|
|
||||||
|
Every signing target that resolves at all resolves to `unknown`. So
|
||||||
|
`unknown: fail_closed` adopted today does not fail closed on an edge case; it
|
||||||
|
fails closed on **essentially every certificate ops-warden issues** whenever
|
||||||
|
flex-auth is unreachable.
|
||||||
|
|
||||||
|
That is the precise configuration `ADR-0006` rejected and `ADR-0009` replaced: one
|
||||||
|
value making the decision engine a uniform dependency of every signing path,
|
||||||
|
*including the continuity paths needed to repair that dependency*. If flex-auth is
|
||||||
|
unreachable and the operator needs an SSH certificate to reach the host and
|
||||||
|
restore it, `unknown: fail_closed` denies the certificate — and the target is
|
||||||
|
`unknown` precisely because nobody has classified the repair path.
|
||||||
|
|
||||||
|
The zone model already answers this: classify continuity paths `z2-continuity`,
|
||||||
|
whose stance is `fail_open`, and the deadlock does not arise. We accept that as
|
||||||
|
the right answer. The point is that it is *work not yet done*, and the reason it
|
||||||
|
is not done is not ours to fix — 18 of our 18 unknown lanes are unknown because
|
||||||
|
another repo has not published an authoritative workload identity declaration.
|
||||||
|
`ADR-0009` rule 3 forbids us from closing that gap with inference, correctly.
|
||||||
|
|
||||||
|
So as written, v0.8 §6.4 obligation 3 transfers an availability cost onto the
|
||||||
|
consumer for a classification gap the consumer cannot close alone, and it does so
|
||||||
|
at the moment of assent rather than at the moment the gap closes.
|
||||||
|
|
||||||
|
**What we ask for**, in preference order:
|
||||||
|
|
||||||
|
1. **§6.4 obligation 3 names a transition.** A consumer may declare
|
||||||
|
`unknown: fail_open` as a dated, published transitional state with a
|
||||||
|
classification-coverage figure attached, converting to `fail_closed` on
|
||||||
|
coverage rather than on calendar. This keeps the doctrine and prices the
|
||||||
|
adoption honestly.
|
||||||
|
2. Failing that, **§13.1 records classification coverage alongside stance.** A
|
||||||
|
register row reading `unknown: fail_closed` while 100% of that consumer's
|
||||||
|
targets are unknown is conformant and materially misleading; a reader cannot
|
||||||
|
tell a strict consumer from an unclassified one. This is the same defect
|
||||||
|
§11's *published map equals shipped behaviour* rule exists to prevent, one
|
||||||
|
level up: the map is now accurate about itself and inaccurate about its
|
||||||
|
effect.
|
||||||
|
|
||||||
|
## Finding 3 — §12's derived-artifact rule, small and in agreement
|
||||||
|
|
||||||
|
Six instances in one week matches our experience. `registry/generated/high-risk-data-paths.yaml`
|
||||||
|
carries `catalog_revision` and a `catalog_dirty` flag for exactly the reason §12
|
||||||
|
now generalises: a derived artifact whose revision does not describe the file it
|
||||||
|
came from is worse than one with no revision, because the stale one is trusted.
|
||||||
|
No change requested; recording the agreement since the rule is being generalised
|
||||||
|
from other repos' incidents.
|
||||||
|
|
||||||
|
## What ops-warden is doing about it
|
||||||
|
|
||||||
|
Not flipping the cell on a `proposed` standard, and not flipping it before
|
||||||
|
coverage exists — that ordering is `ADR-0006`'s whole holding. `WARDEN-WP-0040`
|
||||||
|
records the adoption sequence: raise signing-target classification coverage
|
||||||
|
first, then convert the cell, with the deadlock case classified before rather
|
||||||
|
than after. If v0.8 is accepted with obligation 3 unchanged, ops-warden's cell
|
||||||
|
remains a **declared gap** under §11 — tracked non-conformance with a named
|
||||||
|
owner and a route, which is the mark §9.1 exists for and which we argued for in
|
||||||
|
the v0.6 round.
|
||||||
|
|
||||||
|
## Axis declaration (second change touching us)
|
||||||
|
|
||||||
|
`pep-stance.yaml` already carries `scope: security-zone`. No change needed. We
|
||||||
|
agree with rejecting a forced migration onto zones — secrets-engine scoping by
|
||||||
|
catalog stage is an accurate statement about their control, and making them
|
||||||
|
assert a zone they cannot know would trade a real fact for a conformant fiction.
|
||||||
|
The unavailability of cross-axis aggregation is better recorded than papered
|
||||||
|
over, and §13.1 now says so.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ruling — GH-DEC-2026-011, 2026-09-09
|
||||||
|
|
||||||
|
gate-house closed the assent round the same day: **ask 1 declined, ask 2
|
||||||
|
adopted**, standard amended at `net-kingdom@64394e9`, dispositioned in
|
||||||
|
`gate-house/docs/conformance/2026-09-06-v08-assent-round.md`.
|
||||||
|
|
||||||
|
**Ask 1 (a dated transitional `unknown: fail_open`) — declined**, and the reason
|
||||||
|
is better than the ask. A sanctioned transitional `fail_open` is
|
||||||
|
*indistinguishable at runtime* from the stance the rule forbids, and it would make
|
||||||
|
the rule optional at the moment of adoption — the only moment it costs anything.
|
||||||
|
Dating an intention to stop being permissive does not make `unknown` any more
|
||||||
|
expensive for an attacker to induce while the transition runs. Accepted without
|
||||||
|
reservation; the argument defeats the ask on its own terms.
|
||||||
|
|
||||||
|
**Ask 2 (coverage published beside stance) — adopted.** §13.1 now carries a
|
||||||
|
Coverage column and this repo's figures are its first entries: *signing targets
|
||||||
|
0/3 resolved; routing lanes 3/21 resolved (2026-09-09, self-measured)*. Three
|
||||||
|
guards came with it, all of which we would have wanted: coverage does not soften
|
||||||
|
the stance, does not gate it, and never makes a non-conformant cell conformant; a
|
||||||
|
blank means "not reported" and never "complete"; and the decision carries an
|
||||||
|
explicit reversal condition — if a row is ever argued to be conformant *because*
|
||||||
|
its coverage is low, the column is doing harm and comes out.
|
||||||
|
|
||||||
|
That reversal condition is the right shape and it binds us first, since the column
|
||||||
|
exists because we asked. Our row reads non-conformant **and** low-coverage; those
|
||||||
|
are two facts, and the second is not a defence of the first.
|
||||||
|
|
||||||
|
**The deadlock was recorded rather than routed around.** §6.4 obligation 3 now
|
||||||
|
states that a stricter stance is not a licence to manufacture the membership that
|
||||||
|
makes it survivable — where a scope is unknown because another repository has
|
||||||
|
published no workload-identity declaration, the consumer MUST NOT infer one. That
|
||||||
|
is `ADR-0009` rule 3 promoted into the standard at exactly the point where the
|
||||||
|
pressure lands, which is more than we asked for.
|
||||||
|
|
||||||
|
### What we did in response
|
||||||
|
|
||||||
|
- `pep-stance.yaml` marks the `unknown` cell as a **declared gap** inline, with
|
||||||
|
the assent, the measured reason for not flipping, the declined ask, and
|
||||||
|
`WARDEN-WP-0040` as its route. The stance value is unchanged — v0.8 is still
|
||||||
|
`proposed` and v0.7 remains in force, so `standard_version` stays `0.7` with a
|
||||||
|
new `standard_version_reviewed: 0.8`.
|
||||||
|
- The coverage figures are published in `pep-stance.yaml` and, because we asked
|
||||||
|
for the column, **measured rather than hand-counted**:
|
||||||
|
`scripts/report_coverage.py` derives both populations from the artifacts the
|
||||||
|
runtime uses, reusing `report_workload_join.build` rather than re-deriving the
|
||||||
|
join. `tests/test_layer_conformance.py` asserts the published block equals the
|
||||||
|
measured one, and asserts the cell stays marked while it is `fail_open`.
|
||||||
|
|
||||||
|
A hand-counted figure in a register that explicitly does not recompute it would
|
||||||
|
decay silently, and a stale number beside a marked cell is worse than the blank
|
||||||
|
the other four rows carry — a blank at least reads as "not reported".
|
||||||
|
- `WARDEN-WP-0040` T04 is closed; T01–T03 stand unchanged. gate-house confirmed
|
||||||
|
they are not asking for the cell to be flipped before coverage exists.
|
||||||
93
intakes/intakes.md
Normal file
93
intakes/intakes.md
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Intake records
|
||||||
|
|
||||||
|
## WARDEN-IN-0001 — Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule demarcation
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: WARDEN-IN-0001
|
||||||
|
kind: intake
|
||||||
|
title: 'Assent requested: Staff layer, doctrine vs runbook, and the access lane/rule
|
||||||
|
demarcation'
|
||||||
|
status: closed
|
||||||
|
outcome: assented
|
||||||
|
origin: cross-repo
|
||||||
|
origin_ref: gate-house GH-DEC-2026-001
|
||||||
|
priority: medium
|
||||||
|
owner: ops-warden
|
||||||
|
requested_by: gate-house
|
||||||
|
standard: net-kingdom/canon/standards/security-layer-model_v0.1.md
|
||||||
|
description: 'gate-house asks ops-warden to assent to three boundary items. (1) ops-warden
|
||||||
|
is Staff, bound by the rule that Staff acts only through Engine APIs and never touches
|
||||||
|
Tooling directly (standard section 5). (2) Doctrine versus runbook: the NetKingdom
|
||||||
|
Security Literacy section in ops-warden INTENT is evidence the security curriculum
|
||||||
|
had no owner; it now has one in gate-house. Proposal is that doctrine and curriculum
|
||||||
|
move to gate-house and that section becomes lane-specific runbooks referencing gate-house
|
||||||
|
doctrine rather than restating it. ops-warden keeps the lanes it stewards and everything
|
||||||
|
operational about them. (3) The access lane/rule demarcation, normative in standard
|
||||||
|
section 8: ops-warden and ops-mason own access lanes — how a worker reaches a host;
|
||||||
|
access-engine owns access rules — whether they may. This demarcation is the condition
|
||||||
|
attached to renaming flex-auth to access-engine, so ops-warden effectively holds
|
||||||
|
a veto on that name. Also requested: add gate-house to the Security Literacy and
|
||||||
|
routing tables — currently every plane is listed and gate-house appears nowhere
|
||||||
|
— routing doctrine and authority-model questions there while continuing to route
|
||||||
|
policy decisions to access-engine. If moving the curriculum out leaves ops-warden
|
||||||
|
unable to instruct its own workers, say so; the boundary is wrong if it does.'
|
||||||
|
notes: 'Assented to all three items in ADR-0010, with reasoning in
|
||||||
|
history/2026-08-28-security-layer-model-assent.md. (1) Staff accepted; the section 5
|
||||||
|
binding rule exposed a real non-conformance — src/warden/vault.py is a direct
|
||||||
|
OpenBao client performing a write, as is warden desk''s bao kv put. Declared in
|
||||||
|
INTENT.md as an engine gap with intended owner secrets-engine and blocker "no engine
|
||||||
|
exposes an SSH-CA surface", not negotiated as an exemption; taint.py declared under
|
||||||
|
the read-only allowance; warden access proxies run under the caller''s identity.
|
||||||
|
An amendment is offered back to gate-house: a second sanctioned shape in section 5 for
|
||||||
|
a declared engine gap carrying intended owner, blocker and review date, machine-readable
|
||||||
|
so section 10 can tell a tracked gap from an undeclared violation. (2) Doctrine versus
|
||||||
|
runbook accepted; the literacy section is now a lane routing runbook referencing
|
||||||
|
gate-house doctrine. Answering gate-house''s test question: it does not leave ops-warden
|
||||||
|
unable to instruct its workers, because what instructs them is warden plan / warden route
|
||||||
|
and .claude/rules/credential-routing.md, which stays inline by design. (3) The lane/rule
|
||||||
|
demarcation assented unconditionally and the access-engine veto not exercised — ops-warden
|
||||||
|
already consumes decisions and renders none. One request on sequencing only: a deprecation
|
||||||
|
window in which both names resolve (598 references across 82 files here). gate-house added
|
||||||
|
to the routing tables in INTENT.md and SCOPE.md.'
|
||||||
|
created: '2026-08-28T19:30:28.087109Z'
|
||||||
|
updated: '2026-08-28T21:05:00Z'
|
||||||
|
state_hub_intake_id: "01a049ed-bbbc-7520-bc7c-6b0912ca534a"
|
||||||
|
```
|
||||||
|
|
||||||
|
## WARDEN-IN-0002 — Review requested: security layer model v0.3 — and does maturity-engine absorb warden route gaps?
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
id: WARDEN-IN-0002
|
||||||
|
kind: intake
|
||||||
|
title: 'Review requested: security layer model v0.3 — and does maturity-engine absorb
|
||||||
|
warden route gaps?'
|
||||||
|
status: open
|
||||||
|
origin: cross-repo
|
||||||
|
origin_ref: net-kingdom security-layer-model_v0.3
|
||||||
|
priority: medium
|
||||||
|
owner: ops-warden
|
||||||
|
requested_by: gate-house
|
||||||
|
description: 'v0.3 is proposed and changes sections 4, 9 and 13 only; the v0.2 assent
|
||||||
|
record stands. Two new engines: approval-engine (section 9.4) and maturity-engine
|
||||||
|
(section 9.5). THE QUESTION FOR YOU concerns section 5.3, which exists because you
|
||||||
|
offered the amendment. v0.3 gives declared gaps an owner: maturity-engine takes
|
||||||
|
the gap register with intended_owner, blocked_on and review dates, and section 13
|
||||||
|
now says the register in the standard is interim and should not outlive that engine.
|
||||||
|
You offered warden route gaps and the 27 delegation catalog entries as reusable
|
||||||
|
prior art. So the question is whether that machinery should MOVE, be MIRRORED, or
|
||||||
|
STAY. Our tentative reading, which we want tested rather than accepted: routing
|
||||||
|
is yours and stays yours — warden route find answers where a credential need goes,
|
||||||
|
and that is lane knowledge, not maturity. What might move is the readiness half:
|
||||||
|
whether a declared gap is still within its review date, and whether an intended
|
||||||
|
owner has an engine surface yet. If splitting those creates two sources for one
|
||||||
|
fact, that is worse than either option and we would rather hear it now. Your SSH-CA
|
||||||
|
signing write would be tracked in maturity-engine as a declared gap with intended
|
||||||
|
owner secrets-engine and a review date — that is reporting your own non-conformance
|
||||||
|
to an engine, so we would rather you assent to it than discover it. Also note approval-engine
|
||||||
|
(section 9.4): it owns the approval object, not the approval workflow, so ops-warden
|
||||||
|
lanes needing approval consume a claim rather than implementing one. Assent, revision,
|
||||||
|
or rejection acceptable.'
|
||||||
|
created: '2026-08-28T20:40:24.957468Z'
|
||||||
|
updated: '2026-08-28T20:40:24.957468Z'
|
||||||
|
state_hub_intake_id: "01a04d97-94cd-7b49-8019-a91c7fce8adb"
|
||||||
|
```
|
||||||
128
layer.yaml
Normal file
128
layer.yaml
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# ops-warden — NetKingdom security layer declaration
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# §11 makes one check mechanical: "every direct Tooling client in a Staff
|
||||||
|
# repository maps to a declared §5.1, §5.2, or §5.3 entry". This file is that
|
||||||
|
# map. It is machine-readable because §5.3 requires it to be — ops-warden
|
||||||
|
# proposed that shape and is implementing it rather than declaring in prose.
|
||||||
|
#
|
||||||
|
# Conformance rule inherited from tenancy.yaml: accuracy, not altitude. A
|
||||||
|
# declared gap is TRACKED NON-CONFORMANCE (§11), never a claim of conformance.
|
||||||
|
|
||||||
|
schema_version: "0.1"
|
||||||
|
framework: netkingdom-security-layer-model
|
||||||
|
standard_version: "0.7"
|
||||||
|
repository: ops-warden
|
||||||
|
layer: staff
|
||||||
|
declared_by: docs/adr/ADR-0010
|
||||||
|
declared_at: "2026-08-29"
|
||||||
|
|
||||||
|
# §6.4 — ops-warden is PEP-shaped (it causes a protected side effect: issuing a
|
||||||
|
# certificate). Its unreachable-engine stance map is published separately, and
|
||||||
|
# asserted equal to shipped behaviour by tests/test_layer_conformance.py.
|
||||||
|
pep_stance: pep-stance.yaml
|
||||||
|
|
||||||
|
# Every direct contact with a Tooling-layer system (§4), one entry each.
|
||||||
|
tooling_contacts:
|
||||||
|
|
||||||
|
- id: ssh-ca-signing-write
|
||||||
|
shape: "5.3" # declared engine gap
|
||||||
|
module: src/warden/vault.py
|
||||||
|
symbol: VaultCA.sign
|
||||||
|
tooling: OpenBao
|
||||||
|
operation: "HTTP POST <addr>/v1/<mount>/sign/<role> with X-Vault-Token"
|
||||||
|
write: true
|
||||||
|
capability: "Sign a short-lived SSH certificate for an adm/agt/atm actor"
|
||||||
|
intended_owner: secrets-engine
|
||||||
|
blocked_on: >-
|
||||||
|
No engine exposes an SSH certificate signing surface. secrets-engine owns
|
||||||
|
credential abstraction, custody and lifecycle, which is the layer this
|
||||||
|
belongs in, but fronts no SSH-CA API today.
|
||||||
|
review: "2026-11-28"
|
||||||
|
note: >-
|
||||||
|
Production-verified and in daily use. This is the one lane ops-warden owns
|
||||||
|
permanently (§4). Signing continues while the gap is open: refusing would
|
||||||
|
remove production host access to close a documentation gap.
|
||||||
|
|
||||||
|
- id: desk-paste-once-provision
|
||||||
|
shape: "5.3"
|
||||||
|
module: src/warden/desk.py
|
||||||
|
symbol: _provision_to_openbao
|
||||||
|
tooling: OpenBao
|
||||||
|
operation: "bao kv put <path> <field>=- (value on stdin, never argv)"
|
||||||
|
write: true
|
||||||
|
capability: "Founder paste-once provisioning of a secret straight into OpenBao"
|
||||||
|
intended_owner: secrets-engine
|
||||||
|
blocked_on: >-
|
||||||
|
No engine exposes an attended provisioning surface for a value the founder
|
||||||
|
holds and no automated path can produce.
|
||||||
|
review: "2026-11-28"
|
||||||
|
note: >-
|
||||||
|
Attended and founder-operated (WP-0029). The value reaches OpenBao without
|
||||||
|
passing through a terminal, an argv, or the audit log.
|
||||||
|
|
||||||
|
- id: taint-metadata-read
|
||||||
|
shape: "5.1" # read-only diagnostic observation
|
||||||
|
module: src/warden/taint.py
|
||||||
|
symbol: fetch_taint_status
|
||||||
|
tooling: OpenBao
|
||||||
|
operation: "bao kv metadata get -format=json <path>"
|
||||||
|
write: false
|
||||||
|
capability: "Report EXPOSED taint (custom_metadata) without reading secret data"
|
||||||
|
intended_owner: secrets-engine
|
||||||
|
blocked_on: >-
|
||||||
|
No engine exposes a disclosure-taint query. Metadata-only by construction —
|
||||||
|
reading the data would be the 2026-07-16 vector this exists to avoid.
|
||||||
|
review: "2026-11-28"
|
||||||
|
|
||||||
|
- id: access-proxy-conduit
|
||||||
|
shape: "5.2" # conduit
|
||||||
|
module: src/warden/proxy.py
|
||||||
|
symbol: proxy_fetch, proxy_attended_login_exec
|
||||||
|
tooling: OpenBao, key-cape
|
||||||
|
operation: "Runs the catalog-declared owner fetch_command as a child process"
|
||||||
|
write: false
|
||||||
|
capability: "warden access --fetch/--exec/--out/--wrap for exec_capable lanes"
|
||||||
|
supplied_authority: none
|
||||||
|
evidence:
|
||||||
|
no_own_credential: src/warden/proxy.py::_caller_env
|
||||||
|
test: tests/test_proxy.py::test_conduit_supplies_no_authority_of_its_own
|
||||||
|
audit: "audit.jsonl records the caller, the lane, and the outcome; never a value"
|
||||||
|
note: >-
|
||||||
|
The §5.2 test is the supplied-authority property: ops-warden presents no
|
||||||
|
credential of its own, cannot widen what the caller could already do, and
|
||||||
|
the action reconstructs as the caller's. Governed by ADR-0002.
|
||||||
|
|
||||||
|
- id: caller-identity-token
|
||||||
|
shape: "5.2"
|
||||||
|
module: src/warden/caller_identity.py
|
||||||
|
symbol: resolve_caller_token
|
||||||
|
tooling: OpenBao
|
||||||
|
operation: "Runs the operator-configured caller_auth command, or reads token env"
|
||||||
|
write: false
|
||||||
|
capability: "Establish the caller's own identity for the pre-sign policy gate"
|
||||||
|
supplied_authority: none
|
||||||
|
detection: voluntary # runs an operator-configured command, so no fixed
|
||||||
|
# argv shape to scan for; declared rather than omitted
|
||||||
|
note: >-
|
||||||
|
Obtains the CALLER's credential by the operator's configured means; adds no
|
||||||
|
authority. Never mints, and never persists what it resolves.
|
||||||
|
|
||||||
|
# Contacts that are deliberately NOT Tooling contacts, recorded so the check is
|
||||||
|
# total rather than silently selective.
|
||||||
|
non_tooling_clients:
|
||||||
|
|
||||||
|
- module: src/warden/policy.py
|
||||||
|
target: access-engine (flex-auth)
|
||||||
|
rationale: "Engine API — §5 permits it; this is the shape §5 prescribes."
|
||||||
|
|
||||||
|
- module: src/warden/worker.py
|
||||||
|
target: state-hub, llm-connect
|
||||||
|
rationale: >-
|
||||||
|
Not catalogued in §4. The layer catalog scopes the security estate, and
|
||||||
|
neither the State Hub nor llm-connect appears in it, so no §5 shape applies
|
||||||
|
on the standard's own terms. Raised with gate-house 2026-08-29 as a scope
|
||||||
|
question rather than resolved unilaterally — see the assessment note.
|
||||||
146
pep-stance.yaml
Normal file
146
pep-stance.yaml
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# ops-warden — PEP unreachable-engine stance map
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# §6.4 obligation 3 requires a declared unreachable-engine stance that is total,
|
||||||
|
# scoped per zone, carries no implicit default and no per-call discretion, and is
|
||||||
|
# "published rather than held in code comments". §6.4 further requires every
|
||||||
|
# PEP-shaped consumer to PUBLISH its map so the maps can be inventoried. This
|
||||||
|
# file is ops-warden's, published because a map that lives only in a dataclass
|
||||||
|
# default is not published — it is merely written down.
|
||||||
|
#
|
||||||
|
# The property that makes this worth reading: it is asserted equal to the shipped
|
||||||
|
# default in src/warden/config.py (PolicyConfig.failure_modes) by
|
||||||
|
# tests/test_layer_conformance.py. A published map that may drift from the code
|
||||||
|
# is worse than none, because it invites reliance it cannot support.
|
||||||
|
|
||||||
|
schema_version: "0.1"
|
||||||
|
framework: netkingdom-security-layer-model
|
||||||
|
# v0.7 is the accepted standard and the one in force. v0.8 is `proposed`: its
|
||||||
|
# assent round closed 2026-09-09 (GH-DEC-2026-011) with nine corrections applied,
|
||||||
|
# but the version is not yet accepted, so this declaration stays pinned to what
|
||||||
|
# binds. The one cell v0.8 would change is marked inline below rather than
|
||||||
|
# silently pre-adopted.
|
||||||
|
standard_version: "0.7"
|
||||||
|
standard_version_reviewed: "0.8" # reviewed and assented; see history/2026-09-09-layer-model-v08-review.md
|
||||||
|
repository: ops-warden
|
||||||
|
pep_shape: true
|
||||||
|
declared_by: docs/adr/ADR-0009
|
||||||
|
|
||||||
|
protected_action: "SSH certificate issuance (warden sign / cert_command)"
|
||||||
|
decision_engine: access-engine # flex-auth until the governed rename
|
||||||
|
scope: security-zone # security-zones_v0.1 membership of the TARGET workload
|
||||||
|
|
||||||
|
# Total by construction: every zone in security-zones_v0.1, plus the two
|
||||||
|
# non-zone outcomes. No implicit default — an unlisted value is a config error,
|
||||||
|
# not a permissive fallback.
|
||||||
|
stance:
|
||||||
|
z0-experimental: fail_open
|
||||||
|
z1-operational: fail_open
|
||||||
|
z2-protected: fail_open
|
||||||
|
z2-continuity: fail_open
|
||||||
|
z3-critical: fail_closed
|
||||||
|
# DECLARED GAP under security-layer-model v0.8 §6.4 obligation 3 (GH-DEC-2026-011,
|
||||||
|
# net-kingdom@64394e9): unknown is not a zone and MUST resolve to fail_closed.
|
||||||
|
# ops-warden ASSENTED to that rule — we went looking for the §5.1 read-only
|
||||||
|
# diagnostic its reversal clause predicts and do not have one, because this map
|
||||||
|
# governs `warden sign`, a credential-issuing side effect.
|
||||||
|
#
|
||||||
|
# The cell has not been flipped, and the reason is measured rather than argued:
|
||||||
|
# 0 of 3 signing targets resolve to a zone, so converting today would fail closed
|
||||||
|
# on essentially every certificate whenever the engine is unreachable — including
|
||||||
|
# the certificate an operator needs to reach the host and repair it. That is
|
||||||
|
# ADR-0006's rejected configuration reached from another direction.
|
||||||
|
#
|
||||||
|
# We asked for a coverage-gated transitional fail_open and were DECLINED: a
|
||||||
|
# sanctioned transitional fail_open is indistinguishable at runtime from the
|
||||||
|
# stance the rule forbids. Our second preference was adopted instead — §13.1 now
|
||||||
|
# carries a Coverage column, and this repo's figures are its first entries.
|
||||||
|
#
|
||||||
|
# So this is tracked non-conformance with a route, not an exemption.
|
||||||
|
# Route: WARDEN-WP-0040. Register row: §13.1, marked.
|
||||||
|
unknown: fail_open # non-conformant at v0.8; see above
|
||||||
|
not-applicable: fail_closed
|
||||||
|
|
||||||
|
# What happens when the stance is applied. §6.4 obligation 1 requires a decision
|
||||||
|
# record for a protected side effect; where the engine is unreachable there is no
|
||||||
|
# decision to hold, so ops-warden records the APPLICATION OF THE STANCE instead.
|
||||||
|
# See the assessment note: obligation 1 as written admits no such case.
|
||||||
|
on_apply:
|
||||||
|
recorded_fields:
|
||||||
|
- policy_zone
|
||||||
|
- policy_failure_mode
|
||||||
|
- policy_decision_id # present only where a decision was actually rendered
|
||||||
|
- outcome
|
||||||
|
written_to:
|
||||||
|
- "signatures log (src/warden/ca.py)"
|
||||||
|
- "audit.jsonl (src/warden/audit.py)"
|
||||||
|
never_recorded: "any secret material, any certificate private key"
|
||||||
|
|
||||||
|
# §6.4 obligation 2 — the verdict is never cached. Input claims (zone membership,
|
||||||
|
# compiled from the flex-auth registry snapshot) are cached under their own
|
||||||
|
# freshness rules; the answer is not.
|
||||||
|
# Classification coverage, published beside the stance because v0.8 §6.4
|
||||||
|
# obligation 3 now requires it and because ops-warden asked for the column.
|
||||||
|
# Coverage is DISCLOSURE, never a transitional licence: it does not soften this
|
||||||
|
# map's stance, does not gate it, and never makes the marked cell conformant.
|
||||||
|
# Self-measured; regenerate with `python scripts/report_coverage.py`.
|
||||||
|
classification_coverage:
|
||||||
|
measured: "2026-09-09"
|
||||||
|
attribution: self-measured
|
||||||
|
signing_targets:
|
||||||
|
resolved: 0
|
||||||
|
unknown: 3
|
||||||
|
not_applicable: 1
|
||||||
|
routing_lanes:
|
||||||
|
resolved: 3
|
||||||
|
unknown: 18
|
||||||
|
not_applicable: 12
|
||||||
|
|
||||||
|
verdict_caching: none
|
||||||
|
input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebuilt by scripts/build_flex_auth_registry.py"
|
||||||
|
|
||||||
|
# §6.4 obligation 4 — reconstructability, bounded by §9.6. ops-warden's audit
|
||||||
|
# emission on this lane is deliberately non-atomic and therefore ATTRIBUTIVE, not
|
||||||
|
# load-bearing: no control branches on the presence of a signing record
|
||||||
|
# (`warden activity` displays it; nothing gates on it). Registered in §13.
|
||||||
|
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"
|
||||||
|
|
@ -10,19 +10,21 @@
|
||||||
# declares it, and is null where the field set has not been established --
|
# declares it, and is null where the field set has not been established --
|
||||||
# null means unknown, never 'one field'.
|
# null means unknown, never 'one field'.
|
||||||
|
|
||||||
generated_at: "2026-08-22T23:31:10Z"
|
generated_at: "2026-09-10T05:54:39Z"
|
||||||
source: ops-warden/registry/routing/catalog.yaml
|
source: ops-warden/registry/routing/catalog.yaml
|
||||||
catalog_revision: "0fae0904ce8d8694338dd53a8a79abec5fec788d"
|
catalog_revision: "406446f7bb38efadf85849756c067a124902a2ec"
|
||||||
catalog_revision_date: "2026-08-23T01:31:05+02:00"
|
catalog_revision_date: "2026-09-10T07:54:39+02:00"
|
||||||
catalog_dirty: false
|
catalog_dirty: false
|
||||||
high_risk_lane_count: 21
|
high_risk_lane_count: 24
|
||||||
concrete_path_count: 14
|
concrete_path_count: 15
|
||||||
|
|
||||||
# Graded high but not a single KV address -- a routing pattern, a broker
|
# Graded high but not a single KV address -- a routing pattern, a broker
|
||||||
# grant, or a non-KV lane. Nothing here for a policy to deny.
|
# grant, or a non-KV lane. Nothing here for a policy to deny.
|
||||||
no_concrete_path:
|
no_concrete_path:
|
||||||
- database-dynamic-credentials
|
- database-dynamic-credentials
|
||||||
- inter-hub-bootstrap-ssh
|
- inter-hub-bootstrap-ssh
|
||||||
|
- net-kingdom-lldap-bind-credential
|
||||||
|
- net-kingdom-privacyidea-admin-token
|
||||||
- object-storage-sts
|
- object-storage-sts
|
||||||
- openbao-api-key
|
- openbao-api-key
|
||||||
- openbao-platform-admin-login
|
- openbao-platform-admin-login
|
||||||
|
|
@ -75,6 +77,11 @@ paths:
|
||||||
metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||||
owner_repo: railiance-platform
|
owner_repo: railiance-platform
|
||||||
fields: null # field set not established -- unknown, not one
|
fields: null # field set not established -- unknown, not one
|
||||||
|
- id: policy-nexus-forgejo-source-read
|
||||||
|
data_path: platform/data/workloads/policy-nexus/forgejo-source-read
|
||||||
|
metadata_path: platform/metadata/workloads/policy-nexus/forgejo-source-read
|
||||||
|
owner_repo: railiance-platform
|
||||||
|
fields: null # field set not established -- unknown, not one
|
||||||
- id: railiance-backup-offsite-lane
|
- id: railiance-backup-offsite-lane
|
||||||
data_path: platform/data/workloads/railiance/backup/offsite-lane
|
data_path: platform/data/workloads/railiance/backup/offsite-lane
|
||||||
metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane
|
metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,24 @@
|
||||||
version: 1
|
version: 1
|
||||||
|
|
||||||
entries:
|
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
|
- id: ssh-cert-host-access
|
||||||
title: Short-lived SSH certificate for host / ops reachability
|
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).
|
# Emits a signed certificate — a public artifact. The private key never leaves the caller (WARDEN-WP-0032-T05).
|
||||||
|
|
@ -196,19 +214,19 @@ entries:
|
||||||
lane: ceremony
|
lane: ceremony
|
||||||
|
|
||||||
- id: whynot-design-npm-publish
|
- id: whynot-design-npm-publish
|
||||||
title: whynot-design npm publish token (@whynot/design → coulomb Gitea registry)
|
title: whynot-design npm publish token (@whynot/design → coulomb Forgejo registry)
|
||||||
# Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05).
|
# Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05).
|
||||||
risk: high
|
risk: high
|
||||||
workload_ref:
|
workload_ref:
|
||||||
applicability: applicable
|
applicability: applicable
|
||||||
unknown_reason: "whynot-design has not published an authoritative workload identity declaration."
|
unknown_reason: "whynot-design has not published an authoritative workload identity declaration."
|
||||||
need_keywords: [whynot-design, whynot, npm, publish, npm_auth_token, gitea, registry, coulomb, package]
|
need_keywords: [whynot-design, whynot, npm, publish, npm_auth_token, forgejo, registry, coulomb, package]
|
||||||
owner_repo: railiance-platform
|
owner_repo: railiance-platform
|
||||||
subsystem: OpenBao
|
subsystem: OpenBao
|
||||||
warden_executes: false
|
warden_executes: false
|
||||||
wiki_ref: wiki/playbooks/whynot-design-npm-publish.md#worker-checklist
|
wiki_ref: wiki/playbooks/whynot-design-npm-publish.md#worker-checklist
|
||||||
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
|
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
|
||||||
reviewed: "2026-06-29"
|
reviewed: "2026-09-04"
|
||||||
status: active
|
status: active
|
||||||
delegation:
|
delegation:
|
||||||
mode: native
|
mode: native
|
||||||
|
|
@ -223,7 +241,15 @@ entries:
|
||||||
# form is superseded; do not reintroduce it.
|
# form is superseded; do not reintroduce it.
|
||||||
auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read"
|
auth_method: "bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read"
|
||||||
path_template: "platform/workloads/coulomb/whynot-design/npm-publish"
|
path_template: "platform/workloads/coulomb/whynot-design/npm-publish"
|
||||||
fetch_command: "bao kv get -field=NPM_AUTH_TOKEN platform/workloads/coulomb/whynot-design/npm-publish"
|
# Field corrected 2026-09-09 on the owner's statement (secrets-engine msg
|
||||||
|
# 15f0c0ca): `npm_token` is the KV FIELD; `NPM_AUTH_TOKEN` is the environment
|
||||||
|
# variable the publication-scope policy injects, and ops-warden had copied the
|
||||||
|
# env var in as the field name. That is ADR-0001's failure mode exactly — a
|
||||||
|
# pointer layer restating an owner's procedure and getting it wrong — so it is
|
||||||
|
# corrected from the owner's doc (secrets-engine docs/whynot-design-real-publish-closeout.md),
|
||||||
|
# never re-derived here. The path is a separate question, open with
|
||||||
|
# railiance-platform; do not change it on this correction.
|
||||||
|
fetch_command: "bao kv get -field=npm_token platform/workloads/coulomb/whynot-design/npm-publish"
|
||||||
policy_ref: "flex-auth check secret.read:whynot-design"
|
policy_ref: "flex-auth check secret.read:whynot-design"
|
||||||
exec_capable: true
|
exec_capable: true
|
||||||
lane: secret
|
lane: secret
|
||||||
|
|
@ -238,9 +264,45 @@ entries:
|
||||||
owner: railiance-platform
|
owner: railiance-platform
|
||||||
automatable: false
|
automatable: false
|
||||||
steps:
|
steps:
|
||||||
- "In the coulomb Gitea/Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity."
|
- "In the coulomb Forgejo registry, revoke the current @whynot/design publish token and generate a new one (scope: package read/write) for the whynot-design publish identity."
|
||||||
- "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish NPM_AUTH_TOKEN=@file` (value from a mode-0600 file)."
|
- "Write it back: `bao kv put platform/workloads/coulomb/whynot-design/npm-publish npm_token=@file` (value from a mode-0600 file). The field is `npm_token`; `NPM_AUTH_TOKEN` is the injected env var, not a KV key."
|
||||||
- "Verify capabilities-safe, then confirm publish works via `secrets-engine exec --catalog whynot-design-npm-publish -- npm whoami` (value used, not printed)."
|
- "Verify capabilities-safe, then publish a fresh version and confirm it with Forgejo-supported `npm view <package>@<version>` through the governed execution lane (value used, not printed)."
|
||||||
|
|
||||||
|
- id: policy-nexus-forgejo-source-read
|
||||||
|
title: Policy Nexus Forgejo private-source repository read token
|
||||||
|
# Read-only at Forgejo, but disclosure grants estate-wide private source access.
|
||||||
|
# Agent callers therefore remain inside Warden's sanctioned transport boundary.
|
||||||
|
risk: high
|
||||||
|
workload_ref:
|
||||||
|
applicability: applicable
|
||||||
|
unknown_reason: "policy-nexus-actions has not published an authoritative workload identity declaration."
|
||||||
|
need_keywords: [policy-nexus, policy, nexus, forgejo, private, source, repository, read, token, actions, FORGEJO_SOURCE_TOKEN]
|
||||||
|
owner_repo: railiance-platform
|
||||||
|
subsystem: OpenBao + Forgejo Actions
|
||||||
|
warden_executes: false
|
||||||
|
wiki_ref: wiki/playbooks/policy-nexus-forgejo-source-read.md#worker-checklist
|
||||||
|
canon_ref: railiance-platform/credential-change-requests/CCR-2026-0014-policy-nexus-forgejo-source-read.yaml
|
||||||
|
reviewed: "2026-09-01"
|
||||||
|
status: active
|
||||||
|
delegation:
|
||||||
|
mode: native
|
||||||
|
intended_owner: railiance-platform
|
||||||
|
reviewed: "2026-09-01"
|
||||||
|
verified: source-read
|
||||||
|
auth_method: "bao login -method=oidc -path=netkingdom role=policy-nexus-forgejo-source-workload-kv-read"
|
||||||
|
path_template: "platform/workloads/policy-nexus/forgejo-source-read"
|
||||||
|
fetch_command: "bao kv get -field=FORGEJO_SOURCE_TOKEN platform/workloads/policy-nexus/forgejo-source-read"
|
||||||
|
policy_ref: "flex-auth check secret.read:policy-nexus"
|
||||||
|
exec_capable: true
|
||||||
|
lane: secret
|
||||||
|
rotation:
|
||||||
|
method: rotate
|
||||||
|
owner: railiance-platform
|
||||||
|
automatable: false
|
||||||
|
steps:
|
||||||
|
- "Mint a replacement PAT for the restricted policy-nexus-source identity with scope exactly read:repository; retain the predecessor until verification passes."
|
||||||
|
- "Use the attended railiance-platform bootstrap to update OpenBao and the coulomb/policy-nexus FORGEJO_SOURCE_TOKEN Actions secret without exposing the value."
|
||||||
|
- "Pass one exact-commit candidate workflow, then revoke the predecessor PAT and record bounded non-secret evidence in CCR-2026-0014."
|
||||||
|
|
||||||
- id: flex-auth-policy-check
|
- id: flex-auth-policy-check
|
||||||
title: Authorization decision — may this actor perform this action
|
title: Authorization decision — may this actor perform this action
|
||||||
|
|
@ -281,16 +343,16 @@ entries:
|
||||||
delegation:
|
delegation:
|
||||||
mode: interim
|
mode: interim
|
||||||
intended_owner: key-cape
|
intended_owner: key-cape
|
||||||
blocked_on: "REFUSED by secrets-engine 2026-08-21: login, MFA and identity-token issuance are key-cape/Keycloak's; secrets-engine may consume OIDC for OpenBao auth but does not own the login capability. ops-warden agrees — intended_owner corrected from secrets-engine to key-cape. Not yet asked of key-cape."
|
blocked_on: "ACCEPTED by key-cape 2026-09-08 (msg 12f1bdfa): key-cape owns interactive login, OIDC identity-token issuance and MFA for this lane, confirming the 2026-08-28 correction of intended_owner from secrets-engine (whose 2026-08-21 refusal was right). Lane stays interim because the accepted ownership covers only the identity half: the fetch_command yields an OPENBAO token, and the netkingdom auth mount, the role=<domain> mapping, the token store and its enforcement are OpenBao's, with role/policy definition railiance-platform's. keycape login is not a drop-in — it yields a key-cape issuer JWT written to a private file, a different credential type with a different verifier (KEY-WP-0014-T04). Retires when a key-cape-native front door yields the credential this route's consumers expect, or when the route is deliberately re-contracted to a JWT. Verified against key-cape docs/native-authentication.md, docs/openbao-service-auth-contract.md, config/service-clients.example.yaml, src/internal/server/oidc/discovery.go."
|
||||||
reviewed: "2026-08-21"
|
reviewed: "2026-09-08"
|
||||||
verified: asked-and-waiting
|
verified: owner-confirmed
|
||||||
# Login lane (WP-0014 T4) — interactive auth bootstrap, not a secret read. No
|
# 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
|
# 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
|
# point is to obtain one). warden runs it interactively as the caller and never
|
||||||
# captures the resulting token — the owner tool writes it to the caller's store.
|
# captures the resulting token — the owner tool writes it to the caller's store.
|
||||||
lane: login
|
lane: login
|
||||||
auth_method: "browser OIDC via key-cape / Keycloak"
|
auth_method: "browser OIDC via key-cape / Keycloak at auth/netkingdom"
|
||||||
fetch_command: "bao login -method=oidc role=<domain>"
|
fetch_command: "bao login -no-print -method=oidc -path=netkingdom role=<domain>"
|
||||||
exec_capable: true
|
exec_capable: true
|
||||||
|
|
||||||
- id: ops-bridge-tunnel
|
- id: ops-bridge-tunnel
|
||||||
|
|
@ -710,9 +772,9 @@ entries:
|
||||||
delegation:
|
delegation:
|
||||||
mode: interim
|
mode: interim
|
||||||
intended_owner: key-cape
|
intended_owner: key-cape
|
||||||
blocked_on: "client_secret_basic exchange is a key-cape protocol procedure, not a KV read; KEY-WP-0008 finished 2026-08-15 without a key-cape-native exchange/rotation command, and the workplan that would add one is not yet opened (key-cape acked interim 2026-08-15)"
|
blocked_on: "Narrowed again 2026-09-09: the AUTHORITY is answered, the ARTIFACT is not. railiance-platform (msg 7c7228ac) confirmed steps 1-2 are theirs: executed by the platform operator attended, never by ops-warden, secrets-engine autonomously, or any unattended agent; transport is the governed openbao-platform-admin-login lane invoked only through `warden access openbao-platform-admin-login --exec -- <command>` with a unique metadata receipt path (RPF-WP-0017 output containment); authority is founder_required attended OIDC via netkingdom role=platform-admin. What is still missing is a reviewed rotation CCR — a two-custodian CAS rotation with a service restart is a distinct version-guarded operation, not an implementation detail of this lane, and it must name the CAS precondition and expected version on both custodians, sibling-field preservation, the restart window, bounded predecessor retention, and the reconcile-to-same-version failure step. That CCR is railiance-platform to write under RPF-WP-0035 once an owner asks for the rotation; ops-warden has asked key-cape whether to schedule it. Executable precedent for the same two-custodian shape: railiance-platform scripts/keycape_approval_custody.py, with its dated receipt under docs/evidence/ and review packet under docs/credential-lane-designs/. Prior (2026-09-08) narrowing to rotation steps 1-2 only: successor generation and the CAS write to both custodians (platform/workloads/rapp-qonto/keycape-client field client_secret, and sso/keycape-rapp-qonto-client key client-secret) remain custody/deployment acts. The key-cape-native exchange now exists (keycape service-token, 2026-09-05, KEY-WP-0014-T03) and step 3 verification exists as one command (keycape verify-client, 2026-09-08, including predecessor rejection and identical-secret detection); both are documented in key-cape/docs/native-authentication.md. The prior blocker recorded both as absent, which was accurate on 2026-08-28 and is not accurate now — corrected by key-cape (msg 08d42f47). Re-checked against key-cape source 2026-09-08."
|
||||||
reviewed: "2026-08-17"
|
reviewed: "2026-09-09"
|
||||||
verified: unverified
|
verified: owner-confirmed
|
||||||
risk: high
|
risk: high
|
||||||
workload_ref:
|
workload_ref:
|
||||||
applicability: applicable
|
applicability: applicable
|
||||||
|
|
@ -727,11 +789,15 @@ entries:
|
||||||
rotation:
|
rotation:
|
||||||
method: rotate
|
method: rotate
|
||||||
owner: key-cape
|
owner: key-cape
|
||||||
automatable: true
|
# Not drivable end to end: steps 1-2 are custody/deployment acts with no
|
||||||
|
# admitted execution transport, so an executable driver (WARDEN-WP-0027
|
||||||
|
# Strand B) must not treat this lane as automatable. Step 3 alone is one
|
||||||
|
# owner command today — the precision lives in the steps, not the flag.
|
||||||
|
automatable: false
|
||||||
steps:
|
steps:
|
||||||
- "Generate a fresh newline-free secret through an approved execution transport; never print it."
|
- "Generate a fresh newline-free secret through an approved execution transport; never print it. NOT automatable — no admitted execution and rollback contract (see delegation.blocked_on)."
|
||||||
- "Write the same value to OpenBao and sso/keycape-rapp-qonto-client, then restart KeyCape."
|
- "CAS-write the same value to platform/workloads/rapp-qonto/keycape-client and sso/keycape-rapp-qonto-client preserving siblings, then restart KeyCape in the agreed window. NOT automatable — same blocker; on failure reconcile both custodians to the same version before declaring rollback complete."
|
||||||
- "Verify positive qonto:read exchange plus wrong-secret and excessive-scope denial without printing tokens."
|
- "Verify positive qonto:read exchange plus predecessor-secret and excessive-scope denial without printing tokens: run the owner command keycape verify-client (key-cape/docs/native-authentication.md)."
|
||||||
|
|
||||||
- id: net-kingdom-lldap-bind-credential
|
- id: net-kingdom-lldap-bind-credential
|
||||||
title: NetKingdom LLDAP bind credential for identity and privacyIDEA resolver
|
title: NetKingdom LLDAP bind credential for identity and privacyIDEA resolver
|
||||||
|
|
|
||||||
161
scripts/check_flex_auth_manifest_coverage.py
Normal file
161
scripts/check_flex_auth_manifest_coverage.py
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Assert every ssh-certificate resource declares its ceilings in the manifest.
|
||||||
|
|
||||||
|
Read-only. Reads `inventory.yaml` and the flex-auth registry snapshot; touches
|
||||||
|
no network, no OpenBao, and no secret material.
|
||||||
|
|
||||||
|
Why this exists (FLEX-DEC-2026-012, 2026-09-07). flex-auth's enrichment used to
|
||||||
|
overlay registry facts additive-if-absent, so a caller-supplied value for a key
|
||||||
|
won and the registry's ceiling never applied. That is fixed on their side:
|
||||||
|
registry facts now win. But they win only where the registry HAS a value —
|
||||||
|
|
||||||
|
"A resource whose manifest omits max_ttl_hours hands that ceiling back to
|
||||||
|
the caller, and registering the resource is not sufficient; the specific
|
||||||
|
key must be present."
|
||||||
|
|
||||||
|
So there are two ways to hand a ceiling back, and this check covers both:
|
||||||
|
|
||||||
|
1. An actor with no manifest resource at all. `warden sign` names
|
||||||
|
`ssh-cert:actor/<name>` for any actor in inventory, whether or not the
|
||||||
|
snapshot was regenerated. Adding an actor and forgetting to rebuild is an
|
||||||
|
honour-system step in `SCOPE.md`, and honour-system steps are what
|
||||||
|
`ADR-0004`'s WARDEN_AGENT_ID marker taught us to stop relying on.
|
||||||
|
2. A manifest resource missing one of the ceiling or allowlist keys. The
|
||||||
|
builder emits all of them today; this asserts it stays true, including for
|
||||||
|
resources added by hand or by a future code path.
|
||||||
|
|
||||||
|
ops-warden sends no `resource.attributes` on a CheckRequest (`src/warden/policy.py`),
|
||||||
|
which is why FLEX-DEC-2026-012 was defence-in-depth rather than a live hole for
|
||||||
|
this repo. That property is asserted separately in `tests/test_policy.py`; this
|
||||||
|
check covers the half that survives it — if anything ever does reach a request
|
||||||
|
field, the ceiling it would have to beat must actually exist.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python scripts/check_flex_auth_manifest_coverage.py \
|
||||||
|
--inventory examples/inventory.seed.yaml \
|
||||||
|
--registry registry/flex-auth/production_registry_snapshot.json [--json]
|
||||||
|
|
||||||
|
Exit: 0 covered, 2 coverage gap, 1 usage/IO error.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
#: Every key ops-warden's shipped policy package branches on as a ceiling or an
|
||||||
|
#: allowlist. Named by flex-auth in FLEX-DEC-2026-012; kept here rather than
|
||||||
|
#: derived from the package so a package edit that drops a branch does not
|
||||||
|
#: silently shrink what this check requires.
|
||||||
|
REQUIRED_CEILING_KEYS = (
|
||||||
|
"actor_id",
|
||||||
|
"actor_type",
|
||||||
|
"allowed_principals",
|
||||||
|
"allowed_subjects",
|
||||||
|
"max_ttl_hours",
|
||||||
|
"security_zone",
|
||||||
|
"security_zone_admission",
|
||||||
|
)
|
||||||
|
|
||||||
|
RESOURCE_TYPE = "ssh-certificate"
|
||||||
|
|
||||||
|
|
||||||
|
def _resources(registry: dict[str, Any]) -> list[dict[str, Any]]:
|
||||||
|
out: list[dict[str, Any]] = []
|
||||||
|
for manifest in registry.get("resource_manifests") or []:
|
||||||
|
out.extend(manifest.get("resources") or [])
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def audit(inventory: dict[str, Any], registry: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
actors = sorted((inventory.get("actors") or {}).keys())
|
||||||
|
resources = _resources(registry)
|
||||||
|
by_id = {str(r.get("id")): r for r in resources}
|
||||||
|
|
||||||
|
unregistered: list[str] = []
|
||||||
|
missing_keys: list[dict[str, Any]] = []
|
||||||
|
|
||||||
|
for name in actors:
|
||||||
|
if f"ssh-cert:actor/{name}" not in by_id:
|
||||||
|
unregistered.append(name)
|
||||||
|
|
||||||
|
# Check every ssh-certificate resource in the manifest, not only the ones an
|
||||||
|
# inventory actor maps to: a resource flex-auth can be asked about is one it
|
||||||
|
# holds, regardless of where it came from.
|
||||||
|
for resource in resources:
|
||||||
|
if str(resource.get("type")) != RESOURCE_TYPE:
|
||||||
|
continue
|
||||||
|
attributes = resource.get("attributes") or {}
|
||||||
|
absent = [
|
||||||
|
key for key in REQUIRED_CEILING_KEYS
|
||||||
|
# A null is not a declaration: flex-auth overlays a registry value
|
||||||
|
# only where one exists, so `key: null` hands the ceiling back
|
||||||
|
# exactly as an absent key does.
|
||||||
|
if attributes.get(key) is None
|
||||||
|
]
|
||||||
|
if absent:
|
||||||
|
missing_keys.append({"resource": str(resource.get("id")), "missing": absent})
|
||||||
|
|
||||||
|
# Not a security defect — a stale resource has ceilings, it just has no
|
||||||
|
# actor. Reported so drift is visible rather than accumulating silently.
|
||||||
|
orphaned = sorted(
|
||||||
|
rid for rid, r in by_id.items()
|
||||||
|
if str(r.get("type")) == RESOURCE_TYPE
|
||||||
|
and rid.removeprefix("ssh-cert:actor/") not in actors
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"actors": len(actors),
|
||||||
|
"resources": sum(1 for r in resources if str(r.get("type")) == RESOURCE_TYPE),
|
||||||
|
"required_keys": list(REQUIRED_CEILING_KEYS),
|
||||||
|
"unregistered_actors": unregistered,
|
||||||
|
"resources_missing_keys": missing_keys,
|
||||||
|
"orphaned_resources": orphaned,
|
||||||
|
"covered": not unregistered and not missing_keys,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--inventory", default="examples/inventory.seed.yaml", type=Path)
|
||||||
|
parser.add_argument(
|
||||||
|
"--registry",
|
||||||
|
default="registry/flex-auth/production_registry_snapshot.json",
|
||||||
|
type=Path,
|
||||||
|
)
|
||||||
|
parser.add_argument("--json", action="store_true", dest="as_json")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
try:
|
||||||
|
inventory = yaml.safe_load(args.inventory.read_text()) or {}
|
||||||
|
registry = json.loads(args.registry.read_text())
|
||||||
|
except (OSError, ValueError) as exc:
|
||||||
|
print(f"error: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
report = audit(inventory, registry)
|
||||||
|
report["inventory"] = str(args.inventory)
|
||||||
|
report["registry"] = str(args.registry)
|
||||||
|
|
||||||
|
if args.as_json:
|
||||||
|
print(json.dumps(report, indent=2))
|
||||||
|
else:
|
||||||
|
print(f"inventory : {args.inventory} ({report['actors']} actors)")
|
||||||
|
print(f"registry : {args.registry} ({report['resources']} ssh-certificate resources)")
|
||||||
|
for name in report["unregistered_actors"]:
|
||||||
|
print(f" MISSING {name} — no manifest resource; every ceiling is caller-supplied")
|
||||||
|
for row in report["resources_missing_keys"]:
|
||||||
|
print(f" UNDECLARED {row['resource']} — {', '.join(row['missing'])}")
|
||||||
|
for rid in report["orphaned_resources"]:
|
||||||
|
print(f" orphaned {rid} — manifest resource with no inventory actor")
|
||||||
|
print("covered" if report["covered"] else "NOT COVERED")
|
||||||
|
|
||||||
|
return 0 if report["covered"] else 2
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
164
scripts/check_layer_conformance.py
Normal file
164
scripts/check_layer_conformance.py
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check ops-warden against the NetKingdom security layer model (§5, §11).
|
||||||
|
|
||||||
|
Read-only. Makes §11's second mechanical check real:
|
||||||
|
|
||||||
|
every direct Tooling client in a Staff repository maps to a declared
|
||||||
|
§5.1, §5.2, or §5.3 entry
|
||||||
|
|
||||||
|
The failure this catches is a *new* direct OpenBao contact appearing in
|
||||||
|
src/warden/ without an entry in layer.yaml — an undeclared violation (§11),
|
||||||
|
which is a finding rather than a tracked gap. It deliberately does NOT check
|
||||||
|
the review dates: a date-triggered failure breaks the build on a calendar day
|
||||||
|
with no code change (the reasoning recorded in WARDEN-WP-0033-T05), so
|
||||||
|
staleness is reported and left to `--report`, never to CI.
|
||||||
|
|
||||||
|
Exit 0 clean, 1 undeclared contact found, 2 declaration malformed.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SRC = ROOT / "src" / "warden"
|
||||||
|
DECL = ROOT / "layer.yaml"
|
||||||
|
|
||||||
|
VALID_SHAPES = {"5.1", "5.2", "5.3"}
|
||||||
|
|
||||||
|
# A direct Tooling contact is an *invocation*, not a mention. Matching the word
|
||||||
|
# "bao" caught help text, a docstring, and the dev-tier doubles library that
|
||||||
|
# simulates bao rather than calling it — three false positives on first run.
|
||||||
|
# So match the two shapes that actually execute:
|
||||||
|
# 1. an HTTP request built against the OpenBao address
|
||||||
|
# 2. an argv list whose first element is the bao binary
|
||||||
|
TOOLING_PATTERNS = (
|
||||||
|
# httpx call whose URL is built from the configured OpenBao/Vault address
|
||||||
|
re.compile(r"""\bhttpx\.\w+\(|url\s*=\s*f?["'].*\{self\._cfg\.addr\}"""),
|
||||||
|
# argv construction: [bao_bin, ...] / ["bao", ...] / [bao_binary, ...]
|
||||||
|
re.compile(r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b)\s*,"""),
|
||||||
|
)
|
||||||
|
|
||||||
|
# httpx alone is not a Tooling contact — policy.py calls an Engine and worker.py
|
||||||
|
# calls the State Hub. A module matching only the httpx pattern counts as a
|
||||||
|
# contact only if it also references the OpenBao address configuration.
|
||||||
|
ADDR_HINT = re.compile(r"""_cfg\.addr|VAULT_ADDR|BAO_ADDR""")
|
||||||
|
|
||||||
|
# Modules that talk to an Engine or to something outside the §4 catalog. Listed
|
||||||
|
# in layer.yaml under non_tooling_clients and excluded from the scan with it.
|
||||||
|
def _excluded(decl: dict) -> set[str]:
|
||||||
|
return {e["module"].split("/")[-1] for e in decl.get("non_tooling_clients", [])}
|
||||||
|
|
||||||
|
|
||||||
|
def load_declaration() -> dict:
|
||||||
|
if not DECL.exists():
|
||||||
|
print(f"MISSING: {DECL} — ops-warden must declare in its own voice (§11)")
|
||||||
|
raise SystemExit(2)
|
||||||
|
decl = yaml.safe_load(DECL.read_text())
|
||||||
|
for key in ("layer", "repository", "standard_version", "tooling_contacts"):
|
||||||
|
if key not in decl:
|
||||||
|
print(f"MALFORMED: layer.yaml has no {key!r}")
|
||||||
|
raise SystemExit(2)
|
||||||
|
for c in decl["tooling_contacts"]:
|
||||||
|
if c.get("shape") not in VALID_SHAPES:
|
||||||
|
print(f"MALFORMED: {c.get('id')} has shape {c.get('shape')!r}, not one of {sorted(VALID_SHAPES)}")
|
||||||
|
raise SystemExit(2)
|
||||||
|
# §5.3 carries four fields, machine-readably. That is the whole point of
|
||||||
|
# the shape; a gap missing them is prose wearing a schema.
|
||||||
|
if c["shape"] == "5.3":
|
||||||
|
for field in ("capability", "intended_owner", "blocked_on", "review"):
|
||||||
|
if not c.get(field):
|
||||||
|
print(f"MALFORMED: §5.3 entry {c['id']!r} is missing {field!r}")
|
||||||
|
raise SystemExit(2)
|
||||||
|
# §5.2's test is the supplied-authority property.
|
||||||
|
if c["shape"] == "5.2" and c.get("supplied_authority") != "none":
|
||||||
|
print(f"MALFORMED: §5.2 conduit {c['id']!r} must declare supplied_authority: none")
|
||||||
|
raise SystemExit(2)
|
||||||
|
return decl
|
||||||
|
|
||||||
|
|
||||||
|
def scan_modules() -> dict[str, list[int]]:
|
||||||
|
"""Return {module_name: [line numbers]} for direct Tooling contacts."""
|
||||||
|
found: dict[str, list[int]] = {}
|
||||||
|
for path in sorted(SRC.rglob("*.py")):
|
||||||
|
if path.name.startswith("test_"):
|
||||||
|
continue
|
||||||
|
text = path.read_text()
|
||||||
|
hits: list[int] = []
|
||||||
|
for n, line in enumerate(text.splitlines(), 1):
|
||||||
|
stripped = line.strip()
|
||||||
|
if stripped.startswith("#") or stripped.startswith('"'):
|
||||||
|
continue
|
||||||
|
if any(p.search(line) for p in TOOLING_PATTERNS):
|
||||||
|
hits.append(n)
|
||||||
|
if hits:
|
||||||
|
# An httpx-only match needs the OpenBao address to be a Tooling
|
||||||
|
# contact; otherwise it is an Engine or non-catalogued call.
|
||||||
|
argv_shape = any(TOOLING_PATTERNS[1].search(ln) for ln in text.splitlines())
|
||||||
|
if argv_shape or ADDR_HINT.search(text):
|
||||||
|
found[path.name] = hits
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--report", action="store_true", help="also print the declaration and gap review dates")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
decl = load_declaration()
|
||||||
|
declared = {c["module"].split("/")[-1] for c in decl["tooling_contacts"]}
|
||||||
|
excluded = _excluded(decl)
|
||||||
|
found = scan_modules()
|
||||||
|
|
||||||
|
undeclared = {m: lines for m, lines in found.items() if m not in declared and m not in excluded}
|
||||||
|
# A voluntary declaration has no fixed argv shape to detect (an
|
||||||
|
# operator-configured command). Over-declaring is safe; not reporting it as
|
||||||
|
# stale keeps the signal meaningful.
|
||||||
|
voluntary = {
|
||||||
|
c["module"].split("/")[-1]
|
||||||
|
for c in decl["tooling_contacts"]
|
||||||
|
if c.get("detection") == "voluntary"
|
||||||
|
}
|
||||||
|
stale_decls = declared - set(found) - voluntary
|
||||||
|
|
||||||
|
if args.report:
|
||||||
|
print(f"{decl['repository']} — layer: {decl['layer']} (model v{decl['standard_version']})")
|
||||||
|
print(f"declared by {decl['declared_by']}\n")
|
||||||
|
for c in decl["tooling_contacts"]:
|
||||||
|
line = f" §{c['shape']} {c['id']:<28} {c['module']}"
|
||||||
|
if c["shape"] == "5.3":
|
||||||
|
overdue = str(c["review"]) < date.today().isoformat()
|
||||||
|
line += f" -> {c['intended_owner']} review {c['review']}"
|
||||||
|
if overdue:
|
||||||
|
line += " [REVIEW OVERDUE]"
|
||||||
|
print(line)
|
||||||
|
gaps = [c for c in decl["tooling_contacts"] if c["shape"] == "5.3"]
|
||||||
|
print(f"\n{len(gaps)} declared gap(s) — tracked non-conformance, not conformance (§11).")
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
if undeclared:
|
||||||
|
ok = False
|
||||||
|
print("\nUNDECLARED TOOLING CONTACT — a finding under §11, not a tracked gap:")
|
||||||
|
for m, lines in sorted(undeclared.items()):
|
||||||
|
print(f" src/warden/{m}: line(s) {', '.join(map(str, lines[:6]))}")
|
||||||
|
print("\nAdd a §5.1/§5.2/§5.3 entry to layer.yaml, or route it through an engine.")
|
||||||
|
|
||||||
|
if stale_decls:
|
||||||
|
print("\nNote: declared but no contact found (module removed or refactored?):")
|
||||||
|
for m in sorted(stale_decls):
|
||||||
|
print(f" {m}")
|
||||||
|
|
||||||
|
if ok and not args.report:
|
||||||
|
print(f"PASS — {len(found)} module(s) with Tooling contact, all declared.")
|
||||||
|
elif ok:
|
||||||
|
print("\nPASS — every direct Tooling contact maps to a declared shape.")
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
129
scripts/report_coverage.py
Normal file
129
scripts/report_coverage.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Measure the classification coverage published in `pep-stance.yaml`.
|
||||||
|
|
||||||
|
Read-only. No network, no OpenBao, no secret material.
|
||||||
|
|
||||||
|
Why this exists. security-layer-model v0.8 §6.4 obligation 3 requires a dated
|
||||||
|
classification-coverage figure beside each stance, and §13.1 carries a Coverage
|
||||||
|
column whose first entries are ops-warden's — because ops-warden asked for the
|
||||||
|
column after measuring that 0 of 3 signing targets resolve to a zone.
|
||||||
|
|
||||||
|
Having asked for it, we own the figure's accuracy. A hand-counted number in a
|
||||||
|
published register decays silently: the register explicitly does not compute
|
||||||
|
anyone's coverage, and a stale figure beside a marked cell is worse than a blank,
|
||||||
|
which at least reads as "not reported". So the figure is measured from the same
|
||||||
|
two artifacts the runtime uses, and `tests/test_layer_conformance.py` asserts the
|
||||||
|
published block equals what this reports — the same property that makes the stance
|
||||||
|
map worth publishing (`pep-stance.yaml` equals `PolicyConfig.failure_modes` by
|
||||||
|
test), applied one level up.
|
||||||
|
|
||||||
|
Two populations, deliberately not summed. They answer different questions and
|
||||||
|
share no denominator:
|
||||||
|
|
||||||
|
signing targets — actor resources in the flex-auth registry snapshot. This is
|
||||||
|
the population the stance map actually governs: `warden sign` resolves a zone
|
||||||
|
per actor resource, and an unresolved one takes the `unknown` cell.
|
||||||
|
routing lanes — catalog entries with an explicit workload reference. Wider than
|
||||||
|
the stance map's reach, and the figure that shows *why* coverage is low: an
|
||||||
|
unknown lane is almost always another repository's undeclared workload
|
||||||
|
identity, which `ADR-0009` rule 3 forbids closing by inference here.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python scripts/report_coverage.py [--json]
|
||||||
|
|
||||||
|
Exit: 0 always. This reports; it does not gate. Coverage is disclosure, never a
|
||||||
|
transitional licence, and a script that failed on low coverage would be arguing
|
||||||
|
the case v0.8 declined.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(REPO / "src"))
|
||||||
|
|
||||||
|
SNAPSHOT = REPO / "registry" / "flex-auth" / "production_registry_snapshot.json"
|
||||||
|
NOT_APPLICABLE = "not-applicable"
|
||||||
|
|
||||||
|
|
||||||
|
def signing_target_coverage(snapshot_path: Path = SNAPSHOT) -> dict[str, int]:
|
||||||
|
"""Zone resolution across the actor resources `warden sign` can name."""
|
||||||
|
registry = json.loads(snapshot_path.read_text())
|
||||||
|
resolved = unknown = not_applicable = 0
|
||||||
|
for manifest in registry.get("resource_manifests") or []:
|
||||||
|
for resource in manifest.get("resources") or []:
|
||||||
|
if str(resource.get("type")) != "ssh-certificate":
|
||||||
|
continue
|
||||||
|
attributes = resource.get("attributes") or {}
|
||||||
|
admission = str(attributes.get("security_zone_admission") or "unknown")
|
||||||
|
zone = str(attributes.get("security_zone") or "unknown")
|
||||||
|
if admission == NOT_APPLICABLE:
|
||||||
|
not_applicable += 1
|
||||||
|
elif zone == "unknown":
|
||||||
|
unknown += 1
|
||||||
|
else:
|
||||||
|
resolved += 1
|
||||||
|
return {"resolved": resolved, "unknown": unknown, "not_applicable": not_applicable}
|
||||||
|
|
||||||
|
|
||||||
|
def routing_lane_coverage(estate_root: Path | None = None) -> dict[str, int]:
|
||||||
|
"""Workload resolution across catalog lanes.
|
||||||
|
|
||||||
|
Delegates to `report_workload_join.build` rather than re-deriving the join: two
|
||||||
|
implementations of "is this lane resolved" would drift, and the published figure
|
||||||
|
should be the one the join report shows.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"report_workload_join", Path(__file__).resolve().parent / "report_workload_join.py"
|
||||||
|
)
|
||||||
|
join = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(join)
|
||||||
|
|
||||||
|
report = join.build(
|
||||||
|
REPO / "registry" / "routing" / "catalog.yaml",
|
||||||
|
estate_root if estate_root is not None else Path.home(),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"resolved": len(report["resolved"]),
|
||||||
|
"unknown": len(report["unknown"]),
|
||||||
|
"not_applicable": len(report["not_applicable"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def measure(estate_root: Path | None = None) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"signing_targets": signing_target_coverage(),
|
||||||
|
"routing_lanes": routing_lane_coverage(estate_root),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--json", action="store_true", dest="as_json")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
report = measure()
|
||||||
|
if args.as_json:
|
||||||
|
print(json.dumps(report, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
for name, counts in report.items():
|
||||||
|
total = sum(counts.values())
|
||||||
|
scoped = counts["resolved"] + counts["unknown"]
|
||||||
|
print(f"{name.replace('_', ' ')}: {counts['resolved']}/{scoped} resolved "
|
||||||
|
f"({counts['unknown']} unknown, {counts['not_applicable']} not-applicable, "
|
||||||
|
f"{total} total)")
|
||||||
|
print()
|
||||||
|
print("Coverage is disclosure, not a transitional licence (v0.8 §6.4 obligation 3).")
|
||||||
|
print("Publish in pep-stance.yaml; §13.1 does not compute it for you.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -12,13 +12,21 @@ reuses `expand_handoff` to build the command it runs as the caller.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Mapping, Optional
|
||||||
|
|
||||||
from warden.config import ConfigError, load_config
|
from warden.config import ConfigError, load_config
|
||||||
from warden.routing.models import RouteEntry
|
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
|
@dataclass
|
||||||
class ExpandedHandoff:
|
class ExpandedHandoff:
|
||||||
"""Handoff templates with `<domain>` substituted when a domain is supplied.
|
"""Handoff templates with `<domain>` substituted when a domain is supplied.
|
||||||
|
|
@ -34,6 +42,25 @@ class ExpandedHandoff:
|
||||||
exec_capable: bool
|
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]:
|
def _sub_domain(value: Optional[str], domain: Optional[str]) -> Optional[str]:
|
||||||
if value and domain:
|
if value and domain:
|
||||||
return value.replace("<domain>", domain)
|
return value.replace("<domain>", domain)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated, List, Optional
|
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.
|
# T04 — agent identity on a high-risk lane: never stream raw secret data.
|
||||||
# Agents may use sanctioned transports (--out / --exec / --wrap / --fingerprint).
|
# 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 = (
|
raw_value_stream = (
|
||||||
not is_login and not do_exec and not wrap and not out_path and not fingerprint
|
not is_login and not do_exec and not wrap and not out_path and not fingerprint
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,48 @@ def _candidate_row(entry: RouteEntry, score: int) -> dict:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#: Verbs that make a need a request to CHANGE custody rather than to read it
|
||||||
|
#: (WARDEN-WP-0038). Reported independently by key-cape (2026-09-08) and
|
||||||
|
#: railiance-platform (2026-09-09): a need saying "generate a successor secret and
|
||||||
|
#: CAS-write it to two custodians" scored well against the lane that READS that
|
||||||
|
#: path and inherited its `autonomous` verdict, answered with three read
|
||||||
|
#: transports.
|
||||||
|
#:
|
||||||
|
#: Deliberately not including "issue" or "sign". SSH certificate issuance is a
|
||||||
|
#: mutating act ops-warden owns outright, and the ownership test below is what
|
||||||
|
#: separates it from someone else's custody — not the absence of the verb.
|
||||||
|
MUTATE_TOKENS = frozenset({
|
||||||
|
"rotate", "rotating", "rotation", "rerotate",
|
||||||
|
"generate", "regenerate", "mint", "reissue",
|
||||||
|
"write", "rewrite", "put", "patch", "cas",
|
||||||
|
"provision", "reprovision", "create", "install",
|
||||||
|
"revoke", "revoking", "disable", "delete", "remove",
|
||||||
|
"replace", "successor", "reset", "restart", "update",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _need_intent(need: str) -> str:
|
||||||
|
"""``mutate`` when the need asks to change a credential, else ``read``.
|
||||||
|
|
||||||
|
Token match, not substring: "update" must not fire on "updated docs" any
|
||||||
|
more than it already would, but "no-update" style hyphenation is normalised
|
||||||
|
the same way the scorer normalises it.
|
||||||
|
"""
|
||||||
|
tokens = {t.strip(".,;:()") for t in need.lower().replace("-", " ").split()}
|
||||||
|
return "mutate" if tokens & MUTATE_TOKENS else "read"
|
||||||
|
|
||||||
|
|
||||||
|
def _owns_write_authority(entry: RouteEntry) -> bool:
|
||||||
|
"""Whether ops-warden may itself perform a mutating act on this lane.
|
||||||
|
|
||||||
|
True only where ops-warden is the designed owner of the front door — the SSH
|
||||||
|
signing lane, `delegation.mode: permanent`. Everywhere else ops-warden is a
|
||||||
|
pointer or a caller-identity proxy (`ADR-0002`, `ADR-0005`), so a write is
|
||||||
|
another component's custody act and no plan verdict here can authorise it.
|
||||||
|
"""
|
||||||
|
return entry.effective_delegation.mode == "permanent"
|
||||||
|
|
||||||
|
|
||||||
def _score_for(catalog: Catalog, entry: RouteEntry, need: str) -> int:
|
def _score_for(catalog: Catalog, entry: RouteEntry, need: str) -> int:
|
||||||
if entry.id == need.strip():
|
if entry.id == need.strip():
|
||||||
return 100
|
return 100
|
||||||
|
|
@ -294,6 +336,7 @@ def build_plan(
|
||||||
)
|
)
|
||||||
|
|
||||||
entry, score = matches[0]
|
entry, score = matches[0]
|
||||||
|
intent = _need_intent(need)
|
||||||
|
|
||||||
# Draft-only top match without active alternatives → unroutable
|
# Draft-only top match without active alternatives → unroutable
|
||||||
if entry.status == "draft" and not include_draft:
|
if entry.status == "draft" and not include_draft:
|
||||||
|
|
@ -378,6 +421,80 @@ def build_plan(
|
||||||
domain=domain,
|
domain=domain,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- mutating need on a lane ops-warden does not own (WARDEN-WP-0038) ------
|
||||||
|
#
|
||||||
|
# Must run before any branch that can return `autonomous`. A write is a
|
||||||
|
# custody act belonging to the lane owner, and `autonomous` is documented as
|
||||||
|
# the signal to proceed without the founder — so returning it here would let
|
||||||
|
# this front door authorise a mutation on someone else's custody. It would
|
||||||
|
# also answer a write with `--out`/`--exec`/`--wrap`, which are reads.
|
||||||
|
if intent == "mutate" and not _owns_write_authority(entry):
|
||||||
|
rotation = entry.rotation
|
||||||
|
owner = (rotation.owner if rotation else None) or entry.owner_repo
|
||||||
|
reasons = [
|
||||||
|
"need asks to change a credential, not read one",
|
||||||
|
f"write authority on this lane belongs to {owner}, not ops-warden",
|
||||||
|
]
|
||||||
|
if rotation is not None:
|
||||||
|
act = FounderAct(
|
||||||
|
kind="approve",
|
||||||
|
summary=(
|
||||||
|
f"Attended owner act required to change {entry.id} — "
|
||||||
|
f"{owner} holds write authority"
|
||||||
|
),
|
||||||
|
details={
|
||||||
|
"lane_id": entry.id,
|
||||||
|
"write_owner": owner,
|
||||||
|
"rotation_method": rotation.method,
|
||||||
|
"rotation_automatable": rotation.automatable,
|
||||||
|
"wiki_ref": entry.wiki_ref,
|
||||||
|
"guidance_command": f"warden rotate-guide {entry.id}",
|
||||||
|
"desk_hint": f"warden desk --act approve --lane {entry.id}",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not rotation.automatable:
|
||||||
|
reasons.append(
|
||||||
|
"lane records the rotation as not automatable — an executable "
|
||||||
|
"driver must not attempt it"
|
||||||
|
)
|
||||||
|
return AccessPlan(
|
||||||
|
need=need,
|
||||||
|
verdict="founder_required",
|
||||||
|
organization_posture=org,
|
||||||
|
policy_gate=gate,
|
||||||
|
lane_id=entry.id,
|
||||||
|
lane_title=entry.title,
|
||||||
|
match_score=score,
|
||||||
|
# Deliberately empty: every command this lane offers is a READ,
|
||||||
|
# and offering one against a write need is the reported defect.
|
||||||
|
commands=[f"warden rotate-guide {entry.id} # guidance, not execution"],
|
||||||
|
founder_act=act,
|
||||||
|
catalog=freshness,
|
||||||
|
candidates=candidates,
|
||||||
|
reasons=reasons,
|
||||||
|
actor=actor,
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
stub = _ccr_stub(need)
|
||||||
|
stub["owner_hint"] = f"{owner} (write authority for {entry.id})"
|
||||||
|
reasons.append("lane records no rotation route — the act has no admitted transport")
|
||||||
|
return AccessPlan(
|
||||||
|
need=need,
|
||||||
|
verdict="unroutable",
|
||||||
|
organization_posture=org,
|
||||||
|
policy_gate=gate,
|
||||||
|
lane_id=entry.id,
|
||||||
|
lane_title=entry.title,
|
||||||
|
match_score=score,
|
||||||
|
commands=[],
|
||||||
|
ccr_stub=stub,
|
||||||
|
catalog=freshness,
|
||||||
|
candidates=candidates,
|
||||||
|
reasons=reasons,
|
||||||
|
actor=actor,
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
|
||||||
if _lane_is_autonomous(entry):
|
if _lane_is_autonomous(entry):
|
||||||
return AccessPlan(
|
return AccessPlan(
|
||||||
need=need,
|
need=need,
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,12 @@ def check_sign_policy(cfg: PolicyConfig, spec: CertSpec) -> str | None:
|
||||||
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
|
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
|
if e.response.status_code in {401, 403}:
|
||||||
|
spec.policy_outcome = "deny"
|
||||||
|
raise CAError(
|
||||||
|
f"flex-auth refused sign policy check (HTTP {e.response.status_code}); "
|
||||||
|
"caller authentication or system binding was rejected"
|
||||||
|
) from e
|
||||||
_evaluator_failure(
|
_evaluator_failure(
|
||||||
f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) "
|
f"flex-auth rejected sign policy check (HTTP {e.response.status_code}) "
|
||||||
f"for security zone {zone!r}",
|
f"for security zone {zone!r}",
|
||||||
|
|
@ -213,6 +219,11 @@ def check_fetch_policy(
|
||||||
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
|
response = httpx.post(url, json=request, headers=headers, timeout=10.0)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
|
if e.response.status_code in {401, 403}:
|
||||||
|
raise CAError(
|
||||||
|
f"flex-auth refused fetch policy check (HTTP {e.response.status_code}); "
|
||||||
|
"caller authentication or system binding was rejected"
|
||||||
|
) from e
|
||||||
_evaluator_failure(
|
_evaluator_failure(
|
||||||
f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})",
|
f"flex-auth rejected fetch policy check (HTTP {e.response.status_code})",
|
||||||
fail_closed=fail_closed,
|
fail_closed=fail_closed,
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ intact. Three guardrails are enforced here in code:
|
||||||
the tool with inherited stdout/stderr so the value never enters warden's memory;
|
the tool with inherited stdout/stderr so the value never enters warden's memory;
|
||||||
sanctioned exec/file transports hold it only for their bounded handoff. The
|
sanctioned exec/file transports hold it only for their bounded handoff. The
|
||||||
high-risk attended-login lane is stricter: it captures every client byte inside
|
high-risk attended-login lane is stricter: it captures every client byte inside
|
||||||
an isolated helper session, permits no output, self-revokes, and cleans up. Audit
|
an isolated helper session, never returns that output, requires successful
|
||||||
records are metadata only.
|
persistence to a private token helper, self-revokes, and cleans up. Audit records
|
||||||
|
are metadata only.
|
||||||
* **G3 — policy gate before fetch.** The CLI runs ``check_fetch_policy`` before
|
* **G3 — policy gate before fetch.** The CLI runs ``check_fetch_policy`` before
|
||||||
calling anything here; this module refuses to run an unresolved command template.
|
calling anything here; this module refuses to run an unresolved command template.
|
||||||
|
|
||||||
|
|
@ -380,9 +381,11 @@ def proxy_attended_login_exec(
|
||||||
"""Run an attended login and one silent child inside a private helper home.
|
"""Run an attended login and one silent child inside a private helper home.
|
||||||
|
|
||||||
The default home is proven writable before the OIDC client starts. Login,
|
The default home is proven writable before the OIDC client starts. Login,
|
||||||
child, and revocation output are captured and discarded. Any non-empty output,
|
child, and revocation output are captured and discarded. A successful login
|
||||||
persistence defect, or non-zero result fails closed; any possibly issued token
|
may return client output only after the private helper has been populated;
|
||||||
is revoked before the isolated helper directory is removed.
|
persistence defects and non-zero results fail closed. The reviewed child must
|
||||||
|
remain silent. Any possibly issued token is revoked before the isolated helper
|
||||||
|
directory is removed.
|
||||||
"""
|
"""
|
||||||
if not child_argv:
|
if not child_argv:
|
||||||
raise ProxyError(
|
raise ProxyError(
|
||||||
|
|
@ -400,6 +403,10 @@ def proxy_attended_login_exec(
|
||||||
root, session, root_created = _prepare_attended_login_home()
|
root, session, root_created = _prepare_attended_login_home()
|
||||||
helper = session / _TOKEN_HELPER_NAME
|
helper = session / _TOKEN_HELPER_NAME
|
||||||
env = _caller_env()
|
env = _caller_env()
|
||||||
|
if not env.get("WARDEN_CONFIG"):
|
||||||
|
caller_config = Path.home() / ".config" / "warden" / "warden.yaml"
|
||||||
|
if caller_config.is_file():
|
||||||
|
env["WARDEN_CONFIG"] = str(caller_config)
|
||||||
env["HOME"] = str(session)
|
env["HOME"] = str(session)
|
||||||
env.pop("BAO_TOKEN", None)
|
env.pop("BAO_TOKEN", None)
|
||||||
env.pop("VAULT_TOKEN", None)
|
env.pop("VAULT_TOKEN", None)
|
||||||
|
|
@ -428,7 +435,7 @@ def proxy_attended_login_exec(
|
||||||
except (OSError, ProxyError):
|
except (OSError, ProxyError):
|
||||||
helper_valid = False
|
helper_valid = False
|
||||||
|
|
||||||
if login.returncode != 0 or login_output.strip() or not helper_valid:
|
if login.returncode != 0 or not helper_valid:
|
||||||
revoked = _revoke_contained(
|
revoked = _revoke_contained(
|
||||||
resolved.argv[0], env=env, possible_output=login_output
|
resolved.argv[0], env=env, possible_output=login_output
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||||
|
|
||||||
from typer.testing import CliRunner
|
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.cli import app
|
||||||
from warden.routing.models import RouteEntry
|
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()
|
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 -------------------------------------------------------------------
|
# --- CLI -------------------------------------------------------------------
|
||||||
|
|
||||||
def test_access_advisory_output(monkeypatch):
|
def test_access_advisory_output(monkeypatch):
|
||||||
|
|
|
||||||
|
|
@ -81,3 +81,86 @@ def test_compiler_joins_explicit_workload_reference_to_resolved_zone(tmp_path):
|
||||||
assert attrs["security_zone"] == "z2-continuity"
|
assert attrs["security_zone"] == "z2-continuity"
|
||||||
assert attrs["security_zone_admission"] == "satisfied"
|
assert attrs["security_zone_admission"] == "satisfied"
|
||||||
assert attrs["security_zone_revision"] == "sha256:zone-revision"
|
assert attrs["security_zone_revision"] == "sha256:zone-revision"
|
||||||
|
|
||||||
|
|
||||||
|
# --- manifest ceiling coverage (FLEX-DEC-2026-012) ----------------------------
|
||||||
|
#
|
||||||
|
# flex-auth fixed enrichment so registry facts beat caller-supplied ones, then
|
||||||
|
# asked every consumer to confirm the ceiling keys are actually declared: the
|
||||||
|
# fix wins only where the registry HAS a value. These assert the two ways a
|
||||||
|
# ceiling gets handed back to the caller.
|
||||||
|
|
||||||
|
import importlib.util # noqa: E402
|
||||||
|
|
||||||
|
import pytest # noqa: E402
|
||||||
|
|
||||||
|
_REPO = ROOT
|
||||||
|
_spec = importlib.util.spec_from_file_location(
|
||||||
|
"check_flex_auth_manifest_coverage",
|
||||||
|
_REPO / "scripts" / "check_flex_auth_manifest_coverage.py",
|
||||||
|
)
|
||||||
|
_coverage = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_coverage)
|
||||||
|
REQUIRED_CEILING_KEYS = _coverage.REQUIRED_CEILING_KEYS
|
||||||
|
audit = _coverage.audit
|
||||||
|
|
||||||
|
_build_spec = importlib.util.spec_from_file_location("build_flex_auth_registry", SCRIPT)
|
||||||
|
_builder = importlib.util.module_from_spec(_build_spec)
|
||||||
|
_build_spec.loader.exec_module(_builder)
|
||||||
|
build_registry = _builder.build_registry
|
||||||
|
|
||||||
|
|
||||||
|
def _repo_inventory_and_registry():
|
||||||
|
inventory = yaml.safe_load((_REPO / "examples" / "inventory.seed.yaml").read_text())
|
||||||
|
registry = json.loads(
|
||||||
|
(_REPO / "registry" / "flex-auth" / "production_registry_snapshot.json").read_text()
|
||||||
|
)
|
||||||
|
return inventory, registry
|
||||||
|
|
||||||
|
|
||||||
|
def test_shipped_snapshot_declares_every_ceiling_key():
|
||||||
|
"""The confirmation flex-auth asked for, as a test rather than a reading."""
|
||||||
|
report = audit(*_repo_inventory_and_registry())
|
||||||
|
assert report["unregistered_actors"] == []
|
||||||
|
assert report["resources_missing_keys"] == []
|
||||||
|
assert report["covered"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_actor_with_no_manifest_resource_is_a_coverage_gap():
|
||||||
|
"""`warden sign` names ssh-cert:actor/<name> whether or not the snapshot was rebuilt."""
|
||||||
|
inventory, registry = _repo_inventory_and_registry()
|
||||||
|
inventory["actors"]["adm-added-after-the-last-build"] = {
|
||||||
|
"type": "adm", "principals": ["adm-full"], "ttl_hours": 48,
|
||||||
|
}
|
||||||
|
report = audit(inventory, registry)
|
||||||
|
assert report["unregistered_actors"] == ["adm-added-after-the-last-build"]
|
||||||
|
assert report["covered"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("key", REQUIRED_CEILING_KEYS)
|
||||||
|
def test_dropping_any_single_ceiling_key_is_a_coverage_gap(key):
|
||||||
|
inventory, registry = _repo_inventory_and_registry()
|
||||||
|
resource = registry["resource_manifests"][0]["resources"][0]
|
||||||
|
resource["attributes"].pop(key)
|
||||||
|
report = audit(inventory, registry)
|
||||||
|
assert report["resources_missing_keys"] == [
|
||||||
|
{"resource": resource["id"], "missing": [key]}
|
||||||
|
]
|
||||||
|
assert report["covered"] is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("key", REQUIRED_CEILING_KEYS)
|
||||||
|
def test_a_null_ceiling_is_not_a_declaration(key):
|
||||||
|
"""flex-auth overlays a registry value only where one exists."""
|
||||||
|
inventory, registry = _repo_inventory_and_registry()
|
||||||
|
resource = registry["resource_manifests"][0]["resources"][0]
|
||||||
|
resource["attributes"][key] = None
|
||||||
|
report = audit(inventory, registry)
|
||||||
|
assert report["covered"] is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_builder_output_is_covered_by_construction(tmp_path):
|
||||||
|
"""Every resource the builder emits carries the full ceiling set."""
|
||||||
|
inventory = yaml.safe_load((_REPO / "examples" / "inventory.seed.yaml").read_text())
|
||||||
|
report = audit(inventory, build_registry(inventory))
|
||||||
|
assert report["covered"] is True
|
||||||
|
|
|
||||||
201
tests/test_layer_conformance.py
Normal file
201
tests/test_layer_conformance.py
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
"""Layer-model conformance (security-layer-model_v0.4 §5, §11).
|
||||||
|
|
||||||
|
Two things are checked here. §11 makes one of them mechanical: every direct
|
||||||
|
Tooling client maps to a declared shape. §5.2 asks for the other: the conduit's
|
||||||
|
supplied-authority property covered by a test.
|
||||||
|
|
||||||
|
Deliberately absent: any assertion on a §5.3 review date. A date-triggered
|
||||||
|
failure breaks the build on a calendar day with no code change, punishing
|
||||||
|
whoever commits next rather than whoever owns the gap — the same reasoning
|
||||||
|
recorded in WARDEN-WP-0033-T05 for blocker staleness.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _decl() -> dict:
|
||||||
|
return yaml.safe_load((ROOT / "layer.yaml").read_text())
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeclaration:
|
||||||
|
def test_declares_staff_layer_in_its_own_voice(self):
|
||||||
|
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"
|
||||||
|
|
||||||
|
def test_every_tooling_contact_maps_to_a_declared_shape(self):
|
||||||
|
"""§11 mechanical check — the guard against a new undeclared client."""
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, str(ROOT / "scripts" / "check_layer_conformance.py")],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, (
|
||||||
|
f"undeclared Tooling contact — a finding under §11, not a tracked gap:\n"
|
||||||
|
f"{result.stdout}{result.stderr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_declared_gaps_carry_all_four_fields(self):
|
||||||
|
"""§5.3 is machine-readable or it is prose wearing a schema."""
|
||||||
|
for c in _decl()["tooling_contacts"]:
|
||||||
|
if c["shape"] == "5.3":
|
||||||
|
for field in ("capability", "intended_owner", "blocked_on", "review"):
|
||||||
|
assert c.get(field), f"{c['id']} missing {field}"
|
||||||
|
|
||||||
|
def test_gaps_are_not_counted_as_conformance(self):
|
||||||
|
"""§11: a declared gap is tracked non-conformance. Keep that visible."""
|
||||||
|
text = (ROOT / "layer.yaml").read_text()
|
||||||
|
assert "TRACKED NON-CONFORMANCE" in text.upper()
|
||||||
|
|
||||||
|
|
||||||
|
class TestConduitSuppliesNoAuthority:
|
||||||
|
"""§5.2: 'MUST NOT present its own credential, MUST NOT widen what the
|
||||||
|
caller could already do.' The standard says this SHOULD be covered by a
|
||||||
|
test; this is that test."""
|
||||||
|
|
||||||
|
def test_conduit_supplies_no_authority_of_its_own(self, monkeypatch):
|
||||||
|
from warden import proxy
|
||||||
|
|
||||||
|
monkeypatch.setenv("VAULT_TOKEN", "caller-own-token")
|
||||||
|
monkeypatch.setenv("HOME", "/home/nobody")
|
||||||
|
before = dict(os.environ)
|
||||||
|
|
||||||
|
env = proxy._caller_env()
|
||||||
|
|
||||||
|
# The child environment IS the caller's environment — nothing added,
|
||||||
|
# nothing removed, no ops-warden credential injected.
|
||||||
|
assert env == before, (
|
||||||
|
"conduit altered the caller's environment; §5.2 requires it to "
|
||||||
|
"supply no authority of its own"
|
||||||
|
)
|
||||||
|
assert env["VAULT_TOKEN"] == "caller-own-token"
|
||||||
|
|
||||||
|
def test_conduit_declares_supplied_authority_none(self):
|
||||||
|
conduits = [c for c in _decl()["tooling_contacts"] if c["shape"] == "5.2"]
|
||||||
|
assert conduits, "no §5.2 conduit declared — proxy.py is one"
|
||||||
|
for c in conduits:
|
||||||
|
assert c["supplied_authority"] == "none"
|
||||||
|
|
||||||
|
def test_proxy_holds_no_credential_constant(self):
|
||||||
|
"""A conduit that presents its own token is not a conduit (§5.2)."""
|
||||||
|
src = (ROOT / "src" / "warden" / "proxy.py").read_text()
|
||||||
|
# It may name token ENV VARS to detect caller auth; it must not carry a
|
||||||
|
# token value or mint one.
|
||||||
|
for forbidden in ("X-Vault-Token", "auth/approle/login", "token create"):
|
||||||
|
assert forbidden not in src, (
|
||||||
|
f"proxy.py references {forbidden!r} — that is presenting or "
|
||||||
|
f"minting authority, not conducting the caller's"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPepStanceMap:
|
||||||
|
"""§6.4: every PEP-shaped consumer MUST publish its unreachable-engine
|
||||||
|
stance map, total and per zone, 'published rather than held in code'.
|
||||||
|
ADR-0009 is named as the reference shape, so it should actually hold."""
|
||||||
|
|
||||||
|
def _stance(self) -> dict:
|
||||||
|
return yaml.safe_load((ROOT / "pep-stance.yaml").read_text())
|
||||||
|
|
||||||
|
def test_published_map_equals_shipped_behaviour(self):
|
||||||
|
"""The whole point. A published map that may drift from the code is
|
||||||
|
worse than none, because it invites reliance it cannot support."""
|
||||||
|
from warden.config import PolicyConfig
|
||||||
|
|
||||||
|
assert self._stance()["stance"] == PolicyConfig().failure_modes
|
||||||
|
|
||||||
|
def test_stance_is_total_over_the_zone_model(self):
|
||||||
|
"""§6.4 obligation 3: total, no implicit default."""
|
||||||
|
stance = self._stance()["stance"]
|
||||||
|
required = {
|
||||||
|
"z0-experimental", "z1-operational", "z2-protected",
|
||||||
|
"z2-continuity", "z3-critical", "unknown", "not-applicable",
|
||||||
|
}
|
||||||
|
assert required <= set(stance), f"stance not total; missing {required - set(stance)}"
|
||||||
|
assert set(stance.values()) <= {"fail_open", "fail_closed"}
|
||||||
|
|
||||||
|
def test_critical_zone_fails_closed(self):
|
||||||
|
"""ADR-0009's one non-negotiable row."""
|
||||||
|
assert self._stance()["stance"]["z3-critical"] == "fail_closed"
|
||||||
|
|
||||||
|
def test_verdict_is_never_cached(self):
|
||||||
|
"""§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"]
|
||||||
|
|
||||||
|
|
||||||
|
# --- classification coverage (v0.8 §6.4 obligation 3) -------------------------
|
||||||
|
|
||||||
|
def test_published_coverage_equals_measured_coverage():
|
||||||
|
"""The published figure must equal what the repo actually measures.
|
||||||
|
|
||||||
|
ops-warden asked gate-house for §13.1's Coverage column and its figures are
|
||||||
|
that column's first entries, so their accuracy is ours to hold. The register
|
||||||
|
explicitly does not compute anyone's coverage, and a stale number beside a
|
||||||
|
marked cell is worse than a blank -- a blank at least reads as "not reported".
|
||||||
|
|
||||||
|
This is the same property that makes the stance map worth publishing (the map
|
||||||
|
equals PolicyConfig.failure_modes by test), applied one level up.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
repo = Path(__file__).resolve().parents[1]
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"report_coverage", repo / "scripts" / "report_coverage.py"
|
||||||
|
)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
|
published = yaml.safe_load((repo / "pep-stance.yaml").read_text())[
|
||||||
|
"classification_coverage"
|
||||||
|
]
|
||||||
|
measured = module.measure()
|
||||||
|
|
||||||
|
for population in ("signing_targets", "routing_lanes"):
|
||||||
|
assert published[population] == measured[population], population
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_unknown_cell_is_marked_as_a_declared_gap():
|
||||||
|
"""A non-conformant cell must say so where it is declared, not only in a review.
|
||||||
|
|
||||||
|
§11's marking obligation, which ops-warden argued for in the v0.6 round and
|
||||||
|
then acquired a marked cell under. If the cell is ever flipped to fail_closed
|
||||||
|
this test fails, which is the correct time to remove the marking.
|
||||||
|
"""
|
||||||
|
repo = Path(__file__).resolve().parents[1]
|
||||||
|
text = (repo / "pep-stance.yaml").read_text()
|
||||||
|
stance = yaml.safe_load(text)["stance"]
|
||||||
|
|
||||||
|
if stance["unknown"] == "fail_open":
|
||||||
|
assert "DECLARED GAP" in text
|
||||||
|
assert "WARDEN-WP-0040" in text
|
||||||
|
else:
|
||||||
|
assert stance["unknown"] == "fail_closed"
|
||||||
|
|
@ -119,3 +119,87 @@ def test_cli_plan_json():
|
||||||
assert payload["verdict"] == "autonomous"
|
assert payload["verdict"] == "autonomous"
|
||||||
assert payload["organization_posture"] == "build"
|
assert payload["organization_posture"] == "build"
|
||||||
assert payload["lane_id"] == "agent-harness-forgejo-deploy"
|
assert payload["lane_id"] == "agent-harness-forgejo-deploy"
|
||||||
|
|
||||||
|
|
||||||
|
# --- mutate intent (WARDEN-WP-0038) -------------------------------------------
|
||||||
|
#
|
||||||
|
# `warden plan` scored a need by keyword overlap with no notion of what the
|
||||||
|
# caller wanted to DO, so "generate a successor secret and CAS-write it to two
|
||||||
|
# custodians" matched the lane that READS that path and inherited its
|
||||||
|
# `autonomous` verdict, answered with --out/--exec/--wrap. Reported independently
|
||||||
|
# by key-cape (2026-09-08) and railiance-platform (2026-09-09), who added: "this
|
||||||
|
# is the same verdict your warden plan should have returned; until that is fixed,
|
||||||
|
# do not let a plan result stand in for this answer."
|
||||||
|
|
||||||
|
QONTO_ROTATION_NEED = (
|
||||||
|
"generate a successor client secret for rapp-qonto keycape client and "
|
||||||
|
"CAS-write it to OpenBao platform/workloads/rapp-qonto/keycape-client "
|
||||||
|
"and sso/keycape-rapp-qonto-client"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reported_qonto_write_no_longer_returns_autonomous():
|
||||||
|
"""The exact need from the report. Regression, in the manner of WP-0033-T06."""
|
||||||
|
plan = build_plan(QONTO_ROTATION_NEED)
|
||||||
|
assert plan.verdict == "founder_required"
|
||||||
|
assert plan.lane_id == "rapp-qonto-keycape-client"
|
||||||
|
assert plan.founder_act is not None
|
||||||
|
assert plan.founder_act.kind == "approve"
|
||||||
|
assert plan.founder_act.details["write_owner"] == "key-cape"
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_write_need_is_never_answered_with_a_read_transport():
|
||||||
|
"""The half that made the wrong verdict actionable rather than merely wrong."""
|
||||||
|
plan = build_plan(QONTO_ROTATION_NEED)
|
||||||
|
joined = " ".join(plan.commands)
|
||||||
|
for read_transport in ("--out", "--wrap", "--fetch", "bao kv get", "--exec"):
|
||||||
|
assert read_transport not in joined, read_transport
|
||||||
|
|
||||||
|
|
||||||
|
def test_mutating_need_names_why_it_escalated():
|
||||||
|
"""WP-0029's property: a verdict carries the reasons that produced it."""
|
||||||
|
plan = build_plan(QONTO_ROTATION_NEED)
|
||||||
|
assert any("change a credential" in r for r in plan.reasons)
|
||||||
|
assert any("write authority" in r for r in plan.reasons)
|
||||||
|
# The lane records automatable: false; a driver must be told so.
|
||||||
|
assert any("not automatable" in r for r in plan.reasons)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ops_warden_still_proceeds_autonomously_on_the_lane_it_owns():
|
||||||
|
"""Signing IS a mutating act. The test is ownership, not the absence of a verb.
|
||||||
|
|
||||||
|
`delegation.mode: permanent` is the whole distinction — if this ever fails,
|
||||||
|
the guard has started refusing ops-warden's own front door.
|
||||||
|
"""
|
||||||
|
for need in (
|
||||||
|
"sign an ssh certificate for agt-state-hub-bridge",
|
||||||
|
"issue a short-lived ssh cert for adm",
|
||||||
|
):
|
||||||
|
plan = build_plan(need)
|
||||||
|
assert plan.verdict == "autonomous", need
|
||||||
|
assert plan.lane_id == "ssh-cert-host-access"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reading_the_same_lane_is_unaffected():
|
||||||
|
plan = build_plan("I need the npm token to publish whynot-design")
|
||||||
|
assert plan.verdict == "autonomous"
|
||||||
|
assert plan.lane_id == "whynot-design-npm-publish"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"verb", ["rotate", "revoke", "regenerate", "reset", "replace"]
|
||||||
|
)
|
||||||
|
def test_mutating_verbs_escalate_on_a_lane_ops_warden_does_not_own(verb):
|
||||||
|
plan = build_plan(f"{verb} the forgejo admin api token")
|
||||||
|
assert plan.verdict == "founder_required"
|
||||||
|
assert plan.founder_act.details["write_owner"] == "railiance-platform"
|
||||||
|
|
||||||
|
|
||||||
|
def test_intent_classifier_matches_tokens_not_substrings():
|
||||||
|
from warden.plan import _need_intent
|
||||||
|
|
||||||
|
assert _need_intent("rotate the forgejo token") == "mutate"
|
||||||
|
assert _need_intent("CAS-write to two custodians") == "mutate"
|
||||||
|
# "created" and "updated" are not the verbs; a need describing state is a read.
|
||||||
|
assert _need_intent("read the token created for whynot-design") == "read"
|
||||||
|
assert _need_intent("which subsystem owns the npm token") == "read"
|
||||||
|
|
|
||||||
|
|
@ -307,3 +307,47 @@ def test_advisory_decision_is_recorded_and_does_not_block(tmp_path):
|
||||||
assert check_sign_policy(cfg, spec) == "decision:advisory"
|
assert check_sign_policy(cfg, spec) == "decision:advisory"
|
||||||
assert spec.policy_zone == "unknown"
|
assert spec.policy_zone == "unknown"
|
||||||
assert spec.policy_outcome == "audit_only"
|
assert spec.policy_outcome == "audit_only"
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_request_asserts_no_resource_attributes(tmp_path, monkeypatch):
|
||||||
|
"""The property FLEX-DEC-2026-012 turned on, asserted rather than assumed.
|
||||||
|
|
||||||
|
flex-auth's enrichment used to overlay registry facts additive-if-absent, so
|
||||||
|
a caller-supplied `resource.attributes` value won and the registry ceiling
|
||||||
|
never applied. Their exploitability assessment rested on ops-warden sending
|
||||||
|
no `resource.attributes` at all -- true when they read it, and nothing here
|
||||||
|
held it true. secrets-engine sends them on every request, so this is a
|
||||||
|
property of this code rather than of the protocol.
|
||||||
|
|
||||||
|
Ceilings must arrive from the registry. If a future change ever needs to send
|
||||||
|
an attribute, this test is the place that argument gets made.
|
||||||
|
"""
|
||||||
|
from warden import policy as policy_mod
|
||||||
|
|
||||||
|
pubkey = tmp_path / "id.pub"
|
||||||
|
pubkey.write_text("ssh-ed25519 AAAA test\n")
|
||||||
|
cfg = PolicyConfig(flex_auth_url="http://flex-auth.test")
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
class _Response:
|
||||||
|
status_code = 200
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return {"effect": "allow", "id": "decision:49350f1064f674d7"}
|
||||||
|
|
||||||
|
def fake_post(url, json=None, headers=None, timeout=None):
|
||||||
|
seen["body"] = json
|
||||||
|
return _Response()
|
||||||
|
|
||||||
|
monkeypatch.setattr(policy_mod.httpx, "post", fake_post)
|
||||||
|
policy_mod.check_sign_policy(cfg, _spec(pubkey))
|
||||||
|
|
||||||
|
assert "attributes" not in seen["body"]["resource"]
|
||||||
|
# The requested TTL is a policy input (ttl_out_of_bounds is denied against
|
||||||
|
# the registry ceiling), so it must travel as context, never as a resource
|
||||||
|
# attribute that would be compared against itself.
|
||||||
|
assert seen["body"]["context"]["ttl_hours"] == 24
|
||||||
|
assert set(seen["body"]["resource"]) == {"id", "type", "system", "tenant"}
|
||||||
|
|
|
||||||
48
tests/test_policy_http_refusal.py
Normal file
48
tests/test_policy_http_refusal.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
"""An explicit caller refusal cannot authorize a CA or credential side effect."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from warden.ca import CAError
|
||||||
|
from warden.config import PolicyConfig
|
||||||
|
from warden.models import ActorType, CertSpec
|
||||||
|
from warden.policy import check_fetch_policy, check_sign_policy
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("status", [401, 403])
|
||||||
|
@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"])
|
||||||
|
@pytest.mark.parametrize("operation", ["sign", "fetch"])
|
||||||
|
def test_explicit_refusal_blocks_under_every_outage_mode(tmp_path, status, failure_mode, operation):
|
||||||
|
cfg = PolicyConfig(flex_auth_url="http://pdp.test")
|
||||||
|
cfg.failure_modes["unknown"] = failure_mode
|
||||||
|
public_key = tmp_path / "id.pub"
|
||||||
|
public_key.write_text("ssh-ed25519 AAAA test\n")
|
||||||
|
spec = CertSpec(actor_name="agt-example", actor_type=ActorType.AGT,
|
||||||
|
pubkey_path=public_key, ttl_hours=1, principals=["agt"])
|
||||||
|
response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check"),
|
||||||
|
text="untrusted response body must not be exposed")
|
||||||
|
with patch("warden.policy.httpx.post", return_value=response):
|
||||||
|
with pytest.raises(CAError, match=f"HTTP {status}") as error:
|
||||||
|
if operation == "sign":
|
||||||
|
check_sign_policy(cfg, spec)
|
||||||
|
else:
|
||||||
|
check_fetch_policy(cfg, need_id="forgejo-admin-api-token",
|
||||||
|
owner_repo="railiance-platform", domain=None)
|
||||||
|
assert "untrusted response" not in str(error.value)
|
||||||
|
if operation == "sign":
|
||||||
|
assert spec.policy_outcome == "deny"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("status", [500, 503])
|
||||||
|
@pytest.mark.parametrize("failure_mode", ["fail_open", "fail_closed"])
|
||||||
|
def test_actual_evaluator_failure_retains_declared_outage_mode(status, failure_mode):
|
||||||
|
cfg = PolicyConfig(flex_auth_url="http://pdp.test")
|
||||||
|
cfg.failure_modes["unknown"] = failure_mode
|
||||||
|
response = httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check"))
|
||||||
|
with patch("warden.policy.httpx.post", return_value=response):
|
||||||
|
if failure_mode == "fail_closed":
|
||||||
|
with pytest.raises(CAError, match=f"HTTP {status}"):
|
||||||
|
check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None)
|
||||||
|
else:
|
||||||
|
assert check_fetch_policy(cfg, need_id="example", owner_repo="example", domain=None) is None
|
||||||
|
|
@ -234,6 +234,28 @@ def test_cli_proxy_requires_caller_auth(monkeypatch, tmp_path):
|
||||||
assert r.exit_code == 3
|
assert r.exit_code == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("status", [401, 403])
|
||||||
|
def test_cli_explicit_policy_refusal_never_fetches_or_starts_child(monkeypatch, tmp_path, status):
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
_proxy_env(monkeypatch, tmp_path)
|
||||||
|
cfg = tmp_path / "warden.yaml"
|
||||||
|
cfg.write_text(cfg.read_text() + "policy:\n flex_auth_url: http://pdp.test\n")
|
||||||
|
monkeypatch.setenv("VAULT_TOKEN", "caller-test-value")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"warden.policy.httpx.post",
|
||||||
|
lambda *a, **k: httpx.Response(status, request=httpx.Request("POST", "http://pdp.test/v1/check")),
|
||||||
|
)
|
||||||
|
calls = []
|
||||||
|
for name in ("proxy_exec", "proxy_fetch", "proxy_fetch_to_file", "proxy_fetch_wrapped"):
|
||||||
|
monkeypatch.setattr("warden.proxy." + name, lambda *a, **k: calls.append(True))
|
||||||
|
result = runner.invoke(app, ["access", "forgejo-admin-api-token", "--exec", "--field", "API_TOKEN", "--", "true"])
|
||||||
|
assert result.exit_code == 4
|
||||||
|
assert f"HTTP {status}" in result.output
|
||||||
|
assert "fail_open applied" not in result.output
|
||||||
|
assert calls == []
|
||||||
|
|
||||||
|
|
||||||
def test_cli_proxy_rejects_retired_no_policy_bypass(monkeypatch, tmp_path):
|
def test_cli_proxy_rejects_retired_no_policy_bypass(monkeypatch, tmp_path):
|
||||||
_proxy_env(monkeypatch, tmp_path)
|
_proxy_env(monkeypatch, tmp_path)
|
||||||
monkeypatch.setenv("VAULT_TOKEN", "caller")
|
monkeypatch.setenv("VAULT_TOKEN", "caller")
|
||||||
|
|
@ -391,6 +413,70 @@ def test_attended_login_unexpected_output_is_contained_revoked_and_cleaned(
|
||||||
assert not (tmp_path / ".warden-attended-login").exists()
|
assert not (tmp_path / ".warden-attended-login").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_attended_login_contained_success_output_never_escapes(monkeypatch, tmp_path, capsys):
|
||||||
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
||||||
|
sentinel = "hvs.NONPRODUCTION_CONTAINED_LOGIN"
|
||||||
|
child_ran = False
|
||||||
|
|
||||||
|
def fake_run(argv, **kw):
|
||||||
|
nonlocal child_ran
|
||||||
|
helper = Path(kw["env"]["HOME"]) / ".vault-token"
|
||||||
|
if argv[:2] == ["bao", "login"]:
|
||||||
|
helper.write_text(sentinel)
|
||||||
|
helper.chmod(0o600)
|
||||||
|
return subprocess.CompletedProcess(
|
||||||
|
argv,
|
||||||
|
0,
|
||||||
|
stdout=json.dumps({"auth": {"client_token": sentinel}}).encode(),
|
||||||
|
stderr=b"",
|
||||||
|
)
|
||||||
|
if argv == ["reviewed-child"]:
|
||||||
|
child_ran = True
|
||||||
|
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
|
||||||
|
if argv[:3] == ["bao", "token", "revoke"]:
|
||||||
|
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
|
||||||
|
raise AssertionError(argv)
|
||||||
|
|
||||||
|
monkeypatch.setattr("warden.proxy.subprocess.run", fake_run)
|
||||||
|
assert proxy_attended_login_exec(
|
||||||
|
ResolvedFetch(argv=["bao", "login", "-no-print"]),
|
||||||
|
child_argv=["reviewed-child"],
|
||||||
|
) == 0
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert child_ran is True
|
||||||
|
assert sentinel not in captured.out
|
||||||
|
assert sentinel not in captured.err
|
||||||
|
assert not (tmp_path / ".warden-attended-login").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_attended_login_preserves_caller_warden_config_for_reviewed_child(
|
||||||
|
monkeypatch, tmp_path
|
||||||
|
):
|
||||||
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
||||||
|
monkeypatch.delenv("WARDEN_CONFIG", raising=False)
|
||||||
|
caller_config = tmp_path / ".config" / "warden" / "warden.yaml"
|
||||||
|
caller_config.parent.mkdir(parents=True)
|
||||||
|
caller_config.write_text("backend: local\n")
|
||||||
|
seen_config = None
|
||||||
|
|
||||||
|
def fake_run(argv, **kw):
|
||||||
|
nonlocal seen_config
|
||||||
|
helper = Path(kw["env"]["HOME"]) / ".vault-token"
|
||||||
|
if argv[:2] == ["bao", "login"]:
|
||||||
|
helper.write_text("non-production-test-double")
|
||||||
|
helper.chmod(0o600)
|
||||||
|
if argv == ["reviewed-child"]:
|
||||||
|
seen_config = kw["env"].get("WARDEN_CONFIG")
|
||||||
|
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
|
||||||
|
|
||||||
|
monkeypatch.setattr("warden.proxy.subprocess.run", fake_run)
|
||||||
|
assert proxy_attended_login_exec(
|
||||||
|
ResolvedFetch(argv=["bao", "login", "-no-print"]),
|
||||||
|
child_argv=["reviewed-child"],
|
||||||
|
) == 0
|
||||||
|
assert seen_config == str(caller_config)
|
||||||
|
|
||||||
|
|
||||||
def test_real_catalog_login_entry_is_login_lane():
|
def test_real_catalog_login_entry_is_login_lane():
|
||||||
from warden.routing import load_catalog
|
from warden.routing import load_catalog
|
||||||
e = load_catalog(_repo_catalog()).get("key-cape-oidc-login")
|
e = load_catalog(_repo_catalog()).get("key-cape-oidc-login")
|
||||||
|
|
@ -526,6 +612,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()
|
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):
|
def test_access_agent_high_risk_fingerprint_allowed(tmp_path, monkeypatch):
|
||||||
"""Agents may use --fingerprint on high-risk lanes (no raw value)."""
|
"""Agents may use --fingerprint on high-risk lanes (no raw value)."""
|
||||||
_proxy_env(monkeypatch, tmp_path)
|
_proxy_env(monkeypatch, tmp_path)
|
||||||
|
|
|
||||||
|
|
@ -143,21 +143,69 @@ def test_route_find_vault_token_ops_warden_prefers_broker_lane():
|
||||||
assert matches[0].id == "ops-warden-warden-sign-token"
|
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():
|
def test_whynot_design_npm_lane_is_concrete_and_resolvable():
|
||||||
"""The provisioned npm publish lane has no placeholders and reports resolvable."""
|
"""The provisioned Forgejo npm lane is exact, concrete, and resolvable."""
|
||||||
catalog = load_catalog(_repo_catalog())
|
catalog = load_catalog(_repo_catalog())
|
||||||
e = catalog.get("whynot-design-npm-publish")
|
e = catalog.get("whynot-design-npm-publish")
|
||||||
assert e is not None and e.is_active and e.exec_capable
|
assert e is not None and e.is_active and e.exec_capable
|
||||||
assert e.resolvable is True
|
assert e.resolvable is True
|
||||||
|
assert "Forgejo registry" in e.title
|
||||||
|
assert "forgejo" in e.need_keywords
|
||||||
|
assert "gitea" not in e.need_keywords
|
||||||
assert "<" not in e.fetch_command and ">" not in e.fetch_command
|
assert "<" not in e.fetch_command and ">" not in e.fetch_command
|
||||||
assert "platform/workloads/coulomb/whynot-design/npm-publish" in e.fetch_command
|
assert "platform/workloads/coulomb/whynot-design/npm-publish" in e.fetch_command
|
||||||
|
rotation = " ".join(e.rotation.steps)
|
||||||
|
assert "Forgejo-supported `npm view <package>@<version>`" in rotation
|
||||||
|
assert "Gitea" not in rotation
|
||||||
|
|
||||||
|
|
||||||
|
def test_policy_nexus_source_read_lane_is_exact_high_risk_and_resolvable():
|
||||||
|
catalog = load_catalog(_repo_catalog())
|
||||||
|
entry = catalog.get("policy-nexus-forgejo-source-read")
|
||||||
|
assert entry is not None and entry.is_active and entry.exec_capable
|
||||||
|
assert entry.resolvable is True
|
||||||
|
assert entry.risk == "high"
|
||||||
|
assert entry.owner_repo == "railiance-platform"
|
||||||
|
assert entry.fetch_command == (
|
||||||
|
"bao kv get -field=FORGEJO_SOURCE_TOKEN "
|
||||||
|
"platform/workloads/policy-nexus/forgejo-source-read"
|
||||||
|
)
|
||||||
|
assert entry.path_template == "platform/workloads/policy-nexus/forgejo-source-read"
|
||||||
|
assert entry.auth_method.endswith(
|
||||||
|
"role=policy-nexus-forgejo-source-workload-kv-read"
|
||||||
|
)
|
||||||
|
assert entry.delegation is not None and entry.delegation.mode == "native"
|
||||||
|
|
||||||
|
|
||||||
|
def test_route_find_policy_nexus_source_read_prefers_concrete_lane():
|
||||||
|
catalog = load_catalog(_repo_catalog())
|
||||||
|
matches = catalog.find(
|
||||||
|
"policy nexus Forgejo private source repository read token Actions", limit=1
|
||||||
|
)
|
||||||
|
assert matches[0].id == "policy-nexus-forgejo-source-read"
|
||||||
|
|
||||||
|
|
||||||
def test_generic_and_template_lanes_not_resolvable():
|
def test_generic_and_template_lanes_not_resolvable():
|
||||||
catalog = load_catalog(_repo_catalog())
|
catalog = load_catalog(_repo_catalog())
|
||||||
# generic openbao lane has <FIELD>/<path_template>; login lane has <domain>.
|
# generic openbao lane has <FIELD>/<path_template>; login lane has <domain>.
|
||||||
assert catalog.get("openbao-api-key").resolvable is False
|
assert catalog.get("openbao-api-key").resolvable is False
|
||||||
assert catalog.get("key-cape-oidc-login").resolvable is False
|
login = catalog.get("key-cape-oidc-login")
|
||||||
|
assert login.resolvable is False
|
||||||
|
assert login.fetch_command == (
|
||||||
|
"bao login -no-print -method=oidc -path=netkingdom role=<domain>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_platform_admin_login_lane_is_exact_and_non_value_bearing():
|
def test_platform_admin_login_lane_is_exact_and_non_value_bearing():
|
||||||
|
|
@ -946,12 +994,31 @@ def test_asked_and_waiting_is_not_verification():
|
||||||
|
|
||||||
|
|
||||||
def test_stale_gaps_flags_unverified_even_when_the_date_is_today():
|
def test_stale_gaps_flags_unverified_even_when_the_date_is_today():
|
||||||
|
"""Property, not a named lane.
|
||||||
|
|
||||||
|
Originally pinned to `key-cape-oidc-login` sitting `asked-and-waiting`;
|
||||||
|
key-cape answered on 2026-09-08, so the fixture lane moved and the test
|
||||||
|
failed on someone else's good news rather than on a defect. The claim being
|
||||||
|
made is about the verification method, so assert that instead: any lane
|
||||||
|
whose blocker was never re-established is stale on the day it was reviewed.
|
||||||
|
"""
|
||||||
catalog = load_catalog(_repo_catalog())
|
catalog = load_catalog(_repo_catalog())
|
||||||
stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=date(2026, 8, 21))}
|
today = date(2026, 9, 8)
|
||||||
# Asked of key-cape on 2026-08-21 and unanswered -- zero days old, still stale.
|
stale = {e.id for e in catalog.stale_gaps(include_draft=True, today=today)}
|
||||||
assert "key-cape-oidc-login" in stale
|
unverified = [
|
||||||
# Confirmed by the owner the same day -- fresh.
|
e for e in catalog.gaps(include_draft=True)
|
||||||
assert "issue-core-ingestion-api-key" not in stale
|
if not e.effective_delegation.is_verified
|
||||||
|
]
|
||||||
|
for entry in unverified:
|
||||||
|
assert entry.id in stale, f"{entry.id} is unverified but not stale"
|
||||||
|
# A lane the owner confirmed today is fresh, so staleness is not vacuous.
|
||||||
|
fresh = [
|
||||||
|
e.id for e in catalog.gaps(include_draft=True)
|
||||||
|
if e.effective_delegation.is_verified
|
||||||
|
and e.effective_delegation.reviewed == today.isoformat()
|
||||||
|
]
|
||||||
|
assert fresh, "expected at least one owner-confirmed-today lane"
|
||||||
|
assert not (set(fresh) & stale)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_verification_method_rejected(tmp_path):
|
def test_invalid_verification_method_rejected(tmp_path):
|
||||||
|
|
@ -979,10 +1046,10 @@ def test_cli_route_gaps_fail_on_stale_exits_3(repo_catalog_env):
|
||||||
assert result.exit_code == 3
|
assert result.exit_code == 3
|
||||||
rows = json.loads(result.stdout)
|
rows = json.loads(result.stdout)
|
||||||
assert any(r["stale"] for r in rows)
|
assert any(r["stale"] for r in rows)
|
||||||
# An asked-and-waiting lane stays stale until it is verified, regardless of
|
# A lane whose blocker was never re-established stays stale regardless of
|
||||||
# how many calendar days have elapsed since the request.
|
# how many calendar days have elapsed since it was reviewed. Asserted over
|
||||||
assert any(
|
# the verification method rather than one named lane -- see
|
||||||
r["stale"]
|
# test_stale_gaps_flags_unverified_even_when_the_date_is_today.
|
||||||
and r["verified"] == "asked-and-waiting"
|
for row in rows:
|
||||||
for r in rows
|
if row["verified"] in ("asked-and-waiting", "unverified"):
|
||||||
)
|
assert row["stale"], f"{row['id']} is unverified but not stale"
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,11 @@ def test_repo_catalog_uses_only_explicit_workload_references():
|
||||||
report = build(ROOT / "registry/routing/catalog.yaml", Path.home())
|
report = build(ROOT / "registry/routing/catalog.yaml", Path.home())
|
||||||
assert report["ok"] is True
|
assert report["ok"] is True
|
||||||
assert len(report["resolved"]) == 3
|
assert len(report["resolved"]) == 3
|
||||||
assert len(report["unknown"]) == 17
|
assert len(report["unknown"]) == 18
|
||||||
assert len(report["not_applicable"]) == 9
|
# 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"]} == {
|
assert {row["lane"] for row in report["resolved"]} == {
|
||||||
"ops-warden-warden-sign-token",
|
"ops-warden-warden-sign-token",
|
||||||
"issue-core-ingestion-api-key",
|
"issue-core-ingestion-api-key",
|
||||||
|
|
|
||||||
77
wiki/ApprovalConsumption.md
Normal file
77
wiki/ApprovalConsumption.md
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Approval consumption — notes for the signing lane's future consume
|
||||||
|
|
||||||
|
**Status:** notes only. **Nothing is wired.** ops-warden's pre-sign gate consults
|
||||||
|
flex-auth (`wiki/PolicyGatedSigning.md`); it does not yet consume an approval
|
||||||
|
artifact. This page exists so the corrections we have already been given survive
|
||||||
|
until it does, rather than sitting in an inbox thread.
|
||||||
|
|
||||||
|
Owner of the contract: `gate-house`, `docs/contracts/approval-consumption.md`.
|
||||||
|
ops-warden is one of two PEP-shaped consumers named there (the SSH signing lane).
|
||||||
|
|
||||||
|
## Validate across two artifacts, never one
|
||||||
|
|
||||||
|
`GH-DEC-2026-005` settles the step-1 artifact: `GET /v1/approvals/{id}/claim`
|
||||||
|
serves the **approval-claim**. flex-auth's proposed `ActionAuthorization` is not
|
||||||
|
required there and will not be served from that endpoint.
|
||||||
|
|
||||||
|
| Artifact | Carries |
|
||||||
|
| --- | --- |
|
||||||
|
| approval-claim (step 1) | the approval fact — binding digest, validity window, consumption state, freshness, issuer |
|
||||||
|
| `DecisionEnvelope` (step 2) | exact `CheckRequest` match, policy pin |
|
||||||
|
|
||||||
|
v0.8 §6.4 obligation 5 hardened this: each artifact **must** be validated against
|
||||||
|
the layer that owns its data, and a PIP **must not** republish the PDP's decision.
|
||||||
|
Neither artifact may be taken from the other.
|
||||||
|
|
||||||
|
## Three things that are easy to get wrong
|
||||||
|
|
||||||
|
**1. Do not require `provenance.authority == 'state-hub'`.**
|
||||||
|
|
||||||
|
State Hub is a read model with no runtime approval authority. This is easy to get
|
||||||
|
wrong precisely *because* the hub is where everything else about a workplan lives,
|
||||||
|
so a consumer reaches for it as the authority on approval too.
|
||||||
|
|
||||||
|
Worth holding as a shape rather than an instance: a PEP requiring the hub to have
|
||||||
|
asserted an approval is the same boundary violated from the **consuming** side
|
||||||
|
that "never register workplans or tasks by hand" forbids from the **writing**
|
||||||
|
side. Same rule, two directions.
|
||||||
|
|
||||||
|
**2. The digest comparison target was corrected. Use `binding.approval_binding_digest`.**
|
||||||
|
|
||||||
|
`GH-DEC-2026-008` as originally issued required the claim's `pdp_digest` to equal
|
||||||
|
the decision's `request_digest`. That is unimplementable — a claim travelling
|
||||||
|
inside a hashed request cannot name the digest of the request containing it — and
|
||||||
|
a fail-closed consumer obeying it would have denied permanently. Corrected by
|
||||||
|
`FLEX-DEC-2026-007` to the PDP's published **exclusion-scoped** digest,
|
||||||
|
`binding.approval_binding_digest`.
|
||||||
|
|
||||||
|
ops-warden never copied the superseded wording anywhere (checked 2026-09-09), so
|
||||||
|
there was nothing to unwind. Recorded here so it cannot be reintroduced from an
|
||||||
|
older reading of the contract.
|
||||||
|
|
||||||
|
**3. Digest comparison does not discharge attribution, and attribution is not
|
||||||
|
available today.**
|
||||||
|
|
||||||
|
`GH-DEC-2026-010` rules that a PEP must be able to **attribute** a decision to
|
||||||
|
access-engine, and that matching digests does not achieve that. flex-auth's
|
||||||
|
envelope is unsigned and its pins serve plain HTTP, so that half is a declared
|
||||||
|
§13 gap owned by access-engine until `FLEX-WP-0024` lands.
|
||||||
|
|
||||||
|
Nothing for ops-warden to build. But when this lane is wired, its validation is
|
||||||
|
**complete-minus-attribution** and must be described that way: fail-closed
|
||||||
|
protects against a decision point that is absent, not against one that lies.
|
||||||
|
|
||||||
|
## Open dependency
|
||||||
|
|
||||||
|
`WARDEN-WP-0039-T03` — whether an admitted flex-auth contract exists for a
|
||||||
|
delegated credential read where caller and resource owner differ. If the answer
|
||||||
|
changes the caller shape it may reach how this lane's consume is built, and
|
||||||
|
`gate-house` has asked to hear that before rather than after: a contract admitted
|
||||||
|
for that shape would reach §6.4 obligation 5, and whether such a delegation
|
||||||
|
attenuates or widens is doctrine they own.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `wiki/PolicyGatedSigning.md` — the pre-sign gate as it actually is today
|
||||||
|
- `pep-stance.yaml` — unreachable-engine stance, and the marked `unknown` cell
|
||||||
|
- `history/2026-09-09-layer-model-v08-review.md` — the v0.8 assent round
|
||||||
|
|
@ -32,6 +32,20 @@ fresh file starts.
|
||||||
high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping
|
high-entropy runs). Signing and proxy paths swallow audit failures so gatekeeping
|
||||||
never blocks the primary action — but tests prove values cannot be written.
|
never blocks the primary action — but tests prove values cannot be written.
|
||||||
|
|
||||||
|
> **Absence of a record is not evidence of absence.** Because emission never
|
||||||
|
> blocks the primary action (`src/warden/ca.py`), a failed append loses the event
|
||||||
|
> while the action still happens. This trail proves that the records it holds were
|
||||||
|
> not altered or truncated; it does **not** prove that every action produced one.
|
||||||
|
> Do not reason from a missing entry. This is the estate-wide bound in
|
||||||
|
> `security-layer-model_v0.4` §9.6 — completeness is the source's obligation, and
|
||||||
|
> **Ruled 2026-08-29** (`security-layer-model_v0.6` §9.6): this trail is
|
||||||
|
> **attributive**, not load-bearing — no control branches on the presence of a
|
||||||
|
> signing record — so the non-atomic trade is legitimate, and the obligations are
|
||||||
|
> to declare it (this note) and never to claim completeness. Atomicity is required
|
||||||
|
> only where a control's soundness depends on an event being present or absent.
|
||||||
|
> Registered in the standard's §13 open-gap table as self-declared. If a future
|
||||||
|
> control ever gates on this trail, the trade must be revisited before it ships.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Query
|
## Query
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,11 @@ truth.
|
||||||
|
|
||||||
## Caller identity
|
## Caller identity
|
||||||
|
|
||||||
|
An HTTP 401 or 403 from the policy service refuses the operation under every
|
||||||
|
outage profile. It is an explicit caller-authentication or system-binding refusal,
|
||||||
|
not evaluator unavailability. Resolve the admitted caller and resource contract;
|
||||||
|
do not retry with a different resource owner or disable caller enforcement.
|
||||||
|
|
||||||
The production flex-auth pin authenticates ops-warden with Kubernetes
|
The production flex-auth pin authenticates ops-warden with Kubernetes
|
||||||
TokenReview and binds `resource.system: ops-warden` to
|
TokenReview and binds `resource.system: ops-warden` to
|
||||||
`system:serviceaccount:ops-warden:ops-warden`. Supported token sources are:
|
`system:serviceaccount:ops-warden:ops-warden`. Supported token sources are:
|
||||||
|
|
@ -187,6 +192,14 @@ Rollback is a versioned profile or registry rollback. Do not reintroduce
|
||||||
`policy.enabled: false`: that would erase per-zone evidence and recreate the
|
`policy.enabled: false`: that would erase per-zone evidence and recreate the
|
||||||
global control ADR-0009 supersedes.
|
global control ADR-0009 supersedes.
|
||||||
|
|
||||||
|
## Approval consumption (not wired)
|
||||||
|
|
||||||
|
This lane does not yet consume an approval artifact. The corrections already
|
||||||
|
received about how it must validate when it does — two artifacts never taken from
|
||||||
|
each other, the corrected `binding.approval_binding_digest` comparison target, and
|
||||||
|
the attribution gap that digest matching does not discharge — are held in
|
||||||
|
`wiki/ApprovalConsumption.md` so they are not lost between now and then.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- `tenancy.yaml`
|
- `tenancy.yaml`
|
||||||
|
|
|
||||||
37
wiki/playbooks/netkingdom-layer-declaration.md
Normal file
37
wiki/playbooks/netkingdom-layer-declaration.md
Normal 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.
|
||||||
55
wiki/playbooks/policy-nexus-forgejo-source-read.md
Normal file
55
wiki/playbooks/policy-nexus-forgejo-source-read.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# Policy Nexus Forgejo source-read token
|
||||||
|
|
||||||
|
Date: 2026-09-01
|
||||||
|
Catalog: `policy-nexus-forgejo-source-read` (status `active`, `resolvable: true`)
|
||||||
|
Owner: `railiance-platform` (OpenBao and credential lifecycle)
|
||||||
|
|
||||||
|
This lane carries the dedicated Forgejo PAT used by Policy Nexus Actions to
|
||||||
|
fetch exact archives from private owner repositories. The Forgejo identity is
|
||||||
|
restricted to organization-wide repository-code read and the PAT scope is
|
||||||
|
exactly `read:repository`. It has no repository write, package, organization
|
||||||
|
administration, instance administration, cluster, or deployment authority.
|
||||||
|
|
||||||
|
The authoritative lifecycle and evidence record is
|
||||||
|
`railiance-platform/credential-change-requests/CCR-2026-0014-policy-nexus-forgejo-source-read.yaml`.
|
||||||
|
Warden is a governed conduit and never owns, persists, caches, or logs the PAT.
|
||||||
|
|
||||||
|
## Owner-confirmed lane
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| OpenBao path | `platform/workloads/policy-nexus/forgejo-source-read` |
|
||||||
|
| Primary field | `FORGEJO_SOURCE_TOKEN` |
|
||||||
|
| Read policy | `workload-kv-read-policy-nexus-forgejo-source` |
|
||||||
|
| OIDC role | `policy-nexus-forgejo-source-workload-kv-read` |
|
||||||
|
| Forgejo identity | `policy-nexus-source` |
|
||||||
|
| Forgejo team | `policy-nexus-source-readers` |
|
||||||
|
| Actions consumer | `coulomb/policy-nexus` secret `FORGEJO_SOURCE_TOKEN` |
|
||||||
|
|
||||||
|
## Worker checklist
|
||||||
|
|
||||||
|
1. Authenticate as an approved operator through the exact OIDC role. Do not
|
||||||
|
substitute the Forgejo admin PAT or a broader OpenBao identity.
|
||||||
|
|
||||||
|
2. Confirm the route before use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden route show policy-nexus-forgejo-source-read --json | jq .resolvable
|
||||||
|
```
|
||||||
|
|
||||||
|
The result must be `true`, and the rendered fetch must contain no placeholder.
|
||||||
|
|
||||||
|
3. Agent callers must use a sanctioned high-risk transport. Pass the value only
|
||||||
|
to the bounded source-consuming child:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden access policy-nexus-forgejo-source-read \
|
||||||
|
--field FORGEJO_SOURCE_TOKEN --exec -- <reviewed-source-read-command>
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not use raw `--fetch`, place the value on argv, or write it to chat, Git,
|
||||||
|
State Hub, workflow logs, or a persistent temporary file.
|
||||||
|
|
||||||
|
4. For rotation or compromise, follow CCR-2026-0014. A replacement is not active
|
||||||
|
until its scope and negative permissions are verified and one exact-commit
|
||||||
|
Policy Nexus candidate workflow succeeds; revoke the predecessor afterward.
|
||||||
|
|
@ -1,11 +1,18 @@
|
||||||
# whynot-design npm publish token
|
# whynot-design npm publish token
|
||||||
|
|
||||||
Date: 2026-06-29
|
Date: 2026-09-04
|
||||||
Catalog: `whynot-design-npm-publish` (status `active`, `resolvable: true`)
|
Catalog: `whynot-design-npm-publish` (status `active`, `resolvable: true`)
|
||||||
Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f617fc)
|
Owner: `railiance-platform` (OpenBao) · provisioning CCR-2026-0001 (commit 8f617fc)
|
||||||
|
|
||||||
The `NPM_AUTH_TOKEN` that publishes `@whynot/design` to the coulomb Gitea npm registry
|
> **Rotation required (2026-09-04).** The OIDC role and OpenBao read path are
|
||||||
(`https://gitea.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold
|
> healthy, but the stored credential failed a real Forgejo publish. Version
|
||||||
|
> `@whynot/design@0.4.2` was published and integrity-verified through the
|
||||||
|
> plan-authorized Forgejo admin recovery lane. Treat this dedicated lane as
|
||||||
|
> unverified for writes until its package token is rotated and re-proven.
|
||||||
|
|
||||||
|
The npm publish token for `@whynot/design` on the coulomb Forgejo npm registry
|
||||||
|
(KV field `npm_token`; reaching the publish command as `NPM_AUTH_TOKEN`)
|
||||||
|
(`https://forgejo.coulomb.social/api/packages/coulomb/npm/`). ops-warden **does not hold
|
||||||
this token** — it is the access front door: `warden access` proxies the read from OpenBao
|
this token** — it is the access front door: `warden access` proxies the read from OpenBao
|
||||||
**as the caller** and never persists, caches, or logs the value.
|
**as the caller** and never persists, caches, or logs the value.
|
||||||
|
|
||||||
|
|
@ -16,7 +23,8 @@ this token** — it is the access front door: `warden access` proxies the read f
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| OpenBao path | `platform/workloads/coulomb/whynot-design/npm-publish` |
|
| OpenBao path | `platform/workloads/coulomb/whynot-design/npm-publish` |
|
||||||
| Field | `NPM_AUTH_TOKEN` |
|
| KV field | `npm_token` |
|
||||||
|
| Injected env var | `NPM_AUTH_TOKEN` (set by the publication-scope policy; not a KV key) |
|
||||||
| KV mount | `platform` |
|
| KV mount | `platform` |
|
||||||
| Read policy | `workload-kv-read-whynot-design-npm-publish` |
|
| Read policy | `workload-kv-read-whynot-design-npm-publish` |
|
||||||
| OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` |
|
| OIDC login | `bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read` |
|
||||||
|
|
@ -42,16 +50,23 @@ this token** — it is the access front door: `warden access` proxies the read f
|
||||||
for this lane (SECRETS-WP-0003, decision e6381a56); ops-warden routes to it:
|
for this lane (SECRETS-WP-0003, decision e6381a56); ops-warden routes to it:
|
||||||
```bash
|
```bash
|
||||||
secrets-engine route whynot-design-npm-publish --json # pointer / readiness
|
secrets-engine route whynot-design-npm-publish --json # pointer / readiness
|
||||||
|
secrets-engine exec --catalog whynot-design-npm-publish -- \
|
||||||
|
npm view @whynot/design@<version> version \
|
||||||
|
--registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/
|
||||||
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
|
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
**ops-warden transparent fallback** — same lane via the `warden access` proxy (fetches as
|
**ops-warden transparent fallback** — same lane via the `warden access` proxy (fetches as
|
||||||
you, holds nothing). Field-verified flags (whynot-design, @whynot/design@0.4.0):
|
you, holds nothing). The project `.npmrc` must point both the `@whynot` scope and
|
||||||
|
token fragment at `forgejo.coulomb.social`:
|
||||||
```bash
|
```bash
|
||||||
# --exec needs the env-var name. The zone-aware policy gate always runs first.
|
# --exec needs the env-var name. The zone-aware policy gate always runs first.
|
||||||
warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN \
|
warden access whynot-design-npm-publish --field npm_token \
|
||||||
|
--exec -- npm view @whynot/design@<version> version \
|
||||||
|
--registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/
|
||||||
|
warden access whynot-design-npm-publish --field npm_token \
|
||||||
--exec -- npm publish
|
--exec -- npm publish
|
||||||
warden access whynot-design-npm-publish --field NPM_AUTH_TOKEN --fetch
|
warden access whynot-design-npm-publish --field npm_token --fetch
|
||||||
```
|
```
|
||||||
On either path the value transits to you (or the child env) and never enters
|
On either path the value transits to you (or the child env) and never enters
|
||||||
ops-warden's memory, disk, or audit log.
|
ops-warden's memory, disk, or audit log.
|
||||||
|
|
@ -63,9 +78,19 @@ this token** — it is the access front door: `warden access` proxies the read f
|
||||||
`resolvable: true` means the lane is concrete and `--fetch` will run; a template lane
|
`resolvable: true` means the lane is concrete and `--fetch` will run; a template lane
|
||||||
reports `false`.
|
reports `false`.
|
||||||
|
|
||||||
4. **Publish is outward-facing and immutable.** `npm publish` is irreversible and public.
|
4. **Publish is outward-facing and immutable.** Before publishing, confirm that
|
||||||
Even once the token resolves, hold for an explicit operator "yes, publish" — do not
|
`package.json#publishConfig.registry` is exactly the Forgejo URL above, verify the
|
||||||
auto-run it from an agent.
|
intended version and `npm pack --dry-run` contents, and obtain explicit operator
|
||||||
|
approval. `npm publish` is irreversible; do not auto-run it from an agent.
|
||||||
|
|
||||||
|
5. **Record non-secret release evidence.** After the owner publishes, record only the
|
||||||
|
package coordinate (for example `@whynot/design@0.4.2`), registry URL, authenticated
|
||||||
|
install result, and release-content verification. Never record the token or npm
|
||||||
|
configuration generated for its delivery.
|
||||||
|
|
||||||
|
Forgejo advertises `npm view`, search, install, publish, unpublish, and dist-tag
|
||||||
|
support; it does not advertise `npm whoami`. Use the exact-version lookup above
|
||||||
|
rather than treating `npm whoami` failure as a credential failure.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
id: ADHOC-2026-06-27
|
id: WARDEN-WP-ADHOC-2026-06-27
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ad Hoc Tasks — 2026-06-27"
|
title: "Ad Hoc Tasks — 2026-06-27"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
|
|
@ -9,7 +9,7 @@ owner: claude
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
created: "2026-06-27"
|
created: "2026-06-27"
|
||||||
updated: "2026-06-27"
|
updated: "2026-06-27"
|
||||||
state_hub_workstream_id: "142b171b-c34b-4a45-91a5-c77e6d07ec6f"
|
state_hub_workstream_id: "a222c91f-3bb5-58a4-b6b2-f0fb18cdd5c3"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ad Hoc Tasks — 2026-06-27
|
# Ad Hoc Tasks — 2026-06-27
|
||||||
|
|
@ -19,10 +19,10 @@ Low-risk opportunistic fixes completed directly during the consolidation session
|
||||||
### T01 — Fix stale `warden` CLI install + make it usable outside the repo
|
### T01 — Fix stale `warden` CLI install + make it usable outside the repo
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-06-27-T01
|
id: WARDEN-WP-ADHOC-2026-06-27-T01
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "867c72c9-9904-400f-8542-04264e5856c2"
|
state_hub_task_id: "9176b560-8ca5-5143-888d-479857fe60f0"
|
||||||
```
|
```
|
||||||
|
|
||||||
issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked
|
issue-core reported (msg `70bcf238`) that the `warden` CLI on `~/.local/bin` lacked
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
id: ADHOC-2026-06-29
|
id: WARDEN-WP-ADHOC-2026-06-29
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ad Hoc Tasks — 2026-06-29"
|
title: "Ad Hoc Tasks — 2026-06-29"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
|
|
@ -9,7 +9,7 @@ owner: claude
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
created: "2026-06-29"
|
created: "2026-06-29"
|
||||||
updated: "2026-06-29"
|
updated: "2026-06-29"
|
||||||
state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91"
|
state_hub_workstream_id: "13fa845f-852e-55ec-a2a5-2296996e0216"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ad Hoc Tasks — 2026-06-29
|
# Ad Hoc Tasks — 2026-06-29
|
||||||
|
|
@ -17,10 +17,10 @@ state_hub_workstream_id: "1c0460b7-bc8a-48db-96d4-681bce18ac91"
|
||||||
### T01 — Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4)
|
### T01 — Joint-smoke mode for the deployed flex-auth (assist FLEX-WP-0007 T4)
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-06-29-T01
|
id: WARDEN-WP-ADHOC-2026-06-29-T01
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "371235cc-b9d3-4103-b09f-e4e01cc83c5b"
|
state_hub_task_id: "62540533-f4ca-5176-9237-32adbeb292ee"
|
||||||
```
|
```
|
||||||
|
|
||||||
flex-auth (msg `ea00620b`) asked ops-warden to help close FLEX-WP-0007 T4 (joint OpenBao
|
flex-auth (msg `ea00620b`) asked ops-warden to help close FLEX-WP-0007 T4 (joint OpenBao
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
id: ADHOC-2026-08-11
|
id: WARDEN-WP-ADHOC-2026-08-11
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ad Hoc Tasks — 2026-08-11"
|
title: "Ad Hoc Tasks — 2026-08-11"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
|
|
@ -9,7 +9,7 @@ owner: claude
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
created: "2026-08-11"
|
created: "2026-08-11"
|
||||||
updated: "2026-08-15"
|
updated: "2026-08-15"
|
||||||
state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2"
|
state_hub_workstream_id: "9f99cc64-4682-5f20-b13e-89af2b6f7c70"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ad Hoc Tasks — 2026-08-11
|
# Ad Hoc Tasks — 2026-08-11
|
||||||
|
|
@ -17,10 +17,10 @@ state_hub_workstream_id: "bc6dd3e3-ab2b-567a-9574-30d2650f20a2"
|
||||||
### T01 — Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite)
|
### T01 — Repair stale `rapp-qonto-keycape-client` wiki anchor (restore green routing suite)
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-11-T01
|
id: WARDEN-WP-ADHOC-2026-08-11-T01
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "ddd0c95c-1196-5251-a4ca-6ef441c2f377"
|
state_hub_task_id: "0771d121-278c-556e-9509-841cf6e657c3"
|
||||||
```
|
```
|
||||||
|
|
||||||
rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the
|
rapp-postgres (msg `96907986`, residual from RAPP-POSTGRES-WP-0002-T04) reported the
|
||||||
|
|
@ -39,10 +39,10 @@ an anchor that does not exist. The intended heading is `## Routing catalog index
|
||||||
### T02 — Triage the stale ops-warden inbox (11 unread, C-28/C-29)
|
### T02 — Triage the stale ops-warden inbox (11 unread, C-28/C-29)
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-11-T02
|
id: WARDEN-WP-ADHOC-2026-08-11-T02
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "fda35ad4-f526-5012-a2be-ef6b5dcbc8af"
|
state_hub_task_id: "0ed58145-732f-5102-b6a8-b931d9b6ba08"
|
||||||
```
|
```
|
||||||
|
|
||||||
`fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible
|
`fix-consistency` flagged 11 unread messages older than 3 days, two of them as possible
|
||||||
|
|
@ -73,10 +73,10 @@ read. Inbox is now empty.
|
||||||
### T03 — warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover
|
### T03 — warden-sign AppRole: PARKED pending WP-0027 break-glass + ops-bridge cutover
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-11-T03
|
id: WARDEN-WP-ADHOC-2026-08-11-T03
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "7ef4b6b0-26bf-5456-8429-8cf8458c2ddc"
|
state_hub_task_id: "337ae793-c6b0-59e9-8a07-3a7ccba237aa"
|
||||||
```
|
```
|
||||||
|
|
||||||
secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run
|
secrets-engine (msg `80456912`, 2026-06-29) is holding a validated non-mutating dry-run
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
id: ADHOC-2026-08-17
|
id: WARDEN-WP-ADHOC-2026-08-17
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ad Hoc Tasks — 2026-08-17"
|
title: "Ad Hoc Tasks — 2026-08-17"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
|
|
@ -9,7 +9,7 @@ owner: claude
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
created: "2026-08-17"
|
created: "2026-08-17"
|
||||||
updated: "2026-08-17"
|
updated: "2026-08-17"
|
||||||
state_hub_workstream_id: "8376eb89-36f2-52b0-a59a-2954b2e49349"
|
state_hub_workstream_id: "5c6c2bbb-b944-5afd-b89c-20d865518849"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ad Hoc Tasks — 2026-08-17
|
# Ad Hoc Tasks — 2026-08-17
|
||||||
|
|
@ -21,10 +21,10 @@ workload-identity owner.
|
||||||
### T01 — Answer flex-auth: how should `/v1/check` authenticate its callers?
|
### T01 — Answer flex-auth: how should `/v1/check` authenticate its callers?
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-17-T01
|
id: WARDEN-WP-ADHOC-2026-08-17-T01
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "b980d1f7-2fcc-58dd-97d8-6e9b9ba57bc9"
|
state_hub_task_id: "04a2f8f9-e70b-5eed-ad87-343c8f9ef501"
|
||||||
```
|
```
|
||||||
|
|
||||||
flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and
|
flex-auth (msg `130a148c`, FLEX-WP-0015 T02) reported that `POST /v1/check` and
|
||||||
|
|
@ -69,10 +69,10 @@ Their four questions answered in `wiki/NetKingdomSecurityMap.md`
|
||||||
### T02 — user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only
|
### T02 — user-engine: USER_ENGINE_PROXY_SECRET stays railiance-apps; record consumer-only
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-17-T02
|
id: WARDEN-WP-ADHOC-2026-08-17-T02
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "e76b9b3e-189b-5a4f-8815-4ccf2057507c"
|
state_hub_task_id: "0e815282-2fad-5c8d-be34-398e492737d0"
|
||||||
```
|
```
|
||||||
|
|
||||||
user-engine (msg `2af4a124`) answered the open confirm question on
|
user-engine (msg `2af4a124`) answered the open confirm question on
|
||||||
|
|
@ -95,10 +95,10 @@ consumes it and has no authority over custody, rotation, or issuance.
|
||||||
### T03 — key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker
|
### T03 — key-cape: `rapp-qonto-keycape-client` interim accepted; refresh the blocker
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-17-T03
|
id: WARDEN-WP-ADHOC-2026-08-17-T03
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "edfb0626-5022-5f49-aaf9-08e0d1002b90"
|
state_hub_task_id: "e21781d9-a35d-5916-b335-d12131f97a22"
|
||||||
```
|
```
|
||||||
|
|
||||||
key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden.
|
key-cape (msg `099b7cba`) acknowledged the lane staying interim on ops-warden.
|
||||||
|
|
@ -118,10 +118,10 @@ workplan.
|
||||||
### T04 — Session hygiene
|
### T04 — Session hygiene
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-08-17-T04
|
id: WARDEN-WP-ADHOC-2026-08-17-T04
|
||||||
status: done
|
status: done
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "62109af6-f095-5e7b-9258-0fcec29d30e8"
|
state_hub_task_id: "b15724e0-c27a-5260-a810-4dd25bff2228"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source
|
- [x] `uv run pytest -q` → 338 passed, 4 deselected (routing anchor + no-double-source
|
||||||
|
|
|
||||||
179
workplans/ADHOC-2026-09-08.md
Normal file
179
workplans/ADHOC-2026-09-08.md
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08
|
||||||
|
type: workplan
|
||||||
|
title: "Ad Hoc Tasks — 2026-09-08"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: finished
|
||||||
|
owner: claude
|
||||||
|
topic_slug: netkingdom
|
||||||
|
created: "2026-09-08"
|
||||||
|
updated: "2026-09-08"
|
||||||
|
state_hub_workstream_id: "182f3aff-8c8a-5766-8a68-5fe35f423f7a"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ad Hoc Tasks — 2026-09-08
|
||||||
|
|
||||||
|
Four key-cape messages, all follow-on from `WARDEN-WP-0033`. Two carry answers
|
||||||
|
ops-warden's catalog was waiting for; both make a recorded blocker less true than
|
||||||
|
it reads. This is the T05 mechanism paying out — and this time the owner ran it
|
||||||
|
against us rather than us running it against them.
|
||||||
|
|
||||||
|
### T01 — Narrow the `rapp-qonto-keycape-client` blocker (msg `08d42f47`)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "29cef28a-d93b-5318-bfdb-69dbe11fa3ba"
|
||||||
|
```
|
||||||
|
|
||||||
|
The blocker recorded "still no key-cape-native exchange/rotation command",
|
||||||
|
`verified: source-read` against key-cape source on 2026-08-28. Accurate then;
|
||||||
|
two things landed after that read:
|
||||||
|
|
||||||
|
- `keycape service-token` (2026-09-05, KEY-WP-0014-T03) — the client_secret_basic
|
||||||
|
exchange the blocker records as absent.
|
||||||
|
- `keycape verify-client` (2026-09-08) — rotation step 3 as one command,
|
||||||
|
including predecessor rejection and identical-secret detection.
|
||||||
|
|
||||||
|
**Narrowed, not cleared**, as key-cape asked. What remains genuinely blocked is
|
||||||
|
steps 1-2: successor generation and the CAS write to both custodians. Those are
|
||||||
|
custody and deployment acts, key-cape deliberately ships no command for them, and
|
||||||
|
no admitted ops-warden lane authorizes them either (T03).
|
||||||
|
|
||||||
|
`verified` moved to `owner-confirmed` — the correction came from the owner.
|
||||||
|
|
||||||
|
`rotation.automatable` flipped `true` → `false`, which key-cape explicitly asked
|
||||||
|
us not to do "either way" without precision. Done with the precision they wanted,
|
||||||
|
in the only place the schema has room for it: the flag is a single hint consumed
|
||||||
|
by a future executable driver (`WARDEN-WP-0027` Strand B), and a driver told
|
||||||
|
`true` would attempt custody acts with no admitted transport — the dangerous
|
||||||
|
direction. So the flag fails safe and the *steps* now carry the per-step truth,
|
||||||
|
each naming whether it is automatable and why. Offered them a schema change if a
|
||||||
|
per-step field would serve better; not built on speculation.
|
||||||
|
|
||||||
|
### T02 — Record key-cape's acceptance of `key-cape-oidc-login` (msg `12f1bdfa`)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "8efb8013-7b90-52ef-a17c-d63f5ce3752c"
|
||||||
|
```
|
||||||
|
|
||||||
|
key-cape **accepted** ownership: interactive login, OIDC identity-token issuance
|
||||||
|
and MFA. This confirms the 2026-08-28 correction of `intended_owner` from
|
||||||
|
secrets-engine to key-cape, and confirms secrets-engine's 2026-08-21 refusal was
|
||||||
|
right. `verified: asked-and-waiting` → `owner-confirmed`.
|
||||||
|
|
||||||
|
**The lane stays `interim`.** Acceptance covers the identity half only. The
|
||||||
|
`fetch_command` yields an *OpenBao* token — the `netkingdom` mount, the
|
||||||
|
`role=<domain>` mapping, the token store and its enforcement are OpenBao's, with
|
||||||
|
role and policy definition railiance-platform's. `keycape login` yields a
|
||||||
|
key-cape issuer JWT to a private file: different credential type, different
|
||||||
|
verifier, different enforcement owner. Accepting an owner is not the same as the
|
||||||
|
owner fronting the lane, and `ADR-0003` says the interim cover is retired when
|
||||||
|
the front door exists, not when ownership is agreed. Retirement condition is now
|
||||||
|
recorded on the entry.
|
||||||
|
|
||||||
|
Their reading of the risk is right and we confirmed it: `warden_executes: false`
|
||||||
|
with a `wiki_ref` pointer, so per `ADR-0001` this is a pointer layer, not a code
|
||||||
|
path with programmatic consumers. `risk: standard` and
|
||||||
|
`workload_ref: not-applicable` are correct — a human identity act with no KV
|
||||||
|
path, consistent with how `ADR-0008` treats the other non-KV lanes.
|
||||||
|
|
||||||
|
### T03 — Answer "who executes Qonto rotation steps 1-2" (msg `0a6c9d88`)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T03
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "bd621821-2116-51c9-a09a-a3660b5e5557"
|
||||||
|
```
|
||||||
|
|
||||||
|
Answer: **nobody, admittedly, today** — and ops-warden must not invent one.
|
||||||
|
|
||||||
|
Steps 1-2 mutate custody on `platform/workloads/rapp-qonto/keycape-client` and
|
||||||
|
`sso/keycape-rapp-qonto-client`. Custody of platform workload paths is
|
||||||
|
railiance-platform's; the admitted execution front door for provisioned
|
||||||
|
secret-exec is `secrets-engine exec`. ops-warden owns neither and absorbing it
|
||||||
|
would be `ADR-0002` and `ADR-0005` violated in one act. Routed to
|
||||||
|
railiance-platform, with secrets-engine named as the transport owner — and
|
||||||
|
secrets-engine independently confirmed the same boundary three days earlier
|
||||||
|
(msg `15f0c0ca`): they will not rewrite a production lane's custody state from a
|
||||||
|
coordination message without owner confirmation.
|
||||||
|
|
||||||
|
key-cape was right not to treat a generic `warden route` match as authorization.
|
||||||
|
See T04 for what our own tool did when asked.
|
||||||
|
|
||||||
|
### T04 — `warden plan` returned `autonomous` for a custody *write* (defect)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T04
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "9ada01d2-b14b-579e-921c-275820f255db"
|
||||||
|
```
|
||||||
|
|
||||||
|
Verifying T03 against our own front door:
|
||||||
|
|
||||||
|
```
|
||||||
|
warden plan "generate a successor client secret ... and CAS-write it to
|
||||||
|
OpenBao platform/workloads/... and sso/..." --json
|
||||||
|
verdict: autonomous
|
||||||
|
lane_id: rapp-qonto-keycape-client
|
||||||
|
commands: warden access ... --exec / --out / --wrap
|
||||||
|
```
|
||||||
|
|
||||||
|
A need that says *generate* and *CAS-write* resolved to `autonomous` and was
|
||||||
|
answered with three **read** transports. This is the `WARDEN-WP-0033-T06` shape
|
||||||
|
recurring — a generic keyword match producing a confident wrong act — except T06
|
||||||
|
fixed one route by adding an exact lane, and the defect is in the classifier.
|
||||||
|
`warden plan` has no notion of read versus mutate intent, so every write need
|
||||||
|
matches the lane that *reads* the same path and comes back `autonomous`.
|
||||||
|
|
||||||
|
Not fixed here: a classifier is design work with an approval-semantics blast
|
||||||
|
radius, which the convention says is a workplan, not an adhoc.
|
||||||
|
Promoted to **`WARDEN-WP-0038`** (`proposed`).
|
||||||
|
|
||||||
|
Worth stating plainly: key-cape distrusted this output on principle and was right
|
||||||
|
to. Had they trusted it, our tool would have told them to proceed autonomously on
|
||||||
|
an unadmitted custody mutation.
|
||||||
|
|
||||||
|
### T05 — Acknowledge the KEY-WP-0009 handoff (msg `5bc58847`)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T05
|
||||||
|
status: done
|
||||||
|
priority: low
|
||||||
|
state_hub_task_id: "06bf8508-d1a2-51b4-b951-02539524ebb9"
|
||||||
|
```
|
||||||
|
|
||||||
|
Informational; no route retired or changed. Their routing note matches ours:
|
||||||
|
"login / OIDC / MFA" → key-cape means **issuance only**, and secret *values*
|
||||||
|
route to OpenBao custody. Nothing to change in `wiki/CredentialRouting.md`.
|
||||||
|
|
||||||
|
Also records that the KEY-WP-0009 reply ops-warden reported never arriving indeed
|
||||||
|
never existed (KEY-WP-0014-T02, audited 2026-09-05). Our 2026-08-28 finding was
|
||||||
|
right, and the correction is theirs, recorded.
|
||||||
|
|
||||||
|
### T06 — Unpin two tests from a transient catalog state
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-ADHOC-2026-09-08-T06
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "a0b88db9-2886-54dd-a925-9e0521044aa0"
|
||||||
|
```
|
||||||
|
|
||||||
|
`test_stale_gaps_flags_unverified_even_when_the_date_is_today` and
|
||||||
|
`test_cli_route_gaps_fail_on_stale_exits_3` both asserted that
|
||||||
|
`key-cape-oidc-login` specifically sat `asked-and-waiting`. T02 answering that
|
||||||
|
question broke them — **the tests failed on good news**, not on a defect, and a
|
||||||
|
test that fails when a counterparty replies will get suppressed rather than read.
|
||||||
|
|
||||||
|
Both now assert the property they were always about: any lane whose blocker was
|
||||||
|
never re-established is stale regardless of its date, over whatever lanes are in
|
||||||
|
that state. The named-lane version of the claim already exists as a unit test on
|
||||||
|
`Delegation.is_verified`, which is where a fixture belongs.
|
||||||
|
|
@ -222,6 +222,39 @@ test results, and infra acceptance. T02 remains `progress` until
|
||||||
that owner gate is open; acceptance will permit preparation of a new scenario,
|
that owner gate is open; acceptance will permit preparation of a new scenario,
|
||||||
not execution or reuse of the terminal one.
|
not execution or reuse of the terminal one.
|
||||||
|
|
||||||
|
**Owner gate CLOSED — accepted 2026-08-23, found 2026-08-28.**
|
||||||
|
`railiance-platform` accepted the exact revision `0fae0904`. Recorded in their
|
||||||
|
`RPF-WP-0017-attended-login-output-containment.md` (`status: finished`), which
|
||||||
|
also records railiance-infra's independent approval at `186b030` and marks all
|
||||||
|
five acceptance criteria met. The receipt digest was verified here rather than
|
||||||
|
taken on trust: `docs/evidence/RAILIANCE-WP-0026-T01-ops-warden-receipt.json` at
|
||||||
|
ops-warden `bc1966da` hashes to
|
||||||
|
`d2ba444ed16989590325697e69d25283dc75a9432c29a72e627e80bf9fd987e4`, matching
|
||||||
|
their record exactly.
|
||||||
|
|
||||||
|
Note the identifier: the remediation interface recorded above as
|
||||||
|
`RAILIANCE-WP-0026-T01` is `RPF-WP-0017-T01` in the owner's repo. Searching for
|
||||||
|
the cited id finds an unrelated workplan, which is part of why this sat unnoticed.
|
||||||
|
|
||||||
|
**Their acceptance is source acceptance only and authorizes no live OIDC or
|
||||||
|
drill** — their words, and the boundary holds. So T02's state changes but its
|
||||||
|
`Done when` does not: what was blocked was *preparing a new scenario*, and that
|
||||||
|
is now permitted. The terminal NO-GO scenario and its receipts remain unusable.
|
||||||
|
|
||||||
|
**Third instance of the same failure this session.** The acceptance existed for
|
||||||
|
five days; no message reached ops-warden. Identically, `key-cape` accepted the
|
||||||
|
WP-0033-T04 question on 2026-08-23 with no message, and nine unread messages
|
||||||
|
were sitting on already-superseded threads. The `verified:` field added by
|
||||||
|
WP-0033-T05 was built for exactly this and it works — what does not work is
|
||||||
|
waiting for a counterparty to tell you. Re-checking a blocker means reading the
|
||||||
|
owner's repository.
|
||||||
|
|
||||||
|
**Remaining to close T02:** one attended production emergency seal/unseal drill,
|
||||||
|
requiring a new scenario id, fresh owner receipts from platform/infra/master, a
|
||||||
|
fully parameterized green preflight, and a new human GO. It is executed by the
|
||||||
|
platform owner, never by a coding agent. That is an operator decision, not an
|
||||||
|
agent one, so T02 stays `progress` and the decision is surfaced rather than taken.
|
||||||
|
|
||||||
## Task: Tamper-evident policy governance + reconcile
|
## Task: Tamper-evident policy governance + reconcile
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ type: workplan
|
||||||
title: "Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed"
|
title: "Native lane handoff — review secrets-engine's catalog admission, and fix what it exposed"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: ops-warden
|
repo: ops-warden
|
||||||
status: active
|
status: finished
|
||||||
owner: ops-warden
|
owner: ops-warden
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
planning_priority: P1
|
planning_priority: P1
|
||||||
depends_on_workplans:
|
depends_on_workplans:
|
||||||
- WARDEN-WP-0030
|
- WARDEN-WP-0030
|
||||||
created: "2026-08-21"
|
created: "2026-08-21"
|
||||||
updated: "2026-08-23"
|
updated: "2026-08-28"
|
||||||
state_hub_workstream_id: "4627d89b-4b00-562a-81e9-76e96f90fa7e"
|
state_hub_workstream_id: "4627d89b-4b00-562a-81e9-76e96f90fa7e"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -160,7 +160,7 @@ railiance-platform may deny more, deny less, or dispute a grade (`ADR-0002`).
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: WARDEN-WP-0033-T04
|
id: WARDEN-WP-0033-T04
|
||||||
status: wait
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "5acac140-a586-5db3-b231-bbf236710786"
|
state_hub_task_id: "5acac140-a586-5db3-b231-bbf236710786"
|
||||||
```
|
```
|
||||||
|
|
@ -212,6 +212,33 @@ accept that target identity with an authoritative workplan/interface, or refuse
|
||||||
and name the actual owner. T04 remains `wait` until one of those two answers is
|
and name the actual owner. T04 remains `wait` until one of those two answers is
|
||||||
recorded; the live AppRole is operational evidence, not an ownership answer.
|
recorded; the live AppRole is operational evidence, not an ownership answer.
|
||||||
|
|
||||||
|
**Answered — accepted. Closed 2026-08-28.** `key-cape` accepted issuance
|
||||||
|
ownership in `KEY-WP-0009-T03` (`status: done`, 2026-08-23): the non-secret static
|
||||||
|
registration `codex-railiance-platform` is published in
|
||||||
|
`key-cape/config/service-clients.example.yaml` — subject
|
||||||
|
`service:codex:railiance-platform`, tenant `tenant:coulomb`, role `coding-agent`,
|
||||||
|
scope `openbao:login`, `client_credentials`, 15-minute lifetime — with the
|
||||||
|
service-auth claims, renewal, expiry and failure semantics in
|
||||||
|
`key-cape/docs/openbao-service-auth-contract.md` (`T02`). The split is exactly the
|
||||||
|
one the routing asked for: KeyCape owns JWT issuance and client disablement,
|
||||||
|
railiance-platform owns the exact-bound OpenBao role and policy, OpenBao owns
|
||||||
|
enforcement, and no secret value enters either repository.
|
||||||
|
|
||||||
|
**The answer was found by reading their repo, not by receiving it.**
|
||||||
|
`KEY-WP-0009-T04` records replying to ops-warden; no such message exists in the
|
||||||
|
ops-warden inbox — 0 messages from `key-cape`, read or unread. The acceptance had
|
||||||
|
been sitting done for five days while this task sat `wait` on it.
|
||||||
|
|
||||||
|
That is the T05 lesson landing on T04: a blocker is a claim about the world at a
|
||||||
|
date, and this one had stopped being true. `verified: source-read` is doing real
|
||||||
|
work here — it is the difference between "we asked" and "we checked". The
|
||||||
|
mechanism is right; what it needs is for someone to run it, which is why the
|
||||||
|
lanes below were re-verified in the same pass rather than date-bumped.
|
||||||
|
|
||||||
|
Consequence for `ADR-0004`: the honour-system `WARDEN_AGENT_ID` marker now has a
|
||||||
|
real issuance identity behind it on the OpenBao side. ops-warden remains a
|
||||||
|
consumer of that identity and does not own it.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: WARDEN-WP-0033-T05
|
id: WARDEN-WP-0033-T05
|
||||||
status: done
|
status: done
|
||||||
|
|
|
||||||
246
workplans/WARDEN-WP-0034-layer-model-v07-conformance.md
Normal file
246
workplans/WARDEN-WP-0034-layer-model-v07-conformance.md
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0034
|
||||||
|
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
|
||||||
|
owner: ops-warden
|
||||||
|
topic_slug: netkingdom
|
||||||
|
planning_priority: P1
|
||||||
|
depends_on_workplans:
|
||||||
|
- WARDEN-WP-0030
|
||||||
|
created: "2026-08-29"
|
||||||
|
updated: "2026-09-05"
|
||||||
|
state_hub_workstream_id: "ae3ff76f-883d-5e2f-b6aa-144d61e8fdef"
|
||||||
|
---
|
||||||
|
|
||||||
|
# WARDEN-WP-0034 — Layer model v0.7 conformance
|
||||||
|
|
||||||
|
`security-layer-model_v0.7` is **accepted**. ops-warden declared Staff and PEP-shaped,
|
||||||
|
shipped the two declaration artifacts the standard now cites as estate reference forms,
|
||||||
|
and had four findings adopted into the text between v0.4 and v0.7.
|
||||||
|
|
||||||
|
The assessment in `history/2026-08-29-v07-scope-intent-assessment.md` checked every
|
||||||
|
v0.7 obligation against shipped code rather than intent. Three gaps survive, plus a
|
||||||
|
role the companion assigns that no conformance check will ever catch.
|
||||||
|
|
||||||
|
## Why a workplan and not an adhoc
|
||||||
|
|
||||||
|
T01 is a `MUST` that is currently unstated. T02 changes an enforcement boundary and
|
||||||
|
touches `ADR-0004`. T04 is a cross-repo stewardship commitment against a standard eight
|
||||||
|
repositories have yet to adopt. The convention reserves adhocs for low-risk work
|
||||||
|
completed directly; none of these qualify.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0034-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "8b3bdb9f-d2c2-5b3e-89e2-417bf3e37484"
|
||||||
|
```
|
||||||
|
|
||||||
|
**State the revocation visibility deadline (§9.7.2, a MUST).**
|
||||||
|
|
||||||
|
ops-warden states none, and the honest value is the certificate TTL: **up to 48 hours**.
|
||||||
|
A cert issued under an allow remains valid for its full TTL even if the authorizing
|
||||||
|
decision is revoked the next minute. There is no CRL, no KRL distribution, and host-side
|
||||||
|
`auth_principals` belongs to `railiance-infra`.
|
||||||
|
|
||||||
|
Add the deadline to `pep-stance.yaml` as what it is — `adm` 48h / `agt` 24h / `atm` 8h —
|
||||||
|
with the mechanism named (TTL expiry, no revocation channel) rather than implied. Assert
|
||||||
|
it against the shipped `ActorType` TTL policy by test, the same way the stance map is
|
||||||
|
asserted equal to shipped behaviour: a stated deadline free to drift from the code has
|
||||||
|
the same defect as a stated stance free to drift.
|
||||||
|
|
||||||
|
**Done when:** the deadline is published, test-bound to the TTL policy, and the absence
|
||||||
|
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: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "3318ee1a-b5d9-5d39-baf7-9c42a8bc7b55"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bind the agent read-boundary to an issued identity (§3.4 rule 1).**
|
||||||
|
|
||||||
|
`ADR-0004`'s boundary triggers on `WARDEN_AGENT_ID` — a variable the agent sets about
|
||||||
|
itself. An agent that omits it is not recognised as one. §3.4 rule 1 now requires
|
||||||
|
authority to be *issued* per task and attributable to the principal acted for.
|
||||||
|
|
||||||
|
The identity exists as of `KEY-WP-0009-T03`: `codex-railiance-platform`, subject
|
||||||
|
`service:codex:railiance-platform`, role `coding-agent`, 15-minute lifetime, with
|
||||||
|
`railiance-platform` enforcing the OpenBao-side policy.
|
||||||
|
|
||||||
|
Key the boundary on the issued identity where one is present; keep `WARDEN_AGENT_ID` as
|
||||||
|
a fallback that fails **toward** the boundary, never away from it. State plainly in the
|
||||||
|
ADR trail which half is enforced and which is advisory: the OpenBao-side deny is real,
|
||||||
|
the ops-warden-side refusal is a courtesy that a determined caller can decline.
|
||||||
|
|
||||||
|
**Done when:** an agent presenting the issued identity is recognised without setting
|
||||||
|
`WARDEN_AGENT_ID`, the fallback still refuses on `risk: high` lanes, and the
|
||||||
|
enforced/advisory split is written down.
|
||||||
|
|
||||||
|
**Watch:** do not turn this into ops-warden validating a token — that is verifying an
|
||||||
|
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: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "a891b32c-b0a7-59f6-a5cd-977be65c09ca"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Derive an emission cadence, or defer it with a reason (§9.6).**
|
||||||
|
|
||||||
|
ops-warden's trail is attributive, so cadence is a SHOULD rather than v0.7's MUST for
|
||||||
|
load-bearing sources. It has been silent through two reviews, which is the one outcome
|
||||||
|
that is not defensible.
|
||||||
|
|
||||||
|
Derive a baseline from the existing `audit.jsonl` and signatures log. If the signal is
|
||||||
|
too bursty to support a threshold — plausible, since volume is operator-driven — record
|
||||||
|
that as the finding with the distribution that shows it, and declare the deferral in
|
||||||
|
`pep-stance.yaml`. A measured "no useful baseline" is a result; silence is not.
|
||||||
|
|
||||||
|
**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: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "94e73daa-f74d-51fd-8639-68896a4066ee"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Answer the question the companion sends the estate here to ask.**
|
||||||
|
|
||||||
|
> *"For how to get something done — which lane, which credential, which route — ask
|
||||||
|
> `ops-warden`."*
|
||||||
|
|
||||||
|
Today the repo answers credential questions and no others. `warden route` and
|
||||||
|
`warden plan` cover lanes, owners and acts. Nothing answers *"which layer am I"*, *"how
|
||||||
|
do I declare"*, *"I am PEP-shaped, what do I owe"* — and eight of fifteen catalogued
|
||||||
|
repositories have yet to declare.
|
||||||
|
|
||||||
|
Provide the path, not the doctrine (that boundary is `ADR-0010`'s and does not move):
|
||||||
|
a routing entry and a short playbook that carry a reader from the companion to the files
|
||||||
|
to copy — `layer.yaml`, `pep-stance.yaml`, `check_layer_conformance.py`,
|
||||||
|
`test_layer_conformance.py` — and the check to run. The standard already names these in
|
||||||
|
§11 and §6.4; what is missing is the route to them.
|
||||||
|
|
||||||
|
**Done when:** `warden route find "how do I declare my layer"` resolves, and the
|
||||||
|
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: wait
|
||||||
|
priority: low
|
||||||
|
state_hub_task_id: "7d1b3c82-9b96-5087-a53a-496212909029"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Two things to raise rather than absorb.**
|
||||||
|
|
||||||
|
Both are other repositories' to own; ops-warden's obligation is to route them, not to
|
||||||
|
fix them (`ADR-0003`, `ADR-0005`).
|
||||||
|
|
||||||
|
1. **`ops-mason` has published no stance map.** §13.1's register has one row and the
|
||||||
|
standard says that is itself the finding. `ops-mason` is catalogued PEP-shaped in
|
||||||
|
the same paragraph and is ops-warden's peer lane owner. Offer the reference form;
|
||||||
|
do not write their map.
|
||||||
|
2. **The 48-hour replay window from T01.** Once stated, raise with `railiance-infra`
|
||||||
|
whether KRL distribution is worth building, and with `access-engine` whether a
|
||||||
|
decision lifetime shorter than the cert TTL is meaningful when nothing can recall
|
||||||
|
the cert. State the question; let the owners answer.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**One of three answered, 2026-09-09.** `flex-auth` answered the decision-lifetime
|
||||||
|
question as `FLEX-DEC-2026-004` (flex-auth commit `6a6464f`), and answered it
|
||||||
|
rather than declining it: a decision lifetime shorter than the certificate TTL is
|
||||||
|
meaningful, but only as authority to **issue**, never as authority to **use** an
|
||||||
|
already-issued certificate. The action is `sign` and it completes at issuance, so
|
||||||
|
the decision is consumed once the certificate exists and no field in the decision
|
||||||
|
record reaches the artifact. 15m against a 48h `adm` certificate was never an
|
||||||
|
inconsistency to reconcile — the question mistook a decision lifetime for a
|
||||||
|
credential lifetime.
|
||||||
|
|
||||||
|
Two obligations land on this side and are accepted: no replay of an allow past
|
||||||
|
`expires_at`, and a fresh Check per sign. The second is where the value is, and it
|
||||||
|
is real rather than nominal because claims are not cached PDP-side (deadline 0 for
|
||||||
|
the claim class), so a revoked principal stops obtaining *new* certificates
|
||||||
|
immediately.
|
||||||
|
|
||||||
|
`pep-stance.yaml` §9.7.2 is unchanged and stays here: flex-auth explicitly declined
|
||||||
|
to move the residue to their side, on the ground that a PDP recalling an issued
|
||||||
|
credential would be "the same shape of lie as fail-open". Declining it was the
|
||||||
|
right call even though absorbing it would have made their record look tidier.
|
||||||
|
|
||||||
|
The TTL lever they named — capping requested TTL per actor class or zone, already a
|
||||||
|
policy input since `ttl_out_of_bounds` is denied before OpenBao is reached — is
|
||||||
|
noted and deliberately not requested. Shortening `adm` from 48h has an operator
|
||||||
|
cost, and issuance on this lane is too sparse (3 signature records across 79 days,
|
||||||
|
`pep-stance.yaml` `emission_cadence`) to price it from intuition.
|
||||||
|
|
||||||
|
T05 stays `wait` on the remaining two: `ops-mason` (no stance map published) and
|
||||||
|
`railiance-infra` (KRL distribution versus TTL expiry). Neither has replied.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- `net-kingdom/SECURITY-COMPANION.md` v0.2
|
||||||
|
- `ADR-0002`, `ADR-0003`, `ADR-0004`, `ADR-0005`, `ADR-0009`, `ADR-0010`
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0035
|
||||||
|
type: workplan
|
||||||
|
title: "Register the Policy Nexus Forgejo source-read route"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: policy-nexus-forgejo-source-read
|
||||||
|
created: "2026-09-01"
|
||||||
|
updated: "2026-09-01"
|
||||||
|
state_hub_workstream_id: "45aec8d3-94b3-586e-b019-a47e656efafa"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Register the exact high-risk lane
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0035-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "dd84f2be-0143-540c-9c16-74f0fd129260"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the exact OpenBao path, field, OIDC role, owner pointer, and rotation
|
||||||
|
boundary from railiance-platform CCR-2026-0014. The entry must be concrete and
|
||||||
|
resolvable while remaining subject to Warden's high-risk agent read boundary.
|
||||||
|
|
||||||
|
## Verify routing and governed use
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0035-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "1fa8f778-3e46-5f44-86c4-cab8628b7e60"
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass catalog, route-selection, proxy, and policy tests; reinstall the CLI; prove
|
||||||
|
the installed route resolves and can hand the value only to a sanctioned child
|
||||||
|
transport without printing or persisting it.
|
||||||
|
|
||||||
|
Completed 2026-09-01. All 406 selected tests passed, including the generated
|
||||||
|
high-risk data-path boundary. The no-cache installed CLI resolves the exact
|
||||||
|
lane, and `warden plan` returns only sanctioned `--exec`, `--out`, and `--wrap`
|
||||||
|
transports for an agent caller. Policy Nexus Actions run 32 separately proved
|
||||||
|
the installed credential against the complete private-source fetch and release
|
||||||
|
path without exposing the value.
|
||||||
43
workplans/WARDEN-WP-0036-attended-login-openbao-output.md
Normal file
43
workplans/WARDEN-WP-0036-attended-login-openbao-output.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0036
|
||||||
|
type: workplan
|
||||||
|
title: "Accept contained OpenBao login output only after helper persistence"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: attended-login-openbao-output
|
||||||
|
created: "2026-09-01"
|
||||||
|
updated: "2026-09-01"
|
||||||
|
state_hub_workstream_id: "d844c96e-152d-53fa-bff6-e072125ef66c"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repair attended-login handoff
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0036-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "7eb8b9c9-1285-5ada-a17b-1d5bfbb8ba59"
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow a successful OpenBao login to proceed when its output is fully contained
|
||||||
|
and the private mode-0600 token helper is populated. Continue failing closed on
|
||||||
|
non-zero login, missing persistence, child output, revocation failure, or cleanup
|
||||||
|
failure.
|
||||||
|
|
||||||
|
## Verify live contained operation
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0036-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "d22bab05-c38b-561f-95de-6c146ce7c6cf"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the proxy regression suite, reinstall the CLI, and complete one governed
|
||||||
|
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.
|
||||||
111
workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md
Normal file
111
workplans/WARDEN-WP-0037-whynot-design-forgejo-npm-lane.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0037
|
||||||
|
type: workplan
|
||||||
|
title: "Repoint the whynot-design npm lane to Forgejo"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: active
|
||||||
|
owner: codex
|
||||||
|
topic_slug: whynot-design-forgejo-npm-lane
|
||||||
|
created: "2026-09-04"
|
||||||
|
updated: "2026-09-04"
|
||||||
|
state_hub_workstream_id: "42a097db-1c24-558e-a724-030bb2b4443e"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verify the existing credential against Forgejo
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0037-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "afc4d325-1c6d-5c25-aaf7-2118aa8f86c6"
|
||||||
|
```
|
||||||
|
|
||||||
|
Use only the governed execution transport to test the existing publish identity
|
||||||
|
against the canonical Forgejo npm endpoint. Do not print or persist the token.
|
||||||
|
|
||||||
|
2026-09-04: `warden plan` classified the exact verification as autonomous and
|
||||||
|
selected this lane. A contained login using the documented
|
||||||
|
`whynot-design-workload-kv-read` role proved `read` on the exact OpenBao data
|
||||||
|
path, and the governed child proved the secret is present. A real 0.4.2 publish
|
||||||
|
through that credential then failed. No value was printed or persisted and each
|
||||||
|
contained session self-revoked. The lane therefore needs token rotation, not an
|
||||||
|
OpenBao policy repair.
|
||||||
|
|
||||||
|
## Repoint the catalog and playbook
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0037-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "7ceb2f74-496c-54e8-bf5a-3c49f994ff23"
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace the retired Gitea endpoint and discovery language with Forgejo while
|
||||||
|
preserving the established OpenBao path, field, and least-privilege boundary.
|
||||||
|
This delivers the npm facet of routed intake
|
||||||
|
`01a06e07-f2f9-7586-9745-b359eb0025b6`; its inter-hub SSH facet remains separate.
|
||||||
|
|
||||||
|
Completed 2026-09-04. The catalog title, discovery keywords, rotation guidance,
|
||||||
|
and worker playbook now name the canonical Forgejo registry at
|
||||||
|
`https://forgejo.coulomb.social/api/packages/coulomb/npm/`. The OpenBao path,
|
||||||
|
field, owner, native-exec pointer, and high-risk boundary are unchanged. A
|
||||||
|
regression assertion prevents the retired Gitea discovery term and guidance
|
||||||
|
from returning.
|
||||||
|
|
||||||
|
## Prove routing and publication
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0037-T03
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
needs_human: true
|
||||||
|
intervention_note: "Rotate the dedicated Forgejo package token, then prove a fresh publish and exact npm view through this lane."
|
||||||
|
state_hub_task_id: "a8b1b855-ab34-5835-b9fd-5f48bc0b6817"
|
||||||
|
```
|
||||||
|
|
||||||
|
Pass catalog and routing tests, verify the checkout route, and record the exact
|
||||||
|
Forgejo package release proven through the lane.
|
||||||
|
|
||||||
|
2026-09-04: repo-local verification is complete: the checkout route ranks
|
||||||
|
`whynot-design-npm-publish` first for a Forgejo npm publish need, reports the
|
||||||
|
canonical Forgejo title and verification command, all focused
|
||||||
|
routing/access/proxy tests pass (`145 passed`).
|
||||||
|
|
||||||
|
The sibling repo's `@whynot/design@0.4.2` release (105 files; IR
|
||||||
|
`designVersion` 0.4.2; five visual tests pass) was published through the
|
||||||
|
plan-authorized `forgejo-admin-api-token` one-time recovery lane. An
|
||||||
|
authenticated exact-version lookup proved that Forgejo's remote integrity
|
||||||
|
matches the local dry-run. T03 waits only on rotating the dedicated package
|
||||||
|
token and proving the next fresh version through `whynot-design-npm-publish`;
|
||||||
|
the package migration itself is complete.
|
||||||
|
|
||||||
|
**2026-09-09 — field claim corrected, path routed.** `secrets-engine` (msg
|
||||||
|
`15f0c0ca`) corrected two things and declined a third, all correctly.
|
||||||
|
|
||||||
|
The KV field is `npm_token`. `NPM_AUTH_TOKEN` is the environment variable their
|
||||||
|
publication-scope policy injects, and ops-warden had copied the env var in as the
|
||||||
|
field name — so the catalog's `fetch_command` named a field that does not exist
|
||||||
|
and could only ever have failed. Corrected in `registry/routing/catalog.yaml` and
|
||||||
|
`wiki/playbooks/whynot-design-npm-publish.md` from the owner's statement rather
|
||||||
|
than re-derived here. This was `ADR-0001`'s failure mode rather than a typo: a
|
||||||
|
pointer layer restating an owner's procedure and getting a detail wrong.
|
||||||
|
|
||||||
|
The endpoint claim already agreed; `delivery_config.npm.registry` has been the
|
||||||
|
Forgejo URL throughout.
|
||||||
|
|
||||||
|
The **path** is routed to `railiance-platform` and the catalog is unchanged pending
|
||||||
|
their answer. Which location backs the lane for reads is custody state they own;
|
||||||
|
`secrets-engine` has no lane read authority to confirm it and refused to rewrite a
|
||||||
|
production lane pointer from a coordination message (`SECRETS-WP-0006-T06`). Their
|
||||||
|
reasoning is right, and asserting our own pointer is authoritative because it is
|
||||||
|
ours would route around it. The ask names a location only and flags that a
|
||||||
|
`bao kv get` answer would be the 2026-07-16 disclosure vector on a `risk: high`
|
||||||
|
lane.
|
||||||
|
|
||||||
|
`ready: false` is not being read as path evidence: a source checkout with no
|
||||||
|
production authority reports not-ready regardless of which path the catalog names.
|
||||||
|
|
||||||
|
T03 still waits on the human rotation of the dedicated Forgejo package token; the
|
||||||
|
path question does not block that, it determines whether the pointer is correct
|
||||||
|
once it rotates.
|
||||||
126
workplans/WARDEN-WP-0038-plan-mutation-intent.md
Normal file
126
workplans/WARDEN-WP-0038-plan-mutation-intent.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0038
|
||||||
|
type: workplan
|
||||||
|
title: "warden plan must distinguish reading a secret from mutating one"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: finished
|
||||||
|
owner: ops-warden
|
||||||
|
topic_slug: netkingdom
|
||||||
|
planning_priority: P1
|
||||||
|
depends_on_workplans:
|
||||||
|
- WARDEN-WP-0029
|
||||||
|
created: "2026-09-08"
|
||||||
|
updated: "2026-09-09"
|
||||||
|
state_hub_workstream_id: "751ad530-e44b-52c4-b70e-cb47568a0179"
|
||||||
|
---
|
||||||
|
|
||||||
|
# WARDEN-WP-0038 — Read versus mutate intent in the policy front door
|
||||||
|
|
||||||
|
## The defect
|
||||||
|
|
||||||
|
`warden plan` scores a need against catalog lanes by keyword overlap and returns
|
||||||
|
a verdict. It has no notion of what the caller wants to *do*. So a need that
|
||||||
|
says *generate a successor secret and CAS-write it to two custodians* matches the
|
||||||
|
lane that **reads** that path, scores well on the shared nouns, and returns:
|
||||||
|
|
||||||
|
```
|
||||||
|
verdict: autonomous
|
||||||
|
commands: warden access <lane> --exec / --out / --wrap
|
||||||
|
```
|
||||||
|
|
||||||
|
Three read transports offered as the answer to a write, under the verdict that
|
||||||
|
means "proceed without the founder". Reproduced 2026-09-08 against
|
||||||
|
`rapp-qonto-keycape-client` (`ADHOC-2026-09-08-T04`).
|
||||||
|
|
||||||
|
## Why this is worse than a bad suggestion
|
||||||
|
|
||||||
|
`warden plan` is the documented first step before drafting any founder credential
|
||||||
|
step (`.claude/rules/credential-routing.md`), and `autonomous` is consumed as an
|
||||||
|
authorization signal by agents that have no other check. `WARDEN-WP-0029` shipped
|
||||||
|
the verdict *because* instinct was not auditable; a verdict that is confidently
|
||||||
|
wrong in the permissive direction is worse than no verdict.
|
||||||
|
|
||||||
|
key-cape hit this and refused to rely on it — "we are not treating a generic
|
||||||
|
match as authorization" — which is the only reason it surfaced as a design
|
||||||
|
question rather than as an unadmitted custody mutation. The control that caught
|
||||||
|
it was a counterparty's caution. That is not a control.
|
||||||
|
|
||||||
|
`WARDEN-WP-0033-T06` is the same shape (rapp-postgres, generic
|
||||||
|
`openbao-api-key` match → `paste_once_provision` for an act that could not work)
|
||||||
|
and was fixed by adding one exact lane plus one regression. That fixed the
|
||||||
|
instance. This is the class.
|
||||||
|
|
||||||
|
## Shape of the fix (to be designed, not assumed)
|
||||||
|
|
||||||
|
- Classify need intent — read / mutate / establish — before scoring, and never
|
||||||
|
answer a mutate need with a read lane's transports.
|
||||||
|
- A mutate need against a lane ops-warden does not own is `unroutable` with a CCR
|
||||||
|
stub naming the custody owner, not `autonomous`. `ADR-0003`: cover gaps, never
|
||||||
|
silently own them; a permissive verdict on someone else's custody act is a
|
||||||
|
silent claim of ownership.
|
||||||
|
- Consider whether `rotation.steps` should be reachable from `plan` at all, given
|
||||||
|
they are advisory prose and several are explicitly not automatable.
|
||||||
|
- Regression from the reproduced case, in the manner of T06.
|
||||||
|
|
||||||
|
## Open question for design
|
||||||
|
|
||||||
|
Whether intent belongs in the matcher or in the lane. A lane could declare which
|
||||||
|
acts it admits (`read`, `rotate`, `provision`) and `plan` could refuse any act a
|
||||||
|
lane does not declare — stricter, and it fails closed on lanes that say nothing,
|
||||||
|
which is the majority today. That is a catalog schema change and needs its own
|
||||||
|
argument.
|
||||||
|
|
||||||
|
## Confirmed independently, then fixed — 2026-09-09
|
||||||
|
|
||||||
|
`railiance-platform` hit the same defect from the other side while answering the
|
||||||
|
Qonto custody routing question (msg `7c7228ac`). Having stated that the correct
|
||||||
|
verdict is `founder_required` — attended OIDC via `netkingdom` `role=platform-admin`,
|
||||||
|
through the governed `openbao-platform-admin-login` lane — they added:
|
||||||
|
|
||||||
|
> Note this is the same verdict your `warden plan` should have returned; it
|
||||||
|
> returning `autonomous` and offering read transports for a write is a real
|
||||||
|
> defect and WARDEN-WP-0038 is the right place for it. Until that is fixed, do
|
||||||
|
> not let a plan result stand in for this answer.
|
||||||
|
|
||||||
|
Two counterparties reporting the same wrong verdict in two days, one of whom is
|
||||||
|
the write authority being wrongly bypassed, moved this from proposed to shipped.
|
||||||
|
|
||||||
|
### What shipped
|
||||||
|
|
||||||
|
`_need_intent()` classifies a need as `mutate` or `read` by token match, and
|
||||||
|
`_owns_write_authority()` tests `delegation.mode == "permanent"`. A mutating need
|
||||||
|
on a lane ops-warden does not permanently own can no longer reach any branch that
|
||||||
|
returns `autonomous`:
|
||||||
|
|
||||||
|
- lane declares a `rotation` → `founder_required`, with an `approve` act naming
|
||||||
|
the write owner, the rotation method, whether it is automatable, and
|
||||||
|
`warden rotate-guide` as guidance;
|
||||||
|
- lane declares none → `unroutable` with a CCR stub whose `owner_hint` names the
|
||||||
|
write authority.
|
||||||
|
|
||||||
|
**Commands are empty of read transports either way.** That is the half that made
|
||||||
|
the wrong verdict actionable rather than merely wrong: `--out`, `--exec`,
|
||||||
|
`--wrap` and a bare `bao kv get` were being offered as the answer to a write, and
|
||||||
|
a test now asserts none of them appears.
|
||||||
|
|
||||||
|
### The design question in the plan, answered by use
|
||||||
|
|
||||||
|
The plan asked whether intent belongs in the matcher or in the lane. It went in
|
||||||
|
the matcher, with the *ownership* test doing the work that a verb list cannot:
|
||||||
|
SSH certificate issuance is itself a mutating act, and `warden sign` must stay
|
||||||
|
`autonomous`. `delegation.mode: permanent` is what separates ops-warden's own
|
||||||
|
front door from someone else's custody — not the absence of a verb from a list.
|
||||||
|
A regression asserts the SSH lane still proceeds, because a guard that refuses
|
||||||
|
our own lane would be a worse defect than the one it fixes.
|
||||||
|
|
||||||
|
The stricter alternative — lanes declaring which acts they admit, `plan` refusing
|
||||||
|
anything undeclared — is not built. It is a catalog schema change that would fail
|
||||||
|
closed on the majority of lanes, which say nothing today, and it needs its own
|
||||||
|
argument rather than arriving as a side effect of this fix.
|
||||||
|
|
||||||
|
### Not claimed
|
||||||
|
|
||||||
|
This narrows what `warden plan` will assert; it does not make the tool an
|
||||||
|
authority on custody. railiance-platform's instruction stands and is the right
|
||||||
|
standing posture: a plan result does not substitute for the owner's answer.
|
||||||
104
workplans/WARDEN-WP-0039-explicit-policy-refusal.md
Normal file
104
workplans/WARDEN-WP-0039-explicit-policy-refusal.md
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0039
|
||||||
|
type: workplan
|
||||||
|
title: "Preserve explicit policy caller refusals before credential and CA effects"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: blocked
|
||||||
|
owner: codex
|
||||||
|
topic_slug: custodian
|
||||||
|
created: "2026-09-08"
|
||||||
|
updated: "2026-09-08"
|
||||||
|
origin: residual
|
||||||
|
origin_ref: HFACT-WP-0001
|
||||||
|
state_hub_workstream_id: "ae44a935-6fca-514c-a385-4550dd2b1fe8"
|
||||||
|
---
|
||||||
|
|
||||||
|
The factory critical-path review reproduced a live HTTP 403 from the reachable
|
||||||
|
PDP for the configured ops-warden caller and a railiance-platform secret read.
|
||||||
|
`check_fetch_policy` treated it as evaluator unavailability and used unknown-zone
|
||||||
|
fail-open. Caller-auth rejection is an explicit refusal, not a transport outage.
|
||||||
|
|
||||||
|
## Preserve explicit authentication and binding refusals
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0039-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
assignee: the-custodian
|
||||||
|
state_hub_task_id: "eaad08d9-ae47-5f50-8278-ea402a0e2869"
|
||||||
|
```
|
||||||
|
|
||||||
|
HTTP 401 and 403 stop signing and credential fetch irrespective of the outage
|
||||||
|
profile. Do not expose response bodies or alter system identity to bypass the
|
||||||
|
refusal. Preserve configured behavior for genuine evaluator failures. Prove
|
||||||
|
both permissive and closed outage profiles and refusal before child execution.
|
||||||
|
|
||||||
|
## Verify and publish the correction
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0039-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
assignee: the-custodian
|
||||||
|
state_hub_task_id: "ac85cf00-391c-5c63-b8f4-ed5bd57684e5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the policy and proxy suites, retain a value-free live refusal receipt,
|
||||||
|
publish the reviewed source, and verify the installed CLI refuses before any
|
||||||
|
credential transport. A refusal is not a successful credential-read admission.
|
||||||
|
|
||||||
|
The owner installer initially reused a cached wheel despite `--force`, leaving
|
||||||
|
the pre-fix policy code installed. Both Makefile installation targets now use
|
||||||
|
`--reinstall-package ops-warden` to refresh this package from the reviewed source.
|
||||||
|
Verify the installed module as well as the checkout before the live refusal test.
|
||||||
|
|
||||||
|
## Resolve the credential proxy's admitted policy binding
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0039-T03
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
assignee: the-custodian
|
||||||
|
blocking_reason: "The configured ops-warden caller represents ops-warden; credential requests name their owner as resource.system. Need the flex-auth/credential-owner contract for that exact delegated read, without broadening caller bindings or relabelling resource ownership."
|
||||||
|
state_hub_task_id: "8ca28b63-2e04-5333-b635-011b124c1806"
|
||||||
|
```
|
||||||
|
|
||||||
|
Consume the existing native-lane handoff (WARDEN-WP-0033 / SECRETS-WP-0006)
|
||||||
|
and flex-auth caller contract. Establish whether this interim transport needs an
|
||||||
|
admitted dedicated policy route or must finish its native handoff. Retain the
|
||||||
|
refusal until that contract yields positive and wrong-caller/owner/tenant
|
||||||
|
negative evidence. No credential read, secret generation, or policy grant is
|
||||||
|
authorized by this workplan alone. HFACT-WP-0001-T03 consumes this return.
|
||||||
|
|
||||||
|
Validation: 429 tests passed (4 integration tests deselected by the repository default); Ruff passed for changed Python files. Full tests used the declared phase-memory source and an isolated temporary memory store. The focused policy/proxy suite passed 69 tests. The existing authenticated SSH policy probe still returns HTTP 200/ALLOW, decision:f3f7c88f9585582a; the credential-owner request returns 403. No CA issue or credential read was performed by these probes.
|
||||||
|
|
||||||
|
Installed verification: source guard present after refreshed owner installation (`19bb75c`). `warden access forgejo-admin-api-token --exec --field API_TOKEN -- true` exits 4 with explicit HTTP 403 before credential transport or child execution. The previous fail-open warning is absent. The probe requested no output value; no credential was fetched. T01/T02 are complete; T03 owns the remaining exact policy-binding admission.
|
||||||
|
|
||||||
|
**T03 routed 2026-09-09.** Asked `flex-auth` whether an admitted contract exists
|
||||||
|
for a delegated credential read where the caller (ops-warden) and the resource
|
||||||
|
owner differ, since that is exactly the shape drawing the live 403. Three outcomes
|
||||||
|
were named as equally acceptable answers, with no preference stated between the
|
||||||
|
first two: an existing contract to conform to; no contract yet, making this a
|
||||||
|
design question they should raise rather than one ops-warden invents a shape for;
|
||||||
|
or that there should be **no** such contract — the interim proxy transport is
|
||||||
|
itself the defect and the answer is to finish the native handoff
|
||||||
|
(`WARDEN-WP-0033` / `SECRETS-WP-0006`).
|
||||||
|
|
||||||
|
The third is a real possibility and was not argued against. Eleven catalog lanes
|
||||||
|
are ops-warden proxies with a named intended owner precisely because no owner
|
||||||
|
front door exists; if delegated reads should not be made policy-admissible, that
|
||||||
|
shortens WP-0033 rather than blocking it.
|
||||||
|
|
||||||
|
Two fixes were explicitly ruled out in the message rather than left unmentioned:
|
||||||
|
broadening the ops-warden caller binding so it may read other systems' resources,
|
||||||
|
and relabelling `resource.system` as ops-warden so the existing binding matches.
|
||||||
|
The second is worse — it would make the audit trail assert ops-warden owns
|
||||||
|
credentials it deliberately does not, which is the claim `ADR-0002` exists to
|
||||||
|
prevent, achieved by editing a field instead of making an argument.
|
||||||
|
|
||||||
|
The refusal stays in place until an answer yields positive evidence plus
|
||||||
|
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).
|
||||||
152
workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md
Normal file
152
workplans/WARDEN-WP-0040-unknown-zone-fail-closed-adoption.md
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
---
|
||||||
|
id: WARDEN-WP-0040
|
||||||
|
type: workplan
|
||||||
|
title: "Adopt unknown -> fail_closed behind signing-target classification coverage"
|
||||||
|
domain: infotech
|
||||||
|
repo: ops-warden
|
||||||
|
status: proposed
|
||||||
|
owner: ops-warden
|
||||||
|
topic_slug: netkingdom
|
||||||
|
planning_priority: P1
|
||||||
|
depends_on_workplans:
|
||||||
|
- WARDEN-WP-0032
|
||||||
|
- WARDEN-WP-0034
|
||||||
|
created: "2026-09-09"
|
||||||
|
updated: "2026-09-09"
|
||||||
|
state_hub_workstream_id: "c8ee441e-1be1-5219-910c-e79ff23cc9ec"
|
||||||
|
---
|
||||||
|
|
||||||
|
# WARDEN-WP-0040 — `unknown` → `fail_closed`, in the right order
|
||||||
|
|
||||||
|
security-layer-model v0.8 §6.4 obligation 3 (proposed, 2026-09-06) rules that
|
||||||
|
`unknown` is not a zone and must resolve to `fail_closed`. ops-warden's
|
||||||
|
`pep-stance.yaml` declares `unknown: fail_open` under `ADR-0009`.
|
||||||
|
|
||||||
|
**We agree with the rule.** `history/2026-09-09-layer-model-v08-review.md` records
|
||||||
|
the assent and why our falsifier check failed: the cell governs certificate
|
||||||
|
issuance, not a §5.1 read-only diagnostic, so nothing rescues it.
|
||||||
|
|
||||||
|
**We disagree with adopting it first.** Measured 2026-09-09: of four signing
|
||||||
|
targets, zero resolve to a zone, three are `unknown`, one is `not-applicable`.
|
||||||
|
Flipping the cell today makes flex-auth a hard dependency of essentially every
|
||||||
|
certificate — `ADR-0006`'s rejected configuration, reached by a different route,
|
||||||
|
including the continuity path needed to repair flex-auth itself.
|
||||||
|
|
||||||
|
So: coverage first, then the cell. That ordering is the whole holding of
|
||||||
|
`ADR-0006` and the reason `ADR-0009` replaced a global switch with a zone map.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0040-T01
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "611f0901-9fb5-5954-8dd0-a860c5f0cbec"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Classify the continuity path before anything else changes.**
|
||||||
|
|
||||||
|
The deadlock is specific and worth naming before it is designed around: flex-auth
|
||||||
|
unreachable → operator needs an SSH certificate to reach the host → target is
|
||||||
|
`unknown` because nobody classified the repair path → `fail_closed` denies it.
|
||||||
|
|
||||||
|
`z2-continuity` exists for this and its stance is `fail_open`, so the fix is
|
||||||
|
classification, not an exception. Determine which actor(s) constitute the
|
||||||
|
repair path, establish whether the declaration is ops-warden's to make (our own
|
||||||
|
`tenancy.yaml` covers ops-warden as a workload; the actors' target workloads may
|
||||||
|
not be ours), and route what is not.
|
||||||
|
|
||||||
|
Blocks T03. Converting the cell with the repair path unclassified is the one
|
||||||
|
outcome this workplan exists to prevent.
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0040-T02
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "54ad4864-7bcf-592e-a187-9239a81a0b11"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Raise signing-target coverage, by asking owners — never by inferring.**
|
||||||
|
|
||||||
|
Three actor resources report `workload_resolution_absent`:
|
||||||
|
`agt-codex-interhub-bootstrap`, `agt-state-hub-bridge`, `atm-backup-daily`.
|
||||||
|
Their owners (ops-bridge and the backup execution unit among them) have not
|
||||||
|
published an authoritative workload identity declaration.
|
||||||
|
|
||||||
|
`ADR-0009` rule 3 forbids closing this with path or repository-name inference,
|
||||||
|
and that prohibition holds under pressure from this workplan specifically —
|
||||||
|
adopting a stricter stance is not a licence to manufacture the membership that
|
||||||
|
makes it survivable.
|
||||||
|
|
||||||
|
Route to each owner with the concrete consequence: while the declaration is
|
||||||
|
absent their actor cannot be issued a certificate during a flex-auth outage once
|
||||||
|
the cell converts. That is a better ask than a generic request to publish a
|
||||||
|
declaration, and it is true.
|
||||||
|
|
||||||
|
Report coverage with `scripts/report_workload_join.py`; this task is done when
|
||||||
|
coverage is stated, not when it reaches a threshold — the threshold is T03's
|
||||||
|
gate, and owners may legitimately decline.
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0040-T03
|
||||||
|
status: wait
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "93eaf1f2-daaa-5e22-8804-85ca9433571c"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Convert the cell and supersede `ADR-0009`'s unknown row.**
|
||||||
|
|
||||||
|
Gated on T01, and on v0.8 (or its successor) reaching `accepted`. Do not convert
|
||||||
|
against a `proposed` standard.
|
||||||
|
|
||||||
|
`ADR-0009` is `owner: ops-warden`, so changing it means a superseding ADR, never
|
||||||
|
an in-place edit (`.claude/rules/architecture.md`). The superseding record should
|
||||||
|
carry the v0.8 argument rather than restate it: unknown is not a zone, so a §9.3
|
||||||
|
per-zone trade was never made for that request.
|
||||||
|
|
||||||
|
`pep-stance.yaml` is asserted equal to `PolicyConfig.failure_modes` by
|
||||||
|
`tests/test_layer_conformance.py`, so the map and the code convert in one commit
|
||||||
|
or the test fails — which is the property that makes the map worth publishing.
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: WARDEN-WP-0040-T04
|
||||||
|
status: todo
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "222a8c5d-0c0f-5a1d-98d8-02be7dca3358"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Return findings to gate-house and track the transition ask.**
|
||||||
|
|
||||||
|
Sent 2026-09-09 with the review. Two asks, in preference order: obligation 3
|
||||||
|
names a dated transitional state gated on coverage; or failing that, §13.1
|
||||||
|
records classification coverage alongside stance, so a register row cannot read
|
||||||
|
`unknown: fail_closed` while every target is unknown.
|
||||||
|
|
||||||
|
If both are declined, that is an answer and this workplan proceeds unchanged —
|
||||||
|
the cell stays a **declared gap** under §11 with this workplan as its route,
|
||||||
|
which is what the `declared-gap` mark (our own v0.6 finding) exists to express.
|
||||||
|
|
||||||
|
**Answered 2026-09-09 — GH-DEC-2026-011.** Ask 1 declined, ask 2 adopted;
|
||||||
|
standard amended at `net-kingdom@64394e9`.
|
||||||
|
|
||||||
|
Ask 1's refusal is correct and we are not appealing it: a sanctioned transitional
|
||||||
|
`fail_open` is indistinguishable at runtime from the stance the rule forbids, and
|
||||||
|
would make the rule optional at the only moment it costs anything. Ask 2 gave us
|
||||||
|
what we actually needed — §13.1 carries a Coverage column with this repo's
|
||||||
|
figures as its first entries, guarded so that coverage never softens a stance,
|
||||||
|
never gates one, and never makes a non-conformant cell conformant.
|
||||||
|
|
||||||
|
The reversal condition on that column binds ops-warden first, since the column
|
||||||
|
exists because we asked: if a row is ever argued conformant *because* its coverage
|
||||||
|
is low, the column comes out. Our row reads non-conformant and low-coverage, and
|
||||||
|
the second is not a defence of the first.
|
||||||
|
|
||||||
|
gate-house confirmed explicitly that they are **not** asking for the cell to be
|
||||||
|
flipped before coverage exists, and that this workplan's order is right. The cell
|
||||||
|
stays a declared gap under §11 with WP-0040 recorded as its route — the outcome
|
||||||
|
this task named as acceptable if both asks were declined, reached with one of them
|
||||||
|
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.
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
id: ADHOC-2026-07-07
|
id: WARDEN-WP-ADHOC-2026-07-07
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Ad Hoc Tasks — 2026-07-07"
|
title: "Ad Hoc Tasks — 2026-07-07"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
|
|
@ -9,7 +9,7 @@ owner: grok
|
||||||
topic_slug: custodian
|
topic_slug: custodian
|
||||||
created: "2026-07-07"
|
created: "2026-07-07"
|
||||||
updated: "2026-07-07"
|
updated: "2026-07-07"
|
||||||
state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783"
|
state_hub_workstream_id: "90568b1e-8395-5c67-9c69-851ed08ff3d3"
|
||||||
---
|
---
|
||||||
|
|
||||||
> **Terminology note:** Historical text in this archived workplan may use the legacy term "workstream". The fleet term is **workplan** (`canon/standards/workplan-terminology-fleet_v0.1.md`).
|
> **Terminology note:** Historical text in this archived workplan may use the legacy term "workstream". The fleet term is **workplan** (`canon/standards/workplan-terminology-fleet_v0.1.md`).
|
||||||
|
|
@ -19,10 +19,10 @@ state_hub_workstream_id: "2e8bc783-2e37-4b86-a691-177b18329783"
|
||||||
### T01 — Roll out proxy pipe fix (be3b4a2)
|
### T01 — Roll out proxy pipe fix (be3b4a2)
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: ADHOC-2026-07-07-T01
|
id: WARDEN-WP-ADHOC-2026-07-07-T01
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "55b6de52-b958-4b72-94eb-4ba1b8da2629"
|
state_hub_task_id: "bf985c95-bea6-5057-94f9-9cfa7e1c9dd8"
|
||||||
```
|
```
|
||||||
|
|
||||||
`warden access` failed on `reuse-surface-hub-write-token` because `shlex.split`
|
`warden access` failed on `reuse-surface-hub-write-token` because `shlex.split`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue