ADHOC-2026-11-07: adopt NK-WP-0058-T07 runbook conventions; unstale CCR seed test

One cluster route (NK_KUBECONFIG, default ~/.kube/config-railiance01) and
server-side apply throughout. Fixes the Makefile dry-run defaulting to the
coulombcore kubeconfig. Lane-run scripts take no default and fail closed when
NK_KUBECONFIG is unset. net-kingdom convention lint: 14 findings -> 0.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 73091@bnt-lap001
Assistant-Session: 9f48906c-6737-449f-8b97-fb86877ca59a
This commit is contained in:
tegwick 2026-11-07 13:25:02 +01:00
parent 782458d327
commit 5649ef90c4
13 changed files with 110 additions and 27 deletions

View file

@ -27,8 +27,8 @@ help: ## Show this help
IMAGE_REGISTRY ?= forgejo.coulomb.social/coulomb/audit-core
GIT_COMMIT := $(shell git rev-parse HEAD)
GIT_COMMIT_SHORT := $(shell git rev-parse --short HEAD)
# railiance01 k3s API is forwarded by ops-bridge tunnel k3s-api-railiance01.
KUBECONFIG_RAILIANCE ?= $(HOME)/.kube/config-hosteurope
# One cluster route (NK-WP-0058-T07): NK_KUBECONFIG, default the railiance01 kubeconfig.
NK_KUBECONFIG ?= $(HOME)/.kube/config-railiance01
image-build: ## Build the immutable image, labelled with the current commit
docker build -f Containerfile \
@ -43,7 +43,7 @@ image-publish: image-build ## Push the commit-tagged image to Forgejo
@echo "Pin the printed digest in deploy/audit-core.yaml and deploy/migrate-job.yaml"
deploy-dry-run: ## Server-side validate the railiance01 manifests
KUBECONFIG=$(KUBECONFIG_RAILIANCE) kubectl apply --dry-run=server --validate=strict \
KUBECONFIG=$(NK_KUBECONFIG) kubectl apply --server-side --dry-run=server --validate=strict \
-f deploy/audit-core.yaml \
-f deploy/senders-scope.yaml \
-f deploy/networkpolicies.yaml \

View file

@ -11,6 +11,7 @@
| workplan | ADHOC-2026-09-14 | finished | — | workplans/ADHOC-2026-09-14.md |
| workplan | ADHOC-2026-09-24 | finished | — | workplans/ADHOC-2026-09-24.md |
| workplan | ADHOC-2026-10-15 | finished | — | workplans/ADHOC-2026-10-15.md |
| workplan | ADHOC-2026-11-07 | finished | — | workplans/ADHOC-2026-11-07.md |
| workplan | AUDIT-WP-0001 | finished | — | workplans/AUDIT-WP-0001-statehub-bootstrap.md |
| workplan | AUDIT-WP-0002 | finished | — | workplans/AUDIT-WP-0002-pluggable-audit-backend.md |
| workplan | AUDIT-WP-0003 | finished | — | workplans/AUDIT-WP-0003-user-engine-event-ingestion-service.md |
@ -32,6 +33,8 @@
| task | ADHOC-2026-09-14-T01 | done | — | workplans/ADHOC-2026-09-14.md |
| task | ADHOC-2026-09-24-T01 | done | — | workplans/ADHOC-2026-09-24.md |
| task | ADHOC-2026-10-15-T01 | done | — | workplans/ADHOC-2026-10-15.md |
| task | ADHOC-2026-11-07-T01 | done | — | workplans/ADHOC-2026-11-07.md |
| task | ADHOC-2026-11-07-T02 | done | — | workplans/ADHOC-2026-11-07.md |
| task | AUDIT-WP-0001-T01 | done | — | workplans/AUDIT-WP-0001-statehub-bootstrap.md |
| task | AUDIT-WP-0001-T02 | done | — | workplans/AUDIT-WP-0001-statehub-bootstrap.md |
| task | AUDIT-WP-0001-T03 | done | — | workplans/AUDIT-WP-0001-statehub-bootstrap.md |

View file

