diff --git a/Makefile b/Makefile index ee0c589..57b2863 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 8d14694..d042afd 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -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 | diff --git a/deploy/README.md b/deploy/README.md index ea8bd3f..9f8127f 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -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: diff --git a/docs/bounded-executor-receipts.md b/docs/bounded-executor-receipts.md index e9df179..2dae6d2 100644 --- a/docs/bounded-executor-receipts.md +++ b/docs/bounded-executor-receipts.md @@ -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` diff --git a/scripts/attended-mint-tenant-engine-sender.sh b/scripts/attended-mint-tenant-engine-sender.sh index 80a5e5a..ddaf76a 100755 --- a/scripts/attended-mint-tenant-engine-sender.sh +++ b/scripts/attended-mint-tenant-engine-sender.sh @@ -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" diff --git a/scripts/attended-prove-sender-bearer-revocation.sh b/scripts/attended-prove-sender-bearer-revocation.sh index 931c5e4..225cb5b 100755 --- a/scripts/attended-prove-sender-bearer-revocation.sh +++ b/scripts/attended-prove-sender-bearer-revocation.sh @@ -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" diff --git a/scripts/attended-seed-nk-bounded-executor-sender.sh b/scripts/attended-seed-nk-bounded-executor-sender.sh index a6bc850..f619219 100755 --- a/scripts/attended-seed-nk-bounded-executor-sender.sh +++ b/scripts/attended-seed-nk-bounded-executor-sender.sh @@ -3,10 +3,14 @@ # NK-WP-0056-T15, CCR-2026-0063). Founder-run under # warden access openbao-platform-admin-login --exec -- # 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" +# 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 diff --git a/scripts/mint-tenant-engine-sender.py b/scripts/mint-tenant-engine-sender.py index 706a2f0..096c6f2 100755 --- a/scripts/mint-tenant-engine-sender.py +++ b/scripts/mint-tenant-engine-sender.py @@ -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, diff --git a/scripts/prove-sender-bearer-revocation.py b/scripts/prove-sender-bearer-revocation.py index f21953e..8ad79a7 100755 --- a/scripts/prove-sender-bearer-revocation.py +++ b/scripts/prove-sender-bearer-revocation.py @@ -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, diff --git a/scripts/prove-tenant-engine-accept.py b/scripts/prove-tenant-engine-accept.py index 387cd3a..186e7a3 100755 --- a/scripts/prove-tenant-engine-accept.py +++ b/scripts/prove-tenant-engine-accept.py @@ -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" diff --git a/scripts/renew-runtime-lease.sh b/scripts/renew-runtime-lease.sh index 34f309f..ea48430 100755 --- a/scripts/renew-runtime-lease.sh +++ b/scripts/renew-runtime-lease.sh @@ -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 diff --git a/tests/test_seed_nk_bounded_executor_sender.py b/tests/test_seed_nk_bounded_executor_sender.py index bdca39f..a8fb1e1 100644 --- a/tests/test_seed_nk_bounded_executor_sender.py +++ b/tests/test_seed_nk_bounded_executor_sender.py @@ -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() diff --git a/workplans/ADHOC-2026-11-07.md b/workplans/ADHOC-2026-11-07.md new file mode 100644 index 0000000..9625826 --- /dev/null +++ b/workplans/ADHOC-2026-11-07.md @@ -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.