Commit graph

47 commits

Author SHA1 Message Date
66db87e6ae feat: refuse to answer a write with a read (WARDEN-WP-0038)
`warden plan` scored needs 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.

Two counterparties reported it in two days. key-cape distrusted the output on
principle and was right to; railiance-platform, answering as the write
authority being wrongly bypassed, said plainly that `founder_required` is the
verdict it should have returned and that until it is fixed a plan result must
not stand in for the owner's answer.

A mutating need on a lane ops-warden does not permanently own can no longer
reach any branch returning `autonomous`: it becomes `founder_required` with
an approve act naming the write owner, or `unroutable` with a CCR stub when
the lane admits no rotation route. Commands carry no read transport either
way, which is the half that made the wrong verdict actionable.

The ownership test does the work a verb list cannot. SSH certificate
issuance is itself a mutating act, so `delegation.mode: permanent` -- not the
absence of a verb -- separates ops-warden's own front door from someone
else's custody. A regression asserts `warden sign` still proceeds; a guard
that refused our own lane would be worse than the defect it fixes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EPuTc18FjU5WFqoSEKH3C

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1276224@bnt-lap001
Assistant-Session: 426ec497-e1c4-4dd3-b417-dfce1ca1dbc3
2026-09-10 08:02:10 +02:00
31d9b6671c Refuse explicit policy authentication and binding denials before side effects
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-08 16:46:00 +02:00
00145d705e feat: complete local layer model v0.7 conformance work
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06eaf-3425-7f92-a0c2-bb4aa4faebe4
2026-09-05 01:19:48 +02:00
8f01eefb1e Preserve Warden config in attended child
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:24:18 +02:00
b4c1d3900a Fix contained OpenBao login handoff
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a058f3-8ba0-7692-a042-9a870fc3d663
2026-09-01 00:13:26 +02:00
0fae0904ce fix: contain attended OpenBao login output
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0290b-3241-74c3-b868-6049545af836
2026-08-23 01:31:05 +02:00
e3b9b1620c fix: route OpenBao recovery ceremonies safely
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0290b-3241-74c3-b868-6049545af836
2026-08-22 20:54:45 +02:00
8280c0b7b7 fix: route OpenBao platform administration login
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0290b-3241-74c3-b868-6049545af836
2026-08-22 19:08:21 +02:00
7ce58ae638 feat: adopt security zones and explicit workload refs
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0291a-1e87-7151-9934-fcbfe3f65eb1
2026-08-22 15:36:37 +02:00
e18894ee5b Scale the blocker window by lane risk, converging with risk-nexus
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
risk-nexus accepted the offer to match their convention rather than grow a second
one, and published it: 14d critical/high, 30d medium, 60d low, nothing auto-closing
on staleness alone. Their preference — point warden route gaps at those windows and
the two registers agree without a shared mechanism — is better than a joint tool.

blocker_stale_days() now maps lane risk onto those windows. A flat 14 would have
been wrong in both directions: too aggressive for a low-risk pointer, and it treated
an admin PAT lane the same as one.

ungraded takes the shortest window, not the longest. ADR-0007 makes an absent grade
a defect and ADR-0008 makes a grade cover the whole path, so a lane nobody graded is
the one whose blocker deserves least trust. Encoding that as 60 days would have been
the fail-open default this repo already fixed once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:29:29 +02:00
55f0f47a02 WARDEN-WP-0033-T05: split the stale cadences, and record how a blocker was verified
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The 90-day --stale-days default on `warden route gaps` was not a loose threshold,
it was an inert one: the delegation register was created 2026-08-15, so it could
not have fired before November. It was inherited from the catalog pointer cadence
and applied to a claim with a completely different half-life.

Two changes. DEFAULT_BLOCKER_STALE_DAYS = 14 now governs interim blockers, while
DEFAULT_STALE_DAYS = 90 keeps governing pointer freshness -- "is this the right
owner and page" is quarterly, "has the owner answered" is not. 14 is calibrated
on blockers that actually cost something: ten days for the secrets-engine lanes,
one for RISK-F-0001, roughly fifty for FLEX-WP-0007.