@ -1,9 +1,9 @@
# railiance01 package
Target: railiance01 only. The workstation kubeconfig that talks to that API
is the `k3s-api-railiance01` tunnel (local port **16444**).
`~/.kube/config-hosteurope` currently points at 16443 (coulombcore); rewrite
the server port or export a copy before applying.
Target: railiance01 only. Reach it through the one cluster route
(NK-WP-0058-T07): `NK_KUBECONFIG`, default `~/.kube/config-railiance01`. Do not
use any other kubeconfig file or a `--server` override. Every apply is
`kubectl apply --server-side`.
Apply order is documented in `docs/operator-runbook.md`. Do not apply the
Deployment until:

View file

@ -339,15 +339,21 @@ run `nk_bind_executor_custody.py fence` for the coding-agent boundary entries.
dry-run first, then the apply:
```bash
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl apply --dry-run=server -f -
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl apply -f -
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" kubectl apply --server-side --dry-run=server -f -
cd /home/worsch/audit-core && python3 -c 'import sys,yaml; d=[x for x in yaml.safe_load_all(open("deploy/networkpolicies.yaml")) if x and x["metadata"]["name"]=="audit-core-nk-bounded-executor-ingress"]; assert len(d)==1; yaml.safe_dump(d[0],sys.stdout)' | KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" kubectl apply --server-side -f -
```
All cluster commands here use the one cluster route (NK-WP-0058-T07):
`NK_KUBECONFIG`, default `~/.kube/config-railiance01`. Every apply is
server-side. The 2026-11-04 apply was client-side, so the live object still
carries a `last-applied-configuration` annotation. A later server-side apply
takes the object over, so nothing needs to be done now.
**2. Read-only preflight** (prints no secrets):
```bash
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get configmap audit-core-senders-scope -o jsonpath='{.data.senders-scope\.json}' | python3 -c 'import sys,json; print([e for e in json.load(sys.stdin) if e["name"]=="nk-bounded-executor"])'
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get deploy audit-core -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" kubectl -n audit-core get configmap audit-core-senders-scope -o jsonpath='{.data.senders-scope\.json}' | python3 -c 'import sys,json; print([e for e in json.load(sys.stdin) if e["name"]=="nk-bounded-executor"])'
KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" kubectl -n audit-core get deploy audit-core -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
bridge status
```
@ -360,7 +366,7 @@ This is one silent command. Read the result from the receipt, not from the
terminal:
```bash
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- /home/worsch/audit-core/scripts/attended-seed-nk-bounded-executor-sender.sh /home/worsch/audit-core/docs/evidence/nk-bounded-executor-sender-seed-r1.json
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- env NK_KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" /home/worsch/audit-core/scripts/attended-seed-nk-bounded-executor-sender.sh /home/worsch/audit-core/docs/evidence/nk-bounded-executor-sender-seed-r1.json
```
The receipt is created exclusively. If you rerun, use `-r2.json`. The receiver
@ -370,7 +376,7 @@ restart is part of this step. No separate rollout is needed.
```bash
python3 -c 'import json; r=json.load(open("/home/worsch/audit-core/docs/evidence/nk-bounded-executor-sender-seed-r1.json")); print(r["status"], r.get("error"), r.get("receiver_probe"), r.get("registry_secret_hash_match"), r.get("sso_secret_hash_match"), r["credential_values_emitted"])'
KUBECONFIG=/home/worsch/.kube/config-railiance01 kubectl -n audit-core get networkpolicy audit-core-nk-bounded-executor-ingress
KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}" kubectl -n audit-core get networkpolicy audit-core-nk-bounded-executor-ingress
```
The expected status is `seeded_pending_sso_delivery`, or `seeded_and_delivered`

View file

@ -6,7 +6,7 @@
# openbao-ui-railiance01 → http://127.0.0.1:18200. railiance-platform's
# openbao-attended-exec.py pins that address for both OIDC and the child.
set -euo pipefail
export RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-railiance01}"
export NK_KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}"
cd "$(dirname "$0")/.."
exec python3 /home/worsch/railiance-platform/scripts/openbao-attended-exec.py -- \
"$PWD/scripts/mint-tenant-engine-sender.py"

View file

@ -2,7 +2,7 @@
# Attended overlap-then-drop proof that a formerly valid audit bearer is 401.
# Uses the operator OpenBao tunnel. Production tokens are not replaced.
set -euo pipefail
export RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-$HOME/.kube/config-railiance01}"
export NK_KUBECONFIG="${NK_KUBECONFIG:-$HOME/.kube/config-railiance01}"
cd "$(dirname "$0")/.."
exec python3 /home/worsch/railiance-platform/scripts/openbao-attended-exec.py -- \
"$PWD/scripts/prove-sender-bearer-revocation.py"

