LANE-KIND accepts an attended step on a login lane, or on an attended-change
lane whose session_lane resolves to an active, reviewed login lane.
keycape-attended-case-change -> session_lane key-cape-oidc-login (OW-1.3-C1).
consumer-owner-deployment and apps-consumer-handover -> pending-owner-confirmation:
no catalog login lane authenticates a railiance-apps operator session.
apps-consumer-handover risk: high (KERNEL-2, change effect).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 903097@bnt-lap001
Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
nk48 1.3 owner review (owner-review-ops-warden.json):
- KERNEL-2: Executor.admit parses catalog_source and resolves descriptor.lane;
refuses a missing, non-active, undelegated or ungraded lane, an owner outside
owner_repo, and a mint/change effect on a lane not graded risk high.
catalog-routing-acceptance still covers the whole catalog digest.
- KERNEL-3: dispatch mode is read from the type's follow_through step
(warden_executes/attended). The step must declare both booleans and a lane
and agree with the descriptor's lane and owner. Attended: human actor,
WARDEN_AGENT_ID unset, parent-session revocation scope, caller lane equal to
the step lane. Owner: native delegation, owner_repo == descriptor.owner, no
Warden-executed lane and no Warden-brokered mint.
- CATALOG-1: draft entries keycape-attended-case-change (OW-1.3-C1),
platform-secret-delivery, consumer-owner-deployment, apps-consumer-handover.
Not promoted; promotion waits for owner confirmation.
- Fixture catalogs and type steps in tests; published routing_lanes coverage
and the workload-join count follow the four new not-applicable lanes.
PARAMETER_PINS untouched (PIN-1 waits for founder acknowledgment).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 903097@bnt-lap001
Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
Durably record the original run Invocation, pins, adapter source and
tenant/realm provenance before dispatch; expose Journal.original_intent and
Adapter.reconcile_original(ReconcileContext) for NK-WP-0048-T15. Missing or
mismatched intent refuses and leaves the step uncertain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 903097@bnt-lap001
Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
reuse-surface-hub-write-token does not select a caller. The data plane
uses the ambient token, which can be the coding-agent role denied by
agent-high-risk-boundary while an attended platform-admin lookup still
shows read. Advisory and proxy output now report that entity, role, and
policies without the token. CCR-2026-0005 still has no OIDC binding for
workload-kv-read-reuse-surface-runtime.
Assistant: grok
Assistant-Session: 01a13b2d-0bc1-7a91-ab1d-325bcfb09bf0
Reject non-null wrap_info (a wrapping token survives token revoke -self),
non-empty data, empty client_token, and leading whitespace before the OIDC
stderr prompt. Fail-closed and hand-off tests now run through the CLI and
assert sentinel absence from both audit files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
b4c1d39 dropped login output from the attended-login fail-closed condition,
so any stdout/stderr was ignored once the helper was populated (RPF-WP-0017
criterion 4 regression). Login output stays captured and never forwarded and
is now accepted only when empty or allowlisted by structure: one bao login
-format=json object on stdout, the OpenBao OIDC prompt on stderr. Anything
else self-revokes, cleans up, and exits 10/login_failed without retry.
Adds populated-helper tests for unexpected stdout, stderr, malformed and
multi-object JSON, and unexpected keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
- fluid-telegram-operator: pointer-only draft entry and playbook from ops-mason's
MASON-WP-0005-T06 proposal; activation waits for the owner-run member and
non-member checks and the salt create-once probe.
- audit-core-senders: tenant-engine recorded as a verified exact-path consumer
projection (MASON-WP-0007-T06). The registry lane itself stays draft.
- warden case plan: {param:env} format spec (net-kingdom 850907a), other specs
and conversions refused; prints case-ref <case_id>@sha256:<file digest>.
- Workload-join unknown count 21 -> 22 (test and pep-stance.yaml).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 427672@bnt-lap001
Assistant-Session: a0a2367e-4697-440c-a4e5-9713c009f6d8
warden case plan resolves an accepted, materialized SecurityCase against its
acknowledged type: warden, attended and owner steps, type defaults, conditions,
rendered entry points and verification. Executes nothing; refuses unmaterialized
cases, missing or unreviewed lanes and secret or type-fixed parameters.
T01 closes on net-kingdom's R8 option 1 and the founder's acknowledgement of
the type (memo r4). WP-0043-T02 closes on railiance-platform b77cb41.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 427672@bnt-lap001
Assistant-Session: a0a2367e-4697-440c-a4e5-9713c009f6d8
Mechanical follow-through check (lane, warden_executes, value_handling,
attended justification, read-free verification, rollback coverage). The
revised type passes schema; lanes identity-provisioner-groups and
keycape-client-secret-generate are not yet in the catalog (gate T02).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 327203@bnt-lap001
Assistant-Session: 55028ae3-4914-4bd1-8f5c-815f3527aef0
Configuration intent (policies, auth roles) routes to the attended
openbao-platform-admin-login lane and value provisioning to openbao-api-key
custody; both fail closed when their front door is missing. The survey lane
now points at ops-mason's read-only ops-mason-survey grant (MASON-IN-0004).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 327285@bnt-lap001
Assistant-Session: d6532cd1-7cd5-417b-baac-b841c9103a6b
check_fetch_policy now posts only to policy.fetch_flex_auth_url (access-engine
pin flex-auth-ops-warden-fetch, workstation 127.0.0.1:19091). Signing keeps
policy.flex_auth_url. Unset or unreachable fetch evaluator fails closed with a
message naming it; no fallback to the signing pin or to allow. warden access
advisory output names the fetch evaluator. Recorded under WARDEN-WP-0039-T03.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3775@bnt-lap001
Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3
`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
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>
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>
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>
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>
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.
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.
- 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>
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.
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.
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.
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>
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>
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>
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>
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>