The second change matters more. `reviewed` records when someone touched an entry,
which is indistinguishable from re-checking it -- six lanes read as freshly
reviewed today because I typed in them. `verified:` now says how the claim was
established, and asked-and-waiting explicitly does NOT count: that is the state
the secrets-engine blocker sat in for ten days while looking current. A lane in
that state is stale at zero days old, and key-cape-oidc-login proves it works.

8 of 14 interim lanes are honestly marked unverified rather than given a fresh
date they did not earn.

--fail-on-stale exits 3 for a cron or gate. No CI test on age: a date-triggered
failure breaks the build for whoever commits next instead of whoever owns the
blocker. The CI test is structural -- every interim lane must record how it was
verified -- so it fails on the commit that introduces the omission.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:26:10 +02:00
d0d4f9d8fc Make the risk grade fail safe, and gate CI on absence
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The mechanism behind RISK-F-0003 was sharper than the finding described.
is_high_risk was risk == "high", but risk was never absent at the model layer:
RouteEntry.risk carried a dataclass default of "standard". An omitted grade was
not unhandled, it was actively resolved to the permissive value — fail-open by
construction, which is why nothing ever warned.

The default is now "ungraded" and is_high_risk returns true for anything outside
an explicit low-risk vocabulary (standard / low / accepted). An omitted grade and
an unrecognised grade from a newer catalog both resolve to high, so the boundary
fails safe in both directions rather than reading an unknown value as permission.

test_every_repo_catalog_lane_is_explicitly_graded is the CI gate that stops an
ungraded lane being committed, per ADR-0007: absence is not a grade.

"accepted" is in the low-risk vocabulary deliberately, ready for the
maturity-derived default — an experimental-context lane may be explicitly
accepted, which is a graded decision rather than an omission.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 01:13:35 +02:00
0a331413a2 Send a caller identity to flex-auth so policy.enabled can flip
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
flex-auth's flex-auth-ops-warden pin (FLEX-WP-0016) TokenReviews the caller and
binds resource.system: ops-warden to system:serviceaccount:ops-warden:ops-warden.
policy.py posted /v1/check with no Authorization header, so the pin logs
"caller authentication warning" and can only run callerAuth.mode: warn — which,
under ADHOC-2026-08-17-T01, is exactly what blocks policy.enabled: true.

- policy.caller_auth (none | file | env | command) + src/warden/caller_identity.py:
  token resolved per call, never cached, written, or logged (ADR-0002)
- both check_sign_policy and check_fetch_policy attach the bearer header; an
  unobtainable token fails closed rather than retrying anonymously
- scripts/check_policy_caller_identity.py: read-only gate, prints length and a
  truncated fingerprint only, distinguishes 401 (audience/binding) from 403
- example config: caller_auth block, and flex_auth_url corrected — it pointed at
  flex-auth.flex-auth.svc, a Service that does not exist
- WARDEN-WP-0031, PolicyGatedSigning caller-identity section and flip sequence

Default stays mode: none, so behaviour is unchanged until an operator opts in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 15:08:34 +02:00
c93e3c9b43 Ship WARDEN-WP-0030: delegation register for every catalog lane
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Every execution position is now explicit. Catalog entries carry
delegation.mode (permanent / native / interim) with intended owner and
blocker. warden route gaps lists the interim set. Promotion requires
the ownership question. Doctrine lives in AccessRouting.md; the
register was published to the named owner repos.
2026-08-15 20:54:58 +02:00
5149946a4c WARDEN-WP-0029: implement plan front door, org posture, desk, freshness
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Ship posture-aware access planning: organization_posture=build (axis C),
catalog freshness warnings, warden plan verdicts, localhost founder desk,
and playbook/agent guidance that retire /tmp file-drop patterns.

Compose route catalog + handoff rather than a second routing layer.
2026-07-18 16:59:37 +02:00
b971403dad WARDEN-WP-0026 finish Strand A (T04/T05/T07)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Promote railiance-backup-offsite-lane to active/resolvable after
capabilities-safe re-verify. Add catalog risk=high, agent read-boundary
(exit 7 + OpenBao policy companion), EXPOSED taint via warden taint, and
close WP-0026.
2026-07-16 23:26:26 +02:00
fc0f18aa5c WARDEN-WP-0026 T03: masking display filter (defense-in-depth)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
- warden/mask.py: fingerprint()/mask_value() — presence, length, 8-char sha256
  prefix; never the value.