View file

@ -3,10 +3,14 @@
# NK-WP-0056-T15, CCR-2026-0063). Founder-run under
# warden access openbao-platform-admin-login --exec -- <this script> <receipt>
# Results go to the receipt only; any output would fail the warden envelope closed.
# Inside the lane HOME is the warden envelope, so the kubeconfig default is absolute.
# Inside the lane HOME is the warden envelope, so there is no kubeconfig default:
# the caller passes the one cluster route (NK-WP-0058-T07), expanded outside the lane:
# warden access ... --exec -- env NK_KUBECONFIG="$HOME/.kube/config-railiance01" <this script> <receipt>
# Unset, it exits 64 silently before anything runs.
set -euo pipefail
receipt="${1:?receipt path required}"
[[ -n "${NK_KUBECONFIG:-}" ]] || exit 64
exec python3 "$(dirname "$0")/seed-nk-bounded-executor-sender.py" \
--kubeconfig "${RAILIANCE01_KUBECONFIG:-/home/worsch/.kube/config-railiance01}" \
--kubeconfig "$NK_KUBECONFIG" \
--confirm 'SEED nk-bounded-executor CCR-2026-0063' \
--receipt "$receipt" >/dev/null 2>&1

View file

@ -25,7 +25,8 @@ TRADE = (
"declared at tenant-engine/docs/evidence-emission.md"
)
EVIDENCE = Path("/home/worsch/audit-core/docs/evidence/2026-09-15-tenant-engine-sender-mint.json")
KUBECONFIG = os.environ.get("RAILIANCE01_KUBECONFIG", "/home/worsch/.kube/config-railiance01")
# One cluster route (NK-WP-0058-T07), set by the parent wrapper outside the lane.
KUBECONFIG = os.environ.get("NK_KUBECONFIG", "")
RECEIVER_IMAGE = (
"forgejo.coulomb.social/coulomb/audit-core@"
"sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615"
@ -77,6 +78,8 @@ def bao(args: list[str], *, payload: dict | None = None) -> dict:
def kube(args: list[str]) -> subprocess.CompletedProcess:
if not KUBECONFIG: # an empty --kubeconfig would fall back to a second, default route
raise SystemExit(64)
return subprocess.run(
["kubectl", "--kubeconfig", KUBECONFIG, "--request-timeout=30s", *args],
stdout=subprocess.PIPE,

View file

@ -19,9 +19,8 @@ from datetime import datetime, timezone
from pathlib import Path
REGISTRY = "platform/data/workloads/audit-core/senders"
KUBECONFIG = os.environ.get(
"RAILIANCE01_KUBECONFIG", "/home/worsch/.kube/config-railiance01"
)
# One cluster route (NK-WP-0058-T07), set by the parent wrapper outside the lane.
KUBECONFIG = os.environ.get("NK_KUBECONFIG", "")
IMAGE = (
"forgejo.coulomb.social/coulomb/audit-core@"
"sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615"
@ -94,6 +93,8 @@ def bao(args: list[str], *, payload: dict | None = None) -> dict:
def kube(args: list[str], *, input_bytes: bytes | None = None) -> subprocess.CompletedProcess:
if not KUBECONFIG: # an empty --kubeconfig would fall back to a second, default route
raise SystemExit(64)
return subprocess.run(
["kubectl", "--kubeconfig", KUBECONFIG, "--request-timeout=30s", *args],
input=input_bytes,

View file

@ -18,7 +18,7 @@ import uuid
from datetime import datetime, timezone
from pathlib import Path
KUBECONFIG = os.environ.get("RAILIANCE01_KUBECONFIG", str(Path.home() / ".kube/config-railiance01"))
KUBECONFIG = os.environ.get("NK_KUBECONFIG", str(Path.home() / ".kube/config-railiance01"))
IMAGE = (
"forgejo.coulomb.social/coulomb/audit-core@"
"sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615"

View file

@ -12,14 +12,16 @@
# even when the command succeeds. Persist metadata only. Never print a token.
#
# warden access openbao-platform-admin-login --exec -- \
# env RAILIANCE01_KUBECONFIG="$HOME/.kube/config-railiance01" \
# env NK_KUBECONFIG="$HOME/.kube/config-railiance01" \
# BAO_ADDR=http://127.0.0.1:18200 \
# /home/worsch/audit-core/scripts/renew-runtime-lease.sh
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export RAILIANCE01_KUBECONFIG="${RAILIANCE01_KUBECONFIG:-/home/worsch/.kube/config-railiance01}"
export KUBECONFIG="$RAILIANCE01_KUBECONFIG"
# One cluster route (NK-WP-0058-T07). No default: HOME inside the lane is the
# warden envelope. Unset, the kubeconfig check below fails closed with evidence.
export NK_KUBECONFIG="${NK_KUBECONFIG:-}"
export KUBECONFIG="$NK_KUBECONFIG"
# bao.coulomb.social is retired (2026-09-15); the tunnel is the operator path.
export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}"
# Contained login persists the session in $HOME/.vault-token and unsets
@ -68,7 +70,7 @@ if ! command -v kubectl >/dev/null 2>&1; then
_write_evidence failed '{"reason": "kubectl_missing"}'
exit 1
fi
if [[ ! -f "$RAILIANCE01_KUBECONFIG" ]]; then
if [[ ! -f "$NK_KUBECONFIG" ]]; then
_write_evidence failed '{"reason": "kubeconfig_missing"}'
exit 1
fi

View file

@ -181,7 +181,10 @@ def approved_ccr():
def test_ccr_contract_as_drafted_matches_and_refuses_until_approved():
pytest.importorskip("yaml")
SEED.check_ccr(approved_ccr())
proposed, _ = SEED.load_ccr()
# The live CCR was approved after this test was written (railiance-platform
# f3b0c4f), so the unapproved case is a synthetic copy, not the live file.
proposed = approved_ccr()
proposed["status"] = "proposed"
with pytest.raises(SEED.Refused, match="approved_ccr_required"):
SEED.check_ccr(proposed)
partial = approved_ccr()

View file

@ -0,0 +1,61 @@
---
id: ADHOC-2026-11-07
type: workplan
title: "Adopt NetKingdom runbook conventions (NK-WP-0058-T07)"
domain: infotech
repo: audit-core
status: finished
owner: claude-code
topic_slug: audit-evidence-dependencies
created: "2026-11-07"
updated: "2026-11-07"
---
# Adopt NetKingdom runbook conventions (NK-WP-0058-T07)
## Fix the 14 convention-lint findings
```task
id: ADHOC-2026-11-07-T01
status: done
priority: medium
```
Asked by net-kingdom (hub message 9e57fd89): `python3
~/net-kingdom/tools/conventions/lint.py ~/audit-core` reported
`one-cluster-route` 11 and `server-side-apply` 3. It now exits 0 with no
exceptions claimed.
- **One cluster route:** `NK_KUBECONFIG`, default
`~/.kube/config-railiance01`. It replaces `RAILIANCE01_KUBECONFIG`,
`KUBECONFIG_RAILIANCE` and the hard-coded `/home/worsch/.kube/...` defaults.
- The Makefile's `deploy-dry-run` pointed at `config-hosteurope`, which
reaches coulombcore, not railiance01. That was a real wrong-cluster
default, now fixed.
- Scripts that run inside a `warden access --exec` lane, where `HOME` is the
envelope, have no default. The caller passes `NK_KUBECONFIG`, expanded
outside the lane. If it is unset, the scripts fail closed: the seed wrapper
exits 64, the lease renewal writes `kubeconfig_missing` evidence, and the
Python children raise before running kubectl. An empty `--kubeconfig`
would fall back to the default route.
- **Server-side apply:** the runbook in `docs/bounded-executor-receipts.md` and
the Makefile dry-run now use `kubectl apply --server-side`. The live ingress
policy was applied client-side on 2026-11-04 and still carries a
`last-applied-configuration` annotation. A later server-side apply takes it
over, and no live change is made here.
## Unstale the CCR-2026-0063 seed test
```task
id: ADHOC-2026-11-07-T02
status: done
priority: low
```
`test_ccr_contract_as_drafted_matches_and_refuses_until_approved` expected the
live CCR-2026-0063 to be unapproved. It was approved in railiance-platform
f3b0c4f. The refusal case now uses a synthetic `proposed` copy.
Full suite: 374 passed, 46 skipped, 1 failed. The failure is
`test_capability_review_against_live_catalog`, which needs `infospace-bench==0.1.0`
installed. It is environmental and fails the same way without these changes.