Compare commits

..

No commits in common. "main" and "csoc-platform-mail" have entirely different histories.

779 changed files with 5577 additions and 96969 deletions

94
.custodian-brief.md Normal file
View file

@ -0,0 +1,94 @@
<!-- 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

@ -1,58 +0,0 @@
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/**", "sso-mfa/k8s/synthetic-journey/**", "bounded-executor/**", "tools/bounded-execution-admission/**", "examples/bounded-execution-admission/**", ".forgejo/workflows/identity-journeys.yaml"]
paths: ["identity-provisioner/**", ".forgejo/workflows/identity-journeys.yaml"]
workflow_dispatch:
jobs:
provider:
@ -18,14 +18,3 @@ 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

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

View file

@ -1,10 +0,0 @@
# 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,6 +189,3 @@ 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,14 +2,13 @@
# 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 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.
# 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>
creation_rules:
- path_regex: secrets/.*$

View file

@ -229,39 +229,6 @@ 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
@ -283,20 +250,3 @@ 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,20 +32,3 @@ 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,7 +26,6 @@ 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) |
---
@ -145,18 +144,3 @@ 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,42 +50,52 @@ hooks-test: ## Test that the pre-commit hook blocks plaintext secrets
@rm -rf sso-mfa/bootstrap/secrets/_hooktest
# ── SOPS / age ────────────────────────────────────────────────────────────────
sops-setup: ## Validate the registered public age recipient; never install a private key
@python3 sso-mfa/bootstrap/sops-private.py setup
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
# 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-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)
sops-edit: ## Replace encrypted content from private INPUT outside Git; explicit custody required
@python3 sso-mfa/bootstrap/sops-write.py edit
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-encrypt: ## Encrypt private, untracked FILE in place with the registered public recipient
@python3 sso-mfa/bootstrap/sops-write.py encrypt
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-decrypt: ## Decrypt age-only JSON/YAML to new private OUTPUT outside Git; explicit custody required
@python3 sso-mfa/bootstrap/sops-private.py decrypt
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-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
sops-custody-check: ## Validate a custody age key against keys/age.pub without keeping it on disk
@bash sso-mfa/bootstrap/sops-custody-unlock.sh \
--expected-recipient "$$NK_CUSTODY_RECIPIENT" --check-only
--expected-recipient "$(OPERATOR_AGE_PUBKEY)" \
--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 "$$NK_CUSTODY_RECIPIENT"
--expected-recipient "$(OPERATOR_AGE_PUBKEY)"
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"
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"
check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted
@echo "Checking for unencrypted files under secrets/..."
@ -100,7 +110,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 file detected. For private untracked input: make sops-encrypt FILE=<file>; tracked plaintext requires incident review."; \
echo "ERROR: Unencrypted secret(s) detected. Encrypt with: sops --encrypt --in-place <file>"; \
exit 1; \
fi; \
echo "✔ All secrets/ files appear SOPS-encrypted"
@ -113,16 +123,18 @@ 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 private service files for public-recipient encryption
creds-generate: ## Generate all service secrets and print KeePassXC entry guide
@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 : encrypt to the registered public recipient; never copy values into transcripts."
@echo "Next : enter values into KeePassXC, then set keepass_confirmed: true"
@echo " Run 'make creds-bundle' to create an encrypted offsite backup."
creds-bundle: ## Age-encrypt the ops bundle for offsite storage
@ -139,25 +151,33 @@ 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: ## Observe current Secret presence without rewriting native acceptance
creds-verify: ## Check all expected K8s secrets exist and update creds-state.yaml
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>
@bash sso-mfa/bootstrap/creds-rotate.sh
@[[ -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
## ── Agent-driven credential lifecycle (NK-WP-0005) ──────────────────────────
creds-agent-init: ## Attended bootstrap using the public recipient and encrypted recovery handoff
creds-agent-init: ## Fully automated credential bootstrap — generates, encrypts, injects, delivers emergency bundle
@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: ## Print the non-secret recovery guide; no keys or passwords are read
creds-emergency-reprint: ## Re-deliver emergency bundle (if lost/stolen — reprints, rotates nothing)
@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)
@ -197,35 +217,6 @@ 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 \
@ -243,7 +234,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
bash -n sso-mfa/k8s/lldap/break-glass.sh || true # may have env assumptions
@echo "✔ bootstrap scripts syntax OK"
security-bootstrap-validate-keycape-client: ## Validate KeyCape OpenBao client definition+deployment (T08)
@ -306,11 +297,13 @@ 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: ## Retired: see docs/legacy-lldap-lifecycle-retirement.md
@bash sso-mfa/k8s/lldap/dry-run-nonroot-user.sh
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-lifecycle-cleanup-dryrun-users: ## 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-validate-custody-roster: ## Validate and verify the signed local custody roster
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
@ -388,14 +381,13 @@ security-bootstrap-ui: security-bootstrap-metadata-init ## Serve local custody a
--host "$(SECURITY_BOOTSTRAP_HOST)" \
--port "$(SECURITY_BOOTSTRAP_PORT)"
.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 \
.PHONY: runbooks-validate 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 service-identity-test service-identity-check security-tools \
bounded-execution-admission-test bounded-execution-admission-check bounded-executor-test \
custody-record-test \
emission-cadence-profile-test \
posture-feedback-reference security-scenario-compose-lightweight \
security-bootstrap-console-test security-bootstrap-scripts-syntax \
@ -424,16 +416,3 @@ 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.8.md) — the
- [Security layer model](canon/standards/security-layer-model_v0.7.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,38 +227,3 @@ 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,26 +1,23 @@
---
id: netkingdom-security-companion-v0.3
id: netkingdom-security-companion-v0.2
type: standard-companion
title: "NetKingdom Security — Working Companion v0.3"
title: "NetKingdom Security — Working Companion v0.2"
domain: netkingdom
status: accepted
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
version: "0.2"
companion_to: canon/standards/security-layer-model_v0.7.md
owner: gate-house
publication_owner: net-kingdom
created: "2026-08-29"
updated: "2026-10-11"
updated: "2026-08-29"
review_interval: 3m
standard_token: security-companion_v0.3
standard_token: security-companion_v0.2
---
# NetKingdom Security — Working Companion
**Start here.** This is the operative form of
[`canon/standards/security-layer-model_v0.8.md`](canon/standards/security-layer-model_v0.8.md)
[`canon/standards/security-layer-model_v0.7.md`](canon/standards/security-layer-model_v0.7.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** —
@ -59,13 +56,6 @@ 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
@ -167,13 +157,6 @@ 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.
@ -236,5 +219,5 @@ Stated so nobody plans around a capability that does not exist:
---
**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.
**Statute:** [`canon/standards/security-layer-model_v0.7.md`](canon/standards/security-layer-model_v0.7.md) — accepted 2026-08-29.
**Owner:** `gate-house`. **Guidance on getting things done:** `ops-warden`.

View file

@ -12,48 +12,36 @@
| 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 | finished | — | workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md |
| workplan | NK-WP-0022 | blocked | — | 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 | backlog | — | workplans/NK-WP-0027-reef-placement-reconciliation.md |
| workplan | NK-WP-0027 | blocked | — | 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 | finished | — | workplans/NK-WP-0031-deterministic-posture-feedback.md |
| workplan | NK-WP-0031 | blocked | — | 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 | active | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| workplan | NK-WP-0035 | blocked | — | 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 | active | — | workplans/NK-WP-0040-execution-attribution-receipt.md |
| workplan | NK-WP-0040 | blocked | — | 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 | finished | — | workplans/NK-WP-0042-workload-mfa-step-up.md |
| workplan | NK-WP-0042 | blocked | — | 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 | 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 |
| 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 |
| 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 |
@ -61,15 +49,6 @@
| 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 |
@ -102,7 +81,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 | 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-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 |
@ -123,11 +102,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 | 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-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-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 | todo | — | 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-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 |
@ -144,7 +123,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 | 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-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 |
@ -164,8 +143,6 @@
| 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 |
@ -186,7 +163,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 | cancel | — | 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-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 |
@ -205,168 +182,11 @@
| 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 | 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-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 | 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 |
| task | NK-WP-0047-T03 | wait | — | workplans/NK-WP-0047-platform-password-reset-mail.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

@ -1,18 +0,0 @@
# 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

@ -1,62 +0,0 @@
# 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
```

View file

@ -1,340 +0,0 @@
#!/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())

View file

@ -1,179 +0,0 @@
"""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

@ -1,368 +0,0 @@
#!/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

@ -1,395 +0,0 @@
"""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

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

View file

@ -1,233 +0,0 @@
# 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

@ -1,105 +0,0 @@
#!/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

@ -1,283 +0,0 @@
#!/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

@ -1,322 +0,0 @@
"""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

@ -1,158 +0,0 @@
"""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

@ -1,405 +0,0 @@
"""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

@ -1,246 +0,0 @@
"""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

@ -1,133 +0,0 @@
"""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

@ -1,582 +0,0 @@
{
"$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

@ -1,454 +0,0 @@
{
"$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

@ -1,56 +0,0 @@
{
"$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

@ -1,496 +0,0 @@
{
"$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

@ -1,86 +0,0 @@
---
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

@ -1,254 +0,0 @@
---
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 and secret-free handoff refinements **Supersedes:** v0.1 (retired with NK-WP-0004)
**Version:** 0.2 **Status:** current with OpenBao runtime refinement **Supersedes:** v0.1 (retired with NK-WP-0004)
---
@ -17,7 +17,7 @@ control plane.
## 2. Trust Hierarchy
```
age public recipient ────────► encrypts secrets.enc/ in git
age private key ─────────────► encrypts secrets.enc/ in git
│
└──► ops bundle (encrypted snapshot of all secrets)
└──► stored offsite; decrypt with age key
@ -49,36 +49,30 @@ authority once the platform control plane is alive.
## 3. Credential Lifecycle
### Phase 0 — Bootstrap (attended, public-recipient encryption)
### Phase 0 — Bootstrap (run once, agent-driven)
`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.
```
make creds-agent-init
```
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.
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`
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.
The script resumes from where it left off if interrupted — each phase is
tracked in `creds-state.yaml`.
### Phase 1 — Normal operation
@ -105,42 +99,60 @@ See Section 6 for details.
### Phase 3 — Recovery
Use the emergency bundle stored in your personal password manager.
See Section 4 — Emergency Recovery Handoff.
See Section 4 — Emergency Bundle.
---
## 4. Emergency Recovery Handoff
## 4. Emergency Bundle
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.
### Contents
`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.
| 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 |
Recovery sequence:
### Delivery
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 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.
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.
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
---
@ -232,9 +244,7 @@ make creds-bundle
Store the bundle offsite (cloud, external drive, second location). Decrypt:
```
# 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.
age -d -i ~/.config/sops/age/keys.txt ops-bundle-<date>.tar.age | tar xf -
```
---
@ -246,21 +256,19 @@ The following are permanently prohibited:
1. **Committing `secrets/` (plaintext) to git** — the pre-commit hook blocks
this; do not bypass with `--no-verify`
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.
2. **Storing secrets in KeePassXC as the primary credential store** — KeePassXC
is for optional personal backup of the emergency bundle only
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.
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.
4. **Hardcoding secrets in manifests or ConfigMaps** — all service secrets
must flow through K8s Secrets created by `create-secrets.sh` scripts
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.
5. **Storing the age private key in the repo** — the key lives outside the
repo at `~/.config/sops/age/keys.txt`
6. **Using OpenBao as a policy decision point** — OpenBao stores, leases,
audits, and revokes secret material; identity comes from the IAM

View file

@ -51,9 +51,7 @@ 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
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,
owner-instance migration and validation. The King's Guard draft is provenance,
not a fallback schema.
## 2. Ownership
@ -159,16 +157,3 @@ 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

@ -1,214 +0,0 @@
---
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,23 +3,16 @@ id: netkingdom-security-layer-model-v0.8
type: standard
title: "NetKingdom Security Layer Model v0.8"
domain: netkingdom
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"
status: proposed
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-10-11"
last_reviewed: "2026-10-10"
updated: "2026-09-09"
last_reviewed: "2026-09-09"
review_interval: 3m
source_revision: "gate-house@4bd50bfcac850f34e5563ade61fc92103b062f4d; A9-A13 set v0.3 and A2 v1.1"
source_revision: "gate-house@516ed4e"
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.
@ -195,31 +188,6 @@ 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
@ -319,49 +287,34 @@ assent to them sight-unseen.
## 4. Layer catalog
| 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).
| 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 |
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 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.
`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.
## 5. The binding rule
@ -1229,65 +1182,15 @@ 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 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.
**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.
Mechanically checkable:
@ -1300,11 +1203,8 @@ Mechanically checkable:
surface;
- no §4 capability is catalogued without an engine surface, a `pending` mark, or
a `declared-gap` mark;
- 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
- every repository catalogued in §4 as a source of evidence declares its
**emission guarantee** in its machine-readable layer declaration: 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
@ -1312,9 +1212,7 @@ 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, and, where that guarantee is not itself
completeness, an explicit statement that completeness is not claimed; for an
**attributive** source, the trade it
reconciliation, not either alone; 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
@ -1332,8 +1230,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 repeatedly
corrected; the instances are listed in §15. Drafted in `gate-house/docs/contracts/approval-emission-detection.md`,
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`,
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
@ -1341,21 +1239,6 @@ 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
@ -1528,33 +1411,8 @@ follow-up. Settled in `GH-DEC-2026-006`.
## 14. Adoption
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.
Status is **proposed**. v0.7 remains **accepted** and in force until this version
is accepted in its place; it is not patched.
Two things that acceptance does and does not mean, kept apart because
`ops-warden` asked for the distinction:
@ -1564,7 +1422,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** | 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. |
| **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 |
**Ten of this version's twelve changes as cut were requested by another
@ -1612,20 +1470,6 @@ 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

@ -1,380 +0,0 @@
---
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-15"
revision: "draft-14"
owner: net-kingdom
last_reviewed: "2026-08-23"
review_interval: 6m

View file

@ -1,117 +0,0 @@
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

@ -1,135 +0,0 @@
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,14 +1,5 @@
# 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

@ -1,65 +0,0 @@
---
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-4"
revision: "draft-3"
version: "0.1"
last_reviewed: "2026-08-31"
review_interval: 6m

View file

@ -1,4 +1,4 @@
# Attended procedure inventory — 2026-09-05 baseline, reviewed 2026-10-15
# Attended procedure inventory — 2026-09-05
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,41 +11,42 @@ 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) | 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/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/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) | 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/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/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) | 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/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/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) | 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) |
| `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 |
| `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` | [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/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/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) | 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 |
| `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 |
| `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) | source + isolated native fixture verified 2026-10-14; estate exercise still unknown; [procedure](../sso-mfa/k8s/lldap/GROUP-MAINTENANCE.md) |
| `verify-t07.sh` | [DR-RUNBOOK.md](../sso-mfa/k8s/backup/DR-RUNBOOK.md) | unknown |
| `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.
@ -57,31 +58,3 @@ 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,11 +17,9 @@ 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. 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.
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.
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

@ -1,450 +0,0 @@
---
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

@ -1,611 +0,0 @@
# 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

@ -1,57 +0,0 @@
# 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

@ -1,172 +0,0 @@
# 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

@ -1,43 +0,0 @@
{
"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

@ -1 +0,0 @@
{"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

@ -1 +0,0 @@
{"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

@ -1,18 +0,0 @@
{
"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

@ -1,29 +0,0 @@
# 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

@ -1,14 +0,0 @@
{
"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

@ -1,461 +0,0 @@
<!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

@ -1,580 +0,0 @@
{
"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

@ -1,166 +0,0 @@
{
"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

@ -1,14 +0,0 @@
{
"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

@ -1,191 +0,0 @@
{
"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

@ -1,19 +0,0 @@
# 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

@ -1,14 +0,0 @@
{
"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

@ -1,58 +0,0 @@
{
"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

@ -1,32 +0,0 @@
{
"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

@ -1,476 +0,0 @@
{
"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

@ -1,33 +0,0 @@
{
"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

@ -1,15 +0,0 @@
{
"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

@ -1,106 +0,0 @@
{
"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

@ -1,28 +0,0 @@
{
"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

@ -1,149 +0,0 @@
{
"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

@ -1,234 +0,0 @@
{
"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

@ -1,45 +0,0 @@
{
"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

@ -1,28 +0,0 @@
{
"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

@ -1,19 +0,0 @@
{
"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

@ -1,48 +0,0 @@
{
"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

@ -1,93 +0,0 @@
{
"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

@ -1,23 +0,0 @@
{
"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

@ -1,562 +0,0 @@
{
"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

@ -1,22 +0,0 @@
[
{
"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

@ -1,30 +0,0 @@
{
"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

@ -1,57 +0,0 @@
{
"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

@ -1,29 +0,0 @@
[
{
"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

@ -1,593 +0,0 @@
{
"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

@ -1,26 +0,0 @@
{
"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

@ -1,63 +0,0 @@
{
"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

@ -1,23 +0,0 @@
{
"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

@ -1,107 +0,0 @@
{
"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

@ -1,108 +0,0 @@
{
"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

@ -1,106 +0,0 @@
{
"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

@ -1,17 +0,0 @@
[
{
"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

@ -1,69 +0,0 @@
{
"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

@ -1,60 +0,0 @@
{
"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

@ -1,14 +0,0 @@
{
"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

@ -1,89 +0,0 @@
{"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