- proxy.proxy_fetch_fingerprint + `warden access --fingerprint`: masked status view
  (presence/length/hash) that emits no value, so it bypasses the T02 stdout guard.
  Lets two parties compare sha256 prefixes to confirm a shared value without seeing
  it (e.g. rotation landed).
- documented as defense-in-depth (raw bao bypasses it) in OperatorAccessAssist.md
  and the module docstring.
- tests: tests/test_mask.py + CLI fingerprint test. 299 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:54:55 +02:00
359ca1bd0e WARDEN-WP-0026 T02: safe access transports (no secret values on stdout)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
- proxy.py: proxy_fetch_to_file (mode-0600 file), build_wrapped_fetch +
  proxy_fetch_wrapped (single-use OpenBao response-wrapping token), _capture_value
  helper, is_bao_kv_fetch.
- warden access: --out FILE, --wrap [--wrap-ttl], --unsafe-stdout. Raw --fetch to a
  non-TTY stdout is refused (exit 6) — captured/piped output is the disclosure risk;
  sanctioned transports are --out / --exec / --wrap.
- canon: anti-pattern (secret value onto captured stdout) + transport table in
  .claude/rules/credential-routing.md; OperatorAccessAssist.md examples + G2 updated.
- tests: file/wrap/build + stdout-guard in tests/test_proxy.py. 293 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:51:56 +02:00
c3eb59ea04 WARDEN-WP-0026 T06: rotation guidance registry + warden rotate-guide
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
- routing model: RotationGuide (method rotate|re-establish, steps, owner,
  automatable), RouteEntry.rotation + has_rotation + vends_secret.
- catalog parser: validate rotation block; secret-material screen gains a
  prose-safe mode (high-entropy detector only) so authored steps aren't tripped
  by substrings like "s."/"exists.".
- CLI: `warden rotate-guide <id>` (human + --json); route show --json now
  carries has_rotation + rotation.
- scorecard: catalog_rotation_coverage — every active secret-vending lane must
  carry a rotation block (SSH/login/pointer lanes exempt). Promotion checklist
  criterion 9.
- data: rotation blocks for all 7 active vending lanes + the draft
  railiance-backup lane (re-establish: age keypair regen + re-encrypt).
- fix pre-existing collision: bare `npm` keyword on forgejo-admin -> forgejo-npm
  so "npm token" routes to the generic lane (restores test_access expectations).
- tests: rotation parse/coverage/prose-screen/CLI in tests/test_routing.py;
  scorecard count 6 -> 7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 14:40:30 +02:00
be3b4a2a86 Fix warden access proxy for catalog fetch commands with shell pipes.
resolve_fetch_command used shlex.split, which treated `|` as a literal
argument — breaking reuse-surface-hub-write-token (kubectl | base64 -d).
Piped commands now run via shell=True with inherited stdio for --fetch.
2026-07-07 16:42:26 +02:00
210f7eab68 Add Makefile targets to install and verify phase-memory with warden.
install-all uses uv tool install --with-editable for sibling phase-memory.
check-memory and verify-memory confirm warden can load experiential memory.
2026-07-03 00:54:21 +02:00
120de64bcb Enable implicit phase-memory activation on every warden command.
Load coordination memory by default via ensure_memory_context on app bootstrap
and route/access flows; invalidate cache after episode writes. WARDEN_MEMORY=0
remains the opt-out. Document that warden memory activate is optional only.
2026-07-03 00:49:36 +02:00
04929e7981 Implement WARDEN-WP-0024 experiential memory and agent sessions.
Add phase-memory bridge, warden memory CLI, route/access/sign recording,
memory-aware worker planning with OpenRouter skip, tests, wiki, and AGENTS.md
orientation for Claude, Codex, Grok, and future agent sessions.
2026-07-02 23:40:45 +02:00
d6088e4e16 Implement WP-0022 audit trail and WP-0023 INTENT–SCOPE closeout
Add unified metadata-only audit.jsonl with secret-material guard, instrument
sign/access/worker paths, and expose warden activity CLI. Surface broker hint
when VAULT_TOKEN is unset, refresh INTENT/SCOPE docs, and add production
integration checklists plus catalog lane promotion playbook.
2026-07-01 23:32:38 +02:00
a10bbd2162 feat(WARDEN-WP-0021): T3-T5 — visibility, approve loop, runbook (scheduled worker complete)
T4 (review→send loop): conservative tick persists structured drafts to
state_dir/worker-drafts.json; `warden worker drafts` lists them, `warden worker approve
<id> [--body …]` sends the reviewed draft as the reply + marks read + drops it. Escalated
plans persist no draft. Live-verified end-to-end.

