Compare commits

...
Sign in to create a new pull request.

454 commits

Author SHA1 Message Date
e6252bc0e4 NK-WP-0056-T21 done: request phase presents the approval through informed-decision
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 17s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Successful in 24s
admission_memo composes the present_existing envelope (admission YAML as
packet, three required highlights, intent = binding without actor, key
nk-bind-executor.<approval_id>); InformedDecision.submit posts it with the
projected intake token and checks the receipt. Exit 7 when the memo is
refused (approval stays usable). Envelope validated by informed-decision's
own validate_submission with the present_existing profile. Service-identity
declaration notes key-cape's prepared approval:read. 61 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-11-01 00:39:17 +01:00
45feaa13cc NK-WP-0056-T20 done: oidc-service-client 0.2 with the owner reviews; memo r2
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 24s
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Identity provider journey acceptance / provider (push) Successful in 1m2s
ESO role audience + explicit max TTL and ESO policy self-lifecycle (ops-mason
R3/R4, railiance-platform R3); consumer role renamed to differ from its policy
(railiance-platform R4); default SA and external-secrets/openbao namespaces
refused (ops-mason R6); consumer-cannot-read-delivered-secret verification
(ops-warden R7, ops-mason R7); excess scope invalid_profile_usage (key-cape
R3); attended rotation, broker admission pending, one rollout per case;
catalog fields (ops-warden R6). First case consumer_owner_repo
rapp-bounded-executor. CCR-2026-0060/0061 names checked by test. Memo r2
validated against informed-decision intake and State Hub dispatcher.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-11-01 00:34:17 +01:00
1e17b39212 State Hub ids written back (NK-WP-0056-T20, T21)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 23:22:04 +01:00
620f558f18 NK-WP-0056-T18 done (image c67cb044 pinned); T20 type r2 and T21 memo submission from owner reviews
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 23:20:22 +01:00
7bd6033b16 NK-WP-0056-T18: accept audit-core 202/200, distinct 409; progress note
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 8s
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Successful in 1m10s
audit-core review (AUDIT-WP-0018-T02): a first accept is 202 and a duplicate
200; the client accepted only 200/201, so the executor would have stopped
with audit_unavailable after consuming its approval. 409 (run id reused with
a different body) is now a distinct, never-retried defect. 58 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 23:04:44 +01:00
b71f509ce0 NK-WP-0056-T18 (part): executor's own approval client and request phase
KeyCapeClientCredentials: client_secret_basic exchange, one scope per token,
public Host for the in-cluster URL, no redirects, bounded response checks.
ApprovalEngine takes a per-scope token provider (create/read/consume) and
can create. cli.py --request files the approval for one admission as
service:nk-bind-executor (principal = first approver's subject, target
admission id+digest, validity = window, human_control, required_count =
approvers) and verifies the created object. The client secret comes through
its own OpenBao lane (oidc-service-client case), read once, login revoked at
once, kept in memory only. Refusals before any call: other executor,
unmapped approver, closed window, shared role. 56 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 23:02:19 +01:00
5d5f15df55 NK-WP-0056-T14 done: SecurityCaseType oidc-service-client 0.1 (proposed)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Confidential client_credentials service clients: one audience, closed
resource scopes, fixed service subject and tenant, at most 15m; secret minted
in custody, read only by KeyCape (ESO) and the consumer's own bound
Kubernetes-auth role (no Secret copy). First case nk-bind-executor-approval
(approval-engine create/read/consume as service:nk-bind-executor). Capability
net-kingdom.keycape-service-client-registration; keycape-client.py renders
service clients in key-cape's shape (adoption of secrets-engine-approval
reproduces key-cape's record). Acknowledgement memo r1 validated against
informed-decision intake and State Hub dispatcher; hub decision f8044f81.
Test fixture's synthetic signing key is now computed, not a literal (secret
scan generic-api-key).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 21:30:21 +01:00
8cecf8949a State Hub ids written back (NK-WP-0056-T18, T19)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 21:22:07 +01:00
7b0a885137 NK-WP-0056: split T14 into T14 (service-client case type), T18 (executor approval client), T19 (real-service exercise)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder decision 2026-10-31. T14 in progress. T06 depends on T18 and T19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 21:20:40 +01:00
b76456134c Decision services alignment review: approval-engine, informed-decision, State Hub decisions
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-requested review (2026-10-31), driven by NK-WP-0056-T17. Findings:
informed-decision couples presentation with creation (actor informed-decision),
one weight for every decision, two unlinked decision stores, executor-filed
approvals invisible. Proposal: present-existing-approval contract, decision
classes C0-C3 (executed means C2), hub decisions resolved by memo write-back;
first slice S1-S6 with owners.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 21:15:52 +01:00
96c859f77c NK-WP-0056-T17 done: founder aal2 approval path does not exist yet
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
informed-decision is the live approver surface but its intake always creates
the approval with actor informed-decision and refuses a pinned approval_id;
approval-engine lets only the actor create; the executor and BEA require
actor service:nk-bind-executor. Options A (INFD presents an existing
approval, recommended), B (relax actor binding), C (founder CLI approver).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:57:35 +01:00
f8223452a0 State Hub ids written back (NK-WP-0056-T14..T17)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:56:23 +01:00
2058036650 NK-WP-0056: split T06 prerequisites into T14-T17 (founder decision 2026-10-31)
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
T14 executor approval identity and request phase (KeyCape client, OpenBao
lane, create-then-consume), T15 audit sender (schema, credential lane,
registry), T16 owner ingress, T17 founder aal2 approval path. T06 depends on
all four.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:54:55 +01:00
a020758448 NK-WP-0056-T10 done: unattended live journey passes at aal2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Job sso/bounded-executor-journey-20261031-194719 (image 71da4a51, journey-read
role only, effects false) passed all eight steps to a signature-verified aal2
ID token with no person in the journey. Fail-closed half: synthetic broken
svc-authelia bind fails at authelia_first_factor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:48:42 +01:00
d685df4d56 NK-WP-0056-T10: startup race fixed; image 71da4a51 pinned
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:35:29 +01:00
e830238746 NK-WP-0056-T10: examples re-pinned at 15c9cdb
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 6s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Successful in 1m5s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:33:28 +01:00
15c9cdbef8 NK-WP-0056-T10: wait for OpenBao reachability before the first login
First live journey Job was refused at setup (URLError). netcheck showed every
connection refused at 0 s and open from 3 s: k3s applies NetworkPolicy to a
new pod a moment late. Both CLI modes now poll OpenBao's unauthenticated
sys/health for up to 30 s before logging in; only connection errors retry.
48 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 20:33:17 +01:00
110eb63058 NK-WP-0056-T10: journey-only image 550bb9d0 pinned in rapp
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:59:35 +01:00
9a81123ade NK-WP-0056-T10: examples re-pinned at abed436
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 5s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 18s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:57:16 +01:00
abed4360d4 NK-WP-0056-T10: journey-only executor mode; fail-closed proof on the T08 stack
cli.py --journey-only: read-only, no admission; journey-read role only
(CCR-2026-0059), no Kubernetes/directory client, no approval or audit call;
exit 0 pass / 1 fail closed / 4 refused. Shared operations.journey_from_kv
with the admission check. Synthetic proof: healthy pass, broken svc-authelia
bind fails closed at authelia_first_factor, restored pass, journey token
cannot read the bind; all_met, no values. 45 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:56:52 +01:00
ef6284c371 NK-WP-0056-T10: executor lanes set up and verified (founder-run receipts)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
rapp deploy (sso/nk-bind-executor uid bd15569c, role = ceiling), bindings
confirmed (railiance-platform b4bafb0), executor-lanes.py setup
lanes_prepared and verify lanes_verified. Left for T10: a journey-only,
read-only run mode and its unattended live run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:47:29 +01:00
12c9b2df43 NK-WP-0056-T10: set cas_required by metadata write (platform-admin has no patch)
First attended setup stopped at cas_required: platform-admin holds
create/read/update/delete/list on platform/*, not patch, so 'kv metadata
patch' was denied. Policies were already applied; nothing after. Now a
metadata write naming only cas_required (KV v2 keeps unnamed fields; checked
on the pinned OpenBao image), guarded by a before/after comparison of
max_versions, delete_version_after and custom_metadata. Receipt of the
stopped attempt kept as evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:42:40 +01:00
634b3a41df NK-WP-0056-T10: attended OpenBao setup and proof for the executor lanes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
tools/service-binds/executor-lanes.py (bind-lane.py shape): under both
sha256-bound, approved, binding-confirmed CCRs (0058 write, 0059 journey) and
an attended platform-admin, apply both policies, set cas_required on the
svc-authelia key, create the standing journey-read role, refuse if the
window-scoped write role exists, prove the agent fence; verify proves the
journey token reads only its path (<=15m) and denies wrong SA, wrong audience
and write-role login. Value-free receipts. 12 tests on a fake OpenBao (real
helpers), in make bounded-executor-test. Founder runbook:
sso-mfa/k8s/bounded-executor/T10-LANES-RUNBOOK.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:15:46 +01:00
37f4d4f306 NK-WP-0056-T10: image 716dccef pinned (CI tests passed); CCR-2026-0059 approved
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:08:59 +01:00
4815c58dce NK-WP-0056-T10: examples re-pinned at 1cdc18f; gaps fixed, what is left
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 6s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 38s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:02:09 +01:00
1cdc18f75f NK-WP-0056-T10: one OpenBao login per lane; renew before the 15m lease ends
The executor logs in separately for the bind write (CCR-2026-0058) and the
journey read (CCR-2026-0059) and refuses one role for both; every login is
revoked at exit, also when setup fails. Clients log in again (re-reading the
rotating projected JWT) before the lease ends, since a run's budget (20m)
outlasts a 15m token. Synthetic pilot with per-lane tokens: completed and
reverted, all_met.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 19:01:44 +01:00
74599a583d NK-WP-0056-T10: prerequisites; CCR-2026-0059 drafted; two executor gaps recorded
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:49:00 +01:00
b39fc6348f NK-WP-0056-T13: rapp-bounded-executor pushed to Forgejo (rebased onto stub)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:41:27 +01:00
387f8b1c8b NK-WP-0056: sso ingress for the bounded executor; README and handoff follow-up
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 6s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 37s
netpol-sso.yaml admits only executor pods to LLDAP 17170, KeyCape 8080 and
Authelia 9091 (never LDAP 3890); test_netpol.py pins it. Prepared, not
applied. README reflects T13. Live reads: OpenBao open to all namespaces;
approval-engine and audit-core need pod-scoped rules (requested: hub messages
39a3bffa, deea5bf8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:27:30 +01:00
cd0ce905a2 NK-WP-0056-T13: name the rapp pin commit
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:21:03 +01:00
7a7748e869 NK-WP-0056-T13 done: bounded-executor image pinned in rapp
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:19:27 +01:00
4ebbe2ef14 NK-WP-0056-T13: founder server dry run passed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:15:36 +01:00
1ab019e339 NK-WP-0056-T13 (part): rapp-bounded-executor reshaped to one Job per admission
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Local rapp repo (c9184ba): ceiling copy, egress-only NetworkPolicy, per-admission
Job renderer with refusals, 12 tests, rmgr validate ok. Limited-identity server
dry run admits SA and NetworkPolicy; RBAC and Job dry run is founder-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 18:11:31 +01:00
ba9d266b7e NK-WP-0056-T13 (part): executor CLI, clients, Containerfile, image workflow
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 23s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 32s
CLI entry point and approval-engine/audit-core clients; events carry
occurred_at (audit-core envelope); hash-locked image with pinned lldap
stage builds locally; image workflow and executor tests in CI; pilot
example pins the executed files. rapp reshaping still open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 16:51:07 +01:00
fe18096aa3 State Hub ids written back
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 16:27:33 +01:00
db56d8468e NK-WP-0056-T12 done: pilot operations; synthetic rotation and revert pass
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
operations.py: OpenBao KV v2 CAS, LLDAP self-rotation (forward and revert),
ExternalSecret force-sync and status, rollout restart (RolloutFailed is a
definite failure), pilot checks. synthetic_pilot.py on the T08 stack plus
OpenBao 2.5.4 with CCR-2026-0058's policy: rotation completed; a one-off wrong
delivery reverted in declared order with the earlier value restored.

Runner: revert steps run in declared order (BEA-10), definite failures revert,
unknown outcomes stop, receipts carry error types. Found and corrected (in
railiance-platform) CCR-2026-0058's required_parameters refusing reads.
Packaging moved to new T13. 29 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 16:24:17 +01:00
9b7701a32a NK-WP-0056-T11: test count
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 16:01:17 +01:00
d1cf754222 NK-WP-0056-T11 done: bounded executor core runner
Consumes one admission once through the approval-engine carrier after an
effect-free preflight (validity, source pins, trust, window vs budget,
binding digest, actor, human aal2 approvers), emits run.started, runs steps
with settle-timed checks, reverts on failure with per-revert checks, retries
within attempts, stops on unknown outcomes, honours budget and kill switch,
and emits one value-free receipt (PDP not-evaluated). 22 tests with fakes.
BEA-9 extended to revert steps; examples regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 16:00:52 +01:00
3b1abc4054 NK-WP-0056: State Hub ids for T11 and T12
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 15:44:07 +01:00
cdf0d9bf25 NK-WP-0056: founder decisions folded in; runtime split into T11 and T12
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Window-scoped write role (CCR-2026-0058 approved as amended), bind
self-rotation (pilot step is OPERATE on the one bind; svc-executor-pw
dropped), pilot without the PDP gate (not-evaluated), started event plus one
receipt per run, rapp Job in sso, owner workplans approved. New tasks: T11
core runner, T12 pilot operations and packaging; T06 depends on T12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 15:42:30 +01:00
c9ab01e23e NK-WP-0056-T02: note all misattributed hub replies
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:35:59 +01:00
9d1fc3f55b Link reset transition checkpoints to State Hub tasks
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: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 14:34:44 +01:00
522b5d08ed NK-WP-0056-T02 done: all seven owners answered; founder decisions collected
approval-engine and audit-core answers recorded; open founder decisions listed
in the answer table; misattributed hub messages noted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:34:00 +01:00
aeb7fd81f4 NK-WP-0056-T02: record access-engine, secrets-engine and railiance-platform answers; LLDAP self-rotation proven
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
A lldap_strict_readonly account changes its own password with its own token
on the pinned LLDAP and cannot change another account's, so the pilot needs
no lldap_password_manager account (secrets-engine Q2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:30:33 +01:00
c07e6ebe34 Prepare guarded reset-role transition and native recovery proof
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 14:26:17 +01:00
2387409d08 NK-WP-0056-T03 done: executor identity and authority ceiling, in source
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Executor credentials declared (planned) with executor_subject; service-identity
gains convention executor (SI-15: approved-only grants, no person, not
interactive) and --admissions, checking that an admission's ceiling lies inside
the executor's declared grants (pilot covered). RBAC in sso-mfa/k8s/bounded-executor
(not applied): ServiceAccount without automount, a Role limited to the authelia
Deployment and ExternalSecret, no read of credential objects; tests tie Role,
declaration and pilot ceiling. The pilot example's delivery check now reads the
ExternalSecret status. railiance-apps and glas-harness answers recorded (T02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:21:04 +01:00
43a7890759 NK-WP-0056-T02: blocking reason is the pending owner answers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:07:52 +01:00
8cb416e82a NK-WP-0056-T02: founder chose the executor runtime owner; owner requests sent
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
net-kingdom writes the executor, a new rapp-* package deploys it (hub
decision 6d1adce9). Requests to approval-engine, access-engine,
secrets-engine, railiance-platform, audit-core, glas-harness and
railiance-apps recorded with an answer table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 14:06:31 +01:00
b5cb1520d8 Complete attended reset-role observation with verified receipt
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 14:00:35 +01:00
989d5d4f5d NK-WP-0056-T01 done: Bounded Execution Admission v0.1 (proposed)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Standard, schema and validator for the founder's admission of one bounded
executor run: exact plan by digest, window by risk class, SI MODE @ SCOPE
ceiling with least-privilege coverage, blast radius by consumer, named
source-pinned checks, revert per mutating step, budget, single use, and the
NK-WP-0040 receipt. check-approval verifies a carrier record (Approval
Engine or founder SSH signature): aal2 human approver, not the executor,
consumed at most once in the window. Examples express the bind split;
step 10 is refused as human-intrinsic. 22 tests; make targets; tool index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 13:55:18 +01:00
27b4ea6933 Prepare attended read-only classification for accepted reset subject
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 13:54:51 +01:00
b8d3dacc41 Record native reset rehearsal task identifier
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 13:42:37 +01:00
a192c9abfc Record native reset authority rehearsal and remaining admission scope
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 13:40:07 +01:00
661ab6a52e NK-WP-0056-T09 done: syn-journey live, synthetic journey passes at aal2
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Founder-run under CCR-2026-0057: KeyCape client registered, lane.py setup
(privacyIDEA-generated key, KV v1) and verify passed; the live journey
reached aal2 with otp against kc.coulomb.social. Receipts, findings and the
one teardown of a partial run in docs/evidence. syn-journey declared active.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 13:33:31 +01:00
488f5f4ece Record projected reset followthrough task identifiers
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: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 13:30:58 +01:00
05fa8f67f6 Track post-split password reset authority and consumer return
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: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 13:29:30 +01:00
55fe8303f3 NK-WP-0056-T09: enroll with a privacyIDEA-generated key, write KV last
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 15s
Third founder setup run stopped at /token/init. Reproduced on the pinned
privacyIDEA 3.12.2: an admin policy with action '*' enables
totp_force_server_generate, so a supplied otpkey is silently replaced and
verify_enrollment then fails with ERR905. setup now enrolls with genkey=1,
takes the seed from the enrollment response (memory only), confirms with a
code, and writes PASSWORD and TOTP_SEED to KV (CAS=0) last. Failed receipts
kept as evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 13:23:47 +01:00
79e533b548 NK-WP-0056-T09: lane looks up the privacyIDEA token by serial; name login failures
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 16s
The first founder setup run stopped in phase absent (no change made):
listing tokens by a user that does not resolve yet is an error in
privacyIDEA. Ownership is now read from the token found by serial, and
pi-admin login, privacyIDEA requests and the LLDAP admin login refuse with
their own reasons. Failed receipt kept as 00-setup-refused-absent.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 13:01:42 +01:00
d654f8a6ec NK-WP-0056-T09: SecurityCase nk-synthetic-journey accepted by the founder
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 12:51:09 +01:00
aedf43860e NK-WP-0056-T09: route the human gate (decision 91323969)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 12:44:24 +01:00
ddde021bd0 NK-WP-0056-T09: prepare synthetic journey identity for founder run
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 16s
lane.py setup/verify/teardown (attended; CCR-2026-0057 sha256-bound):
generates password and TOTP seed in memory, KV CAS=0 with coding agents
fenced, LLDAP user syn-journey with no groups, privacyIDEA TOTP token with
enrollment confirmation, live journey as proof. SecurityCase for the public
KeyCape client nk-synthetic-journey; syn-journey declared SERVICE (planned);
proposed syn-<check> naming row in the SI standard; founder runbook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 12:42:36 +01:00
22e50ef04b NK-WP-0056-T08 done: synthetic identity stack on pinned images; broken binds fail closed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 14s
stack.py starts LLDAP, privacyIDEA (pi_fixture.py in its image), Authelia
and KeyCape from the repository manifests on a throwaway 127.0.0.1-only
network and runs the journey: healthy passes at aal2; svc-authelia fails
closed at first factor, svc-keycape at the token exchange, svc-privacyidea
at the OTP check - the latter only after privacyIDEA's resolver cache
(CACHE_TIMEOUT 120, as live) expired. Each scenario passes again after
restore. Runner: corrected bind-to-step mapping, host_map_only,
min_time_step for privacyIDEA's TOTP replay guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 12:27:01 +01:00
0409c7b375 NK-WP-0056-T05 done: journey runner green in identity-provider journey acceptance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 11:54:46 +01:00
72ff31b497 NK-WP-0056: hub task ids for T08-T10
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 11:52:09 +01:00
0b0c6c9628 NK-WP-0056-T05: synthetic MFA journey runner; split T05 into T05/T08/T09/T10
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 16s
Stdlib runner that signs a synthetic user in through KeyCape (PKCE, aal2),
Authelia first factor and the privacyIDEA-backed OTP challenge, verifies the
ID token (RS256 via JWKS, assurance aal2/otp) and writes a value-free receipt
naming the first failed step and the bind on it. 26 tests with per-bind fakes
run in the identity-provider journey acceptance workflow.

Founder decision 2026-10-31: real pinned images for the synthetic stack (T08),
synthetic user and seed in OpenBao (T09), unattended live run (T10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 994874@bnt-lap001
Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
2026-10-31 11:50:30 +01:00
ad7d1cdf49 NK-WP-0056-T04 done: svc-privacyidea written through from OpenBao; all four binds in platform custody
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-run under CCR-2026-0055: setup, verify and realm_bootstrap.py
--rotate-bind --from-openbao with the admin MFA proof. The first apply's check
got a code that was not admin's, so both MFA prompts now name the user.
Custody re-sealed after setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 11:19:21 +01:00
c28651e55b NK-WP-0056-T04: svc-privacyidea lane in OpenBao custody with attended write-through
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
bind-lane svc-privacyidea (CCR-2026-0055) has no workload reader. Setup and
verify prove the value is current by an LLDAP sign-in as svc-privacyidea, fence
coding agents, write KV with CAS=0 and refuse any workload role.
realm_bootstrap.py --rotate-bind rewrites only BINDPW on the established
service bind, --from-openbao reads it in memory under the attended
platform-admin session, and an applied rotation requires --verify-user.
Lane tests: 20; realm_bootstrap tests: 102.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 11:06:53 +01:00
12834b6e8a NK-WP-0056-T04: svc-privacyidea design proposal (OpenBao custody, attended write-through)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 11:00:04 +01:00
a65673985c NK-WP-0056-T04: svc-keycape bind delivered from OpenBao; bindPW cleared from keycape-config
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-run under CCR-2026-0054: setup, ESO apply, verify, rollout of KeyCape
main-e2b5fa1 with KEYCAPE_LLDAP_BIND_PW, clear-password, restart, env-only verify;
admin signed in with OTP before and after the clear. Custody re-sealed after setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 10:52:05 +01:00
6c7c37aa70 NK-WP-0056-T04: svc-keycape bind lane (source only, CCR-2026-0054)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
KeyCape takes KEYCAPE_LLDAP_BIND_PW (key-cape KEY-WP-0046). ESO creates the
dedicated Secret keycape-lldap-bind. bind-lane setup reads the current bindPW
from keycape-config and requires the svc-keycape bindDN; verify compares the
delivered Secret, and the config value while it is present.
directory-bind-rollout gains clear-password, which runs only with the service
bind and the delivered env (switch restores). deployment.yaml pins image
main-e2b5fa1 and adds the env. Nothing is applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 10:31:30 +01:00
6aae759043 NK-WP-0056-T04: svc-keycape delivery design proposal (KeyCape env override, dedicated ESO Secret)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 10:21:30 +01:00
76573deea2 NK-WP-0056-T04: record svc-authelia lane live and verified
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 10:11:22 +01:00
7f37e2521f NK-WP-0056-T04: svc-authelia bind delivered from OpenBao; setup and verify receipts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Founder-run under CCR-2026-0053: setup (custody equal to live, KV v1 CAS=0),
ESO apply, verify and Authelia restart passed; admin signed in with OTP.
Custody copy re-sealed after setup (same recipient, contents unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 10:11:07 +01:00
034634ac5f NK-WP-0056-T04: svc-authelia bind lane (source only, CCR-2026-0053)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
bind-lane.py gains per-lane Secret keys. Authelia keeps its bind user in
authelia-config, so setup refuses unless the live configuration binds
uid=svc-authelia, and only the ldap_password key is decoded. The ESO manifest
merges ldap_password alone into authelia-secrets. The tests are parametrized
per lane (12). tools/service-binds is now listed in security-tools.json, which
fixes the index test that failed after the pilot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 09:20:53 +01:00
436c5fe585 NK-WP-0054-T05: post-split restore drill passed as admin; SI-X1 retired
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
All five NK-WP-0053 stages passed on generation 20261031T034845Z-a13dc92e2c61,
the first capture after the 2026-10-30 bind split, operated by admin. Uncounted
attempts are kept with their causes. The drill document gains the OpenBao
platform-admin login that stage 1 silently requires. The LLDAP declaration moves
ld-admin and the svc-* binds to active and admin to human-only; SI-X1 is closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 896183@bnt-lap001
Assistant-Session: 3a18c01a-3205-4d02-94db-9efff25649a2
2026-10-31 08:31:17 +01:00
87e57aa710 NK-WP-0056-T04: svc-provisioner bind delivered from OpenBao; setup and verify receipts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 23:19:10 +01:00
985cfc37de NK-WP-0056-T04: bind ESO sets explicit target metadata so the last-applied annotation is not copied
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 23:16:21 +01:00
57369779e2 NK-WP-0056-T04: bind-lane requires a confirmed Kubernetes binding, as the CCR apply gate does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 23:05:26 +01:00
162b65efd8 NK-WP-0056-T04: bind-lane accepts approvals recorded by credential-change.py approve
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 23:00:48 +01:00
e0719ff663 NK-WP-0056-T04: bind-lane test reads the CCR as YAML
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 23:00:31 +01:00
c6df34c4de NK-WP-0056-T04: svc-provisioner bind lane into OpenBao/ESO (source only, CCR-2026-0052)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 22:54:25 +01:00
3a9d640efb NK-WP-0055-T08: kingpath off-site receipts uploaded and verified
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 22:22:35 +01:00
5a52dc8a23 NK-WP-0056-T04: custody client secret reconciled with live; re-sealed custody
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 22:01:30 +01:00
b19f7f6493 NK-WP-0056-T04: reconcile tool reports a value-free error class
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 21:58:01 +01:00
a4b6a8f753 NK-WP-0056-T04: reconcile tool names the failing phase (custody, cluster, live-pair)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 21:54:47 +01:00
05d63e05bd NK-WP-0056-T04: value-free custody reconciliation for the KeyCape client secret
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 21:51:58 +01:00
14e7056469 NK-WP-0054-T05: evidence — resolver password restored from custody; OpenBao login works
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 21:49:34 +01:00
08087ec5ac NK-WP-0054-T05: privacyIDEA resolver still bound as admin after step 8; fixed live, runbook check added
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 673304@bnt-lap001
Assistant-Session: 959b12c7-cf2e-440b-a60d-91229cc9e96e
2026-10-30 21:18:15 +01:00
103c2c1505 NK-WP-0054-T05: re-seal custody after the bind split (ld-admin and svc-* binds)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Encrypted to keys/age.pub; plaintext shredded by the founder after step 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 620478@bnt-lap001
Assistant-Session: 944e8353-7102-4bc8-affe-84586a10f2d0
2026-10-30 20:02:36 +01:00
232bb50a0c chore(consistency): register NK-WP-0056 task identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 620478@bnt-lap001
Assistant-Session: 944e8353-7102-4bc8-affe-84586a10f2d0
2026-10-30 19:59:32 +01:00
c1a9009707 NK-WP-0056: intervention notes for founder-gated tasks
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 620478@bnt-lap001
Assistant-Session: 944e8353-7102-4bc8-affe-84586a10f2d0
2026-10-30 19:57:15 +01:00
be3971c0f2 NK-WP-0056: propose the bounded executor path (founder admits, executors run)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s
Classifies each bind-split runbook step as delegable, root-class or human-intrinsic and
plans admission contract, owner agreement, executor identity, OpenBao custody,
synthetic MFA verification, an unattended bind-rotation pilot and budget enforcement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 620478@bnt-lap001
Assistant-Session: 944e8353-7102-4bc8-affe-84586a10f2d0
2026-10-30 19:55:39 +01:00
098c2c7810 NK-WP-0054-T05: live LLDAP bind split passed (2026-10-30); runbook setup fixes
Founder-run steps 1-10, verify --old-admin-retired pass. Evidence and deviations in
docs/evidence/2026-10-30-nk54-bind-split/. Runbook: export the admin KUBECONFIG and
decrypt the sealed custody copy first. Finding: custody AUTHELIA_KEYCAPE_CLIENT_SECRET
is stale against the live hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 620478@bnt-lap001
Assistant-Session: 944e8353-7102-4bc8-affe-84586a10f2d0
2026-10-30 19:54:33 +01:00
81292e2231 NK-WP-0053 finished: G1 decided (Scaleway second-provider intake, e8935fa6)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-30 00:17:11 +01:00
f24af91211 NK-WP-0053-T02/T04: first admin-operated identity restore drill passed (2026-10-29)
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-30 00:12:41 +01:00
f4a74ebc1c NK-WP-0055-T07: finding - identity backup refused 2026-10-20..29 on a full Storage Share account
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-30 00:03:58 +01:00
dbf22cea91 List-first pruning of a full Storage Share CNPG dump folder (founder-run)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The CNPG Option A dumps prune only locally, so r01-* folders grow without bound;
the Backup account filled and the identity backup has refused since 2026-10-20.
plan is read-only and hashes the plan; delete needs that hash, re-lists and
refuses on any change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 23:43:00 +01:00
cb286e7b03 NK-WP-0054-T05: provisioner journeys green; preconditions 2 and 3 done
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 23:00:20 +01:00
4261a5a4f8 identity-provisioner: platform group boundary test without pytest
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 11s
Build and Publish identity-provisioner / build-and-push (push) Successful in 3s
The journeys workflow runs unittest in python:3.12-slim, which has no pytest;
this module failed to import there since e861f63 (2026-10-14).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:48:54 +01:00
c1bedf1a6e NK-WP-0054-T05: pin identity-provisioner image with LLDAP_ADMIN_USERNAME support
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:44:01 +01:00
07ff92bf96 NK-WP-0054-T05: pin LLDAP to the running v0.6.2 digest
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:26:38 +01:00
59aa0eeebb NK-WP-0054-T05 phase 4: founder runbook for the LLDAP bind split
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:20:09 +01:00
e556af8571 NK-WP-0054-T05 phase 4: realm_bootstrap --switch-bind moves the resolver to svc-privacyidea
Only from the legacy uid=admin bind, only BINDDN/BINDPW, nothing else drifted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:18:30 +01:00
028a7a97e7 NK-WP-0054-T05 phase 4: KeyCape directory-bind rollout (switch to svc-keycape, revert)
Edits only lldap.bindDN/bindPW in sso/keycape-config, modelled on
openbao-signin-diagnosis-rollout.py: inspect/dry-run/apply, uid and
resourceVersion test patch, re-parse check, contained errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:16:59 +01:00
05a9aefaed NK-WP-0054-T05 phase 3b: restore launchers follow the archived identities; startup proof
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:11:19 +01:00
e6f4e28164 NK-WP-0054-T05 phase 3a: founder-run bind-split helper with end-to-end test
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
prepare-custody, plan, accounts and verify for ld-admin and the svc-* accounts;
named rotations for the Authelia bind and LLDAP_LDAP_USER_PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 22:00:49 +01:00
18dae2a0ac NK-WP-0054-T05 phase 2c: Authelia refuses svc-* and ld-admin sign-in; synthetic check
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-29 21:52:52 +01:00
145ca8e08c NK-WP-0054-T05 phase 2b: each directory consumer binds with its own account
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Failing after 12s
Build and Publish identity-provisioner / build-and-push (push) Successful in 50s
Custody gains LLDAP_SVC_{AUTHELIA,KEYCAPE,PRIVACYIDEA,PROVISIONER}_PASS and
LLDAP_LDAP_USER_PASS becomes ld-admin's. Authelia, KeyCape, the privacyIDEA
resolver and identity-provisioner (new Secret identity-provisioner-ldap) switch
to svc-* accounts; LLDAP's configured super-admin is ld-admin. The LLDAP image
pin waits for the running digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 20:33:04 +01:00
2ebc4f0ae8 NK-WP-0054-T05: identity-provisioner logs in as a configurable LLDAP user; svc-*/-admin names reserved
LLDAP_ADMIN_USERNAME (default admin) lets the provisioner use svc-provisioner
after the bind split; preferred usernames starting svc- or ending -admin are
refused like admin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 20:11:04 +01:00
cdf7aa0c77 NK-WP-0054-T05: svc-provisioner in lldap_admin under exception SI-X6 (founder decision)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 19:25:30 +01:00
3a83fed2c6 NK-WP-0054-T05: rehearsal findings and remaining phases recorded; lint fixes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 19:01:40 +01:00
84cc7bf13c NK-WP-0054-T05: synthetic bind-split rehearsal on pinned LLDAP (phase 1 of 4)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 18:59:49 +01:00
dcb5bc2540 NK-WP-0054: founder decision 9f0f7a87 - split goes live this week, admin dispositions, crisis list confirmed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 18:53:10 +01:00
417d88d80e NK-WP-0055-T05 done by decision; T08 sealed Storage Share copy with digest-bound verify
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 12:37:46 +01:00
666160a390 NK-WP-0055-T15: credential rotation-lane register with coverage check
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 11:01:08 +01:00
5466c831cd NK-WP-0040-T02: railiance-infra names the S1 converge wrapper as emitter (babae80)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:58:04 +01:00
635029b117 NK-WP-0040-T02 / NK-WP-0054-T04: integrate railiance-platform answers (bd8ca57)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:54:14 +01:00
49eb79e267 NK-WP-0054-T04: KeyCape client scopes declared from key-cape source (a1ab8ff)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:53:35 +01:00
81422e3d66 NK-WP-0035-T04: readback records audit-core's independent_reader basis
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:50:58 +01:00
53f8327cdb NK-WP-0011: set status blocked (sync had reverted backlog to active)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:24:17 +01:00
56d915d2e2 NK-WP-0011: blocked on the ADR-0009 adoption trigger (backlog is refused while tasks wait)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:21:36 +01:00
d042d7f4d2 Workplan triage: NK-WP-0048/0050 blocked, NK-WP-0011 backlog, NK-WP-0053 pulled forward
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0053 drill and G1 review run now with the first admin MFA run instead of
at the 2027-01-31 deadline; the quarterly repeat moves to disaster-control's
restore-proof register once the workplan closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:15:06 +01:00
95d39916f1 NK-WP-0053-T04: drill covers Authelia and by-user MFA, operated by admin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 308701@bnt-lap001
Assistant-Session: 5548e447-444c-4dcc-bed1-05c16eebfe56
2026-10-28 09:11:58 +01:00
7384007256 NK-WP-0055-T13: done; king path drilled and production SSO-down walk passed
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 4s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 01:04:29 +01:00
46ea820b97 NK-WP-0055-T16: record hub id
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:57:46 +01:00
66795e5eb4 NK-WP-0055-T16: initial root token revoked at production entry; signing key in the safe
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:55:36 +01:00
e9928fa3d9 NK-WP-0055-T13: king-path drill passed; post-event review record
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:46:02 +01:00
7e6b23d85b Crisis list item 5: admission signing key is ~/.ssh/gexec_approver_ed25519
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:31:01 +01:00
185b44a9b1 NK-WP-0055-T13: record the admission signing key anchor in the crisis list
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:26:21 +01:00
7dafb6a3cf NK-WP-0055-T13: production generate-root listener live; DR guide and crisis list updated
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-28 00:06:55 +01:00
f100aa0542 NK-WP-0055-T13: king-path restore drilled; split backup and custodian age keys in the crisis list
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:55:19 +01:00
317d578193 NK-WP-0055-T13: kingpath-offsite --verify restores and checks the break-glass copy
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:47:21 +01:00
fd64fc8478 NK-WP-0055-T13: founder chooses a standing loopback generate-root listener
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:39:28 +01:00
6b6ff3d56e NK-WP-0055-T13: OpenBao break-glass ceremony documented; generate-root disabled on production
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:34:17 +01:00
c917558aae NK-WP-0055-T13: break-glass material sealed to Storage Share and read back
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:31:27 +01:00
d4267ff9b6 NK-WP-0055-T11: record identity-provider intake packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:30:39 +01:00
ccd0b15ee0 NK-WP-0055-T11: audit-core intake packages for Authelia, LLDAP, privacyIDEA (draft)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:30:14 +01:00
a60091775f Close SI-X4: OpenBao platform-admin binds aal2 (founder-applied, RPF-WP-0065-T04)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:23:06 +01:00
6d4acb0ca9 NK-WP-0055-T13: retire the ops bundle; root-token check joins the king-path drill
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:13:13 +01:00
d7fde1f2ba NK-WP-0055-T13: kingpath-offsite helper seals break-glass material to Storage Share
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:07:22 +01:00
e9bc86160f NK-WP-0055-T13: founder custody answers recorded in the crisis list and DR guide
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 23:00:04 +01:00
f2956334bf NK-WP-0055: correlation contract accepted; T13 break-glass findings in the backup docs
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:44:22 +01:00
56f2aa9a3a NK-WP-0055-T14: founder chooses digest-pinned mounted policy bundles
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:38:30 +01:00
169c93876c NK-WP-0055-T11: proposed Audit Correlation Contract v0.1; owner scoping recorded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:37:43 +01:00
e15b61c074 NK-WP-0055-T10: founder production-scope decisions; ADR-0018 defers Topaz
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:24:54 +01:00
656c35fc7b NK-WP-0055-T10: record founder answers on unseal, share custody and HA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:19:47 +01:00
06c08a90d1 NK-WP-0055: record hub ids for T10-T15
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:13:41 +01:00
c8f44fa614 NK-WP-0055-T01: production readiness ledger; gaps become T10-T15
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:11:57 +01:00
15574a5518 NK-WP-0055: record hub ids
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:08:11 +01:00
65b9d3f729 NK-WP-0055: production readiness workplan; NK-WP-0049 finished with residuals moved
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 22:06:58 +01:00
ac6dc720e6 NK-WP-0049: T21 accepted; T03 closes the G08 handoff in builder mode, T44 remains
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:58:57 +01:00
84e7a7ea30 NK-WP-0049-T21: correct the factor-diagnostic row with source and live evidence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:57:49 +01:00
687ec01d9b NK-WP-0049-T21: builder-mode caller disposition draft for founder acceptance
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:53:52 +01:00
7e9bcdd4b7 NK-WP-0049-T22: historical secrets verified unreferenced and deleted by the founder
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:51:58 +01:00
34b570a677 NK-WP-0049-T44: record hub task id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:48:03 +01:00
86e1107279 NK-WP-0049: option A (builder mode); T15 and T18 accepted, phase B paused into T44
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
T18 acceptance includes the CA-bundle check: the pinned OpenBao image
trusts the exact ISRG Root X1 anchor of the KeyCape chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:46:21 +01:00
81f8f03730 NK-WP-0049-T43: cert count unreadable; plan reissue for the safe date
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:34:44 +01:00
5c2432698d NK-WP-0049-T43: founder accepts the inherited-pipe custody design with four optimizations
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:27:39 +01:00
737b1b071b NK-WP-0049-T43: record the three owner replies and remaining open items
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 4515@bnt-lap001
Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354
2026-10-27 21:16:05 +01:00
095019d134 NK-WP-0049-T43: record founder answers; owner requests sent
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 628290@bnt-lap001
Assistant-Session: 70de878b-b4e0-4110-a1ec-bbd553a5f483
2026-10-27 19:05:27 +01:00
425265e57f NK-WP-0054: record the founder's state-hub attribution decisions
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Option A is phase 1 with no hub_core change; session/run id equality only; phase 3 observes
tokens and enforcement is decided later; no backfill. Sent to state-hub. Decision c926a190
resolved.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-25 00:08:31 +02:00
2de259a46d NK-WP-0054-T02: Service Identity and Privilege standard ratified as v0.1
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The founder ratified the standard (status accepted) on 2026-10-25 and accepted the
emergent-security start card 0.2.7. Ratification applies no exception's elimination path,
changes no live service and does not make any declaration owner-confirmed. T02 done.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-25 00:03:24 +02:00
d0f259b12c NK-WP-0054: rename task T03b to T08 (the registry accepts numeric ids only)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:58:08 +02:00
951c7eac2d NK-WP-0054: standard draft-4 applies the owner replies; review outcome recorded
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
security-canon confirmed all five mode readings and found citation errors, now
corrected in section 4 and 8 (anti-patterns cited as rules, a MUST resting on a SHOULD,
INV-001 for SI-9, SI-15 beyond AUDIT-001). Environment of platform-root, GOVERN for a
single-tier service admin (SI-4 allows it), principal_type ownership wording. Records
railiance-platform's prepared F-1 and aal2 changes (source only), key-cape's agent
token mechanism, glas-harness's harness-launched-only scope and state-hub's attribution
design. A hub decision routes ratification and the workers' open choices. No founder
decision changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:55:44 +02:00
2438556edf NK-WP-0054-T02: founder approved draft-3 in principle; ratification awaits two owner replies
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
The founder accepted the SecurityCanon-profile approach and confirmed the platform-root
and admin definitions. T02 now waits on railiance-platform (F-1, aal2) and security-canon
(mode readings) before ratification as v0.1. The review decision is resolved.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:41:55 +02:00
ac66753bd4 NK-WP-0054-T03b: declarations and validator migrated to canon grants (draft-3)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Schema, validator, fixtures, declarations and tests (40 pass) now use the imported
principal_type and authority grants MODE @ SCOPE with activation and environments,
estate conventions bind and service-admin, unknown placeholders, and an elimination
path on every exception. SI-4 and SI-7 became decidable. Before/after over the same
declarations: 1/11/2/5/6 to 1/10/2/5/7 (non-conformant/unknown/excepted/planned/
conformant); the only move is KeyCape's secrets-engine-openbao client, whose scope is
established. lldap/admin is now also excepted under SI-4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:35:30 +02:00
956a7e11a6 NK-WP-0054-T03b: record hub task id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:14:04 +02:00
b7749277fd NK-WP-0054: security review; standard draft-3 as a SecurityCanon profile; agent security quickstart
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Review against security-canon (Mode of Authority, SEC-DSM 1.0.0) showed the draft
restated canon rules and invented a parallel taxonomy. Draft-3 imports principal_type
and MODE @ SCOPE grants, treats break-glass as an activation, cites SEC-DSM per rule,
states the estate identities in canon terms and adds SI-15 (accountable automation:
prepare, admit, execute, verify, retain). Founder decisions and rule ids are kept.

For agents: tools/security-tools.json and make security-tools list each security tool,
the question it answers and whether an agent may run it; a test keeps the index
complete. AGENTS.md and CLAUDE.md carry the same quickstart block. Validator migration
to grants is T03b. Review record in history/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:12:37 +02:00
a3bab9210b NK-WP-0054-T02: agent ceiling is 60 minutes; ld-admin is LLDAP-only and admin stays the human's name
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder clarifications: the SI-14 token ceiling is 60 minutes (3600 s); ld-admin is for
LLDAP only and the name admin remains the human platform administrator's wherever it
appears. The LLDAP rehearsal is now introducing ld-admin by configuration and demoting
admin, not renaming. The validator rejects a service-admin named admin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 22:57:23 +02:00
ca03da52c6 NK-WP-0054-T02: draft-2 of the Service Identity and Privilege standard after the founder's review
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Applied the founder's four decisions: OpenBao platform-admin binds aal2 (SI-8 MUST);
the human administrator keeps directory admin rights through an admin group (SI-5);
declarations start in this repository and move to the services; agents get their own
class and rule SI-14. Finding F-1: the coding-agent role presents as a service against
IAM Profile v0.3. Schema, validator and tests follow (37 pass). Rehearsals listed, not
decided. Not ratified; owner review still open.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 22:50:01 +02:00
48385816fd NK-WP-0054-T03: service identity schema, offline validator and first declarations
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Schema, validator (30 tests), make targets and eight declarations authored from the
inventory, all unconfirmed by their owners. First run: 0 non-conformant, 12 unknown,
2 excepted, 5 planned, 6 conformant. Provisional: built against draft-1 before the
founder's review. Building it changed the draft: custody is a list, SI-X1 now covers
SI-9 and NAMING, and the reserved-name rule applies only to credential-holding
identities (open question 7). Unknown is a finding for every class.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 22:38:23 +02:00
aa96128d73 NK-WP-0054: active (founder directed the work to proceed; T01 done, T02 drafted)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 20:06:15 +02:00
768e7252e8 NK-WP-0054-T02: draft-1 of the Service Identity and Privilege standard
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Thirteen rules (one identity one class, people and services never share a
credential, read-only per-service bind identities, one service-admin per
service, break-glass is not routine, assurance on privileged roles, custody
class, declaration, recovery, validation run by the operator identity), naming
svc-<service> and <short>-admin, five initial exceptions and six open questions.
Proposed; extends IAM Profile v0.3 and Credential Management v0.2. Hub decision
routes the review.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 20:02:30 +02:00
597ffbf111 NK-WP-0054-T01: service identity inventory; T05 scope corrected to four accounts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Read-only inventory from manifests and files, no Secrets read. The admin credential
has five consumers (human, Authelia, KeyCape, the privacyIDEA resolver and the
identity-provisioner), so the bind split needs svc-authelia, svc-keycape,
svc-privacyidea and a scoped provisioner account. Unknowns and who confirms them
are listed. trigger-admin recorded as an uninventoried legacy admin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 19:58:23 +02:00
73870c5d37 NK-WP-0054, NK-WP-0053-T04: record hub ids
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 19:32:22 +02:00
034eacab9b NK-WP-0054: service identity policy plan; crisis custody list; NK-WP-0053-T04
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The founder's three-identity model and the finding that the human admin is also
the directory bind account lead to a proposed workplan: svc-<service> bind
accounts, per-service admin accounts, a governing standard and a conformance
health check. docs/crisis-custody-list.md is the first concrete list of what
unlocks recovery, with unknowns marked TO CONFIRM. NK-WP-0053-T04 extends the
quarterly drill to the Authelia and by-user MFA stages with admin as operator.
Hub decision routes the three founder gates.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 19:30:52 +02:00
1ce56365b9 NK-WP-0049-T43: record hub task id
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:55:42 +02:00
463fbbb954 NK-WP-0049-T43: phase B plan, unsigned admission template and decision; refresh position
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
docs/nk49-phase-b-plan.md sets out the shared internal network for the isolated
original KeyCape and the restored OpenBao, owner by owner, with what the pinned
T18 machinery forces (a new reviewed module; the T40 window has expired).
The unsigned admission template names placeholders only. Hub decision e4db6c9f
routes the founder choices. The workplan position paragraph now matches the
task counts (37 done, 1 in progress, 5 waiting).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:54:25 +02:00
a9f68fc8a2 NK-WP-0049-T19: done; founder ran the isolated existing-token MFA check on the actual backup
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Restored LLDAP, database and original keys in one network-free namespace; archived
resolver repointed to the restored directory in the disposable copy. By user and
realm through /validate/check: wrong key refused (error 907), wrong code refused,
the founder's TOTP PIN+code accepted. Loopback only; cleanup verified. Does not
prove the KeyCape consumer login chain or production token validity.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:49:33 +02:00
5008e34f1a NK-WP-0049-T42: rebuild the MFA stage around the restored directory; check by user and realm
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The founder's first real run stopped at ResolverError on all checks: real tokens
belong to an LDAP user and the archived resolver names the in-cluster directory.
Restored LLDAP now joins the database's network-free namespace, archived resolver
addresses are repointed to loopback in the disposable copy only (any other address
is refused), and the checks call POST /validate/check by user and realm as KeyCape
does. Fifteen native fixture tests pass (HOTP and TOTP+PIN, negatives, bundle).
Also covers the user-to-token resolver and the encrypted bind password.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:42:35 +02:00
9a3a84c99c NK-WP-0049-T42: fix launcher preflight entry point; test the launcher path
The MFA launcher called m.preflight() but the module only exposed pi.preflight,
so its catch-all reported a generic preflight failure before any key prompt.
Add preflight() and execute the launcher's own snippet in the tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 18:17:08 +02:00
8058b80213 NK-WP-0049-T42: record hub task id
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 17:39:13 +02:00
1f04c23392 NK-WP-0049-T42: isolated existing-token MFA stage, fixture-tested
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
isolated-privacyidea-mfa.py restores the privacyIDEA database in the accepted
network-none stage and runs privacyIDEA's own check_serial_pass three times in
separate processes: wrong key, wrong code, the owner's code. The serial list is
terminal-only; receipts carry booleans, categories and a serial hash. A first
fixture run was vacuous (PRIVACYIDEA_CONFIGFILE overrode the wrong-key config);
the probe now fixes the variable and asserts the key path. Eight native fixture
tests pass (HOTP, TOTP with PIN, negatives, encrypted bundle). The user-to-token
resolver is not proven here and is recorded as such. T19 awaits the founder run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 17:37:57 +02:00
1959a184cb NK-WP-0049-T17: done; founder ran the isolated Authelia restore on the actual backup
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Original config validated, storage key opened the store, issuer key equals the
archived key, archived keycape client secret accepted (no grant issued), wrong
secret and wrong password refused, existing-user first factor passed against the
restored LLDAP. Loopback only; cleanup verified. Does not prove the full login
chain, MFA (T19) or current production password validity.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 17:25:16 +02:00
006dc23fdf NK-WP-0049-T41: isolated Authelia restore stage, fixture-tested
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
isolated-authelia.py runs restored LLDAP as the network-none anchor with
Authelia joining its namespace. Checks the original config, storage key,
issuer key, KeyCape's client secret and existing-user first factor, with
wrong-password and wrong-client-secret refusal. Confinement rewrites: LDAP
to loopback, file notifier, NTP check off, healthcheck file off. Imports
isolated-lldap.py unedited; the T38-pinned KeyCape sources are untouched.
Native fixture tests on the pinned images pass (9). T17 now awaits the
founder-attended run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 17:14:52 +02:00
4719970abb NK-WP-0049-T41: record hub task id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 16:52:41 +02:00
b2de807d7d NK-WP-0049: redraft T17 Authelia acceptance and add T41 adapter preparation
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Design review 2026-10-24: KeyCape's source confirms the KeyCape->Authelia->LLDAP
human-login chain. T17 now checks the durable Authelia secrets and the keycape
client verifier rather than leaning on storage decryption. T41 prepares the
stage in the existing anchor-namespace pattern, reusing pinned modules unedited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 16:51:20 +02:00
acf961ad84 NK-WP-0049-T18: refresh blocking reason after the issuer attempt and OpenBao restore
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4384@bnt-lap001
Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64
2026-10-24 11:54:34 +02:00
9cb2295037 NK-WP-0049-T18: original OpenBao restored in isolation (RPF-WP-0064); consumer login run remains
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4384@bnt-lap001
Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64
2026-10-24 11:43:55 +02:00
a09bf20120 NK-WP-0049-T18: attended original KeyCape attempt verified in isolation; receipt retained outside Git by hash
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4384@bnt-lap001
Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64
2026-10-23 20:01:28 +02:00
a20dcfe162 NK-WP-0049-T40: done; founder signed the attempt admission, selector preflight admitted
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4384@bnt-lap001
Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64
2026-10-23 19:06:52 +02:00
8314cefa71 NK-WP-0049-T40: unsigned attempt admission prepared for the 19:00 session; evening order recorded
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4101@bnt-lap001
Assistant-Session: bc7d2ce5-6f32-45f1-83e5-0dcbb8e610a7
2026-10-23 13:27:46 +02:00
5a8958bf08 NK-WP-0035-T04: attended receiver readback; presence 3/3, counts refused by reconciliation source scope
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4101@bnt-lap001
Assistant-Session: bc7d2ce5-6f32-45f1-83e5-0dcbb8e610a7
2026-10-23 10:56:40 +02:00
7e1c073c72 NK-WP-0049-T33: done; attempt-host admission anchor installed and self-tested, T40 signature next
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4101@bnt-lap001
Assistant-Session: bc7d2ce5-6f32-45f1-83e5-0dcbb8e610a7
2026-10-23 09:24:53 +02:00
1eded6a356 NK-WP-0049-T33: LE staging rehearsal green; only the anchor install remains
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 4101@bnt-lap001
Assistant-Session: bc7d2ce5-6f32-45f1-83e5-0dcbb8e610a7
2026-10-23 09:10:02 +02:00
2a9d946524 NK-WP-0048-T34: integrate the corrected F3 owner configuration inverse text into 1.3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Applies the Custodian nk48-r5-f3-implementation-r2 type candidate (addresses
RAPPS-F3-SESSION-1). Descriptors unchanged; validator and suites pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 07:08:41 +02:00
fad2c72750 NK-WP-0048-T12: register-client restarts the issuer and waits for readiness after a changed registry
Per coordinator r6. New register-client descriptor sha256:434c53e8...43cb;
1.2/1.3 compositions regenerated; 1.3 map and status-only ready_check updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 07:06:48 +02:00
3a68d05eb9 test: resolve relative PYTHONPATH for the adapter source-closure probe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 07:02:49 +02:00
14de7334ed NK-WP-0049-T33: staging rehearsal prepared by railiance-cluster; only the attended run remains
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 07:00:25 +02:00
33bf19f2ba NK-WP-0053-T01: done; disaster-control adopted the identity lane (DISCTL-WP-0004)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 06:59:24 +02:00
4f616cfff8 NK-WP-0035-T04: independent receiver readback helper for the retained hourly capture
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 06:57:26 +02:00
1ed7953ae8 NK-WP-0053-T01: quarterly isolated identity restore drill procedure
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 06:53:26 +02:00
f00440e6cc NK-WP-0049-T24: record founder split (G1 accept, G2 watchdog link, G3 NK-WP-0053 drill intake)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 06:52:44 +02:00
ef3a7ee76b NK-WP-0049-T15: incorporate the T02 caller ledger into the restore packet
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:27:33 +02:00
4c4c7c4569 NK-WP-0049-T33: independent attempt receipt verifier, retained location and ACME reissue step
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:22:50 +02:00
710dc99585 docs: index privacyIDEA and KeyCape helper entry points from the NK49 caller ledger
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:19:09 +02:00
21ce056221 NK-WP-0049-T02: keep authentication-policy-rollout for greenfield/recovery activation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:18:06 +02:00
dfe5a5ed28 NK-WP-0048-T12: KeyCape restart and post-restart readiness source candidate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:17:42 +02:00
40ea96ca79 NK-WP-0049-T02: complete caller ledger (93 entries, historical objects); unblock T21/T22 ledger dependency
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:15:14 +02:00
fa709f8098 NK-WP-0049-T39: drop needs_human from resolved gate (C-41 422)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:13:22 +02:00
6a1745b034 NK-WP-0049-T02: mechanical first-pass caller ledger over the 93-entry inventory
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:10:48 +02:00
578d6b1ccf NK-WP-0048-T12: narrow to missing post-registration KeyCape restart/readiness check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:09:33 +02:00
579d2e367e NK-WP-0048-T16: wait on 1.3 re-freeze and coordinator r6 (r5 findings changed type.yaml)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:08:26 +02:00
35ce5776bc NK-WP-0049-T24: record single-provider/host-local limits; route founder scope decision e4c288d9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:07:30 +02:00
e3f1212636 NK-WP-0035-T04: record completed hourly capture; wait on attended readback and KG acceptance
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:06:49 +02:00
5695904b63 NK-WP-0049: close T35/T36/T39 on founder ACME reissue disposition; narrow T33 blockers
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 696317@bnt-lap001
Assistant-Session: bab9e511-0915-45b3-926a-2fab552edff6
2026-10-23 00:03:51 +02:00
68066346ef Assemble the accepted engine rule into the proposed layer-model successor.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Section 3.3 of security-layer-model v0.9 now states that an engine enables one capability, that several engines may share a store, and that operator use is a separate access rule. Accepted v0.8 is unchanged.

Assistant: grok
Assistant-Session: 01a1609f-72b8-7222-9dff-fae300115e42
2026-10-22 14:34:24 +02:00
e1f7491e31 Wave-2 right-sizing applied 2026-10-22 (CUST-WP-0071)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 276069@bnt-lap001
Assistant-Session: 2e192b3d-b3e8-4403-94d4-d55a2c8b476c
2026-10-22 00:58:51 +02:00
ae6861dec2 NK-WP-0052-T01: prepare wave-2 requests for sso, mfa and net-kingdom-pg
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Requests lowered per the 2026-10-21 node budget; lldap (53.7% throttled)
CPU limit 200m -> 1000m. kubectl diff against live: resources only. No apply.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 276069@bnt-lap001
Assistant-Session: 2e192b3d-b3e8-4403-94d4-d55a2c8b476c
2026-10-22 00:08:06 +02:00
8a5c1fe4d8 chore(consistency): write back State Hub ids
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 8340@bnt-lap001
Assistant-Session: 2e192b3d-b3e8-4403-94d4-d55a2c8b476c
2026-10-21 12:29:29 +02:00
f9eb5e8cba NK-WP-0052: wave-2 resource right-sizing receiving workplan
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Receiving workplan for the 2026-10-21 node allocation review
(RESOURCE-WP-0008-T03 / CUST-WP-0071-T03). Wave-2 apply is human-gated;
no manifest changes. WORK-RECORDS.md regenerated from staged sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 8340@bnt-lap001
Assistant-Session: 2e192b3d-b3e8-4403-94d4-d55a2c8b476c
2026-10-21 08:48:07 +02:00
0af1fb7390 chore(workplans): sync NK-WP-0049 T39/T40 hub task ids
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 809600@bnt-lap001
Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
2026-10-21 01:23:33 +02:00
f890760176 NK-WP-0049: T33 attempt assembly requires secrets-engine selector --admission; T35/T36 owner prep
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Bind secrets-engine selector/source pins and founder-signed attempt admission
(d1ab097d) as T33's required pre-key step; record kc-tls owner (RCLUSTER-WP-0018)
and public served-chain metadata; add founder gates T39 (TLS disposition,
decision 9f9d0d88) and T40 (attempt signature). Preparation only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 809600@bnt-lap001
Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
2026-10-21 01:04:19 +02:00
d2d1dbcd36 Bind completed receiver generator task to owner projection
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: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 13:54:05 +02:00
1bd869c7a6 build: keep generated orientation brief local and untracked
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 4s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 13:50:40 +02:00
bde143e030 build: derive coordination receiver from staged owner sources
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 13:44:36 +02:00
4c7f32adc7 build: adopt staged records and retain pending owner handoff evidence
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 13:32:59 +02:00
b99e396283 Record delivered temporary Forgejo public-client flow with retained confidential acceptance
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15302-b965-7d43-a9d8-42710914b24e
2026-10-19 10:49:50 +02:00
a7339fa60d Declare approved Forgejo public PKCE development sign-in profile
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: 01a15302-b965-7d43-a9d8-42710914b24e
2026-10-19 10:35:33 +02:00
b3f31d9f26 Record published consumer build and refine private input inverse and PDP work
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-18 00:13:34 +02:00
201e2fa0f9 Track active consumer release source build under the existing task
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 23:27:12 +02:00
6201a1d2de Record compiled any-of consumer source and actual release follow-through
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.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 23:22:16 +02:00
0241070b91 Record actual Forgejo chart compatibility and preserve exact any-of gate work
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 23:00:00 +02:00
befb0bc68c fix: bind issuer template exactly and align catalog admission fixtures
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.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 20:44:31 +02:00
342a6f25af test: restore independent mint admission coverage and refine remaining fixes
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.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 20:28:50 +02:00
3236d5000e docs: update published Warden original-context checkpoint
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 13:30:20 +02:00
001a38ca2a NK-WP-0048-T16 r5: regenerate the 1.3 map and both compositions; tests follow the r5 lanes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
1.3 composes completely with the new lanes and an all-human actor set. 1.2 is
correctly refused on the three moved lanes and stays plan-only. The 1.2
admission test's unique checks move into test_admit_1_3_type.py. Tests run
against ops-warden 117fec9.

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
2026-10-17 12:01:18 +02:00
407ee5ec36 Merge r5 adapter changes (OW-1.3-C1 lane, RAPPS-1.3-C1 human actor, RAPPS-1.3-C2 hand-over probes)
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
2026-10-17 11:59:49 +02:00
aba012eba2 NK-WP-0048-T16 r5: 1.3 step text states how each step runs; canon clarifies kernel-dispatched attended steps
Folds NK-1.3-C1/C2, RPF-1.3-C1/C2, RAPPS-1.3-C3, KC-1.3-C1 and the OW-1.3-C1 lane names.
Parameters untouched (fingerprint unchanged). The execution_descriptors map is
regenerated after the adapter changes land.

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
2026-10-17 11:59:17 +02:00
cbadd359ba NK-WP-0048 r5 adapters: OW-1.3-C1 attended change lane, RAPPS-1.3-C1 human actor, RAPPS-1.3-C2 handover observations
Lane key-cape-oidc-login -> keycape-attended-case-change in the register-client,
ensure-groups and bind-env descriptors; deliver-consumer-secret actor agent ->
human; verify_consumer reports a value-free, exactly-once probe record list bound
into the observation digest. Composition artifacts, type.yaml and admit/compose
tests are left for the orchestrator to regenerate.

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
2026-10-17 11:59:14 +02:00
239a528d62 NK-WP-0048-T16: r4 approved the 1.3 candidate; founder chose option A; owner reviews open
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 11:40:20 +02:00
1920b134e2 NK-WP-0048-T16: propose 1.3 successor type with the execution_descriptors map
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 4s
Source-only candidate. The real-kernel admission test shows the remaining gate is an
ops-warden 1.3 parameter pin plus 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
2026-10-17 11:25:35 +02:00
acda26c7a9 Merge 1.3 successor source support in adapters and composer
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
2026-10-17 11:21:53 +02:00
835680ceb9 Source support for oidc-browser-relying-party@1.3 successor selection
ensure-groups and hand-over accept 1.3 via explicit version_root only (1.1
stays default; hand-over applies 1.2 owner handling to 1.3).
deliver-consumer-secret accepts exactly {1.2, 1.3}. compose-1.2-descriptors
gains --version {1.2,1.3} (default 1.2) and refuses cleanly when
security-case-type-revisions/<version> is absent. The committed 1.2
composition artifact is regenerated with its recorded args: only the adapter
source and descriptor digests of the three edited adapters change.

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
2026-10-17 11:21:15 +02:00
9877ee8b3d NK-WP-0048-T30: done; coordinator review r3 approved the 1.2 adapter set at ff8f597
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 11:15:36 +02:00
ff8f597d5f NK-WP-0048-T30: regenerate 1.2 artifact after r2 fixes; record r2 outcome
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 11:08:22 +02:00
2ee4ed0b4d Merge NK48 r2 finding fixes (R2-F1 mint revocation, R2-F2 deliver-secret case binding, R2-F3 bind-env closure)
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
2026-10-17 11:07:56 +02:00
d16db30282 Pin bind-env full local source closure (NK48-R2-F3)
bind-env's source_digest covered bind-env-adapter.py and keycape-client.py
only, but keycape-client.py executes portal-client-rollout.py, which
executes openbao-client-config.py and register-user-engine-portal.py.
Pin the same five-file closure the registration adapter pins, and add a
closure-completeness test that imports every *-adapter.py in a fresh
interpreter and fails on any loaded local file its SOURCE_FILES omits.

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
2026-10-17 11:07:33 +02:00
640292ccea Bind deliver-secret reference to the admitted case (NK48-R2-F2)
build() now takes case_source, checks it against admitted.pins.case, refuses
non-confidential cases and a client_id differing from the admitted target, and
derives sso/keycape-{client_id}-client (key client-secret) from
platform/workloads/keycape/{client_id} per type 1.2. No caller-chosen
reference remains. Fixtures use the 1.2 forgejo example and real names;
regression tests cover the reviewer's forgejo/grafana demonstration, a public
client and a case-pin mismatch.

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
2026-10-17 11:07:31 +02:00
eeed8e118e NK48-R2-F1: revoke session and broker grant independently in mint-secret
A raising parent-session revocation no longer skips broker-grant revocation,
and the reverse; the first failure is re-raised after both are attempted, so
the attempt is refused rather than reported as success. Regression tests
cover each direction and both failing.

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
2026-10-17 11:07:16 +02:00
68f4bbb8ca Test kernel admission of every 1.2 descriptor (NK48-R1-F1 follow-up)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Run Executor.admit on all seven composed descriptors with the real 1.2
type, example case and reviewed browser validator. Pin agent-mint and
wrong-lane refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1011674@bnt-lap001
Assistant-Session: b3b5b6f0-3bf5-41a3-8150-3ca4606cc028
2026-10-17 10:49:50 +02:00
410d5239cd Make the mint-secret actor human (NK48-R1-F1)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Kernel a45cf47 refuses effect mint unless the actor is human, so the
agent descriptor could never be admitted. Regenerate the 1.2 candidate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1011674@bnt-lap001
Assistant-Session: b3b5b6f0-3bf5-41a3-8150-3ca4606cc028
2026-10-17 10:44:23 +02:00
582ab9fad0 NK-WP-0048-T30: record founder confirmation of T25-T29 lane and schema names
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
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
2026-10-17 10:33:35 +02:00
8b30eef8a5 NK-WP-0048-T25/T30: mint-secret actor is agent (founder decision); 1.2 set composes
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
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
2026-10-17 08:48:22 +02:00
e6014dd804 NK-WP-0048-T30: record composer merge; wait on mint-secret actor decision and owner name confirmation
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 08:12:10 +02:00
641c44c4b7 Merge NK-WP-0048-T30 follow-up: parent-session revocation for ensure-groups and hand-over, parameterised hand-over owner
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
2026-10-17 08:12:00 +02:00
376b44b65b NK-WP-0048-T30F: parent-session revocation for ensure-groups and hand-over; hand-over owner parameter
- ensure-groups-adapter.py: descriptor revocation_scopes=('parent-session',);
  build() requires revoke_session and calls it in a finally block on every
  run/rollback; OwnerResult now carries revoked=True and the session
  revocation evidence (previously revoked was set to the rollback flag).
- handover-adapter.py: same revocation fix; descriptor(owner) takes the owner
  as a parameter (type 1.2 '{consumer_owner_repo}') instead of hard-coded
  'apps'; build() derives it from admitted.owner and a 1.2 case must name
  the same consumer_owner_repo.
- Tests: kernel Descriptor.decode accepts both descriptors; revocation runs
  even on failed attempts; missing revocation evidence refuses before
  publication; 1.2 owner binding.
- descriptor-composition-1.2.candidate.json regenerated (same checker digest,
  consumer_owner_repo=railiance-apps): only mint-secret remains refused.
- test_compose_1_2_descriptors.py pins that finding set and checks the
  committed artifact equals regeneration.

Outside the assigned file list (needed for the composer to pass the owner):
compose-1.2-descriptors.py passes consumer_owner to hand-over as it does
for deliver-consumer-secret, and drops the stale proposed owner 'apps'.

Not an admission, owner review or pin; mint-secret actor left for owner decision.

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
2026-10-17 08:11:30 +02:00
3c62bec31a Merge NK-WP-0048-T30 1.2 descriptor composer and candidate artifact
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
2026-10-17 08:08:24 +02:00
60b41a1f51 NK-WP-0048-T30: source composer for the seven-step 1.2 descriptor set
Composes through the explicit 1.2 version root, closes each adapter
descriptor through Warden Descriptor.decode and checks step/owner/lane
against the 1.2 follow_through. Refuses partial, duplicate, unknown,
mismatched and 1.1-default-changing compositions. The generated candidate
artifact is not an admission: the current adapters do not compose
(ensure-groups, mint-secret, hand-over refused) and worker-proposed names
are marked owner_confirmation_required.

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
2026-10-17 08:07:59 +02:00
71a7c06249 NK-WP-0048: record T30 prerequisites and T15 source merge; T15 waits on owner holder and re-review
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 08:03:37 +02:00
65559efa2f Merge NK-WP-0048 T30 prerequisites (T24 1.2 path, T27 Warden wrapping) and T15 reconcile source
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
2026-10-17 08:03:12 +02:00
061461e4a4 NK-WP-0048-T15 source: adapter-owned original-intent reconcile
build() accepts paired holder/observe read-only capabilities and installs
Adapter.reconcile_original; verify accepts the published reconcile record.
Fixture now exercises the adapter's own path. No add replay, no undo after
restart; missing/ambiguous/changed state stays uncertain. Holder choice
remains an owner gate.

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
2026-10-17 08:02:25 +02:00
7442d212ec NK-WP-0048-T27W: wrap bind-env as a full Warden Adapter
Source-digest, descriptor, case and acceptance pins; owner-published and
authenticated evidence; parent-session revocation in finally. Existing
reference-only refusals and created-only rollback kept. Synthetic tests only.

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
2026-10-17 08:02:15 +02:00
531151b854 NK-WP-0048 T30 prereq: explicit 1.2 version root for ensure-groups adapter
Canonical 1.1 stays the default; oidc-browser-relying-party@1.2 is accepted
only via build(version_root='1.2'). Adds a public group_binding helper pinned
by test to the provisioner's _group_binding, replacing the private call.

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
2026-10-17 08:01:32 +02:00
ece7b0a2a8 NK-WP-0048: close T24-T29 and T31 as source candidates; record T30/T15 inputs
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
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
2026-10-17 07:58:26 +02:00
7dbe75f33e Merge NK-WP-0048 T24-T29 adapter candidates and T31 restart fixtures
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
2026-10-17 07:57:54 +02:00
46885bf4e5 NK-WP-0048: record State Hub ids for T24-T31 from fix-consistency
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
2026-10-17 07:57:52 +02:00
74adcd7260 NK-WP-0048-T31: restart and lost-receipt fixtures against Warden original intent
Fixture-only reconcile_original owner over the T13 registration adapter,
driven through Warden's actual Executor/Journal (ops-warden a45cf47):
original intent kept separate from the reconcile attempt across restart;
holder-unavailable, missing/ambiguous record, wrong attempt/case/source/
principal, NO_CHANGE original, changed revision and absent client entry
all refuse and stay uncertain with no add replay or undo. The holder is
fixture-only; actual authenticated holder selection stays a T15 owner gate.

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
2026-10-17 07:57:13 +02:00
5a2e7d67a2 NK-WP-0048-T24: ensure-groups adapter source candidate
Binds group creation to the T14/T21 PostgreSQL journal seam (exact case,
source and principal binding; refuses untrusted journals), canonical 1.1
case only, created-only rollback via the journal receipt. Source candidate;
owner review and native action remain separate gates.

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
2026-10-17 07:56:25 +02:00
650c1ac57c NK-WP-0048-T28: deliver-consumer-secret adapter source candidate
Unexercised, injection-only, value-free evidence seam for type 1.2 with
positive/refusal fixtures and created-only rollback. Canonical 1.1 default
unchanged; no owner review or native action supplied.

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
2026-10-17 07:55:47 +02:00
ec635393b6 NK-WP-0048-T29: Apps hand-over adapter source candidate
Injection-only consumer verification and hand-over receipt seam with
positive/refusal fixtures and created-only rollback. Canonical 1.1 default;
1.2 only by explicit version-root selection. Unexercised; Apps owner review
before pinning remains a separate gate.

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
2026-10-17 07:55:36 +02:00
558c5bcbf6 NK-WP-0048-T25: mint-secret adapter source candidate (value-free)
Injection-only seam for the type 1.2 mint-secret step: closed value-free
custody receipts, created-only rollback by exact version, parent-session and
broker-grant revocation. Source candidate only; no owner review or admission.

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
2026-10-17 07:55:29 +02:00
1b60f6e9c6 NK-WP-0048-T26: deliver-secret adapter source candidate
Injection-only, value-free Platform delivery seam with exact metadata-only
receipt contract, created-only rollback and synthetic positive/refusal tests.
Unexercised; Platform owner source review and native action stay separate gates.

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
2026-10-17 07:55:24 +02:00
6fbce73b87 NK-WP-0048-T27: bind-env adapter source candidate (references only)
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
2026-10-17 07:55:04 +02:00
2019e823f0 NK-WP-0048: split T16 into T24-T30 and T15 fixtures into T31
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Each adapter candidate fits one per-task budget; T31 builds on Warden's
original-intent interface (ops-warden a45cf47).

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
2026-10-17 07:52:18 +02:00
503a4f6175 NK-WP-0048: record T16 budget decomposition and T15 Warden interface check
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
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
2026-10-17 04:31:15 +02:00
c52ca7f405 fix(recovery): require one original client and refine native owner gates
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.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 04:15:30 +02:00
70f408ca23 feat(identity): add PostgreSQL provenance and verify sealed SOPS custody
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Failing after 13s
Build and Publish identity-provisioner / build-and-push (push) Successful in 26s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 01:36:38 +02:00
6cb43c0cc5 docs(security): record verified source delivery and native preparation
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.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 00:42:06 +02:00
4f65e41ac5 fix(identity): retain group provenance and prepare sealed recovery custody
Some checks failed
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Failing after 20s
Build and Publish identity-provisioner / build-and-push (push) Successful in 8s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-17 00:28:17 +02:00
878abc9358 docs(recovery): retain isolated source publication and task bindings
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: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-16 23:38:15 +02:00
5fcd5952ad feat(recovery): add isolated KeyCape restore and loss cleanup proof
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: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-16 23:35:54 +02:00
85a54ab9da docs(cases): retain checker snapshot publication and task binding
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: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-16 23:28:35 +02:00
8790dd18ae fix(identity): execute reviewed checker from sealed attempt snapshot
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: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
2026-10-16 23:21:57 +02:00
cbabb6dc36 docs(cases): record checker artifact and boundary-fix publication
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 22:30:47 +02:00
916ba4b33e fix(identity): isolate checker environment and executable selection
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 22:08:28 +02:00
0394484b92 docs(cases): record registration source publication and task projection
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 21:52:05 +02:00
0775c2bfa4 feat(identity): prepare fixed attended registration adapter
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 21:42:33 +02:00
015694ee41 fix(cases): validate optional exact machine descriptor pins
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 21:23:04 +02:00
8ae5150d4d docs(recovery): accept retained token material and audit continuity
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 21:14:23 +02:00
repo-manager
3223039446 repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 21:06:22 +02:00
49ddde451a fix(discovery): project recovery capability summary from owner entry
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 21:01:51 +02:00
1ab5d53bd7 Prepare isolated privacyIDEA token and audit recovery checks
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 21:00:36 +02:00
0e298813c7 Accept actual isolated LLDAP recovery and clear directory prerequisites
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 20:40:14 +02:00
repo-manager
3670c6dabb repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 20:34:11 +02:00
d5484bb90a Prepare native isolated directory recovery and offline service images
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 20:33:57 +02:00
31f483966c Accept independent outage recovery and Storage Share guide publication
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 20:04:44 +02:00
fbdd62b7ed Fix private recovery prompts on nonseekable terminals
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 19:56:06 +02:00
b35e81f48f Record recovery handoff scope and preserve prior generated brief
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 18:48:29 +02:00
repo-manager
9dce45903e repo.work.assign_missing_identifiers
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 17:59:40 +02:00
1764f40b43 Prepare independent outage recovery and Storage Share documentation
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 17:59:18 +02:00
2a3800e7f1 plan: make task accountability visible in projected descriptions
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 16:29:34 +02:00
716de2edfd plan: project task ownership and preserve pre-existing staged brief
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 16:25:15 +02:00
repo-manager
3831662343 repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 16:12:28 +02:00
8727985834 plan: expose completed recovery milestones and refine remaining work
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 10:57:05 +02:00
cc2dd01f40 ops(recovery): verify complete hardened recurring backup service
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 09:41:02 +02:00
1de6deaf0a ops(recovery): verify full archive recovery and activate protected backup timers
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 09:38:31 +02:00
17a8fa1495 feat(recovery): verify scoped delivery and create first full offsite bundle
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 09:21:55 +02:00
8857d9ff6f security(recovery): fence agent reads before publishing backup credentials
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 07:49:48 +02:00
72c598e7de fix(recovery): distinguish malformed paths from outside-folder denial
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 07:48:36 +02:00
a17aa7edc1 fix(recovery): bind admitted host and record live provider scope approval
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 07:44:36 +02:00
c31d21ba7d feat(cases): verify browser case source against owner receipts
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: 01a140c4-354a-7823-8f0d-8f717397f786
2026-10-16 01:51:58 +02:00
994d11c869 feat(recovery): prepare full identity backup and bootstrap handoff
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 01:37:11 +02:00
dca999222f Record off-host database recovery and prepare identity backup coverage
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 00:32:50 +02:00
a1939a7d06 Prepare bounded off-host privacyIDEA recovery drill
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-16 00:09:13 +02:00
c262470089 Inventory off-host recovery assets through existing bounded backup routes
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 23:58:41 +02:00
d9557ba90e Harden remaining bootstrap custody callers and prepare isolated recovery
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 22:57:14 +02:00
a6a9a3a266 Defer reef tasks so backlog remains stable during synchronization
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 22:29:08 +02:00
258c85420e Backlog NK-WP-0027 and document demand-driven resumption
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 22:27:57 +02:00
b91bf46e82 Finish NK-WP-0022 with verified CoulombCore cluster retirement
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 22:10:34 +02:00
341344a126 Prepare CoulombCore retirement and reconcile conditional recovery gate
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 21:54:35 +02:00
c21ab4ccda Checkpoint live hourly observation before independent acceptance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 21:46:32 +02:00
bd1bed731f Register hourly cadence receiver and deploy verified durable Qonto collector
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 21:43:38 +02:00
1ae0fe337a Record approved hourly runtime rollout and native verification
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 20:57:08 +02:00
9c4b1d7285 Record managed identity for shared coordination frame task
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: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 20:47:38 +02:00
2d0681f361 Adopt shared hourly coordination frame and prepare guarded runtime patches
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 20:45:06 +02:00
a83691398e Reference established observer task for heartbeat repair
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:58:14 +02:00
730e06dfe2 Revalidate cadence sources and prepare operational adoption gates
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:55:00 +02:00
311620bbdb Complete NK-WP-0031 with audit-core freshness adoption evidence
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:29:41 +02:00
9442858535 Complete NK-WP-0051 with attended member and refusal evidence
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a13eab-1e6a-7b33-9c37-372e90ada3a2
2026-10-15 19:23:19 +02:00
c19eabc4ae Accept the account-site session lines (NK-WP-0051-T03 done).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Records the user-engine digest and that Authelia's authentication_level
is not assurance, since KeyCape checks the one-time code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 05:22:52 +02:00
deedca52a4 Accept the KeyCape sign-in diagnosis page (NK-WP-0051-T02 done).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 00:33:07 +02:00
39b04113e5 Record the live openbao-admin signInDiagnosis flag (NK-WP-0051-T02).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 00:25:20 +02:00
c7ad8a5bf0 Prepare the openbao-admin signInDiagnosis flag rollout (NK-WP-0051-T02).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
One-line, byte-preserving edit of sso/keycape-config with inspect, server
dry-run, attended apply and an exact rollback. No values are emitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 00:16:15 +02:00
916bc7d5b8 Roll KeyCape to cdec9dd7 with the sign-in diagnosis page (NK-WP-0051-T02).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-run rollout: check-config valid, diff image-only, dry-run passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-15 00:09:12 +02:00
cb634be39b Record NK-WP-0051 owner implementations and fix the assurance source.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-14 23:53:38 +02:00
2cc09ceb05 Preview one uid's directory groups for sign-in diagnosis (NK-WP-0051-T04).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-14 23:41:32 +02:00
106d97f4a8 Write the sign-in diagnosis contract (NK-WP-0051-T01).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-14 23:40:25 +02:00
custodian-sync
8f0a590200 chore(consistency): renormalize lifecycle state [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-14:
  - workplan status: ready → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-14 23:39:40 +02:00
c12eb899b4 Sharpen NK-WP-0051 field names, opt-in page, and preview tool.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1041694@bnt-lap001
Assistant-Session: 7af0bb68-ac4a-4ce3-8639-24ddd84c0b52
2026-10-14 23:38:45 +02:00
bb02e725f4 Record the State Hub ids for NK-WP-0051.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Assistant: grok
Assistant-Session: 01a13b2d-5f6d-7a32-b2c9-384625c55ca6
2026-10-14 22:27:40 +02:00
4717863554 Add a workplan for workload sign-in diagnosis.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
NK-WP-0051 records who shows the uid, the group names, and whether this login verified a one-time code.

Assistant: grok
Assistant-Session: 01a13b2d-5f6d-7a32-b2c9-384625c55ca6
2026-10-14 22:25:51 +02:00
e99c3e9791 docs(reuse): establish reviewed NetKingdom security capabilities
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a137ca-5c77-7a71-b9ae-23ad9b0b5684
2026-10-14 06:40:30 +02:00
repo-manager
1a36e857cc repo.work.assign_missing_identifiers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
source: repo-manager
reason: deterministic projection registration

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 02:11:30 +02:00
4dffe2fe7a fix: preserve resolver configuration during attended password cutover
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 01:52:37 +02:00
ab25dde80d security: repair LDAP group callers and fail incomplete T07 acceptance
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 01:17:35 +02:00
e861f63513 security: reserve platform IAM groups in service group operations
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Failing after 16s
Build and Publish identity-provisioner / build-and-push (push) Successful in 7s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 00:44:33 +02:00
84c5dbeec8 security: retire unsafe legacy LDAP lifecycle callers
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-14 00:08:42 +02:00
276b95f6ba security: reject Playwright audience and delivery alias collisions
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 23:44:41 +02:00
eba6e1fdbb security: bind Playwright activation and recovery to exact issuer state
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 23:39:58 +02:00
5dd74dfbdf security: prepare scoped Playwright consumer registrations and owner delivery
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 23:13:48 +02:00
30f0d27840 security: preserve realm configuration and remove shell credential evaluation
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 22:47:24 +02:00
96feef46ab security: replace retired MFA diagnostic credential discovery with scoped checks
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 22:24:35 +02:00
1cbe00fd78 security: preserve privacyIDEA key custody and restore the actual PVC
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 21:46:41 +02:00
16021d3953 docs: distinguish remaining custody callers from repaired SOPS paths
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 21:10:23 +02:00
8f808577d6 fix(security): constrain SOPS writes and custody argument 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-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 21:03:24 +02:00
b01a30bd49 fix(security): make service Secret updates private and conditional
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: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 20:20:42 +02:00
7d4bb1653e fix(security): reconcile KeyCape bootstrap with scoped factor custody
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 19:46:53 +02:00
19a1c75d30 fix(security): make admin bootstrap private and non-overwriting
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: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 15:02:15 +02:00
6ae4708ea0 fix(security): keep rotation credentials out of command output
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 12:54:10 +02:00
681c5b0ea1 fix: decrypt SOPS through explicit custody into private output
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: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 12:08:35 +02:00
4cf26a9bc8 fix: make custody input explicit and remove partial decryptions
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: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 10:53:30 +02:00
b97f9f616f fix: separate bootstrap encryption from private root custody
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: 01a12d13-27f2-7163-b8c0-f682af02772c
2026-10-12 09:41:08 +02:00
custodian-sync
d552bf253c chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-11:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 23:25:33 +02:00
8bcd7b882f NK-WP-0022-T08: founder retires coulombcore for machine reuse; host-side inventory, no cert rotation
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 23:23:31 +02:00
aeb26b37d2 Requalify tasks after founder attended-window choices (2026-10-11, round 3)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 21:06:42 +02:00
beba775bd2 Sync work-record index after requalification
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:51:34 +02:00
custodian-sync
349e6104a6 chore(consistency): sync task status from DB [auto]
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Updated by fix-consistency on 2026-10-11:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:51:33 +02:00
5cc5e895e9 Requalify tasks after round-2 founder decisions and fix mislabeled human gates (2026-10-11)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:50:47 +02:00
511c92457c Sync work-record index after triage
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:36:06 +02:00
custodian-sync
012efc7012 chore(consistency): sync task status from DB [auto]
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled
Updated by fix-consistency on 2026-10-11:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:36:05 +02:00
custodian-sync
0e0552bbf4 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:51 +02:00
custodian-sync
b45ef84127 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:51 +02:00
custodian-sync
efc9686d6f chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:50 +02:00
custodian-sync
e550ea8c22 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:50 +02:00
custodian-sync
90f5c72b7f chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:50 +02:00
06595627a4 Demote off-critical-path workplans to residual/backlog (2026-10-11 triage)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-authorized triage; tasks unchanged. See the-custodian
history/20261011-netkingdom-security-progress-assessment.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:35:13 +02:00
96eb40c695 Preserve October 10 owner decision source packets
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 18:46:21 +02:00
8964affdea Retain reviewed security workplan consolidation and bindings
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 18:46:21 +02:00
7ceac4372d Publish reviewed bounded JWT ready handoff and retained owner history
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 18:31:04 +02:00
bf6a44db6d Bound JWT private input and gate native effects on public health
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 17:30:10 +02:00
dfc357eb8a Publish reviewed KEY39 lifecycle source and preserve failed review history
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 15:33:14 +02:00
98f09f4d4b Record independent KEY39 lifecycle deadline review
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 14:21:04 +02:00
bb9b9b556c Select explicitly pinned security case type revisions without fallback
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 09:09:24 +02:00
f6856cdb53 Record founder source acknowledgment for isolated Forgejo type 1.2
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 08:56:45 +02:00
584a964684 Prepare reviewed Forgejo type 1.2 source decision package
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 04:48:34 +02:00
c26b018328 Prepare reviewed Forgejo consumer ESO scope and truthful recovery records
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 04:09:46 +02:00
6180f87894 Retain accepted Staff doctrine handoff and proposed successor adoption
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 04:04:20 +02:00
d8eabe675b Review exact one-use catalogue exception and recovery limits
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 03:33:47 +02:00
e3fffe3ab4 Review bounded Staff evidence doctrine without changing accepted canon
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 03:29:45 +02:00
42aaf60062 Record exact conditional technical review of user readback rollout
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 02:44:21 +02:00
81fa1769e0 Give changed draft publications new immutable revision identities
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 02:25:23 +02:00
f4436dadd5 Preserve exact served security layer v0.7 revision artifacts
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 02:15:29 +02:00
0e17d956ab Accept security layer v0.8 from confirmed native review
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.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 01:53:56 +02:00
custodian-sync
f6b4be384f chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 5s
Updated by fix-consistency on 2026-10-10:
  - update .custodian-brief.md for net-kingdom

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-10 20:30:29 +02:00
custodian-sync
0a0512a4b6 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-10:
  - update .custodian-brief.md for net-kingdom

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-10 20:14:03 +02:00
e26736a1f1 NK-WP-0048-T06: refresh blocking reason to the current Forgejo gates
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 15:39:56 +02:00
69c5f5a1a8 chore: record hub ids for NK-WP-ADHOC-2026-10-10
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 15:30:28 +02:00
8d2083e9d0 chore: regenerate WORK-RECORDS (NK-WP-ADHOC-2026-10-10)
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 15:30:22 +02:00
35880bbce4 NK-WP-0048: run keycape check-config before any registry patch
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
keycape-client.py --check-config runs KeyCape's startup checks (key-cape
c528daa) on the live and proposed registry via stdin; apply refuses a
registry KeyCape would refuse at start. Type 1.1 patch names the option.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 15:28:05 +02:00
1c2d0c2cf6 Follow the access-engine CLI rename: ./cmd/flex-auth -> ./cmd/access-engine
FLEX-DEC-2026-020; the old path is a stub that exits 2. Runtime names unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 15:28:05 +02:00
e2174cebaf NK-WP-0048: oidc-browser-relying-party 1.1; re-pin the Forgejo case
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Adds verification consumer-enforces-groups and makes hand-over require the
consumer to enforce access_groups (key-cape review R2). Founder go to move to
1.1 before any apply, so no 1.0 run overlaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 13:25:54 +02:00
61ab6bcc33 NK-WP-0048: key-cape type review R1-R4 as a 1.0 patch
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
R1: redirect URIs refuse '?' and '#' in the type and keycape-client.py.
R2: allowed_scopes always includes groups; access_groups is enforced by the
consumer, not KeyCape. R3/R4: mfa_required false and restart-for-all-clients
documented. The consumer-enforcement verification step (1.1) awaits a decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 13:18:11 +02:00
744b4aa36e NK-WP-0048: record key-cape adoption of the derived Forgejo custody path
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 12:40:12 +02:00
20a3e091f5 user-engine: record USER_ENGINE_MFA_MANAGEMENT_URL in migration input
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder go 2026-10-10. Live already carries the value via rapp-user-engine
(the apply home); this file is not applied.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 10:28:00 +02:00
9a9b8e373a NK-WP-0048: adopt {client_id:env} in bind-env and its rollback (1.0 patch)
ops-warden's case planner renders the spec since 23ba584.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 736651@bnt-lap001
Assistant-Session: f6189624-fd64-4109-b53e-f8a59ca8a2d8
2026-10-10 10:28:00 +02:00
850907a987 NK-WP-0048: {param:env} format spec; check follow-through/rollback templates
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Answers ops-warden's observation on the literal KEYCAPE_<CLIENT_ID>_CLIENT_SECRET.
The type adopts the spec once ops-warden's planner renders it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 19:02:56 +02:00
502907a2d3 NK-WP-0042-T02: record founder's pilot decision (coulomb-social, MFA optional)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Acceptance remains open under decision cef97ccd for user-engine's attended
U05-U08 session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 14:43:19 +02:00
2612753a7a chore: regenerate WORK-RECORDS (NK-WP-0048-T05 done)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 12:31:03 +02:00
d88f814ece NK-WP-0048-T06: roll out identity-provisioner main-630ebfd
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Founder-approved. Image only (kubectl set image); service groups ensure/revoke
present in the running pod, readyz 200. Rollback digest recorded in the workplan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 12:30:12 +02:00
922f1ee041 NK-WP-0048-T06: write the Forgejo reference case (accepted)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
security-cases/forgejo-netkingdom-oidc.yaml carries the founder's 2026-10-08
acceptance of the key-cape request; validates and renders plan-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 12:26:17 +02:00
custodian-sync
44c74b8798 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-09:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 12:16:28 +02:00
eb8d7b2edc NK-WP-0048-T05: done; oidc-browser-relying-party 1.0 acknowledged (memo r4)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
The founder accepted memo r4 in informed-decision. Its hub write-back is off
in production, so hub decision e418263e was resolved from the founder's report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 12:15:50 +02:00
257c810709 NK-WP-0042-T02: record user-engine's pilot proposal (coulomb-social)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
The message dependency is cleared; T02 waits on the founder under
decision cef97ccd for the pilot choice and journey acceptance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 11:47:34 +02:00
0e86f957a2 NK-WP-0048-T05: take ops-warden R8 option 1; ensure-groups attended; memo r4
ensure-groups and its rollback run attended in the register-client session
(key-cape-oidc-login), inside the identity-provisioner pod: the provisioner's
only HTTP credential is user-engine's runtime token, so a caller-proxy lane
would act as user-engine. Lane identity-provisioner-groups is dropped.
Memo r4 supersedes r3; acknowledges ops-warden's WARDEN-WP-0044-T01 review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 469893@bnt-lap001
Assistant-Session: 49296f15-2171-4137-b972-2443bab95edb
2026-10-09 11:47:34 +02:00
fbcb76055d NK-WP-0046: finished; all tasks done (live go caafe4be applied 2026-10-07)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-09 00:23:33 +02:00
custodian-sync
fab7a60d39 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-09:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-09 00:22:59 +02:00
33ebda59d4 NK-WP-0048-T05: fold ops-warden's review into the type; build memo r3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Canon section 3 defines warden_executes narrowly and adds per-step lane,
value_handling and ready_check; catalog entries need a templated body. The
type gets lanes, generated-in-custody minting, a Ready gate before bind-env,
memberships only from the accepted case, mfa-pinned and secret-delivered
checks, and the R4-prime catalog body. 110 tests pass. Memo r3 supersedes r2
and validates on both sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-09 00:22:04 +02:00
custodian-sync
2bf4fa0b9a chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-09:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-09 00:01:06 +02:00
dd664355ef NK-WP-0048-T05: record ops-warden's review and the open r2 answer at close
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-09 00:00:20 +02:00
custodian-sync
ba1c389b24 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 10s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:53:21 +02:00
e4119eaa6b NK-WP-0048-T05: close the superseded memo with return, not by leaving it open
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:52:34 +02:00
custodian-sync
3500a6722c chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:42:46 +02:00
00bcb17479 NK-WP-0048-T05: build revised memo r2 after ops-mason's review
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
The first memo described the type before the materialization review. r2 uses a
new memo id (a delivered idempotency key refuses new content) and says it
supersedes the first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:41:32 +02:00
custodian-sync
ad71665bd5 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:28:05 +02:00
074b3a047b NK-WP-0048-T05: fold ops-mason's materialization review into oidc-browser-relying-party
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Derive custody at platform/workloads/keycape/{client_id} (secret_ref is no
longer case-settable), reuse the workload-kv-read-keycape-* policy family with
templated bodies, add the external-secrets-keycape-{client_id} ESO role, name
the catalog entry {consuming_service}-keycape-client and collapse the duplicate
KV-path rollback. Schema and validator require a body on policy/role items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:27:22 +02:00
c0347351fc chore(consistency): regenerate WORK-RECORDS for NK-WP-0046/0048
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 330051@bnt-lap001
Assistant-Session: b98d4912-95a3-4619-b7fb-57aa1db7407b
2026-10-08 23:27:22 +02:00
custodian-sync
b63484dd5e chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:24:43 +02:00
58dfb66add NK-WP-0048-T05: memo delivered to informed-decision; awaiting answer
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:23:58 +02:00
7572627adb NK-WP-0048-T05: use State Hub's stable idempotency key; validate both sides
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
The dispatcher refused the envelope (idempotency_key_not_stable). The builder
now derives state-hub.<decision>.<memo> and runs State Hub's validate_envelope
as well as informed-decision's validate_submission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:22:42 +02:00
custodian-sync
137c9f43c0 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:10:33 +02:00
c2cb04ce94 NK-WP-0048-T05: record the prepared memo and the attended enqueue step
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:09:51 +02:00
969d201f1b NK-WP-0048-T05: Decision Memo for acknowledging oidc-browser-relying-party 1.0
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Packet, envelope builder and the built memo intake envelope for hub decision
e418263e. The envelope passes informed-decision's own intake validator
(secret scan, one question, packet digest).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:09:25 +02:00
e32cc27fcc chore(consistency): sync NK-WP-0048
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:03:48 +02:00
custodian-sync
cec1f11192 chore(consistency): sync task status from DB [auto]
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:03:44 +02:00
6a5857746d NK-WP-0048-T09: keycape-client entry point and declaration
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
One case-driven entry point for the live KeyCape client registry, with the
existing safeguards; apply requires the case acceptance record. Declaration
net-kingdom.keycape-client-registration (implemented). The type pins it and
adds the confidential delivery and env-binding steps. 48 + 71 tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 23:03:00 +02:00
custodian-sync
2720c05102 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:59:14 +02:00
630ebfdd61 NK-WP-0048-T08: identity-provisioner service-access groups ensure/revoke
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Identity provider journey acceptance / provider (push) Successful in 9s
Build and Publish identity-provisioner / build-and-push (push) Successful in 9s
POST /v1/groups/ensure and /v1/groups/revoke: reserved prefixes refused, no
user creation, validate-before-mutate, read-back before success, delete only
empty groups on request. The oidc-browser-relying-party type now names them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:58:36 +02:00
b1b14bd489 chore(consistency): sync NK-WP-0048
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:46:16 +02:00
custodian-sync
125595067f chore(consistency): sync task status from DB [auto]
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:46:15 +02:00
fa2cb57384 NK-WP-0048-T04: SecurityCaseType oidc-browser-relying-party 1.0 (proposed)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Composes key-cape.oidc-client-registration; confidential/public conditions,
one justified attended step, threat model, fit table of hand-built clients,
Forgejo and public-portal examples. Validator gains when/required_when and
provider discovery. 43 tests pass. Adds T08 (identity-provisioner groups ensure).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:45:31 +02:00
custodian-sync
9f49aae925 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:30:07 +02:00
d8cf42bab6 NK-WP-0048: canon accepted; SecurityCaseType schemas, validator and template (T01, T03)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 4s
security-case-types_v0.1 accepted by the founder 2026-10-08. Adds JSON schemas
for types and cases, tools/security-case-types/security_case_validator.py
(validate-type, validate-case, render via the scenario composer, plan-only),
and a runnable _template type with README sections, an example case and tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:29:13 +02:00
custodian-sync
dcacdf57f3 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:22:29 +02:00
custodian-sync
c24ae4cca9 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-08:
  - workplan status: ready → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:22:13 +02:00
df15d6900d NK-WP-0048-T02 done: SecurityCaseType inventory
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
36 of 37 CCRs are already workload-kv-read; browser and service OIDC clients
and forge read identities follow. Ranking and recommendation recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:21:40 +02:00
a49d5bcc6a chore(consistency): record State Hub IDs for NK-WP-0048
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 7s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:19:55 +02:00
custodian-sync
2994a0af66 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-08:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:17:48 +02:00
4b21f7bdee Propose SecurityCaseTypes canon and add NK-WP-0048
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s
security-case-types_v0.1 (proposed): types designed once with docs, tests and
an informed-decision; known cases light (accept, ops-mason materializes,
ops-warden follows through). Founder direction 2026-10-08.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5457@bnt-lap001
Assistant-Session: 792bf84a-3783-4f5d-a811-cded61dd2012
2026-10-08 22:17:03 +02:00
custodian-sync
d5dd19dfe2 chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Updated by fix-consistency on 2026-10-07:
  - update .custodian-brief.md for net-kingdom

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
2026-10-07 02:21:50 +02:00
1fbb8dbdf6 Register secrets-engine-openbao live and fold its env binding in (NK-WP-0046-T03)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Founder live go caafe4be. Config resourceVersion 71471711 -> 71655453 with only
this client added; KeyCape restarted on the kept image 6f79a2af.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
2026-10-07 02:20:08 +02:00
7a2fe60399 chore: index completed platform reset mail workplan
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.1-sol
Assistant-Session: 01a10e2d-a5ef-7e92-b03d-b1694e915680
2026-10-06 20:57:09 +02:00
custodian-sync
36ecf9b674 chore(consistency): sync task status from DB [auto]
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Updated by fix-consistency on 2026-10-06:
  - update .custodian-brief.md for net-kingdom

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a10e2d-a5ef-7e92-b03d-b1694e915680
2026-10-06 20:56:39 +02:00
d644e6a79f docs: complete native reset mail and personal login acceptance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a10e2d-a5ef-7e92-b03d-b1694e915680
2026-10-06 20:54:42 +02:00
779 changed files with 96968 additions and 5576 deletions

View file

@ -1,94 +0,0 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — net-kingdom
**Domain:** infotech
**Last synced:** 2026-10-06 18:41 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Deployment-owned registration of the secrets-engine-openbao KeyCape client
Progress: 2/3 done | workplan_id: `1de95344-8a72-5797-93e5-6c1038c03b01`
**Open tasks:**
- ! Live registration rollout under the founder's go `94cff4cd`
### Deliver password-reset mail from platform mailbox
Progress: 2/3 done | workplan_id: `53b7c429-e780-5dfe-9ad4-88dd0490ecb8`
**Open tasks:**
- ! Complete actual reset and personal login acceptance `0dd88024`
### Implement deterministic posture and evidence feedback
Progress: 4/5 done | workplan_id: `9d7b04f9-3803-5613-b7a5-8bd606c77f5a`
**Open tasks:**
- ! Obtain audit-core freshness adoption `7d2029d2`
*(wait: audit-core tenancy.yaml must publish structured authoritative owner, observation/expiry and replacement-action metadata for WH-ENG-20260822-AUDIT-E2-03. Re-evaluate with explicit current as-of; later recovery prose does not renew E2.)*
### Publish the NetKingdom emission-cadence security profile
Progress: 4/5 done | workplan_id: `04685f94-1991-5e62-80d2-5669913e99fc`
**Open tasks:**
- ! Bind and hand off the published contract `e3fbc8b8`
*(wait: Approval Engine and Qonto owner declarations now pass the published schema and supplied-inventory profile; local-identity incompatibility is retained. Requires actual sender cadence mappings, lifecycle delivery/readback/reconciliation and Kings Guard operational feed evidence.)*
### Reconcile reef placement and security-zone canon dependencies
Progress: 3/6 done | workplan_id: `965ad365-6b81-50a1-a2a3-2d0c1fcce0b4`
**Open tasks:**
- ! T02 — Extend provider declarations to reefs `5b35b646`
*(wait: Requires repo-manager acceptance of the reef provider carrier/schema, then railiance-infra authored ceilings, conditions and evidence in the authoritative reef declaration.)*
- ! T03 — Reconcile reef ceilings mechanically `e3f0bb0f`
*(wait: Mechanical join of reef ceilings to consumer V declarations waits on T02.)*
- ! T06 — Resolve the `DataClassification` mismatch `bf8b3e1d`
*(wait: Requires InfoTechCanon ruling on public disclosure classification versus synthetic provenance, then ops-warden authoritative dataclass_floor mapping and tests; no public-to-synthetic alias.)*
### Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker
Progress: 1/9 done | workplan_id: `1075448f-d533-5f9e-94b7-c3adfe151a07`
**Open tasks:**
- ! Admit the database consumer `2fe6f100`
*(wait: ADR-0009 adoption trigger absent; then railiance-platform/rapp-postgres must admit the database consumer and return backup/restore evidence.)*
- ! Package the broker deployment `32d1411b`
*(wait: ADR-0009 adoption trigger and named package/runtime owner absent; requires T02 database admission and governed OpenBao/ESO role plus immutable issuer assignment.)*
- ! Integrate an upstream identity provider `6697a994`
*(wait: Named tenant/upstream IdP owner/federation need absent; requires admitted broker and owner-reviewed tenant-scoped claim mappings plus actual login evidence.)*
- ! Define federated assurance `d845380d`
*(wait: Adoption trigger absent; requires per-IdP assurance trust mapping and reviewed privileged step-up delivery from issuer/package and access-engine owners.)*
- ! Verify IAM conformance and coexistence `9cb7fd93`
*(wait: Requires admitted live broker, canonical tenant issuer selection and dedicated test-client conformance/coexistence evidence; adoption trigger absent.)*
- ! Enforce tenant and platform boundaries `38998c68`
*(wait: Requires tenant-engine tier/issuer ownership and access-engine boundary/freshness enforcement evidence after adoption trigger and broker admission.)*
- ! Prove recovery and audit delivery `e5f44ddc`
*(wait: Requires admitted database/broker, named recovery and audit owners, off-host restore and observed audit/break-glass receipts; adoption trigger absent.)*
- … and 1 more open tasks
### Let workloads require MFA for all or part of their features
Progress: 1/2 done | workplan_id: `3f702215-704b-5788-8ca0-b8b9ba2dd3f8`
**Open tasks:**
- ! Agree the user-facing step-up and enrollment journey `4e51585d`
*(wait: Agree the MFA-required user experience with user-engine and one pilot workload; must be accepted from a user's perspective.)*
### Cut over NetKingdom identity to railiance01 and retire CoulombCore
Progress: 7/8 done | workplan_id: `d76ddccc-00c8-548a-b141-2cd660fa38da`
**Open tasks:**
- ! T08 - Final deletion and closure `42a3b4c0`
*(wait: Explicit approval, rollback retention window expiry and one successful railiance01 restore/restart drill before destructive CoulombCore cleanup.)*
### Define an execution-attribution receipt for Railiance runs
Progress: 1/2 done | workplan_id: `e2533f3a-aa43-59b3-bff3-8e64b6149487`
**Open tasks:**
- ! Agree the evidence holder and schema with audit-core and Railiance `963120c1`
*(wait: Requires audit-core/Railiance owner agreement on emitter, evidence holder and receipt fields before local schema/validator implementation; then actual emission/readback and invalid attribution/clock/unknown-case evidence.)*
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,58 @@
name: Build and Publish bounded-executor
# Fleet image-publish pattern: build from a tarball of the pushed commit,
# never from a workstation tree. Executor tests run in identity-journeys.yaml
# on the same paths; do not pin a digest in rapp-bounded-executor if that job
# failed (NK-WP-0056-T13). Build context is the repository root.
on:
push:
branches: [main]
paths:
- "bounded-executor/**"
- "tools/bounded-execution-admission/validate.py"
- "canon/schemas/bounded-execution-admission_v0.1.schema.json"
- "sso-mfa/k8s/synthetic-journey/journey.py"
- ".forgejo/workflows/bounded-executor-image.yaml"
workflow_dispatch:
env:
REGISTRY: forgejo.coulomb.social
IMAGE_NAME: coulomb/bounded-executor
DOCKER_HOST: tcp://127.0.0.1:2375
jobs:
build-and-push:
runs-on: container-build
steps:
- name: Build and push image
env:
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -eu
REF="${GITHUB_SHA:-main}"
SHORT="${REF:0:7}"
mkdir -p buildctx "${HOME}/bin"
wget -qO /tmp/repo.tar.gz \
"https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz"
tar xzf /tmp/repo.tar.gz -C buildctx --strip-components=1
wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \
| tar xz --strip-components=1 -C "${HOME}/bin" docker/docker
export PATH="${HOME}/bin:${PATH}"
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
IMAGE="${REGISTRY}/${IMAGE_NAME}"
docker build -f buildctx/bounded-executor/Containerfile \
-t "${IMAGE}:latest" -t "${IMAGE}:main-${SHORT}" \
buildctx
docker push "${IMAGE}:latest"
docker push "${IMAGE}:main-${SHORT}"
echo "pushed ${IMAGE}:latest and ${IMAGE}:main-${SHORT}"
- name: Report immutable digest
run: |
set -eu
export PATH="${HOME}/bin:${PATH}"
IMAGE="${REGISTRY}/${IMAGE_NAME}"
SHORT="${GITHUB_SHA:0:7}"
docker inspect --format='{{index .RepoDigests 0}}' "${IMAGE}:main-${SHORT}"

View file

@ -2,7 +2,7 @@ name: Identity provider journey acceptance
on:
push:
branches: [main]
paths: ["identity-provisioner/**", ".forgejo/workflows/identity-journeys.yaml"]
paths: ["identity-provisioner/**", "sso-mfa/k8s/synthetic-journey/**", "bounded-executor/**", "tools/bounded-execution-admission/**", "examples/bounded-execution-admission/**", ".forgejo/workflows/identity-journeys.yaml"]
workflow_dispatch:
jobs:
provider:
@ -18,3 +18,14 @@ jobs:
tar xzf identity-source.tar.gz -C identity-source --strip-components=1
cd identity-source
PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=identity-provisioner python3 -m unittest discover -s identity-provisioner/tests -v
- name: Run synthetic MFA journey runner tests (NK-WP-0056-T05)
run: |
set -eu
cd identity-source/sso-mfa/k8s/synthetic-journey
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest test_journey test_lane -v
- name: Run bounded executor tests (NK-WP-0056-T11..T13)
run: |
set -eu
cd identity-source
python3 -m pip install --no-cache-dir --require-hashes -r bounded-executor/requirements.txt
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s bounded-executor/tests -v

View file

@ -0,0 +1 @@
{"version":1,"repo_slug":"net-kingdom"}

View file

@ -0,0 +1,10 @@
# Explicit opt-in recipe; no environment files or external make includes.
CUSTODIAN_SOURCE ?= /home/worsch/the-custodian
GENERATED_REPO_SLUG ?= net-kingdom
.PHONY: generated-work-records generated-work-records-check
generated-work-records:
python3 tools/coordination-frame/staged.py --repo .
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)"
generated-work-records-check:
python3 tools/coordination-frame/staged.py --repo . --check
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)" --check

3
.gitignore vendored
View file

@ -189,3 +189,6 @@ cython_debug/
# Patch backups written by scripts/patch-*.py — git history is the real backup
*.bak-*
# Generated workstation orientation; keep local, regenerate from State Hub.
/.custodian-brief.md

View file

@ -2,13 +2,14 @@
# Any file under a secrets/ directory (at any depth) is encrypted with the operator age key.
# Same age keypair as railiance-infra — one key per operator across all repos.
#
# Key fingerprint: age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4
# Public key stored in: keys/age.pub
#
# To edit an encrypted file: sops secrets/<file>
# To encrypt a new file: sops --encrypt --in-place secrets/<file>
# To decrypt to stdout (inspect): sops -d secrets/<file>
# To add a recipient: update .sops.yaml + sops --rotate --in-place secrets/<file>
# To replace encrypted content: make sops-edit FILE=secrets/<file>.yaml INPUT=<private-directory>/<replacement>.yaml
# To encrypt a private untracked file: make sops-encrypt FILE=secrets/<file>.yaml
# Attended private recovery: make sops-decrypt FILE=secrets/<file>.yaml OUTPUT=<private-directory>/<new-file>
# Run through the custody unlock helper; never decrypt into a transcript.
# To rotate the data key only: make sops-rotate FILE=secrets/<file>.yaml
# Recipient changes require a separately reviewed custody migration; rotate does not apply this config.
creation_rules:
- path_regex: secrets/.*$

View file

@ -229,6 +229,39 @@ To create a new workplan:
the session close protocol. Ask the operator only if the CLI or API is
unavailable.
### Task refinement after substantial work (required)
Established by operator instruction on 2026-10-16; decision
`d90b2f1f-cdaf-43b1-99e7-71afb43672bb`.
At each substantive checkpoint and before session close, compare the completed
work and new knowledge with the current task structure. If substantial work was
completed while the workplan's task granularity stayed unchanged, **add completed
task records for those evidenced outcomes and refine the remaining tasks**.
A long progress note inside the same broad open task does not satisfy this rule.
- Give each substantial completed deliverable a task with its actual outcome,
completion date, evidence and limits. Retrospective recording is allowed; do
not invent completion dates, deliveries, native acceptance or approvals.
- Split remaining work into independently manageable deliverables with an owner,
next action, completion criteria and real dependencies. Use `todo` for work
that can start; use `wait` only with a concrete prerequisite and unblock condition.
- Preserve existing task IDs, Hub UUIDs and history. When narrowing an umbrella
task, explain the split and map moved scope to the new task IDs. Do not mark
the umbrella done unless its retained scope is complete; preserve externally
referenced acceptance gates until their dependencies are satisfied.
- Keep task counts honest: extracting completed scope must not count the same
outcome twice or imply an effort-based completion percentage. Distinguish
source/fixture proof, actual operational proof and owner acceptance.
- Record new risks and out-of-scope findings as explicit disposition/handoff
tasks or linked live work records; do not silently expand authorization.
Make these changes in the repository source, commit/push, run the repository's
required projection sync, and verify the actual projected task counts, statuses
and dependencies. Progress events supplement the task structure; they do not
replace it. A checkpoint report must show both substantive outcomes and newly
refined remaining work when this rule is triggered.
<!-- BEGIN EMERGENT-SECURITY-PILOT -->
## NetKingdom situational knowledge pilot
@ -250,3 +283,20 @@ do not silently download or execute a replacement.
This pilot adds situational knowledge, not authority, live readiness or a change
to this repository's owner contracts. Work: `emergent-security/EMERGENT-WP-0002`.
<!-- END EMERGENT-SECURITY-PILOT -->
<!-- BEGIN NETKINGDOM-SECURITY-QUICKSTART -->
## Security quickstart for agents (Claude Code, Codex, Grok)
1. **Find the tool:** `make security-tools` lists every security tool, the question it answers, how to run
it and whether an agent may run it (`tools/security-tools.json` is the machine-readable form).
Tool availability is not permission.
2. **Know the rules:** `SECURITY-COMPANION.md` section 7 ("If you are an agent").
3. **Credentials:** `warden route find "<need>" --json`. Never ask for, print or store a secret value.
4. **Accounts and accountable automation:** `canon/standards/service-identity-privilege_v0.1.md`, rules
SI-14 (agents) and SI-15 (prepare, admit, execute, verify, retain).
5. **Your effective authority is everything your tools can reach** (SEC-DSM DSM-AGENT-008). Live changes,
private keys and production credentials belong to the person: prepare and test on synthetic fixtures,
and give them the exact command to run.
6. **Leave a trail:** commits get assistant trailers from the hook; log State Hub progress with your agent
name and session.
<!-- END NETKINGDOM-SECURITY-QUICKSTART -->

View file

@ -32,3 +32,20 @@ do not silently download or execute a replacement.
This pilot adds situational knowledge, not authority, live readiness or a change
to this repository's owner contracts. Work: `emergent-security/EMERGENT-WP-0002`.
<!-- END EMERGENT-SECURITY-PILOT -->
<!-- BEGIN NETKINGDOM-SECURITY-QUICKSTART -->
## Security quickstart for agents (Claude Code, Codex, Grok)
1. **Find the tool:** `make security-tools` lists every security tool, the question it answers, how to run
it and whether an agent may run it (`tools/security-tools.json` is the machine-readable form).
Tool availability is not permission.
2. **Know the rules:** `SECURITY-COMPANION.md` section 7 ("If you are an agent").
3. **Credentials:** `warden route find "<need>" --json`. Never ask for, print or store a secret value.
4. **Accounts and accountable automation:** `canon/standards/service-identity-privilege_v0.1.md`, rules
SI-14 (agents) and SI-15 (prepare, admit, execute, verify, retain).
5. **Your effective authority is everything your tools can reach** (SEC-DSM DSM-AGENT-008). Live changes,
private keys and production credentials belong to the person: prepare and test on synthetic fixtures,
and give them the exact command to run.
6. **Leave a trail:** commits get assistant trailers from the hook; log State Hub progress with your agent
name and session.
<!-- END NETKINGDOM-SECURITY-QUICKSTART -->

View file

@ -26,6 +26,7 @@ If yes to any of the above, don't add it here.
| CP-NK-004 | KeyCape OIDC hostname | `kc.coulomb.social` | `sso-mfa/k8s/keycape/ingress.yaml`, `sso-mfa/k8s/authelia/configmap.yaml`, `sso-mfa/k8s/keycape/create-secrets.sh` |
| CP-NK-005 | Authelia login portal hostname | `auth.coulomb.social` | `sso-mfa/k8s/authelia/ingress.yaml`, `sso-mfa/k8s/authelia/configmap.yaml` |
| CP-NK-006 | LLDAP admin web UI hostname | `lldap.coulomb.social` | `sso-mfa/k8s/lldap/ingress.yaml` |
| CP-NK-007 | Ecosystem coordination frame | `f = 1h` | `coordination-engine/config/coordination-frame.json` (owner source) |
---
@ -144,3 +145,18 @@ to the parent domain (`coulomb.social`) so the cookie is valid across both
is IP-restricted by the `lldap-admin-allowlist` Traefik Middleware (VPN/office
CIDRs only). The LDAP port (3890) is cluster-internal only and never exposed
via Ingress.
## CP-NK-007 — Ecosystem coordination frame
**Owner:** coordination-engine. **Initial setting:** `frame_seconds: 3600`.
Founder decision 2026-10-15: pace long-term coordination in hourly rather than
daily frames. This is an operator-selected pace, not a topology-derived value.
The canonical file is `coordination-engine/config/coordination-frame.json`;
NetKingdom keeps no independent editable copy.
Cadence adopters derive heartbeat f, receiver wait 2f and effective deadline
3f (including Audit Core's existing 1.5 grace). Change f once, then run
`tools/coordination-frame/render.py --write` and `--check`. Generated owner
artifacts carry the source digest. Runtime owners deploy reviewed artifacts;
source regeneration is not deployment or estate-wide timer adoption.

147
Makefile
View file

@ -50,52 +50,42 @@ hooks-test: ## Test that the pre-commit hook blocks plaintext secrets
@rm -rf sso-mfa/bootstrap/secrets/_hooktest
# ── SOPS / age ────────────────────────────────────────────────────────────────
sops-setup: ## Legacy dev-local setup: copy age key to SOPS default path
@mkdir -p ~/.config/sops/age
@if [[ -f ~/.config/age/key.txt ]]; then \
cp -n ~/.config/age/key.txt ~/.config/sops/age/keys.txt || true; \
chmod 600 ~/.config/sops/age/keys.txt; \
echo "✔ SOPS key ready at ~/.config/sops/age/keys.txt"; \
elif [[ -f ~/.config/sops/age/keys.txt ]]; then \
echo "✔ SOPS key already present at ~/.config/sops/age/keys.txt"; \
else \
echo "ERROR: age key not found at ~/.config/age/key.txt"; \
echo " Generate one with: age-keygen -o ~/.config/age/key.txt"; \
exit 1; \
fi
sops-setup: ## Validate the registered public age recipient; never install a private key
@python3 sso-mfa/bootstrap/sops-private.py setup
sops-edit: ## Edit an encrypted file with SOPS: make sops-edit FILE=secrets/foo.yaml
@[[ -n "$(FILE)" ]] || (echo "Usage: make sops-edit FILE=secrets/path/to/file.yaml"; exit 1)
sops $(FILE)
# Raw values prevent both Make-function evaluation and shell interpolation.
unexport FILE INPUT OUTPUT COMMAND OPERATOR_AGE_PUBKEY
sops-edit sops-encrypt sops-decrypt sops-rotate: export NK_SOPS_FILE := $(value FILE)
sops-edit: export NK_SOPS_INPUT := $(value INPUT)
sops-decrypt: export NK_SOPS_OUTPUT := $(value OUTPUT)
sops-encrypt: ## Encrypt a file in place: make sops-encrypt FILE=secrets/foo.yaml
@[[ -n "$(FILE)" ]] || (echo "Usage: make sops-encrypt FILE=secrets/path/to/file.yaml"; exit 1)
sops --encrypt --in-place $(FILE)
@echo "✔ Encrypted $(FILE)"
sops-edit: ## Replace encrypted content from private INPUT outside Git; explicit custody required
@python3 sso-mfa/bootstrap/sops-write.py edit
sops-decrypt: ## Decrypt a file to stdout (never writes plaintext to disk): make sops-decrypt FILE=secrets/foo.yaml
@[[ -n "$(FILE)" ]] || (echo "Usage: make sops-decrypt FILE=secrets/path/to/file.yaml"; exit 1)
sops -d $(FILE)
sops-encrypt: ## Encrypt private, untracked FILE in place with the registered public recipient
@python3 sso-mfa/bootstrap/sops-write.py encrypt
sops-rotate: ## Rotate SOPS recipients after updating .sops.yaml: make sops-rotate FILE=secrets/foo.yaml
@[[ -n "$(FILE)" ]] || (echo "Usage: make sops-rotate FILE=secrets/path/to/file.yaml"; exit 1)
sops --rotate --in-place $(FILE)
@echo "✔ Recipients rotated for $(FILE)"
sops-decrypt: ## Decrypt age-only JSON/YAML to new private OUTPUT outside Git; explicit custody required
@python3 sso-mfa/bootstrap/sops-private.py decrypt
sops-custody-check: ## Validate a custody age key against keys/age.pub without keeping it on disk
sops-rotate: ## Rotate the SOPS data key; recipient changes require a separate reviewed procedure
@python3 sso-mfa/bootstrap/sops-write.py rotate
sops-custody-check sops-custody-shell sops-custody-run: export NK_CUSTODY_RECIPIENT := $(value OPERATOR_AGE_PUBKEY)
sops-custody-run: export NK_CUSTODY_COMMAND := $(value COMMAND)
sops-custody-check: ## Validate a custody age key without keeping it on disk
@bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient "$(OPERATOR_AGE_PUBKEY)" \
--check-only
--expected-recipient "$$NK_CUSTODY_RECIPIENT" --check-only
sops-custody-shell: ## Open a shell with a temporary SOPS_AGE_KEY_FILE from custody material
@bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient "$(OPERATOR_AGE_PUBKEY)"
--expected-recipient "$$NK_CUSTODY_RECIPIENT"
sops-custody-run: ## Run COMMAND with a temporary custody age key: make sops-custody-run COMMAND='make -C ../inter-hub recovery-drill'
@[[ -n "$(COMMAND)" ]] || (echo "Usage: make sops-custody-run COMMAND='make -C ../inter-hub recovery-drill'"; exit 1)
@COMMAND='$(COMMAND)' bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient "$(OPERATOR_AGE_PUBKEY)" \
-- bash -lc "$$COMMAND"
sops-custody-run: ## Run COMMAND inside the temporary custody handoff
@[[ -n "$$NK_CUSTODY_COMMAND" ]] || (echo "Usage: make sops-custody-run COMMAND='<approved command>'"; exit 1)
@bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient "$$NK_CUSTODY_RECIPIENT" -- bash -lc "$$NK_CUSTODY_COMMAND"
check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted
@echo "Checking for unencrypted files under secrets/..."
@ -110,7 +100,7 @@ check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted
done < <(git ls-files --others --cached 'secrets' 2>/dev/null | grep -v '/$$'); \
if [[ "$$bad" -ne 0 ]]; then \
echo ""; \
echo "ERROR: Unencrypted secret(s) detected. Encrypt with: sops --encrypt --in-place <file>"; \
echo "ERROR: Unencrypted file detected. For private untracked input: make sops-encrypt FILE=<file>; tracked plaintext requires incident review."; \
exit 1; \
fi; \
echo "✔ All secrets/ files appear SOPS-encrypted"
@ -123,18 +113,16 @@ creds-init: sops-setup hooks ## One-time setup: verify prerequisites, configure
@command -v kubectl >/dev/null 2>&1 \
&& echo "✔ kubectl found" \
|| echo "WARN: kubectl not found (required for creds-apply/verify)"
@test -f ~/.config/sops/age/keys.txt \
|| (echo "ERROR: age key not found — run 'make sops-setup' after placing your key at ~/.config/age/key.txt"; exit 1)
@echo ""
@echo "✔ creds-init complete. Next: make creds-generate"
creds-generate: ## Generate all service secrets and print KeePassXC entry guide
creds-generate: ## Generate private service files for public-recipient encryption
@command -v openssl >/dev/null 2>&1 || (echo "ERROR: openssl not found"; exit 1)
cd sso-mfa/bootstrap && bash gen-secrets.sh
@sed -i "s|^generated_at: .*|generated_at: $$(date -Iseconds)|" sso-mfa/bootstrap/creds-state.yaml
@echo ""
@echo "State: generated_at updated in sso-mfa/bootstrap/creds-state.yaml"
@echo "Next : enter values into KeePassXC, then set keepass_confirmed: true"
@echo "Next : encrypt to the registered public recipient; never copy values into transcripts."
@echo " Run 'make creds-bundle' to create an encrypted offsite backup."
creds-bundle: ## Age-encrypt the ops bundle for offsite storage
@ -151,33 +139,25 @@ creds-bundle: ## Age-encrypt the ops bundle for offsite storage
creds-apply: ## Apply all create-secrets.sh scripts to the cluster in dependency order
bash sso-mfa/bootstrap/creds-apply.sh
creds-verify: ## Check all expected K8s secrets exist and update creds-state.yaml
creds-verify: ## Observe current Secret presence without rewriting native acceptance
bash sso-mfa/bootstrap/creds-verify.sh
creds-status: ## Print human-readable credential state from creds-state.yaml
bash sso-mfa/bootstrap/creds-status.sh
creds-rotate: export SECRET := $(SECRET)
creds-rotate: ## Guided rotation for one secret: make creds-rotate SECRET=<name>
@[[ -n "$(SECRET)" ]] \
|| (echo "Usage: make creds-rotate SECRET=<name>"; \
echo ""; \
echo "Known secrets:"; \
echo " PI_SECRET_KEY PI_PEPPER PI_DB_PASSWORD LLDAP_JWT_SECRET"; \
echo " LLDAP_LDAP_USER_PASS AUTHELIA_SESSION_SECRET"; \
echo " AUTHELIA_KEYCAPE_CLIENT_SECRET KEYCAPE_RSA_KEY"; \
echo " BREAKGLASS_PASSWORD"; \
exit 1)
SECRET=$(SECRET) bash sso-mfa/bootstrap/creds-rotate.sh
@bash sso-mfa/bootstrap/creds-rotate.sh
## ── Agent-driven credential lifecycle (NK-WP-0005) ──────────────────────────
creds-agent-init: ## Fully automated credential bootstrap — generates, encrypts, injects, delivers emergency bundle
creds-agent-init: ## Attended bootstrap using the public recipient and encrypted recovery handoff
@bash sso-mfa/bootstrap/creds-bootstrap-agent.sh
creds-agent-status: ## Show current v2 bootstrap state (agent mode)
@bash sso-mfa/bootstrap/creds-status.sh --v2
creds-emergency-reprint: ## Re-deliver emergency bundle (if lost/stolen — reprints, rotates nothing)
creds-emergency-reprint: ## Print the non-secret recovery guide; no keys or passwords are read
@bash sso-mfa/bootstrap/emergency-bundle.sh --reprint
openbao-init-unseal: ## SOPS-held OpenBao init/unseal (NET-WP-0020 T2; requires sops-held-automation model selected)
@ -217,6 +197,35 @@ posture-feedback-test: ## Run deterministic posture-feedback tests
custody-record-test: ## Run custody-record schema/validator tests (NK-WP-0045-T03)
python3 -m pytest tools/custody-record/tests
service-identity-test: ## Run service-identity schema/validator tests (NK-WP-0054-T03)
python3 -m pytest tools/service-identity/tests
service-identity-check: ## Evaluate the checked-in service identity declarations (read-only, offline)
python3 tools/service-identity/validate.py tools/service-identity/declarations/*.yaml
bounded-execution-admission-test: ## Run bounded-execution admission validator tests (NK-WP-0056-T01)
python3 -m pytest tools/bounded-execution-admission/tests
bounded-execution-admission-check: ## Validate the example admissions (read-only, offline)
python3 tools/bounded-execution-admission/validate.py validate examples/bounded-execution-admission/*.yaml
bounded-executor-test: ## Bounded executor RBAC and declaration tests; the pilot admission inside its declared grants (NK-WP-0056-T03)
python3 -m pytest sso-mfa/k8s/bounded-executor bounded-executor/tests tests/test_executor_lanes.py
python3 tools/service-identity/validate.py tools/service-identity/declarations/bounded-executor.yaml \
--admissions examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml
bind-split-test: ## LLDAP bind-split helper tests on a throwaway pinned LLDAP (NK-WP-0054-T05; needs docker)
uv run --with pytest --with ldap3 --with pyyaml python -m pytest sso-mfa/k8s/lldap/test_bind_split.py sso-mfa/k8s/lldap/test_identity_provisioner_reconcile.py
rotation-lanes-test: ## Run rotation-lane register tests (NK-WP-0055-T15)
python3 -m pytest tools/rotation-lanes/tests
rotation-lanes-check: ## Check every known credential has a rotation lane (read-only, offline)
python3 tools/rotation-lanes/check.py
security-tools: ## List the security tools, what each answers, and whether an agent may run it
python3 tools/security_tools.py
posture-feedback-reference: ## Emit the proposal-only reference report at its fixed example time
python3 tools/posture-feedback/posture_feedback.py \
--as-of 2026-08-23T12:00:00Z \
@ -234,7 +243,7 @@ security-bootstrap-console-test: ## Run automated tests for bootstrap console UI
security-bootstrap-scripts-syntax: ## Shell syntax check for bootstrap scripts
bash -n sso-mfa/k8s/lldap/dry-run-nonroot-user.sh
bash -n sso-mfa/k8s/lldap/create-user.sh
bash -n sso-mfa/k8s/lldap/break-glass.sh || true # may have env assumptions
bash -n sso-mfa/k8s/lldap/break-glass.sh
@echo "✔ bootstrap scripts syntax OK"
security-bootstrap-validate-keycape-client: ## Validate KeyCape OpenBao client definition+deployment (T08)
@ -297,13 +306,11 @@ security-bootstrap-lifecycle-guide: ## Print the practical T05 operator flow gui
security-bootstrap-onboarding-dry-run-template: ## Print non-secret NET-WP-0017-T06 onboarding dry-run evidence JSON template (use to start T06 evidence after running the lifecycle flow)
python3 tools/security-bootstrap-console/security_bootstrap_console.py onboarding-dry-run-template
security-bootstrap-onboarding-dry-run: ## Run the T06 non-root dry-run orchestrator (see sso-mfa/k8s/lldap/dry-run-nonroot-user.sh and NET-WP-0019). Usage: make ... SUBJECT=foo EMAIL=foo@ex.com DISPLAY="Foo"
@cd sso-mfa/k8s/lldap && \
./dry-run-nonroot-user.sh $(or $(SUBJECT),t06-dryrun) $(or $(EMAIL),t06-dryrun@coulomb.social) "$(or $(DISPLAY),T06 Dry Run User)" --actor user --scope none
security-bootstrap-onboarding-dry-run: ## Retired: see docs/legacy-lldap-lifecycle-retirement.md
@bash sso-mfa/k8s/lldap/dry-run-nonroot-user.sh
security-bootstrap-lifecycle-cleanup-dryrun-users: ## Clean up dry-run/test users by pattern (NET-WP-0019 T04). Usage: make ... PATTERN=t06-*
@cd sso-mfa/k8s/lldap && \
./dry-run-nonroot-user.sh --cleanup-only "$(or $(PATTERN),t06-*)"
security-bootstrap-lifecycle-cleanup-dryrun-users: ## Retired: see docs/legacy-lldap-lifecycle-retirement.md
@bash sso-mfa/k8s/lldap/dry-run-nonroot-user.sh
security-bootstrap-validate-custody-roster: ## Validate and verify the signed local custody roster
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
@ -381,13 +388,14 @@ security-bootstrap-ui: security-bootstrap-metadata-init ## Serve local custody a
--host "$(SECURITY_BOOTSTRAP_HOST)" \
--port "$(SECURITY_BOOTSTRAP_PORT)"
.PHONY: runbooks-validate help hooks hooks-test sops-setup sops-edit sops-encrypt sops-decrypt sops-rotate \
.PHONY: runbooks-validate bind-split-test rotation-lanes-test rotation-lanes-check help hooks hooks-test sops-setup sops-edit sops-encrypt sops-decrypt sops-rotate \
check-secrets creds-init creds-generate creds-bundle creds-apply creds-verify \
creds-status creds-rotate \
creds-agent-init creds-agent-status creds-emergency-reprint \
openbao-init-unseal openbao-init-unseal-dry-run \
iam-profile-conformance-test playbook-contract-test posture-feedback-test \
custody-record-test \
custody-record-test service-identity-test service-identity-check security-tools \
bounded-execution-admission-test bounded-execution-admission-check bounded-executor-test \
emission-cadence-profile-test \
posture-feedback-reference security-scenario-compose-lightweight \
security-bootstrap-console-test security-bootstrap-scripts-syntax \
@ -416,3 +424,16 @@ security-bootstrap-ui: security-bootstrap-metadata-init ## Serve local custody a
security-bootstrap-metadata-init security-bootstrap-ui \
security-bootstrap-console-test security-bootstrap-scripts-syntax \
security-bootstrap-validate-keycape-client
# CUST-WP-0079-T05: staged-source generation; hook uses the isolated recipe.
.PHONY: generated-work-records generated-work-records-check
generated-work-records:
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records
generated-work-records-check:
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records-check
.PHONY: coordination-frame-staged coordination-frame-staged-check
coordination-frame-staged:
python3 tools/coordination-frame/staged.py --repo .
coordination-frame-staged-check:
python3 tools/coordination-frame/staged.py --repo . --check

View file

@ -15,7 +15,7 @@ The dynamic, self-optimizing security platform is the long-term direction in
- **[SECURITY-COMPANION.md](SECURITY-COMPANION.md) — start here.** The working
form of the security layer model: what to declare, what binds you, what you may
never claim about evidence, and the two things the estate cannot do yet
- [Security layer model](canon/standards/security-layer-model_v0.7.md) — the
- [Security layer model](canon/standards/security-layer-model_v0.8.md) — the
statute the companion serves (accepted 2026-08-29): how the security estate is
layered (Taxonomy / Tooling / Engines / Staff) and what each layer may own
- [Security scenario composition](canon/standards/security-scenario-composition_v0.1.md)

View file

@ -227,3 +227,38 @@ keywords: [meta-orchestration, railiance, responsibility, capability, trust-stat
`tools/playbook-capability-contract/`, `tools/security-scenario-composer/`,
`tools/tenancy-posture/`, and `tools/posture-feedback/`
- Work state: `.custodian-brief.md` and `workplans/`
## Repository maintenance reuse
The tracked `.generated-work-records.json` adopts the Custodian staged-source
adapter for WORK-RECORDS.md. `make generated-work-records` uses the existing
State Hub renderer through `.generated-work-records.mk`; the shared pre-commit
hook runs this isolated recipe only for opted-in repositories. The corresponding
`make generated-work-records-check` is read-only. Canonical repository identity
is explicit and does not depend on the checkout directory.
Unstaged source changes are preserved; conflicts and unstaged generated-file
changes stop the commit. For `git commit --only`, generate first and include
WORK-RECORDS.md with the selected source paths. Existing generated-only guards
and exact-scope secret scans remain in place. This is repository maintenance,
not a runtime service, authorization decision, deployment or publication.
Other index generators and hub-derived briefs retain their owner boundaries.
## Coordination receiver generation — 2026-10-19
The shared opt-in commit recipe now compiles the local proposed Approval Engine
receiver from staged NetKingdom/coordination-engine/approval-engine sources,
then renders WORK-RECORDS. Dedicated Make generation/check targets preserve
unstaged authored work and refuse receiver conflicts or alternate-index writes.
Only the local receiver is staged; sibling sources and runtime registries are
untouched. Missing owner source checkouts fail closed. Multi-owner binding
publication and actual receiver activation retain their existing owner gates.
## Local orientation brief
`.custodian-brief.md` is a generated local orientation snapshot. It remains on
disk for offline use and is root-ignored rather than versioned. Regenerate it
with the State Hub brief renderer; workplans and other authored repository
records remain authoritative. Brief refreshes must not create Git commits.

View file

@ -1,23 +1,26 @@
---
id: netkingdom-security-companion-v0.2
id: netkingdom-security-companion-v0.3
type: standard-companion
title: "NetKingdom Security — Working Companion v0.2"
title: "NetKingdom Security — Working Companion v0.3"
domain: netkingdom
status: accepted
version: "0.2"
companion_to: canon/standards/security-layer-model_v0.7.md
version: "0.3"
companion_to: canon/standards/security-layer-model_v0.8.md
derived: true
derived_from: canon/standards/security-layer-model_v0.8.md
canonical_source_digest: sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b
owner: gate-house
publication_owner: net-kingdom
created: "2026-08-29"
updated: "2026-08-29"
updated: "2026-10-11"
review_interval: 3m
standard_token: security-companion_v0.2
standard_token: security-companion_v0.3
---
# NetKingdom Security — Working Companion
**Start here.** This is the operative form of
[`canon/standards/security-layer-model_v0.7.md`](canon/standards/security-layer-model_v0.7.md)
[`canon/standards/security-layer-model_v0.8.md`](canon/standards/security-layer-model_v0.8.md)
(accepted): the same rules, without the change log or the review history.
The statute governs where the two disagree. **A disagreement is a finding** —
@ -56,6 +59,13 @@ layer: Staff # Taxonomy | Tooling | Engine | Staff
role: null # Engines only: PDP | PIP | Evidence | Lifecycle
```
The four layer tokens are closed and compared without ASCII case sensitivity.
INTENT frontmatter is authoritative; a sidecar is derived, and disagreement is a
finding. Declaration keys/values must not pin a standard or companion version;
prose citations record provenance. Every checker run names the standard it
validated against and its explicit scope, including a single-repository scope.
Stance maps, claim declarations and emission guarantees are distinct records.
Working references, both offered estate-wide: `ops-warden`'s `layer.yaml`,
`scripts/check_layer_conformance.py`, `tests/test_layer_conformance.py`;
`kings-guard`'s adaptation of the same for a repository with no Tooling
@ -157,6 +167,13 @@ evidence. The mechanically checkable overlay is
it imports the InfoTechCanon contract assigned by `GH-DEC-2026-004` and
remains proposed pending owner-instance validation.
Marked sources publish an inventory per event class with the declared emission
guarantee; evidence-source markings distinguish yes, no and unassessed. Where a
load-bearing guarantee is not itself completeness, explicitly state that
completeness is not claimed. Load-bearing guarantees state controls, not their
sufficiency; neither detection nor the archive proves completeness. A failure-path release
exception is not admitted by this standard.
Otherwise evidence is **attributive**: seek atomicity, and if you trade it away
deliberately, declare the trade and never describe the trail as complete.
@ -219,5 +236,5 @@ Stated so nobody plans around a capability that does not exist:
---
**Statute:** [`canon/standards/security-layer-model_v0.7.md`](canon/standards/security-layer-model_v0.7.md) — accepted 2026-08-29.
**Statute:** [`canon/standards/security-layer-model_v0.8.md`](canon/standards/security-layer-model_v0.8.md) — accepted through memo version 2 at 2026-10-10T23:23:53Z.
**Owner:** `gate-house`. **Guidance on getting things done:** `ops-warden`.

View file

@ -12,36 +12,48 @@
| workplan | NK-WP-ADHOC-2026-08-14 | finished | — | workplans/ADHOC-2026-08-14.md |
| workplan | NK-WP-ADHOC-2026-08-23 | finished | — | workplans/ADHOC-2026-08-23.md |
| workplan | NK-WP-ADHOC-2026-10-03 | finished | — | workplans/ADHOC-2026-10-03.md |
| workplan | NK-WP-ADHOC-2026-10-10 | finished | — | workplans/ADHOC-2026-10-10.md |
| workplan | NK-WP-ADHOC-2026-10-11 | finished | — | workplans/ADHOC-2026-10-11.md |
| workplan | NK-WP-ADHOC-2026-10-16 | finished | — | workplans/ADHOC-2026-10-16.md |
| workplan | NET-WP-0020 | finished | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| workplan | NK-WP-0009 | finished | — | workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md |
| workplan | NK-WP-0011 | blocked | — | workplans/NK-WP-0011-enterprise-federation-saml.md |
| workplan | NK-WP-0021 | finished | — | workplans/NK-WP-0021-activity-core-ops-sso-operators.md |
| workplan | NK-WP-0022 | blocked | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| workplan | NK-WP-0022 | finished | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| workplan | NK-WP-0023 | finished | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| workplan | NK-WP-0024 | finished | — | workplans/NK-WP-0024-user-engine-portal-integration-expansion.md |
| workplan | NK-WP-0025 | finished | — | workplans/NK-WP-0025-public-self-registration-and-application-jit.md |
| workplan | NK-WP-0026 | finished | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| workplan | NK-WP-0027 | blocked | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| workplan | NK-WP-0027 | backlog | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| workplan | NK-WP-0028 | finished | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| workplan | NK-WP-0029 | finished | — | workplans/NK-WP-0029-scope-and-intent-reconciliation.md |
| workplan | NK-WP-0030 | finished | — | workplans/NK-WP-0030-deterministic-security-scenario-composition.md |
| workplan | NK-WP-0031 | blocked | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| workplan | NK-WP-0031 | finished | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| workplan | NK-WP-0032 | finished | — | workplans/NK-WP-0032-openbao-operator-loopback-callback.md |
| workplan | NK-WP-0033 | finished | — | workplans/NK-WP-0033-keycape-secret-exposure-rotation.md |
| workplan | NK-WP-0034 | finished | — | workplans/NK-WP-0034-verification-that-verifies.md |
| workplan | NK-WP-0035 | blocked | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| workplan | NK-WP-0035 | active | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| workplan | NK-WP-0036 | finished | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
| workplan | NK-WP-0037 | finished | — | workplans/NK-WP-0037-vergabe-company-welcome.md |
| workplan | NK-WP-0038 | finished | — | workplans/NK-WP-0038-tenant-scoped-identity-lifecycle.md |
| workplan | NK-WP-0039 | finished | — | workplans/NK-WP-0039-flex-auth-access-engine-coordinate-intake.md |
| workplan | NK-WP-0040 | blocked | — | workplans/NK-WP-0040-execution-attribution-receipt.md |
| workplan | NK-WP-0040 | active | — | workplans/NK-WP-0040-execution-attribution-receipt.md |
| workplan | NK-WP-0041 | finished | — | workplans/NK-WP-0041-onboarding-journey-usability.md |
| workplan | NK-WP-0042 | blocked | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| workplan | NK-WP-0042 | finished | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| workplan | NK-WP-0043 | finished | — | workplans/NK-WP-0043-cadence-assessment-and-operating-guidance.md |
| workplan | NK-WP-0044 | finished | — | workplans/NK-WP-0044-runbook-packs-for-runbook-tutorials.md |
| workplan | NK-WP-0045 | finished | — | workplans/NK-WP-0045-root-grant-custody-record.md |
| workplan | NK-WP-0046 | active | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| workplan | NK-WP-0047 | active | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| workplan | NK-WP-0046 | finished | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| workplan | NK-WP-0047 | finished | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| workplan | NK-WP-0048 | blocked | — | workplans/NK-WP-0048-security-case-types.md |
| workplan | NK-WP-0049 | finished | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| workplan | NK-WP-0050 | blocked | — | workplans/NK-WP-0050-playwright-consumer-registration.md |
| workplan | NK-WP-0051 | finished | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| workplan | NK-WP-0052 | finished | — | workplans/NK-WP-0052-wave2-resource-right-sizing.md |
| workplan | NK-WP-0053 | finished | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md |
| workplan | NK-WP-0054 | active | — | workplans/NK-WP-0054-service-identity-policy.md |
| workplan | NK-WP-0055 | active | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| workplan | NK-WP-0056 | proposed | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
| task | NK-WP-ADHOC-2026-07-02-T02 | done | — | workplans/ADHOC-2026-07-02.md |
| task | NK-WP-ADHOC-2026-08-14-T01 | done | — | workplans/ADHOC-2026-08-14.md |
@ -49,6 +61,15 @@
| task | NK-WP-ADHOC-2026-08-14-T03 | done | — | workplans/ADHOC-2026-08-14.md |
| task | NK-WP-ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md |
| task | NK-WP-ADHOC-2026-10-03-T01 | done | — | workplans/ADHOC-2026-10-03.md |
| task | NK-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md |
| task | NK-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T03 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T04 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T05 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T06 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-11-T07 | done | — | workplans/ADHOC-2026-10-11.md |
| task | NK-WP-ADHOC-2026-10-16-T01 | done | — | workplans/ADHOC-2026-10-16.md |
| task | NET-WP-0020-T01 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| task | NET-WP-0020-T02 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
| task | NET-WP-0020-T03 | done | — | workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md |
@ -81,7 +102,7 @@
| task | NK-WP-0022-T05 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| task | NK-WP-0022-T06 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| task | NK-WP-0022-T07 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| task | NK-WP-0022-T08 | wait | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| task | NK-WP-0022-T08 | done | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| task | NK-WP-0023-T01 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| task | NK-WP-0023-T02 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
| task | NK-WP-0023-T03 | done | — | workplans/NK-WP-0023-user-engine-portal-platform-integration.md |
@ -102,11 +123,11 @@
| task | NK-WP-0026-T01 | done | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| task | NK-WP-0026-T02 | done | — | workplans/NK-WP-0026-flex-auth-caller-identity-rollout.md |
| task | NK-WP-0027-T01 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T02 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T03 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T02 | todo | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T03 | todo | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T04 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T05 | done | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T06 | wait | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0027-T06 | todo | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| task | NK-WP-0028-T01 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | NK-WP-0028-T02 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
| task | NK-WP-0028-T03 | done | — | workplans/NK-WP-0028-canon-publication-and-grouping-semantics.md |
@ -123,7 +144,7 @@
| task | NK-WP-0031-T01 | done | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0031-T02 | done | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0031-T03 | done | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0031-T04 | wait | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0031-T04 | done | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0031-T05 | done | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| task | NK-WP-0032-T01 | done | — | workplans/NK-WP-0032-openbao-operator-loopback-callback.md |
| task | NK-WP-0032-T02 | done | — | workplans/NK-WP-0032-openbao-operator-loopback-callback.md |
@ -143,6 +164,8 @@
| task | NK-WP-0035-T03 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T04 | wait | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T05 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T06 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T07 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0036-T01 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
| task | NK-WP-0036-T02 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
| task | NK-WP-0036-T03 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
@ -163,7 +186,7 @@
| task | NK-WP-0041-T02 | done | — | workplans/NK-WP-0041-onboarding-journey-usability.md |
| task | NK-WP-0041-T03 | done | — | workplans/NK-WP-0041-onboarding-journey-usability.md |
| task | NK-WP-0042-T01 | done | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| task | NK-WP-0042-T02 | wait | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| task | NK-WP-0042-T02 | cancel | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| task | NK-WP-0043-T01 | done | — | workplans/NK-WP-0043-cadence-assessment-and-operating-guidance.md |
| task | NK-WP-0043-T02 | done | — | workplans/NK-WP-0043-cadence-assessment-and-operating-guidance.md |
| task | NK-WP-0043-T03 | done | — | workplans/NK-WP-0043-cadence-assessment-and-operating-guidance.md |
@ -182,11 +205,168 @@
| task | NK-WP-0045-T06 | done | — | workplans/NK-WP-0045-root-grant-custody-record.md |
| task | NK-WP-0046-T01 | done | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| task | NK-WP-0046-T02 | done | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| task | NK-WP-0046-T03 | wait | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| task | NK-WP-0046-T03 | done | — | workplans/NK-WP-0046-secrets-engine-openbao-client-registration.md |
| task | NK-WP-0047-T01 | done | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| task | NK-WP-0047-T02 | done | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| task | NK-WP-0047-T03 | wait | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| task | NK-WP-0047-T03 | done | — | workplans/NK-WP-0047-platform-password-reset-mail.md |
| task | NK-WP-0048-T01 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T02 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T03 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T04 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T05 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T06 | wait | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T07 | wait | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T08 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T09 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T10 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T11 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T12 | wait | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T13 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T14 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T15 | wait | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T16 | wait | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T17 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T18 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T19 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T20 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T21 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T22 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T23 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T24 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T25 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T26 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T27 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T28 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T29 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T30 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T31 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T32 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T33 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T34 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T35 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T36 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T37 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T38 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T39 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T40 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T41 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T42 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T43 | progress | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0048-T44 | done | — | workplans/NK-WP-0048-security-case-types.md |
| task | NK-WP-0049-T01 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T02 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T03 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T04 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T05 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T06 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T07 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T08 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T09 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T10 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T11 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T12 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T13 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T14 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T15 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T16 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T17 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T18 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T19 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T20 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T21 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T22 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T23 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T24 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T25 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T26 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T27 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T28 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T29 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T30 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T31 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T32 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T33 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T34 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T35 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T36 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T37 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T38 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T39 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T40 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T41 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T42 | done | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T43 | cancel | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0049-T44 | cancel | — | workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md |
| task | NK-WP-0050-T01 | done | — | workplans/NK-WP-0050-playwright-consumer-registration.md |
| task | NK-WP-0050-T02 | progress | — | workplans/NK-WP-0050-playwright-consumer-registration.md |
| task | NK-WP-0050-T03 | wait | — | workplans/NK-WP-0050-playwright-consumer-registration.md |
| task | NK-WP-0051-T01 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0051-T02 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0051-T03 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0051-T04 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0051-T05 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0051-T06 | done | — | workplans/NK-WP-0051-sign-in-diagnosis.md |
| task | NK-WP-0052-T01 | done | — | workplans/NK-WP-0052-wave2-resource-right-sizing.md |
| task | NK-WP-0053-T01 | done | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md |
| task | NK-WP-0053-T02 | done | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md |
| task | NK-WP-0053-T03 | done | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md |
| task | NK-WP-0053-T04 | done | — | workplans/NK-WP-0053-identity-restore-drill-cadence.md |
| task | NK-WP-0054-T01 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T02 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T03 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T04 | wait | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T05 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T06 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T07 | progress | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T08 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T09 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T10 | progress | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T11 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T12 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T13 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T14 | done | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T15 | wait | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0054-T16 | wait | — | workplans/NK-WP-0054-service-identity-policy.md |
| task | NK-WP-0055-T01 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T02 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T03 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T04 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T05 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T06 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T07 | todo | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T08 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T09 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T10 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T11 | progress | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T12 | todo | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T13 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T14 | todo | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T15 | done | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0055-T16 | wait | — | workplans/NK-WP-0055-security-platform-production-readiness.md |
| task | NK-WP-0056-T01 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T02 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T03 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T04 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T05 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T06 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T07 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T08 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T09 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T10 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T11 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T12 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T13 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T14 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T15 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T16 | wait | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T17 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T18 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T19 | todo | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T20 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| task | NK-WP-0056-T21 | done | — | workplans/NK-WP-0056-bounded-executor-path.md |
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NET-IN-0001 | open | — | intakes/intakes.md |
| intake | NET-IN-0002 | open | — | intakes/intakes.md |
| intake | NET-IN-0003 | open | — | intakes/intakes.md |
| intake | NET-IN-0004 | open | — | intakes/intakes.md |

View file

@ -0,0 +1,18 @@
# Bounded executor image (NK-WP-0056-T13). Build context: the repository root.
# docker build -f bounded-executor/Containerfile .
# lldap_set_password comes from the same pinned LLDAP image production runs, so OPAQUE matches.
FROM docker.io/lldap/lldap@sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d AS lldap
FROM python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9
RUN useradd --create-home --uid 10001 executor
WORKDIR /app
COPY --from=lldap /app/lldap_set_password /app/lldap_set_password
COPY bounded-executor/requirements.txt /app/bounded-executor/requirements.txt
RUN python -m pip install --no-cache-dir --require-hashes -r /app/bounded-executor/requirements.txt
# The executed files keep their repository paths: admissions pin them by path and sha256.
COPY canon/schemas/bounded-execution-admission_v0.1.schema.json /app/canon/schemas/
COPY tools/bounded-execution-admission/validate.py /app/tools/bounded-execution-admission/
COPY sso-mfa/k8s/synthetic-journey/journey.py /app/sso-mfa/k8s/synthetic-journey/
COPY bounded-executor/executor.py bounded-executor/operations.py bounded-executor/clients.py bounded-executor/cli.py /app/bounded-executor/
USER 10001:10001
ENV PYTHONDONTWRITEBYTECODE=1
ENTRYPOINT ["python", "/app/bounded-executor/cli.py"]

View file

@ -0,0 +1,62 @@
# Bounded executor (NK-WP-0056)
Runs one founder-approved Bounded Execution Admission (`canon/standards/bounded-execution-admission_v0.1.md`)
exactly once. Founder decision 6d1adce9: net-kingdom writes the executor; `rapp-bounded-executor` deploys it as one
Job per admission in `sso`.
`executor.py` is the core runner (T11):
- **preflight, no effect:**
- the admission is valid (BEA-1..14);
- the executed files match their pins;
- the trust states hold;
- every operation and check is known;
- the window is open and longer than the wall-time budget;
- the approval-engine approval binds this admission's digest, names this executor as actor and was approved
only by listed HUMAN approvers at aal2. The name-to-subject map is configured; no guessing.
- **consume once:** `request_digest` = admission digest, `decision_id` = run id. An unclear outcome is resolved by
reading the approval back, never by a second consume.
- **custody:** emit `execution.run.started` to audit-core. If the event can't be stored, the run stops.
- **steps:** in plan order, one consumer at a time. Checks run after every mutating step, after their settle time.
The kill switch and the budget are checked between steps.
- **failure:**
- A failed check, an exhausted budget or the kill switch reverts in reverse order.
- Each revert must pass its own checks.
- A clean revert may retry within `attempts`.
- An outcome that can't be determined stops without guessing.
- A failed revert stops and alerts.
- **receipt:** one `execution.attribution.receipt` per run, with PDP `not-evaluated` in the pilot. It never holds a
value that an operation handled.
Operations, checks, the approval carrier, the trust oracle, the audit sink, alerts and clocks are injected.
`operations.py` (T12) holds the pilot's real operations:
- OpenBao KV v2 with CAS;
- LLDAP self-rotation;
- ExternalSecret force-sync and its status;
- rollout restart, where a rollout that never becomes ready is a definite failure and is reverted;
- the synthetic journey check.
An operation signals a known-bad outcome with `definite_failure`, and the runner reverts. Any other error stops
without guessing.
`synthetic_pilot.py` runs a rotation and a deliberate revert on the T08 stack plus OpenBao:
`uv run bounded-executor/synthetic_pilot.py`.
`cli.py --journey-only` (T10) is a read-only run with no admission. It uses the journey-read role only (CCR-2026-0059),
runs the synthetic journey once and prints its value-free receipt. Exit codes: `0` pass, `1` fail closed, `4` refused.
`synthetic_journey_only.py` proves it on the T08 stack: it passes when healthy and fails closed on a broken bind.
`cli.py` (T13) is the Job's entry point: one admission, one approval id, a non-secret config. Exit codes: `0`
completed, `3` reverted, `4` refused before any effect, `5` stopped, unknown or failed revert, `6` consumed but not in
custody. `Containerfile` builds the image, which `.forgejo/workflows/bounded-executor-image.yaml` publishes by digest.
Deployment is in `rapp-bounded-executor`, which pins the digest and renders one ConfigMap and Job per admission
(`make run`, founder-run). That package also holds the ServiceAccount and Role, copied from
`sso-mfa/k8s/bounded-executor/rbac.yaml`, and the executor's egress-only NetworkPolicy. The receiving side in `sso` is
`allow-bounded-executor-to-*` in `sso-mfa/k8s/network-policies/netpol-sso.yaml`.
```bash
python3 -m pytest bounded-executor/tests # or: make bounded-executor-test
```

340
bounded-executor/cli.py Normal file
View file

@ -0,0 +1,340 @@
#!/usr/bin/env python3
"""Run one admission (NK-WP-0056-T13): the entry point of the rapp-bounded-executor Job.
python3 bounded-executor/cli.py --admission FILE --approval-id ID --config FILE [--result FILE]
The config (JSON, non-secret) names endpoints, token files and the pilot's targets:
executor_identity, runtime_principal, tenant, environment, approver_subjects{name: subject_id},
trust_satisfied[...] trust states the operator attests for this run (recorded)
approval_engine{url, token_file, ca_file?}
audit_core{url, token_file, ca_file?}
openbao{addr, k8s_role, jwt_file, ca_file?} bind write role (CCR-2026-0058); jwt_file: audience-openbao token
lldap{url, ca_file?, set_password: "/app/lldap_set_password"}
kubernetes{namespace, external_secret, deployment}
bind{kv, user}, journey{kv, k8s_role, config{...}} journey read role (CCR-2026-0059): one login per lane
Exit codes: 0 completed, 3 reverted, 4 refused before any effect, 5 stopped / unknown / revert failed / budget,
6 consumed but not in custody. The result printed is value-free.
python3 bounded-executor/cli.py --journey-only --config FILE [--result FILE]
python3 bounded-executor/cli.py --request --admission FILE --config FILE [--result FILE]
Request (NK-WP-0056-T18): file the approval for one admission as the executor itself (approval-engine requires
binding.actor == the token's sub). Binding: action bounded-execution, actor = executor_identity, principal = the
first listed approver's subject, target {admission_id, admission_digest}, validity = the admission window,
human_control on, required_count = number of listed approvers. Prints the approval id; consumes nothing.
With informed_decision{url, token_file} configured (T21), the approval is then presented to the founder: one memo
through informed-decision's present_existing intake, carrying the admission YAML. Exit codes: 0 requested (and
presented, if configured), 4 refused, 7 requested but the memo was not accepted (resubmitting is idempotent).
approval_engine.client{token_url, client_id, k8s_role, kv, host?} selects the executor's own KeyCape client
(SecurityCaseType oidc-service-client, case nk-bind-executor-approval): the client secret is read once through its
own OpenBao lane (k8s_role, field CLIENT_SECRET), that login is revoked at once, and every approval call exchanges a
fresh token for exactly its scope. approval_engine.token_file remains for fixtures.
Journey-only (NK-WP-0056-T10): read-only, no admission. Logs in with the journey role only (journey.k8s_role, the
openbao-audience token), runs the synthetic journey once and prints its value-free receipt. No write role, no
Kubernetes or directory client, no approval or audit call. Needs only openbao{addr, jwt_file, ca_file?} and
journey{kv, k8s_role, config}. Exit codes: 0 pass, 1 journey failed (fail closed), 4 refused before the journey.
"""
from __future__ import annotations
import argparse
import importlib.util
import json
import pathlib
import sys
HERE = pathlib.Path(__file__).resolve().parent
def _load(name):
spec = importlib.util.spec_from_file_location(name, HERE / f"{name}.py")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
X = _load("executor")
OPS = _load("operations")
C = _load("clients")
EXIT = {"completed": 0, "reverted": 3, "refused": 4}
def approval_carrier(cfg: dict, logins: list):
"""The approval-engine client: the executor's own KeyCape client when configured, else a token file."""
ae, o = cfg["approval_engine"], cfg["openbao"]
if "client" not in ae:
return C.ApprovalEngine(ae["url"], ae["token_file"], ae.get("ca_file"))
c = ae["client"]
lane = OPS.OpenBao(o["addr"], k8s_role=c["k8s_role"], jwt_file=o["jwt_file"], ca_file=o.get("ca_file"))
logins.append(lane)
data, _ = lane.read(c["kv"])
lane.revoke_self() # one read; the role's token is not kept for the run
if set(data) != {"CLIENT_SECRET"} or not isinstance(data["CLIENT_SECRET"], str) or not data["CLIENT_SECRET"]:
raise OPS.OperationError("approval client kv shape")
secret = data["CLIENT_SECRET"]
tokens = C.KeyCapeClientCredentials(c["token_url"], c["client_id"], lambda: secret, host=c.get("host"),
ca_file=c.get("ca_file"))
return C.ApprovalEngine(ae["url"], token_provider=tokens.token, ca_file=ae.get("ca_file"))
def distinct_roles(cfg: dict) -> None:
roles = [cfg["openbao"].get("k8s_role"), cfg.get("journey", {}).get("k8s_role"),
cfg.get("approval_engine", {}).get("client", {}).get("k8s_role")]
roles = [r for r in roles if r]
if len(roles) != len(set(roles)):
raise ValueError("every lane must use its own OpenBao role")
def build(cfg: dict, logins: list):
"""`logins` collects every OpenBao client as soon as it exists, so the caller revokes them all, even when a
later step of setup fails."""
o = cfg["openbao"]
if cfg["journey"]["k8s_role"] == o["k8s_role"]:
raise ValueError("the journey read and the bind write must use separate roles")
distinct_roles(cfg)
OPS.wait_reachable(o["addr"], ca_file=o.get("ca_file")) # new pod: NetworkPolicy may not be applied yet
def login(role: str):
client = OPS.OpenBao(o["addr"], k8s_role=role, jwt_file=o["jwt_file"], ca_file=o.get("ca_file"))
logins.append(client)
return client
bao = login(o["k8s_role"])
journey_bao = login(cfg["journey"]["k8s_role"])
directory = OPS.Directory(cfg["lldap"]["url"], [cfg["lldap"].get("set_password", "/app/lldap_set_password")],
ca_file=cfg["lldap"].get("ca_file"))
kube = OPS.Kube()
k = cfg["kubernetes"]
operations, checks = OPS.pilot_registry(
bao=bao, journey_bao=journey_bao, directory=directory, delivery=OPS.KubeDelivery(kube, k["namespace"], k["external_secret"]),
restart=OPS.KubeRestart(kube, k["namespace"], k["deployment"]), bind_kv=cfg["bind"]["kv"],
bind_user=cfg["bind"]["user"], journey_kv=cfg["journey"]["kv"], journey_config=cfg["journey"]["config"])
trusted = set(cfg.get("trust_satisfied", []))
def alert(kind: str, info: dict) -> None:
print(json.dumps({"alert": kind, **info}), file=sys.stderr, flush=True)
deps = X.Deps(carrier=approval_carrier(cfg, logins),
operations=operations, checks=checks, trust=lambda state: state in trusted,
audit=C.AuditCore(cfg["audit_core"]["url"], cfg["audit_core"]["token_file"],
cfg["audit_core"].get("ca_file")),
alert=alert)
config = X.Config(executor_identity=cfg["executor_identity"], runtime_principal=cfg["runtime_principal"],
tenant=cfg["tenant"], environment=cfg["environment"],
approver_subjects=dict(cfg["approver_subjects"]))
return config, deps
def request_binding(doc: dict, cfg: dict) -> tuple[dict, dict, int]:
"""The approval binding for one admission. Refuses an invalid admission, another executor, an unmapped
approver or a closed window, before anything is sent."""
report = X.bea.evaluate(doc, json.loads(X.bea.SCHEMA.read_text()))
if not report["valid"]:
raise ValueError("admission invalid")
spec = doc["spec"]
if spec["executor"]["identity"] != cfg["executor_identity"]:
raise ValueError("admission names another executor")
approvers = spec["approval"]["approvers"]
principal = cfg["approver_subjects"][approvers[0]]
closes = X.dt.datetime.fromisoformat(spec["window"]["not_after"].replace("Z", "+00:00"))
if X.dt.datetime.now(X.dt.UTC) >= closes:
raise ValueError("admission window closed")
binding = {"action": X.ACTION, "actor": cfg["executor_identity"], "principal": principal,
"purpose": f"{spec['id']}: {spec['title']}"[:200],
"target": {"admission_id": spec["id"], "admission_digest": X.bea.admission_digest(doc)}}
validity = {"not_before": spec["window"]["not_before"], "expires_at": spec["window"]["not_after"]}
return binding, validity, len(approvers)
MEMO_CLASS = "bounded-execution"
REVIEW_GROUP = "net-kingdom-admins"
def admission_memo(doc: dict, admission_text: str, binding: dict, approval: dict) -> dict:
"""The informed-decision intake envelope that presents this executor-filed approval (present_existing profile,
informed-decision docs/decision-services-alignment-answer.md S1). The packet carries the admission YAML itself;
required highlights point at the scope ceiling, the window and budget, and the revert. The intent restates the
approval's binding without the actor (informed-decision takes the actor from its registry)."""
spec = doc["spec"]
approval_id = approval["id"]
memo_id = (spec["id"].lower() + "-" + approval_id.split("-")[0])[:63]
item = memo_id + "-admission"
digest = "sha256:" + X.hashlib.sha256(admission_text.encode("utf-8")).hexdigest()
ceiling = "; ".join(f"{c['mode']} {c['scope']}" for c in spec["scope_ceiling"])
budget = spec["budget"]
def highlight(n, severity, section, note):
return {"id": f"{memo_id}-h{n}", "item_id": item, "severity": severity, "required_ack": True,
"locator": {"kind": "admission", "admission_id": spec["id"], "section": section}, "note": note}
return {
"kind": "informed-decision.memo-intake",
"version": 1,
"idempotency_key": f"nk-bind-executor.{approval_id}",
"memo_class": MEMO_CLASS,
"review_group": REVIEW_GROUP,
"drafted_by": "net-kingdom bounded executor (service:nk-bind-executor)",
"approval_intent": {k: binding[k] for k in ("action", "principal", "purpose", "target")},
"memo": {
"id": memo_id,
"version": 1,
"question": f"Admit {spec['id']}: {spec['title']}?"[:400],
"requested_act": "approve",
"binding_level": "organizational",
"brief": (f"Bounded Execution Admission {spec['id']} (risk {spec['risk_class']}). "
f"Executor {spec['executor']['identity']} may act once, inside "
f"{spec['window']['not_before']} .. {spec['window']['not_after']}, within: {ceiling}. "
f"Budget {budget['wall_time_seconds']} s, {budget['attempts']} attempt(s). "
f"Purpose: {spec['purpose']}")[:20000],
"binding": {
"justification": None,
"principal": {"display_name": f"Named reviewer in {REVIEW_GROUP}", "id": "pending-human-session",
"identifiers": [], "kind": "person", "role": "approver"},
"target": {"environment": "prod", "id": spec["id"], "kind": "bounded-execution-admission",
"label": spec["title"][:200], "requires_new_bind": False},
"terms": None,
},
"step_kind": "approve",
"packet": [{"item_id": item, "label": f"Admission {spec['id']} (YAML)", "hash": digest}],
"highlights": [
highlight(1, "critical", "scope_ceiling",
"Approving lets the executor act once, unattended, within exactly this scope ceiling: "
+ ceiling),
highlight(2, "warning", "window_budget",
f"Window {spec['window']['not_before']} .. {spec['window']['not_after']}; budget "
f"{budget['wall_time_seconds']} s, {budget['attempts']} attempt(s); the executor stops "
"and reverts when a check fails or the budget is exhausted."),
highlight(3, "warning", "plan_revert",
"Each mutating step names its own checks and revert; read the plan before approving."),
],
"locale": "en",
"approval_id": approval_id,
"approval_binding_digest": approval["binding"]["digest"],
"sealed": False,
},
"packet": [{"item_id": item, "media_type": "text/plain", "content": admission_text}],
}
def run_request(admission_path: str, cfg: dict) -> tuple[dict, int]:
logins: list = []
try:
doc = X.bea.load(pathlib.Path(admission_path))
binding, validity, required = request_binding(doc, cfg)
distinct_roles(cfg)
OPS.wait_reachable(cfg["openbao"]["addr"], ca_file=cfg["openbao"].get("ca_file"))
created = approval_carrier(cfg, logins).create(binding, validity, required)
got = created.get("binding") or {}
if (got.get("actor") != binding["actor"] or got.get("target") != binding["target"]
or created.get("status") != "requested" or not created.get("id")):
raise ValueError("created approval does not match the request")
result = {"mode": "request", "outcome": "requested", "approval_id": created["id"],
"binding_digest": got.get("digest"), "admission_id": binding["target"]["admission_id"],
"admission_digest": binding["target"]["admission_digest"], "required_count": required,
"effects": "approval requested"}
code = 0
if "informed_decision" in cfg: # present it to the founder (T21); the approval stays usable either way
idd = cfg["informed_decision"]
envelope = admission_memo(doc, pathlib.Path(admission_path).read_text(), binding, created)
try:
receipt = C.InformedDecision(idd["url"], idd["token_file"], idd.get("ca_file")).submit(envelope)
result.update(outcome="presented", memo_id=receipt["memo_id"],
effects="approval requested; memo submitted to informed-decision")
except Exception as error: # the approval exists; the memo can be resubmitted with the same key
result.update(outcome="requested_not_presented", memo_id=envelope["memo"]["id"],
reason="memo:" + type(error).__name__)
code = 7
except Exception as error: # nothing was consumed; at most an unapproved request exists
result = {"mode": "request", "outcome": "refused", "reason": "request:" + type(error).__name__}
code = 4
finally:
for client in logins:
client.revoke_self()
return result, code
def journey_only(cfg: dict, logins: list) -> dict:
o, j = cfg["openbao"], cfg["journey"]
OPS.wait_reachable(o["addr"], ca_file=o.get("ca_file")) # new pod: NetworkPolicy may not be applied yet
client = OPS.OpenBao(o["addr"], k8s_role=j["k8s_role"], jwt_file=o["jwt_file"], ca_file=o.get("ca_file"))
logins.append(client)
receipt = OPS.journey_from_kv(client, j["kv"], j["config"])
return {"mode": "journey-only", "outcome": "pass" if receipt["result"] == "pass" else "fail",
"effects": False, "journey": receipt}
def run_journey_only(cfg: dict) -> tuple[dict, int]:
logins: list = []
try:
result = journey_only(cfg, logins)
code = 0 if result["outcome"] == "pass" else 1
except Exception as error: # nothing was changed: the journey only reads and signs in
result = {"mode": "journey-only", "outcome": "refused", "reason": "setup:" + type(error).__name__,
"effects": False}
code = 4
finally:
for client in logins:
client.revoke_self()
return result, code
def exit_code(result: dict) -> int:
if result.get("reason") in ("receipt_not_in_custody", "audit_unavailable") and result["outcome"] != "refused":
return 6
return EXIT.get(result["outcome"], 5)
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
parser.add_argument("--journey-only", action="store_true", help="read-only journey run, no admission (T10)")
parser.add_argument("--request", action="store_true", help="file the approval for --admission (T18)")
parser.add_argument("--admission")
parser.add_argument("--approval-id")
parser.add_argument("--config", required=True)
parser.add_argument("--result", help="also write the result here")
args = parser.parse_args(argv)
if args.request:
if args.journey_only or args.approval_id or not args.admission:
parser.error("--request takes --admission and --config only")
result, code = run_request(args.admission, json.loads(pathlib.Path(args.config).read_text()))
_emit(result, args.result)
return code
if args.journey_only == bool(args.admission or args.approval_id):
parser.error("give either --journey-only, or --admission with --approval-id")
if not args.journey_only and not (args.admission and args.approval_id):
parser.error("--admission and --approval-id go together")
cfg = json.loads(pathlib.Path(args.config).read_text())
if args.journey_only:
result, code = run_journey_only(cfg)
_emit(result, args.result)
return code
admission = X.bea.load(pathlib.Path(args.admission))
logins: list = []
try:
config, deps = build(cfg, logins)
result = X.Run(admission, args.approval_id, config, deps).execute()
except Exception as error: # setup failed before preflight: nothing was consumed
result = {"outcome": "refused", "reason": "setup:" + type(error).__name__, "run_id": None, "effects": False}
finally:
for client in logins:
client.revoke_self()
_emit(result, args.result)
return exit_code(result)
def _emit(result: dict, path: str | None) -> None:
text = json.dumps(result, indent=2, sort_keys=True)
if path:
pathlib.Path(path).write_text(text + "\n")
print(text)
if __name__ == "__main__":
sys.exit(main())

179
bounded-executor/clients.py Normal file
View file

@ -0,0 +1,179 @@
"""Clients for the executor's owners (NK-WP-0056-T13): approval-engine (carrier) and audit-core (sink).
Stdlib HTTP only. Bearer tokens are read from files at call time (projected or ESO-delivered), never logged.
Errors carry status codes only, never response bodies.
"""
from __future__ import annotations
import base64
import json
import pathlib
import ssl
import urllib.error
import urllib.parse
import urllib.request
class ClientError(Exception):
pass
class _NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs): # credentials never follow a redirect
return None
def _opener(ca_file: str | None):
context = ssl.create_default_context(cafile=ca_file) if ca_file else ssl.create_default_context()
return urllib.request.build_opener(urllib.request.ProxyHandler({}), _NoRedirect(),
urllib.request.HTTPSHandler(context=context)).open
class KeyCapeClientCredentials:
"""client_credentials exchange at KeyCape (src/internal/server/oidc/token.go: client_secret_basic, every
requested scope must be allowed for the client). A fresh token per call and scope; nothing is cached or logged,
as secrets-engine's approval consumer does. `secret` is a callable so the value stays in its owner's frame.
`host` sends the public Host (and X-Forwarded-*) when the token URL is the in-cluster service, the way the
synthetic journey reaches KeyCape (sso-mfa/k8s/synthetic-journey/journey.py host_map)."""
def __init__(self, token_url: str, client_id: str, secret, *, host: str | None = None,
ca_file: str | None = None, timeout: float = 15):
parsed = urllib.parse.urlsplit(token_url)
if parsed.scheme not in ("https", "http") or not parsed.netloc or parsed.query or parsed.fragment:
raise ClientError("token url")
if not callable(secret):
raise ClientError("secret provider")
self.token_url, self.client_id, self.secret, self.host = token_url, client_id, secret, host
self.open = _opener(ca_file)
self.timeout = timeout
def token(self, scope: str) -> str:
if not scope or " " in scope:
raise ClientError("one scope per token")
basic = base64.b64encode((urllib.parse.quote(self.client_id, safe="") + ":"
+ urllib.parse.quote(self.secret(), safe="")).encode()).decode()
headers = {"Authorization": "Basic " + basic, "Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"}
if self.host:
headers.update({"Host": self.host, "X-Forwarded-Host": self.host, "X-Forwarded-Proto": "https"})
body = urllib.parse.urlencode({"grant_type": "client_credentials", "scope": scope}).encode()
request = urllib.request.Request(self.token_url, method="POST", headers=headers, data=body)
try:
with self.open(request, timeout=self.timeout) as response:
data = json.loads(response.read(1 << 16))
except urllib.error.HTTPError as error:
error.close()
raise ClientError(f"token {error.code}") from None
token = data.get("access_token")
if (not isinstance(token, str) or not token or len(token) > 16384 or any(c.isspace() for c in token)
or str(data.get("token_type", "")).lower() != "bearer"
or not isinstance(data.get("expires_in"), int) or not 0 < data["expires_in"] <= 900):
raise ClientError("token response")
return token
class _Http:
def __init__(self, base: str, token_file: str | None = None, ca_file: str | None = None, timeout: float = 15,
token_provider=None):
parsed = urllib.parse.urlsplit(base)
if parsed.scheme not in ("https", "http") or not parsed.netloc or parsed.query or parsed.fragment:
raise ClientError("base url")
if (token_file is None) == (token_provider is None):
raise ClientError("exactly one of token_file and token_provider")
self.base = base.rstrip("/")
self.token_file = token_file
self.token_provider = token_provider
self.open = _opener(ca_file)
self.timeout = timeout
def bearer(self, scope: str | None) -> str:
if self.token_provider is not None:
return self.token_provider(scope)
return pathlib.Path(self.token_file).read_text().strip()
def call(self, method: str, path: str, body: dict | None = None, headers: dict | None = None,
scope: str | None = None) -> tuple[int, dict]:
head = {"Authorization": "Bearer " + self.bearer(scope),
"Accept": "application/json", **(headers or {})}
data = None
if body is not None:
head["Content-Type"] = "application/json"
data = json.dumps(body).encode()
request = urllib.request.Request(self.base + path, method=method, headers=head, data=data)
try:
with self.open(request, timeout=self.timeout) as response:
raw = response.read(1 << 20)
return response.status, (json.loads(raw) if raw else {})
except urllib.error.HTTPError as error:
error.close()
raise ClientError(f"{method} {error.code}") from None
class ApprovalEngine:
"""POST /v1/approvals, GET /v1/approvals/{id} and POST /v1/approvals/{id}/consume (approval-engine api.py).
Each call asks for exactly the scope it needs (approval:create, approval:read, approval:consume)."""
def __init__(self, base: str, token_file: str | None = None, ca_file: str | None = None, token_provider=None):
self.http = _Http(base, token_file, ca_file, token_provider=token_provider)
def create(self, binding: dict, validity: dict, required_count: int) -> dict:
"""File the approval request as the actor itself. human_control is always on and pdp_path off (pilot)."""
status, data = self.http.call("POST", "/v1/approvals", {
"binding": binding, "validity": validity, "required_count": required_count,
"human_control": True, "pdp_path": False}, scope="approval:create")
if status != 201:
raise ClientError(f"create {status}")
return data
@staticmethod
def _path(approval_id: str) -> str:
if not approval_id or "/" in approval_id or len(approval_id) > 128:
raise ClientError("approval id")
return "/v1/approvals/" + urllib.parse.quote(approval_id, safe="")
def get(self, approval_id: str) -> dict:
return self.http.call("GET", self._path(approval_id), scope="approval:read")[1]
def consume(self, approval_id: str, request_digest: str, decision_id: str) -> dict:
return self.http.call("POST", self._path(approval_id) + "/consume",
{"request_digest": request_digest, "decision_id": decision_id},
scope="approval:consume")[1]
class InformedDecision:
"""POST /v1/memos on informed-decision's private workload listener (informed-decision INFD-WP-0010,
present_existing). The bearer is the executor's projected ServiceAccount token, audience informed-decision-intake;
informed-decision checks it with TokenReview and joins it to service:nk-bind-executor in its registry."""
def __init__(self, base: str, token_file: str, ca_file: str | None = None):
self.http = _Http(base, token_file, ca_file)
def submit(self, envelope: dict) -> dict:
status, receipt = self.http.call("POST", "/v1/memos", envelope)
memo = envelope["memo"]
if status not in (200, 201) or receipt.get("memo_id") != memo["id"] \
or receipt.get("approval_id") != memo["approval_id"] \
or receipt.get("approval_binding_digest") != memo["approval_binding_digest"]:
raise ClientError(f"memo receipt {status}")
return receipt
class AuditCore:
"""POST /v1/events with Idempotency-Key = event id (audit-core docs/event-envelope.md)."""
def __init__(self, base: str, token_file: str, ca_file: str | None = None):
self.http = _Http(base, token_file, ca_file)
def emit(self, event: dict) -> None:
"""audit-core answers 202 for a first accept and 200 for an idempotent duplicate (AUDIT-WP-0018-T02 tests).
409 means the run id was reused with a different body: a defect, never retried."""
try:
status, _ = self.http.call("POST", "/v1/events", event, {"Idempotency-Key": event["id"]})
except ClientError as error:
if str(error).endswith(" 409"):
raise ClientError("audit conflict 409: run id reused with a different body") from None
raise
if status not in (200, 201, 202):
raise ClientError(f"audit {status}")

View file

@ -0,0 +1,368 @@
#!/usr/bin/env python3
"""Bounded executor: core runner (NK-WP-0056-T11).
Runs one Bounded Execution Admission (canon/standards/bounded-execution-admission_v0.1.md) exactly once:
preflight (no effect) the admission is valid (BEA-1..14); the executed source matches its pins; the
trust states hold; every operation and check is known; the approval carried by
approval-engine binds this admission's digest, names this executor as actor, was
approved by listed HUMAN approvers at aal2, is unconsumed, and the window is open
consume once, with request_digest = admission digest and decision_id = run id; an
unclear outcome is resolved by reading the approval back, never by consuming again
started `execution.run.started` to the audit sink; no custody, no run
steps in plan order, one consumer at a time; checks after each mutating step, after
their settle time; the kill switch and the budget are checked between steps
failure a failed check, an exhausted budget or the kill switch runs the revert steps of
the mutated steps in the admission's declared order (or stops, as it says), each
proving the restored state with its own checks; an error of unknown outcome stops
without guessing; a failed revert stops and alerts
receipt one `execution.attribution.receipt` per run (PDP `not-evaluated` in the pilot)
Every effect goes through an injected interface: Carrier, operation and check callables, TrustOracle,
AuditSink, Alert, clocks. The runner never prints, logs or stores a value an operation handles.
"""
from __future__ import annotations
import dataclasses
import datetime as dt
import hashlib
import importlib.util
import json
import pathlib
import time
import uuid
from typing import Callable, Protocol
HERE = pathlib.Path(__file__).resolve().parent
REPO = HERE.parent
_spec = importlib.util.spec_from_file_location("bea_validate", REPO / "tools/bounded-execution-admission/validate.py")
bea = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(bea)
ACTION = "bounded-execution"
STARTED = "execution.run.started"
RECEIPT = "execution.attribution.receipt"
class Refused(Exception):
"""Refused before any effect."""
class Unknown(Exception):
"""An operation's outcome is unknown: stop, do not guess, do not revert blindly."""
class StepFailed(Exception):
"""A definite, known-bad outcome (e.g. a rollout that never became ready): handled like a failed check.
Operations outside this module may instead set `definite_failure = True` on their exception class."""
definite_failure = True
class Carrier(Protocol):
def get(self, approval_id: str) -> dict: ...
def consume(self, approval_id: str, request_digest: str, decision_id: str) -> dict: ...
class AuditSink(Protocol):
def emit(self, event: dict) -> None: ...
Operation = Callable[[dict, "Context"], None] # (step, context) -> None, raises Unknown on unclear outcome
Check = Callable[[dict, "Context"], bool] # (check, context) -> passed
@dataclasses.dataclass
class Config:
executor_identity: str # must equal spec.executor.identity and the approval's actor
runtime_principal: str # e.g. system:serviceaccount:sso:nk-bind-executor
tenant: str
environment: str
approver_subjects: dict[str, str] # admission approver name -> carrier subject_id (no guessing)
source_root: pathlib.Path = REPO
clock_source: str = "system-utc"
@dataclasses.dataclass
class Context:
"""What operations and checks may see. Values they fetch stay inside them."""
run_id: str
admission_id: str
started_monotonic: float
data: dict = dataclasses.field(default_factory=dict) # operation-private state (e.g. versions), never emitted
@dataclasses.dataclass
class Deps:
carrier: Carrier
operations: dict[str, Operation]
checks: dict[str, Check]
trust: Callable[[str], bool]
audit: AuditSink
alert: Callable[[str, dict], None]
should_stop: Callable[[], bool] = lambda: False
wall: Callable[[], dt.datetime] = lambda: dt.datetime.now(dt.UTC)
monotonic: Callable[[], float] = time.monotonic
sleep: Callable[[float], None] = time.sleep
def iso(t: dt.datetime) -> str:
return t.astimezone(dt.UTC).isoformat(timespec="seconds").replace("+00:00", "Z")
class Run:
def __init__(self, admission: dict, approval_id: str, config: Config, deps: Deps, schema: dict | None = None):
self.doc = admission
self.spec = admission.get("spec", {})
self.approval_id = approval_id
self.cfg = config
self.d = deps
self.schema = schema or json.loads(bea.SCHEMA.read_text())
self.run_id = str(uuid.uuid4())
self.steps: list[dict] = []
self.approver_subject: str | None = None
self.consumed_at: str | None = None
self.start_wall: dt.datetime | None = None
self.start_mono: float | None = None
# ------------------------------------------------------------------ preflight (no effect)
def preflight(self) -> None:
report = bea.evaluate(self.doc, self.schema)
if not report["valid"]:
raise Refused("admission_invalid:" + ",".join(sorted({f["rule"] for f in report["findings"]})))
spec = self.spec
if spec["executor"]["identity"] != self.cfg.executor_identity:
raise Refused("not_this_executor")
for pinned in spec["plan"]["source"]["files"]:
path = self.cfg.source_root / pinned["path"]
if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != pinned["sha256"]:
raise Refused("source_drift")
for state in spec["trust_requires"]:
if not self.d.trust(state):
raise Refused("trust_not_satisfied:" + state)
for step in spec["plan"]["steps"] + spec["plan"].get("revert_steps", []):
if step["operation"] not in self.d.operations:
raise Refused("unknown_operation")
for check in spec["checks"]:
if check["id"] not in self.d.checks:
raise Refused("unknown_check")
self._check_window()
self._check_approval(self.d.carrier.get(self.approval_id), consumed_ok=False)
def _check_window(self) -> None:
now = self.d.wall()
start, end = (bea.parse_time(self.spec["window"][k]) for k in ("not_before", "not_after"))
if not start <= now < end:
raise Refused("outside_window")
if now + dt.timedelta(seconds=self.spec["budget"]["wall_time_seconds"]) > end:
raise Refused("window_shorter_than_budget") # a run must not spill past its admission
def _check_approval(self, approval: dict, consumed_ok: bool) -> None:
spec = self.spec
binding = approval.get("binding") or {}
target = binding.get("target") or {}
if (binding.get("action") != ACTION or binding.get("actor") != self.cfg.executor_identity
or target.get("admission_id") != spec["id"]
or target.get("admission_digest") != bea.admission_digest(self.doc)
or binding.get("human_control") is not True):
raise Refused("approval_binding")
status = approval.get("status")
if status != "approved" and not (consumed_ok and status == "consumed"):
raise Refused("approval_status")
if not consumed_ok and approval.get("consumption"):
raise Refused("approval_already_consumed")
allowed = {self.cfg.approver_subjects.get(name) for name in spec["approval"]["approvers"]} - {None}
entries = approval.get("entries") or []
if not entries:
raise Refused("approval_no_entries")
for entry in entries: # aal2 is recorded, not enforced, by approval-engine today: check every entry
if (entry.get("subject_id") not in allowed or str(entry.get("principal_type", "")).lower() != "human"
or entry.get("assurance") != "aal2" or entry.get("subject_id") == self.cfg.executor_identity):
raise Refused("approval_entry")
self.approver_subject = entries[0]["subject_id"]
window_end = bea.parse_time(spec["window"]["not_after"])
expires = (approval.get("validity") or {}).get("expires_at")
if expires and bea.parse_time(expires) < window_end:
raise Refused("approval_expires_before_window_end")
# ------------------------------------------------------------------ consume (the one irreversible act)
def consume(self) -> None:
self._check_window() # again, right before the one irreversible act
digest = bea.admission_digest(self.doc)
try:
response = self.d.carrier.consume(self.approval_id, digest, self.run_id)
except Exception:
response = None # unclear outcome: read back, never consume twice
if response is not None:
if (response.get("status") != "consumed" or response.get("request_digest") != digest
or response.get("decision_id") != self.run_id or response.get("idempotent") is not False):
raise Refused("consume_response")
self.consumed_at = response.get("consumed_at")
approval = self.d.carrier.get(self.approval_id)
self._check_approval(approval, consumed_ok=True)
consumption = approval.get("consumption") or {}
if approval.get("status") != "consumed" or consumption.get("request_digest") != digest \
or consumption.get("decision_id") != self.run_id:
raise Refused("consume_not_confirmed")
self.consumed_at = consumption.get("consumed_at", self.consumed_at)
# ------------------------------------------------------------------ execution
def _budget_left(self) -> float:
return self.spec["budget"]["wall_time_seconds"] - (self.d.monotonic() - self.start_mono)
def _record(self, step: dict, kind: str, outcome: str, checks: list[dict] | None = None,
error: BaseException | None = None) -> None:
entry = {"id": step["id"], "kind": kind, "operation": step["operation"], "target": step["target"],
"consumer": step["consumer"], "outcome": outcome, "checks": checks or []}
if error is not None:
entry["error_type"] = type(error).__name__ # the type only: messages can carry values
self.steps.append(entry)
def _run_checks(self, ids: list[str], ctx: Context) -> tuple[bool, list[dict]]:
results = []
for cid in ids:
check = next(c for c in self.spec["checks"] if c["id"] == cid)
settle = check.get("settle_seconds", 0)
if settle:
if self._budget_left() < settle + check["timeout_seconds"]:
results.append({"id": cid, "result": "budget"})
return False, results
self.d.sleep(settle)
began = self.d.monotonic()
try:
passed = bool(self.d.checks[cid](check, ctx))
except Exception:
passed = False
if self.d.monotonic() - began > check["timeout_seconds"]:
passed = False
results.append({"id": cid, "result": "pass" if passed else "fail"})
if not passed:
return False, results
return True, results
def _revert(self, done: list[dict], ctx: Context) -> bool:
"""Undo the mutated steps. Revert steps run in the order the admission declares them (the author knows
the recovery order: data before delivery before restart), limited to those whose forward step ran.
Each must pass its own checks; an irreversible step that ran makes the revert fail."""
mutated = [s for s in done if s["mutates"]]
if any(s.get("irreversible") for s in mutated):
return False
wanted = {s["revert"] for s in mutated}
for revert in self.spec["plan"].get("revert_steps", []):
if revert["id"] not in wanted:
continue
try:
self.d.operations[revert["operation"]](revert, ctx)
except Exception as error:
self._record(revert, "revert", "error", error=error)
return False
ok, results = self._run_checks(revert.get("checks_after", []), ctx)
self._record(revert, "revert", "done" if ok else "check_failed", results)
if not ok:
return False
return True
def _attempt(self, ctx: Context) -> str:
"""One pass over the plan. Returns completed | reverted | revert_failed | stopped | unknown | budget."""
policy = self.spec["stop_and_revert"]
done: list[dict] = []
for step in self.spec["plan"]["steps"]:
if self.d.should_stop() or self._budget_left() <= 0:
reason = "stopped" if self.d.should_stop() else "budget"
action = policy["on_budget_exhausted"] if reason == "budget" else "revert"
if action == "revert" and done:
return reason if self._revert(done, ctx) else "revert_failed"
return reason
try:
self.d.operations[step["operation"]](step, ctx)
except Exception as error:
if getattr(error, "definite_failure", False) and step["mutates"]:
# known-bad state: treat like a failed check, the step counts as done for the revert
done.append(step)
self._record(step, "step", "failed", error=error)
if policy["on_check_failure"] == "revert":
return "reverted" if self._revert(done, ctx) else "revert_failed"
return "stopped"
self._record(step, "step", "unknown", error=error) # outcome not established: stop
return "unknown"
done.append(step)
ok, results = self._run_checks(step.get("checks_after", []), ctx) if step["mutates"] else (True, [])
self._record(step, "step", "done" if ok else "check_failed", results)
if not ok:
if policy["on_check_failure"] == "revert":
return "reverted" if self._revert(done, ctx) else "revert_failed"
return "stopped"
return "completed"
# ------------------------------------------------------------------ evidence
def _event(self, kind: str, body: dict, occurred_at: dt.datetime) -> dict:
"""audit-core's ingest envelope (docs/event-envelope.md): all eight fields, offset-qualified time."""
return {"id": f"run:{self.run_id}:{'started' if kind == STARTED else 'receipt'}", "type": kind,
"correlation_id": self.run_id, "subject": f"admission:{self.spec['id']}",
"source": "net-kingdom.bounded-executor", "tenant": self.cfg.tenant,
"occurred_at": occurred_at.astimezone(dt.UTC).isoformat(timespec="seconds"), "data": body}
def _receipt(self, outcome: str, attempts: int) -> dict:
end = self.d.wall()
return self._event(RECEIPT, occurred_at=end, body={
"run_id": self.run_id,
"initiator": {"sub": self.approver_subject, "role": "approver"},
"executing_workload": self.cfg.executor_identity,
"runtime_principal": self.cfg.runtime_principal,
"tenant": self.cfg.tenant, "environment": self.cfg.environment,
"source_commit": self.spec["plan"]["source"]["commit"],
"artifact_digest": self.spec["plan_digest"],
"target": f"admission:{self.spec['id']}", "action": ACTION,
"decision_refs": [
{"label": "admission", "ref": self.spec["id"]},
{"label": "admission_digest", "ref": bea.admission_digest(self.doc)},
{"label": "approval", "ref": self.approval_id},
],
"pdp": "not-evaluated",
"time_interval": {"start": iso(self.start_wall), "end": iso(end), "clock_source": self.cfg.clock_source},
"outcome": outcome,
"reverted": any(s["kind"] == "revert" for s in self.steps),
"attempts": attempts,
"budget": {"limit": {"wall_time_seconds": self.spec["budget"]["wall_time_seconds"],
"attempts": self.spec["budget"]["attempts"]},
"used": {"wall_time_seconds": round(self.d.monotonic() - self.start_mono, 3),
"attempts": attempts}},
"steps": self.steps,
})
# ------------------------------------------------------------------ the run
def execute(self) -> dict:
try:
self.preflight()
except Refused as refusal:
return {"outcome": "refused", "reason": str(refusal), "run_id": None, "effects": False}
self.start_wall, self.start_mono = self.d.wall(), self.d.monotonic()
try:
self.consume()
except Refused as refusal: # consumed or not, nothing else may happen now
self.d.alert("consume_unconfirmed", {"admission": self.spec["id"], "run_id": self.run_id})
return {"outcome": "refused", "reason": str(refusal), "run_id": self.run_id, "effects": "consume-only"}
try:
self.d.audit.emit(self._event(STARTED, {"run_id": self.run_id, "admission": self.spec["id"],
"approval": self.approval_id, "consumed_at": self.consumed_at,
"executing_workload": self.cfg.executor_identity},
occurred_at=self.d.wall()))
except Exception:
self.d.alert("started_not_in_custody", {"admission": self.spec["id"], "run_id": self.run_id})
return {"outcome": "stopped", "reason": "audit_unavailable", "run_id": self.run_id, "effects": "consume-only"}
ctx = Context(self.run_id, self.spec["id"], self.start_mono)
attempts, outcome = 0, "budget"
while attempts < self.spec["budget"]["attempts"]:
attempts += 1
outcome = self._attempt(ctx)
if outcome != "reverted" or self._budget_left() <= 0:
break # only a clean revert may be retried, and only within budget
if outcome in ("unknown", "revert_failed"):
self.d.alert(outcome, {"admission": self.spec["id"], "run_id": self.run_id})
receipt = self._receipt(outcome, attempts)
try:
self.d.audit.emit(receipt)
except Exception:
self.d.alert("receipt_not_in_custody", {"admission": self.spec["id"], "run_id": self.run_id})
return {"outcome": outcome, "reason": "receipt_not_in_custody", "run_id": self.run_id, "receipt": receipt}
return {"outcome": outcome, "run_id": self.run_id, "receipt": receipt}

View file

@ -0,0 +1,395 @@
"""Pilot operations and checks for the bounded executor (NK-WP-0056-T12).
The T06 pilot rotates one directory bind (svc-authelia) under one admission:
openbao.kv.write-next-version read version N, write a fresh value as N+1 with cas=N
openbao.kv.write-previous-value-as-next-version revert: write the previous value as the next version (CAS)
lldap.user.change-own-password-from-kv set the bind's LLDAP password to the current KV value,
signing in as the bind with a value it still accepts
(self-rotation, founder decision 3d0c8f86); forward and revert
eso.force-sync ask the delivery to re-project the value (ExternalSecret)
k8s.rollout-restart restart the consumer and wait for the rollout
Checks: kv-advanced, bind-signs-in, eso-synced, synthetic-journey.
Values live only in Context.data (memory) and in the requests that carry them; nothing here prints, logs or
returns a value. Every adapter is stdlib HTTP so the image needs no client libraries.
"""
from __future__ import annotations
import datetime as dt
import importlib.util
import json
import os
import pathlib
import secrets
import ssl
import subprocess
import time
import urllib.error
import urllib.parse
import urllib.request
from typing import Callable
HERE = pathlib.Path(__file__).resolve().parent
REPO = HERE.parent
_spec = importlib.util.spec_from_file_location("journey", REPO / "sso-mfa/k8s/synthetic-journey/journey.py")
journey = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(journey)
FIELD = "LDAP_BIND_PASSWORD"
class OperationError(Exception):
"""A definite failure: the operation did not take effect."""
class RolloutFailed(Exception):
"""The restart was issued but the consumer never became ready: a definite, known-bad outcome that the
executor reverts (executor.StepFailed semantics)."""
definite_failure = True
def _opener(ca_file: str | None):
context = ssl.create_default_context(cafile=ca_file) if ca_file else ssl.create_default_context()
return urllib.request.build_opener(urllib.request.ProxyHandler({}), urllib.request.HTTPSHandler(context=context))
# --------------------------------------------------------------------------------------------------
# OpenBao KV v2
# --------------------------------------------------------------------------------------------------
def wait_reachable(addr: str, *, timeout: float = 30, interval: float = 1, ca_file: str | None = None,
opener=None, clock=time.monotonic, sleep=time.sleep) -> float:
"""Wait until OpenBao answers on the network, before the first login. k3s applies NetworkPolicy to a new pod a
moment after it starts, and connections in that window are refused (seen live 2026-10-31: refused at 0 s,
open at 3 s). Polls the unauthenticated sys/health. Any HTTP answer, including 429/473/503, counts as reachable;
only connection errors are retried. Returns the seconds waited; raises Unreachable after `timeout`."""
open_ = opener or _opener(ca_file).open
start = clock()
while True:
request = urllib.request.Request(addr.rstrip("/") + "/v1/sys/health", method="GET")
try:
with open_(request, timeout=5):
return clock() - start
except urllib.error.HTTPError as error:
error.close()
return clock() - start
except (urllib.error.URLError, OSError):
if clock() - start >= timeout:
raise Unreachable("openbao not reachable") from None
sleep(interval)
class Unreachable(Exception):
pass
class OpenBao:
def __init__(self, addr: str, *, token: str | None = None, k8s_role: str | None = None,
jwt_file: str | None = None, k8s_mount: str = "kubernetes", ca_file: str | None = None,
timeout: float = 15):
self.addr = addr.rstrip("/")
self.open = _opener(ca_file).open
self.timeout = timeout
self.token = token
self.login_args = None if token is not None else (k8s_mount, k8s_role, jwt_file)
self.renew_at = None # monotonic time after which the next call logs in again
if self.token is None:
self._login()
RENEW_MARGIN = 120 # seconds before the lease ends
def _login(self) -> None:
"""Kubernetes-auth login. The role's tokens live 15m and a run's budget may be longer, so the client logs
in again before the lease ends (and revokes the old token). The projected JWT is re-read every time:
kubelet rotates it."""
mount, role, jwt_file = self.login_args
jwt = pathlib.Path(jwt_file).read_text().strip()
auth = self._call("POST", f"/v1/auth/{mount}/login", {"role": role, "jwt": jwt}, auth=False)["auth"]
old, self.token = self.token, auth["client_token"]
lease = int(auth.get("lease_duration") or 0)
self.renew_at = time.monotonic() + max(lease - self.RENEW_MARGIN, lease / 2) if lease else None
if old is not None:
self._revoke(old)
def _call(self, method: str, path: str, body: dict | None = None, auth: bool = True) -> dict:
if auth and self.login_args and self.renew_at is not None and time.monotonic() >= self.renew_at:
self._login()
headers = {"Content-Type": "application/json"}
if auth:
headers["X-Vault-Token"] = self.token
request = urllib.request.Request(self.addr + path, method=method, headers=headers,
data=json.dumps(body).encode() if body is not None else None)
try:
with self.open(request, timeout=self.timeout) as response:
raw = response.read()
except urllib.error.HTTPError as error:
error.close()
raise OperationError(f"openbao {method} {error.code}") from None
return json.loads(raw) if raw else {}
@staticmethod
def _api(kv_path: str) -> str:
mount, _, rest = kv_path.partition("/")
return f"/v1/{mount}/data/{rest}"
def read(self, kv_path: str) -> tuple[dict, int]:
body = self._call("GET", self._api(kv_path))["data"]
return body["data"], int(body["metadata"]["version"])
def write(self, kv_path: str, data: dict, cas: int) -> int:
return int(self._call("POST", self._api(kv_path), {"options": {"cas": cas}, "data": data})["data"]["version"])
def revoke_self(self) -> None:
if self.token is not None:
self._revoke(self.token)
def _revoke(self, token: str) -> None:
request = urllib.request.Request(self.addr + "/v1/auth/token/revoke-self", method="POST", data=b"{}",
headers={"Content-Type": "application/json", "X-Vault-Token": token})
try:
with self.open(request, timeout=self.timeout):
pass
except Exception:
pass
# --------------------------------------------------------------------------------------------------
# LLDAP
# --------------------------------------------------------------------------------------------------
class Directory:
"""Sign-in over LLDAP's HTTP API; password changes through the pinned image's lldap_set_password (OPAQUE)."""
def __init__(self, base_url: str, set_password_command: list[str], *, ca_file: str | None = None,
tool_base_url: str | None = None, timeout: float = 15):
self.base = base_url.rstrip("/")
self.tool_base = (tool_base_url or base_url).rstrip("/")
self.command = set_password_command
self.open = _opener(ca_file).open
self.timeout = timeout
def login(self, user: str, password: str) -> str | None:
request = urllib.request.Request(self.base + "/auth/simple/login", method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps({"username": user, "password": password}).encode())
try:
with self.open(request, timeout=self.timeout) as response:
return json.load(response).get("token")
except urllib.error.HTTPError as error:
error.close()
return None
def set_own_password(self, token: str, user: str, password: str) -> None:
done = subprocess.run([*self.command, "--base-url", self.tool_base, "--token", token, "--username", user],
env={"PATH": os.environ.get("PATH", "/usr/bin:/bin"), "LLDAP_USER_PASSWORD": password},
capture_output=True, timeout=60, check=False)
if done.returncode:
raise OperationError("lldap_set_password refused")
# --------------------------------------------------------------------------------------------------
# Kubernetes (stdlib client, in-cluster ServiceAccount)
# --------------------------------------------------------------------------------------------------
class Kube:
def __init__(self, api: str = "https://kubernetes.default.svc",
token_file: str = "/var/run/secrets/kubernetes.io/serviceaccount/token",
ca_file: str | None = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", timeout: float = 15):
self.api = api.rstrip("/")
self.token_file = token_file
self.open = _opener(ca_file).open
self.timeout = timeout
def _call(self, method: str, path: str, body: dict | None = None) -> dict:
headers = {"Authorization": "Bearer " + pathlib.Path(self.token_file).read_text().strip(),
"Accept": "application/json"}
data = None
if body is not None:
headers["Content-Type"] = "application/merge-patch+json"
data = json.dumps(body).encode()
request = urllib.request.Request(self.api + path, method=method, headers=headers, data=data)
try:
with self.open(request, timeout=self.timeout) as response:
return json.load(response)
except urllib.error.HTTPError as error:
error.close()
raise OperationError(f"kube {method} {error.code}") from None
def get(self, path: str) -> dict:
return self._call("GET", path)
def patch(self, path: str, body: dict) -> dict:
return self._call("PATCH", path, body)
def now_utc() -> dt.datetime:
return dt.datetime.now(dt.UTC).replace(microsecond=0)
class KubeDelivery:
"""eso.force-sync and its eso-synced check, through the ExternalSecret API (no Secret read)."""
def __init__(self, kube: Kube, namespace: str, external_secret: str, clock: Callable[[], dt.datetime] = now_utc):
self.kube, self.clock = kube, clock
self.path = f"/apis/external-secrets.io/v1/namespaces/{namespace}/externalsecrets/{external_secret}"
def force_sync(self) -> dt.datetime:
at = self.clock()
self.kube.patch(self.path, {"metadata": {"annotations": {"force-sync": str(int(at.timestamp()))}}})
return at
def synced(self, since: dt.datetime) -> bool:
status = self.kube.get(self.path).get("status", {})
ready = any(c.get("type") == "Ready" and c.get("status") == "True" for c in status.get("conditions", []))
refreshed = status.get("refreshTime")
return ready and bool(refreshed) and dt.datetime.fromisoformat(refreshed.replace("Z", "+00:00")) >= since
class KubeRestart:
"""k8s.rollout-restart: patch the restartedAt annotation, then wait until the rollout is complete."""
def __init__(self, kube: Kube, namespace: str, deployment: str, timeout: float = 300, poll: float = 3,
clock: Callable[[], dt.datetime] = now_utc, sleep: Callable[[float], None] = time.sleep,
monotonic: Callable[[], float] = time.monotonic):
self.kube, self.timeout, self.poll, self.clock, self.sleep, self.monotonic = kube, timeout, poll, clock, sleep, monotonic
self.path = f"/apis/apps/v1/namespaces/{namespace}/deployments/{deployment}"
def restart(self) -> None:
stamp = self.clock().isoformat().replace("+00:00", "Z")
self.kube.patch(self.path, {"spec": {"template": {"metadata": {"annotations": {
"kubectl.kubernetes.io/restartedAt": stamp}}}}})
deadline = self.monotonic() + self.timeout
while self.monotonic() < deadline:
d = self.kube.get(self.path)
spec, status = d.get("spec", {}), d.get("status", {})
want = spec.get("replicas", 1)
if (status.get("observedGeneration", 0) >= d.get("metadata", {}).get("generation", 0)
and status.get("updatedReplicas") == want and status.get("availableReplicas") == want
and status.get("replicas") == want and not status.get("unavailableReplicas")):
return
self.sleep(self.poll)
raise RolloutFailed("rollout did not complete") # known-bad: the runner reverts
# --------------------------------------------------------------------------------------------------
# The pilot registry
# --------------------------------------------------------------------------------------------------
def pilot_registry(*, bao: OpenBao, journey_bao: OpenBao, directory: Directory, delivery, restart, bind_kv: str,
bind_user: str, journey_kv: str, journey_config, journey_state: dict | None = None):
"""Return (operations, checks) for the bind-rotation admission. `bao` holds only the bind write role
(CCR-2026-0058); `journey_bao` only the journey read role (CCR-2026-0059): one role per lane. `delivery` has force_sync()/synced(since);
`restart` has restart(). Values stay in ctx.data. `journey_state` carries the last one-time-code time step
across runs (privacyIDEA accepts each 30 s step once)."""
shared = journey_state if journey_state is not None else {}
def kv_target(step: dict) -> str:
target = step["target"]
if not target.startswith("openbao:") or target[len("openbao:"):] != bind_kv:
raise OperationError("target outside the configured bind")
return bind_kv
def generate(step, ctx):
path = kv_target(step)
data, version = bao.read(path)
if set(data) != {FIELD}:
raise OperationError("kv shape")
fresh = secrets.token_urlsafe(32)
ctx.data.update(previous=data[FIELD], fresh=fresh)
ctx.data["expect"] = (bao.write(path, {FIELD: fresh}, cas=version), "fresh")
def restore_kv(step, ctx):
path = kv_target(step)
_, version = bao.read(path)
ctx.data["expect"] = (bao.write(path, {FIELD: ctx.data["previous"]}, cas=version), "previous")
def change_own_password(step, ctx):
if step["target"] != "lldap:" + bind_user:
raise OperationError("target outside the configured bind")
want = bao.read(bind_kv)[0][FIELD]
if directory.login(bind_user, want):
return # already in effect
for known in (ctx.data.get("previous"), ctx.data.get("fresh")):
if known and known != want:
token = directory.login(bind_user, known)
if token:
directory.set_own_password(token, bind_user, want)
if not directory.login(bind_user, want):
raise OperationError("password change not effective")
return
raise OperationError("no known value signs in as the bind")
def force_sync(step, ctx):
ctx.data["delivered_at"] = delivery.force_sync()
def rollout_restart(step, ctx):
restart.restart()
def check_kv_advanced(check, ctx):
version, which = ctx.data["expect"]
data, current = bao.read(bind_kv)
return current == version and data.get(FIELD) == ctx.data[which]
def check_bind_signs_in(check, ctx):
return directory.login(bind_user, bao.read(bind_kv)[0][FIELD]) is not None
def check_eso_synced(check, ctx):
deadline = time.monotonic() + check["timeout_seconds"] * 0.8
while True:
if delivery.synced(ctx.data["delivered_at"]):
return True
if time.monotonic() > deadline:
return False
time.sleep(2)
def check_journey(check, ctx):
receipt = journey_from_kv(journey_bao, journey_kv, journey_config, shared)
ctx.data.setdefault("journeys", []).append({k: receipt.get(k) for k in ("result", "failed_step", "reason")})
return receipt["result"] == "pass"
operations = {
"openbao.kv.write-next-version": generate,
"openbao.kv.write-previous-value-as-next-version": restore_kv,
"lldap.user.change-own-password-from-kv": change_own_password,
"eso.force-sync": force_sync,
"k8s.rollout-restart": rollout_restart,
}
checks = {"kv-advanced": check_kv_advanced, "bind-signs-in": check_bind_signs_in,
"eso-synced": check_eso_synced, "synthetic-journey": check_journey}
return operations, checks
def journey_from_kv(journey_bao: OpenBao, journey_kv: str, journey_config, state: dict | None = None) -> dict:
"""Read the synthetic identity through the journey role and run the journey once. Shared by the admission check
(`synthetic-journey`) and the journey-only run (NK-WP-0056-T10). The values stay in this frame; the returned
receipt is the journey runner's value-free receipt. `state` carries the last one-time-code time step, because
privacyIDEA accepts each 30 s step only once."""
state = state if state is not None else {}
creds, _ = journey_bao.read(journey_kv)
if set(creds) != {"PASSWORD", "TOTP_SEED"}:
raise OperationError("journey kv shape")
cfg = _load_config(journey_config() if callable(journey_config) else journey_config)
last = state.get("journey_step")
receipt = journey.run_journey(cfg, creds["PASSWORD"], journey.decode_seed(creds["TOTP_SEED"]),
min_time_step=None if last is None else last + 1)
if receipt.get("otp_time_step") is not None:
state["journey_step"] = receipt["otp_time_step"]
return receipt
def _load_config(config: dict) -> dict:
import tempfile
fd, path = tempfile.mkstemp(suffix=".json", prefix="journey-")
try:
with os.fdopen(fd, "w") as out:
json.dump(config, out)
return journey.load_config(path)
finally:
os.unlink(path)

View file

@ -0,0 +1,2 @@
jsonschema>=4.23,<5
PyYAML>=6,<7

View file

@ -0,0 +1,233 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in --generate-hashes --python-version 3.12 --python-platform x86_64-unknown-linux-gnu -o requirements.txt
attrs==26.1.0 \
--hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \
--hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32
# via
# jsonschema
# referencing
jsonschema==4.26.0 \
--hash=sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326 \
--hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce
# via -r requirements.in
jsonschema-specifications==2025.9.1 \
--hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \
--hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d
# via jsonschema
pyyaml==6.0.3 \
--hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \
--hash=sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a \
--hash=sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3 \
--hash=sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956 \
--hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
--hash=sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c \
--hash=sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65 \
--hash=sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a \
--hash=sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0 \
--hash=sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b \
--hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
--hash=sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 \
--hash=sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7 \
--hash=sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e \
--hash=sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007 \
--hash=sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310 \
--hash=sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4 \
--hash=sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9 \
--hash=sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295 \
--hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \
--hash=sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0 \
--hash=sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e \
--hash=sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac \
--hash=sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9 \
--hash=sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7 \
--hash=sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35 \
--hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
--hash=sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b \
--hash=sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69 \
--hash=sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5 \
--hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
--hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \
--hash=sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369 \
--hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \
--hash=sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824 \
--hash=sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198 \
--hash=sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065 \
--hash=sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c \
--hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
--hash=sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764 \
--hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
--hash=sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b \
--hash=sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00 \
--hash=sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac \
--hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
--hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \
--hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \
--hash=sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3 \
--hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \
--hash=sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4 \
--hash=sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b \
--hash=sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf \
--hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
--hash=sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702 \
--hash=sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8 \
--hash=sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788 \
--hash=sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da \
--hash=sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d \
--hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
--hash=sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c \
--hash=sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba \
--hash=sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f \
--hash=sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917 \
--hash=sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5 \
--hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
--hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
--hash=sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b \
--hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
--hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
--hash=sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3 \
--hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
--hash=sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926 \
--hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
# via -r requirements.in
referencing==0.37.0 \
--hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231 \
--hash=sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8
# via
# jsonschema
# jsonschema-specifications
rpds-py==2026.9.1 \
--hash=sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25 \
--hash=sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68 \
--hash=sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495 \
--hash=sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32 \
--hash=sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96 \
--hash=sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834 \
--hash=sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0 \
--hash=sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6 \
--hash=sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e \
--hash=sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa \
--hash=sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759 \
--hash=sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff \
--hash=sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7 \
--hash=sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c \
--hash=sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903 \
--hash=sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d \
--hash=sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9 \
--hash=sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e \
--hash=sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b \
--hash=sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a \
--hash=sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a \
--hash=sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2 \
--hash=sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945 \
--hash=sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91 \
--hash=sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044 \
--hash=sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69 \
--hash=sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e \
--hash=sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3 \
--hash=sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4 \
--hash=sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33 \
--hash=sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52 \
--hash=sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9 \
--hash=sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb \
--hash=sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35 \
--hash=sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299 \
--hash=sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c \
--hash=sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063 \
--hash=sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a \
--hash=sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6 \
--hash=sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12 \
--hash=sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14 \
--hash=sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0 \
--hash=sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30 \
--hash=sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31 \
--hash=sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02 \
--hash=sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af \
--hash=sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef \
--hash=sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0 \
--hash=sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888 \
--hash=sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a \
--hash=sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c \
--hash=sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b \
--hash=sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86 \
--hash=sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f \
--hash=sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531 \
--hash=sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6 \
--hash=sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82 \
--hash=sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d \
--hash=sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7 \
--hash=sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448 \
--hash=sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391 \
--hash=sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb \
--hash=sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4 \
--hash=sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e \
--hash=sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c \
--hash=sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15 \
--hash=sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f \
--hash=sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297 \
--hash=sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36 \
--hash=sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d \
--hash=sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b \
--hash=sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237 \
--hash=sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb \
--hash=sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477 \
--hash=sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c \
--hash=sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e \
--hash=sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608 \
--hash=sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07 \
--hash=sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c \
--hash=sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35 \
--hash=sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0 \
--hash=sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785 \
--hash=sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad \
--hash=sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc \
--hash=sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9 \
--hash=sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025 \
--hash=sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f \
--hash=sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009 \
--hash=sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d \
--hash=sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118 \
--hash=sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db \
--hash=sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9 \
--hash=sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab \
--hash=sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37 \
--hash=sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d \
--hash=sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc \
--hash=sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9 \
--hash=sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3 \
--hash=sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569 \
--hash=sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69 \
--hash=sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b \
--hash=sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018 \
--hash=sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f \
--hash=sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c \
--hash=sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f \
--hash=sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5 \
--hash=sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445 \
--hash=sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1 \
--hash=sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e \
--hash=sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429 \
--hash=sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa \
--hash=sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b \
--hash=sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250 \
--hash=sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e \
--hash=sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83 \
--hash=sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f \
--hash=sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd \
--hash=sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346 \
--hash=sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858 \
--hash=sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871 \
--hash=sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da \
--hash=sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0 \
--hash=sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f \
--hash=sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050 \
--hash=sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31 \
--hash=sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df \
--hash=sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12 \
--hash=sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567
# via
# jsonschema
# referencing
typing-extensions==4.16.0 \
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
# via referencing

View file

@ -0,0 +1,105 @@
#!/usr/bin/env python3
# /// script
# dependencies = ["PyYAML>=6,<7", "jsonschema>=4,<5"]
# ///
"""Journey-only run on the T08 synthetic stack: passes when healthy, fails closed on a broken bind (NK-WP-0056-T10).
The same code path the Job runs with `cli.py --journey-only` (`operations.journey_from_kv`). The difference is the
login: here a dev-OpenBao token that holds only the journey-read policy, instead of Kubernetes auth. Scenarios:
healthy the journey passes
broken bind svc-authelia's password is changed in LLDAP while Authelia keeps the old one (a half-done
rotation). The journey must fail closed at authelia_first_factor.
restored the password is set back and the journey passes again
lane boundary the journey token cannot read the svc-authelia bind
Run: uv run bounded-executor/synthetic_journey_only.py [--report FILE]. Exit 0 when every expectation is met.
"""
from __future__ import annotations
import argparse
import json
import pathlib
import secrets
import sys
import tempfile
HERE = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
import synthetic_pilot as P # noqa: E402 (loads executor, operations and the T08 stack)
OPS, stack_mod = P.OPS, P.stack_mod
def summary(receipt: dict) -> dict:
return {k: receipt.get(k) for k in ("result", "failed_step", "reason")}
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
parser.add_argument("--report")
args = parser.parse_args(argv)
report = {"schema": "netkingdom.bounded-executor.synthetic-journey-only/v1",
"started_at": OPS.now_utc().isoformat(), "images": {"openbao": P.OPENBAO_IMAGE},
"code_path": "operations.journey_from_kv (cli.py --journey-only)", "results": []}
ok = False
with tempfile.TemporaryDirectory(prefix="nk56-journey-only-") as tmp:
stack = stack_mod.Stack(pathlib.Path(tmp), pi_cache_timeout=120)
bao = None
secrets_in_report = []
try:
stack.up()
bao = P.OpenBaoDev(stack.network, stack.tag)
write_token, journey_token = bao.setup(stack.pw["svc-authelia"], stack.pw[stack_mod.USER], stack.seed)
secrets_in_report = [stack.pw["svc-authelia"], stack.pw[stack_mod.USER], write_token, journey_token, bao.root]
journey_bao = OPS.OpenBao(bao.addr, token=journey_token)
state: dict = {}
def run():
return OPS.journey_from_kv(journey_bao, P.JOURNEY_KV, stack.journey_config(), state)
healthy = run()
report["results"].append({"scenario": "healthy", "expected": "pass", "journey": summary(healthy),
"met": healthy["result"] == "pass"})
stack.set_password(stack.lldap_token(), "svc-authelia", secrets.token_urlsafe(24))
broken = run()
report["results"].append({"scenario": "broken bind (svc-authelia)",
"expected": "fail at authelia_first_factor", "journey": summary(broken),
"met": broken["result"] == "fail"
and broken["failed_step"] == "authelia_first_factor"})
stack.set_password(stack.lldap_token(), "svc-authelia", stack.pw["svc-authelia"])
restored = run()
report["results"].append({"scenario": "restored", "expected": "pass", "journey": summary(restored),
"met": restored["result"] == "pass"})
try:
journey_bao.read(P.BIND_KV)
denied = False
except OPS.OperationError:
denied = True
report["results"].append({"scenario": "lane boundary", "expected": "journey token cannot read the bind",
"met": denied})
journey_bao.revoke_self()
ok = all(r["met"] for r in report["results"])
except Exception as error: # report the type only: messages could carry context
report["error"] = type(error).__name__
finally:
if bao:
bao.remove()
stack.down()
text = json.dumps(report, sort_keys=True)
report["no_values_in_report"] = not any(s and s in text for s in secrets_in_report)
report["all_met"] = ok and report["no_values_in_report"]
report["finished_at"] = OPS.now_utc().isoformat()
out = json.dumps(report, indent=2, sort_keys=True)
if args.report:
pathlib.Path(args.report).write_text(out + "\n")
print(out)
return 0 if report["all_met"] else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,283 @@
#!/usr/bin/env python3
# /// script
# dependencies = ["PyYAML>=6,<7", "jsonschema>=4,<5"]
# ///
"""Synthetic pilot: the executor rotates the svc-authelia bind on the T08 stack (NK-WP-0056-T12).
Agent-runnable, local Docker only, generated values, everything removed afterwards. Starts the T08 identity
stack (LLDAP, privacyIDEA, Authelia, KeyCape on the pinned images) and an OpenBao dev server on the platform's
pinned 2.5.4 image (KV v2 at `platform`, `cas_required` on the bind). The executor gets its own token with
exactly CCR-2026-0058's policy file plus a journey-read policy, never the root token.
Not on this stack: approval-engine (an in-process carrier with its consume semantics stands in), audit-core
(an in-memory sink), and Kubernetes. Stand-ins replace the ExternalSecret delivery (re-project the KV value
into Authelia's configuration) and the rollout (restart the Authelia container). The real Kubernetes adapters
(operations.KubeDelivery, KubeRestart) are covered by unit tests and exercised live in T06.
Runs:
rotation one admission, the bind rotates; afterwards the old value is refused and the journey passes
revert a second admission whose first delivery projects a wrong value while reporting success:
Authelia does not become ready after the restart (definite failure), the executor reverts in
declared order, the previous value is back and the journey passes again
Writes a value-free report. Exit 0 when both runs end as expected.
Run: uv run bounded-executor/synthetic_pilot.py [--report FILE]
"""
from __future__ import annotations
import argparse
import base64
import copy
import datetime as dt
import hashlib
import importlib.util
import json
import pathlib
import secrets
import sys
import tempfile
import yaml
HERE = pathlib.Path(__file__).resolve().parent
REPO = HERE.parent
def _load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
X = _load("executor", HERE / "executor.py")
OPS = _load("operations", HERE / "operations.py")
stack_mod = _load("stack", REPO / "sso-mfa/k8s/synthetic-journey/stack.py")
bea = X.bea
OPENBAO_IMAGE = "quay.io/openbao/openbao@sha256:436eaf9778cad75507ff70ea26ace30dcbe15606e619ac3823495663d7f7c115" # 2.5.4
BIND_KV = "platform/workloads/net-kingdom/directory-binds/svc-authelia"
JOURNEY_KV = "platform/workloads/net-kingdom/synthetic-journey/syn-journey"
POLICY_FILE = REPO.parent / "railiance-platform/openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl"
JOURNEY_POLICY = 'path "platform/data/workloads/net-kingdom/synthetic-journey/syn-journey" { capabilities = ["read"] }\n'
EXECUTOR = "service:nk-bind-executor"
APPROVER_SUB = "synthetic-admin-sub"
class OpenBaoDev:
def __init__(self, network: str, tag: str):
self.name = f"nk56-executor-{tag}-openbao"
self.root = secrets.token_hex(16)
stack_mod.docker("run", "--detach", "--pull=never", "--name", self.name, "--network", network,
"--publish", "127.0.0.1::8200", "--cap-add", "IPC_LOCK", "--env", "BAO_DEV_ROOT_TOKEN_ID",
OPENBAO_IMAGE, "server", "-dev", "-dev-listen-address=0.0.0.0:8200",
env={"BAO_DEV_ROOT_TOKEN_ID": self.root})
self.addr = f"http://127.0.0.1:{stack_mod.port(self.name, 8200)}"
stack_mod.wait_http(self.addr + "/v1/sys/health", "openbao")
self.admin = OPS.OpenBao(self.addr, token=self.root)
def call(self, method, path, body=None):
return self.admin._call(method, path, body)
def setup(self, bind_value: str, journey_password: str, seed: bytes) -> str:
self.call("POST", "/v1/sys/mounts/platform", {"type": "kv", "options": {"version": "2"}})
self.admin.write(BIND_KV, {OPS.FIELD: bind_value}, cas=0)
self.call("POST", "/v1/platform/metadata/workloads/net-kingdom/directory-binds/svc-authelia", {"cas_required": True})
self.admin.write(JOURNEY_KV, {"PASSWORD": journey_password,
"TOTP_SEED": base64.b32encode(seed).decode()}, cas=0)
self.call("PUT", "/v1/sys/policies/acl/bind-executor-write", {"policy": POLICY_FILE.read_text()})
self.call("PUT", "/v1/sys/policies/acl/journey-read", {"policy": JOURNEY_POLICY})
tokens = []
for policy in ("bind-executor-write", "journey-read"): # one token per lane, as the live roles
tokens.append(self.call("POST", "/v1/auth/token/create", {"policies": [policy], "no_default_policy": True,
"ttl": "15m"})["auth"]["client_token"])
return tuple(tokens)
def remove(self):
stack_mod.docker("rm", "--force", self.name, check=False)
class Delivery:
"""Stand-in for the ExternalSecret: projects the KV value into Authelia's configuration file."""
def __init__(self, stack, bao_admin):
self.stack, self.bao, self.corrupt = stack, bao_admin, 0 # number of next deliveries to corrupt
def force_sync(self) -> dt.datetime:
at = OPS.now_utc()
value = self.bao.read(BIND_KV)[0][OPS.FIELD]
if self.corrupt:
self.corrupt -= 1
value = secrets.token_urlsafe(32) # a one-off wrong projection that still reports success
path = self.stack.workdir / "authelia.yml"
config = yaml.safe_load(path.read_text())
config["authentication_backend"]["ldap"]["password"] = value
path.write_text(yaml.safe_dump(config)) # same inode: the bind mount sees it
self.synced_at = OPS.now_utc()
return at
def synced(self, since: dt.datetime) -> bool:
return getattr(self, "synced_at", None) is not None and self.synced_at >= since
class Restart:
"""Stand-in for the rollout: restart the Authelia container and wait for it to answer."""
def __init__(self, stack):
self.stack = stack
def restart(self):
stack_mod.docker("restart", self.stack.names["authelia"])
# docker assigns a new ephemeral host port on restart (an artifact of this stand-in)
self.stack.ports["authelia"] = stack_mod.port(self.stack.names["authelia"], 9091)
try:
stack_mod.wait_http(f"http://127.0.0.1:{self.stack.ports['authelia']}/api/health", "authelia",
{"Host": "auth.journey.test", "X-Forwarded-Proto": "https"}, attempts=60)
except stack_mod.SetupError:
raise OPS.RolloutFailed("authelia did not become ready") from None
class MemoryCarrier:
"""approval-engine's approval object and consume semantics, in memory."""
def __init__(self):
self.approvals = {}
def approve(self, approval_id, admission):
spec = admission["spec"]
self.approvals[approval_id] = {
"id": approval_id, "status": "approved",
"binding": {"action": "bounded-execution", "actor": EXECUTOR, "principal": "admin", "purpose": spec["purpose"],
"target": {"admission_id": spec["id"], "admission_digest": bea.admission_digest(admission)},
"human_control": True, "pdp_path": False},
"validity": {"not_before": spec["window"]["not_before"], "expires_at": spec["window"]["not_after"]},
"entries": [{"subject_id": APPROVER_SUB, "assurance": "aal2", "principal_type": "human",
"approved_at": OPS.now_utc().isoformat()}]}
def get(self, approval_id):
return copy.deepcopy(self.approvals[approval_id])
def consume(self, approval_id, request_digest, decision_id):
a = self.approvals[approval_id]
if a["status"] != "approved":
raise RuntimeError("409")
a["status"] = "consumed"
a["consumption"] = {"request_digest": request_digest, "decision_id": decision_id,
"consumed_at": OPS.now_utc().isoformat()}
return {"approval_id": approval_id, "status": "consumed", "request_digest": request_digest,
"decision_id": decision_id, "consumed_at": a["consumption"]["consumed_at"], "idempotent": False}
def admission(admission_id: str, attempts: int) -> dict:
"""The pilot example, pinned to the current files, with a window around now."""
doc = bea.load(REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml")
spec = doc["spec"]
spec["id"] = admission_id
now = dt.datetime.now(dt.UTC).replace(microsecond=0)
spec["window"] = {"not_before": (now - dt.timedelta(minutes=5)).isoformat().replace("+00:00", "Z"),
"not_after": (now + dt.timedelta(hours=2)).isoformat().replace("+00:00", "Z")}
spec["budget"]["attempts"] = attempts
files = []
for f in spec["plan"]["source"]["files"]:
files.append({"path": f["path"], "sha256": hashlib.sha256((REPO / f["path"]).read_bytes()).hexdigest()})
spec["plan"]["source"]["files"] = files
pins = {f["path"]: f["sha256"] for f in files}
for c in spec["checks"]:
c["sha256"] = pins[c["ref"]]
spec["plan_digest"] = bea.plan_digest(doc)
report = bea.evaluate(doc, json.loads(bea.SCHEMA.read_text()))
if not report["valid"]:
raise SystemExit(f"synthetic admission invalid: {report['findings']}")
return doc
def lldap_command():
return ["docker", "run", "--rm", "--pull=never", "--network", "host", "--env", "LLDAP_USER_PASSWORD",
"--entrypoint", "/app/lldap_set_password", stack_mod.LLDAP_IMAGE]
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
parser.add_argument("--report")
args = parser.parse_args(argv)
report = {"schema": "netkingdom.bounded-executor.synthetic-pilot/v1",
"started_at": OPS.now_utc().isoformat(), "images": {"openbao": OPENBAO_IMAGE},
"policy_file_sha256": hashlib.sha256(POLICY_FILE.read_bytes()).hexdigest(), "runs": []}
ok = False
with tempfile.TemporaryDirectory(prefix="nk56-executor-") as tmp:
stack = stack_mod.Stack(pathlib.Path(tmp), pi_cache_timeout=120)
bao = None
try:
stack.up()
bao = OpenBaoDev(stack.network, stack.tag)
executor_token, journey_token = bao.setup(stack.pw["svc-authelia"], stack.pw[stack_mod.USER], stack.seed)
executor_bao = OPS.OpenBao(bao.addr, token=executor_token)
journey_bao = OPS.OpenBao(bao.addr, token=journey_token)
directory = OPS.Directory(stack.lldap, lldap_command())
delivery = Delivery(stack, bao.admin)
journey_state = {}
operations, checks = OPS.pilot_registry(
bao=executor_bao, journey_bao=journey_bao, directory=directory, delivery=delivery, restart=Restart(stack),
bind_kv=BIND_KV, bind_user="svc-authelia", journey_kv=JOURNEY_KV,
journey_config=stack.journey_config, journey_state=journey_state)
carrier, events, alerts = MemoryCarrier(), [], []
audit = type("Sink", (), {"emit": lambda _s, e: events.append(e)})()
cfg = X.Config(executor_identity=EXECUTOR, runtime_principal="synthetic:executor-token",
tenant="tenant:platform", environment="TEST", approver_subjects={"admin": APPROVER_SUB})
deps = X.Deps(carrier=carrier, operations=operations, checks=checks, trust=lambda s: True,
audit=audit, alert=lambda kind, info: alerts.append(kind))
def run(admission_id, attempts, corrupt):
doc = admission(admission_id, attempts)
approval = "apr-" + admission_id
carrier.approve(approval, doc)
before_value, before_version = bao.admin.read(BIND_KV)
delivery.corrupt = 1 if corrupt else 0
result = X.Run(doc, approval, cfg, deps).execute()
delivery.corrupt = 0
after_value, after_version = bao.admin.read(BIND_KV)
changed = after_value != before_value
old_refused = directory.login("svc-authelia", before_value[OPS.FIELD]) is None
new_works = directory.login("svc-authelia", after_value[OPS.FIELD]) is not None
receipt = result.get("receipt", {}).get("data", {})
return {"admission": admission_id, "outcome": result["outcome"], "reason": result.get("reason"),
"kv_version": [before_version, after_version], "kv_value_changed": changed,
"previous_value_refused_by_lldap": old_refused, "current_kv_value_signs_in": new_works,
"steps": [(s["id"], s["kind"], s["outcome"], [c["result"] for c in s.get("checks", [])],
s.get("error_type"))
for s in receipt.get("steps", [])],
"reverted": receipt.get("reverted"), "pdp": receipt.get("pdp"),
"events": [e["type"] for e in events if e["correlation_id"] == result.get("run_id")]}
rotation = run("BEA-2026-9001", 1, corrupt=False)
rotation["met"] = (rotation["outcome"] == "completed" and rotation["kv_value_changed"]
and rotation["previous_value_refused_by_lldap"] and rotation["current_kv_value_signs_in"])
report["runs"].append(rotation)
revert = run("BEA-2026-9002", 1, corrupt=True)
revert["met"] = (revert["outcome"] == "reverted" and not revert["kv_value_changed"]
and revert["current_kv_value_signs_in"]
and any(s[0] == "restart" and s[2] in ("check_failed", "failed") for s in revert["steps"])
and all(s[2] == "done" for s in revert["steps"] if s[1] == "revert"))
report["runs"].append(revert)
report["alerts"] = alerts
secrets_in_report = [stack.pw["svc-authelia"], executor_token, journey_token, bao.root]
text = json.dumps(report)
report["no_values_in_report"] = not any(v in text for v in secrets_in_report)
ok = rotation["met"] and revert["met"] and report["no_values_in_report"]
finally:
if bao:
bao.remove()
stack.down()
report["all_met"] = ok
report["finished_at"] = OPS.now_utc().isoformat()
text = json.dumps(report, indent=2, default=str)
if args.report:
pathlib.Path(args.report).write_text(text + "\n")
print(text)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())

View file

@ -0,0 +1,322 @@
"""The executor's own approval client and request phase (NK-WP-0056-T18). One fake server plays OpenBao, KeyCape and
approval-engine; no live service."""
from __future__ import annotations
import base64
import contextlib
import copy
import importlib.util
import io
import json
import pathlib
import sys
import tempfile
import threading
import unittest
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import yaml
HERE = pathlib.Path(__file__).resolve().parent
def load(name):
spec = importlib.util.spec_from_file_location(name, HERE.parent / f"{name}.py")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
CLI = load("cli")
C, OPS, X = CLI.C, CLI.OPS, CLI.X
ADMISSION = X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
SECRET = "synthetic-client-secret/with:specials"
CLIENT_KV = "platform/workloads/keycape/nk-bind-executor-approval"
APPROVAL_ROLE = "keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read"
class World:
def __init__(self):
self.requests = [] # (method, path, headers, body)
self.revoked = []
self.token_status = 200
self.token_body = None
self.create_status = 201
self.create_override = {}
self.kv = {"CLIENT_SECRET": SECRET}
self.memos = []
self.memo_status = 201
def serve(w: World):
class H(BaseHTTPRequestHandler):
def _send(self, code, body, headers=None):
raw = json.dumps(body).encode()
self.send_response(code)
for k, v in (headers or {}).items():
self.send_header(k, v)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def _body(self):
return self.rfile.read(int(self.headers.get("Content-Length") or 0))
def do_GET(self):
w.requests.append(("GET", self.path, dict(self.headers), None))
if self.path == "/v1/sys/health":
return self._send(200, {"initialized": True})
if self.path == "/v1/platform/data/workloads/keycape/nk-bind-executor-approval":
return self._send(200, {"data": {"data": w.kv, "metadata": {"version": 1}}})
self._send(404, {})
def do_POST(self):
raw = self._body()
w.requests.append(("POST", self.path, dict(self.headers), raw))
if self.path == "/v1/auth/kubernetes/login":
role = json.loads(raw)["role"]
return self._send(200, {"auth": {"client_token": f"tok-{role}", "lease_duration": 900}})
if self.path == "/v1/auth/token/revoke-self":
w.revoked.append(self.headers.get("X-Vault-Token"))
return self._send(204, {})
if self.path == "/token":
if w.token_status == 302:
return self._send(302, {}, {"Location": "http://elsewhere.invalid/token"})
if w.token_status != 200:
return self._send(w.token_status, {"error": "invalid_scope"})
scope = urllib.parse.parse_qs(raw.decode())["scope"][0]
return self._send(200, w.token_body or {"access_token": "at-" + scope.replace(":", "-"),
"token_type": "Bearer", "expires_in": 900})
if self.path == "/v1/memos":
env = json.loads(raw)
w.memos.append((dict(self.headers), env))
if w.memo_status != 201:
return self._send(w.memo_status, {"error": "approval_not_requested"})
m = env["memo"]
return self._send(201, {"memo_id": m["id"], "approval_id": m["approval_id"],
"approval_binding_digest": m["approval_binding_digest"]})
if self.path == "/v1/approvals":
body = json.loads(raw)
created = {"id": "apr-1", "status": "requested",
"binding": {**body["binding"], "digest": "sha256:" + "ab" * 32, "human_control": True},
"validity": body["validity"], **w.create_override}
return self._send(w.create_status, created)
self._send(404, {})
def log_message(self, *a):
return
server = ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.02}, daemon=True).start()
return server
class Base(unittest.TestCase):
def setUp(self):
self.w = World()
self.server = serve(self.w)
self.addCleanup(self.server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = pathlib.Path(tmp.name)
(self.root / "jwt").write_text("synthetic-openbao-audience-jwt\n")
self.addr = f"http://127.0.0.1:{self.server.server_port}"
def cfg(self, **approval_client):
return {
"executor_identity": "service:nk-bind-executor",
"approver_subjects": {"admin": "uid=admin,ou=people,dc=netkingdom,dc=local"},
"openbao": {"addr": self.addr, "k8s_role": "net-kingdom-bind-executor-svc-authelia-write",
"jwt_file": str(self.root / "jwt")},
"journey": {"kv": "x", "k8s_role": "net-kingdom-bind-executor-syn-journey-read", "config": {}},
"approval_engine": {"url": self.addr, "client": {
"token_url": self.addr + "/token", "client_id": "nk-bind-executor-approval",
"k8s_role": APPROVAL_ROLE, "kv": CLIENT_KV, "host": "kc.coulomb.social", **approval_client}},
}
def calls(self, path):
return [r for r in self.w.requests if r[1] == path]
class KeyCapeExchange(Base):
def client(self):
return C.KeyCapeClientCredentials(self.addr + "/token", "nk-bind-executor-approval", lambda: SECRET,
host="kc.coulomb.social")
def test_client_secret_basic_one_scope_public_host(self):
self.assertEqual(self.client().token("approval:create"), "at-approval-create")
_, _, headers, raw = self.calls("/token")[0]
user, _, password = base64.b64decode(headers["Authorization"].split()[1]).decode().partition(":")
self.assertEqual((urllib.parse.unquote(user), urllib.parse.unquote(password)),
("nk-bind-executor-approval", SECRET))
self.assertEqual(urllib.parse.parse_qs(raw.decode()),
{"grant_type": ["client_credentials"], "scope": ["approval:create"]})
self.assertEqual((headers["Host"], headers["X-Forwarded-Proto"]), ("kc.coulomb.social", "https"))
def test_refusals(self):
with self.assertRaises(C.ClientError):
self.client().token("approval:read approval:create") # one scope per token
self.w.token_status = 400
with self.assertRaisesRegex(C.ClientError, "token 400"):
self.client().token("approval:read")
self.w.token_status = 302 # credentials never follow a redirect
with self.assertRaises(C.ClientError):
self.client().token("approval:read")
self.w.token_status = 200
for body in ({"access_token": "a", "token_type": "Bearer", "expires_in": 3600},
{"access_token": "a", "token_type": "mac", "expires_in": 900},
{"access_token": "a b", "token_type": "Bearer", "expires_in": 900}):
self.w.token_body = body
with self.subTest(body=body), self.assertRaisesRegex(C.ClientError, "token response"):
self.client().token("approval:read")
class ScopesPerCall(Base):
def test_each_approval_call_asks_for_exactly_its_scope(self):
seen = []
ae = C.ApprovalEngine(self.addr, token_provider=lambda scope: seen.append(scope) or "t")
ae.create({"action": "a"}, {}, 1)
with contextlib.suppress(C.ClientError):
ae.get("apr-1")
with contextlib.suppress(C.ClientError):
ae.consume("apr-1", "sha256:" + "0" * 64, "run-1")
self.assertEqual(seen, ["approval:create", "approval:read", "approval:consume"])
body = json.loads(self.calls("/v1/approvals")[0][3])
self.assertEqual((body["human_control"], body["pdp_path"], body["required_count"]), (True, False, 1))
def test_exactly_one_token_source(self):
with self.assertRaises(C.ClientError):
C.ApprovalEngine(self.addr)
with self.assertRaises(C.ClientError):
C.ApprovalEngine(self.addr, token_file="/x", token_provider=lambda s: "t")
INFD = pathlib.Path("/home/worsch/informed-decision")
class PresentToFounder(Base):
"""T21: after creating the approval the executor presents it through informed-decision (present_existing)."""
def run_cli(self, cfg):
(self.root / "cfg.json").write_text(json.dumps(cfg))
(self.root / "intake-token").write_text("synthetic-intake-token\n")
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--request", "--admission", str(ADMISSION), "--config", str(self.root / "cfg.json")])
return code, json.loads(out.getvalue()), out.getvalue()
def cfg(self, **kw):
return {**super().cfg(), "informed_decision": {"url": self.addr,
"token_file": str(self.root / "intake-token")}}
def test_files_then_presents_with_the_admission_as_packet(self):
code, result, text = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"]), (0, "presented"))
headers, env = self.w.memos[0]
self.assertEqual(headers["Authorization"], "Bearer synthetic-intake-token")
self.assertEqual(env["memo"]["approval_id"], "apr-1")
self.assertEqual(env["memo"]["approval_binding_digest"], "sha256:" + "ab" * 32)
self.assertEqual(env["packet"][0]["content"], ADMISSION.read_text())
body = json.loads(self.calls("/v1/approvals")[0][3])
self.assertEqual(env["approval_intent"], {k: body["binding"][k] for k in ("action", "principal", "purpose", "target")})
self.assertNotIn("actor", env["approval_intent"])
self.assertEqual(result["memo_id"], env["memo"]["id"])
self.assertNotIn(SECRET, text)
def test_refused_memo_keeps_the_approval_and_exits_7(self):
self.w.memo_status = 409
code, result, _ = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"], result["approval_id"]), (7, "requested_not_presented", "apr-1"))
def test_envelope_passes_informed_decisions_own_validator(self):
if not (INFD / "informed_decision" / "intake.py").exists():
self.skipTest("informed-decision checkout absent")
sys.path.insert(0, str(INFD))
try:
from informed_decision import intake
finally:
sys.path.remove(str(INFD))
doc = X.bea.load(ADMISSION)
binding, _, _ = CLI.request_binding(doc, super().cfg())
approval = {"id": "0f1e2d3c-4b5a-4968-8776-655443322110", "binding": {**binding, "digest": "sha256:" + "b" * 64}}
env = CLI.admission_memo(doc, ADMISSION.read_text(), binding, approval)
grant = intake.SubmitterGrant("nk-bind-executor", frozenset({"bounded-execution"}),
frozenset({"net-kingdom-admins"}))
profile = intake.PresentExisting("service:nk-bind-executor", frozenset({"bounded-execution"}))
validated = intake.validate_submission(env, submitter_id="nk-bind-executor", grant=grant,
present_existing=profile)
self.assertEqual(validated.memo.id, env["memo"]["id"])
class RequestPhase(Base):
def run_cli(self, cfg, admission=ADMISSION):
(self.root / "cfg.json").write_text(json.dumps(cfg))
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--request", "--admission", str(admission), "--config", str(self.root / "cfg.json")])
return code, json.loads(out.getvalue()), out.getvalue()
def admission_with(self, mutate):
doc = yaml.safe_load(ADMISSION.read_text())
mutate(doc["spec"])
path = self.root / "admission.yaml"
path.write_text(yaml.safe_dump(doc, sort_keys=False))
return path
def test_files_the_approval_as_the_executor(self):
code, result, text = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"], result["approval_id"]), (0, "requested", "apr-1"))
doc = X.bea.load(ADMISSION)
body = json.loads(self.calls("/v1/approvals")[0][3])
self.assertEqual(body["binding"], {
"action": "bounded-execution", "actor": "service:nk-bind-executor",
"principal": "uid=admin,ou=people,dc=netkingdom,dc=local",
"purpose": f"{doc['spec']['id']}: {doc['spec']['title']}"[:200],
"target": {"admission_id": doc["spec"]["id"], "admission_digest": X.bea.admission_digest(doc)}})
self.assertEqual(body["validity"], {"not_before": doc["spec"]["window"]["not_before"],
"expires_at": doc["spec"]["window"]["not_after"]})
self.assertEqual((body["human_control"], body["pdp_path"], body["required_count"]), (True, False, 1))
# the approval token was exchanged for exactly approval:create, with the bearer from KeyCape
self.assertEqual(self.calls("/v1/approvals")[0][2]["Authorization"], "Bearer at-approval-create")
# the approval lane logged in once, read the secret and was revoked; no other lane logged in
logins = [json.loads(r[3])["role"] for r in self.calls("/v1/auth/kubernetes/login")]
self.assertEqual(logins, [APPROVAL_ROLE])
self.assertIn("tok-" + APPROVAL_ROLE, self.w.revoked)
self.assertNotIn(SECRET, text)
def test_refused_before_anything_is_sent(self):
cases = {
"another executor": (self.cfg(), self.admission_with(
lambda s: s["executor"].update(identity="service:other"))),
"unmapped approver": ({**self.cfg(), "approver_subjects": {}}, ADMISSION),
"closed window": (self.cfg(), self.admission_with(lambda s: s["window"].update(
not_before="2026-01-01T09:00:00Z", not_after="2026-01-01T11:00:00Z"))),
"shared role": (self.cfg(k8s_role="net-kingdom-bind-executor-syn-journey-read"), ADMISSION),
}
for label, (cfg, admission) in cases.items():
self.w.requests.clear()
with self.subTest(label):
code, result, _ = self.run_cli(cfg, admission)
self.assertEqual((code, result["outcome"]), (4, "refused"))
self.assertEqual([r for r in self.w.requests if r[1] != "/v1/sys/health"], [])
def test_refused_when_the_created_approval_differs(self):
for override in ({"status": "approved"}, {"binding": {"actor": "service:other"}}):
self.w.create_override = override
with self.subTest(override=override):
code, result, _ = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"]), (4, "refused"))
def test_bad_client_kv_shape_is_refused_and_revoked(self):
self.w.kv = {"CLIENT_SECRET": SECRET, "EXTRA": "x"}
code, result, text = self.run_cli(self.cfg())
self.assertEqual((code, result["reason"]), (4, "request:OperationError"))
self.assertIn("tok-" + APPROVAL_ROLE, self.w.revoked)
self.assertEqual(self.calls("/v1/approvals"), [])
self.assertNotIn(SECRET, text)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,158 @@
"""approval-engine and audit-core clients, and the CLI's setup failure path (NK-WP-0056-T13). Fakes only."""
from __future__ import annotations
import importlib.util
import json
import pathlib
import sys
import tempfile
import threading
import unittest
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HERE = pathlib.Path(__file__).resolve().parent
def load(name):
spec = importlib.util.spec_from_file_location(name, HERE.parent / f"{name}.py")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
C = load("clients")
CLI = load("cli")
class Fake:
def __init__(self):
self.requests = []
self.status = 200
def serve(fake):
class H(BaseHTTPRequestHandler):
def handle_one(self, method):
raw = self.rfile.read(int(self.headers.get("Content-Length", "0") or 0))
self.server_fake.requests.append((method, self.path, dict(self.headers), json.loads(raw) if raw else None))
body = json.dumps({"id": "apr-1", "status": "approved"} if method == "GET" else
{"status": "consumed", "idempotent": False}).encode()
self.send_response(self.server_fake.status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
self.handle_one("GET")
def do_POST(self):
self.handle_one("POST")
def log_message(self, *a):
return
H.server_fake = fake
server = ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.02}, daemon=True).start()
return server
class ClientTests(unittest.TestCase):
def setUp(self):
self.fake = Fake()
self.server = serve(self.fake)
self.addCleanup(self.server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.token = pathlib.Path(tmp.name) / "token"
self.token.write_text("synthetic-bearer\n")
self.base = f"http://127.0.0.1:{self.server.server_port}"
def test_approval_engine_get_and_consume(self):
ae = C.ApprovalEngine(self.base, str(self.token))
self.assertEqual(ae.get("apr-1")["status"], "approved")
ae.consume("apr-1", "sha256:" + "a" * 64, "run-1")
(_, p1, h1, _), (_, p2, _, b2) = self.fake.requests
self.assertEqual((p1, p2), ("/v1/approvals/apr-1", "/v1/approvals/apr-1/consume"))
self.assertEqual(h1["Authorization"], "Bearer synthetic-bearer")
self.assertEqual(b2, {"request_digest": "sha256:" + "a" * 64, "decision_id": "run-1"})
def test_approval_id_cannot_escape_the_path(self):
ae = C.ApprovalEngine(self.base, str(self.token))
for bad in ("../admin", "", "a/b"):
with self.subTest(bad=bad), self.assertRaises(C.ClientError):
ae.get(bad)
self.assertEqual(self.fake.requests, [])
def test_audit_core_idempotency_key_equals_event_id(self):
event = {"id": "run:r1:started", "type": "execution.run.started", "source": "s", "subject": "x",
"tenant": "tenant:platform", "correlation_id": "r1", "occurred_at": "2026-11-03T09:30:00+00:00",
"data": {"a": 1}}
C.AuditCore(self.base, str(self.token)).emit(event)
((method, path, headers, body),) = self.fake.requests
self.assertEqual((method, path, headers["Idempotency-Key"]), ("POST", "/v1/events", "run:r1:started"))
self.assertEqual(body, event)
def test_errors_carry_status_only(self):
self.fake.status = 403
with self.assertRaises(C.ClientError) as caught:
C.ApprovalEngine(self.base, str(self.token)).get("apr-1")
self.assertEqual(str(caught.exception), "GET 403")
class AuditStatuses(unittest.TestCase):
"""audit-core: 202 first accept, 200 duplicate, 409 reused run id (audit-core AUDIT-WP-0018-T02, 2026-10-31)."""
def emit_with(self, status):
fake = Fake()
fake.status = status
server = serve(fake)
self.addCleanup(server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
token = pathlib.Path(tmp.name) / "token"
token.write_text("synthetic-bearer\n")
C.AuditCore(f"http://127.0.0.1:{server.server_port}", str(token)).emit({"id": "run:r:started"})
def test_202_and_200_are_stored(self):
self.emit_with(202)
self.emit_with(200)
def test_409_is_a_distinct_defect(self):
with self.assertRaisesRegex(C.ClientError, "conflict 409"):
self.emit_with(409)
with self.assertRaisesRegex(C.ClientError, "500"):
self.emit_with(500)
class CliTests(unittest.TestCase):
def test_exit_codes(self):
self.assertEqual(CLI.exit_code({"outcome": "completed"}), 0)
self.assertEqual(CLI.exit_code({"outcome": "reverted"}), 3)
self.assertEqual(CLI.exit_code({"outcome": "refused", "reason": "approval_entry"}), 4)
self.assertEqual(CLI.exit_code({"outcome": "revert_failed"}), 5)
self.assertEqual(CLI.exit_code({"outcome": "stopped", "reason": "audit_unavailable"}), 6)
self.assertEqual(CLI.exit_code({"outcome": "completed", "reason": "receipt_not_in_custody"}), 6)
def test_setup_failure_refuses_before_any_effect(self):
with tempfile.TemporaryDirectory() as tmp:
root = pathlib.Path(tmp)
(root / "jwt").write_text("synthetic-jwt")
cfg = {"openbao": {"addr": "http://127.0.0.1:9", "k8s_role": "r", "jwt_file": str(root / "jwt")}}
(root / "cfg.json").write_text(json.dumps(cfg))
admission = CLI.X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
import contextlib, io
wait = CLI.OPS.wait_reachable
self.addCleanup(setattr, CLI.OPS, "wait_reachable", wait)
CLI.OPS.wait_reachable = lambda addr, **kw: wait(addr, **{**kw, "timeout": 0.2, "interval": 0.1})
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--admission", str(admission), "--approval-id", "apr-1", "--config", str(root / "cfg.json")])
self.assertEqual(code, 4)
self.assertEqual(json.loads(out.getvalue())["reason"].split(":")[0], "setup")
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,405 @@
"""Core runner tests (NK-WP-0056-T11). Fakes only: carrier, operations, checks, trust, audit, clocks."""
from __future__ import annotations
import copy
import datetime as dt
import hashlib
import importlib.util
import json
import pathlib
import sys
import tempfile
import unittest
HERE = pathlib.Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("executor", HERE.parent / "executor.py")
X = importlib.util.module_from_spec(spec)
sys.modules["executor"] = X # dataclasses resolve deferred annotations through sys.modules
spec.loader.exec_module(X)
bea = X.bea
PILOT = bea.load(X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml")
ADMIN_SUB = "kc-sub-0f3a" # opaque KeyCape subject of the human approver `admin`
NOW = dt.datetime(2026, 11, 3, 9, 30, tzinfo=dt.UTC)
SECRET = "synthetic-bind-value-never-emitted"
class Clock:
def __init__(self):
self.t = 0.0
self.sleeps = []
def monotonic(self):
return self.t
def sleep(self, s):
self.sleeps.append(s)
self.t += s
class FakeCarrier:
"""approval-engine's object and consume semantics (store.Approval.as_dict, Engine.consume)."""
def __init__(self, admission, **over):
self.approval = {
"id": "apr-1", "status": "approved",
"binding": {"action": "bounded-execution", "actor": admission["spec"]["executor"]["identity"],
"principal": "admin", "purpose": admission["spec"]["purpose"],
"target": {"admission_id": admission["spec"]["id"],
"admission_digest": bea.admission_digest(admission)},
"human_control": True, "pdp_path": False},
"validity": {"not_before": admission["spec"]["window"]["not_before"],
"expires_at": admission["spec"]["window"]["not_after"]},
"entries": [{"subject_id": ADMIN_SUB, "assurance": "aal2", "principal_type": "human",
"approved_at": "2026-11-03T08:55:00Z"}],
}
for k, v in over.items():
self.approval[k] = v
self.consumes = 0
self.raise_on_consume = False
self.consume_lands = True
def get(self, approval_id):
return copy.deepcopy(self.approval)
def consume(self, approval_id, request_digest, decision_id):
self.consumes += 1
if self.consume_lands:
if self.approval["status"] == "consumed":
same = self.approval["consumption"]["request_digest"] == request_digest
if not same:
raise RuntimeError("409")
return {"approval_id": approval_id, "status": "consumed", "request_digest": request_digest,
"decision_id": self.approval["consumption"].get("decision_id"),
"consumed_at": "x", "idempotent": True}
self.approval["status"] = "consumed"
self.approval["consumption"] = {"request_digest": request_digest, "decision_id": decision_id,
"consumed_at": "2026-11-03T09:30:00Z"}
if self.raise_on_consume:
raise TimeoutError("connection reset")
return {"approval_id": approval_id, "status": "consumed", "request_digest": request_digest,
"decision_id": decision_id, "consumed_at": "2026-11-03T09:30:00Z", "idempotent": False}
class World:
def __init__(self, admission, clock):
self.calls = []
self.fail_check = {} # check id -> number of times to fail
self.raise_op = set() # operation names that raise
self.unknown_op = set()
self.slow_op = 0.0
self.events = []
self.alerts = []
self.audit_down = False
self.stop_after = None
self.clock = clock
self.carrier = FakeCarrier(admission)
self.trust_ok = True
def op(self, step, ctx):
if step["operation"] in self.unknown_op:
raise X.Unknown()
if step["id"] in self.raise_op:
raise RuntimeError("boom")
ctx.data["value"] = SECRET # operations may hold values; the runner must never emit them
self.calls.append(step["id"])
self.clock.t += self.slow_op
def check(self, check, ctx):
self.calls.append("check:" + check["id"])
left = self.fail_check.get(check["id"], 0)
if left:
self.fail_check[check["id"]] = left - 1
return False
return True
def emit(self, event):
if self.audit_down:
raise ConnectionError()
self.events.append(event)
def should_stop(self):
return self.stop_after is not None and len([c for c in self.calls if not c.startswith("check:")]) >= self.stop_after
def deps(self, admission):
ops = {s["operation"]: self.op for s in admission["spec"]["plan"]["steps"] + admission["spec"]["plan"]["revert_steps"]}
checks = {c["id"]: self.check for c in admission["spec"]["checks"]}
audit = type("A", (), {"emit": lambda _s, e: self.emit(e)})()
return X.Deps(carrier=self.carrier, operations=ops, checks=checks, trust=lambda s: self.trust_ok,
audit=audit, alert=lambda kind, info: self.alerts.append(kind),
should_stop=self.should_stop, wall=lambda: NOW,
monotonic=self.clock.monotonic, sleep=self.clock.sleep)
def synthetic_admission(root: pathlib.Path, mutate=None) -> dict:
"""The pilot example, with its pinned files replaced by temporary ones and the digest recomputed."""
doc = copy.deepcopy(PILOT)
files = []
for f in doc["spec"]["plan"]["source"]["files"]:
path = root / f["path"]
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("synthetic " + f["path"])
files.append({"path": f["path"], "sha256": hashlib.sha256(path.read_bytes()).hexdigest()})
doc["spec"]["plan"]["source"]["files"] = files
by_path = {f["path"]: f["sha256"] for f in files}
for c in doc["spec"]["checks"]:
c["sha256"] = by_path[c["ref"]]
if mutate:
mutate(doc["spec"])
doc["spec"]["plan_digest"] = bea.plan_digest(doc)
return doc
class Base(unittest.TestCase):
mutate = None
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = pathlib.Path(tmp.name)
self.doc = synthetic_admission(self.root, self.mutate)
self.clock = Clock()
self.w = World(self.doc, self.clock)
self.cfg = X.Config(executor_identity="service:nk-bind-executor",
runtime_principal="system:serviceaccount:sso:nk-bind-executor",
tenant="tenant:platform", environment="PRODUCTION",
approver_subjects={"admin": ADMIN_SUB}, source_root=self.root)
def run_it(self, doc=None):
doc = doc or self.doc
return X.Run(doc, "apr-1", self.cfg, self.w.deps(doc)).execute()
def ops(self):
return [c for c in self.w.calls if not c.startswith("check:")]
def assert_no_effect(self, result, reason):
self.assertEqual((result["outcome"], result["reason"]), ("refused", reason))
self.assertEqual(self.w.carrier.consumes, 0)
self.assertEqual(self.w.calls, [])
self.assertEqual(self.w.events, [])
class HappyPathTests(Base):
def test_completes_with_started_and_receipt(self):
result = self.run_it()
self.assertEqual(result["outcome"], "completed", result)
self.assertEqual(self.w.carrier.consumes, 1)
self.assertEqual(self.ops(), ["generate", "set-directory", "deliver", "restart"])
self.assertEqual([e["type"] for e in self.w.events], [X.STARTED, X.RECEIPT])
started, receipt = self.w.events
self.assertEqual(started["id"], f"run:{result['run_id']}:started")
for event in self.w.events: # audit-core: eight truthy fields, offset-qualified occurred_at
for key in ("id", "type", "source", "subject", "tenant", "correlation_id", "occurred_at", "data"):
self.assertTrue(event[key], key)
self.assertEqual(dt.datetime.fromisoformat(event["occurred_at"]).utcoffset(), dt.timedelta(0))
data = receipt["data"]
self.assertEqual(data["pdp"], "not-evaluated")
self.assertEqual({r["label"] for r in data["decision_refs"]}, {"admission", "admission_digest", "approval"})
self.assertEqual(data["initiator"]["sub"], ADMIN_SUB)
self.assertEqual(data["artifact_digest"], self.doc["spec"]["plan_digest"])
self.assertFalse(data["reverted"])
self.assertNotIn(SECRET, json.dumps(self.w.events))
def test_consumed_with_admission_digest_and_run_id(self):
result = self.run_it()
consumption = self.w.carrier.approval["consumption"]
self.assertEqual(consumption["request_digest"], bea.admission_digest(self.doc))
self.assertEqual(consumption["decision_id"], result["run_id"])
class RefusalTests(Base):
def test_invalid_admission(self):
doc = copy.deepcopy(self.doc)
doc["spec"]["plan"]["steps"][0]["operation"] = "tampered"
self.w = World(doc, self.clock)
self.assertEqual(self.run_it(doc)["reason"], "admission_invalid:BEA-2")
self.assertEqual(self.w.carrier.consumes, 0)
def test_refusals_before_any_effect(self):
cases = {
"not_this_executor": lambda: setattr(self.cfg, "executor_identity", "service:other"),
"source_drift": lambda: (self.root / "bounded-executor/operations.py").write_text("changed"),
"trust_not_satisfied:runtime_secret_trust": lambda: setattr(self.w, "trust_ok", False),
"approval_binding": lambda: self.w.carrier.approval["binding"]["target"].update(admission_digest="sha256:" + "0" * 64),
"approval_status": lambda: self.w.carrier.approval.update(status="requested"),
"approval_entry": lambda: self.w.carrier.approval["entries"][0].update(assurance="aal1"),
"approval_expires_before_window_end": lambda: self.w.carrier.approval["validity"].update(expires_at="2026-11-03T10:00:00Z"),
}
for reason, mutate in cases.items():
with self.subTest(reason=reason):
self.setUp()
mutate()
self.assert_no_effect(self.run_it(), reason)
def test_approval_by_someone_not_listed_or_not_human(self):
for entry in ({"subject_id": "kc-sub-other", "assurance": "aal2", "principal_type": "human"},
{"subject_id": ADMIN_SUB, "assurance": "aal2", "principal_type": "service"}):
with self.subTest(entry=entry):
self.setUp()
self.w.carrier.approval["entries"] = [entry]
self.assert_no_effect(self.run_it(), "approval_entry")
def test_actor_must_be_this_executor(self):
self.w.carrier.approval["binding"]["actor"] = "service:someone-else"
self.assert_no_effect(self.run_it(), "approval_binding")
def test_outside_window(self):
self.w.deps = (lambda orig: (lambda doc: (lambda d: (setattr(d, "wall", lambda: NOW + dt.timedelta(hours=3)), d)[1])(orig(doc))))(self.w.deps)
self.assert_no_effect(self.run_it(), "outside_window")
def test_window_too_short_for_the_budget(self):
late = dt.datetime(2026, 11, 3, 10, 50, tzinfo=dt.UTC) # 10 min left, budget 20 min
orig = self.w.deps
self.w.deps = lambda doc: (lambda d: (setattr(d, "wall", lambda: late), d)[1])(orig(doc))
self.assert_no_effect(self.run_it(), "window_shorter_than_budget")
def test_already_consumed_is_never_reused(self):
self.w.carrier.approval["status"] = "consumed"
self.w.carrier.approval["consumption"] = {"request_digest": bea.admission_digest(self.doc), "decision_id": "earlier-run"}
self.assert_no_effect(self.run_it(), "approval_status")
def test_unknown_operation(self):
d = self.w.deps(self.doc)
d.operations.pop("eso.force-sync")
result = X.Run(self.doc, "apr-1", self.cfg, d).execute()
self.assert_no_effect(result, "unknown_operation")
class ConsumeTests(Base):
def test_unclear_consume_resolved_by_reading_back(self):
self.w.carrier.raise_on_consume = True # consume landed, response lost
result = self.run_it()
self.assertEqual(result["outcome"], "completed")
self.assertEqual(self.w.carrier.consumes, 1) # never consumed twice
def test_unclear_consume_that_did_not_land_stops(self):
self.w.carrier.raise_on_consume = True
self.w.carrier.consume_lands = False
result = self.run_it()
self.assertEqual((result["outcome"], result["reason"]), ("refused", "consume_not_confirmed"))
self.assertEqual((self.w.calls, self.w.events), ([], []))
self.assertIn("consume_unconfirmed", self.w.alerts)
def test_idempotent_consume_is_not_a_fresh_run(self):
original = self.w.carrier.consume
self.w.carrier.consume = lambda a, d, r: dict(original(a, d, r), idempotent=True)
result = self.run_it()
self.assertEqual(result["reason"], "consume_response")
self.assertEqual(self.w.calls, [])
def test_no_custody_no_run(self):
self.w.audit_down = True
result = self.run_it()
self.assertEqual((result["outcome"], result["reason"]), ("stopped", "audit_unavailable"))
self.assertEqual(self.w.calls, [])
self.assertIn("started_not_in_custody", self.w.alerts)
class FailureTests(Base):
def test_failed_check_reverts_in_reverse_and_verifies(self):
self.doc = synthetic_admission(self.root, lambda s: s["budget"].update(attempts=1))
self.w = World(self.doc, self.clock)
self.w.fail_check["eso-synced"] = 1
result = self.run_it()
self.assertEqual(result["outcome"], "reverted", result)
# declared recovery order, only for steps that ran (restart did not run, so no restart-again)
self.assertEqual(self.ops(), ["generate", "set-directory", "deliver", "restore-kv", "restore-directory", "redeliver"])
data = result["receipt"]["data"]
self.assertTrue(data["reverted"])
reverts = [x for x in data["steps"] if x["kind"] == "revert"]
self.assertEqual([x["checks"][0]["id"] for x in reverts], ["kv-advanced", "bind-signs-in", "eso-synced"])
self.assertTrue(all(x["outcome"] == "done" for x in reverts))
self.assertEqual(data["attempts"], 1)
def test_clean_revert_may_retry_within_attempts(self):
self.doc = synthetic_admission(self.root, lambda s: s["budget"].update(attempts=2))
self.w = World(self.doc, self.clock)
self.w.fail_check["synthetic-journey"] = 1
result = self.run_it()
self.assertEqual(result["outcome"], "completed")
self.assertEqual(result["receipt"]["data"]["attempts"], 2)
self.assertTrue(result["receipt"]["data"]["reverted"])
def test_revert_whose_check_fails_counts_as_failed(self):
self.doc = synthetic_admission(self.root, lambda s: s["budget"].update(attempts=1))
self.w = World(self.doc, self.clock)
self.w.fail_check["eso-synced"] = 1
self.w.fail_check["kv-advanced"] = 0
original = self.w.check
calls = {"kv": 0}
def check(c, ctx): # kv-advanced passes forward, fails on the revert
if c["id"] == "kv-advanced":
calls["kv"] += 1
if calls["kv"] == 2:
self.w.calls.append("check:kv-advanced")
return False
return original(c, ctx)
self.w.check = check
result = self.run_it()
self.assertEqual(result["outcome"], "revert_failed")
self.assertNotIn("restore-directory", self.ops()) # stop at the failed revert
def test_failed_revert_stops_and_alerts(self):
self.w.fail_check["eso-synced"] = 1
self.w.raise_op = {"restore-directory"}
result = self.run_it()
self.assertEqual(result["outcome"], "revert_failed")
self.assertIn("revert_failed", self.w.alerts)
self.assertNotIn("redeliver", self.ops()) # stop at the failed revert, do not continue blindly
def test_unknown_outcome_stops_without_revert(self):
self.w.unknown_op = {"eso.force-sync"}
result = self.run_it()
self.assertEqual(result["outcome"], "unknown")
self.assertEqual(self.ops(), ["generate", "set-directory"])
self.assertFalse(result["receipt"]["data"]["reverted"])
self.assertIn("unknown", self.w.alerts)
def test_definite_failure_reverts_like_a_failed_check(self):
self.doc = synthetic_admission(self.root, lambda s: s["budget"].update(attempts=1))
self.w = World(self.doc, self.clock)
original = self.w.op
def op(step, ctx):
if step["id"] == "restart":
self.w.calls.append("restart")
raise X.StepFailed("never became ready")
return original(step, ctx)
self.w.op = op
result = self.run_it()
self.assertEqual(result["outcome"], "reverted")
self.assertEqual(self.ops(), ["generate", "set-directory", "deliver", "restart",
"restore-kv", "restore-directory", "redeliver", "restart-again"])
restart = next(s for s in result["receipt"]["data"]["steps"] if s["id"] == "restart")
self.assertEqual((restart["outcome"], restart["error_type"]), ("failed", "StepFailed"))
def test_budget_exhausted_reverts(self):
self.w.slow_op = 700.0 # wall_time_seconds is 1200
result = self.run_it()
self.assertEqual(result["outcome"], "budget")
self.assertTrue(result["receipt"]["data"]["reverted"])
def test_kill_switch_reverts(self):
self.w.stop_after = 2
result = self.run_it()
self.assertEqual(result["outcome"], "stopped")
self.assertIn("restore-kv", self.ops())
self.assertNotIn("deliver", self.ops())
def test_settle_time_is_waited_and_counted(self):
self.doc = synthetic_admission(self.root, lambda s: s["checks"][3].update(settle_seconds=125))
self.w = World(self.doc, self.clock)
result = self.run_it()
self.assertEqual(result["outcome"], "completed")
self.assertIn(125, self.clock.sleeps)
def test_receipt_custody_failure_is_reported(self):
original = self.w.emit
self.w.emit = lambda e: (original(e) if e["type"] == X.STARTED else (_ for _ in ()).throw(ConnectionError()))
result = self.run_it()
self.assertEqual(result["reason"], "receipt_not_in_custody")
self.assertIn("receipt_not_in_custody", self.w.alerts)
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,246 @@
"""One OpenBao login per lane, renewal before the lease ends, revocation on every path (NK-WP-0056-T10). Fakes only."""
from __future__ import annotations
import contextlib
import importlib.util
import io
import json
import pathlib
import sys
import tempfile
import threading
import time
import unittest
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HERE = pathlib.Path(__file__).resolve().parent
def load(name):
spec = importlib.util.spec_from_file_location(name, HERE.parent / f"{name}.py")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
CLI = load("cli")
OPS = sys.modules["operations"]
class Bao:
def __init__(self, lease=900):
self.lease = lease
self.logins, self.revoked, self.reads = [], [], []
self.n = 0
def serve(bao: Bao):
class H(BaseHTTPRequestHandler):
def _send(self, body):
raw = json.dumps(body).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_POST(self):
body = json.loads(self.rfile.read(int(self.headers.get("Content-Length") or 0)) or b"{}")
if self.path == "/v1/auth/kubernetes/login":
bao.n += 1
bao.logins.append((body["role"], body["jwt"]))
return self._send({"auth": {"client_token": f"tok-{body['role']}-{bao.n}",
"lease_duration": bao.lease}})
if self.path == "/v1/auth/token/revoke-self":
bao.revoked.append(self.headers.get("X-Vault-Token"))
return self._send({})
self._send({})
def do_GET(self):
bao.reads.append((self.path, self.headers.get("X-Vault-Token")))
self._send({"data": {"data": {"PASSWORD": "p", "TOTP_SEED": "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"}, "metadata": {"version": 3}}})
def log_message(self, *a):
return
server = ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.02}, daemon=True).start()
return server
class Base(unittest.TestCase):
def setUp(self):
self.bao = Bao()
self.server = serve(self.bao)
self.addCleanup(self.server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = pathlib.Path(tmp.name)
(self.root / "jwt").write_text("synthetic-openbao-audience-jwt\n")
self.addr = f"http://127.0.0.1:{self.server.server_port}"
class CliLanes(Base):
def run_cli(self, cfg):
(self.root / "cfg.json").write_text(json.dumps(cfg))
admission = CLI.X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--admission", str(admission), "--approval-id", "apr-1",
"--config", str(self.root / "cfg.json")])
return code, json.loads(out.getvalue())
def cfg(self, journey_role="journey-read"):
# lldap is missing on purpose: setup fails after both logins
return {"openbao": {"addr": self.addr, "k8s_role": "bind-write", "jwt_file": str(self.root / "jwt")},
"journey": {"kv": "x", "k8s_role": journey_role, "config": {}}}
def test_two_lanes_two_logins_both_revoked_when_setup_fails(self):
code, result = self.run_cli(self.cfg())
self.assertEqual((code, result["reason"]), (4, "setup:KeyError"))
self.assertEqual([role for role, _ in self.bao.logins], ["bind-write", "journey-read"])
self.assertEqual(sorted(self.bao.revoked), ["tok-bind-write-1", "tok-journey-read-2"])
def test_one_role_for_both_lanes_is_refused_before_any_login(self):
code, result = self.run_cli(self.cfg(journey_role="bind-write"))
self.assertEqual((code, result["reason"]), (4, "setup:ValueError"))
self.assertEqual(self.bao.logins, [])
class JourneyOnly(Base):
SEED = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"
def setUp(self):
super().setUp()
self.calls = []
original = (OPS.journey.run_journey, OPS._load_config)
self.addCleanup(lambda: (setattr(OPS.journey, "run_journey", original[0]),
setattr(OPS, "_load_config", original[1])))
OPS._load_config = lambda cfg: cfg
self.result = "pass"
def run_journey(cfg, password, seed, min_time_step=None):
self.calls.append(cfg)
return {"result": self.result, "failed_step": None if self.result == "pass" else "authelia_first_factor",
"reason": None, "otp_time_step": 1}
OPS.journey.run_journey = run_journey
def cfg(self, **journey):
# write role, approval-engine, audit-core, lldap and kubernetes are absent on purpose: not needed
return {"openbao": {"addr": self.addr, "k8s_role": "bind-write", "jwt_file": str(self.root / "jwt")},
"journey": {"kv": "platform/workloads/x", "k8s_role": "journey-read", "config": {"c": 1}, **journey}}
def run_cli(self, cfg, *extra):
(self.root / "cfg.json").write_text(json.dumps(cfg))
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--journey-only", "--config", str(self.root / "cfg.json"), *extra])
return code, json.loads(out.getvalue())
def test_pass_logs_in_with_the_journey_role_only_and_revokes(self):
code, result = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"], result["effects"]), (0, "pass", False))
self.assertEqual([role for role, _ in self.bao.logins], ["journey-read"])
self.assertEqual(self.bao.revoked, ["tok-journey-read-1"])
self.assertEqual(self.calls, [{"c": 1}])
self.assertNotIn(self.SEED, json.dumps(result))
def test_failed_journey_fails_closed_with_exit_1(self):
self.result = "fail"
code, result = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"], result["journey"]["failed_step"]), (1, "fail", "authelia_first_factor"))
def test_incomplete_config_is_refused_before_any_login(self):
cfg = self.cfg()
del cfg["journey"]["k8s_role"]
code, result = self.run_cli(cfg)
self.assertEqual((code, result["reason"]), (4, "setup:KeyError"))
self.assertEqual(self.bao.logins, [])
def test_admission_arguments_conflict_with_journey_only(self):
with self.assertRaises(SystemExit), contextlib.redirect_stderr(io.StringIO()):
self.run_cli(self.cfg(), "--approval-id", "apr-1")
class WaitReachable(unittest.TestCase):
"""k3s applies NetworkPolicy to a new pod a moment late: early connections are refused (live, 2026-10-31)."""
def fake(self, outcomes):
self.now, self.slept, calls = 0.0, [], list(outcomes)
def open_(request, timeout):
outcome = calls.pop(0)
if isinstance(outcome, BaseException):
raise outcome
return contextlib.nullcontext()
def sleep(seconds):
self.slept.append(seconds)
self.now += seconds
return dict(opener=open_, clock=lambda: self.now, sleep=sleep)
def test_refused_then_reachable(self):
refused = OPS.urllib.error.URLError(ConnectionRefusedError())
waited = OPS.wait_reachable("http://bao:8200", **self.fake([refused, refused, "ok"]))
self.assertEqual((waited, self.slept), (2.0, [1, 1]))
def test_any_http_answer_is_reachable(self):
sealed = OPS.urllib.error.HTTPError("http://bao/v1/sys/health", 503, "sealed", {}, io.BytesIO(b""))
self.assertEqual(OPS.wait_reachable("http://bao:8200", **self.fake([sealed])), 0.0)
def test_gives_up_after_the_timeout(self):
refused = OPS.urllib.error.URLError(ConnectionRefusedError())
with self.assertRaises(OPS.Unreachable):
OPS.wait_reachable("http://bao:8200", timeout=3, **self.fake([refused] * 10))
self.assertEqual(self.slept, [1, 1, 1])
class Renewal(Base):
def test_logs_in_again_before_the_lease_ends_and_revokes_the_old_token(self):
self.bao.lease = 1 # renew_at = max(1 - margin, 0.5 s)
client = OPS.OpenBao(self.addr, k8s_role="journey-read", jwt_file=str(self.root / "jwt"))
client.read("platform/workloads/x")
time.sleep(0.6)
client.read("platform/workloads/x")
self.assertEqual(len(self.bao.logins), 2)
self.assertEqual(self.bao.revoked, ["tok-journey-read-1"])
self.assertEqual([tok for _, tok in self.bao.reads], ["tok-journey-read-1", "tok-journey-read-2"])
def test_a_long_lease_is_not_renewed(self):
client = OPS.OpenBao(self.addr, k8s_role="r", jwt_file=str(self.root / "jwt"))
client.read("platform/workloads/x")
client.read("platform/workloads/x")
self.assertEqual(len(self.bao.logins), 1)
def test_a_given_token_never_logs_in(self):
client = OPS.OpenBao(self.addr, token="static")
client.read("platform/workloads/x")
client.revoke_self()
self.assertEqual((self.bao.logins, self.bao.revoked), ([], ["static"]))
class JourneyUsesItsOwnLane(unittest.TestCase):
def test_journey_check_reads_only_through_the_journey_client(self):
class Recorder:
def __init__(self, name):
self.name, self.reads = name, []
def read(self, path):
self.reads.append(path)
return {"PASSWORD": "p", "TOTP_SEED": "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"}, 1
write, read = Recorder("write"), Recorder("read")
_, checks = OPS.pilot_registry(bao=write, journey_bao=read, directory=None, delivery=None, restart=None,
bind_kv="b", bind_user="svc-authelia", journey_kv="j", journey_config={})
original = (OPS.journey.run_journey, OPS._load_config)
OPS.journey.run_journey = lambda cfg, pw, seed, min_time_step=None: {"result": "pass"}
OPS._load_config = lambda cfg: cfg
try:
self.assertTrue(checks["synthetic-journey"]({}, type("Ctx", (), {"data": {}})()))
finally:
OPS.journey.run_journey, OPS._load_config = original
self.assertEqual((write.reads, read.reads), ([], ["j"]))
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,133 @@
"""Kubernetes adapters of the pilot operations against a fake API server (NK-WP-0056-T12). No cluster."""
from __future__ import annotations
import datetime as dt
import importlib.util
import json
import pathlib
import sys
import tempfile
import threading
import unittest
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HERE = pathlib.Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("operations", HERE.parent / "operations.py")
OPS = importlib.util.module_from_spec(spec)
sys.modules["operations"] = OPS
spec.loader.exec_module(OPS)
ES = "/apis/external-secrets.io/v1/namespaces/sso/externalsecrets/authelia-secrets"
DEP = "/apis/apps/v1/namespaces/sso/deployments/authelia"
T0 = dt.datetime(2026, 11, 3, 9, 30, tzinfo=dt.UTC)
class Api:
def __init__(self):
self.patches = []
self.auth = []
self.es_status = {"conditions": [{"type": "Ready", "status": "True"}], "refreshTime": "2026-11-03T09:29:00Z"}
self.dep = {"metadata": {"generation": 5}, "spec": {"replicas": 1},
"status": {"observedGeneration": 5, "replicas": 1, "updatedReplicas": 1, "availableReplicas": 1}}
self.gets_until_ready = 0
self.forbidden = False
def serve(api: Api):
class Handler(BaseHTTPRequestHandler):
def _send(self, code, body):
raw = json.dumps(body).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_GET(self):
api.auth.append(self.headers.get("Authorization"))
if api.forbidden:
return self._send(403, {"kind": "Status"})
if self.path == ES:
return self._send(200, {"status": api.es_status})
if self.path == DEP:
if api.gets_until_ready > 0:
api.gets_until_ready -= 1
return self._send(200, {**api.dep, "status": {**api.dep["status"], "updatedReplicas": 0,
"unavailableReplicas": 1}})
return self._send(200, api.dep)
self._send(404, {})
def do_PATCH(self):
api.patches.append((self.path, self.headers.get("Content-Type"),
json.loads(self.rfile.read(int(self.headers["Content-Length"])))))
if api.forbidden:
return self._send(403, {"kind": "Status"})
if self.path == DEP:
api.dep["metadata"]["generation"] += 1
api.dep["status"]["observedGeneration"] = api.dep["metadata"]["generation"]
self._send(200, {})
def log_message(self, *a):
return
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.02}, daemon=True).start()
return server
class KubeTests(unittest.TestCase):
def setUp(self):
self.api = Api()
self.server = serve(self.api)
self.addCleanup(self.server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
token = pathlib.Path(tmp.name) / "token"
token.write_text("synthetic-sa-token\n")
self.kube = OPS.Kube(api=f"http://127.0.0.1:{self.server.server_port}", token_file=str(token), ca_file=None)
def test_force_sync_patches_only_the_annotation(self):
delivery = OPS.KubeDelivery(self.kube, "sso", "authelia-secrets", clock=lambda: T0)
self.assertEqual(delivery.force_sync(), T0)
((path, ctype, body),) = self.api.patches
self.assertEqual((path, ctype), (ES, "application/merge-patch+json"))
self.assertEqual(body, {"metadata": {"annotations": {"force-sync": str(int(T0.timestamp()))}}})
self.assertEqual(self.api.auth, []) # no read before the patch
def test_synced_needs_ready_and_a_refresh_after_the_sync(self):
delivery = OPS.KubeDelivery(self.kube, "sso", "authelia-secrets")
self.assertFalse(delivery.synced(T0)) # refreshed before T0
self.api.es_status["refreshTime"] = "2026-11-03T09:30:05Z"
self.assertTrue(delivery.synced(T0))
self.api.es_status["conditions"][0]["status"] = "False"
self.assertFalse(delivery.synced(T0))
def test_restart_patches_restarted_at_and_waits_for_the_rollout(self):
self.api.gets_until_ready = 2
restart = OPS.KubeRestart(self.kube, "sso", "authelia", timeout=10, poll=0.01, clock=lambda: T0)
restart.restart()
((path, _, body),) = self.api.patches
self.assertEqual(path, DEP)
self.assertEqual(body["spec"]["template"]["metadata"]["annotations"]["kubectl.kubernetes.io/restartedAt"],
"2026-11-03T09:30:00Z")
self.assertEqual(self.api.gets_until_ready, 0)
def test_rollout_that_never_becomes_ready_is_a_definite_failure(self):
self.api.gets_until_ready = 10 ** 6
ticks = iter(range(10 ** 6))
restart = OPS.KubeRestart(self.kube, "sso", "authelia", timeout=5, poll=0, monotonic=lambda: next(ticks),
sleep=lambda s: None)
with self.assertRaises(OPS.RolloutFailed) as caught:
restart.restart()
self.assertTrue(caught.exception.definite_failure)
def test_forbidden_is_an_operation_error_and_sends_the_bearer(self):
self.api.forbidden = True
with self.assertRaises(OPS.OperationError):
OPS.KubeDelivery(self.kube, "sso", "authelia-secrets").synced(T0)
self.assertEqual(self.api.auth, ["Bearer synthetic-sa-token"])
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,582 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://netkingdom.io/schemas/bounded-execution-admission_v0.1.schema.json",
"title": "NetKingdom Bounded Execution Admission v0.1",
"description": "An immutable request that a person admits, by digest, for one bounded executor run. Structure only; the semantic rules (BEA-1..BEA-14) are in tools/bounded-execution-admission/validate.py and the standard.",
"type": "object",
"additionalProperties": false,
"required": [
"apiVersion",
"kind",
"spec"
],
"properties": {
"apiVersion": {
"const": "netkingdom.io/bounded-execution-admission/v0.1"
},
"kind": {
"const": "BoundedExecutionAdmission"
},
"spec": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"title",
"purpose",
"risk_class",
"window",
"approval",
"executor",
"scope_ceiling",
"blast_radius",
"trust_requires",
"plan",
"plan_digest",
"checks",
"stop_and_revert",
"budget",
"consumption",
"receipt",
"not_authorized"
],
"properties": {
"id": {
"type": "string",
"pattern": "^BEA-[0-9]{4}-[0-9]{4}$"
},
"title": {
"type": "string",
"minLength": 1
},
"purpose": {
"type": "string",
"minLength": 1
},
"work_ref": {
"type": "string"
},
"risk_class": {
"enum": [
"low",
"medium",
"high"
]
},
"window": {
"type": "object",
"additionalProperties": false,
"required": [
"not_before",
"not_after"
],
"properties": {
"not_before": {
"type": "string",
"format": "date-time"
},
"not_after": {
"type": "string",
"format": "date-time"
}
}
},
"approval": {
"type": "object",
"additionalProperties": false,
"required": [
"approvers",
"principal_type",
"assurance",
"carriers"
],
"properties": {
"approvers": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
},
"uniqueItems": true
},
"principal_type": {
"const": "HUMAN"
},
"assurance": {
"const": "aal2"
},
"carriers": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"enum": [
"approval-engine",
"founder-ssh-signature"
]
}
}
}
},
"executor": {
"type": "object",
"additionalProperties": false,
"required": [
"identity",
"principal_type",
"runtime"
],
"properties": {
"identity": {
"type": "string",
"minLength": 1
},
"principal_type": {
"enum": [
"WORKLOAD",
"AGENT",
"SERVICE",
"HUMAN"
]
},
"runtime": {
"type": "string",
"minLength": 1
},
"harness_launched": {
"type": "boolean"
}
}
},
"scope_ceiling": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/grant"
}
},
"blast_radius": {
"type": "object",
"additionalProperties": false,
"required": [
"unit",
"consumers",
"max_concurrent"
],
"properties": {
"unit": {
"enum": [
"consumer",
"account",
"host",
"tenant"
]
},
"consumers": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
},
"max_concurrent": {
"type": "integer",
"minimum": 1
}
}
},
"trust_requires": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"description": "Playbook Capability Contract trust states the executor must find satisfied before the first step.",
"items": {
"enum": [
"bare_host_trust",
"cluster_trust",
"bootstrap_secret_trust",
"bootstrap_identity_trust",
"runtime_secret_trust",
"runtime_identity_trust",
"runtime_authorization_trust",
"tenant_onboarding_trust"
]
}
},
"plan": {
"type": "object",
"additionalProperties": false,
"required": [
"source",
"steps"
],
"properties": {
"source": {
"type": "object",
"additionalProperties": false,
"required": [
"repo",
"commit",
"files"
],
"properties": {
"repo": {
"type": "string"
},
"commit": {
"type": "string",
"pattern": "^[0-9a-f]{7,40}$"
},
"files": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/pinned_file"
}
}
}
},
"steps": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/step"
}
},
"revert_steps": {
"type": "array",
"items": {
"$ref": "#/$defs/step"
}
}
}
},
"plan_digest": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$"
},
"checks": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/check"
}
},
"stop_and_revert": {
"type": "object",
"additionalProperties": false,
"required": [
"on_check_failure",
"on_budget_exhausted",
"on_unknown",
"on_revert_failure"
],
"properties": {
"on_check_failure": {
"enum": [
"revert",
"stop"
]
},
"on_budget_exhausted": {
"enum": [
"revert",
"stop"
]
},
"on_unknown": {
"const": "stop"
},
"on_revert_failure": {
"const": "stop-and-alert"
},
"alert": {
"type": "string"
}
}
},
"budget": {
"type": "object",
"additionalProperties": false,
"required": [
"wall_time_seconds",
"attempts",
"cost"
],
"properties": {
"wall_time_seconds": {
"type": "integer",
"minimum": 1
},
"attempts": {
"type": "integer",
"minimum": 1
},
"cost": {
"type": "object",
"additionalProperties": false,
"required": [
"compute_cpu_seconds",
"egress_bytes",
"currency",
"amount"
],
"properties": {
"compute_cpu_seconds": {
"type": "integer",
"minimum": 0
},
"egress_bytes": {
"type": "integer",
"minimum": 0
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"amount": {
"type": "number",
"minimum": 0
}
}
}
}
},
"consumption": {
"type": "object",
"additionalProperties": false,
"required": [
"single_use",
"consume_before_first_step"
],
"properties": {
"single_use": {
"const": true
},
"consume_before_first_step": {
"const": true
}
}
},
"receipt": {
"type": "object",
"additionalProperties": false,
"required": [
"schema",
"emit_to",
"retain"
],
"properties": {
"schema": {
"const": "netkingdom.playbook-capability-contract/v0.2#execution-attribution-receipt"
},
"emit_to": {
"type": "string",
"minLength": 1
},
"retain": {
"const": "by-hash-no-secret-values"
},
"independent_verifier": {
"type": "string"
}
}
},
"not_authorized": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1
}
}
}
}
},
"$defs": {
"grant": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"scope",
"environments"
],
"properties": {
"mode": {
"enum": [
"USE",
"OBSERVE",
"OPERATE",
"ADMINISTER",
"GOVERN",
"CONSTRUCT",
"SOVEREIGN"
]
},
"scope": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._-]*(/[A-Za-z0-9][A-Za-z0-9._-]*)*$"
},
"environments": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"enum": [
"DEVELOPMENT",
"TEST",
"STAGING",
"PRODUCTION",
"RECOVERY"
]
}
}
}
},
"pinned_file": {
"type": "object",
"additionalProperties": false,
"required": [
"path",
"sha256"
],
"properties": {
"path": {
"type": "string",
"minLength": 1
},
"sha256": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
}
}
},
"step": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"operation",
"target",
"consumer",
"class",
"mutates",
"requires"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$"
},
"operation": {
"type": "string",
"minLength": 1
},
"target": {
"type": "string",
"minLength": 1
},
"consumer": {
"type": "string",
"minLength": 1
},
"class": {
"enum": [
"delegable",
"root-class",
"human-intrinsic"
]
},
"mutates": {
"type": "boolean"
},
"requires": {
"$ref": "#/$defs/grant_need"
},
"checks_after": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"revert": {
"type": "string"
},
"irreversible": {
"type": "boolean"
}
}
},
"grant_need": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"scope"
],
"properties": {
"mode": {
"enum": [
"USE",
"OBSERVE",
"OPERATE",
"ADMINISTER",
"GOVERN",
"CONSTRUCT",
"SOVEREIGN"
]
},
"scope": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9._-]*(/[A-Za-z0-9][A-Za-z0-9._-]*)*$"
}
}
},
"check": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"description",
"ref",
"sha256",
"timeout_seconds",
"expect"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$"
},
"description": {
"type": "string",
"minLength": 1
},
"ref": {
"type": "string",
"minLength": 1
},
"sha256": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
},
"timeout_seconds": {
"type": "integer",
"minimum": 1
},
"settle_seconds": {
"type": "integer",
"minimum": 0
},
"expect": {
"const": "pass"
}
}
}
}
}

View file

@ -0,0 +1,454 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://netkingdom.local/schemas/security-case-type_v0.1.schema.json",
"title": "NetKingdom SecurityCaseType v0.1",
"description": "Structure of security-case-types/<type-id>/type.yaml (canon/standards/security-case-types_v0.1.md). Semantic rules (rollback coverage, template placeholders, secret-shaped values, acceptance by risk class) are enforced by tools/security-case-types/security_case_validator.py.",
"type": "object",
"additionalProperties": false,
"required": [
"apiVersion",
"kind",
"spec"
],
"properties": {
"apiVersion": {
"const": "netkingdom.io/security-case-type/v0.1"
},
"kind": {
"const": "SecurityCaseType"
},
"spec": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"version",
"status",
"title",
"purpose",
"parameters",
"composition",
"materialization",
"follow_through",
"verification",
"rollback",
"acceptance"
],
"properties": {
"id": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+$"
},
"status": {
"enum": [
"draft",
"proposed",
"acknowledged",
"implemented",
"deprecated",
"retired"
]
},
"title": {
"type": "string",
"minLength": 1
},
"purpose": {
"type": "string",
"minLength": 1
},
"parameters": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/parameter"
}
},
"composition": {
"$ref": "#/$defs/composition"
},
"materialization": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/materialization"
}
},
"follow_through": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/step"
}
},
"verification": {
"type": "array",
"minItems": 2,
"items": {
"$ref": "#/$defs/check"
}
},
"rollback": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/rollback"
}
},
"acceptance": {
"type": "object",
"additionalProperties": false,
"required": [
"accepted_by",
"risk_class"
],
"properties": {
"accepted_by": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"enum": [
"owner",
"founder"
]
}
},
"risk_class": {
"enum": [
"low",
"medium",
"high"
]
}
}
},
"history": {
"type": "array",
"items": {
"type": "object"
}
},
"execution_descriptors": {
"type": "object",
"maxProperties": 128,
"propertyNames": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"not": {
"pattern": "[^a-z0-9-]"
}
},
"additionalProperties": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$",
"minLength": 71,
"maxLength": 71
},
"description": "Optional exact owner machine-descriptor byte digests keyed by declared follow-through step. A pin alone is not owner acknowledgment, execution authority or native evidence."
}
}
}
},
"$defs": {
"parameter": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"type",
"case_settable",
"sensitivity",
"description"
],
"properties": {
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]*$"
},
"type": {
"enum": [
"string",
"integer",
"number",
"boolean",
"array",
"object"
]
},
"case_settable": {
"type": "boolean"
},
"required": {
"type": "boolean"
},
"default": {},
"sensitivity": {
"enum": [
"public",
"operational",
"security_sensitive",
"secret_reference"
]
},
"constraints": {
"type": "object",
"additionalProperties": false,
"properties": {
"pattern": {
"type": "string"
},
"enum": {
"type": "array"
},
"min_items": {
"type": "integer",
"minimum": 0
},
"unique": {
"type": "boolean"
},
"required_when": {
"type": "string"
}
}
},
"description": {
"type": "string",
"minLength": 1
}
}
},
"composition": {
"type": "object",
"additionalProperties": false,
"required": [
"authority",
"initial_trust",
"capabilities",
"providers"
],
"properties": {
"authority": {
"enum": [
"platform",
"netkingdom",
"tenant"
]
},
"initial_trust": {
"type": "array",
"minItems": 1,
"items": {
"type": "string"
}
},
"capabilities": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string"
}
},
"providers": {
"type": "object",
"additionalProperties": {
"type": "string",
"minLength": 1
}
},
"parameter_bindings": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
},
"materialization": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"kind",
"template"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"kind": {
"enum": [
"openbao_policy",
"openbao_role",
"kv_path",
"catalog_entry"
]
},
"template": {
"type": "string",
"minLength": 1
},
"description": {
"type": "string"
},
"when": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]* (==|!=) [A-Za-z0-9_.:/-]+$"
},
"body": {
"type": "string",
"minLength": 1,
"description": "The rendered object's content over case parameters: a policy's rules or a role's bindings. Required for openbao_policy and openbao_role; literal braces are doubled."
}
},
"allOf": [
{
"if": {
"properties": {
"kind": {
"enum": [
"openbao_policy",
"openbao_role"
]
}
},
"required": [
"kind"
]
},
"then": {
"required": [
"body"
]
}
}
]
},
"step": {
"type": "object",
"additionalProperties": false,
"required": [
"auth",
"entry_point",
"id",
"owner",
"value_handling",
"warden_executes"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"owner": {
"type": "string",
"minLength": 1
},
"entry_point": {
"type": "string",
"minLength": 1
},
"auth": {
"type": "string",
"minLength": 1
},
"warden_executes": {
"type": "boolean",
"description": "ops-warden invokes the owner's declared entry point through the caller proxy, under the caller's identity; ops-warden holds no credential for it (ops-warden ADR-0002)."
},
"attended": {
"type": "boolean"
},
"attended_justification": {
"type": "string"
},
"description": {
"type": "string"
},
"when": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]* (==|!=) [A-Za-z0-9_.:/-]+$"
},
"lane": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]*$",
"description": "ops-warden catalog lane that resolves this step; required when warden_executes or attended"
},
"value_handling": {
"enum": [
"none",
"generated_in_custody"
]
},
"ready_check": {
"type": "string",
"minLength": 1,
"description": "status-only gate checked before the next step; never reads a value"
}
}
},
"check": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"check",
"expect",
"reads_secret_value"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"check": {
"type": "string",
"minLength": 1
},
"expect": {
"enum": [
"allow",
"present",
"deny"
]
},
"reads_secret_value": {
"const": false
},
"when": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]* (==|!=) [A-Za-z0-9_.:/-]+$"
}
}
},
"rollback": {
"type": "object",
"additionalProperties": false,
"required": [
"reverses",
"action"
],
"properties": {
"reverses": {
"type": "string",
"minLength": 1
},
"action": {
"type": "string",
"minLength": 1
},
"when": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]* (==|!=) [A-Za-z0-9_.:/-]+$"
}
}
}
}
}

View file

@ -0,0 +1,56 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://netkingdom.local/schemas/security-case_v0.1.schema.json",
"title": "NetKingdom SecurityCase v0.1",
"description": "Structure of a security case file (canon/standards/security-case-types_v0.1.md section 4). Parameter values are checked against the referenced type by tools/security-case-types/security_case_validator.py.",
"type": "object",
"additionalProperties": false,
"required": ["apiVersion", "kind", "spec"],
"properties": {
"apiVersion": {"const": "netkingdom.io/security-case/v0.1"},
"kind": {"const": "SecurityCase"},
"spec": {
"type": "object",
"additionalProperties": false,
"required": ["id", "type", "status", "owner", "parameters"],
"properties": {
"id": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$"},
"type": {"type": "string", "pattern": "^[a-z0-9][a-z0-9-]*@\\d+\\.\\d+$"},
"status": {"enum": ["proposed", "accepted", "materialized", "live", "retired"]},
"owner": {
"type": "object",
"additionalProperties": false,
"required": ["repo", "person"],
"properties": {
"repo": {"type": "string", "minLength": 1},
"person": {"type": "string", "minLength": 1}
}
},
"parameters": {"type": "object"},
"acceptance": {
"type": "object",
"additionalProperties": false,
"required": ["at", "record", "by"],
"properties": {
"at": {"type": "string", "minLength": 1},
"record": {"type": "string", "minLength": 1},
"by": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["role", "name"],
"properties": {
"role": {"enum": ["owner", "founder"]},
"name": {"type": "string", "minLength": 1}
}
}
}
}
},
"receipts": {"type": "array", "items": {"type": "object"}}
}
}
}
}

View file

@ -0,0 +1,496 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://policy.coulomb.social/schemas/service-identity/v0.1",
"title": "NetKingdom service identity declaration v0.1",
"description": "One service declares every account it holds (NetKingdom Service Identity and Privilege v0.1, draft-3: an estate profile of SecurityCanon). Accounts carry an imported principal_type and authority grants written MODE @ SCOPE with activation and environment. Holds no credential and no secret-shaped value. Facts a declarer cannot establish are 'unknown', never guessed.",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"service",
"owner",
"declared_at",
"attestation",
"complete",
"identities"
],
"properties": {
"schema_version": {
"const": "0.1"
},
"service": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]{1,40}$"
},
"owner": {
"type": "string",
"minLength": 3,
"maxLength": 200
},
"declared_at": {
"type": "string",
"format": "date"
},
"attestation": {
"type": "object",
"additionalProperties": false,
"required": [
"authored_by",
"basis",
"owner_confirmed"
],
"properties": {
"authored_by": {
"type": "string",
"minLength": 2,
"maxLength": 100
},
"basis": {
"type": "array",
"minItems": 1,
"maxItems": 40,
"items": {
"type": "string",
"minLength": 3,
"maxLength": 200
}
},
"owner_confirmed": {
"type": "boolean"
}
}
},
"complete": {
"type": "boolean",
"description": "True only when the owner asserts that every identity the service holds is listed."
},
"identities": {
"type": "array",
"minItems": 1,
"maxItems": 200,
"items": {
"$ref": "#/$defs/identity"
}
},
"exceptions": {
"type": "array",
"maxItems": 100,
"items": {
"$ref": "#/$defs/exception"
}
}
},
"$defs": {
"name": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$"
},
"unknownable": {
"description": "A fact that may be unknown rather than guessed."
},
"consumer": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"kind"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"kind": {
"enum": [
"person",
"service"
]
}
}
},
"identity": {
"type": "object",
"additionalProperties": false,
"required": [
"name",
"principal_type",
"purpose",
"grants",
"interactive_login",
"second_factor",
"custody",
"consumers",
"owner",
"rotation",
"status"
],
"properties": {
"name": {
"$ref": "#/$defs/name"
},
"principal_type": {
"enum": [
"HUMAN",
"WORKLOAD",
"SERVICE",
"AGENT",
"CONTROLLER",
"DEVICE",
"unknown"
],
"description": "Imported from InfoTechCanon via SecurityCanon. 'unknown' is a declarer's placeholder."
},
"conventions": {
"type": "array",
"uniqueItems": true,
"items": {
"enum": [
"bind",
"service-admin",
"executor"
]
},
"description": "Estate naming conventions for recurring grant shapes (standard section 2). More than one is itself a finding."
},
"purpose": {
"type": "string",
"minLength": 3,
"maxLength": 300
},
"grants": {
"type": "array",
"minItems": 1,
"maxItems": 20,
"items": {
"$ref": "#/$defs/grant"
}
},
"federated_role": {
"type": "boolean",
"description": "A role mapping federated principals to authority; holds no credential."
},
"assurance_claim_bound": {
"type": "boolean"
},
"interactive_login": {
"type": "boolean"
},
"second_factor": {
"enum": [
"required",
"not_enforced",
"not_supported",
"not_applicable",
"unknown"
]
},
"custody": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"enum": [
"offline-key",
"personal-safe",
"openbao-lease",
"delivered-secret",
"none",
"unknown"
]
},
"description": "Every place the credential lives. One credential in two places is two entries."
},
"consumers": {
"type": "array",
"maxItems": 50,
"items": {
"$ref": "#/$defs/consumer"
}
},
"owner": {
"type": "string",
"minLength": 3,
"maxLength": 200
},
"rotation": {
"type": "object",
"additionalProperties": false,
"required": [
"path",
"coupling"
],
"properties": {
"path": {
"type": "string",
"minLength": 3,
"maxLength": 300
},
"coupling": {
"type": "array",
"maxItems": 20,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 100
}
}
}
},
"status": {
"enum": [
"active",
"legacy",
"planned"
]
},
"disposition": {
"type": "object",
"additionalProperties": false,
"required": [
"action",
"owner",
"date"
],
"properties": {
"action": {
"enum": [
"retire",
"accept",
"defer"
]
},
"owner": {
"type": "string",
"minLength": 3,
"maxLength": 200
},
"date": {
"type": "string",
"format": "date"
}
}
},
"recovery": {
"type": "object",
"additionalProperties": false,
"required": [
"needed",
"works_with_sso_down",
"in_crisis_list"
],
"properties": {
"needed": {
"type": "boolean"
},
"works_with_sso_down": {
"enum": [
true,
false,
"unknown"
]
},
"in_crisis_list": {
"type": "boolean"
}
}
},
"agent": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"presents_principal_type",
"max_token_ttl_seconds",
"boundary_policy"
],
"properties": {
"mode": {
"enum": [
"autonomous",
"delegated",
"unknown"
]
},
"presents_principal_type": {
"enum": [
"agent",
"service",
"human",
"unknown"
]
},
"max_token_ttl_seconds": {
"type": "integer",
"minimum": 1,
"maximum": 86400
},
"boundary_policy": {
"type": "string",
"minLength": 3,
"maxLength": 200
}
}
},
"executor_subject": {
"type": "string",
"minLength": 1,
"maxLength": 120,
"description": "For a credential held by a bounded executor (NK-WP-0056): the subject the executor presents in its admissions (spec.executor.identity). Grants of all identities with the same subject form the executor's declared authority."
}
},
"allOf": [
{
"if": {
"properties": {
"status": {
"const": "legacy"
}
},
"required": [
"status"
]
},
"then": {
"required": [
"disposition"
]
}
},
{
"if": {
"properties": {
"principal_type": {
"const": "AGENT"
}
},
"required": [
"principal_type"
]
},
"then": {
"required": [
"agent"
]
}
}
]
},
"exception": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"rules",
"identity",
"reason",
"owner",
"expires",
"compensating_control",
"elimination_path"
],
"properties": {
"id": {
"type": "string",
"pattern": "^SI-X[0-9]{1,3}$"
},
"rules": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"pattern": "^(SI-(1[0-5]|[1-9])|NAMING)$"
}
},
"identity": {
"$ref": "#/$defs/name"
},
"reason": {
"type": "string",
"minLength": 10,
"maxLength": 400
},
"owner": {
"type": "string",
"minLength": 3,
"maxLength": 200
},
"expires": {
"type": "string",
"format": "date"
},
"compensating_control": {
"type": "string",
"minLength": 10,
"maxLength": 400
},
"elimination_path": {
"type": "string",
"minLength": 10,
"maxLength": 400,
"description": "What change ends the exception (DSM-EXC-004)."
}
}
},
"grant": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"scope"
],
"description": "MODE @ SCOPE with SecurityCanon dimensions. 'unknown' is a declarer's placeholder, not a canon mode.",
"properties": {
"mode": {
"enum": [
"USE",
"OBSERVE",
"OPERATE",
"ADMINISTER",
"GOVERN",
"CONSTRUCT",
"SOVEREIGN",
"unknown"
]
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 120
},
"activation": {
"enum": [
"STANDING",
"JUST_IN_TIME",
"SCHEDULED",
"APPROVED",
"INCIDENT",
"BREAK_GLASS",
"RECOVERY"
]
},
"environments": {
"type": "array",
"uniqueItems": true,
"items": {
"enum": [
"DEVELOPMENT",
"TEST",
"STAGING",
"PRODUCTION",
"RECOVERY"
]
}
},
"read_only": {
"type": "boolean"
},
"note": {
"type": "string",
"maxLength": 200
}
}
}
}
}

View file

@ -0,0 +1,86 @@
---
id: NK-STD-AUDIT-CORRELATION
title: "Audit Correlation Contract"
version: "0.1"
status: accepted
owner: net-kingdom
created: "2026-10-27"
work: NK-WP-0055-T11
---
# Audit Correlation Contract v0.1
## Purpose
This contract defines one correlation id that links the audit records of a single unit of work across all of these:
- identity issuance;
- the authorization decision;
- secret access;
- workload enforcement.
Without it, the architecture's onboarding audit-readiness step (`docs/platform-identity-security-architecture.md`, step 7)
cannot be tested.
Owners:
- **NetKingdom** owns this contract.
- **audit-core** owns the `correlation_id` envelope field (AUDIT-WP-0017-T05).
- **Each emitting service** owns its own propagation.
## Rules
**AC-1. Format.** `correlation_id` is the 32-character lowercase hex trace id of
[W3C Trace Context](https://www.w3.org/TR/trace-context/). An all-zero id is invalid. The value is opaque and carries no
personal data, tenant name or secret. Receivers may store other ids of up to 128 characters, but those do not satisfy this
contract.
**AC-2. Transport.** On HTTP, the id travels in the standard `traceparent` header. The `correlation_id` is the trace-id
part of that header. There is no custom header. For non-HTTP calls (LDAP, internal queues), the emitting service records
the id it received on its inbound HTTP leg.
**AC-3. Not a token claim.** The id is never placed in an access token, ID token or other credential. A token outlives a
unit of work, so a claim would wrongly link unrelated requests.
**AC-4. Who sets it.**
- The **originator of the unit of work** sets the id: the workload or the user-facing application, at the start of the
request it is serving.
- Every downstream component copies the inbound id unchanged into its audit record and into its own outbound calls.
- A component that receives no valid `traceparent` starts a new trace. It records that fact as
`correlation_origin: generated`; propagated ids are recorded as `correlation_origin: propagated`.
- `parent_event_id` is optional, and is used only when one record directly causes another.
**AC-5. Trust.** The id is a claim made by the caller and nothing more:
- It never influences an authentication, authorization or secret decision.
- It is not evidence of who acted. The record's authenticated subject is that evidence.
- Where a decision record is signed (access-engine FLEX-WP-0024), the signature covers the id, so the record cannot be
relinked afterwards.
**AC-6. Per emitter.**
| Emitter | Where it reads the id | Owner task |
|---|---|---|
| Workload / application | Generates the id, or takes it from its own inbound request | each workload owner |
| KeyCape | Inbound `traceparent` on the token and authorize endpoints | key-cape (via NK-WP-0055-T11) |
| access-engine | Inbound `traceparent` on the check APIs; copied into the decision record | FLEX-WP-0031-T11 |
| OpenBao | Audits the `traceparent` request header in plain text via `sys/config/auditing/request-headers` (`hmac: false`) and maps it on shipping | RPF-WP-0066-T02 |
| Authelia, LLDAP, privacyIDEA | Inbound `traceparent` where the service exposes it; otherwise `correlation_origin: generated` | NK-WP-0055-T11 identity intake |
| Kubernetes API audit | Not part of the chain. It keeps its own `auditID`, and k3s does not carry the id | RCLUSTER-WP-0021 |
**AC-7. End-to-end test.** The tenant onboarding completion test (NK-WP-0055-T12) passes only when a single
`correlation_id` set by a test workload can be found in audit-core in all of these records:
- the KeyCape issuance record;
- the access-engine decision record;
- the OpenBao audit record;
- the workload enforcement record.
The test uses synthetic subjects only.
## Open for owners
- audit-core: confirm the `correlation_origin` field name and the validation for the new sources (AUDIT-WP-0017-T05).
- railiance-platform: confirm that the OpenBao version in use supports audited request headers (RPF-WP-0066-T01).
- key-cape: accept the propagation task.

View file

@ -0,0 +1,254 @@
---
id: netkingdom-bounded-execution-admission-v0.1
type: standard
title: "NetKingdom Bounded Execution Admission v0.1"
domain: netkingdom
status: proposed
version: "0.1"
owner: net-kingdom
created: "2026-10-31"
updated: "2026-10-31"
last_reviewed: "2026-10-31"
review_interval: 3m
scope: accountable-automation
schema:
- canon/schemas/bounded-execution-admission_v0.1.schema.json
validator:
- tools/bounded-execution-admission/validate.py
examples:
- examples/bounded-execution-admission/
related:
- canon/standards/service-identity-privilege_v0.1.md
- canon/standards/playbook-capability-contract_v0.2.md
- workplans/NK-WP-0056-bounded-executor-path.md
- workplans/NK-WP-0040-execution-attribution-receipt.md
---
# NetKingdom Bounded Execution Admission v0.1
## 0. Status
Proposed (NK-WP-0056-T01). Nothing here is enforced by a running service yet. The executor runtime
is T02/T03, budget and kill-switch enforcement is T07, and the first live use is the T06 pilot. A
document that passes the validator is a well-formed **request**. It is an **admission** only together
with a carrier record that a person approved (section 5).
## 1. Purpose
SI-15 names the golden path *prepare → admit → execute → verify → retain*, and allows "a bounded
executor under the admission" at step 3. This standard defines what that admission is. The founder
approves, at `aal2`:
- an exact plan, by digest;
- a validity window;
- a scope ceiling in SI `MODE @ SCOPE` terms;
- a blast-radius rule;
- a stop-and-revert policy tied to named machine checks;
- a risk class;
- an expense budget.
The executor consumes the admission once. Top-level decisions and guardrails stay with the person,
and execution below them is delegated, bounded and reconstructable.
Out of scope: who runs the executor (T02), how a carrier stores approvals (Approval Engine owns that),
and the receipt's emission and custody (NK-WP-0040-T02).
## 2. Vocabulary
| Term | Meaning |
| --- | --- |
| Admission request | The immutable document defined here (`BoundedExecutionAdmission`), identified by `spec.id` and by its digest |
| Carrier | The system that records a person's approval of one request and its single consumption: Approval Engine, or a founder SSH signature (NK-WP-0049 practice) |
| Admission | A request plus a carrier record that passes BEA-C1..C7 |
| Executor | The non-human identity that runs the plan: a `WORKLOAD`, `SERVICE` or harness-launched `AGENT` (SI-14) |
| Step class | `delegable` (the executor may run it under an admission), `root-class` (the founder admits each run; high risk only), `human-intrinsic` (never in an admission: for example a person's own password) |
| Check | A named, source-pinned machine check that must pass after a mutating step |
| Consumer | The unit the blast radius counts: a service whose behaviour a step can change |
## 3. The admission request
A YAML or JSON document validated by `canon/schemas/bounded-execution-admission_v0.1.schema.json`:
| Field | Meaning |
| --- | --- |
| `spec.id`, `title`, `purpose`, `work_ref` | Identity and intent. `id` is `BEA-YYYY-NNNN` |
| `risk_class` | `low`, `medium` or `high`. Bounds the window, the attempts, the ceiling and the step classes |
| `window.not_before` / `not_after` | When the admission can be consumed. At most 72 h (low), 24 h (medium) or 4 h (high) |
| `approval` | Who may approve (`approvers`, `HUMAN`, `aal2`) and through which carriers |
| `executor` | The executing identity, its principal type and runtime |
| `scope_ceiling` | Grants `{mode, scope, environments}` that bound every step. Scopes are `/`-separated paths, and a grant covers its own scope and anything below it |
| `blast_radius` | `unit`, the `consumers` the plan may touch, and `max_concurrent` |
| `trust_requires` | Playbook Capability Contract trust states the executor must find satisfied before the first step |
| `plan.source` | Repository, commit, and the sha256 of every file the run executes or checks with |
| `plan.steps`, `plan.revert_steps` | Ordered steps: `operation`, `target`, `consumer`, `class`, `mutates`, `requires` (the grant it needs), `checks_after`, `revert` or `irreversible` |
| `plan_digest` | `sha256:` over the canonical JSON of `plan` (sorted keys, no whitespace, UTF-8) |
| `checks` | Named checks: `ref` (a pinned file), `sha256`, `timeout_seconds`, optional `settle_seconds`, `expect: pass` |
| `stop_and_revert` | `on_check_failure` and `on_budget_exhausted` (`revert` or `stop`); `on_unknown: stop`; `on_revert_failure: stop-and-alert` |
| `budget` | `wall_time_seconds`, `attempts`, and `cost` (CPU seconds, egress bytes, currency, amount) |
| `consumption` | `single_use: true`, `consume_before_first_step: true` |
| `receipt` | The NK-WP-0040 execution-attribution receipt, where it goes, and that it is retained by hash with no secret values |
| `not_authorized` | What this admission explicitly does not cover |
`settle_seconds` exists because of the T08 finding: privacyIDEA's resolver cache hides a broken bind
for up to 120 s, so a check run sooner proves nothing.
## 4. Rules (validator: `validate`)
| Rule | Requirement |
| --- | --- |
| BEA-1 | The document matches the schema (`HUMAN` approvers at `aal2`, single use, `on_unknown: stop`) |
| BEA-2 | `plan_digest` equals the digest of `plan`: the person admits exactly this plan |
| BEA-3 | The window is positive and no longer than the risk class allows |
| BEA-4 | The executor is not `HUMAN` and not an approver. An `AGENT` must be harness-launched (SI-14) |
| BEA-5 | No `SOVEREIGN` or `GOVERN` in the ceiling. No `CONSTRUCT` in `PRODUCTION`. `ADMINISTER` needs `max_concurrent: 1`; at a whole-service scope (no `/`) it needs `high` risk and only `root-class` steps |
| BEA-6 | Least privilege: every step and revert step is covered by a ceiling grant of the same mode, and every ceiling grant covers at least one step |
| BEA-7 | No `human-intrinsic` step. A `root-class` step needs `high` risk |
| BEA-8 | Every step's consumer is in the blast radius. `medium` and `high` risk change one consumer at a time |
| BEA-9 | Every mutating step names checks, revert steps included: a revert must prove the restored state with its own checks. Every check is used and pinned: its `ref` and `sha256` appear in `plan.source.files` |
| BEA-10 | Every mutating step has a revert step, or is `irreversible` at `high` risk. Revert steps mutate and do not nest. Step ids are unique. The executor runs revert steps in their declared order (the recovery order: data, then delivery, then restart), limited to those whose forward step ran |
| BEA-11 | A revertable plan reverts on check failure. A plan with nothing to revert stops |
| BEA-12 | The wall time fits the window, the checks (timeouts plus settle times) fit the wall time, and attempts are at most 3 (1 at `high` risk) |
| BEA-13 | The executed source is pinned by file and commit |
| BEA-14 | No secret-shaped value anywhere in the document |
Findings name the rule and a location, never a value.
## 5. Carrier and consumption (validator: `check-approval`)
A carrier record (`netkingdom.io/bounded-execution-approval/v0.1`) states the carrier, its approval
reference, the admission id and digest, the approver (name, principal type, assurance), the approval
time, and the consumptions.
The **admission digest** is `sha256:` over the canonical JSON of the whole request, including
`plan_digest`. That digest is what a carrier binds.
| Rule | Requirement |
| --- | --- |
| BEA-C1 | The carrier is one the request allows, and the record has the expected shape |
| BEA-C2 | The record names this admission's id and digest |
| BEA-C3 | The approver is a listed `HUMAN` approver and not the executor |
| BEA-C4 | The approval was made at `aal2` |
| BEA-C5 | The approval was made before the window closed |
| BEA-C6 | Consumed at most once |
| BEA-C7 | The consumption happened in the window, after approval, by the executor identity |
**Approval Engine (preferred carrier).** Approval Engine already stores an approval that binds action,
actor, principal, purpose and target by digest, carries a validity window, and is consumed exactly
once (`POST /v1/approvals/{id}/consume`), read back through `GET /v1/approvals/{id}`. The proposed
mapping, which is for agreement with Approval Engine and Access under T02, not decided here:
- `action: bounded-execution`;
- `actor`: the executor identity;
- `principal`: the approver;
- `purpose`: `spec.purpose`;
- `target: {admission_id, admission_digest}`;
- validity equal to `window`;
- the executor consumes before its first step (`consume_before_first_step`).
Approval Engine's own contract notes that `consume` does not check that the request digest is
equivalent to the decision. An independent mediator must therefore fetch the approval and compare
`target.admission_digest` itself. A `consumed: true` returned by the caller is not proof.
**Founder SSH signature (transitional carrier).** This is the NK-WP-0049 practice:
- the founder signs the canonical request with `ssh-keygen -Y sign`, namespace
`netkingdom-bounded-execution`;
- the signature is verified against the repository's `allowed_signers`;
- `aal2` is established by the signing key's custody, not by a token.
Single use is then only as strong as the executor's own consumption ledger. That is acceptable for
the transition and is recorded as such in the receipt.
## 6. Mapping
**SI-15 golden path.**
| Golden-path step | What it means here |
| --- | --- |
| Prepare | `plan.source` (source-reviewed, pinned files), tested on synthetic fixtures. For bind changes, NK-WP-0056-T08's synthetic stack |
| Admit | Carrier approval of the admission digest at `aal2` |
| Execute | The executor, inside `scope_ceiling`, `blast_radius`, `window` and `budget`, consuming once |
| Verify | The named checks during the run. Afterwards, an independent verifier checks the carrier record (BEA-C1..C7) and the receipt's source hashes |
| Retain | The receipt, kept by hash with no secret values |
**Playbook Capability Contract.** `trust_requires` uses the contract's trust-state vocabulary. The
executor refuses to start unless each state is satisfied, using the readiness checks of the
capability declarations it runs. A step's `operation` should name a declared capability operation
once Railiance declarations exist for it.
**Execution-attribution receipt (Playbook Capability Contract v0.2, NK-WP-0040).** One receipt per run:
| Receipt field | Filled from |
| --- | --- |
| `initiator` | The carrier's approver (the founder). The person authorized the run; the executor ran it |
| `delegation` | Absent unless the approver acted through an agent |
| `executing_workload`, `runtime_principal` | `executor.identity`, `executor.runtime` |
| `run_id` | New per run |
| `source_commit` | `plan.source.commit` |
| `artifact_digest` | `plan_digest` |
| `target`, `action` | Per step |
| `decision_refs` | `spec.id`, the admission digest and the carrier's approval reference |
| `time_interval` | Measured |
The receipt adds the outcome per step, check results, whether a revert ran, and the budget used. It
never holds a secret value.
Founder decision 2026-10-31, agreed with audit-core (AUDIT-WP-0018):
- When the executor consumes the admission, before its first step, it emits a separate
`execution.run.started` event, so a run that crashes before its receipt is still detectable.
- It then emits one `execution.attribution.receipt` per run:
- `target` and `action` at the admission level (`admission:<spec.id>`, `bounded-execution`);
- per-step detail in `steps[]`;
- `decision_refs` labelled `admission`, `admission_digest` and `approval`.
- Envelope ids are `run:<run_id>:started` and `run:<run_id>:receipt`.
**Policy decision point.** access-engine's gate (FLEX-WP-0040) has two decisions: `start` before
consume, and `execute-step` after it. The T06 pilot runs without the gate: its receipt records the
gate as `not-evaluated` and claims no allow. Any further admission needs the gate, or an exception
the founder records (founder decision 2026-10-31).
**Failure classes.** A failed check, or an operation that reports a definite failure (for example a rollout that never becomes ready), reverts per `on_check_failure`. An error whose outcome is not established stops (`on_unknown: stop`) without reverting. Receipts name the error type, never its message.
**Credentials during a run.** Write roles exist only during the admitted window: the platform creates
them when an admission is approved and deletes them after the receipt (CCR-2026-0058). A directory
bind rotates its own password, so no password-manager account is needed (founder decisions
2026-10-31).
## 7. The bind split as admissions
NK-WP-0056 tagged each step of the 2026-10-30 LLDAP bind split. The examples express them:
| Runbook step | Class | Example |
| --- | --- | --- |
| 2, 4, 10 (verify) | delegable, read-only | `bind-split-read-only-verify.yaml`: low risk, `OBSERVE` only, nothing to revert |
| 3 create `svc-*` accounts | root-class | `bind-split-create-service-accounts.yaml`: high, whole-directory `ADMINISTER` (no narrower LLDAP grant, SI-X6), revert deletes what was created |
| 5–8 switch one consumer | delegable | `bind-rotation-svc-authelia.yaml`: medium, one consumer, a synthetic-journey check after the restart, a revert per step. This is the T06 pilot shape |
| 9 change LLDAP's super-admin | root-class | `bind-split-switch-super-admin.yaml`: high, one attempt, `settle_seconds: 125` on the journey check (privacyIDEA cache) |
| 10 `admin`'s own password; seal `ld-admin` | human-intrinsic | Refused: `tools/bounded-execution-admission/fixtures/invalid/human-intrinsic-step.yaml` (BEA-7) |
The examples are illustrative. They pin real files at a real commit, but the executor and its
operations do not exist yet.
## 8. Conformance
```bash
make bounded-execution-admission-test # validator tests
make bounded-execution-admission-check # validate the examples
python3 tools/bounded-execution-admission/validate.py check-approval <admission> <carrier-record>
python3 tools/bounded-execution-admission/validate.py digest <admission>
```
Agents may run these: they are offline and read-only. Writing an admission is preparation, and
approving one is the founder's act.
## 9. Open items
- T02: the carrier mapping with Approval Engine and Access (action name, actor and representation,
digest equivalence, consume scope for the executor), and the executor runtime owner.
- T03: the executor identity and its grants, matching the ceilings in the examples.
- T07: enforcement of budget and attempts at run time, the founder's kill switch, and aggregate
review through posture feedback (NK-WP-0031).
- NK-WP-0040-T02: receipt emission, custody and schema in audit-core.
- Recurring admissions (one approval for repeated runs of the same plan) are deliberately excluded
from v0.1: every admission is single use.

View file

@ -1,5 +1,5 @@
# Credential Management Standard — net-kingdom
**Version:** 0.2 **Status:** current with OpenBao runtime refinement **Supersedes:** v0.1 (retired with NK-WP-0004)
**Version:** 0.2 **Status:** current with OpenBao runtime and secret-free handoff refinements **Supersedes:** v0.1 (retired with NK-WP-0004)
---
@ -17,7 +17,7 @@ control plane.
## 2. Trust Hierarchy
```
age private key ─────────────► encrypts secrets.enc/ in git
age public recipient ────────► encrypts secrets.enc/ in git
│
└──► ops bundle (encrypted snapshot of all secrets)
└──► stored offsite; decrypt with age key
@ -49,30 +49,36 @@ authority once the platform control plane is alive.
## 3. Credential Lifecycle
### Phase 0 — Bootstrap (run once, agent-driven)
### Phase 0 — Bootstrap (attended, public-recipient encryption)
```
make creds-agent-init
```
`make creds-agent-init` retains its compatibility name but runs in the operator's
attended terminal. It requires an independently registered `keys/age.pub`; it
never generates a root key or searches HOME for one. The operator separately
establishes custody of the matching private key before bootstrap.
This single command runs the full bootstrap end-to-end:
1. Verifies prerequisites (age, kubectl, openssl, cluster)
2. Generates or verifies the age keypair at `~/.config/sops/age/keys.txt`
3. Generates all service secrets via `gen-secrets.sh`
4. Encrypts them to `secrets.enc/` with age and commits
5. Injects them into the cluster via `creds-apply.sh`
6. Verifies all K8s Secrets exist
7. Waits for privacyIDEA to be Ready, then runs enckey bootstrap + admin creation
8. Applies KeyCape secrets (requires pi-admin)
9. Hands off runtime secret authority to OpenBao when the Railiance
platform layer is present and verified
10. Creates the ops bundle (age-encrypted snapshot)
11. Delivers the emergency bundle to the terminal for human storage
← **only human touchpoint**
12. Shreds all plaintext and marks `bootstrap_complete: true`
The workflow generates service files with private permissions, encrypts with the
public recipient, applies and verifies the scoped cluster setup, then creates a
fresh encrypted ops bundle. The recovery handoff displays its location and digest.
The operator stores that exact artifact off-host and verifies decryption in an
isolated recovery workspace before typing `STORED AND VERIFIED`. Only then does
bootstrap remove plaintext and mark completion. This acknowledgment is a human
statement; a separate native drill receipt is required to establish recovery.
The script resumes from where it left off if interrupted — each phase is
tracked in `creds-state.yaml`.
For an encrypted resume with no plaintext service directory, invoke bootstrap
through `sops-custody-unlock.sh` from the operator terminal. Only the explicitly
supplied temporary `NETKINGDOM_CUSTODY_AGE_KEY_FILE` is used, and it must match the
registered public recipient before any cluster call. There is no ambient key
fallback. Use the owner-approved target and change scope; the helper supplies
custody material, not authorization.
`bash sso-mfa/bootstrap/creds-bootstrap-agent.sh --dry-run` emits a non-mutating
plan without reading keys or state, contacting the cluster, or creating files.
A terminal check prevents accidental transcript-driven execution; it is not a
credential-isolation boundary or proof of the caller's identity.
Historical `emergency_bundle_delivered` fields remain historical. New receipts
use `recovery_handoff_confirmed`, `recovery_handoff_bundle_sha256` and a timestamp;
an old true value cannot skip acknowledgment of a newly generated bundle.
### Phase 1 — Normal operation
@ -99,60 +105,42 @@ See Section 6 for details.
### Phase 3 — Recovery
Use the emergency bundle stored in your personal password manager.
See Section 4 — Emergency Bundle.
See Section 4 — Emergency Recovery Handoff.
---
## 4. Emergency Bundle
## 4. Emergency Recovery Handoff
### Contents
The custodian age private key and platform-root account/factors stay in the
independently held password safe/offline custody packet. OpenBao init/unseal
material follows its separately selected custody ceremony; the lab SOPS-held
automation branch is outside the new workflow tests and retains its own gate. Service recovery files
travel in the age-encrypted ops bundle. The bundle must not contain its own
private decryption key. Current workload authority remains in OpenBao.
| Item | Purpose |
|------|---------|
| age private key | Decrypt any `secrets.enc/*.age` file from git |
| privacyIDEA admin password | Direct access to privacyIDEA if cluster/auth is down |
| LLDAP admin password | Direct LDAP directory access |
| PostgreSQL root password | Direct database access |
| break-glass user password | Emergency login if Authelia/KeyCape is down |
| ops bundle location + decrypt command | Point-in-time snapshot of all secrets |
| OpenBao unseal/recovery material, if used | Platform-control-plane break-glass only |
`make creds-emergency-reprint` is now a **non-secret recovery guide**. It reads no
private key or service secret, writes no plaintext copy and cannot reconstruct
lost custody material. Legacy secret-export arguments are refused. Clearing a
terminal or shredding a temporary file cannot retract an existing transcript.
### Delivery
Recovery sequence:
The emergency bundle is displayed once in the terminal at the end of
`make creds-agent-init`. The operator must copy it into their personal
password manager before pressing Enter to continue.
1. Retrieve the separately held key and exact encrypted snapshot from their
independent custody locations. Check the non-secret digest/receipt.
2. Use the custody unlock helper in the approved isolated workspace. Decrypt to
private files, never stdout; verify required service files without printing
their contents. Consult `docs/security-bootstrap-age-custody.md`.
3. Execute the scoped service owner's recovery procedure with the approved
cluster/target. A historical snapshot can contain superseded credentials;
validate current authority and consumer acceptance before reuse.
4. Remove temporary keys and decrypted files, and retain only safe outcomes,
artifact identities and timestamps. Loss or suspected disclosure requires
the separate owner rotation/revocation process; guide output rotates nothing.
The confirmation gate is a **deliberate security control** — bootstrap
does not complete until the human confirms receipt.
### Re-delivery
If the emergency bundle is lost or stolen:
```
make creds-emergency-reprint
```
This reprints the bundle from the current age key and secrets.
It does **not** rotate any secrets — if you suspect compromise, rotate
the affected secrets separately with `make creds-rotate`.
### Storage recommendations
Store the emergency bundle in one or more of:
- 1Password, Bitwarden, KeePassXC, or equivalent
- Encrypted offline storage
- Printed and physically secured (air-gapped scenarios)
The agent does not care which — it only cares that you confirm receipt.
### Recovery procedure
1. Restore `~/.config/sops/age/keys.txt` from the emergency bundle
2. Clone net-kingdom repo
3. Run: `make creds-apply` — re-injects all secrets from `secrets.enc/`
4. Use break-glass passwords for direct service access if needed
The 2026-10-12 source correction removes the old stdout handoff. It does not
retroactively revoke any root or password disclosed by earlier runs. Existing
founder-deferred exposure rotation remains in CUST-WP-0073-T05. Native acceptance
of the corrected workflow remains NK-WP-0049; source tests do not certify it.
---
@ -244,7 +232,9 @@ make creds-bundle
Store the bundle offsite (cloud, external drive, second location). Decrypt:
```
age -d -i ~/.config/sops/age/keys.txt ops-bundle-<date>.tar.age | tar xf -
# In the approved isolated recovery workspace, with the custody helper:
age -d -i "$NETKINGDOM_CUSTODY_AGE_KEY_FILE" -o secrets.tar ops-bundle-<date>.tar.age
# Inspect/extract privately under the owner procedure; never print file contents.
```
---
@ -256,19 +246,21 @@ The following are permanently prohibited:
1. **Committing `secrets/` (plaintext) to git** — the pre-commit hook blocks
this; do not bypass with `--no-verify`
2. **Storing secrets in KeePassXC as the primary credential store** — KeePassXC
is for optional personal backup of the emergency bundle only
2. **Using a personal safe as runtime workload secret authority** — runtime
workload custody belongs to OpenBao. Independently held bootstrap/root
custody and offline recovery remain distinct from runtime workload delivery.
3. **Skipping the `emergency_bundle_delivered` confirmation gate** — even in
non-interactive or automated runs, the agent MUST NOT mark bootstrap
complete until the human confirms receipt. This gate exists to ensure
break-glass access is always available.
3. **Skipping the current encrypted-recovery handoff acknowledgment** — a
historical `emergency_bundle_delivered` value cannot certify a new bundle.
Completion requires the attended acknowledgment bound to its SHA256; native
recovery readiness still requires the separate drill receipt.
4. **Hardcoding secrets in manifests or ConfigMaps** — all service secrets
must flow through K8s Secrets created by `create-secrets.sh` scripts
5. **Storing the age private key in the repo** — the key lives outside the
repo at `~/.config/sops/age/keys.txt`
5. **Storing or printing the root key through automation** — the private key
belongs in independent custody. Only the explicit attended unlock supplies a
temporary key; no permanent HOME key discovery or generation in bootstrap.
6. **Using OpenBao as a policy decision point** — OpenBao stores, leases,
audits, and revokes secret material; identity comes from the IAM

View file

@ -51,7 +51,9 @@ InfoTechCanon corrected its adoption brief: `972c0b6701d1693f` identified the
The schema ID is an identifier; supply the file from the owner checkout, not a
network download from that hostname. This profile remains proposed pending
owner-instance migration and validation. The King's Guard draft is provenance,
operational source/observer acceptance. Approval Engine and Qonto owner
instances migrated and passed scoped validation on 2026-10-03; that source
validation does not establish deployed delivery or observation. The King's Guard draft is provenance,
not a fallback schema.
## 2. Ownership
@ -157,3 +159,16 @@ This profile can become accepted only when:
checked with owner-authored class inventories; and
- `kings-guard` has been given the canonical coordinates so its handover draft
can become historical.
## Coordination-frame configuration — 2026-10-15
Source owners may derive their cadence timing from the ecosystem coordination
frame at `coordination-engine/config/coordination-frame.json`. The operator's
current frame is f=1h; the first adopter mapping is heartbeat f, receiver wait
2f, and effective missing-heartbeat deadline 3f after the receiver's 1.5 grace.
Namespaced frame provenance accompanies the derived numeric declaration values.
Changing the frame requires regenerating the source/receiver settings together
and separately deploying them through owner change control. It changes neither
source classification nor the heartbeat-plus-reconciliation requirement, and
a compiled configuration is not evidence of operating emission or observation.

View file

@ -0,0 +1,214 @@
---
id: netkingdom-security-case-types-v0.1
type: standard
title: "NetKingdom Security Case Types v0.1"
domain: netkingdom
status: accepted
version: "0.1"
owner: net-kingdom
created: "2026-10-08"
updated: "2026-10-08"
last_reviewed: "2026-10-08"
review_interval: 3m
scope: meta-orchestration
accepted: "2026-10-08"
accepted_by: founder
schema:
- canon/schemas/security-case-type_v0.1.schema.json
- canon/schemas/security-case_v0.1.schema.json
validator:
- tools/security-case-types/security_case_validator.py
template:
- security-case-types/_template/
related:
- canon/standards/playbook-capability-contract_v0.2.md
- canon/standards/security-scenario-composition_v0.1.md
- canon/standards/credential-management_v0.2.md
- docs/security-bootstrap-use-cases.md
---
# NetKingdom Security Case Types v0.1
## 1. Purpose
Most security work in the ecosystem repeats a small number of shapes: a browser
service that signs users in through NetKingdom, a service that authenticates
with its own credential, a workload that reads one KV path, an agent that needs
a short-lived SSH certificate. Until now each occurrence was built by hand: a
new helper script, a new custody path, a new founder checklist. That is slow,
and every hand-built instance can drift from the last one.
This standard separates **designing a shape** from **using it**:
- A **SecurityCaseType** is designed once, with ample documentation and tests,
and acknowledged as an informed-decision, because its shape is part of the
security architecture as a whole.
- A **SecurityCase** of an implemented type is light: the requester fills in the
type's parameters, the case is accepted, ops-mason arranges it, and ops-warden
carries it out on the authentication path the type provides.
Making a new type is deliberately harder than making a new case.
## 2. Terms
| Term | Meaning |
| --- | --- |
| **SecurityCaseType** | A reusable, versioned security pattern for one kind of use case. Defines parameters, composition, materialization, execution route, verification, rollback and threats. |
| **SecurityCase** | One instance of a type, with concrete parameter values, an owner and an acceptance record. |
| **KnownSecurityCase** | A SecurityCase whose type is `implemented` (section 5). Only known cases take the light path. |
| **Type acknowledgment** | The informed-decision that admits a type, or a new major version of a type. |
| **Case acceptance** | The record that the case's owner, and where the type requires it the founder, accepted this instance. |
| **Materialization** | ops-mason building the structure a case needs: OpenBao policies, roles and KV path structure (never values), and catalog entries. |
| **Follow-through** | ops-warden executing the case's declared entry points on the type's authentication path, and verifying it. |
A SecurityCaseType is not a new orchestration engine. It is a NetKingdom-owned
**composition** of playbook capabilities that their owning repos declare under
the Playbook Capability Contract, plus the guarantees in section 3. Execution
stays with the declared entry points (ADR-0007, ADR-0012).
## 3. What a SecurityCaseType must define
A type is a directory `security-case-types/<type-id>/` in net-kingdom containing:
1. **`type.yaml`** (machine-readable, schema `canon/schemas/security-case-type_v0.1.schema.json`):
- `id`, `version`, `status` (section 5), `title`, `purpose`
- `parameters`: each with type, validation, allowed range and whether a case
may set it. Secret values are never parameters, only custody references.
- `composition`: required playbook capability ids with exact provider pins,
in the security-scenario-composition form (fail-closed ordering, explicit
initial trust, owner attribution)
- `materialization`: the OpenBao structure ops-mason creates per case, as
templates over the parameters: policy, role, KV path, catalog entry;
a policy, role or catalog entry templates its body (`body`), not only its
name, so that cases add values and never design
- `follow_through`: the ops-warden route for each step, `warden_executes`,
the auth method and the entry point; which steps need an attended human
login, if any, and why. Each step also names:
- `lane`: the reviewed ops-warden catalog lane that resolves it. This is
required for every step ops-warden executes or groups into an attended
session, and ops-warden refuses to plan a step whose lane is missing or
unreviewed.
- `value_handling`: `none`, or `generated_in_custody` for a secret that is
generated inside the broker and custody. A generated value never passes
through the run, its argv, stdout or receipts.
- optionally `ready_check`: a status-only gate that must hold before the
next step (for example, a delivery reporting Ready). It never reads a
value.
`warden_executes: true` means exactly this: ops-warden invokes the owner's
declared entry point through its existing caller proxy, under the caller's
identity and the caller's own lane. It never means that ops-warden holds a
standing credential for the step; that would make it a broker (ops-warden
ADR-0002). *(Clarified 2026-10-09 from ops-warden's review,
WARDEN-WP-0044-T01 R1–R3.)*
A step listed in the type's `execution_descriptors` is admissible to
ops-warden's case kernel whatever its `warden_executes` value, and the kernel
enforces the step's mode. For an `attended` step, "kernel-dispatched" means
the reviewed adapter runs only inside the attended human session (descriptor
actor `human`, the step's own lane, parent-session revocation per attempt),
never under an agent or a standing credential. The human session is the
authority, and the kernel is only the dispatcher. For an owner-path step
(neither `warden_executes` nor `attended`), the lane must resolve to the
owner's own delegated path, and ops-warden holds no owner credential.
`warden_executes` keeps its caller-proxy meaning above for unattended steps.
*(Clarified 2026-10-17 from the 1.3 owner reviews, NK-1.3-C2 and ops-warden
KERNEL-3.)*
- `verification`: the checks that prove a case is live (positive and
negative), runnable without reading a secret value
- `rollback`: the exact inverse of each step
- `acceptance`: who accepts a case (owner only, or owner and founder) and
what risk class this type carries
2. **`README.md`**: the threat model, what the type protects and from whom,
design alternatives considered, and known limits.
3. **Tests**: validation of `type.yaml`, a rendered example case, and the
composer's output for that case. A type with no runnable tests cannot be
acknowledged.
4. **Reference instances**: existing hand-built instances this type replaces
or adopts, so that the type is grounded in practice.
## 4. What a SecurityCase must define
A case is a file `security-cases/<case-id>.yaml`, in the requesting owner's
repository or in net-kingdom when it has no other owner:
- `type`: `<type-id>@<version>`, which must be `implemented`
- `parameters`: values for the type's parameters
- `owner`: the repository and person accountable for the case
- `acceptance`: who accepted it, when, and where (State Hub decision, or a
record in the owner's repository)
- `status`: `proposed` → `accepted` → `materialized` → `live` → `retired`
Nothing else. Everything a case needs beyond its parameters belongs in its type.
If a case cannot be expressed in its type's parameters, it is not a case of that
type: either change the case or propose a new type version (section 6).
## 5. Type lifecycle
| Status | Meaning | Who moves it |
| --- | --- | --- |
| `draft` | being written; not usable | type author |
| `proposed` | complete per section 3, tests green, Decision Memo prepared | type author |
| `acknowledged` | admitted by informed-decision | the founder, through informed-decision |
| `implemented` | ops-mason can materialize it and ops-warden can follow through; one reference case is live and verified | net-kingdom, with ops-mason and ops-warden |
| `deprecated` | no new cases; existing cases keep running | net-kingdom |
| `retired` | no cases remain | net-kingdom |
Only `implemented` types accept new cases.
## 6. Changing a type
- **Patch** (documentation, tests, a stricter validation): a normal reviewed
change; no decision.
- **Minor** (a new optional parameter, an extra verification step): a
reviewed change, recorded in the type's history; no new acknowledgment unless
the risk class changes.
- **Major** (a changed authentication path, materialization or trust
transition; a looser validation; a higher risk class): a new informed-decision,
as for a new type. Existing cases stay on their version until migrated.
## 7. The light path for known cases
For a case of an `implemented` type:
1. The requester writes the case file and validates it against the type.
2. The accepter records acceptance (section 4). This is the only human step
unless the type's `acceptance` requires an attended login for a specific
step, which the type must justify.
3. ops-mason materializes it from the type's templates. Its executive-summary
gate collapses to the case acceptance, because the shape was decided when the
type was acknowledged.
4. ops-warden follows through on the type's route and runs the type's
verification.
5. The case moves to `live` with the verification receipt.
No case-specific helper script is written. If one seems necessary, the type is
incomplete.
## 8. Roles
| Party | Responsibility |
| --- | --- |
| net-kingdom | owns this standard, the type directory, the schema and the composer; admits and retires types |
| ops-mason | materializes OpenBao structure for cases from type templates; proposes the type's materialization section |
| ops-warden | follows through on cases; owns the routing catalog entries a type defines; proposes the type's follow-through section |
| owning repositories (key-cape, railiance-platform, ...) | declare the playbook capabilities that types compose; own execution |
| informed-decision | presents the type's Decision Memo to the founder and records acknowledgment |
| requesters | write cases, own them, retire them |
## 9. Boundaries
- No secret value in a type, a case, a test or a receipt.
- This standard does not grant execution authority. Each step runs under its
owner's declared entry point and its own authentication.
- Existing hand-built instances keep working. They are adopted as cases when
their type is implemented, not rewritten in place.
## 10. Open questions (carried after acceptance, 2026-10-08)
- Where should case files live by default: with the requester, or centrally in
net-kingdom?
- Is the informed-decision Decision Memo for a type rendered from `type.yaml`
and `README.md`, or written separately?
- Which steps of the first types genuinely need an attended human login (for
example key-cape's live client configuration), and how is that minimised?

View file

@ -3,16 +3,23 @@ id: netkingdom-security-layer-model-v0.8
type: standard
title: "NetKingdom Security Layer Model v0.8"
domain: netkingdom
status: proposed
status: accepted
revision: "accepted-1"
accepted_memo: "infd-20261010-security-layer-v08"
accepted_memo_version: 2
accepted_disposition_id: "disp-43e051e9-b12f-4c34-b26e-40b85a96c2e8"
accepted_approval_id: "8432d0dc-adc0-42d0-9a5b-6b71ff492e15"
accepted_at: "2026-10-10T23:23:53Z"
accepted_source_sha256: "103a635f19ab9fe53094d69c3eeebcccd8e87ac9588846167e7e204a7c6de167"
version: "0.8"
supersedes: canon/standards/security-layer-model_v0.7.md
owner: gate-house
publication_owner: net-kingdom
created: "2026-08-28"
updated: "2026-09-09"
last_reviewed: "2026-09-09"
updated: "2026-10-11"
last_reviewed: "2026-10-10"
review_interval: 3m
source_revision: "gate-house@516ed4e"
source_revision: "gate-house@4bd50bfcac850f34e5563ade61fc92103b062f4d; A9-A13 set v0.3 and A2 v1.1"
standard_token: security-layer-model_v0.8
# "assented_by" records assent to a BOUNDARY, given at the version named.
# It is not assent to the current text. Revision reviews are listed in §14.
@ -188,6 +195,31 @@ cannot be lost in a summary.
| **Engines** | interfaces for a modeled concept | APIs, contracts | yes |
| **Staff** | management, operations, change, controlling | specifications, decisions, workplans, tasks | **no** |
**The vocabulary is these four tokens, and it is closed:**
```
Taxonomy Tooling Engine Staff
```
A layer declaration (§11) carries exactly one of them. **Comparison is ASCII
case-insensitive** and a conformance run MUST fold case before comparing: two spellings
of `Engine` do not describe two boundaries, and a check that reports nine findings about
capital letters has made the one real disagreement unfindable. The spellings above are
canonical for new and changed declarations; a lowercase declaration is **conforming**,
not tolerated.
A value outside the four is not conforming and does not name a tier this model has not
enumerated. A fifth layer arrives by amending this section, argued by a repository that
bears a cost under the four-layer cut — not by a repository declaring one.
`Engines` appears in the table heading above for its plural reading; the token is
`Engine`, as §4's Layer column carries it.
A **role** is not a layer. §3.3's Engine roles and §6.4's PEP shape describe what a
repository does within its layer, are carried in a separate `role:` key, and are not
governed by this vocabulary. Ruled in `GH-DEC-2026-012` R2 and again in
`GH-DEC-2026-017` §3.
### 3.1 Taxonomy
Cross-cutting language. Taxonomy repositories define terms and semantic
@ -287,34 +319,49 @@ assent to them sight-unseen.
## 4. Layer catalog
| Repository | Layer | Role | Owns |
| --- | --- | --- | --- |
| `info-tech-canon` | Taxonomy | — | ecosystem-wide semantic contracts and terminology |
| `net-kingdom` | Taxonomy | — | NetKingdom standards of record; publication |
| `key-cape` | Tooling | — | packaged identity tooling; IAM profile; authentication |
| `OpenBao` | Tooling | — | secret storage, leases, PKI, dynamic secret engines |
| `user-engine` | Engine | PIP | users, accounts, memberships |
| `tenant-engine` | Engine | PIP | tenant-as-an-entity facts |
| `zone-engine` | Engine | PIP | zone identity and membership — offline reference conformance per its 2026-08-23 disposition |
| `secrets-engine` | Engine | Lifecycle | credential abstraction, custody, lifecycle |
| `audit-core` | Engine | Evidence | audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6) |
| `access-engine` | Engine | **PDP** | **the policy decision** — the only decision point (§6) |
| `approval-engine` | Engine | PIP | the approval object — durable, authenticated, consumable, atomically supersedable (§9.4) |
| `maturity-engine` | Engine | PIP | graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5) |
| `gate-house` | Staff | — | security doctrine, authority context, curriculum; **conformance review — through `maturity-engine` (§9.5)** |
| `ops-mason` | Staff | PEP-shaped | building and tearing down access routes and perimeters |
| `ops-warden` | Staff | PEP-shaped | operational access lanes, stewardship, runbooks; SSH certificate issuance — **declared-gap** (§9.1, §13) |
| `kings-guard` | Staff | — | adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation **pending**; **proposes** containment, which it does not own (§9.2) |
| `whitehat-security` | Staff | — | offensive validation |
| Repository | Layer | Role | Owns | Evidence source |
| --- | --- | --- | --- | --- |
| `info-tech-canon` | Taxonomy | — | ecosystem-wide semantic contracts and terminology | unassessed |
| `net-kingdom` | Taxonomy | — | NetKingdom standards of record; publication | unassessed |
| `key-cape` | Tooling | — | packaged identity tooling; IAM profile; authentication | unassessed |
| `OpenBao` | Tooling | — | secret storage, leases, PKI, dynamic secret engines | unassessed |
| `user-engine` | Engine | PIP | users, accounts, memberships | unassessed |
| `tenant-engine` | Engine | PIP | tenant-as-an-entity facts | unassessed |
| `zone-engine` | Engine | PIP | zone identity and membership — offline reference conformance per its 2026-08-23 disposition | unassessed |
| `secrets-engine` | Engine | Lifecycle | credential abstraction, custody, lifecycle | unassessed |
| `audit-core` | Engine | Evidence | audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6) | no |
| `access-engine` | Engine | **PDP** | **the policy decision** — the only decision point (§6) | yes |
| `approval-engine` | Engine | PIP | the approval object — durable, authenticated, consumable, atomically supersedable (§9.4) | unassessed |
| `maturity-engine` | Engine | PIP | graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5) | unassessed |
| `gate-house` | Staff | — | security doctrine, authority context, curriculum; **conformance review — through `maturity-engine` (§9.5)** | unassessed |
| `ops-mason` | Staff | PEP-shaped | building and tearing down access routes and perimeters | unassessed |
| `ops-warden` | Staff | PEP-shaped | operational access lanes, stewardship, runbooks; SSH certificate issuance — **declared-gap** (§9.1, §13) | unassessed |
| `kings-guard` | Staff | — | adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation **pending**; **proposes** containment, which it does not own (§9.2) | unassessed |
| `whitehat-security` | Staff | — | offensive validation | unassessed |
**Evidence source is a marking, not a reading.** A repository is a source of evidence
where it **emits** events into the estate's evidence stream. Custody is disjoint from
emission: holding a record never discharges any emitter's guarantee, and `audit-core`'s
`Evidence` role is custody. The distinction is what `§9.6` rests on — an archive cannot
prove a record was never sent, which has content only because the archive and the
emitter are different parties.
The column carries exactly `yes`, `no`, or `unassessed`: `access-engine` is
`yes`, `audit-core` is `no`, and all other rows remain `unassessed` until
assessed. `—` is not a value. The §11 check reports `unassessed` as
unassessed, never as conforming or as not a source (`GH-DEC-2026-024` §2).
An **actuation surface** — reduce authority, require step-up, isolate a workload
— is catalogued nowhere because it does not exist. See §9.2: it is an Engine
concept held at zero, not a Staff capability.
`access-engine` is the ruled name for the repository currently called
`flex-auth`; both denote the same authority until the governed rename completes.
Execution conditions for that rename are recorded in its migration decision, not
here.
`access-engine` is the ruled repository name formerly carried by `flex-auth`;
both names denote the same authority across the governed rename. A13's
circulated coordinate was pending, not broken. The repository coordinate now
resolves at `access-engine`: `FLEX-WP-0020` records rename operation
`a15af186-6b98-47a4-9b53-79e131747d11` completed on 2026-09-30.
Runtime names remain `flex-auth` under `FLEX-DEC-2026-013`; rename execution
conditions and remaining separate owner gates live in that workplan, not here.
## 5. The binding rule
@ -1182,15 +1229,65 @@ A layer stated *about* a repository by another repository is not a declaration.
Review notes, catalog rows, and correspondence record an intent to adopt; only
the repository's own file conforms.
**Declaration form.** Because prose cannot distinguish a declaration from a
transcribed review, a declaration MUST carry a machine-readable form: a `layer:`
key in the `INTENT.md` frontmatter, or an equivalent declaration file. Without
it this section asserts a property it cannot deliver — the defect this standard
has now corrected three times elsewhere. `ops-warden` has implemented a reference
form (`layer.yaml`, a conformance script, and a test covering the §5.2
no-authority property) and offered it to the repositories that have yet to
declare. Raised by `audit-core`, which noted that `flex-auth`'s conforming
declaration is legible as one only by following its decision trail.
**Declaration form.** Because prose cannot distinguish a declaration from a transcribed review, a declaration
MUST carry a machine-readable form: **a `layer:` key in the repository's own `INTENT.md`
frontmatter.** An equivalent declaration file MAY accompany it and is a **derived
artifact** under this section's derived-artifact rule: it MUST be marked as derived, MUST
name `INTENT.md` as what it derives from, and MUST agree with it. `INTENT.md` governs.
This is not a preference between two files. This section already says a repository
declares its layer in its own `INTENT.md`; the alternative form was added to give the
declaration a machine-readable *form*, and was read as giving it a second *authority*.
**A disagreement between the two forms is a finding in its own right** and MUST be
reported rather than resolved away by precedence. Precedence says which value is the
repository's answer; it does not say the disagreement did not happen. Where one
appearance is reachable by two routes, the record says which route.
**A conformance run MUST state its scope.** This section's obligations attach to
estate-authored repositories **in §4**. A repository outside §4 may declare voluntarily
using this form, and a voluntary declaration is welcome; it is not a §4 obligation and a
run that grades it is over-scoped. A run over §4 and a run over every repository carrying
a declaration answer different questions, and a report that does not say which it did
cannot be acted on. **A run over a single repository is a permitted third scope:** it
names the repository and states whether that repository is in §4, and a run over a
repository outside §4 reports a voluntary result, not §4 conformance.
**A layer declaration MUST NOT carry a standard version.** The declared layer is a
standing property of the repository and does not change when this standard is revised; a
version in the declaration makes every revision read as though it invalidated every
declaration. That is the confusion this standard's own `assented_by` note exists to
prevent: assent records assent to a **boundary**, given at the version named, and is not
assent to the current text.
**The rule reaches content, not a key name.** The declaration, for this purpose, is every
key of the `INTENT.md` frontmatter and every key of a derived declaration file. No key or
value of either carries a version of this standard **or of its companion** — including a
version carried in a path or file name, such as a `standard:` key naming
`…security-layer-model_v0.7.md`. Comments, and the version of a declaration file's own
schema, are not versions of this standard and are not reached. *(r3)* **A version is
reached when it is carried as a pin:** in a key naming a version of this standard or its
companion, in a path or file name, or in the value of a key that identifies this standard
or its companion. A citation of an earlier revision in a record's prose, stating where a
rule came from, is provenance and is not reached.
**The rule reaches the declaration only.** A stance map, claims map, or evidence
classification states a position on clause text, which is version-scoped as a layer is
not, and it SHOULD carry the version of the text it answers; this rule does not apply to
it and a conformance run MUST NOT apply it there.
**Version-scoped state belongs to the conformance run.** Every run MUST state, in its
output, the version or commit of this standard it checked against and its scope. A checker
re-run on demand satisfies this by stating both on every run; no declaring repository is
required to emit a durable conformance record. Where a run's output is **retained or
relied upon**, the retained output is a derived artifact under this section's
derived-artifact rule, and the party retaining it carries the version or commit it was
derived at.
If a future revision changes §3's vocabulary such that a declared token no longer denotes
the same layer, declarations do **not** silently retarget: that revision carries a
re-declaration round, run in this standard's authoring voice. A per-file version pin would
not have caused anyone to re-read anything.
Mechanically checkable:
@ -1203,8 +1300,11 @@ Mechanically checkable:
surface;
- no §4 capability is catalogued without an engine surface, a `pending` mark, or
a `declared-gap` mark;
- every repository catalogued in §4 as a source of evidence declares its
**emission guarantee** in its machine-readable layer declaration: for a
- every repository **marked in §4 as an evidence source** declares its
**emission guarantee** in its machine-readable layer declaration, **per event
class** — a single repository-level guarantee over a stream containing both
a high-volume and a rare class is an average, not a declaration, and will be
satisfied by rate monitoring that cannot see the rare event go missing; for a
**load-bearing** source, a local transactional outbox (§9.4), its declared
cadence, and the detection surface the governing cadence profile requires for
that class — under `net-kingdom`'s
@ -1212,7 +1312,9 @@ Mechanically checkable:
MAY be covered by rate monitoring where the source has classified it as
suitable, with a positive window and a positive minimum; a **rare**
load-bearing class MUST NOT be, and MUST carry a heartbeat **and**
reconciliation, not either alone; for an **attributive** source, the trade it
reconciliation, not either alone, and, where that guarantee is not itself
completeness, an explicit statement that completeness is not claimed; for an
**attributive** source, the trade it
makes and an explicit statement that completeness is not claimed. Which class
an event falls in, and whether it is rare, is the source's published
classification: a conformance run is supplied that inventory and MUST NOT
@ -1230,8 +1332,8 @@ A source that declares no emission guarantee is not conforming, and neither is
one that declares load-bearing emission with rate monitoring alone where its
event class is rare (§9.6). The declaration is what makes §9.6 checkable rather
than reviewable; without it the section states an obligation whose satisfaction
cannot be observed, which is the §9.1 defect this standard has now corrected four
times. Drafted in `gate-house/docs/contracts/approval-emission-detection.md`,
cannot be observed, which is the §9.1 defect this standard has repeatedly
corrected; the instances are listed in §15. Drafted in `gate-house/docs/contracts/approval-emission-detection.md`,
which `approval-engine`'s `cadence.yaml` implements as the reference instance.
The check states no MUST/SHOULD split of its own: which evidence classes must
declare cadence, and in which form, is the governing profile's to say (§17), and
@ -1239,6 +1341,21 @@ this item follows it rather than restating it.
Raised by `audit-core` as the general form of `GH-IN-0001`, so the finding that
produced `GH-WP-0002` cannot recur unnoticed.
**The completeness non-claim binds both arms.** Where the declared guarantee is
not itself completeness, the declaration carries an explicit statement that
completeness is not claimed. A load-bearing guarantee states the controls, not
their sufficiency. The outbox is atomicity at the source against crash and partial
failure. Heartbeat and reconciliation detect loss after the fact. Neither is a
proof that a record was never sent, and neither covers a compromised source
suppressing an event and its own count together. Completeness, where this standard
actually requires it, is emission atomic with the state change; the declaration
may say that, and it MUST NOT describe the detection surface or the archive as
that completeness. Ruled in `GH-DEC-2026-024` §1, from the bound on rule 13 and
from §9.6.
No detectable or direction-limited failure-path release exception is added to
§9.6 or this check (`GH-DEC-2026-024` §3).
The example-validation check is the mechanical half of §12's derived-artifact
rule; `approval-engine` reports it at fifteen lines. Its second clause is the
non-obvious one: an example set that silently omits an optional field teaches
@ -1411,8 +1528,33 @@ follow-up. Settled in `GH-DEC-2026-006`.
## 14. Adoption
Status is **proposed**. v0.7 remains **accepted** and in force until this version
is accepted in its place; it is not patched.
Status is **accepted**. The confirmed human acceptance of memo
`infd-20261010-security-layer-v08`, version 2, approval
`8432d0dc-adc0-42d0-9a5b-6b71ff492e15`, at 2026-10-10T23:23:53Z adopts the exact
proposed source SHA256
`103a635f19ab9fe53094d69c3eeebcccd8e87ac9588846167e7e204a7c6de167`.
This version now governs; v0.7 remains unchanged as historical accepted text.
The acceptance metadata and this adoption notice are publication annotations,
not changes to the approved normative provisions. The bounded publication
receipt is `docs/security-layer-v0.8-acceptance.md`. Checker convergence remains
the separate post-flip GH-WP-0004-T11 obligation.
**Publication preparation history, 2026-10-10.** A9–A13 (A11 r2, A12 r3) and
`GH-DEC-2026-024`'s A2/A10 corrections are assembled into this proposed
cut. This source preparation neither ratifies the standard nor closes
`GH-DEC-2026-019`'s hold by itself. Condition 2 is satisfied by audit-core's
returned 2026-09-23 §11 review (GH-WP-0004-T07); condition 3 by the four
declarations/dated gaps (T08). Condition 1 is source-complete: GateHouse T09 records and dispositions the
applied fresh A11 r2/A12 r3 technical returns from kings-guard,
approval-engine and railiance-master on 2026-10-10, all against amendment-set
SHA256 9b454b83c9aeed08ba13dba4d812f5ae6b4293ced9e43088d64cc3cc00b1459d.
Prior-revision assent was not carried over. Those receipts are technical
returns to the exact amendments, not founder acceptance or a review of
every final-cut adaptation. Gate-house retains authoring
disposition and net-kingdom publication; the named founder acceptance act
was separately supplied by the confirmed human memo acceptance above. T11 checker
convergence is now due after this flip, rather than being inferred complete.
Two things that acceptance does and does not mean, kept apart because
`ops-warden` asked for the distinction:
@ -1422,7 +1564,7 @@ Two things that acceptance does and does not mean, kept apart because
| **Boundary assent** | given by the four repositories below, at the version named in each record, and undisturbed since |
| **Revision review** | v0.7's changes were each the adopted remedy of a v0.6 finding. All fifteen v0.6 findings were subsequently audited against the v0.7 body — not against its change log — and confirmed dispositioned (`gate-house/docs/conformance/2026-09-06-v06-findings-audit.md`) |
| **Reviewed as text** | v0.8's circulated round returned findings from `access-engine`, `approval-engine`, `ops-warden`, and `net-kingdom`, and every finding is dispositioned in `gate-house/docs/conformance/2026-09-06-v08-assent-round.md`. Nine corrections landed in this text during circulation (§15 items 12–20) and one request was declined with its reasons in §6.4 |
| **Not claimed** | `kings-guard` and `audit-core` have not returned a text review; the sections each was asked to attack — §9.5's criteria-grounding clause and §12's step-four paragraph for the first, §11's emission-guarantee wording for the second — carry no assent from the repository best placed to test them. `approval-engine` named the sections it did **not** read (§1–§4, §7, §8, §9.1–§9.2, §9.5–§9.6, §9.8, §10, §18, §20) rather than let a three-finding review read as a clean bill |
| **Not claimed** | No clean-bill review of every section is claimed. `kings-guard`'s §9.5 criteria-grounding and §12 step-four review is not claimed here. `audit-core` returned its §11 review on 2026-09-23; its findings are dispositioned by GH-DEC-2026-024 and incorporated in this proposed cut. `approval-engine` named the sections it did not read in the earlier circulation; A11 r2/A12 r3 returns concern those exact amendments, not all sections. |
**Ten of this version's twelve changes as cut were requested by another
@ -1470,6 +1612,20 @@ basis. Declaration requests are open as intakes in each.
## 15. Change log
**2026-10-10 source finalization of the held proposed cut:** A9 closes the
layer tokens and ASCII case comparison (§3); A10 adds assessed evidence-source
marking (§4) and per-class emission inventory (§11); A11 r2 makes INTENT
frontmatter authoritative, sidecars derived and disagreements findings, and
admits the explicit single-repository scope (§11); A12 r3 bans declaration
pins while preserving prose provenance and requires version/scope in each run
(§11). A13's coordinate note follows the governed completed rename while
preserving `flex-auth` runtime names (§4). GH-DEC-2026-024 adds the explicit
load-bearing completeness non-claim and preserves the rejection of a
failure-path exception; no other section's authority is expanded.
Sources: gate-house/docs/amendments/v0.8-section-11-declaration-amendments.md
v0.3; gate-house/docs/amendments/v0.8-amendment-set.md v1.1; GH-DEC-2026-019,
020, 021 and 024. The older numbered change list below remains history.
v0.7 → v0.8:
1. **§9.7.3 corrected** — the PEP MUST consume before the protected side effect.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,380 @@
---
id: netkingdom-service-identity-privilege
type: standard
title: "NetKingdom Service Identity and Privilege v0.1"
domain: netkingdom
status: accepted
version: "0.1"
created: "2026-10-24"
updated: "2026-10-25"
scope: service-account-governance
revision: "v0.1"
owner: net-kingdom
last_reviewed: "2026-10-25"
review_interval: 3m
standard_token: service-identity-privilege_v0.1
profiles:
- security-canon/infospace/standards/devops-secrets-management/SecurityCanonDevOpsSecretsManagementStandard.md
- security-canon/infospace/vocabulary/mode-of-authority/SecurityCanonModeOfAuthority.md
related:
- SECURITY-COMPANION.md
- canon/standards/iam-profile_v0.3.md
- canon/standards/credential-management_v0.2.md
- canon/standards/tenancy-posture_v0.1.md
- canon/standards/posture-feedback_v0.1.md
- docs/service-identity-inventory.md
- docs/crisis-custody-list.md
---
# NetKingdom Service Identity and Privilege v0.1
## 0. Status
**Accepted, v0.1. Ratified by the founder on 2026-10-25.** The text is draft-4, unchanged by ratification: the
founder approved draft-3 in principle on 2026-10-24, both owner replies arrived the same day (security-canon and
railiance-platform; §10), and the corrections they produced were applied before ratification. Ratifying the
standard does not apply any exception's elimination path, change any live service, or make a declaration
owner-confirmed; each service owner still adopts it on their own schedule, and the declarations stay
`owner_confirmed: false` until they do.
- **draft-1** (2026-10-24): thirteen rules, naming, five initial exceptions, seven open questions.
- **draft-2** (2026-10-24): the founder's review decided six points (§10), added the agent class and SI-14.
- **draft-3** (2026-10-24): realigned as NetKingdom's **estate profile of SecurityCanon**, after a comparison
with the canon showed that drafts 1 and 2 restated SEC-DSM rules and invented a taxonomy beside the canon's.
Classes are replaced by the imported `principal_type` and **authority grants** written `MODE @ SCOPE` with
the canon's dimensions. Break-glass is an *activation*, not a kind of identity. Every rule now names the SEC-DSM
rules it profiles and states only what NetKingdom adds. New: the estate identities in canon terms (§3) and
SI-15, accountable automation. All founder decisions and every rule id from draft-2 are kept.
- **draft-4** (2026-10-24): applies the owner replies. security-canon confirmed all five mode readings and found
citation errors (§4, §8), now corrected; railiance-platform prepared the F-1 and `aal2` role changes (source only);
key-cape, glas-harness and state-hub assessed the agent-identity and attribution gaps (§10). No decision changed.
The offline validator (`tools/service-identity`) implements draft-3 (principal type and grants, NK-WP-0054-T08).
The live health check (T04) does not exist yet.
## 1. Purpose, scope and authority
This standard says which accounts, with which authority, may exist inside the services that make up
NetKingdom, and how that is declared and checked.
It is a **profile**, not a second rulebook. Per SecurityCanon SEC-ADR-001, the identity model belongs to
InfoTechCanon and security concept definitions to SecurityCanon; NetKingdom holds the security architecture and
standards of record for the estate. So this standard:
- **imports** the vocabulary: `principal_type` as classified by SecurityCanon over the InfoTechCanon Principal
(the canon itself words its ownership two ways; open as SECURITY-IN-0001) and the Mode of Authority grant
notation with its dimensions (SecurityCanon, draft 0.2.0);
- **profiles** the DevOps Secrets Management Standard (`SEC-DSM` 1.0.0): it names the rules that apply to
NetKingdom accounts and, where NetKingdom is stricter, says so;
- **adds** only what is estate-specific: which identities exist, their names, the declarations, the link to
crisis custody, and how conformance is checked.
Out of scope: tokens (IAM Profile v0.3), custody procedure (Credential Management v0.2), tenant isolation
(Tenancy Posture, Security Zones), and accounts a tenant creates inside its own workloads.
## 2. Vocabulary (imported)
**Principal type** (`principal_type`): `HUMAN`, `WORKLOAD`, `SERVICE`, `AGENT`, `CONTROLLER`, `DEVICE`. IAM
Profile v0.3 carries the same distinction in tokens in lower case. `Environment` is cited as a Mode of Authority
dimension without naming an owner, because the canon names two.
**Authority grant:** `MODE @ SCOPE`, where the mode is one of `USE`, `OBSERVE`, `OPERATE`, `ADMINISTER`,
`GOVERN`, `CONSTRUCT`, `SOVEREIGN`. A grant may state `activation` (`STANDING`, `JUST_IN_TIME`, `SCHEDULED`,
`APPROVED`, `INCIDENT`, `BREAK_GLASS`, `RECOVERY`), `provenance`, `environment` (`DEVELOPMENT`, `TEST`,
`STAGING`, `PRODUCTION`, `RECOVERY`), `evidence` and `relation` (`DELEGATED`, `IMPERSONATED`). Modes classify the
*kind* of authority, not its amount.
**Account** (NetKingdom term): an identity held in a service's own store, with or without a stored credential.
**Credential** and **standing authority** are used as in SEC-DSM §4.
Estate conventions, which are names for recurring grant shapes and not new canon terms:
| Convention | Canon expression |
| --- | --- |
| Bind account `svc-<service>` | a `SERVICE` principal with `USE @ directory`, read-only operations, one consumer |
| Service-admin account `<short>-admin` | an account held by a `HUMAN`, `ADMINISTER @ <that service>` |
| Break-glass | any grant with `activation = BREAK_GLASS` |
| Agent | an `AGENT` principal with `relation = DELEGATED` and the person it acts for as `authority_context` |
Draft-2's classes map as follows: `human` → `HUMAN`; `break-glass` → `activation = BREAK_GLASS`; `bind` and
`infrastructure` → `SERVICE` with the matching grant; `service-admin` → the convention above; `workload` →
`WORKLOAD` or `SERVICE`; `agent` → `AGENT`.
## 3. The estate identities
The founder's model, written in canon terms. This is what the declarations must express.
| Account | Principal | Grants | Activation | Environment |
| --- | --- | --- | --- | --- |
| `platform-root` | `HUMAN` | `SOVEREIGN @ platform`; `CONSTRUCT @ platform` (build mode), a separate grant | `BREAK_GLASS` for `SOVEREIGN`; standing for `CONSTRUCT` | `SOVEREIGN`: `PRODUCTION`, and `RECOVERY` for restored environments; `CONSTRUCT`: `DEVELOPMENT` and `TEST` only, never `PRODUCTION` |
| `admin` | `HUMAN` | `OPERATE @ platform`; `OPERATE @ backup-system`; `ADMINISTER @ directory` through an admin group | `STANDING` | `PRODUCTION` |
| `tegwick` | `HUMAN` | `CONSTRUCT @ product` | `STANDING` | `DEVELOPMENT`, `TEST` |
| `ld-admin` | account held by a `HUMAN` | `ADMINISTER @ lldap` | `BREAK_GLASS` (sealed fallback) | `PRODUCTION` |
| `pi-admin` | account held by a `HUMAN` | `ADMINISTER @ privacyidea`; `GOVERN @ privacyidea` (its policy `pi-admin-all-rights` can set the policies that bound other admins) | `STANDING` (exception SI-X5: only a local admin exists) | `PRODUCTION` |
| `svc-authelia`, `svc-keycape`, `svc-privacyidea` | `SERVICE` | `USE @ directory`, read-only | `STANDING` | `PRODUCTION` |
| provisioner account | `SERVICE` | `ADMINISTER @ directory/people,groups`, narrowest working scope | `STANDING` | `PRODUCTION` |
| Claude Code, Codex, Grok sessions | `AGENT` | what the tools they can reach allow (SI-14) | per task | that of the person they act for |
Per-service admin disposition (founder decision `9f0f7a87`, 2026-10-28, NK-WP-0054-T06). A service with no
local admin plane has no service-admin account; SI-4 applies where one exists.
| Service | Service-admin account | Custody | Day-to-day administration |
| --- | --- | --- | --- |
| LLDAP | `ld-admin`, configured super-admin (introduction rehearsed in isolation first) | Founder's password safe; crisis list | `admin` through an admin group |
| privacyIDEA | `pi-admin` (exists; SI-X5) | Founder's replicated browser password safe; crisis item 7 | `pi-admin` until an attributed admin path exists |
| Authelia | none: administered only through reviewed configuration | not applicable | reviewed config change |
| KeyCape | none: client registry is configuration; `/platform/authentication-policy` needs role `platform-operator` at `aal2` | not applicable | `admin` with `platform-operator` |
| OpenBao | none locally: federated `platform-admin` role (`aal2`); root only through the `generate-root` ceremony | unseal shares, crisis item 3 | `admin` via `platform-admin` |
| PostgreSQL | CNPG `postgres` superuser | its cluster Secret, used only through railiance-platform | railiance-platform owner operations |
`backup operator → OPERATE @ backup-system` and `developer → CONSTRUCT @ product` are the canon's own
translations, and match the founder's intent that `admin` operates backups and `tegwick` builds.
Readings confirmed by security-canon on 2026-10-24 (its review, `docs/netkingdom-service-identity-review.md`):
`USE @ directory` for a bind, `SOVEREIGN` with `BREAK_GLASS` for `platform-root` (the canon's vocabulary card has the
same shape), `ADMINISTER` for a local admin (the canon's own example is "break-glass admin → ADMINISTER,
activation=BREAK_GLASS"), and `OPERATE @ backup-system` for `admin`. Notes: `SOVEREIGN @ platform` does not imply
sovereignty over realms nested inside it, so recovering OpenBao, LLDAP or the identity realm is its own scope. The
canon flags `GOVERN` plus `CONSTRUCT` on one actor as a finding, and is silent on `SOVEREIGN` plus `CONSTRUCT`;
the two grants are kept separate and split by environment. Whether break-glass use of production is
`PRODUCTION` or `RECOVERY` is open in the canon (SECURITY-IN-0002); the direct form is used here.
## 4. Rules
Each rule keeps its draft-2 id. **Canon** names the SEC-DSM rules it rests on, with their strength: *MUST* and
*MUST NOT* bind; *SHOULD* guides. **NetKingdom adds** states what is stricter or estate-specific. A NetKingdom
MUST that rests on a canon SHOULD is said to do so. security-canon checked these references on 2026-10-24
(`docs/netkingdom-service-identity-review.md` in that repository).
**SI-1 One account, one principal type, one purpose.** Canon: DSM-ID-004 (MUST: human and machine credentials are
distinguishable). DSM-AP-003 (shared production account) is an anti-pattern and is cited only as an illustration.
NetKingdom adds: an account presented by a `HUMAN` and by a `SERVICE` is non-conformant, whatever its privileges.
**SI-2 People and services never share a credential.** Canon: DSM-ID-004 (MUST NOT: human credentials embedded in
automated workloads; SHOULD NOT: machine credentials used interactively). NetKingdom adds: the stronger "never
share", and that a human's password is not stored where a service reads it, including a Kubernetes Secret.
**SI-3 Bind accounts.** Canon: DSM-ID-001 (SHOULD: each workload its own identifiable identity; MUST NOT: depend on
a shared generic credential where individual identity is practical) and DSM-AUTH-001 (MUST: least authority).
NetKingdom adds: the SHOULD becomes MUST, so each consuming service has its own bind account with read-only
`USE @ directory`; and "never used interactively" strengthens DSM-ID-004's SHOULD NOT. A service that must write gets
its own `SERVICE` account with the narrowest working `ADMINISTER` scope, never the service-admin account.
**SI-4 One local admin per service.** Canon: DSM-AUTH-001 (MUST) supports limiting the grant to that one service.
NetKingdom adds: one local admin account per service, named `<short>-admin`, whose grant is `ADMINISTER @ that
service` and, where the service has a single admin tier, `GOVERN @ that service`. Nothing in the canon requires the
single account or the naming.
**SI-5 Attributed administration first.** Canon: DSM-AUDIT-003 (SHOULD: audit records identify the principal) and
the objective of reducing standing authority (SEC-DSM section 4.12, a statement of objective). NetKingdom adds, as a
MUST resting on those: day-to-day administration uses the administrator's own `HUMAN` account through group
membership. The local service-admin account is a sealed fallback listed in the crisis custody list.
**SI-6 Break-glass is an activation.** Canon: DSM-HUM-003 (MUST: break-glass is exceptional, attributable, auditable,
time-bounded where practical); DSM-ROOT-001, -003, -004 (MUST); DSM-ROOT-002, -005 are SHOULD. NetKingdom adds:
`platform-root` holds `SOVEREIGN @ platform` with `activation = BREAK_GLASS` in production, held offline; it
authorizes and does not operate; it is never used for routine work, validation or as a drill's test subject; its
`CONSTRUCT` authority (build mode) is not available in `PRODUCTION`. DSM-ROOT-003 itself requires only that root
credentials not travel through ordinary application delivery.
**SI-7 The developer identity.** Canon: DSM-AP-012 (an anti-pattern: developers routinely holding production
secrets) illustrates the risk; it is not a rule. NetKingdom adds, as its own MUST: `tegwick` holds `CONSTRUCT @
product` in `DEVELOPMENT` and `TEST`, no production credential and no recovery custody item.
**SI-8 Assurance.** NetKingdom rule, using IAM Profile v0.3 assurance claims. Every `HUMAN` account and every
service-admin account requires a second factor where the service can enforce one. A role that grants `ADMINISTER` or
higher MUST bind the assurance claim itself (for example `aal2` in an OpenBao role); relying on an upstream check
needs an exception. Decided 2026-10-24 for OpenBao `platform-admin`.
**SI-9 Every place a credential lives is declared.** Canon: DSM-INV-001 (MUST: maintain an inventory of
security-significant persistent credentials); DSM-INV-002 (SHOULD: attributes; it has no location field) and
DSM-STORE-002 (SHOULD: minimize copies). NetKingdom adds: declaring every storage place, and that a person's
password safe MUST NOT hold a `SERVICE`, `WORKLOAD` or `AGENT` credential.
**SI-10 Owner, rotation and coupling.** Canon: DSM-INV-003, DSM-ROT-001, DSM-ARCH-002 and DSM-TRANS-002 (MUST);
DSM-ROT-003 (MUST NOT consider rotation complete until adopted) is the basis of coupling. NetKingdom adds:
declaring the coupling. Static binds are governed residual secrets (exception SI-X2).
**SI-11 Declared or it is a finding.** Canon: DSM-INV-006 (MUST treat a discovered, unowned production credential as
a finding); DSM-INV-005 and -007 are SHOULD. NetKingdom adds, stricter: every account that exists and is not
declared is `unknown`, and `unknown` is itself a conformance result. A legacy account has a disposition with owner
and date.
**SI-12 Recovery identities are in the crisis custody list.** Canon: DSM-ROOT-001 and -004 (MUST). NetKingdom adds:
each recovery item answers "does it work with SSO and OpenBao down?", and an account that cannot be used while its
own service is down is never the only recovery path.
**SI-13 Validation is operated by the operator identity.** NetKingdom rule. Restore drills and backup validation are
`OPERATE @ backup-system`, run by `admin` with `admin`'s own token as the test subject, and record which identity ran
each stage.
**SI-14 Agents.** Canon: DSM-AGENT-001, -004, -006, -007, -008 (MUST or MUST NOT); DSM-AGENT-002, -003, -005 are
SHOULD; DSM-AP-016 and -017. DSM treats an agent as a kind of workload identity; mapping it to the `AGENT` principal
type is NetKingdom's reading. NetKingdom adds: an agent presents as `AGENT` with its mode (IAM Profile v0.3); its
tokens last at most 60 minutes (3600 seconds, decided 2026-10-24; DSM-AGENT-003 only says "short"); it is bounded by
a named boundary policy; and its **effective authority includes everything reachable through its tools**
(DSM-AGENT-008). **Scope:** the rule can be met in full only for sessions that a harness launches (glas-harness,
GLAS-WP-0018). An interactive Claude Code, Codex or Grok terminal stays at the person's tool-reachable authority
until a session contract exists, and declarations say so (§10).
**SI-15 Accountable automation.** Canon: DSM-AUDIT-001 binds the secrets or capability authority and not every
action of an agent, and DSM-AUDIT-003 is a SHOULD; DSM-DX-002 (SHOULD: reusable integrations) is only the generic
basis for having a golden path. The reconstructability claim and the path below are NetKingdom's, resting on the
companion's section 7 ("every action is reconstructable as the caller's action"). Every state-changing action by an
`AGENT` or `WORKLOAD` is reconstructable: actor, on whose authority, run or session, operation, result and where the
evidence is kept. High-impact actions follow the NetKingdom golden path:
1. **Prepare.** The agent writes source-reviewed tooling and tests it on synthetic fixtures. No live effect.
2. **Admit.** A person authorizes the exact action: a signed admission or a recorded decision.
3. **Execute.** The person, or a bounded executor under the admission, runs it, isolated where possible.
4. **Verify.** An independent verifier checks the receipt against the source it names.
5. **Retain.** The receipt is kept by hash, with no secret values.
Evidence available today: commit trailers (`Assistant`, `Assistant-Session`, `Assistant-Process`), safe receipts
with source hashes, signed admissions, and State Hub progress events. Hub authorship is unauthenticated free text; the
design to attribute it is STATE-WP-0100 (§10).
## 5. Naming
| Convention | Pattern | Notes |
| --- | --- | --- |
| Bind account | `svc-<service>` | one per consuming service; `svc-<service>-<function>` if one needs several |
| Service-admin account | `<short>-admin` | the short form belongs to one service: `pi-admin` (privacyIDEA), `ld-admin` (LLDAP only) |
| Break-glass | `platform-root` | the king credential |
| Human | the person's own login | `admin`, `tegwick` |
| Synthetic sign-in principal | `syn-<check>` | proposed 2026-10-31 (NK-WP-0056-T09): a `SERVICE` that signs in like a person to prove a journey (password and MFA), with no groups or rights and credentials only in OpenBao; never `svc-` (bind accounts must not sign in, SI-3) |
| Workload, service, agent | the token subject form in use | for example `service:secrets-engine`; IAM Profile v0.3 governs the token |
The name `admin` is the human platform administrator's name wherever it appears (decided 2026-10-24); no
service-admin account is called `admin`. A `svc-` or `-admin` name MUST NOT be used for a human account. A
federated role that holds no credential, such as an OpenBao role mapping a group to a policy, is not an account
and is not bound by this rule.
## 6. Declaration
Each service declares every account it holds: name, `principal_type`, purpose, grants (`MODE @ SCOPE` with
activation and environment), operations, where the credential lives, consumers, owner, rotation and coupling,
second-factor requirement, status (`active`, `legacy`, `planned`) and exceptions. The canon's ten questions are
the checklist for a complete entry: who is the actor, on whose authority, what mode, over what scope, which
operations, where the authority originates, how it is activated, which realm enforces it, who keeps the evidence,
and who can override enforcement.
Declarations start in this repository (`tools/service-identity/declarations/`) and move to each service's
repository as its owner confirms them (decided 2026-10-24). A declaration holds no credential and no
secret-shaped value.
## 7. Conformance and the health check
Per account the result is `conformant`, `non-conformant`, `excepted`, `unknown` or `planned`. Unknown is a
finding, not a pass. These match the companion's conformance states: `excepted` is a declared gap,
`non-conformant` is an undeclared violation.
The offline validator decides from a declaration alone: SI-1, SI-2, SI-3, SI-4, SI-6, SI-7, SI-8, SI-9, SI-11,
SI-12, SI-14 and naming; more than one service-admin account per service is an advisory. SI-5, SI-10, SI-13 and
SI-15 are process rules, checked by evidence rather than by declaration. Dates are explicit (`--as-of`).
The health check (T04) is SEC-DSM's automated discovery (DSM-INV-004, a SHOULD) applied to accounts. It compares
declared against observed state from owner-provided read-only metadata and repository manifests. It MUST NOT read a
Secret; that rule is NetKingdom's and is compatible with DSM-AUDIT-002.
Its findings are proposals routed to the owner through Posture Feedback, with an evidence-freshness bound. It
changes nothing.
## 8. Exceptions
Exceptions follow DSM-EXC-001 to DSM-EXC-004: explicit, recorded (rule, account, reason, owner, compensating
control), expiring or revalidated. DSM-EXC-002 accepts an elimination path *or* a review trigger; NetKingdom requires
the **elimination path** (what change ends the exception), which is stricter and consistent with DSM-EXC-004.
DSM-EXC-001 binds violations of a canon MUST; an exception against a rule that is only NetKingdom's is NetKingdom's own.
| Id | Rules | Account | Reason | Elimination path |
| --- | --- | --- | --- | --- |
| SI-X2 | SI-10 | LDAP bind accounts | Static long-lived credential; LDAP has no lease | Governed residual secret (DSM-TRANS-002) until a federated directory lookup exists |
| SI-X3 | naming, SI-11 | `trigger-admin` (privacyIDEA) | Legacy account, consumers uninventoried | NK-WP-0049-T22 deleted the unreferenced Kubernetes copy (2026-10-27); revoking the privacyIDEA account stays under CUST-WP-0073-T05 / NK-WP-0055-T06 |
| SI-X5 | SI-4, SI-5 | privacyIDEA | Only a local administrator is supported | Upstream support for attributed administrators |
| SI-X6 | SI-3 | LLDAP `svc-provisioner` | LLDAP v0.6.2 has no group narrower than `lldap_admin` that can create users or add members (rehearsal 2026-10-28); own account and Secret, but super-admin rights | A narrower LLDAP role, or a provisioner that no longer creates users (founder decision 2026-10-28) |
**Closed:** SI-X1 (LLDAP `admin` as human, super-admin and shared bind of four services) closed 2026-10-31: the
bind split ran live on 2026-10-30 (`verify --old-admin-retired` passed) and the restore drill on the first post-split
generation passed all five stages as `admin` (NK-WP-0054-T05,
[evidence](../../docs/evidence/2026-10-31-identity-restore-drill/drill.json)). SI-X4 (OpenBao `netkingdom-platform-admin` bound the group claim only) closed 2026-10-27: the founder applied the
`aal2` binding (RPF-WP-0065-T04; receipts show `bound_claims` = `net-kingdom-admins` plus `/assurance/level: aal2`, and a
fresh MFA login verified).
**Findings at adoption, not excepted:** F-1, the OpenBao coding-agent role binds `principal_type: service`, so the
agent presents as a service against IAM Profile v0.3 (SI-14). railiance-platform has prepared, in source only,
a transitional role that accepts both `service` and `agent`, plus an exception record with compensating controls,
the removal condition and a review date of 2026-12-31 (RPF-WP-0065). KeyCape must first emit `principal_type: agent`
(KEY-WP-0043), because flipping the role alone would lock the agent out. The account stays non-conformant until the
owner accepts that exception or the issuer and role change land.
## 9. Relationship to other documents
- **SecurityCanon:** vocabulary from Mode of Authority (draft 0.2.0); rules profiled from SEC-DSM 1.0.0. A
disagreement between this profile and the canon is a finding for the canon's owner, not a local override.
- **InfoTechCanon:** owns the identity model and `principal_type`.
- **Security Companion** (`SECURITY-COMPANION.md`, operative form of the security layer model): its §7 rules for
agents ("no standing credential", "tool availability is not permission", "every action is reconstructable as the
caller's action") are the basis of SI-14 and SI-15. Its four conformance states map to §7 here.
- **IAM Profile v0.3:** governs tokens, including the agent principal. KeyCape's confidential clients stay governed
by the profile plus a declaration (settled 2026-10-24).
- **Credential Management v0.2 §6** lists `LLDAP_LDAP_USER_PASS` as a three-way coupling; the inventory found five
consumers. SI-X1 was eliminated on 2026-10-31; that row is now out of date and is a follow-up of NK-WP-0054.
- **Crisis custody list** (`docs/crisis-custody-list.md`) is the recovery companion to SI-9 and SI-12, and the
enumeration of roots that DSM-ROOT-001 asks for.
## 10. Decisions and open items
**Approved by the founder, 2026-10-24, on review of draft-3:** the profile approach (§1, §2); `platform-root` as
`SOVEREIGN @ platform` with `BREAK_GLASS` activation in production and build-mode `CONSTRUCT @ platform` in
`DEVELOPMENT` and `TEST` only; `admin` as `OPERATE @ platform`, `OPERATE @ backup-system` and `ADMINISTER @ directory`
through an admin group (§3). Ratification as v0.1 follows the owner replies named in §0.
**Decided by the founder, 2026-10-24:**
1. OpenBao `platform-admin` binds the `aal2` claim itself (SI-8). The change is railiance-platform's.
2. The human administrator administers through a group on their own account (SI-5); service-admin accounts are
sealed fallbacks.
3. Declarations start here and move to each service repository as owners confirm them (§6).
4. Agents are a distinct principal with SI-14; their token ceiling is 60 minutes.
5. `ld-admin` is for LLDAP only; `admin` stays the human's name on every service.
**Settled with draft-3 (not objected to in review):** KeyCape's confidential clients are governed by IAM Profile v0.3 plus a
declaration; the reserved-name rule applies to credential-holding accounts, not to federated roles.
**Open in the estate, with the plan that eliminates each gap:**
- **Hub authorship is unauthenticated.** Progress events and decisions carry a free-text `author`. For SI-15 they
need an attributable actor and session, as commits already have.
- **Agents act under the person's ambient authority.** There is no per-run agent identity yet (DSM-AGENT-002). Until
there is, each agent session's effective authority is the person's tool-reachable authority, and that is a
declared gap, not conformance.
**Owner replies, 2026-10-24** (all documentary; none applied anything live):
| Owner | Result | Plan |
| --- | --- | --- |
| security-canon | All five mode readings confirmed; citation errors found and corrected in §4, §8 | SECURITY-IN-0001 (ownership wording), SECURITY-IN-0002 (four canon questions) |
| railiance-platform | F-1 and `aal2` prepared in source, rollback written; nothing applied | RPF-WP-0065; the `aal2` live step is the founder's |
| key-cape | Agent tokens are feasible **with changes**, not today. Autonomous agents: `client_credentials` with a new agent client class (2 to 3 days). Delegated agents: RFC 8693 token exchange (1 to 1.5 weeks). Missing: `principal_type` `agent`, the `agent{id,mode}` claim, `act.sub`. Risks: revocation state is in memory; client registrations are static | KEY-WP-0043 |
| glas-harness | Per-session identity and attribution are feasible **only for sessions the harness launches**. Interactive terminals stay outside | GLAS-WP-0018 |
| state-hub | Option A recorded: `X-Actor-*` headers into an append-only attribution table, labelled `claimed`, `unattributed` or `legacy`; real authentication needs key-cape tokens (Option C). **Decided 2026-10-25:** Option A is phase 1 with no `hub_core` schema change; correlation by equal session and run id only (no commit verifier); phase 3 observes tokens and enforcement is a separate later decision; existing authors are not backfilled | STATE-WP-0100 |
**Needs an amendment:** IAM Profile v0.3 asks for assurance evidence for "any delegated human authority" but defines no
nested claim for it. Delegated agent tokens need one; it belongs in the IAM Profile v0.4 amendments.
**Open canon questions** (security-canon, SECURITY-IN-0002): whether break-glass use of production is `PRODUCTION` or
`RECOVERY`; whether one actor may hold `SOVEREIGN` and `CONSTRUCT` separated by environment; whether a sealed offline
credential is a `HUMAN` principal or a credential held by a human; and the ownership wording of `principal_type` and
`Environment`.
**To be rehearsed in isolation, not decided:** introducing `ld-admin` as the configured super-admin and demoting
`admin` (T06); the provisioner's narrowest scope (T05); whether a bind account can be kept from signing in
interactively (SI-3).
## 11. Adoption
Structure first, tooling second. Ratify the vocabulary and rules (the declarations and validator already follow
draft-3); confirm the declarations with their owners; run the health check read-only (T04) and publish the
first report; then eliminate the exceptions one by one, starting with SI-X1 (T05). No exception is closed by
writing a declaration. Each is closed by the live change and an evidence run.

View file

@ -8,7 +8,7 @@ version: "0.1"
created: "2026-08-17"
updated: "2026-08-23"
scope: multi-tenancy-security-framework
revision: "draft-14"
revision: "draft-15"
owner: net-kingdom
last_reviewed: "2026-08-23"
review_interval: 6m

View file

@ -0,0 +1,117 @@
apiVersion: netkingdom.io/playbook-capability/v0.1
kind: PlaybookCapabilityDeclaration
metadata:
id: net-kingdom.keycape-client-registration
name: KeyCape OIDC client registration (live registry)
owner: net-kingdom
repo: net-kingdom
domain: infotech
contract_version: "0.1"
source_links:
- label: Entry point
path: sso-mfa/k8s/keycape/keycape-client.py
- label: Tests
path: sso-mfa/k8s/keycape/test_keycape_client.py
- label: SecurityCaseType
path: security-case-types/oidc-browser-relying-party/type.yaml
spec:
playbook:
path: sso-mfa/k8s/keycape/keycape-client.py
type: cli-sequence
invocation: keycape-client.py apply --client-file <client.yaml> --case-acceptance <record> --expected-uid <uid> --expected-resource-version <rv>
description: >-
Add or remove exactly one client in the live KeyCape client registry
without exporting secret values: contained kubectl, cluster and issuer
pins, byte-preserving merge, compare-and-swap, read-back. Replaces the
per-client helpers beside it. Field rules follow key-cape's canonical model
(KEY-WP-0028). Modes: inspect, dry-run, apply; operations add and remove.
capabilities:
- id: c1.lightweight-sso
tier: C1
resource_kinds:
- identities
- roles_scopes_policies
description: Register one OIDC relying party with the running lightweight issuer.
parameters:
- name: client_id
type: string
required: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
sensitivity: operational
tuning_authority: netkingdom_tunable
description: OAuth2 client_id, unique in the registry.
- name: display_name
type: string
required: true
constraints:
pattern: ^[^\n\r]{1,80}$
sensitivity: public
tuning_authority: netkingdom_tunable
description: Human-readable client name shown at sign-in.
- name: redirect_uris
type: string
required: true
constraints:
pattern: ^https://[^*\s,]+(,https://[^*\s,]+)*$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: Comma-separated exact redirect URIs; wildcards are never accepted.
- name: allowed_scopes
type: string
required: true
constraints:
pattern: ^openid(,(profile|email|groups))*$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: Comma-separated scopes the client may request; openid first.
- name: client_type
type: string
required: true
constraints:
pattern: ^(confidential|public)$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: confidential (server-side, holds a secret) or public (PKCE only).
- name: mfa_required
type: string
required: true
constraints:
pattern: ^(true|false)$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: Pin MFA on this client instead of relying on the provider default.
responsibilities:
- resource_kind: identities
owner: net-kingdom
resources:
- oidc:static-clients
repo_owns: Live registry change for exactly this client, its idempotency and removal; field rules are key-cape's canonical model.
netkingdom_orchestrates: Supply the case's validated, non-secret client fields.
- resource_kind: roles_scopes_policies
owner: key-cape
resources:
- iam-profile:client-scope-allowlists
repo_owns: Enforce exact redirect URIs, allowed scopes and the MFA pin at authorization time.
netkingdom_orchestrates: Choose scopes and MFA posture per SecurityCaseType.
trust:
requires:
- state: runtime_secret_trust
readiness_checks: []
- state: runtime_identity_trust
readiness_checks: []
satisfies:
- state: tenant_onboarding_trust
readiness_checks:
- id: client-authorize-accepts-registered-redirect
description: The authorization endpoint accepts the client with each registered redirect URI.
evidence: GET /authorize with client_id and each redirect_uri returns the login page, not an error.
- id: client-authorize-refuses-other-redirect
description: The authorization endpoint refuses an unregistered redirect URI for the client.
evidence: GET /authorize with an unregistered redirect_uri returns an OAuth error.
catalog:
publish: capabilities/playbooks/net-kingdom.keycape-client-registration.yaml
maturity: implemented
consumers:
- netkingdom-security-scenario-composer
- netkingdom-security-case-types

View file

@ -0,0 +1,135 @@
apiVersion: netkingdom.io/playbook-capability/v0.1
kind: PlaybookCapabilityDeclaration
metadata:
id: net-kingdom.keycape-service-client-registration
name: KeyCape confidential service client registration (live registry)
owner: net-kingdom
repo: net-kingdom
domain: infotech
contract_version: "0.1"
source_links:
- label: Entry point
path: sso-mfa/k8s/keycape/keycape-client.py
- label: Tests
path: sso-mfa/k8s/keycape/test_keycape_client.py
- label: SecurityCaseType
path: security-case-types/oidc-service-client/type.yaml
spec:
playbook:
path: sso-mfa/k8s/keycape/keycape-client.py
type: cli-sequence
invocation: keycape-client.py apply --client-file <client.yaml> --case-acceptance <record> --expected-uid <uid> --expected-resource-version <rv>
description: >-
Add or remove exactly one client_credentials service client in the live
KeyCape client registry, with the same safeguards as browser clients:
contained kubectl, cluster and issuer pins, byte-preserving merge,
compare-and-swap, KeyCape check-config, read-back, and no secret values.
Field rules are key-cape's (src/internal/config/validate.go): confidential,
env secretRef, serviceSubject and tenant required, no redirect URIs, token
lifetime 1m-1h. Replaces per-client scripts such as approval-clients-rollout.py.
capabilities:
- id: c1.lightweight-sso
tier: C1
resource_kinds:
- identities
- roles_scopes_policies
description: Register one confidential service client with the running lightweight issuer.
parameters:
- name: client_id
type: string
required: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
sensitivity: operational
tuning_authority: netkingdom_tunable
description: OAuth2 client_id, unique in the registry.
- name: display_name
type: string
required: true
constraints:
pattern: ^[^\n\r]{1,80}$
sensitivity: public
tuning_authority: netkingdom_tunable
description: Human-readable client name.
- name: audience
type: string
required: true
constraints:
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: The single resource server the tokens are for (aud).
- name: allowed_scopes
type: string
required: true
constraints:
pattern: ^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*(,[a-z][a-z0-9-]*:[a-z][a-z0-9-]*)*$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: Comma-separated resource scopes of the audience; no OIDC identity scopes.
- name: service_subject
type: string
required: true
constraints:
pattern: ^service:[a-z0-9][a-z0-9-]{1,62}$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: The sub KeyCape puts into the client's tokens.
- name: tenant
type: string
required: true
constraints:
pattern: ^tenant:[a-z0-9][a-z0-9-]{1,62}$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: The registration-bound tenant claim.
- name: roles
type: string
required: false
constraints:
pattern: ^[a-z][a-z0-9-]{1,62}(,[a-z][a-z0-9-]{1,62})*$
sensitivity: operational
tuning_authority: netkingdom_tunable
description: Comma-separated roles carried in the token.
- name: token_lifetime
type: string
required: true
constraints:
pattern: ^(5|10|15)m$
sensitivity: security_sensitive
tuning_authority: netkingdom_tunable
description: Access-token lifetime; at most 15m (stricter than KeyCape's 1h ceiling).
responsibilities:
- resource_kind: identities
owner: net-kingdom
resources:
- oidc:static-clients
repo_owns: Live registry change for exactly this client, its idempotency and removal; field rules are key-cape's canonical model.
netkingdom_orchestrates: Supply the case's validated, non-secret client fields.
- resource_kind: roles_scopes_policies
owner: key-cape
resources:
- iam-profile:client-scope-allowlists
repo_owns: Issue client_credentials tokens only for the registered scopes, audience, subject and tenant; refuse excess scopes.
netkingdom_orchestrates: Choose audience, scopes, subject and lifetime per SecurityCaseType.
trust:
requires:
- state: runtime_secret_trust
readiness_checks: []
- state: runtime_identity_trust
readiness_checks: []
satisfies:
- state: tenant_onboarding_trust
readiness_checks:
- id: service-token-claims
description: A client_credentials exchange for each allowed scope returns a token with the registered sub, aud, tenant and lifetime.
evidence: The consuming service's own exchange, reported as claims only (no secret or token value).
- id: service-token-refuses-excess-scope
description: A client_credentials exchange for a scope outside allowed_scopes is refused.
evidence: KeyCape returns invalid_scope.
catalog:
publish: capabilities/playbooks/net-kingdom.keycape-service-client-registration.yaml
maturity: implemented
consumers:
- netkingdom-security-scenario-composer
- netkingdom-security-case-types

View file

@ -1,5 +1,14 @@
# NetKingdom Runtime Architecture
> Lifecycle update, 2026-10-14: the lifecycle tooling descriptions below are a
> historical snapshot, not current execution instructions. Account creation,
> pattern deletion and inferred-claims verification in the old 0019 helpers are
> retired. Their secret handling and success assertions are not accepted evidence
> of current safety or readiness. Use the [current owner routes](legacy-lldap-lifecycle-retirement.md)
> and actual native receipts. Ordinary lifecycle delivery has since advanced in
> NK-WP-0025/NK-WP-0038; independent recovery and other IAM callers remain open.
**Status:** draft (initial capture for NET-WP-0018-T02)
**Date:** 2026-06-03
**Context:** Documents the *as-deployed* runtime after the first successful bootstrap (0015-0017) + T06-adjacent polish (0019). Not an idealized future architecture. Specific enough to guide a scratch rebuild or rehearsal without rediscovering integration details. Incorporates pragmatic audit paths and known UE integration points/gaps per the persisted assessment.

View file

@ -0,0 +1,65 @@
---
id: NK-ADR-0018
type: architecture-decision-record
title: "The access-engine Core Is the Production PDP; Topaz Is Deferred"
status: accepted
owner: net-kingdom
revision: "1"
decided: "2026-10-27"
last_reviewed: "2026-10-27"
review_interval: 12m
amends: [NK-ADR-0006]
---
# ADR-0018 - access-engine Core as Production PDP
**Status:** Accepted by Bernd Worsch on 2026-10-27, as hub decision `37464824-a939-415f-8a7a-5f81cbf498c8`
(NK-WP-0055-T10).
**Date:** 2026-10-27
**Decider:** Bernd Worsch
## Context
ADR-0006 and `docs/platform-identity-security-architecture.md` name Topaz as the first delegated policy decision point
(PDP) behind flex-auth, now access-engine. In practice Topaz exists only as an adapter shape (FLEX-WP-0004), and nothing
deploys it. Every live decision comes from the access-engine Go core.
The core already provides:
- tenant scope;
- relationship facts and inherited access;
- check, list-allowed and explain;
- caller authentication.
The core was shaped to Topaz's policy-plus-directory model from the start. The production readiness ledger (NK-WP-0055-T01)
lists the Topaz runtime as an open area.
## Decision
- The access-engine Go core is the production PDP. A delegated Topaz runtime is not required for production readiness, and
that readiness area is an accepted exception.
- The PDP readiness checks still apply, but they are now run against the core:
- health;
- data freshness;
- policy load status;
- fail-closed behaviour, verified per consumer through each consumer's `pep-stance.yaml`.
- The policy import gap that Topaz bundles would have closed moves to NK-WP-0055-T14. Either policies load without an image
rebuild, or rebuild-and-deploy is accepted as the import path.
- Topaz is no longer an expected audit source (NK-WP-0055-T11).
- Topaz is deferred, not rejected. ADR-0006's rule still holds for any future delegated PDP: it evaluates policy, and it
never becomes the canonical policy model, the identity provider or the control plane.
## Revisit when
- a tenant needs per-object sharing, or relationship data outgrows the core's relationship facts;
- the number of tenants or the depth of group nesting makes core evaluation a bottleneck;
- policy changes become too frequent for the T14 import path;
- the production declaration (NK-WP-0055-T09) reviews its accepted exceptions.
## Consequences
- There is one less runtime to operate, back up, audit and drill.
- Authorization availability is coupled to access-engine. HA is out of scope by the same decision.
- Reversibility is high: adding Topaz later extends the system rather than rebuilding it.
- Where `docs/platform-identity-security-architecture.md` describes Topaz as the first delegated PDP, read it with this
amendment.

View file

@ -3,7 +3,7 @@ id: net-kingdom-architecture
title: "NetKingdom architecture"
status: proposed
owner: net-kingdom
revision: "draft-3"
revision: "draft-4"
version: "0.1"
last_reviewed: "2026-08-31"
review_interval: 6m

View file

@ -1,4 +1,4 @@
# Attended procedure inventory — 2026-09-05
# Attended procedure inventory — 2026-09-05 baseline, reviewed 2026-10-15
Reviewed all 32 shell scripts under `sso-mfa/k8s/` for NK-WP-0034-T03.
Exercise status: unknown unless the row states otherwise. Existing deployment
@ -11,42 +11,41 @@ runbook or a dedicated procedure. Missing runbooks are listed separately.
| Script (under `sso-mfa/k8s/`) | Runbook | Exercise status |
| --- | --- | --- |
| `authelia/create-secrets.sh` | [README.md](../sso-mfa/k8s/authelia/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `keycape/configure-openbao-oidc.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | unknown |
| `keycape/create-pi-token.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | unknown |
| `keycape/configure-openbao-oidc.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | retired 2026-10-15 before token/cluster access; Platform governed owner operation replaces the root-token pod prompt |
| `keycape/create-pi-token.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | retired; current factor reader is Platform-owned RPF-WP-0040; historical Secret/provider revocation remains open |
| `keycape/create-secrets.sh` | [README.md](../sso-mfa/k8s/keycape/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `keycape/patch-openbao-client.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | unknown |
| `keycape/refresh-pi-token-live.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | unknown |
| `keycape/register-coulomb-social.sh` | None found | unknown |
| `keycape/patch-openbao-client.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | retired 2026-10-15; use reviewed guarded registration/rollout adapters |
| `keycape/refresh-pi-token-live.sh` | [README.md](../sso-mfa/k8s/keycape/README.md) | retired; no personal-admin token refresh or live acceptance claimed |
| `keycape/register-coulomb-social.sh` | None found | retired 2026-10-15; raw registry replacement/output removed |
| `keycape/verify-openbao-client.sh` | [README.md](../sso-mfa/k8s/keycape/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `lldap/bootstrap-users.sh` | [README.md](../sso-mfa/k8s/lldap/README.md), [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md) | unknown |
| `lldap/break-glass.sh` | [platform-root-custody.md](../docs/platform-root-custody.md) | unknown |
| `lldap/bootstrap-users.sh` | [README.md](../sso-mfa/k8s/lldap/README.md), [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md) | source + isolated native fixture verified 2026-10-14; estate exercise still unknown; [procedure](../sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md) |
| `lldap/break-glass.sh` | [platform-root-custody.md](../docs/platform-root-custody.md) | retired 2026-10-14; [owner routes](legacy-lldap-lifecycle-retirement.md) |
| `lldap/create-secrets.sh` | [README.md](../sso-mfa/k8s/lldap/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `lldap/create-user.sh` | [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `lldap/dry-run-nonroot-user.sh` | [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `lldap/manage-group-members.sh` | [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md) | unknown |
| `lldap/create-user.sh` | [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | retired 2026-10-14; [owner routes](legacy-lldap-lifecycle-retirement.md) |
| `lldap/dry-run-nonroot-user.sh` | [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | retired 2026-10-14; [owner routes](legacy-lldap-lifecycle-retirement.md) |
| `lldap/manage-group-members.sh` | [OPERATOR-GROUPS.md](../sso-mfa/k8s/lldap/OPERATOR-GROUPS.md) | source + isolated native fixture verified 2026-10-14; estate exercise still unknown; [procedure](../sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md) |
| `postgresql/create-secrets.sh` | [README.md](../sso-mfa/k8s/postgresql/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `privacyidea/bootstrap-admin.sh` | [README.md](../sso-mfa/k8s/privacyidea/README.md) | unknown |
| `privacyidea/bootstrap-realm.sh` | [keycape-exposure-resolver-reconciliation.md](../docs/keycape-exposure-resolver-reconciliation.md) | unknown |
| `privacyidea/check-user-mfa-state.sh` | [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `privacyidea/bootstrap-realm.sh` | [privacyIDEA repair contract](../sso-mfa/k8s/privacyidea/README.md#configuration-preserving-realm-repair) | source repaired: literal private files, preview/explicit apply, configuration preservation; live acceptance pending |
| `privacyidea/check-user-mfa-state.sh` | [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | source repaired: explicit admitted private JWT, scoped read-only counts; live diagnostic delivery not admitted by this repair |
| `privacyidea/create-secrets.sh` | [README.md](../sso-mfa/k8s/privacyidea/README.md), [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md) | unknown |
| `privacyidea/enckey-bootstrap.sh` | [README.md](../sso-mfa/k8s/privacyidea/README.md) | unknown |
| `privacyidea/reconcile-lldap-resolver-live.sh` | [keycape-exposure-resolver-reconciliation.md](../docs/keycape-exposure-resolver-reconciliation.md) | attempted by Bernd Worsch 2026-08-27; no completed PASS receipt |
| `privacyidea/repair-realm-live.sh` | [smooth-bootstrap-guide.md](../docs/smooth-bootstrap-guide.md), [keycape-exposure-resolver-reconciliation.md](../docs/keycape-exposure-resolver-reconciliation.md), [verify-t06.md](../docs/verify-t06.md) | unknown |
| `privacyidea/update-lldap-resolver-live.sh` | None found | unknown |
| `user-engine/verify-operability.sh` | None found | unknown |
| `verify-identity-cutover-dependencies.sh` | None found | unknown |
| `privacyidea/enckey-bootstrap.sh` | [README.md](../sso-mfa/k8s/privacyidea/README.md) | source/isolated native recovery adapter verified 2026-10-13; current estate recovery remains NK-WP-0049-T03 |
| `privacyidea/reconcile-lldap-resolver-live.sh` | [keycape-exposure-resolver-reconciliation.md](../docs/keycape-exposure-resolver-reconciliation.md) | historical check 2026-09-23 with predecessor unproven; current core exercised 2026-10-14 on disposable PI/LLDAP, full production procedure unexercised |
| `privacyidea/repair-realm-live.sh` | [privacyIDEA repair contract](../sso-mfa/k8s/privacyidea/README.md#configuration-preserving-realm-repair), [verify-t06.md](../docs/verify-t06.md) | source repaired: attended in-memory input and propagated functional proof; live acceptance pending |
| `privacyidea/update-lldap-resolver-live.sh` | [keycape-exposure-resolver-reconciliation.md](keycape-exposure-resolver-reconciliation.md) | core exercised 2026-10-14 on disposable PI/LLDAP; production unexercised |
| `user-engine/verify-operability.sh` | None found | retired 2026-10-15; use rapp-user-engine verification; no stale manifest rollback |
| `verify-identity-cutover-dependencies.sh` | None found | read-only source review; tracing disabled and private staging 2026-10-15; no new native exercise claim |
| `verify-t02.sh` | [README.md](../sso-mfa/k8s/README.md) | unknown |
| `verify-t03.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
| `verify-t04.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
| `verify-t05.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
| `verify-t06.sh` | [verify-t06.md](../docs/verify-t06.md) | unexercised on provider; HTTP simulation passed |
| `verify-t07.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
| `verify-t07.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | source + isolated native fixture verified 2026-10-14; estate exercise still unknown; [procedure](../sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md) |
| `verify-t08.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
## Scripts without a matching runbook
- `sso-mfa/k8s/keycape/register-coulomb-social.sh` — header usage is available, but a runbook with prerequisites, verification and exercise history was not found in the reviewed runbook set.
- `sso-mfa/k8s/privacyidea/update-lldap-resolver-live.sh` — header usage is available, but a runbook with prerequisites, verification and exercise history was not found in the reviewed runbook set.
- `sso-mfa/k8s/user-engine/verify-operability.sh` — header usage is available, but a runbook with prerequisites, verification and exercise history was not found in the reviewed runbook set.
- `sso-mfa/k8s/verify-identity-cutover-dependencies.sh` — header usage is available, but a runbook with prerequisites, verification and exercise history was not found in the reviewed runbook set.
@ -58,3 +57,31 @@ header now says unexercised to successful completion. Existing incident
authority/window requirements still apply; this documentation review does not
authorize that attended cutover. The changed helper must be included in the
revision recorded by the operator.
## Bootstrap/root-output caller review — 2026-10-15
The original 32-shell-script snapshot above is historical and does not cover the
subsequently added Python adapters or bootstrap/Make callers. The current exact
scope, source digests and per-entrypoint dispositions are recorded in
[the caller source inventory](evidence/2026-10-15-bootstrap-recovery/caller-source-inventory.json).
It enumerates all current non-test shell/Python files under bootstrap and k8s,
plus Make, the console, legacy delivery copier and isolated transport checker.
Enumeration is complete for that declared scope; it is not a claim that every
helper has current production acceptance or that no future defect can exist.
| Caller group | Current source boundary | Native acceptance |
| --- | --- | --- |
| Generate/encrypt/decrypt/bundle | Public recipient for encryption; explicit private temporary key for decryption; private files; fresh no-overwrite bundle output; no age private key in bundle; no plaintext tar intermediate | Disposable actual age checks; exact off-host artifact/current coverage pending |
| SOPS setup/read/edit/encrypt/rotate/unlock | Existing private-input and recipient checks; no automatic permanent HOME key installation | Existing source/disposable receipts; independent recovery pending |
| Component create/apply/rotation | Literal private input, create-or-match or explicit named field update; no credential argv; contained errors; unknown writes require reconciliation | Current multi-consumer recovery/revocation remains owner work |
| Bootstrap orchestration | Attended entry; no automatic erase/regeneration of retained service custody; failed OpenBao verification stops before final cleanup; encrypted handoff is not proof of recovery | No production bootstrap executed in this review |
| Lab OpenBao init/unseal | Explicit lab model, admitted kubeconfig and exact cluster UID; private no-overwrite init receipt; shares stdin only; no automatic post-init root configuration | Captured fake-cluster transport tests only; historical lab receipt does not cover new revision |
| Legacy User Engine delivery copier | Retired; scoped email ESO delivery remains authoritative; audit delivery replacement needs owner custody path; existing Secrets untouched | No provider token copied, rotated or revoked |
| Legacy registry patchers/root prompt | Retired; raw Python registry patch output disabled; guarded owner adapters remain available | Existing registry/clients unchanged |
| Secret presence diagnostic | Current scoped factor Secret replaces old personal-token expectation; historical applied/native state never rewritten from presence | Presence only |
| Dependency/client verification | No tracing of decoded config; private staging; suppressed provider response/port-forward diagnostics | No new native acceptance claimed |
| Legacy managed workload rollback | Retired; current rapp-user-engine release/verification contract is authoritative | No workload rollback or reapply performed |
The [recovery ceremony](bootstrap-recovery-ceremony.md) records exact outstanding
owner evidence. Retired callers are not evidence of provider revocation. A
successful local backup job is not evidence of an off-host matching key/data set.

View file

@ -17,9 +17,11 @@ The first execution, on 2026-08-27, found four defects:
2. `GET /user/` parsed as a dict when privacyIDEA returns a list — an
`AttributeError` outside the `except` clause, so the run died as a traceback
rather than a receipt.
3. Resolver writes omitting `TIMEOUT`, `CACHE_TIMEOUT`, `SIZELIMIT` — a resolver
write replaces the whole object, so each run silently un-repaired a resolver
an operator had just fixed by hand.
3. Missing resolver tuning (`TIMEOUT`, `CACHE_TIMEOUT`, `SIZELIMIT`) was
historically attributed to whole-object replacement. Correction, 2026-10-14:
native privacyIDEA 3.12.2 preserves omitted fields; sending explicit defaults
can overwrite custom tuning. The maintained cutover helper updates only the
bind password and verifies preservation. See the current resolver runbook.
4. No way to declare an unavailable predecessor — the prompt could not be left
empty, so an operator had to type a placeholder, which also fails the bind
and was recorded as a **passing** denial proof.

View file

@ -0,0 +1,450 @@
---
title: "audit-core intake packages: Authelia, LLDAP, privacyIDEA"
status: draft
owner: net-kingdom
work: NK-WP-0055-T11
answers: audit-core AUDIT-WP-0017-T04 (three of the four identity providers; KeyCape is separate)
contract: canon/standards/audit-correlation_v0.1.md (accepted)
created: "2026-10-27"
---
# audit-core intake packages: Authelia, LLDAP, privacyIDEA (draft)
This document gives audit-core the intake package for each of the three identity providers that NetKingdom deploys:
- Authelia (`sso-mfa/k8s/authelia`);
- LLDAP (`sso-mfa/k8s/lldap`);
- privacyIDEA (`sso-mfa/k8s/privacyidea`).
It follows the common package in audit-core `AUDIT-WP-0017` ("Common intake package for every new sender") and
the wire contract in audit-core `docs/event-envelope.md`. KeyCape's package belongs to key-cape and is not covered here.
**Status.** Draft for audit-core review. It plans work only:
- it activates no sender;
- it mints no credential;
- it makes no live change.
**How each claim is marked:**
- **[repo]**: read from this repository's manifests.
- **[upstream]**: from upstream product behaviour as we know it. Not yet verified against the pinned image.
- **[estimate]**: a volume figure that must be measured before audit-core sets thresholds.
- **[unknown]**: an open question.
## 1. Choices that apply to all three providers
**Separate sources.** Each provider gets its own `source`, as AUDIT-WP-0017-T04 asks, so that silence in one of them
can be detected:
- `netkingdom.authelia`
- `netkingdom.lldap`
- `netkingdom.privacyidea`
**Owner.** The owning repo for all three sources is `net-kingdom`. ADR-0015 moves runtime packaging to `rapp-*`
repositories. If that move happens, the source name stays the same and only the owning repo changes.
**Tenant policy.** All three providers are platform-scoped: there is a single realm and directory, `dc=netkingdom,dc=local`.
- The envelope requires `tenant` to be a non-empty value.
- Proposal: use the fixed value `platform` for every event, and give the sender credential only `platform` as a permitted tenant.
- [unknown] Is `platform` the spelling audit-core uses for platform-scoped sources? If not, use audit-core's spelling.
**Shipper.** One small collector per provider: the *intake shipper*.
- It runs in the provider's namespace, either as a sidecar or as a CronJob.
- Each shipper has its own sender credential.
- It normalises native records into the envelope and posts them to `POST /v1/events`.
- On retry it reuses the same `id`, so delivery is idempotent.
**Envelope mapping:**
| Envelope field | Mapping rule |
|---|---|
| `id` and `Idempotency-Key` | A deterministic hash of the provider name and a stable native key (defined per provider below). Retries and re-reads after a restart produce the same id. |
| `type` | One value from the provider's action list below. The receiver stores it as `action`. |
| `subject` | The affected object as a **pseudonymous** reference, e.g. `user:<pseudonym>`, `group:<cn>` or `token:<serial>`. Never a raw username or email address. |
| `occurred_at` | The provider's native event time as RFC 3339 with an explicit `Z`. All three providers run in UTC. |
| `correlation_id` | The 32-hex trace id (AC-1) if a valid `traceparent` was seen. Otherwise a newly generated trace id, with `data.correlation_origin: generated` (AC-4). |
| `data` | Holds the outcome, the actor (pseudonymised), the native record id, `correlation_origin`, and the fields listed per provider. |
**Placement of `actor` and `outcome`.** The receiver fixes `actor=null` and `outcome=recorded` at ingest. The real
actor and outcome therefore go inside `data`:
- `data.outcome` is one of `success`, `failure` or `refused`.
- `data.actor` is the acting principal.
**Pseudonymisation of personal data.** The rules are the same for all three providers.
- **Usernames, emails and display names:**
- Replace them with a keyed pseudonym before sending: HMAC-SHA256 truncated to 32 hex characters.
- The key is a NetKingdom-held value in OpenBao and is never sent to audit-core.
- This keeps records from the three providers joinable on the same user without exposing the name.
- Raw names must never appear in `subject` or `id`.
- [unknown] Whether audit-core prefers its own per-subject encrypted personal payload (`docs/erasure-and-audit.md`)
instead. If so, the raw name may travel in that payload, and our pseudonym becomes only the join key.
- **Synthetic, test and break-glass accounts:** Service and break-glass account names (`admin`, the KeyCape bind
user, `identity-provisioner`) are operational identifiers, not personal data. They may be sent in clear. This is
what lets break-glass use stand out.
- **Client IP addresses:** Personal data. Send them as a truncated prefix (IPv4 /24, IPv6 /48) plus a keyed
pseudonym of the full address. Ban and regulation events need the full-address pseudonym for correlation.
[unknown] Whether security operations need the clear IP for 30 days. That would need an owner decision under a
separate retention period.
- **Never sent:**
- passwords and password attempts;
- OTP values;
- session cookies;
- JWTs;
- OIDC codes and tokens;
- client secrets;
- enrollment seeds and QR payloads;
- LDAP bind credentials.
The receiver's `secret_policy` is the backstop for these, not the control.
**Credential.** One sender credential per source:
- it is minted through the attended OpenBao path that `warden route find "audit-core sender credential"` points to;
- it is delivered to the shipper as a mounted file;
- audit-core never holds or prints it;
- `may_read` is false.
**Retention.** The default applies: a 30-day query window plus archive. privacyIDEA records are the exception
(section 4.5).
**Heartbeat.** Each shipper emits `type: source.heartbeat` every 15 minutes, even when there are no events. The
heartbeat's `data` carries:
- the shipper's cursor (last native id or log offset);
- the count of records read and sent since the last heartbeat.
A heartbeat with a stalled cursor while the provider is serving traffic is the completeness signal (audit-core
`docs/stream-completeness.md`).
## 2. Authelia: `netkingdom.authelia`
### 2.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace `sso`, Deployment `authelia`, selector `app.kubernetes.io/name=authelia`, 1 replica [repo] |
| Image | `authelia/authelia` 4.39.28, pinned by digest [repo] |
| Emitting identity | A shipper ServiceAccount `audit-shipper-authelia` in `sso`. Proposed; it does not exist yet. |
| Role | First-factor login (Authelia checks the password against LLDAP), forward-auth for `activity`/`temporal`, and the OIDC upstream for client `keycape` [repo] |
| Exposure | Behind the Traefik Ingress at `auth.coulomb.social` and `login.coulomb.social` [repo] |
### 2.2 Where the events are
**1. Structured logs.**
- `log.level: info`. No `log.format` is set, so output is text [repo].
- Proposal: set `log.format: json`. The schema is unchanged, and this is a small config change.
- At info level, Authelia logs these events [upstream]:
- unsuccessful first-factor and second-factor attempts, with the username and remote IP;
- regulation bans;
- authorization denials;
- OIDC authorize and token errors.
- Successful logins are not reliably logged at info [upstream].
**2. The `authentication_logs` table in Authelia storage.**
- Storage here is SQLite at `/var/authelia/data/db.sqlite3` on the `authelia` PVC [repo].
- Each row records [upstream]:
- one first-factor or second-factor attempt, success or failure;
- username, `auth_type`, `remote_ip`, `request_uri` and `request_method`;
- a monotonic `id`.
- Since 4.38, the `banned_user` and `banned_ip` tables also exist [upstream].
- This is the **authoritative** source for login outcomes. Logs supplement it for OIDC and authorization events.
### 2.3 Collection
Recommended: a **sidecar** in the `authelia` pod with two read-only inputs.
**(a) The SQLite file, read-only:**
- The sidecar polls `authentication_logs` and the ban tables by `id`, with a cursor kept on a small shipper PVC.
- SQLite's WAL mode allows a concurrent read-only reader [upstream].
- The sidecar opens the database with `mode=ro` and never writes to it.
**(b) The container stdout, with `log.format: json`:**
- The sidecar reads it through a shared `emptyDir` log file. This needs a `--log.file_path` setting [upstream].
- Alternative: a node-level collector, if railiance-cluster provides one. [unknown]
**Native key for `id`:**
- Table rows: `authelia|authentication_logs|<id>`.
- Log lines: `authelia|log|<sha256 of the raw JSON line>`.
**Constraint.** Storage is a single-writer SQLite file on an RWO PVC, so the sidecar must run in the same pod.
[unknown] Whether a planned move of Authelia storage to PostgreSQL would replace (a) with a DB reader.
### 2.4 Correlation
- Browser traffic arrives through Traefik. Today Authelia neither reads nor logs a `traceparent`, and it does not
propagate one [upstream].
- When KeyCape redirects the user to Authelia, that is a browser navigation, so any trace context is lost.
- **Default:** `correlation_origin: generated`. The shipper creates one trace id per record.
- **Improvement, needs work by railiance-cluster:**
- Enable the Traefik access log in JSON with the `traceparent` request header kept. Header names only; never `Cookie` or `Authorization`.
- Join on time, router and client IP to recover propagated ids for OIDC back-channel calls from KeyCape: `/api/oidc/token` and `/api/oidc/userinfo`.
- Only server-to-server calls can carry an inbound `traceparent`.
- [unknown] Whether Traefik tracing or header logging is enabled on railiance01.
- **Same login, two records:** The OIDC `authorize` → `token` exchange is the hop KeyCape shares with Authelia.
- The KeyCape issuance record holds the propagated id.
- The Authelia record for the same login can carry it only if KeyCape sends `traceparent` on the token call **and** Traefik logs it.
### 2.5 Action list (scope rows)
| `type` | From | `data` beyond the common fields |
|---|---|---|
| `auth.first_factor.success` / `.failure` | `authentication_logs` (1FA) | `auth_method: password`, `request_host`, `ip_prefix`, `ip_pseudonym` |
| `auth.second_factor.success` / `.failure` | `authentication_logs` (2FA rows; TOTP/WebAuthn/Duo if ever enabled) | `auth_type` |
| `regulation.user_banned` | Ban table or log | `ban_until`, `failed_count` |
| `regulation.ip_banned` | Ban table (4.38+) | `ban_until` |
| `regulation.ban_lifted` | Ban table `revoked` / expiry | `by` (admin or expiry) |
| `access.denied` | Log, authz deny | `request_host` (e.g. `activity.coulomb.social`), `rule` |
| `oidc.authorize.success` / `.failure` | Log | `client_id` (`keycape`), `scopes`, `error` |
| `oidc.token.issued` / `.failure` | Log | `client_id`, `grant_type`. **No token values.** |
| `oidc.consent.granted` | Log (implicit consent today) | `client_id` |
| `identity.reset_password.requested` / `.completed` | Log, notifier | (Password reset is via LLDAP/Authelia. [unknown] Whether it is enabled.) |
| `source.heartbeat` | Shipper | cursor, counts |
### 2.6 Volume, cadence and retention
- **Volume** [estimate]: a few hundred authentication rows per day across operators, demo users and probes. Peak
under 5 events/s, except during a brute-force attempt.
- **Brute force:** Regulation caps each username at 5 failures per 2 minutes. IP-spread attacks are not capped.
Proposed shipper rate limit: 50 events/s, with overflow counted in the heartbeat. Overflow is never silently dropped.
- **Heartbeat:** every 15 minutes.
- **Allowed silence:** 30 minutes of shipper silence. Gaps between authentication events are normal at night.
- **Retention:** default. [unknown] Whether 90 days is wanted for ban events.
## 3. LLDAP: `netkingdom.lldap`
### 3.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace `sso`, Deployment `lldap`, 1 replica. Ports: LDAP 3890, HTTP/GraphQL 17170 [repo] |
| Image | `lldap/lldap:stable`. **Not pinned** [repo]. Pinning it is a prerequisite for a stable log format. |
| Storage | Default SQLite on the `lldap` PVC. No `LLDAP_DATABASE_URL` is set [repo]. |
| Emitting identity | Shipper ServiceAccount `audit-shipper-lldap` in `sso` (proposed) |
| LDAP clients | Authelia and KeyCape bind as `uid=admin` [repo, Authelia config]. Admin automation: `lldap_admin.py`, `identity-provisioner-reconcile.py`, `break-glass.sh` [repo] |
### 3.2 Where the events are
- **Logs.** LLDAP has no audit table. Its only audit surface is the `tracing` log on stdout [upstream].
- **At default verbosity, LLDAP logs [upstream]:**
- LDAP bind attempts with the bind DN and success or failure;
- GraphQL mutations, i.e. admin changes:
- create, update and delete user;
- create and delete group;
- add and remove a member;
- password change through the OPAQUE flow.
- **Field coverage at default verbosity** [unknown]:
- Which fields appear without `verbose=true` must be verified against the pinned image.
- `verbose=true` logs too much, including search filters that contain usernames.
- **Logins to the web UI** (`/auth/opaque/login/*`, `/auth/simple/login`) are logged with the username [upstream].
### 3.3 Collection
- Recommended: a log **sidecar**, or a node log collector, reading the `lldap` container's stdout.
- **Parsing:**
- LLDAP logs are text spans, not JSON [upstream].
- The shipper keeps a strict per-version parser and an allowlist of message patterns.
- A line it does not recognise is counted in the heartbeat and never guessed.
- **Native key for `id`:** `lldap|<pod uid>|<container restart count>|<line sequence number>`. This stays stable
when the shipper re-reads after its own restart. [unknown] Whether the collector exposes a stable offset. If not,
use a sha256 of pod uid, timestamp and raw line.
- **Cross-check through admin tooling:**
- The repo's admin tools (`lldap_admin.py`, `manage-group-members.sh`, `create-user.sh`) could emit their own
events, as the actor's view of the change.
- Proposal: not in this first package. Rely on the server log so that changes made outside the tools are captured too.
### 3.4 Correlation
- **LDAP (3890):** There are no headers, so `correlation_origin: generated` always (AC-2). The calling component
(Authelia or KeyCape) records its own inbound id. LLDAP records only its own.
- **HTTP/GraphQL (17170)** is behind Traefik (`ingress.yaml`). LLDAP does not log `traceparent` [upstream]. The same
Traefik access-log join as for Authelia could recover it for admin API calls. [unknown]
- **Joining LDAP records:** A bind from Authelia happens within milliseconds of an Authelia `auth.first_factor.*`
record for the same user pseudonym. audit-core may record a `parent_event_id` link only if the shipper can make it
deterministically. Proposal: do not; a time-and-pseudonym join is analysis, not correlation.
### 3.5 Action list
| `type` | Notes |
|---|---|
| `ldap.bind.success` / `.failure` | `bind_dn` (service DN in clear; a user DN is pseudonymised), `client_ip_prefix`. Failures are the main signal. |
| `ldap.bind.service` | Optional. A successful bind by the `uid=admin` service account, which is expected high-volume noise. Proposal: count these in the heartbeat instead of sending one event per bind. |
| `user.created` / `user.updated` / `user.deleted` | `actor` (admin principal), changed attribute **names** only |
| `user.password_changed` | `actor`, `self` true/false. No password material. |
| `group.created` / `group.deleted` | `group` cn in clear (group names are operational) |
| `group.member_added` / `group.member_removed` | `group`, member pseudonym, `actor` |
| `admin.login.success` / `.failure` | Web UI or API login |
| `source.heartbeat` | Includes `service_bind_count` and `unparsed_line_count` |
### 3.6 Volume, cadence and retention
- **Volume** [estimate]:
- Service binds: from hundreds up to a few thousand per day. They follow Authelia logins and KeyCape lookups, and
appear only as counts.
- Events actually sent: under 200 per day.
- Admin changes: a handful per week, with bursts during reconciliation runs (`identity-provisioner-reconcile.py`).
- **Peak:** under 5 events/s.
- **Heartbeat:** every 15 minutes.
- **Allowed silence:** 30 minutes.
- **Retention:** default. Group-membership changes are authorization-relevant. [unknown] Whether audit-core should
keep them for longer.
## 4. privacyIDEA: `netkingdom.privacyidea`
### 4.1 Registration
| Item | Value |
|---|---|
| Deployment | Namespace `mfa`, Deployment `privacyidea`, 1 replica [repo] |
| Image | `ghcr.io/gpappsoft/privacyidea-docker:3.12.2` [repo] |
| Database | PostgreSQL cluster `net-kingdom-pg`, namespace `databases`. Audit uses the default SQL audit module [repo, README; `PI_AUDIT_MODULE` not set, so the default `sqlaudit` applies, upstream] |
| Audit signing | `PI_AUDIT_KEY_PRIVATE` / `PI_AUDIT_KEY_PUBLIC` at `/etc/privacyidea/{private,public}.pem`, from Secret `privacyidea-auditkeys` [repo] |
| Callers | KeyCape (MFA challenge on `/authorize/callback`), NetKingdom admin scripts (`pi_api.py`, `check-user-mfa-state.sh`, onboarding guard) [repo] |
| Emitting identity | Shipper ServiceAccount `audit-shipper-privacyidea`, with a **read-only** PostgreSQL role on the audit table only (proposed) |
### 4.2 Where the events are
**The table.** The signed audit table `pidea_audit` in PostgreSQL [upstream schema, 3.x]. Each request writes one
row with these columns:
- `id`, `date`, `startdate`, `duration`
- `action` (e.g. `POST /validate/check`), `success`, `action_detail`, `info`
- `serial`, `token_type`
- `user`, `realm`, `resolver`, `administrator`
- `client` (IP), `privacyidea_server`
- `policies`, `container_serial`
- `signature`
**The signature.** Each row is signed with the audit private key.
**Proof of the signed history (NK-WP-0049-T20, 2026-10-16):**
- All 279,209 stored rows verified against the retained original audit key pair. None were unsigned, invalid or legacy.
- After restore, a new entry was signed with the preserved identity.
- Tampered rows and a wrong key were refused.
**Consequence.** The audit key pair is a retained identity:
- It must never be regenerated to repair anything (`KEY-RECOVERY.md`).
- The public key is the verification anchor that audit-core needs.
**The application log** at `/var/log/privacyidea/privacyidea.log` (`PI_LOGLEVEL=20`) [repo] duplicates part of this
and is not signed. Out of scope.
### 4.3 Collection: database export with the signature preserved
**Export method.** An exporter (CronJob or sidecar) reads the table:
- `SELECT ... FROM pidea_audit WHERE id > :cursor ORDER BY id LIMIT 500`
- It runs under a dedicated read-only role.
- The cursor is kept on the shipper side and echoed in the heartbeat.
**What each row becomes:**
- `id` = sha256 of `privacyidea|pidea_audit|<id>|<signature>`.
- The signature is included so that a restored database with re-used ids cannot collide silently with earlier events.
- [unknown] Whether a restore can roll ids back. T20 restored into an isolated target, not production.
- `data.native` holds **every signed column, unchanged**, plus `signature`.
- `data.native_signature_alg` holds the privacyIDEA version and audit module.
- `data.verification_key_fingerprint` holds the SHA-256 of the public key.
**Personal data in the signed row.** The `user`, `client`, `administrator` and `info` columns can contain usernames and IPs.
- Pseudonymising them in `data.native` would break the signature.
- **Proposed resolution:** send them in audit-core's per-subject encrypted personal payload, if one exists (`docs/erasure-and-audit.md`), so the row stays verifiable.
- Use pseudonymous copies in the plain `data`, `subject` and `actor` fields.
- **Owner decision needed:** if audit-core cannot hold an encrypted personal payload, we must choose one of:
- (a) keep raw columns in the signed payload and accept them under audit-core's PII handling; or
- (b) send only pseudonymised fields plus the signature and the row hash. Verification would then be possible only
against the source database while it is retained.
**Verification by audit-core** (proposal):
- Store the public key once as source metadata.
- The shipper verifies each row before sending. A failed row is sent as `type: audit.signature_invalid` with the row
id and never dropped.
- audit-core can re-verify a sample.
- [unknown] Whether privacyIDEA's signed-string format is stable across 3.12.x. Pin the exporter to the image version.
**Not used: privacyIDEA's logger or container audit module** (`PI_AUDIT_MODULE` set to a logger-based module) [upstream].
- It would stream audit records to a log handler.
- Its records are **not signed** and would bypass the T20 continuity property.
- Rejected as the primary path; possibly useful later as a low-latency mirror.
### 4.4 Correlation
- privacyIDEA 3.12 neither records nor propagates `traceparent` [upstream]. `pidea_audit` has no column for it.
- Default: `correlation_origin: generated`.
- **Improvement:** KeyCape's MFA call (`/validate/check`) is server-to-server. If KeyCape sends `traceparent` and the
privacyIDEA Ingress/Traefik access log records it, the shipper can join on these fields:
- `startdate`;
- `client` (KeyCape pod IP);
- `serial` / `user`.
The join recovers the propagated id. This is the same dependency on railiance-cluster as for Authelia. [unknown]
- **Cluster-internal calls:** If KeyCape calls privacyIDEA by cluster-internal Service address, the call bypasses
Traefik. Then only a privacyIDEA-side WSGI middleware could log the header. That is an upstream or plugin change.
[unknown] Which address KeyCape uses in production.
### 4.5 Action list
The `type` is derived from `action`, the HTTP method plus endpoint:
| `type` | Native `action` | Notes |
|---|---|---|
| `mfa.challenge.success` / `.failure` | `POST /validate/check`, `/validate/samlcheck` | `serial`, `token_type`, user pseudonym, `realm` |
| `mfa.challenge.triggered` | `POST /validate/triggerchallenge` | |
| `token.enrolled` / `token.enrollment_confirmed` | `POST /token/init` (2-step) | No seed or QR material; `info` is checked for secret-shaped values |
| `token.deleted` / `.disabled` / `.enabled` / `.revoked` / `.reset` / `.unassigned` | `DELETE /token/<serial>`, `POST /token/disable` etc. | `administrator` in `actor` |
| `token.resync` / `.setpin` | `POST /token/resync`, `/token/setpin` | |
| `policy.changed` / `policy.deleted` | `POST/DELETE /policy/*` | Admin |
| `config.changed` | `POST /system/*`, `/resolver/*`, `/realm/*`, `/caconnector/*` | Admin |
| `admin.auth.success` / `.failure` | `POST /auth` | Includes the bootstrap-admin and script logins |
| `audit.read` | `GET /audit/` | Who read the audit log |
| `audit.signature_invalid` | Exporter | Never dropped |
| `privacyidea.other` | Any other `action` | Counted and sent, so the action list can be refined |
| `source.heartbeat` | Exporter | Cursor, `max(id)`, `rows_behind` |
### 4.6 Volume, cadence and retention
- **Volume** [estimate]:
- Average: about 1,300 rows per day. That is 279,209 rows over about 210 days, from deployment on 2026-03-19 to the T20 count on 2026-10-16.
- The rows are probably dominated by automation: MFA-state checks, the guard, probes.
- [unknown] The split between automated calls and human logins. Measure it per `action` before setting thresholds.
- **Peak:** under 10 rows/s.
- **Backfill:** Exporting the historical rows is a one-off backfill of about 280k rows.
- Proposal: run it in batches under 50 events/s.
- The cursor starts at `id=0`.
- [unknown] Whether audit-core wants the history or only the rows from activation onwards.
- **Heartbeat:** every 15 minutes, with `rows_behind`.
- **Allowed silence:** 30 minutes. The automated checks make zero rows over several hours anomalous.
- **Retention.** A longer retention is requested.
- MFA token lifecycle and admin policy changes are the evidence behind privileged-access claims (SI-X4, aal2).
- Proposal: the default 30-day query window plus an archive that keeps signed rows for at least 1 year.
- The source database stays authoritative. [unknown] Its retention and rotation are not configured in the repo today.
## 5. Prerequisites and owner asks
| # | Item | Owner |
|---|---|---|
| P1 | Pin the `lldap/lldap` image to a digest | net-kingdom (or the `rapp-*` owner after ADR-0015) |
| P2 | Set Authelia `log.format: json` and a file path the sidecar can read | net-kingdom |
| P3 | Read-only PostgreSQL role for `pidea_audit` | rapp-postgres / railiance-platform with net-kingdom |
| P4 | Pseudonymisation key in OpenBao, and a sender credential for each source | net-kingdom via the attended OpenBao path (`warden route`) |
| P5 | Traefik JSON access log with `traceparent` kept (no cookies or auth headers) | railiance-cluster |
| P6 | `tenant` value for platform-scoped sources; `correlation_origin` field placement (AUDIT-WP-0017-T05); per-subject encrypted personal payload for signed privacyIDEA rows; whether to backfill privacyIDEA history; longer retention for privacyIDEA | audit-core |
| P7 | KeyCape sends `traceparent` on its back-channel calls to Authelia and privacyIDEA | key-cape |
## 6. Unknowns to verify before overlays are prepared
1. **Log content.** Exact Authelia 4.39.28 and LLDAP log messages and fields at info level. Capture them on synthetic
accounts in a non-production instance.
2. **Concurrent SQLite reads.** Whether Authelia's SQLite storage tolerates a concurrent read-only reader on the RWO PVC
without lock contention. The alternative is to move Authelia storage to PostgreSQL.
3. **Signed-string format.** The privacyIDEA signed-string format and its stability across patch releases.
Exporter-side verification depends on it.
4. **Ingress path and logging.** Whether KeyCape reaches Authelia and privacyIDEA through Traefik or through cluster
Services, and whether Traefik logs request headers on railiance01.
5. **Traffic split and volume.** The real distribution per action and the human-versus-automation split. Every volume
figure above is an estimate.
6. **Password reset.** Whether Authelia password reset is enabled. The notifier is configured, but the reset policy is
not set explicitly in `configmap.yaml`.
7. **Deployed config.** Whether the deployed configuration still matches the repo manifests. ADR-0015 migration and
live drift are not checked in this draft.

View file

@ -0,0 +1,611 @@
# NK-WP-0049 recovery ceremony and closure packet
Status: the actual full off-host identity bundle was independently retrieved and
recovered with the human-held age key. Daily backup/retention and hourly health
timers are active. Full native service authentication, token decryption, audit
continuity and current/legacy caller acceptance remain open for NK-WP-0049.
Approved execution is recorded in decisions40bfae40 and5edd5203; this document
records their scope and evidence.
**Outage quick start:** [Storage Share disaster recovery guide](disaster-recovery-storage-share.md).
The guide and standalone tools ZIP were published and read back on 2026-10-16.
The operator independently retrieved the protected bundle without OpenBao/SSO
and passed the full archive/native PostgreSQL check. T23/T26 are done; see
[the accepted publication and drill record](storage-share-recovery-publication.md).
The [isolated service restore packet](identity-service-restore-packet.md)
contains the draft native-service sequence and remaining T15 admission inputs.
Its actual LLDAP stage passed on 2026-10-16: native existing-user bind, wrong
credential refusal, stable directory/attribute rows, isolation and cleanup.
T16 is done; Authelia/KeyCape/MFA/audit continuity remains separate acceptance.
## What has been established
On 2026-10-15 the operator identified Scaleway and Storage Share as the off-host
backup providers. This identifies destinations, not the exact recovery artifact,
its freshness, its file coverage or independently held decryption key. Platform's
current backup contracts distinguish Scaleway database/full archives from the
Nextcloud secondary copy. An upload-only share is not a retrieval identity.
Read-only primary-cluster metadata is retained under
`docs/evidence/2026-10-15-bootstrap-recovery/`. LLDAP, Authelia, KeyCape,
privacyIDEA, identity-provisioner and User Engine each have one Ready replica.
The three SQLite/key backup CronJobs report successful October 15 invocations.
Their current commands target local PVCs; neither an external export nor complete
off-host key/database coverage is established by those observations. The
privacyIDEA command mentions its encryption key, but not its audit key pair.
This command inspection is a coverage question, not proof that no other backup
exists. The personal-token and trigger-admin Secrets still exist. Neither their
validity nor provider-side revocation has been tested. A scan of 34 current
Deployment/StatefulSet/DaemonSet/CronJob/Pod resources in sso, mfa and user-engine
found no references to those two old Secrets; it still found the expected audit
delivery reference in User Engine. Other namespaces, historic jobs, external
consumers and issued provider credentials are outside that scan.
## Assemble the exact owner packet before execution
Record only safe references and checksums. Keep credentials in their current
owner custody and pass the decryption key through attended temporary unlock.
| Required owner input | Acceptance |
| --- | --- |
| Off-host artifact | Provider, account/folder or object/version, backup timestamp, ciphertext SHA256 and independent retrieval receipt; do not substitute a new local export for the existing off-host backup. |
| Independent key | Human confirms matching custody outside the servers and OpenBao. A successful decrypt using only the OpenBao escrow key does not prove independent recovery. Never send the key in chat. |
| Required assets | Exact manifest of matching service env/config files, LLDAP and Authelia data, privacyIDEA database plus original encryption/audit keys, and KeyCape registry/signing material. Resolve assets held through other current owner lanes explicitly. |
| Recovery environment | Named disposable target, pinned images available independently of the failed production registry, no production ingress, no production identity/client execution, bounded egress, private storage and exact cleanup scope. |
| Native proof | Owner-specific database integrity, identity/group counts, decryption of retained token material, audit signature continuity, issuer/client/signing configuration preservation and admitted login/MFA checks. Distinguish an isolated restore from current production credential validity. |
| Current consumer source | Reconcile the deployed revisions with the repaired caller/provider source. The October 14 reserved platform-group fix must be deployed and independently checked before claiming live enforcement. |
| Legacy credential disposition | IAM owner identifies the exact historical personal-token/trigger principals, actual consumers and revocation method before any deletion. Deleting a Secret does not revoke an issued provider credential. |
The operator supplied the independently held `railiance-backup` age key only to
the attended local custody helper. The actual off-host PI ciphertext decrypted
and restored successfully; the agent received no private key material. The full
identity asset manifest remains unfulfilled. The backup lane recipient and
bootstrap `keys/age.pub` must not be assumed to be the same.
Do not draft a blanket restore or grant approval while full coverage is unknown.
Once fixed, bind the source revisions, target identities, allowed operations,
recovery/cleanup sequence and maintenance window to the reviewed live packet.
Existing eligibility or another client's approval is not this ceremony's grant.
## Execute in stages
1. Use the existing Platform/provider recovery lane to retrieve the identified
ciphertext, verify its object/version and digest, and retain a safe transfer
receipt. Do not request or expose the provider credential to the agent.
2. The human unlocks the independently held age key in the existing custody
helper. Validate it against the registered public recipient. No permanent
key is installed in HOME, generated as a replacement, or copied into a bundle.
3. Run the local transport/coverage check below. It contacts no cluster, keeps
plaintext in a private temporary directory outside Git, refuses unsafe
archives/private age keys, tests an unrelated disposable wrong key, reads the
restored files back and removes that staging. It emits a closed non-secret
receipt. This is one prerequisite, not whole-service recovery acceptance.
4. Under the exact separately reviewed owner ceremony, restore the matching
service data/key/config sets into the disposable environment and perform the
native checks above. Never run the full production bootstrap to test source
repairs. Unknown writes stop for reconciliation rather than automatic retry.
5. Verify cleanup, preserve the original ciphertext and independent custody,
and reconcile current callers/legacy credentials through their owners.
Retain exact source/object/image/actor/time and positive/negative receipts.
6. Close T02/T03 only after the recorded remaining requirements are satisfied.
Update the workplan first, run `statehub fix-consistency --repo net-kingdom`,
and verify the Hub projection. A transport fixture or generated evidence
worksheet cannot mark this plan finished.
## Prepared local transport check
The human invokes this through the existing attended custody helper after the
exact artifact and required-file list are agreed. `NETKINGDOM_CUSTODY_AGE_KEY_FILE`
is the temporary private file provided inside that envelope. The tool never
reads an ambient HOME key. The receipt destination must be new.
```bash
python3 tools/bootstrap-recovery/verify-bundle.py \
--bundle /private/recovery/fetched-ops-bundle.tar.age \
--bundle-sha256 <independently-recorded-ciphertext-sha256> \
--required-file lldap/secrets.env \
--required-file privacyidea/secrets.env \
--required-file privacyidea/pi.enc \
--required-file privacyidea/private.pem \
--required-file privacyidea/public.pem \
--receipt /private/recovery/new-transport-receipt.json
```
This example file list is not an accepted whole-stack manifest. Add every
agreed service file supported by the closed transport profile; database copies
and other archive formats use their native owner restore tools. A successful
receipt explicitly leaves independent custody, current service validity and
native consumer recovery unproven. Off-host retrieval lineage and human custody
confirmation must accompany it; the tool cannot infer where a local input came
from. A failed check publishes no acceptance receipt.
## Rotation boundaries
CUST-WP-0073-T05 remains founder-deferred under decision
`1591daa1-6e85-4dfd-aadd-5a9fe1ef47fa`. This work does not reopen that rotation,
replace signing keys, change directory passwords or erase old backup keys.
Existing historical exposure, provider revocation and coordinated rotation
requirements stay explicit in their live owner records. If completion needs a
protected native operation, prepare that exact operation and obtain its scoped
disposition; never treat a source repair or successful backup job as permission.
## Knowledge/source scope
Emergent Security 0.2.6, package digest
`8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02`:
start/root cards withheld, orientation only. Current SECURITY-COMPANION,
Platform backup/custody procedures, owner runtime contracts and the workplan
were reviewed as sources. No execution authority is derived from the cards.
## Off-host inventory return — 2026-10-15
Both bounded metadata inventories completed. Storage Share's inspected
`Backup/railiance-backups` scope includes the current privacyIDEA database
ciphertext and the October14 OpenBao snapshot. It did not reveal a matching
bootstrap/identity-file bundle in the inspected folders. Scaleway's inspected
`railiance-platform-pg-backup/platform-pg/` tree contains database backup trees
and an application archive subtree, with no identity-file bundle in this bounded
scope. Other accounts/buckets/prefixes are unassessed. See
[the discovery summary](evidence/2026-10-15-bootstrap-recovery/offsite-discovery-summary.json).
No object contents or age keys were read; the operator envelope exited0 and
self-revoked. Existing scoped backup credentials were reused without changes;
the Scaleway bootstrap API key was not used.
The operator subsequently reported the hidden-terminal check passed and the
temporary key file was removed. The later actual database drill also passed;
the private key was not provided to the agent.
## Prepared bounded privacyIDEA database drill — 2026-10-16
The identified existing off-host ciphertext was downloaded through the same
attended Platform Backup-account procedure. The before/GET/after strong-ETag
binding and exact byte count passed. Ciphertext SHA256:
`cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead`.
See `privacyidea-transfer-r1.json` in the October15 evidence folder. The owner
envelope exited0 and self-revoked; no age key or plaintext was read by the fetch.
Ciphertext is retained in the fresh private `/tmp/nk49-offhost-j9dzl0ge/` directory.
The following exact human command performs only an isolated local database
drill. It checks the transfer receipt/digest before opening the temporary key,
checks the backup recipient, refuses an unrelated wrong key, decrypts to private
temporary storage and restores into disposable PostgreSQL. Image
`postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70`
is already cached locally (pg_restore16.14). There is no image pull, network,
published port, host data mount, production connection or IAM/client execution.
Database storage is a bounded temporary memory filesystem. Only fixed aggregate
counts for `token`, `resolver`, and `realm` leave the database. The container,
volumes and temporary plaintext are removed and cleanup is checked. The original
ciphertext remains for reproducibility. This proves database recovery only;
token decryption, audit signature continuity and full identity service recovery
require matching key/config files and further native owner checks.
Executed successfully on 2026-10-16 (local time). The command is retained for
provenance; its r1 receipt now exists and must not be overwritten or rerun merely
to repeat evidence. Run future reviewed drills with a new receipt. Paste the key
only into the hidden prompt, then share the success/error line or safe JSON
receipt. The receipt destination must be new; do not overwrite a failed receipt.
```bash
cd /home/worsch/net-kingdom
bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
--prompt -- python3 tools/bootstrap-recovery/check-privacyidea-database.py \
--transfer-receipt docs/evidence/2026-10-15-bootstrap-recovery/privacyidea-transfer-r1.json \
--receipt docs/evidence/2026-10-15-bootstrap-recovery/privacyidea-database-drill-r1.json
```
Source verification:23 focused checks passed, including a real local Docker
custom-format dump, age encryption, temporary fixture-key unlock, wrong-key
refusal, native restore/readback and cleanup. Those tests use synthetic data and
fresh fixture keys. They are not evidence of actual backup decryption. If the
real drill fails, review its closed receipt; do not expose PostgreSQL/age stderr
or dump contents in chat. T02/T03 remain open.
## Actual database recovery result — 2026-10-16
The operator reported success and the actual receipt was reviewed against the
exact checker source, pinned image and fetched ciphertext SHA256. The retained
ciphertext digest was independently rechecked. Decryption, unrelated wrong-key
refusal, native PostgreSQL restore, fixed aggregate readback and container/plaintext
cleanup all passed. The restored database has five tokens, two resolvers and
two realms. The local Docker inventory contains no remaining NK49 containers.
Evidence: `privacyidea-database-drill-r1.json` and `database-drill-review.json`
in the October15 evidence directory. No production connection occurred.
This proves recovery of the selected database using the operator's independently
held key. It does not prove the recoverability of token plaintext, old audit
signatures, directory accounts, issuer keys or a full identity environment.
## Missing coverage prepared at the operator's request
The operator confirmed there is no known full identity bundle and requested
preparation of the missing coverage. The machine-readable proposal is
[`identity-backup-profile.json`](../tools/bootstrap-recovery/identity-backup-profile.json).
The read-only preparer checked the primary cluster UID, four current Deployment
UIDs/readiness/observed generations, five Bound PVC UIDs and the complete current
Secret/ConfigMap reference sets. It captured names and identities only; it did
not read Secret values or execute in pods. Its exact result is
`identity-backup-preparation-r1.json`. Fourteen tests cover foreign/stale targets,
new unreviewed dependencies, projected Secrets and private-value suppression.
| Component | Required new coverage | Required recovery proof |
| --- | --- | --- |
| LLDAP | Consistent `users.db` snapshot, remaining durable data and current credentials/configuration | User, group and membership preservation; existing-user authentication |
| Authelia | Consistent storage DB, original encryption/OIDC/session keys and current configuration | Storage decryption, signing identity and isolated authentication |
| privacyIDEA | Original enckey plus matching audit pair, configuration/scripts and a linked native database dump | Existing token decryption/MFA, old audit signature verification and new isolated audit entry |
| KeyCape | Current registry/config and signing key, policy/receipts, every referenced credential's recovery or independent reissue disposition | Issuer/JWKS identity, registry/policy continuity and positive/negative consumer acceptance |
The proposed primary prefix is
`platform-pg/application-archives/net-kingdom/`; the proposed secondary folder is
`Backup/railiance-backups/net-kingdom-identity/`. These are **not admitted**.
Platform's current generic application archive adapter only admits
`coulomb-social` and `email-connect`, and their publisher custody profiles are
application-bound. A new NetKingdom profile and scoped publisher require owner
admission; existing credentials must not be borrowed. The operator's Scaleway
bootstrap API key is unnecessary for preparation.
The initial goal is one verified generation, with a measured encrypted size and
fresh provider quota/upload-overlap checks. A proposed 512 MiB ceiling is a
refusal bound, not evidence that the data fits the secondary budget. Seven daily
and two weekly points remain a later measured proposal. No retention deletion
or recurring schedule was installed. Capture must preserve original keys and
use SQLite online-backup/PostgreSQL logical snapshots, record each capture
interval and refuse changed source identities/configuration. It must not claim
global atomicity or substitute a fresh export for the already recovered old dump.
The source implementation below supersedes the earlier adapter-preparation gap.
The Scaleway primary/Storage Share secondary proposal above is deferred: the
initial rollout implements Storage Share only. Neither destination has yet
received a full identity bundle. Owner admission and actual recovery remain open.
## Implemented identity backup packet — 2026-10-16
The new capture, sealing, upload/readback, independent retrieval, archive/native
PostgreSQL verification, separate retention and node installer are implemented
under `tools/bootstrap-recovery/identity-*.py`, `capture-identity.py`,
`retrieve-identity.py`, `verify-identity*.py` and `install-identity-backup.py`.
Source profile: `identity-backup-profile.json`. No production keys were collected,
no new credential grants were made and no timers were installed in this source
checkpoint. Decision `40bfae40-6108-4e51-8613-073339f5345f` remains pending.
The exact new owner requests are:
- [CCR-2026-0048 publisher](../../railiance-platform/credential-change-requests/CCR-2026-0048-net-kingdom-backup-publisher.yaml): a read/create share for only `Backup/railiance-backups/net-kingdom-identity/`, delivered through a dedicated 15-minute, audience/namespace/SA-bound ESO reader.
- [CCR-2026-0049 retention](../../railiance-platform/credential-change-requests/CCR-2026-0049-net-kingdom-backup-retention.yaml): a separate read/delete share for that same folder and separate process. Compromise of this credential can delete points in the folder; it must not be given to the publisher process.
Both require Platform, NetKingdom and Backup-share owner review. Only the scoped
share tokens enter exact new workload KV paths and ESO-managed Secrets. Neither
the operator account password nor the age private key is delivered to the job.
The setup helper refuses proposed/unapproved or hash-mismatched CCRs. Native
positive and wrong-SA/namespace/audience probes must prove the admitted reader
boundary, followed by equality to the exact ESO delivery; temporary readers are
revoked. These are new grants, so prior approval to prepare the missing coverage
does not count as completed credential-owner admission.
The initial provider is Storage Share only. Ciphertext and safe receipt are
created once under a unique generation name, read back using strong ETags and
checked by size/SHA256. A 512 MiB refusal ceiling and two GiB fresh provider
headroom apply; unsupported public-share quota fails closed. Public DAV behavior
and permission denials are checked with disposable public-recipient fixture
ciphertext before accepting the grants. No Scaleway credential or another
workload publisher is reused. Nextcloud documents the public-share DAV interface
in its [official WebDAV guide](https://docs.nextcloud.com/server/25/user_manual/en/files/access_webdav.html).
Two temporary helper Pods mount only reviewed PVCs read-only, have no service
account token, use pinned images, private tmpfs scratch and deny-all network
policies, then are deleted with exact UID preconditions. Kubernetes network
allows combine, so `identity-isolation.py` first prepares two guarded patches to
exclude the capture label from the existing namespace-wide DNS allows. Every
peer/port and ordinary service selector match is preserved. Plans bind the
current cluster/policy UIDs, resource versions and full original specs, and
refuse an existing pod using the capture label. Capture refuses any remaining
matching allow policy, source drift or unknown helper cleanup outcome.
Capture uses native SQLite online backups and a fresh native PostgreSQL dump;
all required current Secrets/configuration and original key pairs are sealed
with the public recipient. The manifest and plaintext hashes are inside the
ciphertext. privacyIDEA service logs, historical LLDAP/Authelia/PI `backups/`
subtrees and Authelia notification.txt are omitted; the audit database is in the
linked PostgreSQL dump, and the full KeyCape policy/receipt tree is preserved.
There is no claim of a globally atomic multi-service snapshot. Drift/partial
cleanup stops future runs and requires exact owner reconciliation.
### Ordered live rollout after exact packet approval
1. Record all required owner reviews in CCR-2026-0048/0049 and bind their exact file hashes. Run `identity-credential-setup.py publisher|retention --ccr-sha256 HASH --receipt PRIVATE_NEW_PATH` through the attended Platform-admin owner conduit. Never inspect private helper output or copy operator credentials into the runtime. Verify actual provider permissions, initial CAS-only KV publication, exact policy/role, agent high-risk denials and revoked fixture readers.
2. Apply only `identity-custody.json`; wait for its two SecretStores/ExternalSecrets to be Ready. Run `verify-identity-delivery.py publisher|retention --ccr-sha256 HASH --kubeconfig REVIEWED_PATH --receipt PRIVATE_NEW_PATH` through that same owner conduit. Resolve any native denial before accepting delivery; do not set confirmed-claim flags from fixtures.
3. Generate a fresh `identity-isolation.py plan --kubeconfig REVIEWED_PATH --receipt PRIVATE_PLAN`; review it, server-dry-run it, then `apply --plan PRIVATE_PLAN --plan-sha256 HASH` using the same explicit kubeconfig and a fresh receipt. Each patch tests UID/resourceVersion/spec. Regenerate stale plans rather than weakening the guards. Confirm the cluster's network policy enforcement through its owning layer.
4. Render `install-identity-backup.py render --decision-id 40bfae40-6108-4e51-8613-073339f5345f --receipt PRIVATE_PACKET`. Transfer only the reviewed runtime files in that packet plus the packet to the admitted node. As root on railiance01, run installer `install --packet PRIVATE_PACKET --packet-sha256 HASH --receipt PRIVATE_NEW_PATH`. Installation verifies exact cluster/source hashes and unit syntax; timers remain disabled. Execute the packet's exact `first_capture_command`. All output receipts/state and encrypted copies stay under root-private `/var/lib/net-kingdom-backup`.
5. Retrieve the successful generation's safe off-site receipt from that private state, then run attended `retrieve-identity.py --source-receipt PRIVATE_NODE_RECEIPT --receipt PRIVATE_INDEPENDENT_TRANSFER` with the independently authorized Backup account via Platform custody. It re-downloads and binds the ciphertext by ETag/size/hash. The receipt names the new private local ciphertext path. A published index alone is not independent retrieval proof.
6. The human independently verifies the downloaded bundle using the hidden-key command below. Review the actual receipt and exact source/artifact/hash. Copy only this safe receipt to private node state; activate with `install-identity-backup.py activate --accepted-receipt PRIVATE_ACTUAL_RECOVERY --receipt PRIVATE_NEW_PATH`. Activation binds the actual artifact to its local successful off-site receipt, pins that generation, checks health and enables the timers.
7. Confirm both active timers, last success, retained protection and safe Hub progress. Full NK49 native identity/caller and legacy-credential acceptance remains separately open.
All actions accept source/target guards; flags do not create authority. The
installer runs as the already admitted node owner. If installation or a grant
partly fails, stop and reconcile its safe receipt/exact resources before retry.
Do not rotate keys, change existing service credentials or remove older backups
as an implicit repair. For rollback, disable the two exact new timers first,
retain the verified ciphertext and restore receipt, then have the owners revoke
only these newly issued shares/roles and remove their delivery resources after
confirming no consumer remains. Never delete existing unrelated backup jobs.
### Independent full archive verification and bootstrap connection
The key is entered only in the operator's hidden terminal, using the independently
held backup key already checked against this public recipient:
```bash
sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
--prompt -- python3 tools/bootstrap-recovery/verify-identity.py \
--transfer-receipt /PRIVATE/independent-transfer.json \
--receipt /PRIVATE/new-full-archive-recovery.json
```
With the reviewed sibling `railiance-bootstrap` checkout, the equivalent handoff
is `bash ../railiance-bootstrap/bin/railiance identity-recovery` with the same
transfer/receipt arguments, run inside that hidden-key wrapper. Its
[bootstrap sequence](../../railiance-bootstrap/docs/identity-recovery.md) links
independent custody and isolated verification to replacement-host/layer handoff.
The wrapper delegates to this verifier and never imports into production.
The complete manifest, SQLite integrity, original RSA pairs, wrong-key refusal,
native isolated PostgreSQL restore and verified cleanup are the archive gate for
recurring backups. A successful archive gate does not prove existing-token
plaintext/MFA, historical/new audit signatures, existing-user login or issuer
consumer flows. Those require separately reviewed isolated service acceptance
before T03 and NK49 can close. The verifier deletes its plaintext; it is not a
production importer.
Today's attended retrieval helper requires healthy OpenBao. For total custody
loss, independently held provider access and a separately reviewed offline
retrieval receipt procedure are still required; that offline adapter is not
implemented. No bare-VM unattended restore is claimed. The backup public key is
not interchangeable with the separate bootstrap encryption recipient.
### Recurrence, retention and health
After the first actual archive gate, the prepared root unit runs daily at
03:45 UTC with up to five minutes jitter. Retention runs in separate processes
before/after capture, protecting newest, seven distinct daily points, two
distinct weekly points and the independently recovered pinned generation.
Unknown/unindexed objects are never candidates. Both pinned and newest points
are re-downloaded and hashed before any expiry; each candidate is checked and
deleted conditionally by ETag. Partial/unknown expiry stops subsequent expiry
until reconciled. A matching guarded local-cache prune preserves protected
points and leaves unknown/failed runs for owner inspection. There is no claim
that 10 GiB is sufficient indefinitely: measured quota/headroom is checked each
run and an excess refuses new capture rather than deleting a protected point.
An hourly node health check reports missing/older-than-25-hour backups or partial
failure to State Hub, with durable local nonsecret outbox/replay. This host-local
check cannot detect or report a dead host; external outage monitoring remains a
separate gap. The daily backup RPO is explicit and distinct from the one-hour
ecosystem coordination frame. Timers are currently disabled/uninstalled.
Find this functionality through the owning registry entry
`capability.security.bootstrap-recovery` (keywords backup, restore,
disaster-recovery, age) federated by reuse-surface, related to
`capability.railiance.bootstrap-sequencing`. Maturity remains D3/A2/C1/R0;
source fixtures and discoverability do not assert live production acceptance.
## Live admission update — 2026-10-16
The operator approved the exact packet in decision
`40bfae40-6108-4e51-8613-073339f5345f`. The first owner login succeeded but
Nextcloud returned publisher permission21 for requested5. Setup refused before
publishing any new KV value. Official Nextcloud link-share normalization adds
permission16 (re-sharing) to read-enabled public links when outgoing federation
is enabled. The operator then explicitly approved folder-only publisher21
(read/create/re-share) and retention25 (read/delete/re-share), recorded in
`5edd5203-fae7-461b-8775-5d22b7873971`. The two CCRs and setup guards now require
that additional explicit approval. Neither credential gains update rights,
other-folder or account administration. New-share metadata and failed-attempt
receipts are retained in the October16 live evidence folder. This supersedes
the initial no-re-sharing provider assumption; it does not infer a native test
pass from approval.
Node preflight also verified that `railiance01` is an SSH alias whose actual
hostname is `239.62.205.92.host.secureserver.net`. Runtime/installer now require
that exact hostname, the reviewed machine-ID hash and the original cluster UID,
plus root. No server hostname or machine identity was changed.52 focused
checks passed including the real synthetic encrypted/native PG fixture after
this guard repair. Unapproved-CCR negative tests use proposed fixtures even
when the actual owner requests have been approved.
Both exact guarded DNS-selector patches passed server-dry-run and were applied.
The node has active k3s with network-policy rules present. The source-bound
runtime release was installed and unit validation completed; both timers remain
disabled. Provider admission/readback and actual full capture remain pending;
no periodic capture/expiry is enabled by installation alone. One subsequent
OIDC attempt timed out before owner-command handoff; it did not run mutations,
and its possible session revocation was unconfirmed. Only the contained owner
login lane is retried, without switching to root or inspecting captured bytes.
The native publisher fixture subsequently proved read/create, denied overwrite
and delete, and exact fixture cleanup. Its malformed parent path was rejected
withHTTP400. The checker now records that separately and also requires a valid
outside-account-folder request to fail authentication; accepting a malformed
path rejection alone would not establish authorization confinement.16 provider
checks pass after this correction. A fresh metadata capture plan confirms zero
allow policies matching helper Pods. The next owner login timed out before
handoff; no corrected provider probe or KV/ESO publication ran in that attempt.
The installed runtime's timers remain disabled while attended admission resumes.
## First full bundle created — 2026-10-16
Both exact roles passed native login, effective single-policy/900-second lifetime,
metadata/sibling/old-escrow/token-create denials, wrong-SA/namespace/audience
rejection, ESO value equality and temporary-reader revocation. Both ExternalSecrets
are Ready and KV versions are1. The operator session self-revoked. CCR-0048/0049
are applied with actual bound-claim evidence; the provider-only request5/9
assumption has been superseded by separately approved21/25.
ESO adds known no-transformation defaults (`conversionStrategy: Default`,
`decodingStrategy: None`, `metadataPolicy: None`) to its spec. The delivery guard
now accepts only those defaults while rejecting extra sources or altered
conversion/property/version/reader bindings. The disabled node units were
replaced with exact previous-packet/content guards, timers verified disabled and
inactive, and preserved rollback bytes; source release
`758ba4d170ba5ed4b6c563d3a379e496c5f81e8bff28c45d142a6bacbfaa21bf`
is installed. 56 focused checks passed, including native synthetic full-archive
PostgreSQL recovery. The task-only disabled-upgrade source and packet are in the
live evidence folder; this is not permission for an unchecked active-job upgrade.
Actual first capture completed under the approved node owner, with all required
source identity guards and native data snapshots. Both temporary helpers and
private plaintext were cleaned. The exact new encrypted artifact is:
- Name: `net-kingdom-identity-20261016T063159Z-5882771aa2ce.tar.age`
- Folder: Storage Share `Backup/railiance-backups/net-kingdom-identity/`
- Bytes:109738344
- Ciphertext SHA256:`7117b3fe1e4840c86b301c94c85e6b16709e5626bb0884bd0580bde630e30e30`
- Status: unique publication and fresh downloaded readback verified; safe index published last.
Private node receipts live under `/var/lib/net-kingdom-backup/runs/` for that
artifact. No age private key was used in capture/upload. The first independent
owner retrieval and human hidden-key archive proof follow; both timers remain
disabled until the actual proof is reviewed. Full directory/issuer/MFA/token and
audit-continuity acceptance remains distinct from archive recovery.
## Actual independent full recovery and activation — 2026-10-16
The attended Backup-account download succeeded and its owner session self-revoked.
The independently fetched 109738344-byte ciphertext was rehashed locally against
the exact published digest. The operator then ran the hidden-key ceremony through
`railiance-bootstrap/bin/railiance identity-recovery`. The original independent
backup key matched the declared recipient; an unrelated key was refused; all 36
manifest files/hashes, original key pairs and SQLite integrity checked; the native
networkless disposable PostgreSQL 16.14 restore read back 5 tokens/2 resolvers/2 realms.
The checker source SHA256 and exact artifact/digest were reviewed, plaintext and
container cleanup passed, and no NK49 Docker containers remain. The agent
received no age private key or restored secret values.
Safe evidence: `independent-owner-transfer.json`, `actual-full-archive-recovery.json`
and `actual-full-recovery-review.json` in the October16 live evidence directory.
This proves actual archive/file/native-database recovery, with full-service
authentication/token decryption/audit-continuity fields explicitly false.
The accepted receipt was copied as root-private state and bound to the exact
successful node off-site receipt. The approved installer activation succeeded;
both timers are enabled and active. Next backup:2026-10-17T03:47:39Z (05:47CEST,
including the configured jitter). Next hourly health run:2026-10-16T08:00:00Z
(10:00CEST). The actual health unit completed with Result=success/ExecMainStatus=0.
A live retention preview listed the recovered artifact as protected, selected no
expiry candidates and deleted nothing; unknown objects remain untouched. No
capture/retention failure marker or pending event outbox remains. Runtime cadence
is daily 03:45 UTC plus up to 5 minutes jitter, hourly health and 25-hour freshness;
the recorded next times are observations, not permanent calendar values.
`activation-result.json` and `activation-review.json` record the actual state.
The verified first generation stays pinned until a separately reviewed owner
change. Both archive creation/retrieval/recovery and the backup job setup are now
complete. NK49 remains active because broader native service/caller and legacy
custody acceptance are separate open closure criteria.
The installed hardened systemd backup service was then run once end to end.
`Result=success` and `ExecMainStatus=0` cover pre-retention, scheduled capture,
fresh upload/download verification and post-retention. It produced a second
verified generation, `net-kingdom-identity-20261016T073927Z-1224cb9362f8.tar.age`,
SHA256 `2b88375c378aac52fbd82f2bfa21fbbd9af61d8f88abdad2ed3f997dc9993b9c`.
Actual retention kept both the independently recovered first point and the newer
point, selected/deleted nothing and removed no local generations. Helpers and
plaintext were cleaned; failure markers and event outbox remain empty. This
validates the actual hardened recurring-job path, beyond a direct CLI capture.
Evidence: `recurring-job-review.json`. No further validation captures are needed
before the next scheduled run absent a new failure or source change.
## Native token-material and audit acceptance — 2026-10-16
The independently retrieved identity generation passed native privacyIDEA checks
on the fixed isolated local engine: all five retained token materials were read
internally using the original enckey; unrelated-key results differed for all five.
All 279,209 stored historical signatures verified, with zero unsigned/invalid
rows. A new isolated entry verified with the preserved identity; tampering and
an unrelated public key were refused. Container/plaintext cleanup passed.
T20/T31 are done. Safe evidence and source/ciphertext/target bindings are in
`evidence/2026-10-16-identity-outage-recovery/actual-isolated-privacyidea-review.json`.
This accepts internal readback and stored-row signature continuity. Existing-token
MFA, Authelia and KeyCape authentication remain separate; event completeness is
not claimed. The published Storage Share kit remains its recorded version.
## KeyCape original attempt assembly (NK-WP-0049-T33) — 2026-10-21
This is the attended order for the original KeyCape attempt. It is preparation and
authorizes nothing. T18 owns the actual run. Abort at any non-zero step, before the
hidden key prompt.
1. **Founder attempt admission (surviving offline authorization).** The founder
signs a `secrets-engine.recovery-attempt-admission.v1` document with namespace
`secrets-engine-recovery-attempt`, case `secrets15-nk49-readiness`, a window of
at most 24 h, issuer `https://kc.coulomb.social`, and the exact hashes below
(secrets-engine `docs/founder-admission.md`; approved in principle by hub decision
`d1ab097d-cc79-4612-9c17-bb9b364081ac`, not yet signed). The verifier anchor
`/etc/secrets-engine/admission/allowed_signers` (root-owned, runtime-unwritable)
must exist on the attempt host. It is absent on the workstation today.
2. **Selector preflight with admission. This step is required.** From a secrets-engine checkout at
`0f44fbeea18147e65f379632621f2f29ded5f310` (or a replacement the owner names):
```sh
python3 -I tools/check-keycape-recovery-selector.py \
--selector docs/keycape-recovery-selector.json \
--source-sha256 sha256:c0ade429edf452ad17f05d690b7ebef091db7f1a42b5ac350f1e40955c8586c0 \
--admission <attempt.json>
```
Proceed only with exit 0 and status `founder_admitted_attempt_not_native_acceptance`.
The selector bytes must hash to
`sha256:02077af3c18b24f5e4a85992a3d4d0df51a8e458de1e48b48f7eb5f482257012`. The
NK49 admission's consumer block must equal the selector's eight fields
(`secrets-engine-openbao`, `KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET`,
audience `secrets-engine-openbao`, scope `openbao:login`, subject
`service:secrets-engine`, tenant `tenant:platform`, roles `secrets-engine`,
deny `openbao:admin`). A pass shows founder authority for this one attempt. It
is not native acceptance and not single use. The NK49 execution keeps single-use
consumption and cleanup.
3. **TLS/trust input: ACME reissue** (founder decision
`9f9d0d88-837a-433c-8a11-d30b633b0073`, resolved 2026-10-21 = A). The
`kc.coulomb.social` TLS identity is reissued through `letsencrypt-prod` ACME
HTTP-01 and chains to ISRG Root X1 (trust sha256 `22b557a2…43d1`). The original
key is not required, and no self-signed substitute is allowed.
**Before the real attempt**, the owner (railiance-cluster) rehearses the same
issuance against Let's Encrypt staging. This protects the duplicate-certificate
limit of 5 per week. At attempt time, record the public chain with
railiance-cluster `tools/public-tls-metadata.py` and check SAN
`kc.coulomb.social` plus the ISRG Root X1 chain. Abort if HTTP-01 cannot
complete or the chain differs. The TLS key stays in the owner's own terminal.
4. **Existing six-argument launcher** `tools/bootstrap-recovery/run-isolated-keycape.sh`
with the reviewed admission digest. Its T34 source closure and abort/cleanup
behaviour are unchanged. The private manifest is derived and the archive
registration is compared to the selector inside custody only.
5. **Safe receipt and independent verification.** The launcher prints
`Safe receipt: <tmpdir>/recovery.json`. Before anyone copies it, verify it
from a separate shell against a clean checkout at the reviewed commit:
```sh
python3 -I tools/bootstrap-recovery/verify-keycape-attempt-receipt.py \
--receipt <tmpdir>/recovery.json \
--transfer-receipt <verified transfer receipt> \
--source-root <clean checkout>/tools/bootstrap-recovery \
--not-after <admission window end, ISO 8601 with offset>
```
The admission window, at most 24 h and signed by the founder, is the execution
deadline. Retain the receipt only on exit 0 (`verified`), and keep it at
`docs/evidence/<date>-nk49-t18-keycape-attempt/recovery.json` together with the
verifier output. On exit 1, keep it out of Git and report the findings. Do not
repair it by hand. The verifier refuses secret-shaped content, open cleanup
obligations, daemon uncertainty, any claim of consumer reconnect or human MFA,
a ciphertext that differs from the transfer receipt, and source that differs
from the checkout. A pass is not consumer continuity (T18), retained-factor MFA
(T19) or owner acceptance.
Still open before T33 can close (2026-10-23): the anchor installed on the attempt
host (SECRETS-WP-0015-T04, founder- and root-owned) and the owner's Let's Encrypt
staging rehearsal from step 3. T35, T36 and T39 are done. The deadline, receipt
location and verifier are fixed above. Public observations are in
`docs/evidence/2026-10-21-nk49-t33-readiness/readiness-observation.json`.

View file

@ -0,0 +1,57 @@
# Crisis custody list
NK-WP-0054-T07. Drafted 2026-10-24 from repository documents and the founder's statements
that day. **This list holds no secret value, ever.** It names what unlocks recovery, who
holds it, where, and whether using it needs the very services being recovered. Anything
the repository cannot establish is marked `TO CONFIRM`; the founder confirms each one.
It belongs with the backup documentation:
[disaster recovery guide](disaster-recovery-storage-share.md),
[identity restore drill](identity-restore-drill.md) and
[Credential Management Standard §4](../canon/standards/credential-management_v0.2.md).
That standard names the custody categories. This list makes them concrete.
## The one rule
For every row, answer: **does using this item work when SSO and OpenBao are down?**
A recovery path that is satisfied only by logging in to something being restored is a
circular dependency and is recorded as a gap, not as custody.
## Roles used below
| Role | Identity | Used for |
| --- | --- | --- |
| Crisis operator | `admin` | Runs recovery and routine validation (founder intent, 2026-10-24) |
| Authorizer | `platform-root` / king credential | Break-glass authority from offline custody; nothing routine |
| Developer | `tegwick` | Builds and tests tooling; holds no recovery keys |
## Items
| # | Item | Unlocks | Holder and location | Works with SSO and OpenBao down? | Last proven use | Gap or action |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | **`railiance-backup` age private key** (recipient `age1zvry…50syr3tsz`). Not the custodian key (item 1b) | Decrypts the identity backup bundle and the break-glass copy on Storage Share (`net-kingdom-kingpath-*.tar.age`) | Two copies: founder's password safe (stored as `railiance-backup.key`) and a copy held by a named trusted person, who can recover if the founder cannot (founder, 2026-10-28) | Yes, offline | Identity stages 2026-10-24; king-path verify drill 2026-10-27 (the helper refused the custodian key three times before the correct key was entered, as designed) | Successor copy is not drill-proven; prove it once with `kingpath-offsite.py --verify` run by or with the successor |
| 1b | **Custodian age private key** (recipient `age1aq8t…qdepsh4`, `keys/age.pub` and `.sops.yaml`). Not the backup key | Decrypts the repository's SOPS custody files and greenfield bootstrap bundles (`docs/security-bootstrap-age-custody.md`) | Founder, password safe (stored as the custodian age private key) | Yes, offline | 2026-10, bootstrap/SOPS work (founder, 2026-10-28; not yet receipt-proven: run `make sops-custody-check`) | Labels in the safe should name the recipient prefix so the two keys cannot be confused |
| 2 | Storage Share `Backup` account app password | Downloads the protected ciphertext without OpenBao or SSO | Founder, in the independent password safe (founder, 2026-10-28) | Yes, provider is outside the platform | Fresh retrieval 2026-10-24 | One provider copy only; residual risk accepted until 2027-01-31 (G1, NK-WP-0053) |
| 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao; with the shares, `generate-root` mints a root token for break-glass (ceremony in `railiance-platform/docs/openbao.md`; production loopback listener 8210 live since 2026-10-28) | Founder as `platform-root` holds all three shares (single-holder custody: accepted exception, hub decision `37464824`, 2026-10-27), in the founder's replicated browser password safe under the `kingpath.credentials` pseudo-URL entry (confirmed 2026-10-27) | Yes: the safe is independent of NetKingdom SSO and OpenBao; it depends on the browser vendor's sync account and its own factor | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Single holder = single point of loss (reviewed at NK-WP-0055-T09). The founder holds the initial root token offline; the king-path drill (2026-10-28) found it **valid** as of the 2026-10-14 snapshot (root policy, no TTL, orphan). Founder decision `7ce684ec` (2026-10-28): stays escrowed during builder mode, revoked at production entry (NK-WP-0055-T16) |
| 4 | `platform-root` / king account and factors | Final authority; break-glass | Independently held password safe (kingpath entry) | **No if used through KeyCape**. The king path with SSO down is offline material instead: unseal shares (item 3) plus the `generate-root` ceremony on the loopback listener, the admin kubeconfig and static tunnel key (item 9, also on Storage Share), the Host Europe console, and the admission signing key (item 5) | OTP validated against restored privacyIDEA 2026-10-24 (isolated). King path drilled 2026-10-28: Storage Share restore of item 9, `generate-root` on an isolated restore, read-only production SSO-down walk ([record](evidence/2026-10-28-king-path-drill.md)) | Not drilled: using the restored item-9 material against the cluster, and the Host Europe console. Both belong in the recurring drill (NK-WP-0053) |
| 5 | Founder admission signing key (SSH ed25519, principal `bernd`, fingerprint `SHA256:NekDABfw8K8IBYznjREg7jPpcK/BzCgO1pH1ebT/FXM`) | Signs restore and drill admissions for the OpenBao isolated restore (namespace `railiance-openbao-restore-attempt`) and secrets-engine admissions | Private key: founder workstation `~/.ssh/gexec_approver_ed25519` (passphrase-protected; signed the T40, `-02` and `-03` admissions); founder decision 2026-10-28: also keep it in the password safe under the kingpath entry, **not** in the Storage Share bundle, so the backup age key alone cannot admit a restore. Public anchor (non-secret, needed on any recovery workstation): `/etc/railiance-openbao-restore/allowed_signers` and `/etc/secrets-engine/admission/allowed_signers`, one line each: `bernd namespaces="railiance-openbao-restore-attempt" ssh-ed25519 ssh-ed25519` ; the secrets-engine file uses namespace `secrets-engine-recovery-attempt` | Yes, offline | Signed the T40 admission 2026-10-23 | Private key stored in the password safe kingpath entry (founder, 2026-10-28). Re-create both anchor files from this row on a recovery workstation. The file name says `gexec_approver`, but the key's role is admission signing |
| 6 | `admin` password and OTP token | Day-to-day platform administration and, per intent, recovery operation | Founder; OTP enrolled 2026-10-24 | **No**: login and token checks need KeyCape, LLDAP and privacyIDEA | Token not yet in any restored backup; first restore proof after the 2026-10-25 03:45 UTC capture | Same account as the directory bind used by Authelia and privacyIDEA until the split in NK-WP-0054-T05 |
| 7 | `pi-admin` credentials (privacyIDEA internal admin) | Token repair and re-enrolment | Founder's replicated browser password safe (confirmed 2026-10-27) | Custody yes (replicated, independent of SSO); use needs a running privacyIDEA | Not yet drilled | Add a pi-admin check to the drill (NK-WP-0053-T04) |
| 8 | ~~Ops bundle (`make creds-bundle`)~~ **Retired 2026-10-27** (founder decision) | — | — | — | Never drilled; none existed on the workstation | Superseded by the daily identity backup, which carries the current service secrets. `make creds-bundle` packs only greenfield `creds-generate` output and stays a greenfield bootstrap step |
| 9 | Operator access to the production host or cluster for a real restore | Applying a restore to production | Cluster: ops-bridge tunnel `k3s-api-railiance01` (SSH as `tegwick` to `92.205.62.239` with a static key file, local port 16444) plus the attended admin kubeconfig `~/.kube/config-railiance01` (client certificate), today on the founder workstation only. Host: Host Europe provider interface with console and rescue access to railiance01 (confirmed 2026-10-27) | Yes: static key, client certificate and provider console need neither KeyCape nor OpenBao. **No** for ops-warden SSH certificates (warden's backend is OpenBao) | Cluster path used 2026-10-27 | Off-workstation copy uploaded and read back 2026-10-27: `Backup/railiance-backups/net-kingdom-kingpath/net-kingdom-kingpath-20261027T223050Z-8c9d48421f1b.tar.age` (10440 bytes, sha256 `8c9d4842…7dd4aa`, items `admin-kubeconfig`, `tunnel-ssh-key`, recipient `railiance-backup`; receipt on the founder workstation). Restore drilled 2026-10-27: fetched from Storage Share, unrelated key refused, decrypted in memory, both items intact (`kingpath-offsite.py --verify`). Using the restored material against the cluster is not drilled. Re-run `kingpath-offsite.py` whenever the kubeconfig or key changes. Open: runs as `tegwick` under an agent-labelled actor; provider console never drilled |
## Non-secret recovery assets
These need no custody but must be reachable: the published recovery kit on Storage Share
(guide, tools ZIP, checksum; verify the checksum against a trusted copy), the pinned
container images, and a trusted checkout of this repository. TLS for `kc.coulomb.social`
has no custody: it is reissued through ACME and needs working public DNS and HTTP-01.
## Maintenance
- Review at every restore drill (NK-WP-0053), and record the date each item was last
proven. An item never proven stays marked as such.
- Loss or suspected exposure of any item goes to its owner's rotation and revocation
process (Standard §6); this list rotates nothing.
- A machine-readable, non-secret record with a freshness check, modelled on
`tools/custody-record`, is proposed in NK-WP-0054-T07. Until then this document is the list.

View file

@ -0,0 +1,172 @@
# NetKingdom disaster recovery — start here
Updated 2026-10-27. Owner: NetKingdom with Railiance Platform and the individual
identity service owners. This guide covers the identity backup, not whole-host,
OpenBao, Forgejo, User Engine or application recovery.
## If the identity services are down
1. Use a trusted recovery workstation. Obtain Storage Share access independently
from your password safe/offline custody, and the independently held
`railiance-backup` age private key (crisis list item 1; **not** the custodian
age key, item 1b, which only opens the repository's SOPS files). Never paste either into chat or a ticket.
The private key must match this **public** recipient:
`age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz`.
The provider password and encryption key are separate requirements.
2. Open <https://nx4069.your-storageshare.de> using the independent provider
account, not NetKingdom SSO. In the Backup account, navigate to
`railiance-backups/net-kingdom-identity/`. Recovery documentation and a small
versioned recovery-tools ZIP are placed beside the encrypted archives.
Keep a copy of this guide and ZIP outside Storage Share as well.
3. Choose an intact recovery generation. The full identity filename starts
`net-kingdom-identity-` and ends `.tar.age`. A `privacyidea_db-*.dump.age`
database-only backup cannot replace the matching service keys and configs.
Each generation has a `.receipt.json` index. Preserve the original objects.
4. Check timestamp, size and SHA256 against a separately trusted receipt. The
index is useful metadata, not a signed independent trust anchor. For the
first actually recovered, protected generation the trusted values are:
- Artifact: `net-kingdom-identity-20261016T063159Z-5882771aa2ce.tar.age`
- Bytes: `109738344`
- SHA256: `7117b3fe1e4840c86b301c94c85e6b16709e5626bb0884bd0580bde630e30e30`
- Strong ETag at publication: `"28ec1f7014e492de4c40e1ae75502f08"`
This is a known good fallback, not a claim that it is the latest backup.
For a newer generation, reconcile its index with the independently retained
publisher receipt before constructing a new download input. Never edit a
receipt just to make a failed integrity check pass. ETag changes require
owner reconciliation even if the filename is unchanged.
5. On Linux with Python 3, unpack the recovery-tools ZIP into a private working
directory and enter it. Verify its `SHA256SUMS` against a trusted repository
copy or independently held kit digest before executing code. A checksum
stored only alongside the download detects damage, not a malicious provider.
Alternatively use an independently available trusted net-kingdom checkout.
```bash
python3 tools/bootstrap-recovery/retrieve-identity-offline.py \
--source-receipt docs/evidence/2026-10-16-identity-backup-live/actual-offsite.json \
--auth account --receipt /tmp/nk49-outage-transfer.json
```
In your own terminal, enter the independently held **Backup** account app
password at the hidden prompt. The fixed account name is `Backup`.
If your independently held credential is a read-capable public share token
for this exact identity folder, use `--auth folder-share` instead. An
upload-only token, another account's password or a share for the backup root
is insufficient for this command. A 401/403/404 stops; do not widen permissions
or substitute another token silently. Download performs only HEAD/GET against
the fixed provider, rejects redirects/proxies, and verifies ETag/size/hash.
Failure removes its incomplete local download. Success leaves ciphertext
in a private `/tmp/nk49-identity-fetched-*` directory named in the receipt.
6. Verify and decrypt through the existing hidden key helper. Requirements:
`age`, `age-keygen`, `openssl`, Python 3 and Docker; the following PostgreSQL
image must already be present on the recovery host:
`postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70`.
Stage and verify this image before a drill. The tools ZIP contains no images.
Allow enough private disk and RAM for a roughly 110 MB ciphertext, a bounded
512 MiB archive and the disposable PostgreSQL container (2 GiB memory limit).
```bash
bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz \
--prompt -- \
python3 tools/bootstrap-recovery/verify-identity.py \
--transfer-receipt /tmp/nk49-outage-transfer.json \
--receipt /tmp/nk49-outage-archive-recovery.json
```
Use new receipt names on every run. The helper removes its temporary private
key on exit. The verifier checks the encrypted manifest and all file hashes,
original key pairs, SQLite integrity, wrong-key refusal and an isolated
PostgreSQL restore. It removes decrypted staging and the disposable database.
It deliberately does not install restored secrets or alter production.
7. Preserve the sanitized receipts and original ciphertext. Remove your local
downloaded ciphertext directory when no longer needed. If the verifier fails,
stop and check the bounded receipt with the service owner; do not regenerate
encryption/signing keys, reset user passwords or delete the provider backup.
## What the backup contains
| Component | Recovery material |
| --- | --- |
| LLDAP | Native SQLite snapshot, remaining durable data, deployment and referenced secrets |
| Authelia | Native SQLite snapshot, encryption/session/OIDC material, current configuration and referenced secrets |
| KeyCape | Original signing/configuration material, client configuration, authentication policies and receipts |
| privacyIDEA | Fresh PostgreSQL dump, original encryption key, matching original audit RSA key pair, current configuration and required durable files |
The encrypted manifest records exact files and capture intervals. This is not
a global atomic snapshot of all services. Historical backups and selected
notification/log files are excluded as documented by the capture profile.
The backup contains sensitive service credentials; only ciphertext belongs on
ordinary persistent recovery media. The age private key is never in the bundle.
## Restoring working services
**Archive recovery passed on 2026-10-16. Isolated restored-service recovery passed
for LLDAP, Authelia, privacyIDEA (MFA and audit), KeyCape (issuer side) and OpenBao
by 2026-10-24, and was owner-accepted in builder mode on 2026-10-27 (NK-WP-0049).**
A joint restore with a live consumer login is still a pre-production drill
(NK-WP-0055-T02/T03). The archive verifier deletes its plaintext and is not a
service importer. Follow `docs/identity-service-restore-packet.md` and the
[identity restore drill](identity-restore-drill.md) for a service drill.
Restore the exact matching data/config/original keys into an isolated recovery
environment first. Keep production ingress, public DNS, notifications, external
provider writes and real consumer traffic disconnected. Establish directory and
database readiness; prove LLDAP existing-user authentication; then privacyIDEA
retained-token MFA/audit continuity, Authelia storage/OIDC, and KeyCape/current
consumer continuity according to their actual dependencies. Existing user/MFA
inputs stay in operator custody. Public issuer identity must be preserved while
test traffic is routed privately; do not change production DNS for a drill.
Only after owner acceptance of native positive/negative tests should the owners
approve a production cutover, including target identity, rollback, DNS/ingress,
current consumers, credential dispositions and monitoring. This guide does not
authorize a production restore or automatic credential rotation.
## Reaching the platform when SSO and OpenBao are down
The normal admin paths need the services being recovered: KeyCape OIDC with
privacyIDEA MFA for OpenBao and portals, and ops-warden SSH certificates, which
are signed by OpenBao. With those down, only paths built on offline material work:
- **Cluster:** the attended admin kubeconfig (`~/.kube/config-railiance01`,
client certificate) through the `k3s-api-railiance01` tunnel, which uses a
static SSH key. Today both are on the founder workstation only.
- **OpenBao:** unseal with the shares (crisis list item 3). If an admin token is
needed while OIDC is down, the shares mint a short-lived root token with the
`generate-root` ceremony in `railiance-platform/docs/openbao.md` ("Break-glass:
OpenBao with SSO down"). On production it runs against the loopback-only
listener `127.0.0.1:8210`, reached with `kubectl port-forward` (live since
2026-10-28, RAPP-OPENBAO-WP-0003). Revoke the token afterwards.
- **Host:** console and rescue access through the Host Europe provider
interface, which is independent of the platform.
- **Workstation lost:** an age-encrypted copy of the cluster break-glass
material is kept on the Storage Share `Backup` account under
`railiance-backups/net-kingdom-kingpath/` (`tools/bootstrap-recovery/kingpath-offsite.py`,
NK-WP-0055-T13); decrypting it needs only crisis list items 1 and 2.
Every use is a break-glass event: record what was done and why for review.
Item-level custody and open gaps are in the crisis custody list; drilling this
path is NK-WP-0055-T13.
## Who and what unlocks recovery
The [crisis custody list](crisis-custody-list.md) names every item that unlocks recovery, who holds
it and whether it still works with SSO and OpenBao down. It holds no secret values.
## Schedule, protection and limits
Daily capture is scheduled at 03:45 UTC with up to five minutes jitter. Hourly
host-local checks flag a backup older than 25 hours. Retention protects the
independently recovered pinned generation, newest generation, seven distinct
daily and two weekly points; unknown/unindexed objects are not auto-deleted.
At this revision the full identity backup has one provider copy on Storage Share.
Existing Scaleway database backups do not establish a second full identity copy.
Host-local health cannot report a dead host. Full service login/MFA/audit proofs,
an independently executed outage-path retrieval drill, a second provider copy
and external host-loss monitoring must not be assumed from an upload or this
guide. Track remaining drills in NK-WP-0053 and production readiness in
NK-WP-0055; current task state lives in the repository and may be newer than
this offline guide.

View file

@ -0,0 +1,43 @@
{
"owner_source_commit": "f6856cdb53c051e35c984bad927395b7e8a8e5e0",
"orientation": {
"package_version": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"start_source_status": "withheld; source changed",
"source_review": "AGENTS/INTENT/SECURITY-COMPANION and relevant actual validator/acknowledgment/workplan reviewed; no guidance or authority inferred"
},
"tests_passed": 43,
"default11_type_status": "acknowledged",
"explicit12_type_status": "acknowledged",
"both_ordinary_admission_refused": true,
"actual_original_and_staged11_cli_render_bytes_identical": true,
"default11_render_digest": "sha256:e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a",
"version12_preparation_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
"canonical11_files": {
"security-case-types/oidc-browser-relying-party/README.md": "c01ec1a3f058eab9e6c461e898166d496adb8542ea46f9e198ced4de83b997ef",
"security-case-types/oidc-browser-relying-party/decision/build_intake.py": "d214019a10801e14d7986474ff83370482116df6a0fdde6ecb255d75645b3b04",
"security-case-types/oidc-browser-relying-party/decision/intake.json": "a0ab9c57c71359b9ed31b9d6b04fd464db3e41bb6fe6f39bc6ac0b85556765e0",
"security-case-types/oidc-browser-relying-party/decision/packet.md": "452ea42168ddef64dc5498ba30ae63ff2ff47a3919f55e22f6379d668ea76c28",
"security-case-types/oidc-browser-relying-party/examples/example-public-portal.yaml": "41cc5bb4842209f5f86830d1e8b4b010c6ef06a050c4160faae06a1dfcd9b6e9",
"security-case-types/oidc-browser-relying-party/examples/forgejo.yaml": "96c8dcd4b4182893b3a005137b880c0a80d4aa07a2668b29964e1db33605e050",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party.py": "7670ad0e415ca9941255bbc111b91fb5c9c3ae41851096d53d85fe7ba1d8660f",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party_contract.py": "ed30cd00ae22c32fbe2184443a2a8a7775ea5c231d1dd8070f230d547051f4e0",
"security-case-types/oidc-browser-relying-party/type.yaml": "f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c"
},
"historical_accepted11_case_sha256": "bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d",
"acknowledged12_files": {
"security-case-type-revisions/1.2/oidc-browser-relying-party/README.md": "4c82d9517d423ada75e680a05b0ecc6dfa0bf9f877a92b68f3ab93c1a86e405e",
"security-case-type-revisions/1.2/oidc-browser-relying-party/decision/acknowledgment.json": "2dcc220febf125a17b3769f7014f18b63979d7121e0f7056a2b0d8b1ae6031b0",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/example-public-portal.yaml": "8d6457248415daa6fc509da4e838aae0b66d7808e9da8e00f4b85a1e7faed10d",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml": "1c868c1da7d9077fae97bc6bc029706ec3dc418cfeaae30dc0f92b5b4ba6f2ab",
"security-case-type-revisions/1.2/oidc-browser-relying-party/tests/test_source_contract.py": "4a958eb2d0b90c1ffd08300d3299e4c31c827dcd7fb3cb4c22df91cc29986124",
"security-case-type-revisions/1.2/oidc-browser-relying-party/type.yaml": "9e60ff569fb0d860a2bbde7bbb0991a54d4b0688f62b56df0fa5d161fd624578"
},
"provider_declaration_inputs": {
"/home/worsch/net-kingdom/capabilities/playbooks/net-kingdom.keycape-client-registration.yaml": "cd81e02c283d514492b7a99062c9ede59a7ae124736de1270a121bf0a96a984e"
},
"ordinary_admission_or_type_implemented_or_case_accepted_or_native_completed": false,
"native_or_credentials_accessed": false,
"formal_net_owner_review": false,
"NK_WP_0048_T06_remains_wait": true
}

View file

@ -0,0 +1 @@
{"memo_id": "infd-20261011-forgejo-type12-source-r1", "version": 1, "register_event_id": "66d85468-6cd9-4a40-8c35-febc152758c0", "revision_event_id": null, "publication_event_id": "cbe4a05f-c7ab-4770-8e6e-8b537b165f7e", "review_event_id": "5be59cf9-a207-43aa-9fe2-131dc1e53538", "review_memo_id": "review-126c6c90-11ba-4609-b8d1-75e054c246cd", "queue_status": "pending", "queue_detail": "queued", "workspace_url": "https://decisions.coulomb.social/decisions/infd-20261011-forgejo-type12-source-r1?version=1", "outcome_review": {"intake_state": "not_submitted", "memo_id": "review-126c6c90-11ba-4609-b8d1-75e054c246cd", "stale": false}, "human_dispositions": 0}

View file

@ -0,0 +1 @@
{"memo_id": "infd-20261011-forgejo-type12-source-r1", "review": {"intake_state": "ready", "memo_id": "review-126c6c90-11ba-4609-b8d1-75e054c246cd", "path": "/review?memo_id=review-126c6c90-11ba-4609-b8d1-75e054c246cd", "stale": false}, "disposition_count": 1, "dispositions": [{"id": null, "approval_id": null, "submission_state": "confirmed", "disposition": {"actor": {"kind": "person", "sub": "uid=platform-root,ou=people,dc=netkingdom,dc=local"}, "approval_id": "8e9332c8-754a-420c-b8b7-445cb4fe6765", "at": "2026-10-11T06:47:39Z", "id": "disp-315d54ad-20f1-40e6-862c-6c7d2e8f0ea8", "memo_id": "review-126c6c90-11ba-4609-b8d1-75e054c246cd", "memo_version": 1, "note": null, "presentation_id": "pres-5a52f932-8c65-46cc-8054-0264f47f934a", "reasons": [], "verb": "accept"}, "approved_at": "2026-10-11T06:47:39+00:00", "decided_at": null}]}

View file

@ -0,0 +1,18 @@
{
"acknowledged_type_sha256": "9e60ff569fb0d860a2bbde7bbb0991a54d4b0688f62b56df0fa5d161fd624578",
"only_type_status_semantic_delta": true,
"full_package_validator_pass": true,
"independent_acknowledged_package_tests_passed": 22,
"closed_schema_checks_pass": true,
"explicit_version_root_loads_exact12": true,
"default11_type_and_case_byte_unchanged": true,
"canonical_case12_proposed_no_acceptance": true,
"ordinary_admission_refused": true,
"full_render_regeneration_identical": true,
"full_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
"execution_permitted": false,
"packet_bound_files_mutated": false,
"genuine_native_human_source_accept_confirmed": true,
"human_approval_id": "8e9332c8-754a-420c-b8b7-445cb4fe6765",
"new_native_action_by_worker": false
}

View file

@ -0,0 +1,29 @@
# v0.7 immutable publication preservation — 2026-10-11
This archive retains exact previously served revision HTML and its original
canonical Markdown. It changes no decision, normative rule or v0.7 source bytes.
The page source commit is ce198fc2905687ea90a2892346b6860281ac87f8; the original
MD SHA-256 is 8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b,
and served HTML SHA-256 is 7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7.
Current canon MD remains byte-identical. The original MD was independently
extracted from that clean Git commit and compared with current canon.
A clean v0.8 publication build re-rendered v0.7 using new repository HEAD
provenance and therefore changed the immutable page. That failed comparison is
preserved in provenance.json. Earlier local checks had not compared the actual
served bytes. PolicyNexus now explicitly freezes this same-current content
revision, while the canonical v0.7 route may show its superseded notice and
current clean source provenance. Publication manifest pins exact archive hashes;
source/page/provenance mismatch or a changed current MD under revision 0.7 fails
closed, as does a conflicting existing output page. Nothing is hand-edited into
the served HTML. This is local preparation; publication/live verification remains
separate and no serving result is claimed here.
Validation: all 33 PolicyNexus unit checks pass, including new same-current
freeze/new HEAD and source-change refusal regressions. Both new cases fail
against the original helper. An actual 73-document clean build from exact HEAD
exports plus reviewed archive inputs preserves the served7d7a38a8 HTML byte for
byte, retains original ce198fc2 source provenance, shows canonical superseded
notice with current0e17d95 provenance, and serves a separate accepted v0.8 page.
An existing conflicting10a7f7f7 output is rejected without replacing it. See
validation.json for exact hashes, exported inputs and limits.

View file

@ -0,0 +1,14 @@
{
"source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8",
"original_source_path": "canon/standards/security-layer-model_v0.7.md",
"source_digest": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b",
"served_page_url": "https://policy.coulomb.social/standards/security-layer-model/v0.7/revisions/0.7/index.html",
"served_page_digest": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7",
"capture_file": "/tmp/security-layer-v07-served-immutable.html",
"current_owner_source_exactly_matches_original_commit": true,
"previous_failed_clean_build_page_digest": "10a7f7f721f90a903bb4dc4aa7613536207be2fd71a29460a86d3159b8c8eecd",
"failure": "Current-source HEAD provenance re-render changed already-served immutable HTML although MD unchanged; existing source tests missed external served bytes.",
"repair": "NetKingdom retains exact served bytes plus original MD; publication pins both; helper permits same-current only with exact MD equality and preserves all existing conflict checks",
"source_decision_changed": false,
"deployed": false
}

View file

@ -0,0 +1,461 @@
<!doctype html>
<html lang="en"><meta charset="utf-8">
<meta name="policy-source-revision" content="ce198fc2905687ea90a2892346b6860281ac87f8">
<meta name="policy-source-digest" content="8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b">
<title>NetKingdom Security Layer Model v0.7</title>
<style>
:root{
--paper:#EDEEF0; --surface:#F6F7F8; --surface-2:#E4E6E9;
--ink:#171D24; --ink-2:#4A5561; --ink-3:#737E8A;
--rule:#D3D7DC; --rule-strong:#B6BCC3;
--brass:#8A6A2E; --brass-soft:#EFE5CD; --brass-line:#C9AE74;
--clay:#8A3A2C; --clay-soft:#F2DFDA;
--l0:#DCE0E2; --l1:#B9C4C7; --l2:#8CA1A6; --l3:#567D84; --l4:#23555E;
--chip-fg:#F6F7F8;
--font-display:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
--font-body:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--font-mono:ui-monospace,"SF Mono","Cascadia Code",Menlo,Consolas,monospace;
--measure:66ch;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
}
:root[data-theme="dark"]{
--paper:#12161A; --surface:#191E24; --surface-2:#222831;
--ink:#E6E9EC; --ink-2:#A3ADB7; --ink-3:#78838E;
--rule:#2A3138; --rule-strong:#3B444D;
--brass:#C9A45C; --brass-soft:#33290F; --brass-line:#6B5426;
--clay:#D08A76; --clay-soft:#3A211B;
--l0:#262C32; --l1:#35424A; --l2:#4A626B; --l3:#6A939D; --l4:#97C4CD;
--chip-fg:#12161A;
}
*{box-sizing:border-box}
body{
margin:0; background:var(--paper); color:var(--ink);
font-family:var(--font-body); font-size:17px; line-height:1.62;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1180px;margin:0 auto;padding:0 24px 96px}
.layout{display:grid;grid-template-columns:180px minmax(0,1fr);gap:56px;align-items:start}
@media (max-width:960px){.layout{grid-template-columns:1fr;gap:0}.rail{display:none}}
/* ---------- rail ---------- */
.rail{position:sticky;top:28px;padding-top:8px;font-family:var(--font-display);font-size:12px;line-height:1.5}
.rail ol{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:7px}
.rail a{color:var(--ink-3);text-decoration:none;display:flex;gap:9px}
.rail a:hover,.rail a:focus-visible{color:var(--brass)}
.rail .n{font-family:var(--font-mono);font-size:10px;color:var(--rule-strong);min-width:16px;padding-top:1px}
.rail .grp{margin-top:14px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--rule-strong)}
/* ---------- header ---------- */
header{padding:64px 0 40px;border-bottom:2px solid var(--ink);margin-bottom:44px}
.eyebrow{font-family:var(--font-mono);font-size:11.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);display:flex;flex-wrap:wrap;gap:14px;margin-bottom:22px}
.eyebrow .stat{color:var(--clay)}
h1{font-family:var(--font-display);font-weight:800;letter-spacing:-.035em;line-height:.94;font-size:clamp(46px,9vw,92px);margin:0 0 6px;text-wrap:balance}
.sub{font-family:var(--font-display);font-weight:500;font-size:clamp(16px,2.4vw,21px);letter-spacing:-.01em;color:var(--ink-2);margin:0 0 30px;max-width:34ch;line-height:1.3}
.metagrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px 28px;border-top:1px solid var(--rule);padding-top:20px}
.metagrid dt{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.metagrid dd{margin:0;font-family:var(--font-display);font-size:13.5px;line-height:1.45;color:var(--ink)}
/* ---------- typography ---------- */
section{margin-bottom:60px;scroll-margin-top:24px}
h2{font-family:var(--font-display);font-weight:750;letter-spacing:-.022em;font-size:clamp(24px,3.4vw,31px);line-height:1.12;margin:0 0 18px;text-wrap:balance;display:flex;gap:14px;align-items:baseline}
h2 .sn{font-family:var(--font-mono);font-size:12px;font-weight:400;color:var(--brass);letter-spacing:.06em;flex:none;padding-top:2px}
h3{font-family:var(--font-display);font-weight:700;font-size:16px;letter-spacing:-.008em;margin:34px 0 10px;color:var(--ink)}
p{margin:0 0 15px;max-width:var(--measure)}
ul,ol{max-width:var(--measure);margin:0 0 15px;padding-left:20px}
li{margin-bottom:7px}
strong{font-weight:600}
em{font-style:italic}
code{font-family:var(--font-mono);font-size:.855em;background:var(--surface-2);padding:1px 5px;border-radius:2px}
a{color:var(--brass)}
.lede{font-size:19px;line-height:1.55;color:var(--ink-2);max-width:60ch}
/* ---------- devices ---------- */
.callout{border-left:3px solid var(--brass);background:var(--brass-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.callout p:last-child{margin-bottom:0}
.callout .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
.rule-quote{border-top:2px solid var(--ink);border-bottom:2px solid var(--ink);padding:26px 0;margin:28px 0;max-width:var(--measure)}
.rule-quote p{font-family:var(--font-display);font-weight:600;font-size:19px;line-height:1.38;letter-spacing:-.014em;margin:0;text-wrap:balance}
.hard{border-left:3px solid var(--clay);background:var(--clay-soft);padding:18px 22px;margin:0 0 24px;max-width:var(--measure)}
.hard .lbl{font-family:var(--font-mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--clay);display:block;margin-bottom:8px}
.hard p:last-child{margin-bottom:0}
.dec{font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;color:var(--brass);text-transform:uppercase}
.vec{font-family:var(--font-mono);font-size:.9em;font-weight:600;background:var(--surface-2);padding:2px 7px;border-radius:2px;white-space:nowrap;letter-spacing:.04em}
/* ---------- tables ---------- */
.scroll{overflow-x:auto;margin:0 0 24px;-webkit-overflow-scrolling:touch}
table{border-collapse:collapse;width:100%;min-width:520px;font-family:var(--font-display);font-size:13.5px;line-height:1.45}
th{text-align:left;font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;padding:0 16px 8px 0;border-bottom:1px solid var(--rule-strong);vertical-align:bottom}
td{padding:11px 16px 11px 0;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2)}
td:first-child{color:var(--ink);font-weight:600}
tbody tr:last-child td{border-bottom:none}
.lvl{font-family:var(--font-mono);font-weight:600;font-size:12px;letter-spacing:.04em;color:var(--ink)}
/* ---------- ladders ---------- */
.breakout{margin:34px 0 40px}
.bhead{display:flex;justify-content:space-between;align-items:baseline;gap:20px;border-bottom:1px solid var(--rule-strong);padding-bottom:9px;margin-bottom:22px;flex-wrap:wrap}
.bhead h3{margin:0;font-size:13px;letter-spacing:.1em;text-transform:uppercase;font-family:var(--font-mono);font-weight:400;color:var(--ink-3)}
.bhead .note{font-family:var(--font-display);font-size:12.5px;color:var(--ink-3)}
.ladders{display:grid;gap:26px}
.ladder{display:grid;grid-template-columns:126px minmax(0,1fr);gap:18px;align-items:start}
@media (max-width:700px){.ladder{grid-template-columns:1fr;gap:10px}}
.ladder .pname{font-family:var(--font-display);font-weight:700;font-size:14px;letter-spacing:-.01em;padding-top:2px}
.ladder .pname span{display:block;font-family:var(--font-mono);font-size:10px;font-weight:400;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:3px}
.rungs{display:grid;gap:3px;grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:700px){.rungs{grid-template-columns:repeat(2,minmax(0,1fr))}}
.rung{padding:9px 10px 11px;background:var(--surface);border-top:4px solid var(--l0);min-width:0}
.rung.r1{border-top-color:var(--l1)} .rung.r2{border-top-color:var(--l2)}
.rung.r3{border-top-color:var(--l3)} .rung.r4{border-top-color:var(--l4)}
.rung .code{font-family:var(--font-mono);font-size:11px;font-weight:600;letter-spacing:.06em;color:var(--ink);display:block;margin-bottom:4px}
.rung .txt{font-family:var(--font-display);font-size:11.5px;line-height:1.34;color:var(--ink-2);display:block}
.rung.na{opacity:.42}
/* ---------- matrix ---------- */
.matrix-shell{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:stretch;margin-bottom:14px}
.ylab{writing-mode:vertical-rl;transform:rotate(180deg);font-family:var(--font-mono);font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3);text-align:center;padding-bottom:22px}
.mgrid{display:grid;grid-template-columns:34px repeat(5,minmax(0,1fr));gap:3px}
.mcell{background:var(--surface);min-height:60px;padding:6px;display:flex;flex-direction:column;justify-content:flex-end;gap:4px;min-width:0}
.mcell.tint1{background:color-mix(in srgb,var(--l1) 26%,var(--surface))}
.mcell.tint2{background:color-mix(in srgb,var(--l2) 26%,var(--surface))}
.mcell.tint3{background:color-mix(in srgb,var(--l3) 24%,var(--surface))}
.mcell.tint4{background:color-mix(in srgb,var(--l4) 22%,var(--surface))}
.mcell.void{background:repeating-linear-gradient(135deg,transparent,transparent 5px,var(--rule) 5px,var(--rule) 6px);opacity:.55}
.rlab,.clab{font-family:var(--font-mono);font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--ink-3);display:flex;align-items:center;justify-content:center}
.rlab{min-height:60px}
.clab{padding-top:7px;min-height:22px}
.pin{font-family:var(--font-mono);font-size:9.5px;font-weight:600;letter-spacing:.02em;background:var(--ink);color:var(--paper);padding:2px 5px;border-radius:2px;line-height:1.3;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pin.ghost{background:transparent;color:var(--ink-2);border:1px dashed var(--rule-strong)}
.mnote{display:flex;gap:22px;flex-wrap:wrap;font-family:var(--font-display);font-size:12px;color:var(--ink-3);padding-top:6px}
.mnote .k{display:flex;align-items:center;gap:7px}
.sw{width:13px;height:13px;flex:none;background:var(--ink)}
.sw.g{background:transparent;border:1px dashed var(--rule-strong)}
.sw.v{background:repeating-linear-gradient(135deg,transparent,transparent 4px,var(--rule) 4px,var(--rule) 5px);border:1px solid var(--rule)}
@media (max-width:640px){
.mgrid{grid-template-columns:28px repeat(5,minmax(0,1fr))}
.mcell{min-height:52px;padding:4px}
.pin{font-size:8px;padding:1px 3px}
.rlab{min-height:52px}
}
/* ---------- methodology ---------- */
.verbs{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule)}
.verb{background:var(--surface);padding:18px 18px 20px}
.verb h4{font-family:var(--font-display);font-weight:750;font-size:15px;margin:0 0 7px;letter-spacing:-.01em}
.verb p{font-family:var(--font-display);font-size:12.5px;line-height:1.46;color:var(--ink-2);margin:0;max-width:none}
.verb .step{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.13em;color:var(--brass);display:block;margin-bottom:9px}
/* ---------- questions ---------- */
.qs{display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule-strong)}
.q{display:grid;grid-template-columns:34px minmax(0,1fr) 170px;gap:18px;padding:16px 0;border-bottom:1px solid var(--rule);align-items:start}
@media (max-width:760px){.q{grid-template-columns:28px minmax(0,1fr);gap:12px}.q .owner{grid-column:2}}
.q .qn{font-family:var(--font-mono);font-size:11px;color:var(--brass);padding-top:3px}
.q .qt{font-family:var(--font-display);font-size:14px;line-height:1.48;color:var(--ink-2)}
.q .qt b{color:var(--ink);font-weight:700;display:block;margin-bottom:2px;font-size:14.5px}
.owner{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;color:var(--ink-3);padding-top:4px}
.owner .tag{display:inline-block;border:1px solid var(--rule-strong);padding:2px 7px;border-radius:2px}
.owner .tag.need{border-color:var(--clay);color:var(--clay)}
/* ---------- misc ---------- */
.numbers{font-family:var(--font-mono);font-size:12.5px;line-height:1.85;background:var(--surface);border-left:3px solid var(--l3);padding:16px 20px;margin:0 0 22px;overflow-x:auto;max-width:var(--measure)}
.numbers .v{color:var(--ink);font-weight:600}
.numbers .k{color:var(--ink-3)}
pre{font-family:var(--font-mono);font-size:12.5px;line-height:1.68;background:var(--surface);border-left:3px solid var(--rule-strong);padding:16px 20px;overflow-x:auto;margin:0 0 22px;max-width:var(--measure);color:var(--ink-2)}
.alt{border-bottom:1px solid var(--rule);padding:14px 0;max-width:var(--measure)}
.alt:last-of-type{border-bottom:none}
.alt b{font-family:var(--font-display);font-size:14px;display:block;margin-bottom:3px}
.alt p{font-size:14.5px;margin:0;color:var(--ink-2)}
.alt .verdict{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--clay)}
footer{border-top:2px solid var(--ink);margin-top:20px;padding-top:22px;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-3);display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap}
.tm td,.tm th{text-align:center}
.tm td:first-child,.tm th:first-child{text-align:left}
.yes{color:var(--l4);font-weight:700}
.no{color:var(--clay);font-weight:700}
.kind{font-family:var(--font-mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;padding:2px 6px;border-radius:2px;white-space:nowrap;border:1px solid var(--rule-strong);color:var(--ink-3)}
.kind.adv{border-color:var(--clay);color:var(--clay)}
.routes{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:2px;background:var(--rule);border:1px solid var(--rule);margin:0 0 22px}
.route{background:var(--surface);padding:16px 18px}
.route h4{font-family:var(--font-display);font-weight:750;font-size:14px;margin:0 0 6px}
.route p{font-family:var(--font-display);font-size:12.5px;line-height:1.45;color:var(--ink-2);margin:0;max-width:none}
.route .tag{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--brass);display:block;margin-bottom:8px}
a:focus-visible,.rail a:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
<div class="wrap"><header><div class="eyebrow"><span>netkingdom-security-layer-model-v0.7</span> <span class="stat">accepted</span> <span>gate-house</span> <span>reviewed 2026-08-28</span><span>generated from canonical source — do not edit</span></div><h1>NetKingdom Security Layer Model v0.7</h1><p class="sub">Source: <code>net-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8</code></p><p class="sub">Review due: 2026-11-28</p></header><div class="layout"><nav class="rail" aria-label="Sections"><ol><li><a href="#s1"><span class="n">1</span>Purpose</a></li><li><a href="#s2"><span class="n">2</span>Authority and conformance</a></li><li><a href="#s3"><span class="n">3</span>The layers</a></li><li><a href="#s4"><span class="n">4</span>Layer catalog</a></li><li><a href="#s5"><span class="n">5</span>The binding rule</a></li><li><a href="#s6"><span class="n">6</span>One decision point</a></li><li><a href="#s7"><span class="n">7</span>Relationship to the Active Secrets Management Canon</a></li><li><a href="#s8"><span class="n">8</span>Vocabulary demarcations</a></li><li><a href="#s9"><span class="n">9</span>Capability assignment</a></li><li><a href="#s10"><span class="n">10</span>Changing layer</a></li><li><a href="#s11"><span class="n">11</span>Conformance</a></li><li><a href="#s12"><span class="n">12</span>The conformance loop</a></li><li><a href="#s13"><span class="n">13</span>Open gaps</a></li><li><a href="#s14"><span class="n">14</span>Adoption</a></li><li><a href="#s15"><span class="n">15</span>Change log</a></li><li><a href="#s16"><span class="n">16</span>Open questions</a></li><li><a href="#s17"><span class="n">17</span>Taxonomy artifacts</a></li><li><a href="#s18"><span class="n">18</span>Composition with the sibling standards</a></li><li><a href="#s20"><span class="n">20</span>The Railiance interaction boundary</a></li></ol></nav><main><section id="s1"><h2><span class="sn">01</span>Purpose</h2>
<p>This standard states how NetKingdom's IT-security estate is layered, and what each layer may and may not do. It answers one question:</p>
<div class="rule-quote"><p><strong>Given a repository, which layer is it in, and what does that permit it to own?</strong></p></div>
<p>The layers are distinguished by <strong>determinism</strong> and by <strong>the kind of artifact the layer produces</strong>, not by technical tier, deployment topology, or team.</p>
<p>It is not an org chart, not a network model, not a deployment topology, and not a dependency graph. It does not assign work, and it does not replace any repository's boundary contract; it constrains what such a contract may claim.</p>
<p><strong>What changed in v0.7.</strong> v0.6 announced a rule it never wrote: §1 and §15 said the standard separates human and agent principals inside Staff, and §3.4 was byte-identical to v0.5. <code>kings-guard</code> found it and put it correctly — <em>a rule stated about a standard in its own change log is not a rule</em>, which is §11's own principle turned on the standard. §3.4 is now written.</p>
<p>The rest are collisions between rules written for the clean case: §6.4's first obligation forbade what its third obligation blesses, and its second forbade the session-bound allow §9.7.1 permits. §9.4's atomicity was described as closing a threat it does not close. §19 graded the document it lived in. And §20 records the interaction boundary with Railiance operations, on definitions from <code>railiance-master</code> rather than inference. §15 records the change list.</p>
<p><strong>What changed in v0.6.</strong> An independent assessment against industry practice (<code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>) found the model sound as a layering constitution and incomplete as a <em>self-healing</em> one: cognition, authority, and execution are specified, but the two verbs that close a healing loop — observe in production and actuate through a deterministic surface — are pending, and one is unstaffed. It also found the Engine layer untyped, so that "we need an engine for X" drifts toward "X now decides", and the enforcement point unnamed.</p>
<p>v0.6 types the engines (§3.3), names the enforcement point (§6.4), replaces the containment assignment with an actuation surface held at zero (§9.2), separates human and agent principals inside Staff (§3.4), puts time into the model (§9.7), requires the Taxonomy artifacts that make §6.2 compileable rather than reviewable (§17), and composes the sibling standards it had only cited (§18). Section numbers below §14 are unchanged: the estate cites them.</p>
<p><strong>What changed in v0.5.</strong> All four reviewing repositories returned findings on v0.4, and one contested a rule. §9.3 was wrong: it collapsed <em>engine reachable but degraded</em> with <em>engine not reachable at all</em>, and the second case has no evaluator in the path to express anything. §9.1 collapsed <em>no route exists</em> with <em>route exists under a declared gap</em>, which would have forced a false "pending" onto a production capability. §11 claimed mechanical checkability for a rule that cannot be checked in prose. §13 filed two opposite conformance states in one table and recorded proposed owners as owners. §9.6 needed the load-bearing distinction it implied but never drew. §15 records the change list.</p>
<p><strong>What changed in v0.4.</strong> <code>audit-core</code> assented to the approval evidence half and corrected the rationale twice. v0.3 rested §9.4 on that repository's INTENT principle 6, which is an aspiration; the shipped bound in its <code>docs/integrity.md</code> is weaker and conditional. More consequentially, no append-only archive can prove <strong>omission at source</strong> — a suppressed revocation leaves the chain intact — which is now stated as an estate-wide doctrine constraint (§9.6) rather than left implicit. <code>audit-core</code> was also referenced as an owner in v0.3 without appearing in the §4 catalog at all; it is catalogued here, as an Engine, on its own declaration. §15 records the change list.</p>
<p><strong>What changed in v0.3.</strong> Two engines were seeded to own concepts v0.2 recorded as unowned: <code>approval-engine</code> takes the approval object that §13 left homeless, and <code>maturity-engine</code> takes graded progression — closing a §9.1 defect in gate-house's own catalog claim, which asserted conformance review with no engine to act through. §15 records the change list. v0.3 is <strong>proposed</strong>: the two new engines are seeded by owner direction and have no other side to assent yet, and the evidence half of the approval split needs <code>audit-core</code>'s assent.</p>
<p><strong>What changed in v0.2.</strong> v0.1 was assented to by all three repositories whose boundaries moved, and each returned a finding. v0.1 had one lane for a Staff repository that legitimately touches Tooling — read-only diagnostics — which is narrower than the estate as it actually stands, and a rule with no lane for a real sanctioned case is satisfied by relabelling rather than by closing the gap. v0.1 also catalogued a capability (§4, containment) that §5 forbade discharging, and applied its reconstructability test to engines but not to the doctrine gate-house feeds them. §15 records the full change list.</p>
</section>
<section id="s2"><h2><span class="sn">02</span>Authority and conformance</h2>
<div class="scroll"><table><thead><tr><th>Fact or rule</th><th>Authority</th></tr></thead><tbody><tr><td>The layers, their definitions, and the rules between them</td><td>This standard, owned by gate-house</td></tr><tr><td>Which layer a given repository is in</td><td>This standard, §4 catalog</td></tr><tr><td>What a repository owns within its layer</td><td>That repository's <code>INTENT.md</code> and boundary contract</td></tr><tr><td>Whether a specific request is permitted</td><td><code>access-engine</code> — never this standard</td></tr><tr><td>Whether a Tooling contact is sanctioned</td><td>The declaring repository, under the shapes in §5, reviewable by gate-house</td></tr><tr><td>Security doctrine and invariants</td><td>gate-house</td></tr><tr><td>Publication</td><td>net-kingdom canon</td></tr><tr><td>Whether an invariant is <em>watched in practice</em></td><td>the observing repository's own report — never this standard, and never §12's diagram</td></tr></tbody></table></div>
<p>A repository conforms when its <code>INTENT.md</code> declares its layer, its claims fall within that layer's permissions (§3), and its Tooling contacts take one of the sanctioned shapes in §5 or are declared as gaps under §5.3.</p>
<p><strong>No estate argument may cite observation that has not happened.</strong> §12 lists <code>kings-guard</code> against the loop's fourth step, and that repository has reported that it has never observed a real event. Until it reports otherwise, no assessment, review, or decision in this estate may treat an invariant as being watched in practice on the strength of the diagram. Lifted here from §12 so it cannot be lost in a summary.</p>
</section>
<section id="s3"><h2><span class="sn">03</span>The layers</h2>
<div class="scroll"><table><thead><tr><th>Layer</th><th>Character</th><th>Produces</th><th>Deterministic</th></tr></thead><tbody><tr><td><strong>Taxonomy</strong></td><td>cross-cutting language</td><td>terms, semantic contracts, standards</td><td>n/a — describes</td></tr><tr><td><strong>Tooling</strong></td><td>infrastructure and state</td><td>data structures, persistence</td><td>yes</td></tr><tr><td><strong>Engines</strong></td><td>interfaces for a modeled concept</td><td>APIs, contracts</td><td>yes</td></tr><tr><td><strong>Staff</strong></td><td>management, operations, change, controlling</td><td>specifications, decisions, workplans, tasks</td><td><strong>no</strong></td></tr></tbody></table></div>
<h3>3.1 Taxonomy</h3>
<p>Cross-cutting language. Taxonomy repositories define terms and semantic contracts so the other layers interoperate without integration by interpretation. They own no runtime position and no state any layer depends on.</p>
<p><code>info-tech-canon</code> holds ecosystem-wide semantic contracts. NetKingdom-specific security architecture — including this standard — is net-kingdom canon's.</p>
<h3>3.2 Tooling</h3>
<p>Deterministic infrastructure: data structures, persistence, and the consistent, performant, scalable keeping of state. Much of it is third-party.</p>
<h3>3.3 Engines</h3>
<p>Deterministic APIs for a modeled concept — a user, a tenant, a zone, a secret, an access rule. An engine's defining property is that <strong>the same authoritative input state yields the same result</strong>. Engines are where the estate's deterministic guarantees live, and therefore where every enforcement boundary MUST sit.</p>
<p>A repository whose core function is inference or judgment fails this test by construction and is Staff, however much of its work happens at runtime.</p>
<p><strong>Engines are typed.</strong> "Engine" is one layer but four roles, and collapsing them hides different failure modes. Every §4 Engine row carries a role:</p>
<div class="scroll"><table><thead><tr><th>Role</th><th>Meaning</th><th>Outage means</th></tr></thead><tbody><tr><td><strong>PDP</strong></td><td>renders the authorization decision — <code>access-engine</code>, and only it (§6)</td><td>consumer residue (§9.3)</td></tr><tr><td><strong>PIP</strong></td><td>supplies facts a decision consumes as claims — user, tenant, zone, approval, maturity</td><td>input degradation, engine's own fallback (§9.3)</td></tr><tr><td><strong>Evidence</strong></td><td>records what happened and proves integrity of what it holds — <code>audit-core</code></td><td>MUST NOT block the operation being recorded — a default, not a property; see below</td></tr><tr><td><strong>Lifecycle</strong></td><td>a deterministic API over Tooling it owns — <code>secrets-engine</code></td><td>the owning engine's failure semantics</td></tr></tbody></table></div>
<p>The roles are why <em>"we need an engine for X"</em> does not mean <em>"X now decides"</em>. A new engine is a PIP unless this standard is amended to say otherwise, and §6 means it can never be a second PDP.</p>
<p>The Evidence row's outage rule is an estate <strong>trade</strong>, not a property of evidence engines. Choosing availability there means accepting that a compromised source can suppress a record and that detection is the answer (§9.6). The opposite shape — <em>do not proceed unless an independent custodian already holds the record</em> — is the only one that puts evidence outside the actor's blast radius <strong>before</strong> the act. The estate has not needed it, so it is not ruled out by a table cell: an operation whose control genuinely requires independent recording before effect is a declared exception, raised when needed. Raised by <code>audit-core</code> against its own row.</p>
<p>The industry vocabulary is deliberately mirrored here — PDP, PIP, PEP as in NIST ZTA and XACML — because it is how the estate talks to the outside and how a PEP is stopped from quietly becoming a PDP. The determinism cut in §3 stays primary where the two disagree.</p>
<h3>3.4 Staff</h3>
<p>Interactive and non-deterministic. Staff is the management layer: operations, change, innovation, and controlling. It works through agentic capability — assistants and autonomous agents — and its artifacts are specifications, decisions, workplans, and tasks.</p>
<p>Staff repositories MUST NOT hold state that another layer depends on at runtime, and MUST NOT render or cache any decision an Engine is responsible for.</p>
<p>Acting at runtime does not make a repository an Engine. Being agentic makes it Staff, and §5 governs how it acts.</p>
<p><strong>Two principals, one layer.</strong> Humans and agents are both non-deterministic and both Staff, so they share the layer's permissions. They do not share blast radius. Four rules bind the agent principal specifically:</p>
<ol><li><strong>No standing credential.</strong> An agent holds no long-lived credential of its own. Authority is issued per task, time-bounded under §9.7, and attributable to the principal on whose behalf it acts.</li><li><strong>Tool use is a conduit or an Engine API.</strong> An agent acts through §5.2 — the owner's tool under the caller's identity, presenting no authority of its own — or through an engine. There is no third route. Tool availability is not permission: a callable tool means the operation exists, not that this actor may invoke it.</li><li><strong>Agent memory is not a state plane.</strong> Agent memory, tool-call traces, and prompt caches are the agent's own. They MUST NOT become state another layer depends on at runtime unless catalogued as Tooling in §4, which subjects them to §5 like anything else, and to §5's sunset.</li><li><strong>Every agent action is reconstructable as the caller's action</strong>, bounded by §9.6 — the archive shows the actions it received, not that it received all of them.</li></ol>
<p>Session semantics — session loops, tool policy, harness routing, model selection — are <strong>not</strong> governed here. They belong to <code>glas-harness</code> and its <code>rein-*</code> backends (§20). This standard governs what an agent may be authorized to do; <code>glas-harness</code> governs how an agent session is conducted. Rule 2 is the seam between them, and neither side may treat its own half as sufficient.</p>
<p>v0.6 claimed these rules in its change log and did not write them. Found by <code>kings-guard</code>, which is the repository they bind hardest and which offered to assent to them sight-unseen.</p>
</section>
<section id="s4"><h2><span class="sn">04</span>Layer catalog</h2>
<div class="scroll"><table><thead><tr><th>Repository</th><th>Layer</th><th>Role</th><th>Owns</th></tr></thead><tbody><tr><td><code>info-tech-canon</code></td><td>Taxonomy</td><td>—</td><td>ecosystem-wide semantic contracts and terminology</td></tr><tr><td><code>net-kingdom</code></td><td>Taxonomy</td><td>—</td><td>NetKingdom standards of record; publication</td></tr><tr><td><code>key-cape</code></td><td>Tooling</td><td>—</td><td>packaged identity tooling; IAM profile; authentication</td></tr><tr><td><code>OpenBao</code></td><td>Tooling</td><td>—</td><td>secret storage, leases, PKI, dynamic secret engines</td></tr><tr><td><code>user-engine</code></td><td>Engine</td><td>PIP</td><td>users, accounts, memberships</td></tr><tr><td><code>tenant-engine</code></td><td>Engine</td><td>PIP</td><td>tenant-as-an-entity facts</td></tr><tr><td><code>zone-engine</code></td><td>Engine</td><td>PIP</td><td>zone identity and membership — offline reference conformance per its 2026-08-23 disposition</td></tr><tr><td><code>secrets-engine</code></td><td>Engine</td><td>Lifecycle</td><td>credential abstraction, custody, lifecycle</td></tr><tr><td><code>audit-core</code></td><td>Engine</td><td>Evidence</td><td>audit event custody, retention, integrity verification, export — explicitly not a decision point (§9.6)</td></tr><tr><td><code>access-engine</code></td><td>Engine</td><td><strong>PDP</strong></td><td><strong>the policy decision</strong> — the only decision point (§6)</td></tr><tr><td><code>approval-engine</code></td><td>Engine</td><td>PIP</td><td>the approval object — durable, authenticated, consumable, atomically supersedable (§9.4)</td></tr><tr><td><code>maturity-engine</code></td><td>Engine</td><td>PIP</td><td>graded progression against declared criteria and evidence; the gap register; capability readiness (§9.5)</td></tr><tr><td><code>gate-house</code></td><td>Staff</td><td>—</td><td>security doctrine, authority context, curriculum; <strong>conformance review — through <code>maturity-engine</code> (§9.5)</strong></td></tr><tr><td><code>ops-mason</code></td><td>Staff</td><td>PEP-shaped</td><td>building and tearing down access routes and perimeters</td></tr><tr><td><code>ops-warden</code></td><td>Staff</td><td>PEP-shaped</td><td>operational access lanes, stewardship, runbooks; SSH certificate issuance — <strong>declared-gap</strong> (§9.1, §13)</td></tr><tr><td><code>kings-guard</code></td><td>Staff</td><td>—</td><td>adaptive defence and judgment; observation of Staff-reachable sources — identity and secret observation <strong>pending</strong>; <strong>proposes</strong> containment, which it does not own (§9.2)</td></tr><tr><td><code>whitehat-security</code></td><td>Staff</td><td>—</td><td>offensive validation</td></tr></tbody></table></div>
<p>An <strong>actuation surface</strong> — reduce authority, require step-up, isolate a workload — is catalogued nowhere because it does not exist. See §9.2: it is an Engine concept held at zero, not a Staff capability.</p>
<p><code>access-engine</code> is the ruled name for the repository currently called <code>flex-auth</code>; both denote the same authority until the governed rename completes. Execution conditions for that rename are recorded in its migration decision, not here.</p>
</section>
<section id="s5"><h2><span class="sn">05</span>The binding rule</h2>
<div class="rule-quote"><p><strong>Staff never touches Tooling directly. It acts only through Engine APIs.</strong></p></div>
<p>A Staff repository MUST NOT hold a direct client for a Tooling-layer system — no direct database connection, no direct OpenBao client, no direct cluster mutation — outside the shapes below. This is the architectural form of <em>no privilege from cognition</em>, and it is deliberately mechanically checkable.</p>
<p><strong>Scope.</strong> "Tooling-layer system" means a system catalogued as Tooling in §4. Infrastructure the estate runs but has not catalogued — the State Hub, <code>llm-connect</code>, and similar — is outside this rule, because a rule that silently covered them would put every Staff repository in undeclared violation on adoption day: they all write progress events. Such clients SHOULD be recorded in the repository's declaration as non-Tooling for completeness of the check, and the way to bring one under §5 is to catalogue it in §4, deliberately.</p>
<p>Raised by <code>ops-warden</code>, which held clients for both and declined to resolve the scope question on gate-house's behalf.</p>
<p><strong>The carve-out sunsets.</strong> It is a pressure valve, and a valve left open becomes a second persistence plane under the Staff layer — which §3.4 forbids in spirit. Three rules bound it: every non-Tooling client MUST be listed in the repository's declaration; an uncatalogued store that another layer <strong>reads</strong> MUST, within two review intervals, either be catalogued as Tooling in §4 or be declared a gap under §5.3; and a Staff-owned event bus or memory store MUST NOT become the estate's de facto state plane. Today's instances are the State Hub and <code>llm-connect</code>; tomorrow's are agent memory, tool-call traces, and prompt caches (§3.4).</p>
<p>Three shapes are sanctioned. Everything else is a violation.</p>
<h3>5.1 Read-only diagnostic observation</h3>
<p>A Staff repository MAY read Tooling state for diagnostics where the owning engine exposes no equivalent. It MUST be declared in the repository's <code>INTENT.md</code>. It grants no write, and it is an engine gap to close, not a standing arrangement.</p>
<h3>5.2 Conduit</h3>
<p>A Staff repository MAY run the <strong>owner's</strong> tool under the <strong>caller's</strong> identity, supplying no authority of its own. The test is the supplied-authority property: the conduit MUST NOT present its own credential, MUST NOT widen what the caller could already do, and MUST be reconstructable as the caller's action in audit.</p>
<p>A conduit that presents its own token is not a conduit; it is §5.3 or a violation. This shape MUST be declared, and the no-authority property SHOULD be covered by a test.</p>
<p>The reconstructability requirement is an audit-dependent claim and is therefore bounded by §9.6: the archive shows the conduit actions it received, not that it received all of them.</p>
<h3>5.3 Declared engine gap</h3>
<p>Where a Staff repository must contact Tooling directly and no engine exposes the capability, it MUST declare the contact rather than take an exemption. A declared gap carries, machine-readably:</p>
<div class="scroll"><table><thead><tr><th>Field</th><th>Meaning</th></tr></thead><tbody><tr><td><code>capability</code></td><td>what the contact does</td></tr><tr><td><code>intended_owner</code></td><td>the engine that should own it</td></tr><tr><td><code>blocked_on</code></td><td>why it cannot move today</td></tr><tr><td><code>review</code></td><td>a date, not "when convenient"</td></tr></tbody></table></div>
<p>A declared gap is <strong>tracked non-conformance</strong>, not conformance. It does not expire on its own and it is not a licence to add more. It exists because a rule offering no lane for a real sanctioned case gets satisfied by relabelling rather than by closing the gap — and a tracked gap is visible, whereas a relabelled one is not.</p>
<p>Prior art: <code>ops-warden</code> runs equivalent machinery for delegated lanes (27 catalog entries carrying <code>delegation:</code>, queryable via <code>warden route gaps</code>), and has offered it as reusable.</p>
<p><strong>No fourth "operator of third-party Tooling" shape.</strong> It has been proposed, on the argument that someone must operate <code>OpenBao</code> and every operational necessity otherwise looks like a gap. Declined: §5.3 already sanctions the operation while keeping it visible, and a clean "operator" shape would convert a tracked gap into a permanent allowance — the relabelling failure this standard exists to prevent. A permanent operational necessity is a declared gap whose review interval keeps returning, which is the correct amount of friction. If the review becomes ceremonial, that is an argument for closing the gap, not for renaming it.</p>
</section>
<section id="s6"><h2><span class="sn">06</span>One decision point</h2>
<p><code>access-engine</code> is the only policy decision point in NetKingdom. No other repository, in any layer, may render or cache authorization decisions.</p>
<p>First ruled in <code>zone-engine/INTENT.md</code> §5 — <em>"flex-auth is the policy decision point. It stays the only one."</em> The failure mode, from the same source: <em>"It becomes a second decision point… it would arrive as a small convenience."</em></p>
<h3>6.1 Compiled data that determines an outcome is still deciding</h3>
<p>A registry, cache, or schema that resolves a result before the engine runs has decided early. Provenance MUST remain reconstructable from the engine's decision record.</p>
<h3>6.2 Doctrine reaches the decision as an input, or it is not applied</h3>
<p>This rule binds gate-house on the same terms. <strong>An authority ceiling, mandate constraint, or operating-mode restriction that determines an outcome MUST reach the decision either as an input claim on the request or as a rule in the versioned policy package</strong>, so that its application is reconstructable from the decision record.</p>
<p>Doctrine that influences outcomes by any other route is a second decision point wearing an author's hat. This is not a limit on gate-house's authorship; it is what keeps that authorship auditable at decision time.</p>
<h3>6.3 No Staff repository may host a decision point</h3>
<p>A deterministic authority boundary inside a non-deterministic layer contradicts the invariant the estate is built on. gate-house was re-cut on this ground.</p>
<h3>6.4 The enforcement point</h3>
<p>The standard has been precise about the decision and silent about the gate. A decision that nothing refuses to proceed without is advice.</p>
<p>A <strong>PEP</strong> is any runtime that causes a protected side effect. It is a <em>shape</em>, not a repository: <code>ops-warden</code> issuing a certificate, <code>ops-mason</code> opening a route, and any protected system acting on a verdict are all PEP-shaped. Being PEP-shaped does not move a repository out of its layer.</p>
<p>Four obligations, and they are normative:</p>
<ol><li><strong>No side effect without a decision record, or a recorded stance.</strong> A PEP MUST NOT perform the protected action unless it holds a decision from <code>access-engine</code> identifying the request it was rendered for, <strong>or</strong> its declared §9.3 stance for the applicable scope permits proceeding without one <strong>and the application of that stance is recorded in place of the decision</strong>. The second limb is stricter than silence, not looser: a fail-open result is metadata, never an absent record. <code>ops-warden</code> <code>ca.py</code> writes the zone, the failure mode, and a decision id present only where a decision was rendered.</li></ol>
<p>v0.6's unqualified form made the shipped stance §9.3 sanctions into a violation — the same defect as v0.5's §9.1, a rule written for the clean case producing a false result on the adjacent case already sanctioned elsewhere. Raised by <code>ops-warden</code>, which is the reference shape for limb two.</p>
<ol><li><strong>No recaching of the verdict beyond its own binding.</strong> A stored verdict replayed <strong>outside the decision's stated binding and lifetime</strong> is a second decision point deciding early (§6.1). Within them it is the decision being used as issued — a session-bound allow under §9.7.1 is used across later requests by construction, and v0.6 forbade what §9.7.1 permits.</li></ol>
<p>The test is mechanical, not a matter of implementer judgement: replay is permitted <strong>iff the canonical request digest matches and the decision's lifetime holds</strong>. <code>access-engine</code> computes that digest over normalised subject, action, resource, and context, and it is already in every decision binding. A retry after a transport failure is therefore the same request; a different resource is not.</p>
<p><strong>Negative caching is permitted, narrowly.</strong> A cached DENY cannot manufacture authority — §8's asymmetry holds — and protects against retry storms. It is permitted where the refusal is itself recorded against the request that was refused (obligation 4), and where the cache lifetime is declared alongside the stance map. A stale deny is an availability failure and will be misdiagnosed as a policy one, so it must be visible as what it is. Ruled explicitly because it is the first thing an implementer under load reaches for. Raised by <code>access-engine</code>.</p>
<ol><li><strong>A declared unreachable-engine stance</strong> (§9.3): total, per zone or equivalent scope, no implicit default, no per-call discretion, published rather than held in code comments or in a dataclass default. <code>ops-warden</code> <code>ADR-0009</code> and its <code>pep-stance.yaml</code> are the reference shape.</li></ol>
<p>The published map MUST equal the shipped behaviour, and that equality SHOULD be asserted by a test. A published map free to drift from the code is worse than none, because it invites reliance it cannot support. Raised by <code>ops-warden</code>, which found its own map unpublished while being cited as the reference for this obligation.</p>
<ol><li><strong>Reconstructability</strong>, bounded by §9.6.</li></ol>
<p>Every PEP-shaped consumer MUST publish its stance map at a path named in its layer declaration, and those maps MUST be inventoried in the §13.1 register until <code>maturity-engine</code> can hold them. §9.3 is otherwise a ruling with no register behind it, and <em>"<code>z0</code>–<code>z2</code> and unknown fail open"</em> becomes the estate's real policy without anyone having compiled it into a versioned package.</p>
<p>v0.6 named a register that did not exist — a requirement whose register is missing is a capability catalogued without a surface, by §9.1's own logic. Raised independently by <code>ops-warden</code> and <code>access-engine</code>; §13.1 now exists, and its first inventory has one row, which is itself the finding.</p>
<p>Raised by the 2026-08-29 independent assessment: NIST ZTA splits decide from enforce, and this standard had only the first half.</p>
</section>
<section id="s7"><h2><span class="sn">07</span>Relationship to the Active Secrets Management Canon</h2>
<pre>Staff interactive, non-deterministic ≈ Cognitive Plane
Engines deterministic APIs ≈ Authority Plane
Tooling deterministic state ≈ Execution Plane
Taxonomy cross-cutting language</pre>
<p><em>Cognition proposes. Authority disposes. Infrastructure executes.</em> is therefore NetKingdom's layering rule, not only its security maxim. §5 and §6 are that principle applied to repositories rather than to requests.</p>
</section>
<section id="s8"><h2><span class="sn">08</span>Vocabulary demarcations</h2>
<div class="scroll"><table><thead><tr><th>Term</th><th>Belongs to</th><th>Not</th></tr></thead><tbody><tr><td><strong>access lane</strong></td><td>ops-warden, ops-mason (Staff) — how a worker reaches a host</td><td>the decision whether they may</td></tr><tr><td><strong>access rule</strong></td><td>access-engine (Engine) — whether an actor may act</td><td>the route by which they arrive</td></tr><tr><td><strong>control plane</strong></td><td>Engine layer</td><td>a Staff repository's self-description</td></tr><tr><td><strong>doctrine</strong></td><td>gate-house</td><td>a lane owner's runbook</td></tr><tr><td><strong>runbook</strong></td><td>the Staff repository stewarding the lane</td><td>a substitute for doctrine</td></tr><tr><td><strong>posture</strong></td><td>kings-guard publishes; gate-house defines its authority meaning; access-engine renders it</td><td>a privilege source</td></tr></tbody></table></div>
<p>Posture carries an asymmetry that MUST hold: adaptive systems may reduce authority, require step-up, or request containment. They MUST NOT probabilistically manufacture additional authority.</p>
<p>The asymmetry is what bounds the damage when observation is incomplete (§9.6): suppressed evidence can only prevent a tightening that should have happened, never engineer a loosening. That is an argument for keeping it absolute rather than situational.</p>
</section>
<section id="s9"><h2><span class="sn">09</span>Capability assignment</h2>
<h3>9.1 The catalog may not assign what the rules forbid discharging</h3>
<p>A Staff repository MUST NOT be catalogued in §4 as owning a capability it cannot discharge under these rules. Two marks distinguish the two ways that happens, and they are not interchangeable:</p>
<div class="scroll"><table><thead><tr><th>Mark</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>pending</strong></td><td>No route exists. No engine exposes the capability, the repository makes no Tooling contact, and the capability is <strong>zero</strong> — not degraded.</td></tr><tr><td><strong>declared-gap</strong></td><td>A route exists through a §5.3 declared gap. The capability <strong>works</strong> and is tracked, with an intended owner and a review date in §13.</td></tr></tbody></table></div>
<p>v0.4 had only <code>pending</code>, which forced a false choice. <code>ops-warden</code> holds production-verified SSH certificate issuance through a declared OpenBao contact; marking it <code>pending</code> would have told readers the repository does not do the one thing it demonstrably does daily, while leaving it unmarked left §4 disagreeing with §13. Neither is acceptable, and the defect was in this section rather than in the catalog.</p>
<p><code>pending</code> was written for <code>kings-guard</code>'s containment — no route, capability zero — and remains correct there. <code>declared-gap</code> is the case §5.3 was added to sanction. Raised by <code>ops-warden</code>.</p>
<p>Both marks apply per capability, not per repository. A repository may hold one capability outright, another under a declared gap, and a third pending.</p>
<h3>9.2 Actuation does not exist, and containment is not Staff's to own</h3>
<p>Self-healing needs four verbs: observe, evaluate, decide, actuate. Observation is <code>kings-guard</code> and is unstaffed (§12). Evaluation is <code>maturity-engine</code> and is seeded. Decision is <code>access-engine</code> and works. <strong>Actuation has no surface at all</strong>, and a model with no actuation surface describes a diagnosis machine rather than a healing one.</p>
<p>v0.5 marked containment <code>pending</code> against <code>kings-guard</code>, which was the right mark on the wrong repository. Containment is not a Staff capability that happens to lack a route: <strong>reduce authority, require step-up, isolate a workload</strong> are authority-changing operations, and under §6 an authority-changing operation is rendered by an Engine and enforced by a PEP (§6.4). A Staff repository proposes containment; it never performs it.</p>
<p>The <strong>actuation surface</strong> is therefore an Engine concept — likely a small surface on <code>access-engine</code> together with runtime PEPs — carrying the same reconstructability rules as any other decision: a containment action is a decision record, not a side channel.</p>
<p>It is <strong>unowned and held at zero</strong>. <code>access-engine</code> is recorded in §13 as a <em>proposed</em> owner and has explicitly not reviewed it (<code>FLEX-DEC-2026-002</code>). No repository may be catalogued as owning containment until the surface exists — §9.1 applied to the estate's most operationally tempting gap, and the standard's own medicine.</p>
<p>Until then <code>kings-guard</code> proposes and judges, its containment claim stays at zero rather than degraded, and no argument may assume the estate can contain anything automatically.</p>
<h3>9.3 Degraded mode: two failure cases, two owners</h3>
<p>v0.4 collapsed two failures into one rule. They have different owners because one has an evaluator in the path and the other does not.</p>
<p><strong>Input degradation — the engine's.</strong> Where <code>access-engine</code> is reachable but cannot reach its own inputs, the deterministic <em>fail to reduced authority</em> default belongs to the engine. This keeps the decision at the decision point and keeps the fallback deterministic, which a Staff-layer fallback could never be.</p>
<p><strong>Engine unreachable — necessarily the consumer's.</strong> Where <code>access-engine</code> is not reachable at all, it applies nothing, because it is not running. Whatever happens next is the consumer's behaviour by construction: fail-open is not expressible by a policy decision point, since there is no evaluator in the path to express it. A standard that assigns this to the engine assigns it to nobody.</p>
<p>The consumer's residue is bounded rather than free. A protected system MUST declare its unreachable-engine stance ahead of time, per zone or equivalent scope, and that stance MUST be auditable and total — no implicit default, no per-call discretion. <code>ops-warden</code> <code>ADR-0009</code> already satisfies this: a total per-zone map, open for <code>z0</code>–<code>z2</code> and unknown, closed for <code>z3-critical</code>, replacing the global <code>policy.enabled</code> / <code>policy.fail_closed</code> switches it superseded.</p>
<p><strong>Unchanged: engine-unavailable is not grounds for a Staff break-glass path.</strong> The distinction is whether an engine is there to ask. A bypass around a <em>reachable</em> engine is a second decision point, and an incident is when an attacker most wants that shortcut. A consumer choosing its declared behaviour when there is no engine to ask is not a bypass; it is the only thing left.</p>
<p>Contested by <code>flex-auth</code> (<code>FLEX-DEC-2026-002</code>), which has held since 2026-08-19 that fail-open is not expressible by a PDP, and which noted v0.4 collided with shipped behaviour in a repository that had assented to this standard.</p>
<h3>9.4 Approvals are an engine concept, not a Staff or audit concern</h3>
<p>The approval object — durable, authenticated entries, distinct-approver counting, atomic supersession, single consumption, revocation without holder cooperation — is owned by <code>approval-engine</code>.</p>
<p>It is not Staff's: §3.4 forbids Staff holding state another layer depends on at runtime. It is not the decision point's: an evaluator that owns the object it evaluates is self-dealing. It is not the audit fabric's: an approval needs mutable, in-path, current-state semantics, and an append-only archive is built for the opposite property.</p>
<p><code>access-engine</code> consumes approvals as <strong>input claims</strong> under §6.2 and never mutates them. Every issuance, use, supersession, and revocation is emitted to <code>audit-core</code>: the operative state and the evidence record are different artifacts with different owners.</p>
<p>The evidence guarantee is bounded, and the bound is <code>audit-core</code>'s <code>docs/integrity.md</code>, not its INTENT principle 6. An in-database hash chain detects a rewritten payload only if the attacker does not also recompute the suffix — which a database owner can. Detection against that class requires the external chain-head attestation, and even with it the store is not WORM, object lock, or archival custody. <code>tamper_evidence</code> is therefore conditional on live preconditions, not a property of the store at rest, and approval events receive exactly the guarantee every other source receives.</p>
<p><strong>Emission atomicity is <code>approval-engine</code>'s obligation.</strong> An approval MUST NOT be issued, consumed, superseded, or revoked without the corresponding event being durably queued in the same transaction.</p>
<p><strong>The queue MUST be local.</strong> The durable queue MUST live in <code>approval-engine</code>'s own transactional store, and <strong>no synchronous dependency on <code>audit-core</code> may sit inside the state-change transaction</strong>. With a genuine local outbox, fail-closed triggers only when <code>approval-engine</code>'s own store is unavailable — where the change could not have been recorded anyway — and an <code>audit-core</code> outage does not block a revocation. Satisfying the requirement by emitting synchronously to <code>audit-core</code> inside the transaction is also atomic, and turns an audit outage into an inability to revoke: the operation least tolerable to block during an incident, and the same coupling this section rejects for reads. Raised by <code>audit-core</code>. <code>audit-core</code> reports what it received and does not imply it is everything that happened; without atomic emission the evidence half is silently incomplete and nothing detects the gap. This is a condition of <code>audit-core</code>'s assent (<code>AUDIT-IN-0001</code>) and belongs in <code>approval-engine</code>'s contract before the evidence half is treated as load-bearing.</p>
<p><code>audit-core</code> MUST NOT expose an approval-validity query. Records, yes; a verdict on whether an approval is still valid, never — a consumer branching on that answer would route an authorization decision through the audit fabric, which is what this section exists to prevent. Callers needing current state ask <code>approval-engine</code>.</p>
<h3>9.5 Graded progression is an engine concept</h3>
<p>Maturity — how far a subject has progressed against declared criteria and submitted evidence — is owned by <code>maturity-engine</code>. Given the same criteria and the same evidence it MUST return the same level; that determinism is what makes it an Engine rather than an opinion.</p>
<p>The division with Staff: <strong>gate-house judges and proposes; maturity-engine computes and remembers.</strong> Interpretation is inference and stays Staff. A criterion that cannot be evaluated by rule is not yet a criterion.</p>
<p>This closes a defect in v0.2's own catalog: <code>gate-house</code> was assigned conformance review with no engine to act through, which is exactly the §9.1 problem raised against the containment claim. Staff acts only through Engine APIs, including gate-house.</p>
<p><strong>A maturity level MUST NOT be compiled into registry content.</strong> Until <code>access-engine</code>'s decision provenance carries a registry-snapshot digest — a gap it self-declared in §13 — a level reaching a decision through the registry is not reconstructable from the decision record. Levels arrive as request claims or as versioned policy rules. Same constraint, and same reason, as zone stance.</p>
<p><strong>A maturity level MUST NOT gate a decision directly.</strong> Under §6.1, compiled data that determines an outcome is still deciding. If a level determines whether an action is permitted, it MUST reach <code>access-engine</code> as an input claim or a versioned policy rule under §6.2, never by a consumer branching on a fetched level.</p>
<p>Approvals and maturity are deliberate opposites — a closed binary state machine against an open graded ladder — and neither engine may drift toward the other.</p>
<h3>9.6 Evidence proves alteration and truncation, not omission at source</h3>
<p>An append-only archive with a verified hash chain proves that records were not <strong>altered or truncated after arrival</strong>. It cannot prove that a record was never sent. Against a compromised or buggy source, a suppressed event leaves the chain perfectly intact and verification reports intact.</p>
<p>This bound is estate-wide. Statements of the form <em>"the audit record proves it happened"</em> are unsound; the sound form is <em>"the archive proves the records it holds were not altered or truncated after arrival"</em>. Its mirror is equally unsound: <strong>absence of a record is not evidence of non-occurrence</strong>, and no control may read it as such.</p>
<p><strong>Load-bearing versus attributive evidence.</strong> The atomicity obligation attaches to the first, not to both:</p>
<div class="scroll"><table><thead><tr><th>Kind</th><th>Test</th><th>Obligation</th></tr></thead><tbody><tr><td><span class="kind ">Load-bearing</span></td><td>a control's soundness depends on the event being present or absent — an approval revocation, a containment action, a denial</td><td>emission MUST be atomic with the state change (§9.4)</td></tr><tr><td><span class="kind ">Attributive</span></td><td>the event supports forensic reconstruction and attribution, and no control branches on its presence</td><td>atomicity SHOULD be sought; where it is deliberately traded away, the trade MUST be declared and completeness MUST NOT be claimed</td></tr></tbody></table></div>
<p>Where a repository deliberately makes emission non-atomic — <code>ops-warden</code>'s <code># audit must not block signing</code> is the estate's live example, chosen so that an audit-store failure cannot remove production host access — the trade is legitimate for attributive evidence, MUST be declared where the trail is documented, and MUST NOT be described in terms that imply completeness. The availability argument is real in both directions: making it atomic gives the estate's operational access lane a new dependency on its own evidence store.</p>
<p><strong>Consequence for adaptive systems.</strong> Suppression does not degrade observation neutrally, it biases it optimistic, and silently: an event never emitted is never evaluated, so no finding is raised and the last posture stands. A confidence score computed from the richness of the record in hand cannot express doubt about the completeness of the stream — a well-formed observation from a 90%-suppressed stream scores high. That is this section's failure reproduced one layer up, in the consumer.</p>
<p>Two things follow.</p>
<p><strong>Which control covers which threat.</strong> v0.6 read as though emission atomicity closed this section's opening sentence. It does not, and the decomposition is owed to the reader:</p>
<div class="scroll"><table class="tm"><thead><tr><th>Threat</th><th>Covered by</th><th>When</th></tr></thead><tbody><tr><td><strong>Accidental omission</strong> — process dies between mutation and emit</td><td class="">emission atomicity, local outbox (§9.4)</td><td class="">prevented</td></tr><tr><td><strong>Adversarial omission</strong> — a compromised source declines to insert, deletes before drain, or drains to nowhere</td><td class="">cadence and reconciliation</td><td class=""><strong>detected, after the fact</strong></td></tr><tr><td>Adversarial omission at a compromised source</td><td class="">—</td><td class=""><strong>nothing in this model prevents it</strong></td></tr></tbody></table></div>
<p>The outbox sits inside the blast radius of the component whose compromise this section posits, so it makes emission atomic against crash and partial failure and nothing more. That residual is real and is stated rather than implied. Raised by <code>audit-core</code>, correcting a remedy it had itself proposed.</p>
<ol><li><strong>The §8 asymmetry bounds the damage, and this is its clearest payoff.</strong> Because an adaptive system may only reduce authority and never manufacture it, suppression can only prevent a tightening that should have happened. It cannot be used to engineer a loosening. The harm is a missed reduction, not an invented privilege — which is an argument for keeping the asymmetry absolute.</li><li><strong>Silence is a signal, and for load-bearing evidence it is the only control in its class.</strong> A source of <strong>attributive</strong> evidence SHOULD declare an expected emission cadence; a source of <strong>load-bearing</strong> evidence <strong>MUST</strong>. A drop below the declared rate is a finding in its own right — the stream observed, not only its contents — and needs no Tooling contact, because the source publishes its own stream.</li></ol>
<p><strong>Rate monitoring is the wrong form for rare events</strong>, and rare is exactly where the stakes are highest: the most valuable event to suppress is the negative one, and revocations, denials, and containment actions are infrequent by nature. A source emitting a handful of revocations a month has no rate to drop below, and suppression is indistinguishable from a quiet month. For <strong>low-volume load-bearing classes</strong> the required form is therefore <strong>positive reconciliation or a heartbeat</strong>: compare the source's own state transitions against the evidence engine's event count per class and treat divergence as a finding, or assert <em>nothing to report</em> as a signed positive claim that can itself go missing. Rate monitoring never produces a claim that can be missing; a heartbeat does. <code>GH-WP-0002-T04</code> is the reference instance. Raised by <code>audit-core</code>.</p>
<p>Raised by <code>audit-core</code> against its own principle; extended by <code>kings-guard</code> from its own evaluator and confidence model.</p>
<h3>9.7 Decisions have a lifetime</h3>
<p>A single decision point deciding on stale claims is a single decision point deciding wrongly. The model has had no temporal law, and the approval race in §16 was its first symptom.</p>
<ol><li><strong>Every allow has an explicit lifetime</strong> — a TTL, or a binding to a session or obligation that ends. An allow with no stated end is a standing grant, and standing grants are what this estate exists to remove.</li><li><strong>Revocation and supersession have a visibility deadline</strong>, and its shape differs by role. A <strong>PEP</strong> has one boundary and MUST state one deadline. A <strong>PDP</strong> MUST state a deadline <strong>per input class</strong>, because a decision is a join over sources with unrelated refresh behaviour — approval-claim freshness, registry snapshot cadence, policy package activation, directory ETag. A single number at a PDP is either a fiction or the worst case, and the worst case is the slowest and least visible input. "Eventually" is not a stance; an unstated deadline is an unbounded replay window.</li></ol>
<p>A consequence worth naming: a stated deadline for a fact carried by a registry snapshot is unfalsifiable while decision provenance holds no snapshot digest, since nobody can determine afterwards which snapshot a decision read. The deadline and the digest are one gap seen from two sides, which promotes <code>access-engine</code>'s self-declared provenance gap (§13) from housekeeping to a conformance prerequisite. Raised by <code>access-engine</code> against its own backlog.</p>
<ol><li><strong>Consumption is a state change, never an inference.</strong> An approval is consumed by a mutation in <code>approval-engine</code> (§9.4). It MUST NOT be inferred from the existence of a decision record — the decision precedes the action and the action precedes consumption, so a decision record proves an intent to act, not an act.</li><li><strong>Three failure modes are named, and each needs an owner</strong>: an allow rendered then never consumed; a double consumption by racing callers; consumption after the authorized action has already failed. Neither engine closes these alone. Recorded in §16 and <code>GH-WP-0002-T06</code>.</li></ol>
<h3>9.8 Partition is not one-dimensional</h3>
<p>§9.3 handles <em>engine unreachable</em>. A real estate spends most of its incident time in the band between reachable and gone: partial PIP reachability, clock skew across a decision and its enforcement, and two consumers with different declared stances seeing different worlds at the same moment.</p>
<p>Two rules hold today, and the rest is open (§16). A PEP MUST resolve its own stance from its declared map without consulting another consumer — divergent views are expected and are not a coordination problem to be solved at enforcement time. And where clock skew could extend a lifetime under §9.7, the shorter reading governs.</p>
</section>
<section id="s10"><h2><span class="sn">10</span>Changing layer</h2>
<p>A repository's layer is not permanent. <code>zone-engine</code> changed layer in practice when its runtime hypothesis was falsified.</p>
<p>A layer change MUST be recorded as a decision, MUST update the repository's <code>INTENT.md</code>, and MUST obtain assent from the repositories whose boundaries move. A repository MUST NOT acquire a new layer's permissions by gradual practice.</p>
<p>"No gradual practice" needs a check rather than a sentence. A layer change MUST carry six artifacts, written from the <code>zone-engine</code> case that the procedure should have been derived from in the first place:</p>
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Why</th></tr></thead><tbody><tr><td>before/after <code>INTENT.md</code></td><td>the declaration is the conformance surface (§11)</td></tr><tr><td>client inventory</td><td>what the repository holds against Tooling, before and after</td></tr><tr><td>gap inventory</td><td>which §5.3 gaps close, open, or transfer</td></tr><tr><td>assent list</td><td>every repository whose boundary moves</td></tr><tr><td>state-migration decision</td><td>what happens to live state and to consumers reading it</td></tr><tr><td>permission freeze</td><td>no new permissions of the target layer are exercised until the cut completes</td></tr></tbody></table></div>
<p>The freeze is the one that makes the rule checkable: a repository mid-change holds its old permissions, not the union of both.</p>
</section>
<section id="s11"><h2><span class="sn">11</span>Conformance</h2>
<p>Conformance has four states, and the distinction between the last two is the point:</p>
<div class="scroll"><table><thead><tr><th>State</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>Conforming</strong></td><td>no Tooling contact, or only §5.1/§5.2 shapes, declared</td></tr><tr><td><strong>Blocked-clean</strong></td><td>the capability does not exist because no engine exposes it, and the repository makes <strong>no</strong> Tooling contact — §9.1 <code>pending</code>, and not a non-conformance</td></tr><tr><td><strong>Declared gap</strong></td><td>a §5.3 contact with owner, blocker, and review date — tracked non-conformance</td></tr><tr><td><strong>Undeclared violation</strong></td><td>anything else — a finding</td></tr></tbody></table></div>
<p><strong>Blocked-clean is not a lesser state than conforming.</strong> A repository that declined a break-glass path and left a capability at zero has complied at cost; a repository that quietly opened a direct client and declared nothing has not. Any downstream scoring — <code>maturity-engine</code> included (§9.5) — MUST NOT rank the first below the second. Raised by <code>kings-guard</code>, whose three gaps are all of this kind and which would otherwise have been graded down three times for having taken the standard seriously.</p>
<p><strong>Who must declare.</strong> A repository the estate authors declares its layer in its own <code>INTENT.md</code>. For a component the estate catalogues but does not author — third-party or vendored, such as <code>OpenBao</code> — the §4 catalog row <strong>is</strong> the declaration, and no <code>INTENT.md</code> obligation attaches. A rule that assigns an obligation the holder cannot discharge is the §9.1 defect applied to conformance rather than capability.</p>
<p>A layer stated <em>about</em> a repository by another repository is not a declaration. Review notes, catalog rows, and correspondence record an intent to adopt; only the repository's own file conforms.</p>
<p><strong>Declaration form.</strong> Because prose cannot distinguish a declaration from a transcribed review, a declaration MUST carry a machine-readable form: a <code>layer:</code> key in the <code>INTENT.md</code> frontmatter, or an equivalent declaration file. Without it this section asserts a property it cannot deliver — the defect this standard has now corrected three times elsewhere. <code>ops-warden</code> has implemented a reference form (<code>layer.yaml</code>, a conformance script, and a test covering the §5.2 no-authority property) and offered it to the repositories that have yet to declare. Raised by <code>audit-core</code>, which noted that <code>flex-auth</code>'s conforming declaration is legible as one only by following its decision trail.</p>
<p>Mechanically checkable:</p>
<ul><li>every estate-authored repository in §4 carries a machine-readable layer declaration;</li><li>every direct Tooling client in a Staff repository maps to a declared §5.1, §5.2, or §5.3 entry, and non-Tooling clients are recorded so the check is total;</li><li>no repository other than <code>access-engine</code> exposes an authorization decision surface;</li><li>no §4 capability is catalogued without an engine surface, a <code>pending</code> mark, or a <code>declared-gap</code> mark.</li></ul>
<p>Requires review: whether claims stay inside layer permissions; whether compiled or cached data has become an early decision (§6.1); whether doctrine is reaching decisions as declared inputs (§6.2); whether the §8 vocabulary is used correctly.</p>
</section>
<section id="s12"><h2><span class="sn">12</span>The conformance loop</h2>
<p>Doctrine no engine implements is fiction. The loop is normative, not aspirational:</p>
<pre>gate-house asserts an invariant
→ the engines implement it, or declare a gap
→ whitehat-security tries to break it
→ kings-guard observes it in operation
→ findings return to gate-house as doctrine change</pre>
<p>A finding that a rule is unsatisfiable is a <strong>success</strong> of this loop, not a failure of the reporting repository. Four of this standard's five versions exist because a reviewing repository used it.</p>
<p><strong>Step four is currently aspiration.</strong> <code>kings-guard</code> has disclosed that it has never observed anything in operation: the pilot is specified and scaffolded, every input is a hand-built fixture, and no test has met a real event. Until it reports otherwise, no argument in this estate may assume an invariant is being watched in practice because §12 lists a repository against that step.</p>
</section>
<section id="s13"><h2><span class="sn">13</span>Open gaps</h2>
<p>Two different things are recorded here, and they are opposite conformance states (§11). A <strong>declared contact</strong> means the repository touches Tooling because no engine exposes the capability. An <strong>unowned capability</strong> means no route exists and the repository makes no contact at all. Reading them as one list would grade restraint as though it were non-conformance.</p>
<p>An <code>intended owner</code> is a <strong>proposal to</strong> the named repository, not an assignment <strong>onto</strong> it. §2 keeps ownership in the repository's own <code>INTENT.md</code>, so the register distinguishes proposed from assented.</p>
<div class="scroll"><table><thead><tr><th>Gap</th><th>State</th><th>Declared by</th><th>Owner</th><th>Owner status</th></tr></thead><tbody><tr><td>SSH-CA signing write (<code>VaultCA</code>, <code>bao kv put</code>)</td><td>declared-contact</td><td>ops-warden</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Authentication / assurance evidence</td><td>unowned-capability</td><td>kings-guard</td><td>identity layer + audit-core</td><td><strong>access-engine declined</strong></td></tr><tr><td>Secret-use evidence</td><td>unowned-capability</td><td>kings-guard</td><td>secrets-engine</td><td>proposed</td></tr><tr><td>Actuation / containment surface</td><td>unowned-capability</td><td><strong>gate-house (estate-wide)</strong></td><td>access-engine + runtime engines</td><td>proposed</td></tr><tr><td>Identity and secret observation</td><td>unowned-capability</td><td>kings-guard</td><td>as above</td><td>proposed</td></tr><tr><td>Stance-map register had no implementation</td><td>declared-contact</td><td>ops-warden, access-engine</td><td>gate-house</td><td>resolved in §13.1</td></tr><tr><td>Registry-snapshot digest in decision provenance</td><td>declared-contact</td><td>flex-auth</td><td>flex-auth</td><td>self-declared</td></tr><tr><td>Approval storage and lifecycle</td><td>—</td><td>flex-auth</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Approval evidence</td><td>—</td><td>gate-house</td><td>audit-core</td><td><strong>assented</strong> (<code>AUDIT-IN-0001</code>)</td></tr><tr><td>Approval evidence custody stronger than the shipped bound — WORM, object lock, transparency log</td><td>unowned-capability</td><td>audit-core</td><td>—</td><td>unassigned</td></tr><tr><td>Emission atomicity for approval state changes</td><td>—</td><td>audit-core</td><td>approval-engine</td><td>assigned (§9.4)</td></tr><tr><td>Non-atomic audit emission on the SSH signing lane</td><td>declared-contact</td><td>ops-warden</td><td>ops-warden</td><td>self-declared, attributive (§9.6)</td></tr></tbody></table></div>
<p>The actuation row is no longer attributed to <code>kings-guard</code>. §9.2 ruled that containment is not a Staff capability lacking a route, so <code>kings-guard</code> is not its declarer: the gap is estate-wide and blocks every repository's ability to act. Raised by <code>kings-guard</code>, which asked not to carry a row for a capability the standard had just ruled was never theirs.</p>
<p><code>access-engine</code> declined authentication and assurance evidence (<code>FLEX-DEC-2026-002</code>): it consumes assurance claims as input and never redefines them, so evidence of authentication belongs to the identity layer and <code>audit-core</code>. It owns evidence of the decision, which it already emits. The containment surface is recorded as proposed and remains <code>pending</code> under §9.2.</p>
<p>Whether approvals warrant custody stronger than every other source is doctrine work not yet done; until it is, approval evidence carries the same guarantee as any other source and §9.6 bounds what may be claimed from it.</p>
<p><strong>What is normative here, and what is a snapshot.</strong> Three rules are part of this standard and survive wherever the register lives:</p>
<ol><li>the two marks — <code>pending</code> and <code>declared-gap</code> (§9.1);</li><li>the owner-status rule — a proposed owner is not an assigned one (§2);</li><li>the scoring rule — <code>blocked-clean</code> MUST NOT rank below conforming (§11).</li></ol>
<p><strong>The table above is a snapshot, not statute.</strong> It moves into <code>maturity-engine</code> as soon as that engine can store state, and the <code>state</code> and owner-status columns MUST survive the migration. A standard that is also a backlog keeps attracting findings that belong in the register, and its review interval is far slower than the register's real rate of change.</p>
<h3>13.1 PEP stance-map register</h3>
<p>Every PEP-shaped consumer publishes an unreachable-engine stance map (§6.4, obligation 3). This is the inventory until <code>maturity-engine</code> can hold it.</p>
<div class="scroll"><table><thead><tr><th>Consumer</th><th>Stance map</th><th>Shape</th></tr></thead><tbody><tr><td><code>ops-warden</code></td><td><code>ops-warden/pep-stance.yaml</code></td><td>total per-zone; open <code>z0</code>–<code>z2</code> and unknown, closed <code>z3-critical</code>; test asserts the published map equals the shipped default (<code>ADR-0009</code>)</td></tr><tr><td><code>ops-mason</code></td><td>—</td><td><strong>not published</strong>; catalogued PEP-shaped in §4</td></tr></tbody></table></div>
<p><strong>One row is the finding.</strong> The aggregate of consumer stances is the estate's real authorization behaviour, and it is currently one published map and one absence. <code>access-engine</code> has noted it is the repository positioned to notice when that aggregate diverges from what the policy packages say — which it cannot do while the register is nearly empty.</p>
</section>
<section id="s14"><h2><span class="sn">14</span>Adoption</h2>
<p>Status is <strong>accepted</strong>, on the owner's decision of 2026-08-29.</p>
<p>Two things that acceptance does and does not mean, kept apart because <code>ops-warden</code> asked for the distinction:</p>
<div class="scroll"><table><thead><tr><th><strong>Boundary assent</strong></th><th>given by the four repositories below, at the version named in each record, and undisturbed since</th></tr></thead><tbody><tr><td><strong>Revision review</strong></td><td>each of the four reviewed v0.6 and returned findings; <strong>every change in v0.7 is the adopted remedy of a finding they raised</strong></td></tr><tr><td><strong>Not claimed</strong></td><td>no repository has reviewed v0.7 <em>as text</em>. The first revision review will confirm or correct it</td></tr></tbody></table></div>
<p>Accepting a standard nobody has re-read is a deliberate call: the estate learns more from using it than from another round of prose refinement, and the changes in v0.7 were requested rather than invented. Findings against the accepted text remain welcome and are §12's normal business, not an exception.</p>
<div class="scroll"><table><thead><tr><th>Repository</th><th>Record</th><th>Outcome</th></tr></thead><tbody><tr><td>flex-auth</td><td><code>FLEX-DEC-2026-001</code></td><td>assent to all three items; one self-declared non-conformance; two rename conditions</td></tr><tr><td>kings-guard</td><td><code>KG-DEC-2026-001</code></td><td>assent; declined the offered §5 relaxation; raised §9.1</td></tr><tr><td>ops-warden</td><td><code>ADR-0010</code></td><td>assent to all three; veto not exercised; offered the §5.3 amendment</td></tr><tr><td>audit-core</td><td><code>AUDIT-IN-0001</code></td><td>assent to the evidence half with conditions; corrected the rationale twice; raised §9.6</td></tr></tbody></table></div>
<p>Adoption for a repository means its <code>INTENT.md</code> declares its layer, its ownership claims fall inside that layer, its Tooling contacts are declared under §5, and any shared boundary has been assented to by the other side.</p>
<p><strong>Adoption status as of 2026-08-29: seven of sixteen</strong> estate-authored §4 repositories have declared in their own voice — <code>gate-house</code>, <code>flex-auth</code>, <code>kings-guard</code>, <code>ops-warden</code>, <code>audit-core</code>, <code>approval-engine</code>, <code>maturity-engine</code>. The remaining nine — <code>info-tech-canon</code>, <code>net-kingdom</code>, <code>key-cape</code>, <code>user-engine</code>, <code>tenant-engine</code>, <code>zone-engine</code>, <code>secrets-engine</code>, <code>ops-mason</code>, <code>whitehat-security</code> — carry a layering review note authored by <code>gate-house</code> and have not answered it. Those notes state a layer but do not constitute a declaration, and this standard does not claim estate-wide adoption on their basis. Declaration requests are open as intakes in each.</p>
</section>
<section id="s15"><h2><span class="sn">15</span>Change log</h2>
<p>v0.1 → v0.2:</p>
<ol><li><strong>§5 restructured</strong> into three sanctioned shapes. Added §5.2 conduit (ops-warden's question, ruled) and §5.3 declared engine gap (ops-warden's amendment, accepted).</li><li><strong>§6.2 added</strong> — doctrine must reach the decision as an input claim or a versioned policy rule (flex-auth's boundary drawn back, accepted).</li><li><strong>§9 added</strong> — the catalog may not assign a capability the rules forbid discharging; containment marked pending; degraded-mode fallback ruled into the engine (kings-guard's finding).</li><li><strong>§11 restructured</strong> — conformance now has three states, distinguishing a tracked gap from an undeclared violation.</li><li><strong>§12 made normative</strong>, with the explicit statement that an unsatisfiability finding is a success of the loop.</li><li><strong>§13 added</strong> — open gaps register, including the unowned approval storage and lifecycle capability.</li><li>§4 catalog gained the pending mark and ops-warden's SSH certificate lane.</li></ol>
<p>v0.2 → v0.3:</p>
<ol><li><strong>§9.4 added</strong> — approvals assigned to <code>approval-engine</code>, with the operative state and the evidence record separated between it and <code>audit-core</code>.</li><li><strong>§9.5 added</strong> — graded progression assigned to <code>maturity-engine</code>, closing the §9.1 defect in gate-house's own conformance-review claim, and carrying the guardrail that a level may never gate a decision directly.</li><li>§4 catalog gained both engines; gate-house's conformance-review claim now names the engine it acts through.</li><li>§13 register updated: the approval hole is assigned, two new entries added.</li></ol>
<p>v0.6 → v0.7, from four reviews:</p>
<ol><li><strong>§3.4 is written.</strong> v0.6 announced the human/agent principal separation in §1 and §15 and left §3.4 byte-identical to v0.5 — a silent edit failure. A rule stated about a standard in its own change log is not a rule. Found by <code>kings-guard</code>. The same failure had also dropped two §16 entries, restored here.</li><li><strong>§6.4 obligation 1 rewritten</strong> — it forbade what obligation 3 blesses. A PEP may proceed under its declared §9.3 stance provided the application of that stance is <em>recorded in place of</em> the decision. Stricter than v0.6 where it counts: a fail-open result is metadata, never silence. Raised by <code>ops-warden</code>.</li><li><strong>§6.4 obligation 2 rewritten</strong> — it forbade the session-bound allow §9.7.1 permits. Scoped to replay outside the decision's own binding and lifetime, with the canonical request digest as the mechanical test, and negative caching ruled permitted where the refusal is recorded and the cache lifetime declared. Raised by <code>access-engine</code>.</li><li><strong>§6.4 obligation 3</strong> gained the requirement that the published stance map equal shipped behaviour, asserted by test. <strong>§13.1</strong> now exists as the register §6.4 mandated and v0.6 did not implement.</li><li><strong>§9.6 gained a threat decomposition</strong> — atomicity prevents accidental omission; cadence and reconciliation detect the adversarial case after the fact; nothing prevents it at a compromised source. Raised by <code>audit-core</code> against its own proposed remedy.</li><li><strong>§9.6 cadence is now MUST for load-bearing sources</strong>, with positive reconciliation or a heartbeat as the required form for low-volume classes, because rate monitoring fails exactly where the stakes are highest.</li><li><strong>§9.7.2 splits by role</strong> — a PDP states a deadline per input class, a PEP one at its boundary. Promotes <code>access-engine</code>'s provenance gap to a conformance prerequisite.</li><li><strong>§3.3's Evidence row</strong> is stated as an estate trade rather than a property, leaving independent-recording-before-effect raisable as a declared exception.</li><li><strong>§17</strong> moves the decision-record schema to <code>access-engine</code>, which argued it against its own interest; <code>kings-guard</code> drafts the emission-cadence schema.</li><li><strong>§13</strong> no longer attributes the actuation gap to <code>kings-guard</code>; it is estate-wide. <strong>§19 removed</strong> — a verdict inside a standard grades the document it lives in. <strong>§17/§18</strong> demoted from H1 to H2.</li><li><strong>§20 added</strong> — the Railiance interaction boundary, on <code>railiance-master</code>'s definitions, including that <code>rein-*</code> is not a fifth axis.</li></ol>
<p>v0.5 → v0.6, from the independent assessment of 2026-08-29:</p>
<ol><li><strong>§3.3 types the engines</strong> — PDP, PIP, Evidence, Lifecycle, with a role column in §4. A new engine is a PIP unless this standard says otherwise, so "we need an engine for X" cannot drift into "X now decides".</li><li><strong>§6.4 names the enforcement point</strong> — a PEP shape with four obligations: no side effect without a decision record, no local recaching of the verdict, a declared unreachable-engine stance, reconstructability. The standard had the decision and not the gate.</li><li><strong>§9.2 replaced</strong> — containment was marked pending against the wrong repository. Actuation is an Engine concept, unowned, held at zero; Staff proposes containment and never performs it.</li><li><strong>§3.4 separates the two Staff principals</strong> — human and agent share the layer but not blast radius: no standing credential, conduit or engine API only, agent memory is not a state plane, every action reconstructable as the caller's.</li><li><strong>§9.7 puts time into the model</strong> — explicit lifetimes, revocation visibility deadlines, consumption as a state change never inferred, and the three race modes named. <strong>§9.8</strong> states what holds under partition and leaves the rest open.</li><li><strong>§17 requires the Taxonomy artifacts</strong> — claim, decision-record, gap-record, and emission-cadence schemas — without which §6.2 and §11 are reviewable but not compileable. Ownership proposed, not assigned.</li><li><strong>§18 composes the sibling standards</strong> — how zone stance, tenancy posture, and a credential lifecycle event each enter a decision as a claim. They were cited in frontmatter and nowhere in the rules.</li><li><strong>§5 gained a sunset</strong> on the uncatalogued-infrastructure carve-out, and §5.3 <strong>declines</strong> a proposed fourth "operator of third-party Tooling" shape: it would convert a tracked gap into a permanent allowance.</li><li><strong>§10 gained the six artifacts</strong> a layer change must carry, written from the <code>zone-engine</code> case, including a permission freeze during the cut.</li><li><strong>§2 lifts the observation rule</strong> — no estate argument may cite observation that has not happened. <strong>§13</strong> separates its three normative rules from the table, which is now a snapshot due to move into <code>maturity-engine</code>.</li><li><strong>§16</strong> the approval custody question is <strong>decided: no</strong>, rather than left open. <strong>§19</strong> records the fitness verdict, including that the estate can propose and decide but cannot yet watch or act.</li></ol>
<p>v0.4 → v0.5, all from review findings:</p>
<ol><li><strong>§9.1 split into two marks</strong> — <code>pending</code> (no route, capability zero) and <code>declared-gap</code> (route exists under §5.3, capability works and is tracked). v0.4's single mark would have forced a false <code>pending</code> onto ops-warden's production SSH issuance. Raised by <code>ops-warden</code>.</li><li><strong>§9.3 rewritten</strong> — input degradation is the engine's; engine-unreachability is necessarily the consumer's, bounded by a declared, auditable, total stance. Contested by <code>flex-auth</code>: fail-open is not expressible by a PDP, and v0.4 collided with <code>ops-warden</code> <code>ADR-0009</code>.</li><li><strong>§5 gained a scope rule</strong> — "Tooling-layer system" means a §4 Tooling row; uncatalogued infrastructure is outside §5 and recorded rather than policed. Without it every Staff repository was in undeclared violation for writing progress events. Raised by <code>ops-warden</code>.</li><li><strong>§9.4 requires a local outbox</strong> — no synchronous dependency on <code>audit-core</code> inside the state-change transaction, so an audit outage cannot block a revocation. Raised by <code>audit-core</code>.</li><li><strong>§9.5 forbids compiling maturity levels into registry content</strong> until decision provenance carries a registry-snapshot digest. Raised by <code>flex-auth</code>.</li><li><strong>§9.6 gained the load-bearing / attributive distinction</strong>, the mirror rule that absence is not evidence of non-occurrence, the optimistic-bias consequence for adaptive systems, and silence-as-signal. Raised by <code>kings-guard</code> on top of <code>audit-core</code>'s original.</li><li><strong>§11 gained a fourth state</strong> — blocked-clean, which MUST NOT rank below conforming — and a machine-readable declaration form. Raised by <code>kings-guard</code> and <code>audit-core</code>.</li><li><strong>§13 gained state and owner-status columns</strong> — declared-contact versus unowned-capability, proposed versus assented owner. <code>access-engine</code>'s decline of authentication evidence is recorded. Raised by <code>kings-guard</code> and <code>flex-auth</code>.</li><li><strong>§8</strong> records the asymmetry's payoff under incomplete observation; <strong>§12</strong> records that its fourth step is unstaffed; <strong>§14</strong> corrects the adoption arithmetic and the status contradiction.</li></ol>
<p>Amended in place while <code>proposed</code>, 2026-08-28: §11 gained the who-must-declare rule after a conformance sweep found the standard required an <code>INTENT.md</code> declaration from <code>OpenBao</code>, which the estate does not author; and §14 gained the honest adoption count.</p>
<p>v0.3 → v0.4:</p>
<ol><li><strong>§4 catalog gained <code>audit-core</code></strong> as an Engine, on its own declaration. v0.3 named it as an owner in §9.4 and §13 without cataloguing it — a §11 defect in the standard itself, raised by <code>audit-core</code>.</li><li><strong>§9.4 evidence rationale rewritten</strong> to cite <code>audit-core</code>'s shipped <code>docs/integrity.md</code> bound rather than its INTENT principle 6, and to state that <code>tamper_evidence</code> is conditional on live preconditions.</li><li><strong>§9.4 gained emission atomicity</strong> as <code>approval-engine</code>'s obligation, and the prohibition on <code>audit-core</code> exposing an approval-validity query.</li><li><strong>§9.6 added</strong> — evidence proves alteration and truncation, not omission at source. Estate-wide; the sound and unsound forms of the claim are stated.</li><li><strong>§13</strong> — evidence half recorded as assented with conditions; two new gaps: stronger approval custody (unassigned) and emission atomicity (<code>approval-engine</code>).</li></ol>
</section>
<section id="s16"><h2><span class="sn">16</span>Open questions</h2>
<ul><li>~~Whether approvals warrant archival custody stronger than every other audit source.~~ <strong>Decided (§13): no.</strong> Approval evidence carries the same bound as every other source. The acute risk for approvals is <em>omission</em> — a suppressed revocation — and archival custody does not address omission at all; emission atomicity with a local outbox (§9.4) and a detection surface (<code>GH-WP-0002-T04</code>) do. Leaving it open while calling the evidence half load-bearing created a promise the archive cannot cash. If a future requirement genuinely needs WORM or a transparency log, that is a different store with a different owner, raised then.</li><li>Whether SSH certificate issuance evidence is load-bearing or attributive (§9.6). Ruled attributive here on the argument that no control branches on the presence of a signing record; <code>ops-warden</code> asked for the ruling and the trade is genuinely two-sided, so it is flagged rather than settled.</li><li>Who marks an approval consumed, and at what point relative to the decision (§9.4). <code>flex-auth</code> notes the decision precedes the action and the action precedes consumption, so an allow rendered against an approval then never consumed, or consumed twice by a racing caller, is a gap neither engine closes alone. Needed before <code>FLEX-WP-0017</code> T05.</li><li>Whether other §4 repositories are missing layer declarations; <code>audit-core</code> flagged its own absence and asked whether the catalog needs the same correction elsewhere.</li><li>Whether the gap register migrates from this standard into <code>maturity-engine</code> once that engine exists, leaving the standard to state the rules only.</li><li>Whether Tooling warrants subdivision between third-party and homegrown.</li><li>How a future <code>role-engine</code> divides responsibility with <code>access-engine</code>.</li><li>Whether declared gaps need an estate-wide register rather than per-repository declarations; ops-warden's <code>warden route gaps</code> is candidate machinery.</li><li>Whether non-security repositories adopt the same model. The determinism cut is not security-specific; if non-security Staff also may not hold runtime-dependent state, the estate gets one constitution rather than a security ghetto.</li><li>The rest of §9.8: split brain, partial PIP reachability, and clock skew beyond the two rules stated.</li><li>Publication integrity of the Taxonomy layer itself. This standard demands reconstructability of decisions while its own publication path has no digest, freeze, or rollback discipline.</li><li>The fitness verdict formerly at §19 now lives in <code>net-kingdom/history/2026-08-29-layering-standard-assessment.md</code>. A grade inside a standard of record becomes normative by adjacency and ages against the text it grades. Raised by <code>access-engine</code>. Its two substantive points remain live: observation in production is unstaffed (§12) and actuation has no surface (§9.2).</li><li>The <strong>working companion</strong> (<code>net-kingdom/SECURITY-COMPANION.md</code>, v0.2, root of the repository for onboarding) is the operative form of this statute. The statute governs on disagreement, and a disagreement is a finding. The v0.1 gap <code>access-engine</code> found — publish your stance map, but nowhere saying where, and no inventory obligation — is fixed in v0.2 §5.3.</li><li>How the Railiance operational axes meet this model beyond §20's first statement, which is deliberately minimal.</li></ul>
</section>
<section id="s17"><h2><span class="sn">17</span>Taxonomy artifacts</h2>
<p>§6.2 says doctrine reaches a decision as an input claim or a versioned policy rule. As prose that is a rule a reviewer can apply. As an interface it does not exist, because nothing defines what a claim <em>is</em>. §11 calls itself mechanically checkable while resting on that gap.</p>
<p>Four artifacts are therefore required, owned by Taxonomy and versioned like any standard:</p>
<div class="scroll"><table><thead><tr><th>Artifact</th><th>Contents</th></tr></thead><tbody><tr><td><strong>request-claim schema</strong></td><td>identity, tenant, zone stance, posture, approval, maturity, assurance — each with its issuer and freshness rule</td></tr><tr><td>~~decision-record schema~~</td><td><strong>moved to <code>access-engine</code></strong> — see below</td></tr><tr><td><strong>gap-record schema</strong></td><td>the §5.3 fields — <code>capability</code>, <code>intended_owner</code>, <code>blocked_on</code>, <code>review</code> — plus the §13 <code>state</code> and owner-status</td></tr><tr><td><strong>emission-cadence declaration</strong></td><td>the expected rate a source publishes, so silence is a finding (§9.6)</td></tr></tbody></table></div>
<p>Until these exist, §6.2 and §11 are reviewable but not compileable, and every engine invents its own claim shape at its own boundary.</p>
<p><strong>The decision-record schema is not Taxonomy's.</strong> A decision record is the PDP's output artifact — the one thing in the estate only <code>access-engine</code> produces — and §2 keeps ownership in the producing repository's own <code>INTENT.md</code>. Taxonomy authoring the schema for an artifact only one engine emits would invert the ownership rule this standard applies everywhere else. <code>access-engine</code> publishes it as a contract; Taxonomy holds only the shared field vocabulary the claim schema references.</p>
<p>Raised by <code>access-engine</code> <strong>against its own interest</strong> — the same §2 argument it used to decline authentication evidence, applied where it takes work on rather than off. Symmetry of that kind is what makes the ownership rule credible.</p>
<p><strong>The emission-cadence declaration has a drafter.</strong> <code>kings-guard</code> is its only consumer, cannot implement silence-as-signal without it, and has offered to draft it against <code>qonto-assistant</code> and hand it to whichever Taxonomy repository takes ownership — rather than inventing a local shape, which is the drift §17 exists to prevent. Accepted as a draft; ownership still rests with Taxonomy.</p>
<p><strong>Ownership is proposed, not assigned.</strong> <code>info-tech-canon</code> holds ecosystem-wide semantic contracts and <code>net-kingdom</code> holds NetKingdom standards of record; the split between them for these four artifacts is theirs to draw, and §2 keeps ownership in the owning repository's <code>INTENT.md</code>. Neither has assented.</p>
</section>
<section id="s18"><h2><span class="sn">18</span>Composition with the sibling standards</h2>
<p>The related-standards list has been frontmatter and little else. If the following sentences cannot be written, the list is decoration — so they are written here rather than in the siblings.</p>
<p><strong>Zone stance</strong> (<code>security-zones_v0.1</code>). A zone answers which scrutiny a workload has qualified for; membership is <code>zone-engine</code>'s. The <em>effect</em> of a zone on a decision belongs in a versioned <code>access-engine</code> policy package, never in registry content — that ruling is zone-engine's §5, and §6.1 is its generalization. Zone stance therefore enters a decision as a <strong>claim on the request or a rule in the package</strong>, and a decision that turned on a zone must name the package version that read it.</p>
<p><strong>Tenancy posture</strong> (<code>tenancy-posture_v0.1</code>). Posture is a bounded security-state input, published by its owner and never a privilege source (§8). It enters as a <strong>claim</strong>, carries its own freshness, and the §8 asymmetry binds it: posture may tighten a decision and may never loosen one. A posture too stale to trust is a missing claim, and a missing claim is not permission.</p>
<p><strong>Credential lifecycle</strong> (<code>credential-management_v0.2</code>). Issuance, rotation, and revocation are <code>secrets-engine</code>'s and <code>OpenBao</code>'s, downstream of a decision — a credential is an artifact of authority, never its source. A lifecycle event becomes an <strong>input</strong> to a later decision as a claim (this credential is current, this lease is bound to this task), never a side channel that changes an outcome without appearing in the decision record. Revocation visibility is bounded by §9.7.</p>
<p>Each of the three composes the same way, which is the point: <strong>facts arrive as claims, effects live in versioned policy, and anything that changes an outcome appears in the decision record.</strong></p>
</section>
<section id="s20"><h2><span class="sn">20</span>The Railiance interaction boundary</h2>
<p>Operations is not NetKingdom's. Workload operations are organized by <strong>Railiance</strong>, whose framework repository is <code>railiance-master</code>, and NetKingdom provides the security and approval framework those operations consume. This section states the boundary as it stands today. It is expected to evolve, and it is written here so that evolution is visible rather than inferred.</p>
<p>Definitions are <code>railiance-master</code>'s and are restated, not authored, here.</p>
<h3>20.1 What Railiance organizes</h3>
<p>A <strong>workload</strong> is a managed running deployable. Human commands, credential patterns, broker actions, approvals, and infrastructure resources that are not themselves deployables <strong>are not workloads</strong> — which is why an approval object (§9.4) is not a Railiance axis and never becomes one.</p>
<p>Every workload is operated through four composable axes, each answering a different question about the same workload:</p>
<div class="scroll"><table><thead><tr><th>Prefix</th><th>Axis</th><th>Question</th></tr></thead><tbody><tr><td><code>railiance-*</code></td><td>ownership</td><td>Who owns this capability?</td></tr><tr><td><code>rail-*</code></td><td>execution contract</td><td>How does this workload run?</td></tr><tr><td><code>rapp-*</code></td><td>managed package</td><td>What exactly is packaged and operated?</td></tr><tr><td><code>reef-*</code></td><td>substrate</td><td>Where is it bound, and as what operational reality?</td></tr></tbody></table></div>
<p><code>rein-*</code> is <strong>not a fifth axis</strong>. Reins are <code>glas-harness</code> agent-harness backends; the name echoes <code>rail-*</code> analogically, not taxonomically. Agentic session semantics — session loops, tool policy, harness routing, model selection — belong to <code>glas-harness</code>. When a rein is installed and operated as a managed service it is a workload like any other, packaged and bound through the four axes above.</p>
<h3>20.2 What holds today</h3>
<p>For any Railiance consumer of NetKingdom security, without exception:</p>
<ol><li>Authorization decisions come from <code>access-engine</code> and from nowhere else (§6).</li><li>Approvals are objects in <code>approval-engine</code>, consumed as claims (§9.4).</li><li>Credentials are materialized by <code>secrets-engine</code> <strong>after</strong> a decision, never as a substitute for one.</li><li>Evidence goes to <code>audit-core</code> under the bound in §9.6.</li><li>Anything causing a protected side effect is <strong>PEP-shaped</strong> and owes the four obligations in §6.4 — including a published unreachable-engine stance in the §13.1 register.</li></ol>
<h3>20.3 What is not settled</h3>
<p>The mapping between the axes and this model is deliberately thin, because guessing it would be worse than admitting it:</p>
<ul><li>A <strong><code>rapp-*</code></strong> is the most likely <em>resource</em> a decision is rendered about, but nothing states its identity form in a request claim.</li><li>A <strong><code>rail-*</code></strong> describes how a workload runs and is therefore where PEP shape is most likely to live — but §6.4 obligations attach to repositories, and a rail is a contract, so whether a rail can <em>carry</em> an obligation is unwritten.</li><li>A <strong><code>reef-*</code></strong> answers where a workload is bound, which is adjacent to a security zone (<code>security-zones_v0.1</code>) without being one. <code>zone-engine</code> records that a reef capping availability for everything bound to it is a canon composition problem. That composition is unwritten.</li><li>The <strong><code>railiance-*</code> ownership axis</strong> names who owns a capability, which is adjacent to the principal a decision is rendered for. Adjacent is not equal, and no rule connects them.</li><li><strong><code>glas-harness</code> and reins</strong> hold tool policy and session semantics for agents, while §3.4 rule 2 holds that an agent acts only through a conduit or an engine API. Those two must compose, and neither side may treat its own half as sufficient. That seam is the most consequential of the five, because it is where "tool availability is not permission" is actually enforced or lost.</li></ul>
<h3>20.4 How this boundary changes</h3>
<p>An interaction boundary between two frameworks is owned by neither alone. Changes to §20 require assent from <code>railiance-master</code> for the axis definitions and from <code>glas-harness</code> for the session and tool-policy seam, on the same terms as any other boundary in this standard (§10). NetKingdom states what a consumer owes; it does not define what a rail, rapp, reef, or rein <em>is</em>.</p>
</section><footer><span>netkingdom-security-layer-model-v0.7 · · accepted</span><span>net-kingdom · canon/standards/security-layer-model_v0.7.md · ce198fc2905687ea90a2892346b6860281ac87f8</span></footer></main></div></div></html>

View file

@ -0,0 +1,580 @@
{
"documents_built": 73,
"served_v07_sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7",
"built_v07_immutable_sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7",
"exact_served_html_preserved": true,
"immutable_original_source_revision": "ce198fc2905687ea90a2892346b6860281ac87f8",
"canonical_source_revision": "0e17d956ab45f8c4a0163b70749490c414e54e67",
"canonical_superseded_notice": true,
"v08_accepted_revision_present": true,
"conflicting_existing_immutable_refused_without_write": true,
"original_failed_clean_page_sha256": "10a7f7f721f90a903bb4dc4aa7613536207be2fd71a29460a86d3159b8c8eecd",
"source_exports": [
{
"repo": "net-kingdom",
"path": "canon/standards/tenancy-posture_v0.1.md",
"sha256": "795944f3f7735f58c5447b57bd3f65766a6e8d7d54f75586f9f269d4f6de5c56",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "the-custodian",
"path": "canon/standards/autonomy-lanes_v0.1.md",
"sha256": "106cb0c99f73983199c9f1d8491143aa2a2eb41ee450a02ec7968d3763f30399",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/standards/contribution-convention_v0.1.md",
"sha256": "6dfc1d7b2b82ab18228a84660c1c7dff607aad1d9da37ac12084cdd2ab8ae112",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/standards/project-repository-flavor_v0.1.md",
"sha256": "61889b2f18a82a08b66ef90d758efe5c028a4f52aced1721a23e69c6f24a8224",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/standards/work-record-types_v0.1.md",
"sha256": "ab5d9de07fd899fc5334c9912571ed3d6748f2ee4bd0c9f81c5606c4727024d1",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/standards/workplan-terminology-fleet_v0.1.md",
"sha256": "431d36f40d90bcd40a640c16b583095ad35757675f78ddcea382e6f1b2e23710",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/coulomb-estate_v0.1.md",
"sha256": "3b4f93acdedc6f6d1a1c737c3327d9d55061506c5a879be09e6b0a2ea7f5f9bc",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "railiance-master",
"path": "docs/architecture/railiance_v0.1.md",
"sha256": "77de319978dfad02f40658bbeb96db46b246a46f1abd6a45f5b1269563de5568",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "net-kingdom",
"path": "docs/architecture/net-kingdom_v0.1.md",
"sha256": "d57021a85db6b69b58d21f6be258a57ec86deecdfff30395412657cf273dd9e7",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "state-hub",
"path": "docs/architecture/state-hub_v0.1.md",
"sha256": "e7104b2182612efe90c9a12168757506402490229814d454e94917ae62d2bfac",
"kind": "clean-head-export",
"source_head": "cccc27ce44928e579627aa86ccee38ae4fb1ca10"
},
{
"repo": "policy-nexus",
"path": "docs/architecture/policy-nexus_v0.1.md",
"sha256": "179cbb86bca95f71f46f51ca1971ff1c274eed7d900adf0672b537d4bcc5b480",
"kind": "reviewed-stage-overlay",
"source_head": "5c41564f04b9d6db3172e422b80961f6dbc4bfc2"
},
{
"repo": "policy-nexus",
"path": "docs/adr/ADR-0001-addressing-and-permanence.md",
"sha256": "a28668fb4b8b6c5ec8c94baac000061276d85ef1849ec7ab8d132b913dbfe3be",
"kind": "reviewed-stage-overlay",
"source_head": "5c41564f04b9d6db3172e422b80961f6dbc4bfc2"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0001-repository-prefix-architecture.md",
"sha256": "b9c993ded8d79d6f871dba9cf08a320b2d619609a448ad3d02b632f5b6f76497",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0002-rail-kubernetes-wave-1-boundary.md",
"sha256": "7e1fc5aedd7294192d8702a22b9f205e5bae20793836fdc48c0c071d10d7ab9d",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0003-rapp-first-wave-selection.md",
"sha256": "28135e94758b6935518d2e83458c1e607deeabb341879b605eef6529b3168cbc",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0004-first-wave-reef-rollout.md",
"sha256": "36ec3aad5082ceff685d66e091c0a24b52abfc65b36595d00dce2b52a7250f4e",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0005-derived-rail-composition.md",
"sha256": "e02982ce54691cf1589ac3d04012b9b9282f9eb54ed4b8f2f0544371f9b87f3e",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0006-reef-production-admission.md",
"sha256": "5d0454384e1a3f31939aaa4e3d4225ede73c3669a2d86b15efb4b56803e01690",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0007-rapp-declaration-contract.md",
"sha256": "b1185388ded53188dc624fbda9bee517dd3ac80b35e881cd4a7962531bec52ff",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0008-private-by-default-exposure.md",
"sha256": "bd97ec8ee663bb416fae53c8cfe29c7066e4fd3a51a5bc364895297e6eb994b7",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-001-event-bridge-architecture.md",
"sha256": "57ecf490465e71cc4970133301cc315748b1d6ee65caac3690472ff88f3aab06",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-002-definition-format.md",
"sha256": "1670e1616094bc6f2c0c7d47b2e019c02d5870b22b564ef83afa24caff197bab",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-003-rule-instruction-model.md",
"sha256": "f1017730074727bcb0c68309c445ffb219ef647c8c5604adbfff76c4aa3f9d66",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-004-producer-trust-boundary.md",
"sha256": "92da58f58ee3dbb62233161a0fc0b7780920f81ec37fdd40d476be354f24e0eb",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-005-ops-runs-vs-dev-work-records.md",
"sha256": "984208262a0f6b67a6dc94cb078ebd0bcddc95d0b21a694d7e207677c10b471f",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0001-catalog-is-a-pointer-layer.md",
"sha256": "7df0bb364276e382cbee9383e7e67d399b0ac1b0353246e0ab323e629e732a6d",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0002-conduit-not-broker.md",
"sha256": "7dcc31732d774ddf2c98636b69ee12e2d74034836ed0def81b7e06461309b53a",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md",
"sha256": "45b47c02afa575fbfe5be980e426c331a1d99bb9cd9c7a8de80bf8e319469f81",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0004-agent-read-boundary-on-high-risk-lanes.md",
"sha256": "e379f47fb56214ff0cc6abcddaaad33c6969cf386e11abeed105b4fef953c5a0",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0005-implement-narrowly-route-broadly.md",
"sha256": "31eafe4d8d9362a3446739d63c3af83fd9138cfdc6ad120086312a67dc0d27d0",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-001-workplans-as-repo-artefacts.md",
"sha256": "183023ee57bae9c29e726fec5ee0361633fe3a2b182436a57869ae4b2a27af24",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-002-custodian-agent-runtime-design.md",
"sha256": "b860b02b1e8f47b914d41c6ecf67f451e86326ed51c7c53dcc6b7870fc889e42",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.html",
"sha256": "ea4eae05b62d50cd8204a47c1a4f3e3e0beda571880ed1de93ccbb17987d84aa",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "docs/evidence/2026-10-11-adr-currency-review/adr-002-accepted-1.md",
"sha256": "6aef66cd5cf71a48f5b4e14401dc19a755e2b182445b272d5952df0eb0dea8ec",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-003-materialized-derived-state.md",
"sha256": "fcb49719e2b85b9120c0bd5bebd5e82748ca713f16b44951c028b60205514e2b",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.md",
"sha256": "3b68adfa6ab329e73f857cf691dc405136d2d66a0135e2c37c236aabe4659557",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "docs/evidence/2026-10-11-adr-currency-review/adr-004-accepted-1.html",
"sha256": "3db1b0f5c50543ff0fb022ea67f1d85b30f8ff691eaa1a2443220b7474b54de7",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-004-connectivity-first-network-posture.md",
"sha256": "4f9978b40c104600c0ccb981a8ee52376a5d3003da4786752029af524b7dede7",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-005-cross-repo-workplans-project-repos.md",
"sha256": "13195a721d0e579715f5f39ca6f72b5e49c583611e6ca089e6d4618708ae917f",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-006-canon-federation-concept-ownership.md",
"sha256": "a454df0e1d227f99ebb36c4abd45c76cc12579086d34f0c0ccfccfd7f4790823",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-007-workplan-identity-and-repo-worker-topology.md",
"sha256": "1169f0c1f485a2bb7241a8f64f3167c060c04f83341b12586fd9be5c2b3693f8",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-010-hub-authority-and-local-cache-model.md",
"sha256": "5979da20799118259fc19246f51e8cc8f2c0c1be3d414318bd51d5a27d9ad565",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-011-federated-namespaces-and-reconciliation-limits.md",
"sha256": "f94f429c72f6cfd01ee83f1e5689d2d10ae52d7588d7cbd3ca40aca7eef46fb0",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "railiance-platform",
"path": "docs/adr/ADR-0001-s3-platform-service-boundary.md",
"sha256": "63697581401b53a8437835c2bb8b40f8054cc40d0bc7a2972f83a4a10377f6ba",
"kind": "clean-head-export",
"source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff"
},
{
"repo": "railiance-platform",
"path": "docs/adr/ADR-0002-placement-policy-ownership.md",
"sha256": "cfc0ad202c2eeeefd963127ff1defa127c715c001ecc684ab8759733bd8fb9f8",
"kind": "clean-head-export",
"source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff"
},
{
"repo": "railiance-platform",
"path": "docs/adr/ADR-0003-decisions-live-in-the-repo.md",
"sha256": "9b12ab6aa0e6f9eba03465782c35d6ff4683b682191599e38f4f9614cde9fa1b",
"kind": "clean-head-export",
"source_head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff"
},
{
"repo": "net-kingdom",
"path": "canon/standards/iam-profile_v0.3.md",
"sha256": "6c47b596ea145fa197c9665081c84902482d90f5bb94d185c1223af65279be1d",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0006-recursive-multi-tenant-identity-authorization.md",
"sha256": "e92a43649bb6e14e53ec62ecc819405bf3a44bda9557487f7177402f107bbd13",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0007-security-orchestration-boundary.md",
"sha256": "b4fcff8448f07aca1fcb6908618bdb19f6c0e7dce25d4c5c8b85eec2f175233b",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0008-object-storage-sts-credential-vending.md",
"sha256": "f47276f4953f62b783397ee7fb1d3693da060103247e425a9a5b40d019fb4272",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0010-orchestration-vs-dependency-self-coherent-intent.md",
"sha256": "b7c4f6a13f2f5add08bd03cb39c4f18ca25b202747dde883a309d1b3eaa1f571",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0011-iam-profile-ownership-and-version-governance.md",
"sha256": "b5c7fd1e78026063b4a2ca4017202512d4f94ab5e12dc8498a34d04d3a48c7a9",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0012-playbook-capability-contract-ownership.md",
"sha256": "e269fabfc376f97f2a03ea66e34059d54016a445a7f30b351a6774b65c96c2ee",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0013-tenant-onboarding-grouping-taxonomy.md",
"sha256": "3a6030a8958176a902942ffd29154104ba3441a09d06edf3deaeadc7291ee0d4",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0014-tenant-capability-roles-and-tenant-engine-ownership.md",
"sha256": "843f7a65f0fc145d08a73e364bc9a1dee0f7b8af934abf1584ee39dffa903ee0",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/adr/ADR-0015-netkingdom-railiance-workload-packaging-and-relational-platform.md",
"sha256": "2000985ef211aeedd3656e15cedb289e655526c2dcc4a161a64ffc27f0289db1",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "railiance-infra",
"path": "docs/adr/ADR-005-k3s-api-tunnel-only.md",
"sha256": "4d93643d00d19b9d7c46843cd9be674c24e4cd613dc8e780c1684d97a6a9030a",
"kind": "clean-head-export",
"source_head": "ef64e324b61e9aa9b9ed03aad7dd2101915057ca"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-006-glas-profile-execution.md",
"sha256": "88041e9eb3a0f91fdd9da46f9813d7304b5ba58e62f9d28ea8f63c0a7b055b63",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "activity-core",
"path": "docs/adr/adr-007-bounded-operations.md",
"sha256": "75f42047b51293f7240f8a8c3ec02bf025a47fe6627f106fc4ef22fb1be7459d",
"kind": "clean-head-export",
"source_head": "fd84f2b6518ff17509930499099ec2a6692d088c"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0007-build-stage-stops-at-credential-disclosure.md",
"sha256": "868f953688988b11ce48f4141833bbca51abdb4e86d5b495a8a905002830d7b0",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0008-grade-the-path-not-the-field.md",
"sha256": "2e22863ba592802cceb40b32d395168b42ace747741f5188307505eaa89ff764",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0009-adopt-security-zones-as-a-consumer.md",
"sha256": "73fff22177b4bec2c56ff737e06e4225eb02d39669be3ea1b723906245f878b8",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "ops-warden",
"path": "docs/adr/ADR-0010-ops-warden-is-staff.md",
"sha256": "064455bcb2870abf8e243f5a8c154e50bfc1c537cfba7996a792f9e314dd78ae",
"kind": "clean-head-export",
"source_head": "3eb648846e7814b9f640d02caa084692a784436b"
},
{
"repo": "railiance-master",
"path": "docs/adr/ADR-0009-netkingdom-security-layer-interaction.md",
"sha256": "c2f81c9718715fc08ea7a2e3021d0d3159958ace23b60a16430020637b9f57f2",
"kind": "clean-head-export",
"source_head": "078515b5adc7ac742ffad1b20577fcf88400acbb"
},
{
"repo": "the-custodian",
"path": "canon/architecture/adr-012-projection-source-and-preliminary-overlay.md",
"sha256": "b5e8582459f546ae789ad5fd62f458454aa19997b520e32b6b9f792d6af55987",
"kind": "clean-head-export",
"source_head": "dcdec162d61398f0def4990957455d45a74a1b01"
},
{
"repo": "net-kingdom",
"path": "canon/standards/posture-feedback_v0.1.md",
"sha256": "dd2628b9f0c2a662ac44af22657d91918da228f307ebc7ceb09fc856162985db",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/evidence/2026-10-11-layer-v07-immutable-publication/security-layer-model-v0.7.md",
"sha256": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b",
"kind": "reviewed-stage-overlay",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "canon/standards/security-layer-model_v0.7.md",
"sha256": "8155e7b123be1e84377d8278525b1bad8961007b6bb8ff012dd01ba24ff8065b",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "docs/evidence/2026-10-11-layer-v07-immutable-publication/security-layer-model-v0.7.html",
"sha256": "7d7a38a8c3b9fff65b46280fcd5723eff77dc9d0784f871bb35f718bc4e8c0e7",
"kind": "reviewed-stage-overlay",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "canon/standards/security-layer-model_v0.8.md",
"sha256": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "canon/standards/security-scenario-composition_v0.1.md",
"sha256": "17b715d78e04470a0f83b8bc8c7313ab16e13e9e741d5ec445172d9008fce937",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "net-kingdom",
"path": "canon/standards/security-zones_v0.1.md",
"sha256": "32e71e9c0d6946bb14099eb66193822da26f199de0e11d8488464207f3bd9906",
"kind": "clean-head-export",
"source_head": "0e17d956ab45f8c4a0163b70749490c414e54e67"
},
{
"repo": "risk-nexus",
"path": "findings/RISK-F-0001-flex-auth-unauthenticated-check.md",
"sha256": "b2d1d0c526d5c9b5729b8353877bb9f29096667145f1498cfde73c9711206fda",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "findings/RISK-F-0008-audit-retention-legal-basis-assumed.md",
"sha256": "1762d479f1ea572bacdb8ea7612b36b342fb6f87c21e5a4a92732c74215865b1",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "docs/method/severity.md",
"sha256": "f064c591aeef9425bbfbd5fa2aae659abb0ef83b8586848300077f41dd4ffaab",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "docs/method/disclosure.md",
"sha256": "bd825a87736c8fb019d4224c3ef4fe3882b8f3028035225b4b8ff8cad62fdb81",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "docs/method/review.md",
"sha256": "8f873d105bf6bd711a1a15ebd739b2abe97365b4e854be9d1c82e3ff3b162cfd",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "docs/method/verification.md",
"sha256": "562b6eb3031ffe35e712c5e11e35aae23ce84a90e0090438dc89513f9685052b",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
},
{
"repo": "risk-nexus",
"path": "docs/method/dependencies.md",
"sha256": "a5e7b42043b0bb366add9bbc5f606e134ef5b0454532480203d5d87099296240",
"kind": "clean-head-export",
"source_head": "7095f328e6639fd0033c90296ebc73e8acb76c9c"
}
],
"limits": "All canonical inputs exported from clean current HEADs; new archival files/helper/manifest are staged and not yet committed. This validates actual rendering/artifact preservation, not final release provenance or deployed serving.",
"regression_proof": {
"tests": [
"test_same_current_frozen_render_survives_new_head_and_superseded_notice",
"test_same_current_frozen_render_rejects_current_source_change"
],
"original_source_tests_run": 2,
"original_failures": 0,
"original_errors": 2,
"original_failed": true,
"final_full_suite_passed": 33
},
"helper_sha256": "815a623617e934dde8cabe9d58ce3d8e00fb74e3f42d21a511a8b0af524afb0e"
}

View file

@ -0,0 +1,166 @@
{
"schema_version": 1,
"review_role": "net-kingdom-integration-technical-review",
"reviewer": "custodian-codex/infd_image_preparation-independent-from-author",
"disposition": "agree-exact-amended-technical-packet-with-existing-human-and-native-gates",
"same_proposal_trial": "RAPP-USER-ENGINE-READBACK-2026-10-11-R2",
"original_conditional_packet_manifest_sha256": "3faf8e2b9b5c04b0e280268106519f52a3ff10eadba1c594c1fcca14630f3b98",
"amended_packet_manifest_sha256": "b4d45051aaa3de9cd7a4a4414a3a2c0a9672773fc6a1cd1e6931b2e21a6087c4",
"original_review_sha256": "2b5b4bb891f22c4c598d59be6c524c68be27d8f824a4d859a03a8c8502c13da5",
"original_receipt_sha256": "37884ee102d354366fbb651721dff7e4885f48951d2717148a4fefeeb26152b6",
"submission_digest": "sha256:0511d2ce7caff063db1d74f986f86e953f4ddb9b7b3affdf78558438f1958416",
"amended_packet_sources": [
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/argocd-before.json",
"sha256": "bd2aca067d8fd80e104e83b0de89e5705226c278ec99207c171eab2a48ea7e0e"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/baseline-source-match.json",
"sha256": "4cc7747b4aafc5f5ca44c93e0f8670ff818dc7c9ff0250e145a8dea081d713a5"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/before-metadata.json",
"sha256": "88a16f59557f5c06f9877d84f4c323905054f1c847a77dbf0474d0b2b343aca9"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/candidate.yaml",
"sha256": "3c0700c9d6505bf3c48b3e01e3b3537855cf0dd09486d4deb4812443d6509d99"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/change-path-before.json",
"sha256": "2e9426f2c1c7fa3ef0b77b17ade11651a823784564f52f043ba6a84069f7a6ca"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/decision-packet.md",
"sha256": "76814d47e33d652c39c78b11ee5a0498c3f10ff89a41c4219048d7d0eab0f5f2"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/exposure-inventory.json",
"sha256": "0597480b6539d0e5f27d2f90096cffa93c8820bceb6074d09ed4e778697de296"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/initial-checker-mismatch.json",
"sha256": "aeb19931ef8022765d1f0fdf6cd610a195687200de622d8d62ba7ba9bea464a3"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/intake.json",
"sha256": "17737f7d6973514095801ba01be995b945331e2ef6d3d0ac010ded72acb85480"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/lifecycle-commands.json",
"sha256": "37b0c9f35fc18180eeb62f3e3fc84ae34e60af05b24f774622f2e72d3c153792"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/memo.json",
"sha256": "117e4468ef00a8d80199e1fdcfb5a6ee8b2da3293505dab8073cfa35cebee6b0"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/proposal.json",
"sha256": "f890fcd61f5614628dfabeef76fc4a609360245fe43a607ff81dd5448c3da219"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/provider-before-metadata.json",
"sha256": "824023edfb175528353ceb3977277fb78811a3b1e7e778aa6b18be9829199f0b"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-ci-repair-native-outcome.json",
"sha256": "81a9115550b22d2ec1079e178383439601931d0658bc24a87ba8409205693bdc"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json",
"sha256": "2f84959e36d442860fb2b5a5817bb707c4f8372797a3ef8dd0d5cab956af86ce"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/review-refinement.json",
"sha256": "abdf518d27d6a6f78429159fa7e01c40ed68ef890a630abfacb92272b3cac12a"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/risk-assessment.md",
"sha256": "db5a9d37d45b4f04dd6841ee77f16b2024c7e7d85c9b880e8f0f8efedfbc0a5d"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/rollback.yaml",
"sha256": "a859b68d9e5c1bc94ef90c286c183c53fc09473572fbd017604383fb8828fa7d"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/server-dry-run.json",
"sha256": "af5924ae0428124a897af979f111e629b85b15b3a06daf924d856afc58fe6bac"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/strict-guard-regressions.json",
"sha256": "777548c530c253b4d62291acc99e73bf9ac182ade4f11567625867c93a7c45b8"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/strict-guard.py",
"sha256": "6b0944584d2bd76177e84c047f3714f6dfac4537c21e00102d270e8a1373876e"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/test-strict-guard.py",
"sha256": "180134191b94db33f53a8708749639814ed8bee3da149f53eeb5533349e86287"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/validation.json",
"sha256": "7d3c57fd6b9aa9f7a2b2a6315a4f2f44e51080ae20a03e980ac31eaadb4944eb"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/verify-dry-run.py",
"sha256": "c5da990c21499cdac04d4592efdfcceb09251a77cce4473d58959fa1e3ca8060"
},
{
"repo": "rapp-user-engine",
"path": "workplans/RAPP-USER-ENGINE-WP-0004-published-readback-rollout.md",
"sha256": "083f7cfcf7f45387a72e28c76a70f2ae8ff8607314265f830907ca7bbf100742"
},
{
"repo": "risk-nexus",
"path": "findings/RISK-F-0016-user-engine-os-runtime-findings.md",
"sha256": "caea6a4232e2b73bc24756721c947016ca7ce0e4b8500ac204b2436e372aea26"
}
],
"verified_refinements": [
"Decision/risk/memo explicitly disclose same workload ServiceAccount audience and3600s projection, routing/rotation separation without cryptographic capability-isolation claim; substitution untested and not prevented",
"Candidate and rollback bytes unchanged from original exact reviewed packet",
"Independent actual-source intake/memo/12documents/lifecycle validation and all26filehash checks pass",
"Strict declared/admitted negative fixtures:8mutations x2checks rejected; exact candidate passes",
"Fresh serverdryrun00:37:02Z read-only all2unique identities and exactly live specs+prescribedchanges; source/serverchecks and clientdiff passed",
"Original conditional snapshot and initial failed refinement checks retained"
],
"source_request_narrowed_or_widened": false,
"risk_nexus_grade_assigned": false,
"human_risk_acceptance": false,
"human_deployment_approval": false,
"native_readback_accepted": false,
"reviewer_native_action_executed": false,
"remaining_execution_gates": [
"Authenticated informed-decision human disposition bound to exact amended packet and twoobjectcandidate, accepting44HIGHresidual risk",
"Fresh strictbaseline/provider/admission/headroom/context/ownership preflight immediately beforeexecution",
"Execute only approved twoobjects before2026-10-12T23:59:59Z; no catalogue/policy/credential/database writes",
"Manualriskreview/renewal or exactrollback by2026-10-18T23:59:59Z; projectedtokenrotation doesnotendtrial",
"Real admitted consumer/catalogue verification; syntheticcases do not establishlogin/MFA or finishtasks"
],
"authority": "Owner technical source review only; no RiskNexus grade, human disposition, runtime grant or scored privileged success created"
}

View file

@ -0,0 +1,14 @@
{
"manifest_sha256": "b4d45051aaa3de9cd7a4a4414a3a2c0a9672773fc6a1cd1e6931b2e21a6087c4",
"manifest_files_digest_verified": 26,
"intake_valid_offline": true,
"submission_digest": "sha256:0511d2ce7caff063db1d74f986f86e953f4ddb9b7b3affdf78558438f1958416",
"memo_typed_valid": true,
"all_12_packet_document_hashes_and_actual_files_match": true,
"all_12_lifecycle_documents_valid": true,
"candidate_rollback_exact_two_object_identity_match": true,
"NP_adds_only_one_conjunctive_TCP8080_provider_rule": true,
"Recreate_replica_and_resource_requests_unchanged": true,
"readback_and_enforcement_same_serviceaccount_audience": true,
"independent_native_action_executed": false
}

View file

@ -0,0 +1,191 @@
{
"schema_version": 1,
"review_role": "net-kingdom-integration-technical-review",
"reviewer": "custodian-codex/infd_image_preparation-independent-from-author",
"review_date": "2026-10-11",
"exact_original_packet_manifest_sha256": "3faf8e2b9b5c04b0e280268106519f52a3ff10eadba1c594c1fcca14630f3b98",
"disposition": "technical-agreement-with-required-disclosure-refinement-and-native-execution-gates",
"risk_nexus_grade_assigned": false,
"human_risk_acceptance": false,
"live_action_approved": false,
"native_action_executed": false,
"review_sha256": "2b5b4bb891f22c4c598d59be6c524c68be27d8f824a4d859a03a8c8502c13da5",
"reviewed_packet_sources": [
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/argocd-before.json",
"sha256": "bd2aca067d8fd80e104e83b0de89e5705226c278ec99207c171eab2a48ea7e0e"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/baseline-source-match.json",
"sha256": "4cc7747b4aafc5f5ca44c93e0f8670ff818dc7c9ff0250e145a8dea081d713a5"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/before-metadata.json",
"sha256": "88a16f59557f5c06f9877d84f4c323905054f1c847a77dbf0474d0b2b343aca9"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/candidate.yaml",
"sha256": "3c0700c9d6505bf3c48b3e01e3b3537855cf0dd09486d4deb4812443d6509d99"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/change-path-before.json",
"sha256": "2e9426f2c1c7fa3ef0b77b17ade11651a823784564f52f043ba6a84069f7a6ca"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/decision-packet.md",
"sha256": "34a555f0a6a778fd43712d4cdef86668189dca015c9dc2dc9f2915ea3231c8e6"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/exposure-inventory.json",
"sha256": "0597480b6539d0e5f27d2f90096cffa93c8820bceb6074d09ed4e778697de296"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/initial-checker-mismatch.json",
"sha256": "aeb19931ef8022765d1f0fdf6cd610a195687200de622d8d62ba7ba9bea464a3"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/intake.json",
"sha256": "909c0a1deae53820e1ea08b2ac7c4d38810550f8d49dbf0ceefcda6c529588df"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/lifecycle-commands.json",
"sha256": "bc05f4317e6df688f496bb87eb08fce6a78459d38b5493364c07b7e12683fda8"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/memo.json",
"sha256": "bdcbee866c9a296a0501d185daadf5d45f3542257232c5ff2b34860a1a618ce8"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/proposal.json",
"sha256": "ed503194028d22386efc5195b813ac0aa0caed5bd0d74ada301a654c4bf02754"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/provider-before-metadata.json",
"sha256": "824023edfb175528353ceb3977277fb78811a3b1e7e778aa6b18be9829199f0b"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-ci-repair-native-outcome.json",
"sha256": "81a9115550b22d2ec1079e178383439601931d0658bc24a87ba8409205693bdc"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json",
"sha256": "2f84959e36d442860fb2b5a5817bb707c4f8372797a3ef8dd0d5cab956af86ce"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/risk-assessment.md",
"sha256": "030a161d3fe0ee7c141ced8ef24f37c85bcfb6379ece4b838b329047ae569da0"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/rollback.yaml",
"sha256": "a859b68d9e5c1bc94ef90c286c183c53fc09473572fbd017604383fb8828fa7d"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/server-dry-run.json",
"sha256": "9384b00e4d10eefbb18c4dbba2f7e58eae01d02e2acd5fa41b243c1b475b4d0e"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/validation.json",
"sha256": "2fc86e37f5e0df4bb18820697ff71ce9258176cd25c9ff005c3fb4b6f970c715"
},
{
"repo": "rapp-user-engine",
"path": "docs/evidence/2026-10-11-published-readback-proposal/verify-dry-run.py",
"sha256": "de88e7d1e33673a69f64d865e02464baa64b1f6729379c4f6e9065e27e911028"
},
{
"repo": "rapp-user-engine",
"path": "workplans/RAPP-USER-ENGINE-WP-0004-published-readback-rollout.md",
"sha256": "083f7cfcf7f45387a72e28c76a70f2ae8ff8607314265f830907ca7bbf100742"
},
{
"repo": "risk-nexus",
"path": "findings/RISK-F-0016-user-engine-os-runtime-findings.md",
"sha256": "caea6a4232e2b73bc24756721c947016ca7ce0e4b8500ac204b2436e372aea26"
}
],
"owner_source_hashes": [
{
"path": "/home/worsch/net-kingdom/AGENTS.md",
"sha256": "065d47b7bdbfe829672a2a6161a7f8cf243237a1210fb61d82f153fa37f22131"
},
{
"path": "/home/worsch/net-kingdom/INTENT.md",
"sha256": "fd2ba8dff9dfd5583ec6d8b0348f273bfe00c59923b5d5d47cd22b1fb4658c54"
},
{
"path": "/home/worsch/net-kingdom/SECURITY-COMPANION.md",
"sha256": "06374445ca888549f1bf79527d1345d94918d7a7ec363160d8427d0e7a31d643"
},
{
"path": "/home/worsch/net-kingdom/canon/standards/security-layer-model_v0.8.md",
"sha256": "3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b"
},
{
"path": "/home/worsch/user-engine/INTENT.md",
"sha256": "8aa0f276e0879d3545338727188a1fd5af973c43acbfa986a135b20979ec7ba4"
},
{
"path": "/home/worsch/user-engine/src/user_engine/application_access.py",
"sha256": "798f180474d98e1c6abacc7b8bfba3184d40ecb77d9ca90f6526cd4f774cf363"
},
{
"path": "/home/worsch/user-engine/src/user_engine/adapters/application_access.py",
"sha256": "adf1fac707446f41b5859ba5102e820cd88f60af5daf20a42f13dfa2067d5481"
},
{
"path": "/home/worsch/user-engine/src/user_engine/adapters/flex_auth.py",
"sha256": "73d645b7b37904ebfe0df6f7ed397b74b4c47f505cd64a30c39e60717ce71455"
},
{
"path": "/home/worsch/user-engine/src/user_engine/web.py",
"sha256": "c5fbd412102238219f3b231557815fb30c0a7f822c28a371bcffc63076456a14"
},
{
"path": "/home/worsch/rapp-user-engine/AGENTS.md",
"sha256": "458931a839e65f752f265a332a54522aff9cb65868c5cc1531479850b4496d5e"
},
{
"path": "/home/worsch/informed-decision/informed_decision/intake.py",
"sha256": "a733b4b608b9ea90b2c8293901aa14388a0bf0d3719f121038bfdcc1faca2038"
},
{
"path": "/home/worsch/informed-decision/informed_decision/memo.py",
"sha256": "697058863a4e6de2c143a2433eaaf5e15e5e7375dc5459af6911b146567e8b5d"
},
{
"path": "/home/worsch/informed-decision/informed_decision/lifecycle_commands.py",
"sha256": "783ec451dd7ef33dc0cfd72c1633ea5208dfd463afeaa0b68d279f156a9b0828"
}
],
"conditions": [
"same-ServiceAccount/audience projection is not proven authority isolation disclosed in packet and risk assessment; regenerate hashes and obtain amended exact review",
"strict fresh preflight exact2uniqueobjectidentities and intendedNPpeer, no zip truncation/duplicate names acceptance",
"explicit human conditional44HIGHrisk+exactrollout disposition",
"manual review/renewal or rollback by dateddeadline; no automaticcontrollerclaim",
"native actualcatalogue/consumer tests before task closure"
],
"knowledge_orientation": {
"version": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"source_status": "withheld; actual NetKingdom owner sources reviewed"
}
}

View file

@ -0,0 +1,19 @@
# Independent NetKingdom integration review of User Engine readback R2
Role: NetKingdom integration technical review, independently performed by custodian-codex worker infd_image_preparation. This is neither RiskNexus grading nor human residual-risk acceptance, live rollout approval or an autonomous authority grant. Exact reviewed source packet manifest SHA256: `3faf8e2b9b5c04b0e280268106519f52a3ff10eadba1c594c1fcca14630f3b98` (22 files). Disposition: technical agreement conditional on the credential-separation disclosure refinement below and existing execution gates. The original revision remains reviewed evidence; any amended memo/packet hashes require explicit amended-hash confirmation.
The packet honestly retains a failed clean-scan gate: actual immutable f239bbf image scan SHA17b793f13bd19fa87bba840ced3c9c40a78a78feda569019e92c347dc7f3e76a contains44 HIGH package/CVE occurrences,8 distinct CVEs,0 CRITICAL and0 fixed HIGH versions for scanned packages. Independent recount agrees. Every primary Debian tracker record was checked on2026-10-11; the stated vulnerable Trixie/unfixed-package observations and upstream fix availability agree. Privileged utility prerequisites constrain plausible paths; they do not prove unreachability. The bounded missing homed/Archive::Tar observations, possible denial of service/application compromise/mounted credential exposure, unchanged old-image uncertainty, and unpriced security incident consequences remain explicit. No scanner severity is downgraded by this review.
The change follows owner intent: User Engine remains a PIP/domain and integration owner; KeyCape owns authentication/client identity, Access Engine remains the sole PDP, and User Engine's local catalogue only joins matching client issuer/id and protected-system registration. The readback cache includes session, issuer, subject and tenant, bounds reuse by lifetime, retries unavailable results and renders expired/missing/non-allow results unavailable. Readback is used for advisory display; the protected mutation adapter still calls its separate enforcement endpoint. The packet correctly leaves catalogue and actual admitted consumer tests open; synthetic policy fixtures are not a real human login/MFA receipt. This source review does not assert that catalogue records already exist or that native consumer acceptance is complete.
Required disclosure refinement: the readback token and existing enforcement token are both Kubernetes projections for the same User Engine ServiceAccount and audience `flex-auth`, expiration3600s. Separate files/projections and distinct HTTP adapters are routing/rotation separation, not demonstrated cryptographic authority isolation. Tokens may be substitutable across existing provider registrations for the same subject/audience; this proposal neither tests nor prevents that substitution. Its statement that no enforcement grant changes is valid only as unchanged admission/policy, not as a new token incapable of enforcement. State this directly in both decision packet and risk assessment and regenerate the exact memo/intake/lifecycle hashes. No credential values, token fingerprints or secret metadata were read.
Candidate and rollback hashes match; both contain exactly Deployment/user-engine and NetworkPolicy/user-engine-runtime in namespace user-engine. Independent comparison confirms the only NP delta is one conjunctive namespace+provider-pod peer on TCP8080; ingress and all prior peers remain identical. The Deployment retains Recreate/one replica, resource requests/limits, database/Secret references and enforcement mounts. The projected readback token and corresponding env/mount plus exact immutable image are the documented deltas. Baseline server-dry-run receipt shows the expected two identities and scoped changes at00:28:18Z; no mutation occurred. No new Secret, ServiceAccount, catalogue seed, policy grant, provider deployment or database write is within the rollout approval.
Fresh preflight must assert exact object counts and unique identities for baseline/candidate/server-admitted objects, exact approved image/hash, unchanged declared baseline/provider readiness/admission and native management ownership. Treat NetworkPolicy additions by their exact new peer, not an unrestricted `spec/egress` allowance. The preserved checker uses zip/list-to-name normalization and a whole-egress change path; the observed current receipt is complete, but that helper alone must not accept a truncated future response or duplicate names. These strict checks fit the existing packet's mandatory two-object/diff stop conditions. Reobserve requested CPU/headroom;3460m/4000m is historical, and no request/surge increase is included.
Execution expires2026-10-12T23:59:59Z; approved residual-risk disposition requires review/renewal or the exact rollback by2026-10-18T23:59:59Z. The service has no controller enforcing either deadline, and expiring projected tokens are routinely renewed while the pod runs. Credential TTL therefore does not end the trial or replace manual risk-review responsibility. Follow the existing supervised record and native change gate; require explicit informed human disposition for both the residual OS risk and exact two-object rollout. No source acceptance or technical review substitutes for it.
Rollback is one coordinated two-object restoration to the exact old immutable image/configuration and prior NP, preserving database/enforcement custody. It must be used on failed readiness/enforcement/unexpected effects and at manual review expiry without renewal; verify recovered readiness, old image/NP and enforcement behavior. The old image is not assumed clean. Deployment readiness, source/CI checks and this technical receipt cannot finish USER-WP0026-T03 or USER-WP0028-T02 without actual admitted consumer/catalogue acceptance.
Primary exposure references: [mount](https://security-tracker.debian.org/tracker/CVE-2026-76642), [nsenter](https://security-tracker.debian.org/tracker/CVE-2026-78408), [subdir](https://security-tracker.debian.org/tracker/CVE-2026-78409), [bind mount](https://security-tracker.debian.org/tracker/CVE-2026-78410), [ACL](https://security-tracker.debian.org/tracker/CVE-2026-54369), [ncurses](https://security-tracker.debian.org/tracker/CVE-2025-69720), [systemd](https://security-tracker.debian.org/tracker/CVE-2026-16742), [Perl](https://security-tracker.debian.org/tracker/CVE-2026-9538).

View file

@ -0,0 +1,14 @@
{
"manifest_sha256": "3faf8e2b9b5c04b0e280268106519f52a3ff10eadba1c594c1fcca14630f3b98",
"manifest_files_digest_verified": 22,
"intake_valid_offline": true,
"submission_digest": "sha256:a1aa280ee929f1c88e7b88570ad5b173aff23dc255c554b62c6e7397662d59e4",
"memo_typed_valid": true,
"all_12_packet_document_hashes_and_actual_files_match": true,
"all_12_lifecycle_documents_valid": true,
"candidate_rollback_exact_two_object_identity_match": true,
"NP_adds_only_one_conjunctive_TCP8080_provider_rule": true,
"Recreate_replica_and_resource_requests_unchanged": true,
"readback_and_enforcement_same_serviceaccount_audience": true,
"independent_native_action_executed": false
}

View file

@ -0,0 +1,58 @@
{
"observed_at": "2026-10-15T20:51:39.050158+00:00",
"read_only": true,
"jobs": [
{
"namespace": "sso",
"name": "lldap-backup",
"uid": "6f150dff-3ef9-4a35-8c20-cfedf3fc3d0c",
"images": [
"nouchka/sqlite3:latest"
],
"command_sha256": "7aad6525ff542febe20aa23a4a9d57754014b291b56cff8c1d35a9c85bc7d7de",
"pvc_refs": [
"lldap-data"
],
"command_mentions_enckey": false,
"command_mentions_private_audit_key": false,
"command_mentions_public_audit_key": false,
"command_mentions_s3_or_webdav": false,
"coverage_semantics": "Command-string observations only; neither success nor complete off-host recovery is inferred."
},
{
"namespace": "sso",
"name": "authelia-backup",
"uid": "b00880db-acbb-413d-ad0c-bce2354449b6",
"images": [
"nouchka/sqlite3:latest"
],
"command_sha256": "2f2cd0b65e840edbfa5a618d52286035bc769167a29540646fae11c89f6e69fc",
"pvc_refs": [
"authelia-data"
],
"command_mentions_enckey": false,
"command_mentions_private_audit_key": false,
"command_mentions_public_audit_key": false,
"command_mentions_s3_or_webdav": false,
"coverage_semantics": "Command-string observations only; neither success nor complete off-host recovery is inferred."
},
{
"namespace": "mfa",
"name": "privacyidea-backup",
"uid": "8bce84b6-a10a-445d-a3c2-bfd2e3ab8d8c",
"images": [
"busybox:stable"
],
"command_sha256": "b4146f4b110ae3448f86552da2087b6c612b920ce33041d2cc9c2287c5287eaf",
"pvc_refs": [
"privacyidea-data"
],
"command_mentions_enckey": true,
"command_mentions_private_audit_key": false,
"command_mentions_public_audit_key": false,
"command_mentions_s3_or_webdav": false,
"coverage_semantics": "Command-string observations only; neither success nor complete off-host recovery is inferred."
}
],
"offhost_asset_coverage_confirmed": false
}

View file

@ -0,0 +1,32 @@
{
"observed_at": "2026-10-15T20:51:17.348168+00:00",
"read_only": true,
"rows": [
{
"namespace": "mfa",
"name": "privacyidea-backup",
"uid": "8bce84b6-a10a-445d-a3c2-bfd2e3ab8d8c",
"schedule": "30 3 * * *",
"suspend": false,
"last_successful_time": "2026-10-15T03:30:05Z"
},
{
"namespace": "sso",
"name": "authelia-backup",
"uid": "b00880db-acbb-413d-ad0c-bce2354449b6",
"schedule": "15 3 * * *",
"suspend": false,
"last_successful_time": "2026-10-15T03:15:04Z"
},
{
"namespace": "sso",
"name": "lldap-backup",
"uid": "6f150dff-3ef9-4a35-8c20-cfedf3fc3d0c",
"schedule": "0 3 * * *",
"suspend": false,
"last_successful_time": "2026-10-15T03:00:06Z"
}
],
"offsite_retrieval_verified": false,
"current_credential_validity_verified": false
}

View file

@ -0,0 +1,476 @@
{
"schema": "netkingdom.bootstrap-caller-inventory.v1",
"observed_at": "2026-10-15T22:29:09.131605+00:00",
"scope": "all current .sh/.py non-test files under sso-mfa/bootstrap and sso-mfa/k8s, Makefile, console, delivery copier and recovery transport tool plus two bounded provider metadata inventory helpers plus bounded fixed-object ciphertext fetch and isolated PostgreSQL recovery check plus proposed full identity backup profile and metadata-only preparer",
"entry_count": 93,
"entries": [
{
"path": "Makefile",
"sha256": "5d2114f6141bb8504c5c780044302126c07958a5732afdb0d0c0218a4f84210e",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/apply-service-secrets.py",
"sha256": "ea9c3418ed00527b9019ab9893f1f3c9fe29d0697f1f0c879e28fdbae373889b",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/creds-apply.sh",
"sha256": "a70dba396484b2a353a39349c3d3719f7b6695b9f0e35025ef79e3db0ec2ff53",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/creds-bootstrap-agent.sh",
"sha256": "cc27c4f3969c292a913a0ae325cd0d4ec2665f9fffbbd1eb1c5f73b08e398a32",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/creds-rotate.sh",
"sha256": "09b387d3555ce2beaeb8d7f000c2d52a1ffea274c793f30bce80a8f3ef5dc2f8",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/creds-status.sh",
"sha256": "f0fe0b885fd22f4789baa86cc2438272700aff8634cfa6b27d55c2c860c9f36d",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/bootstrap/creds-verify.sh",
"sha256": "101b12948c862f21a668b49b3a82efae691ba885d5c8fd7a94a10e095c16a24a",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/bootstrap/decrypt-secrets.sh",
"sha256": "1572c170ceb9eff9ff408409acb82650ac911edb02961cde48806c68fa80ce76",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/emergency-bundle.sh",
"sha256": "c1a0e9853dae6b71537ecb708b05ce967eacf797334192156485ca943dc8c71e",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/bootstrap/encrypt-secrets.sh",
"sha256": "5b4774488e09a22b409079021e7cd10632c7cf6cf9db6f98cd85e3878d34d435",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/gen-secrets.sh",
"sha256": "b4a910cca93427c0e70e1f4c7646fc94d9b885aeae65a10a01cf8e11548ce03d",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/openbao-init-unseal.sh",
"sha256": "49485948a8d44576e293b6f709f1bb444e36bed3310c843d47393c3c8d4c1d0b",
"classification": "lab-only custody adapter; explicit target; production forbidden"
},
{
"path": "sso-mfa/bootstrap/openbao-lab.py",
"sha256": "0c14e38f86e189f6123b99e036e4423ebb31266d90f4c4423d3619c2060c3bc7",
"classification": "lab-only custody adapter; explicit target; production forbidden"
},
{
"path": "sso-mfa/bootstrap/pack-bundle.sh",
"sha256": "53abc7af3dcda6cfa9f52e9c10d352b66defa2cbfa7cd71b07c483c6f4a60f66",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/rotation-env.py",
"sha256": "bab079a51b8e7ad0199676e76e473a13e70f46e0495d8c942502c952236ec893",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/sops-custody-unlock.sh",
"sha256": "8a6c02ace974b489d23869a9e99d3305896f4bdc4c430164df09bb4ce1710959",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/sops-private.py",
"sha256": "113d17ee4cd7ad405e73cc51e1823dc02bd2e618e7fa67c1b624f0226e334ec6",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/bootstrap/sops-write.py",
"sha256": "5a785e9c8e6a40ca9120a668d93992ea5c3115566ada21933536cc2d931f1ddf",
"classification": "private custody/local operation; explicit input; owner procedure required"
},
{
"path": "sso-mfa/k8s/authelia/create-secrets.sh",
"sha256": "137e137d973993e3938dbfe656b036e8d4d9643f84b5e8ca7ab30fbc5810ea1c",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/approval-clients-rollout.py",
"sha256": "488d5cc59e081993dd2b5fc6d9e46bbddcaec4f5170b8f7a5ad0aae3a211a790",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/authentication-policy-rollout.py",
"sha256": "2c1bd23fded4855bae1eb3a08dbc4510d7f60f2bc4d625d60e471ab78f6f0251",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/configure-openbao-oidc.sh",
"sha256": "bf214ac511fbd2269935f3270c6d4491c729d834e8138b9d7404aecf23c0825f",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/create-pi-token.sh",
"sha256": "2f750aea638c06eb11dfe7afcf7b3d6117ba9e964cbf2a48bb12590054178152",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/create-secrets.py",
"sha256": "5da672fff36eb94101741ad99e2a1a96ae81fea15ed1871304c40e63ce27c787",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/create-secrets.sh",
"sha256": "f5131160d91641a59c3163a17a8dd4f9b97d228eb8a5e0d8ad04cc2f7ff51644",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/exercise-approval-clients.py",
"sha256": "e2d3dd741ac52af28dce752344227a4443b78fedd4cfcc06ba902e679dfc9351",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/keycape-client.py",
"sha256": "e31ee2883fad4f93635700608ad98e05984c9e0453515dd7710c897ca856b8c8",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/openbao-client-config.py",
"sha256": "1b908b4c36134212d78f9fbe7a2a201eb3ced06d31bb1dcea4a59d80689d84b1",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/openbao-negative-node-wrapper.py",
"sha256": "76ac013d1a676a0dfa58aea1ce87b0c3e72483ccd21f586a22c5475e9132f6c1",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/openbao-negative-session.py",
"sha256": "24b7b803e4eb7e6e7c0700a069dad0ec315ee7eb5538a88b9e56147f8fab5cdd",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/openbao-signin-diagnosis-rollout.py",
"sha256": "a57b957d6eed9e8fb09d2a7aa7405a39deeafd9aaf57a9e6dc907afabd380371",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/patch-openbao-client.sh",
"sha256": "cfd0dbd0528e799ee59d8aa4cd1cefc2189227cbc92d251f2a67bb25212aecad",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/playwright-activation.py",
"sha256": "5ac683b3324b00e6253f6040f4c73057027b1f98f7071160a6153794ba522757",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/playwright-registration.py",
"sha256": "bf2d6b83d93629df8ddaf6c31d6c7408ae3866969d63895714df0e08a570da99",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/portal-client-rollout.py",
"sha256": "158e1d36eb881cdd0a2eda91b472adea5e8fde5061e00d26dda368580aeb871f",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/refresh-pi-token-live.sh",
"sha256": "2f750aea638c06eb11dfe7afcf7b3d6117ba9e964cbf2a48bb12590054178152",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/register-coulomb-social.py",
"sha256": "b40ff4bdba71b797f8f393e79cb3c9d73b790045a0f7df6ecea35803369793c7",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/register-coulomb-social.sh",
"sha256": "cfd0dbd0528e799ee59d8aa4cd1cefc2189227cbc92d251f2a67bb25212aecad",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/keycape/register-user-engine-portal.py",
"sha256": "67173edc44f5457e7b6a3e0fb2321b398159cfce5dea8ead01eabf835fb38d76",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/secrets-engine-openbao-client-rollout.py",
"sha256": "8b834e5f0353a5e11797637420609e06e7bf7de47777c7e78b774f74d4a6afbd",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/vergabe-client-rollout.py",
"sha256": "8cee6cc5ff3e3e8617304055263d04b393d0e005c34d9af12cb7e8bc685bac1a",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/keycape/verify-openbao-client.sh",
"sha256": "6f8e75ad16f0d35f776d1a8da85842dc1174085a730f0189a7f5a36a0d7db83f",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/keycape/verify-user-engine-portal.py",
"sha256": "ccd5106395bd5d7b4f3dbbcbad449e3793aff217d4429b5d5af9e52ab9a58103",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/lldap/bootstrap-users.sh",
"sha256": "309da1de430638e4ed0b9bfe11b7bbb472a6bfe364cdfb6c69499c6fb78b66d6",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/lldap/break-glass.sh",
"sha256": "a30cc22a990d368b9066e2e8b7aaef1c993c1c8a86e191ad2afb0e5c4d4f3294",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/lldap/create-secrets.sh",
"sha256": "fb2ee3b5af858297d20f8bc8821ee13a86d6ebc9235756b9ed9a8e85a54d84a1",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/lldap/create-user.sh",
"sha256": "a30cc22a990d368b9066e2e8b7aaef1c993c1c8a86e191ad2afb0e5c4d4f3294",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/lldap/dry-run-nonroot-user.sh",
"sha256": "a30cc22a990d368b9066e2e8b7aaef1c993c1c8a86e191ad2afb0e5c4d4f3294",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/lldap/identity-provisioner-reconcile.py",
"sha256": "e124b94c3a5749d4e3fdfc5fd054acfb295594beac24cd399a7b24a8617683d6",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/lldap/lldap_admin.py",
"sha256": "5e39e2eac53846a97cd25151c0a4af1ffc0230db881a6325efb66de6a51bfc36",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/lldap/manage-group-members.sh",
"sha256": "3c85e4e882ad003a4dc513d9085d136531511273db8adf27e1fcf5e749ae04d1",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/postgresql/create-secrets.sh",
"sha256": "801ae8a404f0a8fd2bb349003091ab6a509df2533bc1a0f456dd7d5940d917e9",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/activate-onboarding-policy.py",
"sha256": "edd6d3d4507e11e1632002e56f948bb2e81465132500627b5ddc7fa0f06203d1",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/bootstrap-admin-remote.py",
"sha256": "c83c3a5c9a753a8afec90c35ce5ddbfc15f7b8aff437065f428109d9c5774b89",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/bootstrap-admin.py",
"sha256": "b81b5c3806dbdf03ea7cac37884cc368de0f0ef7880ac326b67af3a291c35890",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/bootstrap-admin.sh",
"sha256": "1a11738c1790cc6b5f23ed40f02971591eb76afa486be89d5535b0027aca6989",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/bootstrap-realm.sh",
"sha256": "d3dc530d378ce826f4875c4cf066a6f7f916c3abd5492eb5ee237a8c98b7957b",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/check-user-mfa-state.sh",
"sha256": "18901decfdc974ba5a6e871b7a4fe8022be59c73123774a31c57303fd5f657be",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/create-secrets.sh",
"sha256": "6644b37f2a0ba09b67e135516204950474bd8fa28a243f62e29b898d00c08d91",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/deploy-factor-recovery.py",
"sha256": "9a46aa7a1fdf7c202981f2529bc138dcdd952fbefab1d18a88ac65176936a389",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/deploy-onboarding-guard.py",
"sha256": "95a3195d01775ea1d800aaa37131d440acc92f2a50215a108c0b63a3441cce94",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/enable-factor-confirmation.py",
"sha256": "f71e22d6a7a99f4293fb688567ec11c6826a543223b27168e0577e8a6070581e",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/enckey-bootstrap.sh",
"sha256": "e47b86ae422f043145525b3deaa4c57ce6ee92974db090695be7fafbe7b61a2e",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/enckey-restore.sh",
"sha256": "fb31831c1492006ad5f32e16f2ce47baf6e20d0cb56a592432da0698052bf362",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/key-custody-remote.py",
"sha256": "27b2b885c81923d85054e3999c5613e104fe627779b2b50398ed9bed5088f47e",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/key-custody.py",
"sha256": "e43d21694cb9c99bd02257a4f225973e992c9af4b0e71788929f949b9752f08a",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/keycape-scope-probe.py",
"sha256": "847b8d7c72c11deab15b3d16964e2619011e21d219b1d82c25f0346493abbbef",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/mfa_state.py",
"sha256": "3f0c2e404d7df41af7f8e1fe66a4a6d392338391456ef298c7dd0219ad3d58dc",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/privacyidea/pi_api.py",
"sha256": "01bbf3a2c1f8c423f864dc4bd2145c5c89f938fa9e8b0ffbbecf6d674484896b",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/realm_bootstrap.py",
"sha256": "2885086fdc4fd64b58496f90e82c75eefc7171017004e0804defaaabf791b3a9",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/reconcile-lldap-resolver-live.sh",
"sha256": "89f5b279cde441ef8a7c63436e63c6242527d36949aba4bad2134c890a9e0b04",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/repair-realm-live.sh",
"sha256": "2684a6caca848f5d9e74191af4ca2864adba92fe3ee410a4236045d1743b99e6",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/resolver_cutover.py",
"sha256": "6ed2df95671897f073a3dc8fee62b780cdff87b08d617d5a337e1fbba206e10a",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/update-lldap-resolver-live.sh",
"sha256": "ffa7c551dbcc6ab74d2cfc0853fd0647dd026259b1541f99e4a84dcbac16588c",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "sso-mfa/k8s/privacyidea/verify_mfa.py",
"sha256": "118d0052433edb85dd53e3c988943392f59d0aa00641d45f8ff3676a15f8af19",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/user-engine/verify-operability.sh",
"sha256": "2e1cbde1d2aa2ce9f27685baf93137d5f13d33a77421a2fba1649f874f2b7f48",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "sso-mfa/k8s/verify-identity-cutover-dependencies.sh",
"sha256": "96da30c4eca28615685ff384bbc7d00d81c7562e8e572a3ad54c3cc6fdd297ca",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t02.sh",
"sha256": "7a9fe613fc83841397f2e3b0b4c26c5464d7b903d5e56a83ec786e2289719b24",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t03.sh",
"sha256": "7b3a888ae20394b761d7cd9cf1bc81f273813794a3002202951b061bd6c27963",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t04.sh",
"sha256": "74128536919f79f17bffc107678718848c4ca095de06140cdc453e68953aabd1",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t05.sh",
"sha256": "bb226bad1e5fb60a5d6f87fa67ecee2d6af84d6d6a84fa02d9839d8bca58a197",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t06.sh",
"sha256": "717317c1d51f7c3361f3b711d79cae52019c36cd6c397e5fa61f07eaad3632e2",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t07.sh",
"sha256": "026406ff1fec9e4bf542741c08002c45ea8a94a4ce5a50980665b601485882df",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "sso-mfa/k8s/verify-t08.sh",
"sha256": "7efe14c0d71f2c846434d19f8d05702d69d3c40a617a700b08fe13b3764d0650",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "tools/bootstrap-recovery/inventory-scaleway.py",
"sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
"classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
},
{
"path": "tools/bootstrap-recovery/inventory-storage-share.py",
"sha256": "673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5",
"classification": "read-only bounded owner backup metadata; existing caller/custody only; no age key or backup content read"
},
{
"path": "tools/bootstrap-recovery/verify-bundle.py",
"sha256": "885b05a2011e711228e42e316bda70bbe0d97d481383d61aa8469d0f10452370",
"classification": "diagnostic; structural observations do not prove recovery or revoke credentials"
},
{
"path": "tools/security-bootstrap-console/security_bootstrap_console.py",
"sha256": "e9419ab1de0725b12b06fc29098c49584cb8acf47ec0ca09d552915f964a2c48",
"classification": "owner-scoped adapter/caller; source presence supplies no live approval or acceptance"
},
{
"path": "tools/sync-user-engine-delivery-secret.sh",
"sha256": "7c0fb6b066e4ad8b103e75551beecd5de65a4bbb22570d95b42e5da6395e2dfc",
"classification": "retired entry point; refuses before credential/cluster access"
},
{
"path": "tools/bootstrap-recovery/fetch-privacyidea-backup.py",
"sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f",
"classification": "bounded off-host transfer / isolated database drill; actual recovery acceptance requires operator execution"
},
{
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
"classification": "bounded off-host transfer / isolated database drill; actual recovery acceptance requires operator execution"
},
{
"path": "tools/bootstrap-recovery/prepare-identity-coverage.py",
"sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1",
"classification": "missing identity backup coverage proposal and metadata-only preparation; no live capture/grant/delivery capability"
},
{
"path": "tools/bootstrap-recovery/identity-backup-profile.json",
"sha256": "176c1f2f9111c2c51503212deb162274609d5e53bfe03d7319e19e44c5e09500",
"classification": "missing identity backup coverage proposal and metadata-only preparation; no live capture/grant/delivery capability"
}
],
"source_inventory_complete_for_declared_scope": true,
"native_exercise_or_full_security_acceptance_claimed": false,
"remaining": "T02 legacy identity/credential disposition, provider enforcement and owner acceptance; T03 exact off-host asset/key lineage and native recovery. Historical exposure rotation stays founder-deferred."
}

View file

@ -0,0 +1,33 @@
{
"observed_at": "2026-10-15T22:08:27.470032+00:00",
"operator_reported_key_check_passed": true,
"private_key_received_by_agent": false,
"actual_ciphertext_decryption_verified": true,
"transfer_receipt": "privacyidea-transfer-r1.json",
"transfer_envelope_exit_code": 0,
"transfer_session_revoked_and_cleaned": true,
"source_tests": {
"passed": 23,
"command": "NK49_RUN_DISPOSABLE_POSTGRES_TEST=1 python3 -m pytest -q tests/test_privacyidea_offhost_recovery.py tests/test_storage_share_inventory.py tests/test_scaleway_backup_inventory.py",
"duration_seconds": 8.54,
"data": "synthetic database and fresh disposable age keys only",
"includes_native_postgres_restore_and_cleanup": true
},
"pending_human_command": null,
"native_identity_recovery_proven": false,
"sources": [
{
"path": "tools/bootstrap-recovery/check-privacyidea-database.py",
"sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4"
},
{
"path": "tools/bootstrap-recovery/fetch-privacyidea-backup.py",
"sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f"
},
{
"path": "tests/test_privacyidea_offhost_recovery.py",
"sha256": "ea977b0d3368fb14f6a7a252c24cc5cb9915022afa3dca61c80952e7489bd0e5"
}
],
"execution_receipt": "privacyidea-database-drill-r1.json"
}

View file

@ -0,0 +1,15 @@
{
"observed_at": "2026-10-15T22:24:49.717396+00:00",
"receipt": "privacyidea-database-drill-r1.json",
"receipt_sha256": "c72dc3ababcc3ef3d7485a434128daf71e0ee58a8118e633fb09fed516f4b980",
"source_and_transfer_binding_verified": true,
"retained_ciphertext_digest_rechecked": true,
"operator_reported_success": true,
"independent_custody_basis": "Operator reports independently held railiance-backup key; attended temporary-key drill decrypted the fetched ciphertext. Agent never received private key material.",
"docker_nk49_container_inventory_empty": true,
"full_identity_recovery_proven": false,
"operator_reports_no_known_full_identity_bundle": true,
"next_work_authorized": "Prepare missing backup coverage; no blanket production restore or credential rotation.",
"coverage_preparation": "identity-backup-preparation-r1.json",
"operator_coverage_decision_id": "ed20023f-0168-421d-be86-460391339876"
}

View file

@ -0,0 +1,106 @@
{
"schema": "netkingdom.identity-backup-preparation.v1",
"status": "prepared_not_admitted",
"observed_at": "2026-10-15T22:32:41.149409+00:00",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"profile_sha256": "176c1f2f9111c2c51503212deb162274609d5e53bfe03d7319e19e44c5e09500",
"source_sha256": "cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1",
"bindings": [
{
"namespace": "sso",
"deployment": "lldap",
"uid": "743a86bd-5cb0-4ca0-9c56-a3438f4746a8",
"generation": 7,
"pvc_bindings": [
{
"name": "lldap-data",
"uid": "0a06b65a-e7b3-42d3-916e-e74dbe5fc7da"
}
],
"secret_names_only": [
"lldap-secrets"
],
"configmap_names_only": []
},
{
"namespace": "sso",
"deployment": "authelia",
"uid": "dc40a925-2306-4a3b-89c0-e714187503c3",
"generation": 28,
"pvc_bindings": [
{
"name": "authelia-data",
"uid": "86826554-b014-4df4-8b28-866aa64f6413"
}
],
"secret_names_only": [
"authelia-secrets",
"authelia-smtp"
],
"configmap_names_only": [
"authelia-config"
]
},
{
"namespace": "sso",
"deployment": "keycape",
"uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
"generation": 65,
"pvc_bindings": [
{
"name": "keycape-authentication-policy",
"uid": "423f558f-8c11-4edf-956b-243559df4a8e"
}
],
"secret_names_only": [
"keycape-approval-engine-operator-client",
"keycape-config",
"keycape-factor-read",
"keycape-informed-decision-sitting-requester-client",
"keycape-rapp-qonto-client",
"keycape-secrets-engine-approval-client",
"keycape-secrets-engine-openbao-client",
"keycape-secrets-engine-requester-client"
],
"configmap_names_only": []
},
{
"namespace": "mfa",
"deployment": "privacyidea",
"uid": "58c7f96d-61cb-4dd4-bca2-54661c0ac375",
"generation": 11,
"pvc_bindings": [
{
"name": "privacyidea-data",
"uid": "65650fc1-02d8-4c0f-84fc-e0015c8f3ed9"
},
{
"name": "privacyidea-logs",
"uid": "39fa90d9-bc54-4a03-a0d6-ae85165e0cb9"
}
],
"secret_names_only": [
"privacyidea-config"
],
"configmap_names_only": [
"factor-recovery-01119187d90d7b76",
"keycape-onboarding-9465e5e0b98f7986",
"privacyidea-cfg"
]
}
],
"secret_values_read": false,
"backup_captured": false,
"uploaded": false,
"runtime_changed": false,
"credential_grant_created": false,
"owner_admission_required": [
"Railiance owner accepts exact read/capture executor, immutable helper images, PVC access and task-scoped caller attribution; no production stop/restart inferred",
"NetKingdom/service owners accept full asset manifest, original key preservation, consistency interval, bounded recovery environment and native acceptance",
"Platform/reef-storage admits both new archive prefixes, immutable create/readback, scoped publisher custody and current quota/headroom; current application archive profiles do not include net-kingdom",
"Resolve every referenced credential through encrypted recovery or an independent owner reissue path; no undocumented grant propagation",
"Implement and test capture/sealing/transfer adapters against this new profile before live execution; existing bootstrap tar checker is insufficient",
"Retrieve by immutable object/version from each admitted provider; digest check, attended independent-key decryption, native service recovery, failed-key case and verified cleanup",
"Only then enable recurring capture/alerts and measured retention; preserve T02 legacy provider-credential and deferred-rotation boundaries"
]
}

View file

@ -0,0 +1,28 @@
{
"observed_at": "2026-10-15T20:55:09.988184+00:00",
"scope": "deployment,statefulset,daemonset,cronjob,pod in sso,mfa,user-engine; not all estate clients or issued provider credentials",
"resources_scanned": 34,
"references": [
{
"namespace": "user-engine",
"kind": "Deployment",
"name": "user-engine",
"uid": "e1c4274a-4b6d-4ea2-b619-e09630b745b0",
"secret_names": [
"user-engine-delivery"
]
},
{
"namespace": "user-engine",
"kind": "Pod",
"name": "user-engine-6c894f6b76-bcvpf",
"uid": "ebe2bab8-67b2-4f8d-afea-74b0cdc9d829",
"secret_names": [
"user-engine-delivery"
]
}
],
"secret_values_exported": false,
"provider_revocation_proven": false,
"legacy_tokens_may_have_other_consumers": true
}

View file

@ -0,0 +1,149 @@
{
"observed_at": "2026-10-15T20:50:28.605750+00:00",
"operation": "read-only Secret metadata output",
"secret_values_exported": false,
"rows": [
{
"namespace": "sso",
"name": "authelia-secrets",
"uid": "2e6c32d4-4178-4c1e-9813-14362d330eaf",
"created_at": "2026-03-25T00:29:22Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "authelia-smtp",
"uid": "5d34559e-8378-4065-b10b-f1ea046b73d5",
"created_at": "2026-10-06T18:30:54Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "identity-provisioner-token",
"uid": "f8449bb9-0c87-46a9-a012-2c2fce9d9b6a",
"created_at": "2026-07-27T23:02:12Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-approval-engine-operator-client",
"uid": "599a61a7-8244-4618-8c44-6473195bbe4e",
"created_at": "2026-09-09T00:09:47Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-config",
"uid": "2e94519d-1550-41c7-9701-2efe47fe1fd3",
"created_at": "2026-03-25T00:36:59Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-factor-read",
"uid": "ee07b0d9-bccf-464a-9d1b-e38d8d577985",
"created_at": "2026-09-13T14:15:49Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-informed-decision-sitting-requester-client",
"uid": "361d439b-3e11-43b4-842f-cf2028acd4a4",
"created_at": "2026-09-15T18:28:30Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-pi-token",
"uid": "1d459ab6-8b42-454d-9220-0fa2ffe4e819",
"created_at": "2026-03-25T00:51:32Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-rapp-qonto-client",
"uid": "d2a06c1b-8836-4ce4-9e3a-feb3604fadae",
"created_at": "2026-07-27T18:08:12Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-secrets-engine-approval-client",
"uid": "eaa28bdf-04af-4e4e-a1fc-fe395c7689a7",
"created_at": "2026-09-09T00:09:47Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-secrets-engine-openbao-client",
"uid": "c5986225-bbcc-40c8-b55d-79c62648a46f",
"created_at": "2026-10-07T00:10:55Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "keycape-secrets-engine-requester-client",
"uid": "8a279143-5553-439a-a529-1fc928010062",
"created_at": "2026-09-14T00:23:36Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "lldap-secrets",
"uid": "c6a9e6be-5bb5-47e6-9faa-06b8d72afec3",
"created_at": "2026-03-25T00:29:16Z",
"type": "Opaque"
},
{
"namespace": "sso",
"name": "lldap-tls",
"uid": "1ab2442f-4c57-450c-aadd-dc4b4e3eb938",
"created_at": "2026-03-25T00:44:05Z",
"type": "kubernetes.io/tls"
},
{
"namespace": "mfa",
"name": "privacyidea-auditkeys",
"uid": "6f0b191f-4e52-4122-9df7-31f791d124f8",
"created_at": "2026-03-25T00:49:48Z",
"type": "Opaque"
},
{
"namespace": "mfa",
"name": "privacyidea-config",
"uid": "3cd352c9-4f8e-4954-8181-d80fc9f62e0a",
"created_at": "2026-03-25T00:29:26Z",
"type": "Opaque"
},
{
"namespace": "mfa",
"name": "privacyidea-enckey",
"uid": "b3ee83fe-cacc-4ab3-a110-baac56328e94",
"created_at": "2026-03-25T00:49:46Z",
"type": "Opaque"
},
{
"namespace": "mfa",
"name": "privacyidea-trigger-admin",
"uid": "d1c643be-672d-4d13-8e46-3a58cb37ae39",
"created_at": "2026-03-25T00:50:53Z",
"type": "Opaque"
},
{
"namespace": "user-engine",
"name": "identity-provisioner-client",
"uid": "3ac5147a-df73-42b5-b596-c467c0571d23",
"created_at": "2026-07-27T23:02:12Z",
"type": "Opaque"
},
{
"namespace": "user-engine",
"name": "user-engine-delivery",
"uid": "2560f99d-621c-4f35-9472-c64ec3f6627e",
"created_at": "2026-08-13T13:02:50Z",
"type": "Opaque"
}
],
"revocation_proven": false,
"deletion_performed": false
}

View file

@ -0,0 +1,234 @@
{
"observed_at": "2026-10-15T20:45:27.835371+00:00",
"cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"read_only": true,
"deployments": [
{
"namespace": "sso",
"name": "lldap",
"uid": "743a86bd-5cb0-4ca0-9c56-a3438f4746a8",
"generation": 7,
"observed_generation": 7,
"ready_replicas": 1,
"containers": [
{
"name": "lldap",
"image": "lldap/lldap:stable",
"secret_key_refs": [
{
"env": "LLDAP_JWT_SECRET",
"secret": "lldap-secrets",
"key": "LLDAP_JWT_SECRET"
},
{
"env": "LLDAP_LDAP_USER_PASS",
"secret": "lldap-secrets",
"key": "LLDAP_LDAP_USER_PASS"
}
]
}
],
"volume_refs": [
{
"volume": "data",
"pvc": "lldap-data"
}
]
},
{
"namespace": "sso",
"name": "authelia",
"uid": "dc40a925-2306-4a3b-89c0-e714187503c3",
"generation": 28,
"observed_generation": 28,
"ready_replicas": 1,
"containers": [
{
"name": "authelia",
"image": "docker.io/authelia/authelia@sha256:bd97cff4fcbf715b5ff1f9ae286afbe6033afce385302520b0368122d43a6f54",
"secret_key_refs": []
}
],
"volume_refs": [
{
"volume": "smtp",
"secret": "authelia-smtp"
},
{
"volume": "secrets",
"secret": "authelia-secrets"
},
{
"volume": "data",
"pvc": "authelia-data"
}
]
},
{
"namespace": "sso",
"name": "keycape",
"uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
"generation": 65,
"observed_generation": 65,
"ready_replicas": 1,
"containers": [
{
"name": "keycape",
"image": "forgejo.coulomb.social/coulomb/key-cape@sha256:cdec9dd7c515f7e0a6169fc3fa560ee2cc47bf40d899755dec808c362f5ce010",
"secret_key_refs": [
{
"env": "KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET",
"secret": "keycape-secrets-engine-openbao-client",
"key": "client-secret"
},
{
"env": "KEYCAPE_RAPP_QONTO_CLIENT_SECRET",
"secret": "keycape-rapp-qonto-client",
"key": "client-secret"
},
{
"env": "KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET",
"secret": "keycape-secrets-engine-approval-client",
"key": "client-secret"
},
{
"env": "KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET",
"secret": "keycape-approval-engine-operator-client",
"key": "client-secret"
},
{
"env": "KEYCAPE_SECRETS_ENGINE_REQUESTER_CLIENT_SECRET",
"secret": "keycape-secrets-engine-requester-client",
"key": "client-secret"
},
{
"env": "KEYCAPE_INFORMED_DECISION_SITTING_REQUESTER_CLIENT_SECRET",
"secret": "keycape-informed-decision-sitting-requester-client",
"key": "client-secret"
}
]
}
],
"volume_refs": [
{
"volume": "authentication-policy",
"pvc": "keycape-authentication-policy"
},
{
"volume": "factor-token",
"secret": "keycape-factor-read"
},
{
"volume": "config-secret",
"secret": "keycape-config"
}
]
},
{
"namespace": "mfa",
"name": "privacyidea",
"uid": "58c7f96d-61cb-4dd4-bca2-54661c0ac375",
"generation": 11,
"observed_generation": 11,
"ready_replicas": 1,
"containers": [
{
"name": "privacyidea",
"image": "ghcr.io/gpappsoft/privacyidea-docker:3.12.2",
"secret_key_refs": []
},
{
"name": "factor-recovery",
"image": "ghcr.io/gpappsoft/privacyidea-docker@sha256:af7841adad262f129e0c1d4f553af13f21cb2f4dc713533f316cfe43ed0b4473",
"secret_key_refs": []
}
],
"volume_refs": [
{
"volume": "data",
"pvc": "privacyidea-data"
},
{
"volume": "logs",
"pvc": "privacyidea-logs"
}
]
},
{
"namespace": "sso",
"name": "identity-provisioner",
"uid": "13d2fadb-6929-4fa9-b2ba-635ab8572f54",
"generation": 18,
"observed_generation": 18,
"ready_replicas": 1,
"containers": [
{
"name": "provisioner",
"image": "forgejo.coulomb.social/coulomb/identity-provisioner@sha256:bafb48604d60f8bb98f58d27019df21d17966efcb742cfc11a6a5ab87f3f9d1f",
"secret_key_refs": [
{
"env": "LLDAP_ADMIN_PASSWORD",
"secret": "lldap-secrets",
"key": "LLDAP_LDAP_USER_PASS"
},
{
"env": "PROVISIONER_SERVICE_TOKEN",
"secret": "identity-provisioner-token",
"key": "token"
}
]
}
],
"volume_refs": []
},
{
"namespace": "user-engine",
"name": "user-engine",
"uid": "e1c4274a-4b6d-4ea2-b619-e09630b745b0",
"generation": 76,
"observed_generation": 76,
"ready_replicas": 1,
"containers": [
{
"name": "portal",
"image": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
"secret_key_refs": [
{
"env": "USER_ENGINE_DATABASE_URL",
"secret": "user-engine-pg-app",
"key": "uri"
},
{
"env": "USER_ENGINE_PROXY_SECRET",
"secret": "user-engine-runtime",
"key": "proxy-secret"
},
{
"env": "USER_ENGINE_EVENT_TOKEN",
"secret": "user-engine-delivery",
"key": "event-token"
},
{
"env": "USER_ENGINE_MAIL_TOKEN",
"secret": "user-engine-scoped-mail",
"key": "token"
},
{
"env": "USER_ENGINE_REGISTRATION_VERIFICATION_TOKEN",
"secret": "user-engine-scoped-mail",
"key": "token"
},
{
"env": "USER_ENGINE_PROVISIONING_TOKEN",
"secret": "identity-provisioner-client",
"key": "token"
}
]
}
],
"volume_refs": []
}
],
"secret_resources_read": false,
"deployment_literal_env_values_exported": false
}

View file

@ -0,0 +1,45 @@
{
"observed_at": "2026-10-15T22:08:27.470032+00:00",
"operator_reports_holding_railiance_backup_age_key": true,
"private_key_received_or_read": false,
"candidate_key_match_verified": true,
"storage_share": {
"inventory": "storage-share-inventory-r1.json",
"status": "metadata_inventory_complete",
"attended_envelope_exit_code": 0,
"session_self_revocation_and_cleanup_confirmed": true,
"selected_candidate": "r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
"candidate_bytes": 89129309,
"candidate_contents_downloaded": true,
"bootstrap_identity_file_bundle_found_in_inspected_scope": false,
"transfer_receipt": "privacyidea-transfer-r1.json",
"attended_transfer_envelope_exit_code": 0,
"transfer_session_self_revocation_and_cleanup_confirmed": true,
"candidate_decryption_and_database_restore_verified": true,
"database_drill_receipt": "privacyidea-database-drill-r1.json"
},
"scaleway": {
"inventory": "scaleway-inventory-r1.json",
"status": "metadata_inventory_complete",
"bucket": "railiance-platform-pg-backup",
"scope": "platform-pg/ and at most three child levels; WAL/base object trees and other buckets/prefixes not enumerated",
"bootstrap_identity_file_bundle_found_in_inspected_scope": false,
"bootstrap_api_key_used": false
},
"key_check": {
"source": "railiance-platform/lib/railiance-backup-common.sh",
"source_sha256": "09f349ae815339732db505d6896fb1ca384be7c66b40574750a3ba30d4d922e0",
"declared_default_public_recipient": "age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz",
"operator_hidden_terminal_check_requested": true,
"scope": "A successful key match checks the declared public recipient, not actual ciphertext decryption or whole-stack recovery.",
"operator_reports_check_passed": true,
"operator_reports_temporary_key_removed": true
},
"source_tests": {
"passed": 13,
"command": "python3 -m pytest -q tests/test_scaleway_backup_inventory.py tests/test_storage_share_inventory.py"
},
"native_recovery_proven": false,
"production_mutations_performed": false,
"private_key_received_or_read_scope": "Agent custody: false; attended human recovery child used the temporary explicit key."
}

View file

@ -0,0 +1,28 @@
{
"schema": "netkingdom.privacyidea-database-drill.v1",
"status": "isolated_database_drill_passed",
"started_at": "2026-10-15T22:18:55.858354+00:00",
"source_sha256": "90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
"production_contacted": false,
"secret_values_exported": false,
"native_identity_recovery_proven": false,
"token_decryption_proven": false,
"audit_signature_continuity_proven": false,
"full_identity_file_coverage_proven": false,
"container_cleanup_verified": true,
"temporary_plaintext_cleanup_verified": true,
"ciphertext_sha256": "cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead",
"registered_recipient_match": true,
"wrong_key_refused": true,
"offhost_ciphertext_decryption_verified": true,
"image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
"pg_restore_version": "pg_restore (PostgreSQL) 16.14 (Debian 16.14-1.pgdg13+1)",
"aggregate_row_counts": {
"token": 5,
"resolver": 2,
"realm": 2
},
"database_restore_and_readback_passed": true,
"finished_at": "2026-10-15T22:19:05.279736+00:00"
}

View file

@ -0,0 +1,19 @@
{
"schema": "netkingdom.privacyidea-offhost-transfer.v1",
"status": "ciphertext_fetched",
"started_at": "2026-10-15T22:07:08.615448+00:00",
"provider": "Storage Share",
"object": "r01-net-kingdom-pg/net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
"age_keys_read": false,
"plaintext_created": false,
"production_writes": false,
"source_sha256": "233160e1206643a5cf7ad05141d733f0a75aff651190e27f77515a124ff0a59f",
"owner_accessor_sha256": "673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5",
"caller_auth_path": "auth/netkingdom/oidc/callback",
"caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b",
"ciphertext_bytes": 89129309,
"ciphertext_sha256": "cc81da442755928171d2c51bce93eda7de4392a0a1aa9d0ffb83a25ba34e5ead",
"object_unchanged_during_transfer": true,
"ciphertext_path": "/tmp/nk49-offhost-j9dzl0ge/privacyidea.dump.age",
"finished_at": "2026-10-15T22:07:16.363645+00:00"
}

View file

@ -0,0 +1,48 @@
{
"workplan": "NK-WP-0049",
"observed_at": "2026-10-15T22:24:49.717396+00:00",
"status": "offhost_privacyidea_database_recovered_full_identity_coverage_open",
"backup_destinations_operator_reported": [
"Scaleway",
"Storage Share"
],
"exact_offhost_artifact_confirmed": true,
"independently_held_matching_key_confirmed": true,
"recovery_key_or_backup_contents_retrieved": true,
"legacy_token_secret_present": true,
"legacy_trigger_secret_present": true,
"legacy_provider_revocation_proven": false,
"native_recovery_proven": false,
"deferred_exposure_rotation_reopened": false,
"caller_inventory": "caller-source-inventory.json",
"verification": "source-verification.json",
"ceremony": "../../bootstrap-recovery-ceremony.md",
"outstanding": [
"Prepare and admit missing encrypted identity data/key/config backup coverage; operator confirms no known full identity bundle",
"Retrieve new off-host bundle and prove original token decryption, audit continuity, directory and issuer recovery with safe negative cases and cleanup",
"T02 current caller/provider source admission and exact legacy provider credential disposition; retain existing deferred-rotation boundary"
],
"operator_reports_holding_railiance_backup_age_key": true,
"operator_report_recorded_at": "2026-10-15T21:52:22.549430+00:00",
"key_material_received": false,
"key_matches_selected_ciphertext_verified": true,
"offsite_discovery": "offsite-discovery-summary.json",
"privacyidea_database_ciphertext_identified": true,
"required_full_asset_coverage_confirmed": false,
"operator_reports_recipient_check_passed": true,
"operator_reports_temporary_key_cleanup": true,
"private_key_received_by_agent": false,
"ciphertext_fetched": true,
"transfer_receipt": "privacyidea-transfer-r1.json",
"prepared_database_drill": "../../bootstrap-recovery-ceremony.md",
"age_private_key_retrieved_by_agent": false,
"offhost_database_restore_proven": true,
"database_drill_receipt": "privacyidea-database-drill-r1.json",
"database_drill_review": "database-drill-review.json",
"operator_reports_no_known_full_identity_bundle": true,
"identity_backup_profile": "../../../tools/bootstrap-recovery/identity-backup-profile.json",
"identity_coverage_preparation": "identity-backup-preparation-r1.json",
"coverage_preparation_status": "prepared_not_admitted",
"coverage_preparation_tests_passed": 14,
"coverage_decision_id": "ed20023f-0168-421d-be86-460391339876"
}

View file

@ -0,0 +1,93 @@
{
"schema": "netkingdom.scaleway-backup-inventory.v1",
"status": "metadata_inventory_complete",
"started_at": "2026-10-15T21:54:28.925951+00:00",
"read_only": true,
"provider": "Scaleway",
"bucket": "railiance-platform-pg-backup",
"prefix": "platform-pg/",
"source_sha256": "a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
"bootstrap_api_key_used": false,
"age_keys_read": false,
"object_contents_downloaded": false,
"secret_values_exported": false,
"scope": "Bounded prefix metadata, at most three child levels; WAL/base trees not enumerated; other buckets/prefixes unassessed",
"prefixes": {
"platform-pg/": {
"directories": [
"platform-pg/application-archives/",
"platform-pg/apps-pg/",
"platform-pg/forgejo-db/",
"platform-pg/platform-pg-2/",
"platform-pg/platform-pg/"
],
"files": []
},
"platform-pg/application-archives/": {
"directories": [
"platform-pg/application-archives/email-connect/"
],
"files": []
},
"platform-pg/apps-pg/": {
"directories": [
"platform-pg/apps-pg/apps-pg/"
],
"files": []
},
"platform-pg/forgejo-db/": {
"directories": [
"platform-pg/forgejo-db/forgejo-db/"
],
"files": []
},
"platform-pg/platform-pg-2/": {
"directories": [
"platform-pg/platform-pg-2/platform-pg-2/"
],
"files": []
},
"platform-pg/platform-pg/": {
"directories": [
"platform-pg/platform-pg/base/",
"platform-pg/platform-pg/wals/"
],
"files": []
},
"platform-pg/application-archives/email-connect/": {
"directories": [
"platform-pg/application-archives/email-connect/2026/"
],
"files": []
},
"platform-pg/apps-pg/apps-pg/": {
"directories": [
"platform-pg/apps-pg/apps-pg/base/",
"platform-pg/apps-pg/apps-pg/wals/"
],
"files": []
},
"platform-pg/forgejo-db/forgejo-db/": {
"directories": [
"platform-pg/forgejo-db/forgejo-db/base/",
"platform-pg/forgejo-db/forgejo-db/wals/"
],
"files": []
},
"platform-pg/platform-pg-2/platform-pg-2/": {
"directories": [
"platform-pg/platform-pg-2/platform-pg-2/base/",
"platform-pg/platform-pg-2/platform-pg-2/wals/"
],
"files": []
},
"platform-pg/application-archives/email-connect/2026/": {
"directories": [
"platform-pg/application-archives/email-connect/2026/10/"
],
"files": []
}
},
"runtime_delivery_uid": "30c4f6fc-8eb7-4cdc-b692-0f74c4a5b3ce",
"finished_at": "2026-10-15T21:54:39.109262+00:00"
}

View file

@ -0,0 +1,23 @@
{
"broad": {
"command": "python3 -m pytest -q tests tools/security-bootstrap-console/tests sso-mfa/k8s/keycape sso-mfa/k8s/lldap/test_identity_provisioner_reconcile.py",
"passed": 723,
"skipped": 65,
"warnings": 2,
"duration_seconds": 171.05,
"scope_note": "Skipped checks are not acceptance evidence; native/estate recovery is not established."
},
"final_focused": {
"command": "python3 -m pytest -q tests/test_bootstrap_secret_output.py tests/test_custody_entrypoints.py tests/test_retirement_and_bundle_boundaries.py tests/test_openbao_lab_boundary.py tests/test_bootstrap_recovery_transport.py",
"passed": 57,
"duration_seconds": 7.45
},
"reproduced_baseline_failures": 6,
"syntax_checks": {
"modified_shell_files": 13,
"modified_python_files": 4,
"passed": true
},
"production_mutations_performed": false,
"production_recovery_claimed": false
}

View file

@ -0,0 +1,562 @@
{
"schema": "netkingdom.storage-share-inventory.v1",
"status": "metadata_inventory_complete",
"started_at": "2026-10-15T21:52:20.452797+00:00",
"read_only": true,
"age_keys_read": false,
"backup_contents_downloaded": false,
"secret_values_exported": false,
"source_sha256": "fe56454e5ca740395e6d6a2a0f8185abdac1d8ca6d0eedb31273db409dee4954",
"owner_sources": [
{
"path": "/home/worsch/railiance-platform/scripts/state_hub_preflight_lane.py",
"sha256": "8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5"
},
{
"path": "/home/worsch/railiance-platform/scripts/migrate_nextcloud_backup_account.py",
"sha256": "d3285f403f3c947f75c3288d989152e59ab9a8f5542bcf901bd0a06b0f3e050c"
}
],
"root": [
{
"name": "core-apps-pg",
"directory": true,
"bytes": null
},
{
"name": "core-gitea-db",
"directory": true,
"bytes": null
},
{
"name": "core-net-kingdom-pg",
"directory": true,
"bytes": null
},
{
"name": "core-state-hub-db",
"directory": true,
"bytes": null
},
{
"name": "forgejo",
"directory": true,
"bytes": null
},
{
"name": "openbao",
"directory": true,
"bytes": null
},
{
"name": "r01-forgejo-db",
"directory": true,
"bytes": null
},
{
"name": "r01-net-kingdom-pg",
"directory": true,
"bytes": null
},
{
"name": "r01-platform-pg",
"directory": true,
"bytes": null
},
{
"name": "r01-state-hub-db",
"directory": true,
"bytes": null
},
{
"name": "r01-user-engine-pg",
"directory": true,
"bytes": null
}
],
"folders": {
"core-apps-pg": [],
"core-gitea-db": [],
"core-net-kingdom-pg": [],
"core-state-hub-db": [],
"openbao": [
{
"name": "openbao-raft-snapshot-RPF-WP-0015-T03-REBOOT-20261014-01-20261014T181053Z.snap.age",
"directory": false,
"bytes": 375618
}
],
"r01-forgejo-db": [
{
"name": "forgejo-db-forgejo-20260906T023000Z.dump.age",
"directory": false,
"bytes": 7515593
},
{
"name": "forgejo-db-forgejo-20260907T023000Z.dump.age",
"directory": false,
"bytes": 7320543
},
{
"name": "forgejo-db-forgejo-20260908T023000Z.dump.age",
"directory": false,
"bytes": 7413040
},
{
"name": "forgejo-db-forgejo-20260909T023000Z.dump.age",
"directory": false,
"bytes": 7663338
},
{
"name": "forgejo-db-forgejo-20260910T023000Z.dump.age",
"directory": false,
"bytes": 8079045
},
{
"name": "forgejo-db-forgejo-20260911T023000Z.dump.age",
"directory": false,
"bytes": 8404583
},
{
"name": "forgejo-db-forgejo-20260912T023000Z.dump.age",
"directory": false,
"bytes": 8631188
},
{
"name": "forgejo-db-forgejo-20260913T023000Z.dump.age",
"directory": false,
"bytes": 8472109
},
{
"name": "forgejo-db-forgejo-20260914T023000Z.dump.age",
"directory": false,
"bytes": 8523610
},
{
"name": "forgejo-db-forgejo-20261005T023000Z.dump.age",
"directory": false,
"bytes": 11745885
},
{
"name": "forgejo-db-forgejo-20261006T023000Z.dump.age",
"directory": false,
"bytes": 11481436
},
{
"name": "forgejo-db-forgejo-20261007T023000Z.dump.age",
"directory": false,
"bytes": 11693800
},
{
"name": "forgejo-db-forgejo-20261008T023000Z.dump.age",
"directory": false,
"bytes": 11784032
},
{
"name": "forgejo-db-forgejo-20261009T023003Z.dump.age",
"directory": false,
"bytes": 12210534
},
{
"name": "forgejo-db-forgejo-20261010T023000Z.dump.age",
"directory": false,
"bytes": 12378726
},
{
"name": "forgejo-db-forgejo-20261011T023000Z.dump.age",
"directory": false,
"bytes": 12369466
},
{
"name": "forgejo-db-forgejo-20261012T023000Z.dump.age",
"directory": false,
"bytes": 12701177
},
{
"name": "forgejo-db-forgejo-20261013T023000Z.dump.age",
"directory": false,
"bytes": 12761857
},
{
"name": "forgejo-db-forgejo-20261014T023000Z.dump.age",
"directory": false,
"bytes": 13110850
},
{
"name": "forgejo-db-forgejo-20261015T023000Z.dump.age",
"directory": false,
"bytes": 13349242
},
{
"name": "forgejo-db-forgejo-weekly-20260906T023000Z.dump.age",
"directory": false,
"bytes": 7515593
},
{
"name": "forgejo-db-forgejo-weekly-20260913T023000Z.dump.age",
"directory": false,
"bytes": 8472109
},
{
"name": "forgejo-db-forgejo-weekly-20261011T023000Z.dump.age",
"directory": false,
"bytes": 12369466
}
],
"r01-net-kingdom-pg": [
{
"name": "net-kingdom-pg-interhub-20260906T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260907T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260908T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260909T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260910T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260911T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260912T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260913T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20260914T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261005T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261006T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261007T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261008T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261009T023003Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261010T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261011T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261012T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261013T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261014T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-20261015T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-weekly-20260906T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-weekly-20260913T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-interhub-weekly-20261011T023000Z.dump.age",
"directory": false,
"bytes": 215541
},
{
"name": "net-kingdom-pg-privacyidea_db-20260906T023000Z.dump.age",
"directory": false,
"bytes": 271941
},
{
"name": "net-kingdom-pg-privacyidea_db-20260907T023000Z.dump.age",
"directory": false,
"bytes": 272271
},
{
"name": "net-kingdom-pg-privacyidea_db-20260908T023000Z.dump.age",
"directory": false,
"bytes": 272599
},
{
"name": "net-kingdom-pg-privacyidea_db-20260909T023000Z.dump.age",
"directory": false,
"bytes": 274660
},
{
"name": "net-kingdom-pg-privacyidea_db-20260910T023000Z.dump.age",
"directory": false,
"bytes": 274660
},
{
"name": "net-kingdom-pg-privacyidea_db-20260911T023000Z.dump.age",
"directory": false,
"bytes": 274996
},
{
"name": "net-kingdom-pg-privacyidea_db-20260912T023000Z.dump.age",
"directory": false,
"bytes": 276353
},
{
"name": "net-kingdom-pg-privacyidea_db-20260913T023000Z.dump.age",
"directory": false,
"bytes": 277468
},
{
"name": "net-kingdom-pg-privacyidea_db-20260914T023000Z.dump.age",
"directory": false,
"bytes": 1008342
},
{
"name": "net-kingdom-pg-privacyidea_db-20261005T023000Z.dump.age",
"directory": false,
"bytes": 58987550
},
{
"name": "net-kingdom-pg-privacyidea_db-20261006T023000Z.dump.age",
"directory": false,
"bytes": 61979707
},
{
"name": "net-kingdom-pg-privacyidea_db-20261007T023000Z.dump.age",
"directory": false,
"bytes": 64976206
},
{
"name": "net-kingdom-pg-privacyidea_db-20261008T023000Z.dump.age",
"directory": false,
"bytes": 68001818
},
{
"name": "net-kingdom-pg-privacyidea_db-20261009T023003Z.dump.age",
"directory": false,
"bytes": 71016444
},
{
"name": "net-kingdom-pg-privacyidea_db-20261010T023000Z.dump.age",
"directory": false,
"bytes": 74006618
},
{
"name": "net-kingdom-pg-privacyidea_db-20261011T023000Z.dump.age",
"directory": false,
"bytes": 77020445
},
{
"name": "net-kingdom-pg-privacyidea_db-20261012T023000Z.dump.age",
"directory": false,
"bytes": 80037505
},
{
"name": "net-kingdom-pg-privacyidea_db-20261013T023000Z.dump.age",
"directory": false,
"bytes": 83066939
},
{
"name": "net-kingdom-pg-privacyidea_db-20261014T023000Z.dump.age",
"directory": false,
"bytes": 86103429
},
{
"name": "net-kingdom-pg-privacyidea_db-20261015T023000Z.dump.age",
"directory": false,
"bytes": 89129309
},
{
"name": "net-kingdom-pg-privacyidea_db-weekly-20260906T023000Z.dump.age",
"directory": false,
"bytes": 271941
},
{
"name": "net-kingdom-pg-privacyidea_db-weekly-20260913T023000Z.dump.age",
"directory": false,
"bytes": 277468
},
{
"name": "net-kingdom-pg-privacyidea_db-weekly-20261011T023000Z.dump.age",
"directory": false,
"bytes": 77020445
}
],
"r01-platform-pg": [],
"r01-state-hub-db": [
{
"name": "state-hub-db-state_hub-20260906T023000Z.dump.age",
"directory": false,
"bytes": 6924847
},
{
"name": "state-hub-db-state_hub-20260907T023000Z.dump.age",
"directory": false,
"bytes": 7141118
},
{
"name": "state-hub-db-state_hub-20260908T023000Z.dump.age",
"directory": false,
"bytes": 7180575
},
{
"name": "state-hub-db-state_hub-20260909T023000Z.dump.age",
"directory": false,
"bytes": 7390311
},
{
"name": "state-hub-db-state_hub-20260910T023000Z.dump.age",
"directory": false,
"bytes": 7542258
},
{
"name": "state-hub-db-state_hub-20260911T023000Z.dump.age",
"directory": false,
"bytes": 7687798
},
{
"name": "state-hub-db-state_hub-20260912T023000Z.dump.age",
"directory": false,
"bytes": 7748393
},
{
"name": "state-hub-db-state_hub-20260913T023000Z.dump.age",
"directory": false,
"bytes": 7765176
},
{
"name": "state-hub-db-state_hub-20260914T023000Z.dump.age",
"directory": false,
"bytes": 7839953
},
{
"name": "state-hub-db-state_hub-20261005T023000Z.dump.age",
"directory": false,
"bytes": 9407746
},
{
"name": "state-hub-db-state_hub-20261006T023000Z.dump.age",
"directory": false,
"bytes": 9464763
},
{
"name": "state-hub-db-state_hub-20261007T023000Z.dump.age",
"directory": false,
"bytes": 9549640
},
{
"name": "state-hub-db-state_hub-20261008T023000Z.dump.age",
"directory": false,
"bytes": 9602097
},
{
"name": "state-hub-db-state_hub-20261009T023003Z.dump.age",
"directory": false,
"bytes": 9693941
},
{
"name": "state-hub-db-state_hub-20261010T023000Z.dump.age",
"directory": false,
"bytes": 9766236
},
{
"name": "state-hub-db-state_hub-20261011T023000Z.dump.age",
"directory": false,
"bytes": 9864346
},
{
"name": "state-hub-db-state_hub-20261012T023000Z.dump.age",
"directory": false,
"bytes": 9973489
},
{
"name": "state-hub-db-state_hub-20261013T023000Z.dump.age",
"directory": false,
"bytes": 10036580
},
{
"name": "state-hub-db-state_hub-20261014T023000Z.dump.age",
"directory": false,
"bytes": 10265536
},
{
"name": "state-hub-db-state_hub-20261015T023000Z.dump.age",
"directory": false,
"bytes": 10388491
},
{
"name": "state-hub-db-state_hub-weekly-20260906T023000Z.dump.age",
"directory": false,
"bytes": 6924847
},
{
"name": "state-hub-db-state_hub-weekly-20260913T023000Z.dump.age",
"directory": false,
"bytes": 7765176
},
{
"name": "state-hub-db-state_hub-weekly-20261011T023000Z.dump.age",
"directory": false,
"bytes": 9864346
}
],
"r01-user-engine-pg": []
},
"caller_auth_path": "auth/netkingdom/oidc/callback",
"caller_entity_id": "4a1f663c-0621-0483-edd0-f852274f4b6b",
"scope": "Backup/railiance-backups root and bounded identity/database subfolders; other accounts/folders unassessed",
"finished_at": "2026-10-15T21:52:28.155972+00:00"
}

View file

@ -0,0 +1,22 @@
[
{
"op": "test",
"path": "/metadata/uid",
"value": "8b44c901-024f-4472-a822-2fd07aa2be5f"
},
{
"op": "test",
"path": "/metadata/resourceVersion",
"value": "75772235"
},
{
"op": "replace",
"path": "/spec/template/spec/containers/0/image",
"value": "forgejo.coulomb.social/coulomb/approval-engine@sha256:16e5d222d58e179f4836f649e8f8128321a8b644d29dc426b4d21a65ff97845d"
},
{
"op": "replace",
"path": "/spec/template/spec/initContainers/0/image",
"value": "forgejo.coulomb.social/coulomb/approval-engine@sha256:16e5d222d58e179f4836f649e8f8128321a8b644d29dc426b4d21a65ff97845d"
}
]

View file

@ -0,0 +1,30 @@
{
"at": "2026-10-15T18:54:13.416550+00:00",
"integrity": "ok",
"schema_version": 5,
"table_counts": {
"approvals": 54,
"entries": 47,
"outbox": 90
},
"sha256": "c338e008865c59d2b583a7236c531450dc4eaa0bf97681bc33b7d438b9867cf4",
"local_copy": "/tmp/nk-wp-0035-approval-backup/pre-frame.sqlite",
"local_mode": "0o600",
"persistent_path": "/data/nk-wp-0035-pre-frame-20261015.sqlite",
"owner_engine_isolated_backup_verify": {
"expected_schema_version": 5,
"foreign_key_violations": 0,
"integrity": [
"ok"
],
"ok": true,
"persistent": true,
"required_tables": {
"approvals": true,
"entries": true,
"outbox": true
},
"schema_current": true,
"schema_version": 5
}
}

View file

@ -0,0 +1,57 @@
{
"storage": {
"schema_version": 5,
"expected_schema_version": 5,
"schema_current": true,
"persistent": true,
"required_tables": {
"approvals": true,
"entries": true,
"outbox": true
},
"integrity": [
"ok"
],
"foreign_key_violations": 0,
"ok": true
},
"outbox": {
"total": 91,
"pending": 0,
"max_age_seconds": 0,
"attempts": 101,
"failed_pending": 0,
"counts": {
"issuance": 47,
"use": 11,
"supersession": 0,
"revocation": 0,
"heartbeat": 33
}
},
"table_counts": {
"approvals": 54,
"entries": 47,
"outbox": 91
},
"recent_heartbeat_rows": [
{
"created_at": "2026-10-15T18:54:55+00:00",
"drained_at": "2026-10-15T18:54:55+00:00",
"attempts": 1
},
{
"created_at": "2026-10-14T23:03:46+00:00",
"drained_at": "2026-10-14T23:03:46+00:00",
"attempts": 1
}
],
"readiness": 200,
"anonymous_refusal": 401,
"at": "2026-10-15T18:55:27.962077+00:00",
"pod_uid": "423398d8-9705-4ab5-8b2d-aaa4f590d3a1",
"image": "forgejo.coulomb.social/coulomb/approval-engine@sha256:251941a5cb2724b57cc32cff6b693b1ab0be695bee4f56f02d51961189c0fa49",
"heartbeat_seconds": 3600,
"frame_digest": "d15e092387f128ffdfd00b9815707a05ff77c5bb0ba4d9a428fbc37b8c440b67",
"ready": true
}

View file

@ -0,0 +1,29 @@
[
{
"op": "test",
"path": "/metadata/uid",
"value": "8b44c901-024f-4472-a822-2fd07aa2be5f"
},
{
"op": "test",
"path": "/metadata/resourceVersion",
"value": "75292270"
},
{
"op": "add",
"path": "/spec/template/metadata/annotations",
"value": {
"coordination.coulomb.social/frame-sha256": "d15e092387f128ffdfd00b9815707a05ff77c5bb0ba4d9a428fbc37b8c440b67"
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/args/-",
"value": "--heartbeat-seconds"
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/args/-",
"value": "3600"
}
]

View file

@ -0,0 +1,593 @@
{
"source_window": {
"source": "approval-engine",
"tenant": "tenant:platform",
"since": "2026-09-13T23:02:55+00:00",
"until": "2026-10-15T19:38:39.029733+00:00",
"bounds": "since-inclusive-until-exclusive",
"window_basis": "created_at",
"counts": {
"issuance": {
"committed": 47,
"drained": 47,
"pending": 0,
"acknowledged_in_window": 47
},
"use": {
"committed": 11,
"drained": 11,
"pending": 0,
"acknowledged_in_window": 11
},
"supersession": {
"committed": 0,
"drained": 0,
"pending": 0,
"acknowledged_in_window": 0
},
"revocation": {
"committed": 0,
"drained": 0,
"pending": 0,
"acknowledged_in_window": 0
},
"heartbeat": {
"committed": 33,
"drained": 33,
"pending": 0,
"acknowledged_in_window": 33
}
},
"means": "Source occurrence counts and local acknowledgments; receiver acceptance-time comparison requires drain-lag and boundary accounting. Agreement does not prove completeness."
},
"outbox_coordinates": [
{
"event_id": "b4f82b2f-c63e-4e56-8eaf-b7019feb0d11",
"class": "heartbeat",
"created_at": "2026-09-13T23:02:55+00:00",
"drained_at": "2026-09-13T23:02:56+00:00"
},
{
"event_id": "7f302456-dccb-4c65-b017-fab8fa3b0a29",
"class": "issuance",
"created_at": "2026-09-14T01:05:11+00:00",
"drained_at": "2026-09-14T01:05:13+00:00"
},
{
"event_id": "c6a4bfef-29db-447e-bc03-feca26b49367",
"class": "issuance",
"created_at": "2026-09-14T01:05:49+00:00",
"drained_at": "2026-09-14T01:05:53+00:00"
},
{
"event_id": "337dc3e2-c531-4e73-bbc1-e2fd680b03c5",
"class": "issuance",
"created_at": "2026-09-14T01:06:17+00:00",
"drained_at": "2026-09-14T01:06:18+00:00"
},
{
"event_id": "1db440ca-4e92-454c-befa-7fb2276a9b26",
"class": "heartbeat",
"created_at": "2026-09-14T23:02:59+00:00",
"drained_at": "2026-09-14T23:02:59+00:00"
},
{
"event_id": "ba93918f-cdbf-4e93-be65-b74b25bd296d",
"class": "heartbeat",
"created_at": "2026-09-15T23:03:01+00:00",
"drained_at": "2026-09-15T23:03:01+00:00"
},
{
"event_id": "ff2d2273-576a-445f-9fac-cc1ca03b09c7",
"class": "issuance",
"created_at": "2026-09-15T23:53:32+00:00",
"drained_at": "2026-09-15T23:53:36+00:00"
},
{
"event_id": "669bf0fb-8a30-4470-a810-c1c3be7a2efc",
"class": "issuance",
"created_at": "2026-09-15T23:56:08+00:00",
"drained_at": "2026-09-15T23:56:11+00:00"
},
{
"event_id": "fadbdb84-ec2e-4572-99a0-13fcb5ecc8a3",
"class": "issuance",
"created_at": "2026-09-15T23:56:41+00:00",
"drained_at": "2026-09-15T23:56:46+00:00"
},
{
"event_id": "4d2b4918-e23d-4e72-91f8-97d0ceb6be5c",
"class": "use",
"created_at": "2026-09-16T00:05:09+00:00",
"drained_at": "2026-09-16T00:05:11+00:00"
},
{
"event_id": "0fbf7112-8835-4967-be01-629ce99fbc1f",
"class": "use",
"created_at": "2026-09-16T00:05:11+00:00",
"drained_at": "2026-09-16T00:05:16+00:00"
},
{
"event_id": "ad6e96ab-2e8b-4ebd-b5ec-8a027477c931",
"class": "use",
"created_at": "2026-09-16T00:08:32+00:00",
"drained_at": "2026-09-16T00:08:36+00:00"
},
{
"event_id": "74f4019b-1dac-46f6-9126-a86f63075ecb",
"class": "heartbeat",
"created_at": "2026-09-16T23:03:03+00:00",
"drained_at": "2026-09-16T23:03:03+00:00"
},
{
"event_id": "b224ac95-1df7-462a-b7b9-fc0e49388319",
"class": "heartbeat",
"created_at": "2026-09-17T23:03:05+00:00",
"drained_at": "2026-09-17T23:03:05+00:00"
},
{
"event_id": "06044e25-0436-43f4-8552-4f7b49f867d3",
"class": "heartbeat",
"created_at": "2026-09-18T23:03:07+00:00",
"drained_at": "2026-09-18T23:03:07+00:00"
},
{
"event_id": "18f2b687-f3f7-40db-a056-3e91c39401be",
"class": "heartbeat",
"created_at": "2026-09-19T23:03:10+00:00",
"drained_at": "2026-09-19T23:03:10+00:00"
},
{
"event_id": "ef66421f-3cd8-457a-8f8f-c6f4310667c6",
"class": "heartbeat",
"created_at": "2026-09-20T23:03:12+00:00",
"drained_at": "2026-09-20T23:03:12+00:00"
},
{
"event_id": "75cbfc23-3f48-4dfd-99b1-5234fa327915",
"class": "issuance",
"created_at": "2026-09-21T19:51:29+00:00",
"drained_at": "2026-09-21T19:51:30+00:00"
},
{
"event_id": "dfb1220b-25e0-43c6-b74e-1575bf5188af",
"class": "heartbeat",
"created_at": "2026-09-21T23:03:12+00:00",
"drained_at": "2026-09-21T23:03:12+00:00"
},
{
"event_id": "2b125feb-7363-4187-a776-f3961f8e991e",
"class": "heartbeat",
"created_at": "2026-09-22T23:03:12+00:00",
"drained_at": "2026-09-22T23:03:12+00:00"
},
{
"event_id": "c0b97e60-3198-46b1-a482-d38030817768",
"class": "heartbeat",
"created_at": "2026-09-23T23:03:16+00:00",
"drained_at": "2026-09-23T23:03:16+00:00"
},
{
"event_id": "5e735adb-b9ca-457a-bbcf-aac1669384db",
"class": "heartbeat",
"created_at": "2026-09-24T23:03:20+00:00",
"drained_at": "2026-09-24T23:03:20+00:00"
},
{
"event_id": "4c8c19be-378b-49f3-9207-a4e8091044b1",
"class": "heartbeat",
"created_at": "2026-09-25T23:03:20+00:00",
"drained_at": "2026-09-25T23:03:20+00:00"
},
{
"event_id": "6bf120bb-ff63-46a8-b3cb-5486ace348a9",
"class": "heartbeat",
"created_at": "2026-09-26T23:03:24+00:00",
"drained_at": "2026-09-26T23:03:24+00:00"
},
{
"event_id": "7fc26b94-9fb8-4492-b2d8-3ee48942ff73",
"class": "issuance",
"created_at": "2026-09-27T18:05:35+00:00",
"drained_at": "2026-09-27T18:05:36+00:00"
},
{
"event_id": "8984622e-193c-42c5-812c-372e17d836f9",
"class": "issuance",
"created_at": "2026-09-27T18:22:57+00:00",
"drained_at": "2026-09-27T18:23:02+00:00"
},
{
"event_id": "11e42f81-84c2-4d5a-9743-ed598faea076",
"class": "issuance",
"created_at": "2026-09-27T19:23:59+00:00",
"drained_at": "2026-09-27T19:24:02+00:00"
},
{
"event_id": "c89e909f-f576-44ed-b107-69f64dc3d686",
"class": "issuance",
"created_at": "2026-09-27T19:24:15+00:00",
"drained_at": "2026-09-27T19:24:17+00:00"
},
{
"event_id": "c2677abf-e3ff-44c2-9818-86838c259f5f",
"class": "issuance",
"created_at": "2026-09-27T19:24:32+00:00",
"drained_at": "2026-09-27T19:24:32+00:00"
},
{
"event_id": "c9d418c8-1f74-4561-82d5-3510c2439172",
"class": "issuance",
"created_at": "2026-09-27T19:24:43+00:00",
"drained_at": "2026-09-27T19:24:47+00:00"
},
{
"event_id": "33254695-544c-4da6-a22d-c0dc360d1f89",
"class": "issuance",
"created_at": "2026-09-27T19:24:55+00:00",
"drained_at": "2026-09-27T19:24:57+00:00"
},
{
"event_id": "5ec40d6f-351f-4b02-ae2a-db73af6ff43c",
"class": "issuance",
"created_at": "2026-09-27T19:25:10+00:00",
"drained_at": "2026-09-27T19:25:12+00:00"
},
{
"event_id": "e81c0b4c-cc0f-4dc9-94e1-dbe0646505e7",
"class": "use",
"created_at": "2026-09-27T19:48:20+00:00",
"drained_at": "2026-09-27T19:48:23+00:00"
},
{
"event_id": "d35d0720-33bc-4d0d-8307-05b9b67b5cdb",
"class": "use",
"created_at": "2026-09-27T19:48:22+00:00",
"drained_at": "2026-09-27T19:48:23+00:00"
},
{
"event_id": "27044b2d-1aab-4239-bf38-59118dfd06bb",
"class": "use",
"created_at": "2026-09-27T19:48:25+00:00",
"drained_at": "2026-09-27T19:48:28+00:00"
},
{
"event_id": "c5c9220c-29d7-4aaa-bd93-3df355caf59c",
"class": "use",
"created_at": "2026-09-27T19:48:30+00:00",
"drained_at": "2026-09-27T19:48:33+00:00"
},
{
"event_id": "9a0c1fec-3105-4d6f-b062-991a8345d16d",
"class": "use",
"created_at": "2026-09-27T20:01:37+00:00",
"drained_at": "2026-09-27T20:01:38+00:00"
},
{
"event_id": "66b91546-36c0-4a25-82ca-4a52eb1eae21",
"class": "use",
"created_at": "2026-09-27T20:01:38+00:00",
"drained_at": "2026-09-27T20:01:38+00:00"
},
{
"event_id": "496e4119-3112-4818-93b8-658ad24c745f",
"class": "heartbeat",
"created_at": "2026-09-27T23:03:25+00:00",
"drained_at": "2026-09-27T23:03:25+00:00"
},
{
"event_id": "a65454b3-0db9-4cb7-9c7d-9c35d9e15e57",
"class": "heartbeat",
"created_at": "2026-09-28T23:03:27+00:00",
"drained_at": "2026-09-28T23:03:27+00:00"
},
{
"event_id": "79a37132-a514-4c95-ac6c-65d08b6967d0",
"class": "heartbeat",
"created_at": "2026-09-29T23:03:28+00:00",
"drained_at": "2026-09-29T23:03:28+00:00"
},
{
"event_id": "0c281084-cb88-44be-9666-5c3e6a59b035",
"class": "heartbeat",
"created_at": "2026-09-30T23:03:32+00:00",
"drained_at": "2026-09-30T23:03:32+00:00"
},
{
"event_id": "36224702-dedd-4bfa-a0a4-383e30f1f781",
"class": "heartbeat",
"created_at": "2026-10-01T23:03:33+00:00",
"drained_at": "2026-10-01T23:03:33+00:00"
},
{
"event_id": "c64733d3-f474-4266-a5ed-6be5cb3ac3a5",
"class": "heartbeat",
"created_at": "2026-10-02T23:03:33+00:00",
"drained_at": "2026-10-02T23:03:33+00:00"
},
{
"event_id": "bd40f557-3927-4fdf-984a-cf8af60f4b5e",
"class": "issuance",
"created_at": "2026-10-03T18:45:15+00:00",
"drained_at": "2026-10-03T18:45:17+00:00"
},
{
"event_id": "f2b199d5-2cca-47c9-ba27-389e79edcc59",
"class": "heartbeat",
"created_at": "2026-10-03T23:03:34+00:00",
"drained_at": "2026-10-03T23:03:34+00:00"
},
{
"event_id": "8589a736-f8e7-42b5-8b1d-6b0681fd7450",
"class": "issuance",
"created_at": "2026-10-04T00:16:08+00:00",
"drained_at": "2026-10-04T00:16:09+00:00"
},
{
"event_id": "02ff2545-0628-4dc3-8428-31246c0e5a98",
"class": "issuance",
"created_at": "2026-10-04T13:14:06+00:00",
"drained_at": "2026-10-04T13:14:51+00:00"
},
{
"event_id": "9511a221-f392-484f-8984-54b4a0be1ac8",
"class": "issuance",
"created_at": "2026-10-04T13:14:20+00:00",
"drained_at": "2026-10-04T13:14:51+00:00"
},
{
"event_id": "3942043a-4cfd-41d4-9c61-ef6510e12f97",
"class": "issuance",
"created_at": "2026-10-04T13:15:31+00:00",
"drained_at": "2026-10-04T13:15:36+00:00"
},
{
"event_id": "46d1d50c-ff4c-45d3-a23d-1ee33fdc10ad",
"class": "issuance",
"created_at": "2026-10-04T13:15:51+00:00",
"drained_at": "2026-10-04T13:15:56+00:00"
},
{
"event_id": "f4b74a51-06c1-43e7-b751-d2ddd2bd1171",
"class": "issuance",
"created_at": "2026-10-04T14:45:00+00:00",
"drained_at": "2026-10-04T14:45:01+00:00"
},
{
"event_id": "1471937f-315a-4cc2-8bd6-6675ba3113b4",
"class": "issuance",
"created_at": "2026-10-04T14:45:11+00:00",
"drained_at": "2026-10-04T14:45:11+00:00"
},
{
"event_id": "34040261-f64d-484a-90e0-aaf3ce68e233",
"class": "issuance",
"created_at": "2026-10-04T14:45:29+00:00",
"drained_at": "2026-10-04T14:45:31+00:00"
},
{
"event_id": "92f9ca1d-c11d-4c89-b685-6b8ac3609900",
"class": "use",
"created_at": "2026-10-04T15:10:13+00:00",
"drained_at": "2026-10-04T15:10:17+00:00"
},
{
"event_id": "a13f7c5d-1e33-480c-8367-64ef088b4e9a",
"class": "use",
"created_at": "2026-10-04T15:10:19+00:00",
"drained_at": "2026-10-04T15:10:22+00:00"
},
{
"event_id": "cc55501a-eba5-4584-81cb-bf9e96000bc0",
"class": "issuance",
"created_at": "2026-10-04T22:17:19+00:00",
"drained_at": "2026-10-04T22:17:20+00:00"
},
{
"event_id": "dedea3c2-fa4b-433c-a035-697cbd6ae6d5",
"class": "heartbeat",
"created_at": "2026-10-04T23:03:35+00:00",
"drained_at": "2026-10-04T23:03:35+00:00"
},
{
"event_id": "a0975609-d8d1-4807-bc51-734584bfc6b8",
"class": "issuance",
"created_at": "2026-10-05T22:31:45+00:00",
"drained_at": "2026-10-05T22:31:46+00:00"
},
{
"event_id": "57f20fdc-dbca-44a5-b888-7c51846f1430",
"class": "issuance",
"created_at": "2026-10-05T22:32:21+00:00",
"drained_at": "2026-10-05T22:32:26+00:00"
},
{
"event_id": "d0a58fc8-a3bc-4d81-a53a-0b260c04b627",
"class": "issuance",
"created_at": "2026-10-05T22:32:41+00:00",
"drained_at": "2026-10-05T22:32:46+00:00"
},
{
"event_id": "a10e31d7-1316-4120-9b75-8ac5881d03f7",
"class": "issuance",
"created_at": "2026-10-05T22:33:00+00:00",
"drained_at": "2026-10-05T22:33:01+00:00"
},
{
"event_id": "89724e03-1740-4f61-aefa-b1351424f7d2",
"class": "issuance",
"created_at": "2026-10-05T22:33:20+00:00",
"drained_at": "2026-10-05T22:33:21+00:00"
},
{
"event_id": "f8508c41-1c83-4dde-a9c1-f8da3af838c4",
"class": "issuance",
"created_at": "2026-10-05T22:33:42+00:00",
"drained_at": "2026-10-05T22:33:46+00:00"
},
{
"event_id": "73a369d4-e559-4518-93f1-4254fcb9021f",
"class": "heartbeat",
"created_at": "2026-10-05T23:03:36+00:00",
"drained_at": "2026-10-05T23:03:36+00:00"
},
{
"event_id": "b867057c-1d26-4bf9-b6c0-9d69437f0a6a",
"class": "heartbeat",
"created_at": "2026-10-06T23:03:38+00:00",
"drained_at": "2026-10-06T23:03:38+00:00"
},
{
"event_id": "f3b7020b-7fd3-4a80-8bee-beb01f944681",
"class": "heartbeat",
"created_at": "2026-10-07T23:03:39+00:00",
"drained_at": "2026-10-07T23:03:39+00:00"
},
{
"event_id": "67d7aae5-43e9-40ca-ab7a-7e3cb40439d4",
"class": "issuance",
"created_at": "2026-10-08T21:51:11+00:00",
"drained_at": "2026-10-08T21:51:14+00:00"
},
{
"event_id": "621a0f5b-65e2-40c9-b263-58434898709e",
"class": "issuance",
"created_at": "2026-10-08T21:56:39+00:00",
"drained_at": "2026-10-08T21:56:39+00:00"
},
{
"event_id": "4248054f-5cda-4b3e-9896-4dbe7a1179f2",
"class": "heartbeat",
"created_at": "2026-10-08T23:03:39+00:00",
"drained_at": "2026-10-08T23:03:39+00:00"
},
{
"event_id": "e58d50c2-5f6f-4cdb-8b1a-dce2373b1d9f",
"class": "issuance",
"created_at": "2026-10-09T10:12:25+00:00",
"drained_at": "2026-10-09T10:12:29+00:00"
},
{
"event_id": "ca825088-173e-47ed-ab2c-b07587399772",
"class": "heartbeat",
"created_at": "2026-10-09T23:03:39+00:00",
"drained_at": "2026-10-09T23:03:39+00:00"
},
{
"event_id": "712df824-df06-4674-a3c3-f75a2bed8625",
"class": "heartbeat",
"created_at": "2026-10-10T23:03:40+00:00",
"drained_at": "2026-10-10T23:03:40+00:00"
},
{
"event_id": "f0bd2e80-cde7-49bd-9d4b-0695290114a8",
"class": "issuance",
"created_at": "2026-10-10T23:23:31+00:00",
"drained_at": "2026-10-10T23:23:35+00:00"
},
{
"event_id": "044a520f-cc68-4a69-8be6-b173ffabbaeb",
"class": "issuance",
"created_at": "2026-10-10T23:23:53+00:00",
"drained_at": "2026-10-10T23:23:55+00:00"
},
{
"event_id": "09c05fcb-5426-4c0c-ad27-45aba842b6e5",
"class": "issuance",
"created_at": "2026-10-11T00:48:11+00:00",
"drained_at": "2026-10-11T00:48:15+00:00"
},
{
"event_id": "c2040c95-8a38-405b-be58-39d146cab281",
"class": "issuance",
"created_at": "2026-10-11T01:14:23+00:00",
"drained_at": "2026-10-11T01:14:31+00:00"
},
{
"event_id": "ce26d525-e8c8-4e8b-bd74-3640167ce744",
"class": "issuance",
"created_at": "2026-10-11T01:46:25+00:00",
"drained_at": "2026-10-11T01:46:26+00:00"
},
{
"event_id": "80e6f45f-0071-4161-9a80-c1f0b2214744",
"class": "issuance",
"created_at": "2026-10-11T02:07:09+00:00",
"drained_at": "2026-10-11T02:07:11+00:00"
},
{
"event_id": "8966501f-a75f-44a5-a8dd-76f3e6458a7c",
"class": "issuance",
"created_at": "2026-10-11T02:34:20+00:00",
"drained_at": "2026-10-11T02:34:21+00:00"
},
{
"event_id": "db163410-52bd-4ef9-9da8-43b553650b24",
"class": "issuance",
"created_at": "2026-10-11T06:47:39+00:00",
"drained_at": "2026-10-11T06:47:43+00:00"
},
{
"event_id": "9a81f17a-cbac-438d-8e2d-27a0bd8d774e",
"class": "issuance",
"created_at": "2026-10-11T06:53:43+00:00",
"drained_at": "2026-10-11T06:53:48+00:00"
},
{
"event_id": "901d01ac-8655-4596-803a-8f8d2ab95dad",
"class": "issuance",
"created_at": "2026-10-11T10:40:55+00:00",
"drained_at": "2026-10-11T10:40:55+00:00"
},
{
"event_id": "3cf709a4-e867-4ecd-9f4f-843093ecc4eb",
"class": "issuance",
"created_at": "2026-10-11T17:19:01+00:00",
"drained_at": "2026-10-11T17:19:03+00:00"
},
{
"event_id": "0d1763bf-7407-4b84-9fe3-a7655b2a60ac",
"class": "heartbeat",
"created_at": "2026-10-11T23:03:41+00:00",
"drained_at": "2026-10-11T23:03:41+00:00"
},
{
"event_id": "32257a42-f68f-4f6e-9815-507f13bdc940",
"class": "heartbeat",
"created_at": "2026-10-12T23:03:41+00:00",
"drained_at": "2026-10-12T23:03:41+00:00"
},
{
"event_id": "dafdf67b-7be0-4c8e-9063-c7a8d9c40c0d",
"class": "heartbeat",
"created_at": "2026-10-13T23:03:42+00:00",
"drained_at": "2026-10-13T23:03:42+00:00"
},
{
"event_id": "3160500d-f313-4055-96d2-8a3e7ba1d235",
"class": "issuance",
"created_at": "2026-10-14T07:37:00+00:00",
"drained_at": "2026-10-14T07:37:01+00:00"
},
{
"event_id": "2ceb8f47-bdcb-4637-a371-f8145b9caef4",
"class": "heartbeat",
"created_at": "2026-10-14T23:03:46+00:00",
"drained_at": "2026-10-14T23:03:46+00:00"
},
{
"event_id": "d66c2b9f-c30b-4db6-8ec0-2c12857d1dc8",
"class": "issuance",
"created_at": "2026-10-15T03:19:34+00:00",
"drained_at": "2026-10-15T03:19:38+00:00"
},
{
"event_id": "76df9dbb-837b-47c4-8026-56db990a59bb",
"class": "heartbeat",
"created_at": "2026-10-15T18:54:55+00:00",
"drained_at": "2026-10-15T18:54:55+00:00"
}
],
"observed_at": "2026-10-15T19:38:37.453790+00:00",
"boundary": "Source-owned occurrence and local acknowledgment coordinates only; independent receiver event-ID/acceptance comparison pending. Existing probe events outside this outbox must be accounted separately."
}

View file

@ -0,0 +1,26 @@
{
"expected_schema_version": 5,
"foreign_key_violations": 0,
"integrity": [
"ok"
],
"ok": true,
"persistent": true,
"required_tables": {
"approvals": true,
"entries": true,
"outbox": true
},
"schema_current": true,
"schema_version": 5,
"live_table_counts": {
"approvals": 54,
"entries": 47,
"outbox": 91
},
"local_copy": "/tmp/nk-wp-0035-approval-backup/pre-schema6.sqlite",
"local_mode": "0600",
"sha256": "70ebf5d0f6218655b3ad3ddb5cc50c52a8210cf061735e45bd0f96d8452367ed",
"persistent_path": "/data/nk-wp-0035-pre-schema6-20261015.sqlite",
"observed_at": "2026-10-15T19:27:13.223863+00:00"
}

View file

@ -0,0 +1,63 @@
{
"storage": {
"schema_version": 6,
"expected_schema_version": 6,
"schema_current": true,
"persistent": true,
"required_tables": {
"approvals": true,
"entries": true,
"outbox": true
},
"integrity": [
"ok"
],
"foreign_key_violations": 0,
"ok": true
},
"outbox": {
"total": 91,
"pending": 0,
"max_age_seconds": 0,
"attempts": 101,
"failed_pending": 0,
"counts": {
"issuance": 47,
"use": 11,
"supersession": 0,
"revocation": 0,
"heartbeat": 33
}
},
"table_counts": {
"approvals": 54,
"entries": 47,
"outbox": 91
},
"new_class_heartbeats": [
{
"asserted_class": "heartbeat",
"created_at": "2026-10-15T18:54:55+00:00",
"drained_at": "2026-10-15T18:54:55+00:00"
},
{
"asserted_class": "heartbeat",
"created_at": "2026-10-14T23:03:46+00:00",
"drained_at": "2026-10-14T23:03:46+00:00"
},
{
"asserted_class": "heartbeat",
"created_at": "2026-10-13T23:03:42+00:00",
"drained_at": "2026-10-13T23:03:42+00:00"
},
{
"asserted_class": "heartbeat",
"created_at": "2026-10-12T23:03:41+00:00",
"drained_at": "2026-10-12T23:03:41+00:00"
}
],
"readiness": 200,
"observed_at": "2026-10-15T19:29:46.446307+00:00",
"pod_uid": "0aff01bc-8719-4229-8f2f-c26781d2e033",
"image": "forgejo.coulomb.social/coulomb/approval-engine@sha256:16e5d222d58e179f4836f649e8f8128321a8b644d29dc426b4d21a65ff97845d"
}

View file

@ -0,0 +1,23 @@
{
"pre_schema": 5,
"post_storage": {
"schema_version": 6,
"expected_schema_version": 6,
"schema_current": true,
"persistent": true,
"required_tables": {
"approvals": true,
"entries": true,
"outbox": true
},
"integrity": [
"ok"
],
"foreign_key_violations": 0,
"ok": true
},
"counts_unchanged": true,
"live_database_overwritten": false,
"nonroot_uid": 10001,
"source_commit": "a786b061da4d7730c0827cf4919ab9206aed6d3e"
}

View file

@ -0,0 +1,107 @@
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "audit-core-migrate-nk-wp-0035-r2",
"namespace": "audit-core",
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"backoffLimit": 0,
"ttlSecondsAfterFinished": 86400,
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"restartPolicy": "Never",
"securityContext": {
"runAsNonRoot": true,
"runAsUser": 10001,
"fsGroup": 10001,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"containers": [
{
"name": "migrate",
"image": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271",
"imagePullPolicy": "IfNotPresent",
"command": [
"python",
"-m",
"audit_core",
"migrate"
],
"env": [
{
"name": "AUDIT_CORE_CREDENTIAL_DIR",
"value": "/etc/audit-core/db"
},
{
"name": "AUDIT_CORE_DATABASE_SCHEMA",
"value": "audit_core"
},
{
"name": "AUDIT_CORE_MIGRATE_ROLE",
"value": "audit_core_migrate"
}
],
"resources": {
"requests": {
"cpu": "25m",
"memory": "64Mi"
},
"limits": {
"cpu": "250m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"volumeMounts": [
{
"name": "tmp",
"mountPath": "/tmp"
},
{
"name": "database-credential",
"mountPath": "/etc/audit-core/db",
"readOnly": true
}
],
"terminationMessagePolicy": "FallbackToLogsOnError"
}
],
"volumes": [
{
"name": "tmp",
"emptyDir": {}
},
{
"name": "database-credential",
"secret": {
"secretName": "audit-core-database-migrate",
"defaultMode": 288
}
}
]
}
},
"activeDeadlineSeconds": 300
}
}

View file

@ -0,0 +1,108 @@
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "audit-core-migrate-nk-wp-0035-r3",
"namespace": "audit-core",
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"backoffLimit": 0,
"ttlSecondsAfterFinished": 86400,
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"restartPolicy": "Never",
"securityContext": {
"runAsNonRoot": true,
"runAsUser": 10001,
"fsGroup": 10001,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"containers": [
{
"name": "migrate",
"image": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271",
"imagePullPolicy": "IfNotPresent",
"command": [
"python",
"-m",
"audit_core",
"migrate"
],
"env": [
{
"name": "AUDIT_CORE_CREDENTIAL_DIR",
"value": "/etc/audit-core/db"
},
{
"name": "AUDIT_CORE_DATABASE_SCHEMA",
"value": "audit_core"
},
{
"name": "AUDIT_CORE_MIGRATE_ROLE",
"value": "audit_core_migrate"
}
],
"resources": {
"requests": {
"cpu": "25m",
"memory": "64Mi"
},
"limits": {
"cpu": "250m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"volumeMounts": [
{
"name": "tmp",
"mountPath": "/tmp"
},
{
"name": "database-credential",
"mountPath": "/etc/audit-core/db",
"readOnly": true
}
],
"terminationMessagePolicy": "FallbackToLogsOnError"
}
],
"volumes": [
{
"name": "tmp",
"emptyDir": {}
},
{
"name": "database-credential",
"secret": {
"secretName": "audit-core-database-migrate",
"defaultMode": 288
}
}
],
"automountServiceAccountToken": false
}
},
"activeDeadlineSeconds": 300
}
}

View file

@ -0,0 +1,106 @@
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "audit-core-migrate-nk-wp-0035",
"namespace": "audit-core",
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"backoffLimit": 0,
"ttlSecondsAfterFinished": 86400,
"template": {
"metadata": {
"labels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"spec": {
"restartPolicy": "OnFailure",
"securityContext": {
"runAsNonRoot": true,
"runAsUser": 10001,
"fsGroup": 10001,
"seccompProfile": {
"type": "RuntimeDefault"
}
},
"containers": [
{
"name": "migrate",
"image": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271",
"imagePullPolicy": "IfNotPresent",
"command": [
"python",
"-m",
"audit_core",
"migrate"
],
"env": [
{
"name": "AUDIT_CORE_CREDENTIAL_DIR",
"value": "/etc/audit-core/db"
},
{
"name": "AUDIT_CORE_DATABASE_SCHEMA",
"value": "audit_core"
},
{
"name": "AUDIT_CORE_MIGRATE_ROLE",
"value": "audit_core_migrate"
}
],
"resources": {
"requests": {
"cpu": "25m",
"memory": "64Mi"
},
"limits": {
"cpu": "250m",
"memory": "128Mi"
}
},
"securityContext": {
"allowPrivilegeEscalation": false,
"capabilities": {
"drop": [
"ALL"
]
},
"readOnlyRootFilesystem": true
},
"volumeMounts": [
{
"name": "tmp",
"mountPath": "/tmp"
},
{
"name": "database-credential",
"mountPath": "/etc/audit-core/db",
"readOnly": true
}
]
}
],
"volumes": [
{
"name": "tmp",
"emptyDir": {}
},
{
"name": "database-credential",
"secret": {
"secretName": "audit-core-database-migrate",
"defaultMode": 288
}
}
]
}
},
"activeDeadlineSeconds": 300
}
}

View file

@ -0,0 +1,17 @@
[
{
"op": "test",
"path": "/metadata/uid",
"value": "b85fe3d0-75c9-4e0d-8c34-c6f1df0881bb"
},
{
"op": "test",
"path": "/metadata/resourceVersion",
"value": "75782608"
},
{
"op": "replace",
"path": "/spec/template/spec/containers/0/image",
"value": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271"
}
]

View file

@ -0,0 +1,69 @@
{
"apiVersion": "networking.k8s.io/v1",
"kind": "NetworkPolicy",
"metadata": {
"name": "audit-core-migrate-egress",
"namespace": "audit-core"
},
"spec": {
"podSelector": {
"matchLabels": {
"app.kubernetes.io/name": "audit-core",
"app.kubernetes.io/component": "migrate"
}
},
"policyTypes": [
"Egress"
],
"egress": [
{
"to": [
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "databases"
}
},
"podSelector": {
"matchLabels": {
"cnpg.io/cluster": "platform-pg"
}
}
}
],
"ports": [
{
"protocol": "TCP",
"port": 5432
}
]
},
{
"to": [
{
"namespaceSelector": {
"matchLabels": {
"kubernetes.io/metadata.name": "kube-system"
}
},
"podSelector": {
"matchLabels": {
"k8s-app": "kube-dns"
}
}
}
],
"ports": [
{
"protocol": "UDP",
"port": 53
},
{
"protocol": "TCP",
"port": 53
}
]
}
]
}
}

View file

@ -0,0 +1,60 @@
{
"prepared_at": "2026-10-15T19:22:57.217356+00:00",
"images": {
"approval-engine": "forgejo.coulomb.social/coulomb/approval-engine@sha256:16e5d222d58e179f4836f649e8f8128321a8b644d29dc426b4d21a65ff97845d",
"audit-core": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271",
"rapp-qonto-collector": "forgejo.coulomb.social/coulomb/rapp-qonto-audit-collector@sha256:82bfd4100e14ddb2cefc4acc02b6b74609846b4a26ad40d9089ed564693f250a"
},
"source_commits": {
"approval-engine": "a786b061da4d7730c0827cf4919ab9206aed6d3e",
"audit-core": "5227fb14e47f26a01930be4611360fd90f740662",
"rapp-qonto": "62a7cc0"
},
"scan_high_critical": {
"approval-engine": {
"count": 0,
"report_sha256": "5e787b9d4bf9514a8182476206bcc01d1d9497cee30efb876373964b1a6a6563",
"created_at": "2026-10-15T19:17:24.836393899Z"
},
"audit-core": {
"count": 0,
"report_sha256": "f7522e60a2f37e3a85cd5ba1f0754a73d637bcb430c2f4bdf0bec925ee5f0e88",
"created_at": "2026-10-15T19:20:34.959522138Z"
},
"rapp-qonto-collector": {
"count": 0,
"report_sha256": "0496a10c3f41925c7f2d2e92220ccc74a1814a2a924b7a3be76e88d8b8e58eb9",
"created_at": "2026-10-15T19:22:30.723382718Z"
}
},
"approval_migration": "v5 to v6; isolated actual backup migration verified with unchanged table counts",
"receiver_migration": "additive0007-import-receipts with native migration lease; no historical import",
"live_applied": false,
"server_dry_run": {
"approval_engine_images": 0,
"receiver_image": 0,
"receiver_migration_job": 0,
"collector_runtime_ingress": 0,
"hourly_observation_job": 0
},
"observation_job": "qonto-hourly-observation-job.json",
"new_sender_scope": {
"name": "rapp-qonto-collector",
"source": "qonto-assistant",
"tenant": "tenant:friendly:binky",
"may_write": true,
"may_read": false,
"secret_policy": "redact",
"bank_credentials_or_calls": false
},
"collector_access": {
"kubernetes": [
"namespace rapp-qonto: get/list pods, get pods/log"
],
"audit_custody": "only its exact writer bearer;1Gi persistent outbox",
"api_egress_ips": [
"10.43.0.1/32",
"92.205.62.239/32"
]
}
}

View file

@ -0,0 +1,14 @@
{
"schema_version": "nk-wp-0035-commit-checkpoint/v1",
"captured_at": "2026-10-15T19:46:19.560233+00:00",
"status": "observation-in-progress",
"job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14",
"expected_completion_utc": "2026-10-15T20:37:00Z",
"independent_readback": "pending; fresh attended operator identity required after capture",
"monitor_session": 48788,
"monitor_is_snapshot": true,
"inventory_line_count": 89,
"monitor_sha256": "2e7900cb78caec010e737514013f67e2dffddf79888bfca1a56ecdcbf7cddbfe",
"inventory_sha256": "558816b9455e99b360855b5ff5fc95acb4e1ad44ec7932c224b314e58809ad9e",
"previous_implementation_commit": "bd1bed7"
}

View file

@ -0,0 +1,89 @@
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:36:39.436327+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:25.773403+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:31.327322+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:36.844493+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:45.324944+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:50.896701+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:37:56.403632+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:01.913613+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:07.419506+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:12.930431+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:13.689275+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:27.490623+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:33.006764+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:38.528648+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:44.056039+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:49.779254+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:38:58.215739+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:03.744933+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:09.267448+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:14.773437+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:15.408990+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:26.248329+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:34.705382+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:40.224525+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:45.741720+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:51.267318+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:39:56.785626+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:02.317486+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:10.766633+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:16.353685+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:17.202469+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:28.123930+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:33.645652+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:42.112688+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:47.586944+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:53.147308+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:40:58.673947+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:04.210811+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:09.732821+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:18.199613+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:18.988584+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:29.828969+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:35.330859+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:40.831866+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:46.397315+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:41:54.954808+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:00.541758+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:06.081033+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:11.601533+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:17.204024+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:17.773813+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:31.579551+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:37.082947+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:42.598851+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:48.118778+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:42:53.674913+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:02.117074+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:07.641536+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:13.199415+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:18.708679+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:19.500795+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:30.362667+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:38.814516+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:44.402933+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:49.968732+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:43:55.640453+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:01.206272+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:06.734327+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:15.170148+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:20.682267+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:21.235075+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:32.084425+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:37.613551+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:43.125384+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:51.542643+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:44:57.035707+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:02.524550+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:08.029220+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:13.538625+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:21.994718+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:22.935593+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:33.980967+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:39.480849+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:45.040850+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:50.583653+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:45:59.047751+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:46:04.646317+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:46:10.158129+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}
{"job_status": {"active": 1, "ready": 1, "startTime": "2026-10-15T19:36:07Z", "terminating": 0, "uncountedTerminatedPods": {}}, "job_uid": "f81f6e4b-b8ab-42d8-ac36-80f1386f9a14", "observed_at": "2026-10-15T19:46:15.677229+00:00", "source_pods": [{"containers": [{"image": "forgejo.coulomb.social/coulomb/rapp-qonto@sha256:2e42b5e665a19ffa3b3ae8109a8aab64f5485329f72810be063263ff5086cb3f", "name": "user-container"}, {"image": "gcr.io/knative-releases/knative.dev/serving/cmd/queue@sha256:371c23206b7bd924474b07e51435198579133eb98fba27a5cb3db9e6ba63b06b", "name": "queue-proxy"}], "deleting": false, "name": "rapp-qonto-00010-deployment-787d88897c-hqwql", "phase": "Running", "resource_version": "75786152", "revision": "rapp-qonto-00010", "uid": "e7dcb53c-39f3-4c94-bf04-b058a28b2946"}]}

Some files were not shown because too many files have changed in this diff Show more