T3 (visibility): `warden worker status` (pending drafts, triage count, last digest, timer
state); best-effort notify-send nudge in the tick when drafts are pending.

T5: wiki/playbooks/scheduled-worker.md (enable/disable, the approve loop, failure modes,
conservative-only posture) + SCOPE note.

WARDEN-WP-0021 finished: the conservative worker now runs on a systemd --user timer
(enabled, every 15 min), triages new inbox messages into drafts you approve with one
command, degrades gracefully, and stops with one command. 249 tests, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:24:10 +02:00
d0261ebb52 feat(WARDEN-WP-0020): conservative triage tier as the --execute default (Option A)
Per Bernd's call: the guardrails prevent security harm but not LLM content errors, so the
worker should triage + draft, not auto-send, until reply quality is proven (matches the
build-stage/recoverability posture).

run_conservative triages NEW messages into a reviewed digest (state_dir/worker-digest.md)
with drafted replies, posts ONE progress note, tracks seen message ids (schedule-safe
dedup), and sends NOTHING to other agents / marks nothing read. `warden worker run
--execute` now runs this conservative tier; `--full-auto` opts into the auto-send path.

Live-verified with the LLM brain on the real inbox: produced a high-quality draft reply to
a secrets-engine coordination message and correctly flagged the llm-connect custody request
as NEEDS YOU. Conservative mode is safe to schedule (T4). 244 tests, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 00:38:36 +02:00
f8ac55367c feat(WARDEN-WP-0020): T3 — guarded executor (worker now acts, not just plans)
HubClient gains writes (mark_read, send_reply, add_progress). execute_plan/execute_plans
run the safe, allowlisted actions autonomously: route_answer (reply with the computed
answer + auto mark-read), reply (LLM-drafted body), progress_note, mark_read. Escalated
plans and non-auto-executable kinds are left for a human; every action is metadata-only
(no secret value read/sent/logged).

Deliberate guardrail: propose_catalog_diff and any code/routing change is NOT auto-executed
even under full-auto — a bad catalog commit could misroute credentials, so it goes to human
review (recoverability over convenience). AUTO_EXECUTABLE is the messaging/hub tier only.

`warden worker run --execute` runs the executor (dry-run still default). 7 executor tests
(reply+mark, with/without body, escalated skip, catalog-diff-left-for-human, progress,
failure-without-crash); 243 pass, lint clean. First live --execute shakedown is the
operator's (staged rollout); T4 schedules it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:19:13 +02:00
859beed07f feat(WARDEN-WP-0020): T2 — llm-connect brain (autonomous worker now thinks)
llm-connect is operational (operator set OPENROUTER_API_KEY). Contract discovered from
the running service: POST /execute {"prompt":...} -> {"content":...}.

LlmConnectBrain embeds the fixed charter + the inbox message as untrusted data, calls
/execute, and parses a JSON action plan (_extract_json tolerates fences/prose), escalating
defensively on malformed/empty/transport errors. The build_plans guardrail still enforces
the allowlist + no-secret invariant on whatever the model returns — the LLM cannot widen
ops-warden's authority. `warden worker run --brain rule|llm` selects the planner.

Live-verified on the real inbox: the LLM brain planned a sensible reply+mark_read for a
secrets-engine coordination message and correctly escalated a secret-custody request as
out-of-lane — better classification than the deterministic RuleBrain.

6 new tests, 236 pass, lint clean. T3 (guarded executor) and T4 (scheduling) remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:10:28 +02:00
4287eccc80 feat(WARDEN-WP-0020): worker drafts real route answers in dry-run (T3 groundwork)
build_plans now computes the concrete routing answer for each route_answer action
in-process (reuses the catalog; read-only, no subprocess/network) and render_plans
shows it as a `draft:` line. The dry-run demonstrates the actual answer the executor
(T3) will send, not just an intent. RuleBrain stays the default; the llm-connect brain
(T2) is gated on llm-connect being operational + its /execute contract.

230 tests, lint clean. Live dry-run verified against the real inbox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 22:42:54 +02:00
211994ddbb feat(WARDEN-WP-0020): ops-warden coordination worker — T1 dry-run scaffold
Foundation for an autonomous worker that handles ops-warden's State Hub coordination
lane via llm-connect (Bernd's call: full-auto in-scope + scheduled, staged dry-run ->
manual -> scheduled). T1 is the llm-connect-independent, safe slice:

src/warden/worker.py — HubClient (read unread to_agent=ops-warden), Brain protocol,
deterministic RuleBrain (answers clear routing questions, escalates the rest),
PlannedAction/WorkerPlan model, guardrail allowlist + validate_action enforced
brain-agnostically (no-secret invariant + prod-config + off-allowlist all escalate),
render_plans dry-run output. `warden worker run --dry-run` (default); --execute refused
(exit 2) until the guarded executor (T3) lands.

Guardrails are load-bearing because full-auto has no human in the loop: message content
is untrusted data, the allowlist is enforced regardless of what the brain proposes.

Hard dependency flagged in the workplan: the brain is llm-connect, which needs its
provider key (OPENROUTER_API_KEY, deferred CCR-2026-0003) before it can run.

18 worker tests; 229 pass, lint clean. Live dry-run against the real hub verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 19:07:06 +02:00
bd335ec724 feat(WARDEN-WP-0019): route secret-exec lanes to secrets-engine (route-primary, proxy fallback)
secrets-engine (SECRETS-WP-0003) shipped a native secret-exec front door
(`secrets-engine route/exec`, decision e6381a56) and asked ops-warden to route to it.
Bernd's call: route-primary, proxy-fallback — surface the secrets-engine exec as the
primary path for owned lanes, keep `warden access --exec` as a transparent fallback.

T1 — RouteEntry gains exec_owner/exec_command/pointer_command (+ has_native_exec),
screened for secret material like the other handoff fields. whynot-design-npm-publish
points its native exec at secrets-engine. `warden access` renders Primary (secrets-engine
exec) + Fallback (warden proxy); route/access JSON gain the fields and a native-exec-aware
next_action. Tests added; 217 pass, lint clean.

T2 — credential-routing.md adds secrets-engine as the secret-exec owner (route primary,
proxy fallback); SCOPE adds secrets-engine to Related Repos and records the npm lane as
production-exercised (@whynot/design@0.4.0); playbook leads with secrets-engine exec and
fixes the fallback one-liner (--field NPM_AUTH_TOKEN, --no-policy) per whynot-design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:41:49 +02:00
e8bb469033 feat(WARDEN-WP-0018): activate whynot-design npm publish lane + resolvable flag
railiance-platform finished provisioning the whynot-design npm publish lane
(CCR-2026-0001, commit 8f617fc: active, readiness=ready, resolvable=true, positive
fetch + negative denial verified). First concrete warden access --fetch-resolvable
non-SSH lane — end-to-end proof of the WP-0014 conduit + WP-0017 discoverability.

T1 — catalog entry whynot-design-npm-publish (active, exec_capable) with the
owner-confirmed zero-placeholder handoff: path platform/workloads/coulomb/whynot-design/
npm-publish (the superseded whynot-design/whynot-design/... form is not used), field
NPM_AUTH_TOKEN, OIDC role whynot-design-workload-kv-read, policy + flex-auth ref. Added
wiki/playbooks/whynot-design-npm-publish.md.

T2 — RouteEntry.resolvable (active + exec_capable + no <…> placeholder), surfaced in
route/access --json; Catalog.find resolves an exact catalog-id first so
`warden access whynot-design-npm-publish` is deterministic. Tests added; fixed a
no-match test query that substring-collided (no ⊂ whynot). 213 pass, lint clean.

T3 — notified whynot-design (zero-placeholder command + resolvable gate + path
correction) and confirmed activation to railiance-platform. Sibling lanes stay draft
per their deferral.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 00:32:00 +02:00
46b340f45f feat(WARDEN-WP-0017): make the access front door discoverable (not SSH-only)
WP-0014 made ops-warden the operator access front door (warden access --fetch/--exec
proxies an exec_capable secret as the caller), but every discovery surface still told
the pre-WP-0014 "SSH certs only, pointer not key" story — so agents like whynot-design
never found the proxy and concluded they had to message ops-warden for a token value.

Messaging/discoverability only; the conduit security model is unchanged (no custody,
no broker).

T1 — CLI: `warden route` table warden column is now three-valued (issue/assist/route);
route + access JSON gain warden_role + exec_capable and a proxy-aware next_action;
`warden access` closing line leads with "ops-warden can fetch this for you as the
caller" for exec_capable lanes (route-only lanes keep "owner vends").

T2 — .claude/rules/credential-routing.md reframed (lead + routing table role column);
SCOPE one-liner + a second capability block for the access front door.

T3 — registered the State Hub capability "Operator access front door (caller-identity
fetch proxy)" (the hub had no ops-warden security capability at all); messaged
whynot-design the corrected `warden access "npm auth token" --fetch/--exec` path.

210 tests pass, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:02:46 +02:00
0b3486af9e fix(cli): bundle registry into wheel so installed warden works outside the repo
issue-core flagged the installed `warden` lacked the `route` subcommand. Two causes:

1. uv reused a cached wheel (version stayed 0.1.0) so the installed warden.cli was
   stale. Documented the cache-clean + --reinstall fix in ADHOC-2026-06-27.
2. Even rebuilt, route/access/policy were unusable outside a checkout because the
   routing catalog + posture descriptors live in registry/ at repo root, outside the
   package. Bundle registry/ into the wheel (hatch force-include -> warden/_registry)
   and add a packaged-data fallback in find_catalog_path / find_posture_path after the
   repo walk, so source runs still prefer the repo's registry/ (single source of truth).

Verified `warden route list` / `warden policy list` work from /tmp. 200 tests, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:40:14 +02:00
41a55c95b0 feat(WARDEN-WP-0015): T3 conformance checker + T4 dev-tier contract doubles
Finish the Workload Security Posture workplan (all five tasks done).

T3 — scripts/check_secret_posture_conformance.py: read-only checker that asserts
env-posture conformance (backend/unseal/real_values per tier) and evaluates the
secret-flow lattice via posture.can_deliver. Metadata-only manifest, no secret
values, exit 0/1/2. examples/posture-conformance.example.yaml as the reference.

T4 — src/warden/doubles.py: generalizes "fake bao" into materialize_doubles() —
hermetic, synthetic-only (synthetic- prefix) stand-ins for bao/key-cape honoring
each argv/stdout/exit contract, for fully offline dev/test access flows. Documented
as the sanctioned dev backend in WorkloadSecurityPosture.md R1.

T5 — INTENT/SCOPE/wiki aligned; canon landing in net-kingdom/info-tech-canon left
owner-driven (tracked via coordination messages).

16 new tests, 200 passing, ruff clean. Archived WP-0012/0014/0015 to
workplans/archived/ with 260627- prefix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:30:30 +02:00
0812d7303d feat(WARDEN-WP-0015): T2 — machine-readable posture descriptors + warden policy
Adds registry/policy/security-posture.yaml (Axis A env postures, Axis B
maturity levels M0-M3, dataclass_floor, lattice rule — no secret
material) and src/warden/posture.py: typed loader with validation
(unique/contiguous ranks, floor references known levels) and the pure
can_deliver() lattice helper (no-write-down: prod posture + workload
maturity >= secret required_maturity + dataclass floor). New `warden
policy list|show` read-only lookup mirroring `warden route`.
tests/test_posture.py covers load, the allow/deny lattice matrix,
validation rejections, and CLI. 184 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 18:10:54 +02:00
1c3d1b4d52 feat(WARDEN-WP-0014): T4 — key-cape login orchestration lane
Adds a lane: secret|login field to RouteEntry. The login lane is an
interactive auth bootstrap: it skips the caller-auth precheck (no token
yet — that's the point) and the secret-read gate (it establishes the
identity the gate needs), runs the owner's login command interactively
as the caller via inherited stdio, and rejects --exec. The token stays
in the caller's own store; warden never captures it (G2 holds). Audited
as action: login. key-cape-oidc-login populated as the reference login
entry. Advisory proxy hint updated now that T3 has shipped.

172 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:31:55 +02:00
6dfa69e310 feat(WARDEN-WP-0014): T3 — OpenBao proxy lane (--fetch / --exec)
Adds transparent, policy-gated, audited proxy of a non-SSH credential
through `warden access`, for exec_capable lanes. Three guardrails in code:

- G1 caller identity: runs the owner's tool with the caller's own env;
  warden injects no token of its own (caller_auth_present check).
- G2 transit-only: --fetch inherits stdout (never PIPE) so the value
  never enters warden's memory or any log; --exec injects into the child
  env only. Audit (access-audit.log) is metadata-only.
- G3 policy gate: check_fetch_policy runs before any fetch; with
  policy.enabled=false the proxy refuses unless --no-policy is given.

resolve_fetch_command refuses unresolved <…> placeholders rather than
guess owner-side names. New warden/proxy.py + policy.check_fetch_policy;
tests/test_proxy.py asserts all three guardrails. 168 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:26:03 +02:00
2c513864bc feat(WARDEN-WP-0014): T2 — warden access advisory front door
Adds `warden access <need> [--domain X] [--json]`: resolves a credential
need against the routing catalog and renders the structured handoff
(owner, auth method, path template, command skeleton, policy gate
status, proxy hint). SSH lane points at `warden sign`; routed lanes end
"warden advises, the owner vends". New pure warden/access.py module
(expand_handoff, policy_gate_status) reused by the T3 proxy lane. JSON
output is stable and secret-free. tests/test_access.py added.

157 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:13:51 +02:00
1f7970ad9b feat(WARDEN-WP-0014): T1 — structured handoff fields in routing catalog
Adds optional assist-layer fields (auth_method, path_template,
fetch_command, exec_capable, policy_ref) to RouteEntry, parsed and
secret-screened in catalog.py. Handoff fields are templates/pointers
only — _assert_no_secret_material rejects known token prefixes and
high-entropy runs, and exec_capable requires a fetch_command. The
openbao-api-key entry is populated as the reference example (covers the
coulomb_social npm shape).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:00:56 +02:00
1237cc767b Complete WARDEN-WP-0012 routing scenario playbooks
Add platform-secret playbooks for issue-core ingestion, OpenRouter llm-connect,
object-storage STS, and database dynamic credentials. Extend the routing catalog
with draft entries and implement `warden route list --stale` for quarterly drift
review. Document the review cadence in AccessRouting and mark the workplan finished.
2026-06-25 10:27:23 +02:00
ac2efa1262 feat(WP-0011): warden route lookup CLI over the pointer catalog
Add a read-only `warden route` command group (list/show/find) that reads
registry/routing/catalog.yaml and tells a worker which subsystem owns a need
and which wiki/canon doc to follow. ops-warden still executes exactly one lane
(SSH); routed entries return a pointer and never call any subsystem.

- src/warden/routing/: models.py + catalog.py loader; enforces the
  no-double-source rule (non-SSH entries with steps/cert_command fail validation),
  dup-id and schema checks.
- route list (active-only unless --all, --tag), route show (SSH appends steps +
  cert pattern; routed ends with "next action on <owner> — see <wiki_ref>"),
  route find (keyword ranking, --json).
- tests/test_routing.py: load/validation, find ranking, CLI JSON shapes, plus a
  drift guard (every wiki_ref anchor resolves; every entry has a reviewed date).
- Docs: wiki/AccessRouting.md CLI section, README quick reference, SCOPE A3 -> A4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:07:13 +02:00
8e9383a33a feat: opt-in flex-auth policy gate and OpenBao verify (WP-0007)
Add policy.py client that calls flex-auth /v1/check before sign/issue when
policy.enabled is true. Record policy_decision_id in signatures.log. Default
off preserves existing inventory-only behavior. Document production OpenBao
health probe and update config/wiki references.
2026-06-17 08:37:14 +02:00
f3547acd0b feat(warden): WARDEN-WP-0003 — test coverage, permissions, status --state-dir
- File permissions: os.chmod(cert, 0o600) after every sign in LocalCA and
  VaultCA; chmod(privkey, 0o600) and chmod(pubkey, 0o644) after generate_keypair
- Scorecard: add check_file_permissions() that flags world/group-readable
  cert and key files; run_scorecard now returns 6 checks
- warden status --state-dir: bypasses config loading entirely for operators
  who have a cert but no warden.yaml installed
- tests/test_vault.py: 11 VaultCA unit tests covering success, HTTP 403,
  RequestError, missing token, missing role, missing pubkey, TTL enforcement,
  eviction, signatures log, and cert mode 600
- tests/test_ca.py: generate_keypair tests (paths, args, overwrite, error,
  permissions) and cert mode 600 assertion after sign
- tests/test_scorecard.py: file_permissions check tests (pass, fail cert,
  fail keys dir); scorecard count updated to 6
- tests/test_cli.py: covers sign, issue, status, scorecard, inventory, log,
  cleanup commands using CliRunner and tmp config/inventory files
- tests/test_integration.py: @pytest.mark.integration tests against real
  ssh-keygen; excluded from default suite via pyproject addopts
- pyproject.toml: addopts = "-m 'not integration'", integration marker declared

All 100 unit tests pass; 3 integration tests pass; ruff clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:05:38 +02:00
9857ed1424 feat(warden): implement WARDEN-WP-0002 correctness and operational completeness
T1 — TTL max enforcement:
  - models.py: MAX_TTL_HOURS policy constant
  - ca.py: _enforce_ttl() raises CAError when spec.ttl_hours > type max
  - Called at top of LocalCA.sign() and VaultCA.sign()
  - scorecard.py: check_ttl_policy() — flags certs with issued TTL > type max
  - run_scorecard() now returns 5 checks

T2 — Stale cert cleanup:
  - ca.py: _evict_cert() removes existing cert before writing new one (no accumulation)
  - cli.py: warden cleanup [actor] [--dry-run] command
  - check_no_stale_certs detail suggests 'warden cleanup' when stale certs found

T3 — Outgoing signatures log:
  - ca.py: _append_signature_log() writes JSONL to state_dir/signatures.log
  - Called after every successful sign() in LocalCA and VaultCA
  - cli.py: warden log [actor] [--last N] [--json] command
  - parse_cert_metadata now also returns valid_from (needed for TTL policy check)

61 tests passing, ruff clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:53:10 +02:00
42ca370085 feat(bootstrap): WARDEN-WP-0001 initial implementation — 42 tests passing
- LocalCA: ssh-keygen -s signing, keypair generation, cert parsing via ssh-keygen -L
- VaultCA: Vault SSH engine backend via httpx
- Inventory: YAML actor registry with ActorType, principals, TTL policy
- Scorecard: four cert-side compliance checks (prefixes, principals, no expired/stale)
- CLI: sign (cert_command interface), issue, status, scorecard, inventory subcommands
- ops-ssh-wrapper: acquire cert and exec SSH command
- Fix: principal parser stops at section headers containing ':' (Critical Options, Extensions)
- Move WARDEN-WP-0001 workplan from ops-bridge; register repo in state-hub (74df727e)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 13:27:49 +02:00
5ae6b988aa Initial Commit 2026-03-28 00:45:43 +00:00