Compare commits
87 commits
csoc-wp-00
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e2809cb1e | |||
| 0c72b3bec8 | |||
| 979b7819df | |||
| b39910e330 | |||
| a15cc888d2 | |||
| dc927d7751 | |||
| c733e86b0f | |||
| 9c74884641 | |||
| 75343cfec1 | |||
| d3c46421b8 | |||
| 169dd3e074 | |||
| 904dcb3ce3 | |||
| d795120f98 | |||
| a70e3b2e9f | |||
| 84a5aa0c92 | |||
| 8434134391 | |||
| bf77257d52 | |||
| 0a1aba9d62 | |||
| cd6cd38356 | |||
| 9ecb123451 | |||
| b7fb7b226f | |||
| 1dd7387cbc | |||
| db0ade1cf3 | |||
| 86c255a932 | |||
| 5d2c0d8564 | |||
| ce1c56dcb3 | |||
| 64530e19e2 | |||
| e9d2b5f39a | |||
| 4a1d6539bf | |||
| 47b3a4a5ca | |||
| 10465cb39c | |||
| 1af6d828f5 | |||
| 3851865c87 | |||
| 170e9b702c | |||
| 3be92daf74 | |||
| 0e1d2b3cfe | |||
| a6fe0aba0f | |||
| 4d6a8dde21 | |||
| 2fdc198b2b | |||
| ab3cf5cdc5 | |||
| a927ec8cc8 | |||
| 308e83fcfb | |||
| 3fb031a47f | |||
| c29408ae67 | |||
| 944b30ad63 | |||
| 0da83472c4 | |||
| 74213d80f0 | |||
| fe5cabecd6 | |||
| fbe012789e | |||
| 9b0999fc37 | |||
| a1ef3fe77a | |||
| 2b9f030c24 | |||
| 91f828b3c1 | |||
| ea2d4632a1 | |||
| 05f654e20f | |||
| fb7d56d3dc | |||
| 72bcad86d7 | |||
| 706461b845 | |||
| 50b66c4e44 | |||
| 390a30a5c1 | |||
|
|
c94b301c2a | ||
| d349815195 | |||
| 367f01cb4b | |||
| c4359794e8 | |||
|
|
602c9dcc1c | ||
| 6953a23e62 | |||
| b6be18b727 | |||
|
|
21022a6e1c | ||
| 7ea9e1c911 | |||
| 815ba1f70c | |||
| 2202e690cc | |||
| de2457cab2 | |||
| f5fc9eaa15 | |||
| 7fb7a1c389 | |||
| 2e95944ba3 | |||
| 4a807932fa | |||
| 22c5695a21 | |||
| bca75d70a0 | |||
| 9499c39676 | |||
| 5f6244ec62 | |||
| f42f8024c0 | |||
| 62dc523d7e | |||
| ae2885728a | |||
| 3f8276e0ea | |||
| 8a57e37ded | |||
|
|
df7b69ff3b | ||
| f713aa90ef |
190 changed files with 20078 additions and 599 deletions
|
|
@ -1,7 +1,7 @@
|
|||
## Stack
|
||||
|
||||
- **Language:** Helm values + Kubernetes manifests, Bash Make targets
|
||||
- **Key deps:** vergabe-teilnahme (Django) and inter-hub releases, shared apps-pg cnpg cluster, SOPS/age; runs against the S2 cluster on railiance01
|
||||
- **Key deps:** vergabe-teilnahme (Django), coulomb-social and reuse-surface releases (inter-hub retired and removed), shared apps-pg cnpg cluster, SOPS/age; runs against the S2 cluster on railiance01
|
||||
|
||||
## Dev Commands
|
||||
|
||||
|
|
@ -13,7 +13,7 @@ make vergabe-dry-run # helm template render, no apply
|
|||
make vergabe-status # pod / svc / ingress / cert state (read-only)
|
||||
make vergabe-deploy # production-touching
|
||||
make vergabe-migrate | vergabe-seed # production-touching (Django management)
|
||||
make check-inter-hub-image-tag # gate: explicit image tag for prod deploys
|
||||
make check-vergabe-image-tag # gate: explicit image tag for prod deploys
|
||||
```
|
||||
|
||||
`*-deploy` / `*-migrate` / `*-seed` change production workloads — operator
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — railiance-apps
|
||||
|
||||
**Domain:** financials
|
||||
**Last synced:** 2026-10-06 14:44 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Coulomb-social persistent personal workspace release
|
||||
Progress: 2/4 done | workplan_id: `79abacf8-361e-51ab-829d-40dec8e142d1`
|
||||
|
||||
**Open tasks:**
|
||||
- ! T03 — Approved upgrade and recovery point `2ce05439`
|
||||
- ! T04 — Personal sign-in and content acceptance `8bc4962a`
|
||||
|
||||
### Deploy and recover the first invited Vergabe company pilot
|
||||
Progress: 2/3 done | workplan_id: `c7fdaa7e-cab8-5d1d-86c2-f1aad7927c57`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Demonstrate restart, isolated restore, rollback and operating ownership `dd069c6d`
|
||||
*(wait: Await populated two-user/document workflow, coherent PostgreSQL/media/issue-state off-host recovery point, isolated restored workflow, rollback evidence and support ownership. September 25 proves database restore and restart only.)*
|
||||
|
||||
## Inbox Hygiene
|
||||
|
||||
**Stale unread:** 2 message(s) older than 3 day(s) — triage at session start.
|
||||
**Missing thread_id:** 2 unread message(s) lack supersession chains.
|
||||
|
||||
---
|
||||
## MCP Orientation (when available)
|
||||
|
||||
If the state-hub MCP server is reachable, call:
|
||||
`get_domain_summary("financials")`
|
||||
This provides richer cross-domain context.
|
||||
If the MCP call fails, use this file as your orientation source.
|
||||
1
.generated-work-records.json
Normal file
1
.generated-work-records.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":1,"repo_slug":"railiance-apps"}
|
||||
8
.generated-work-records.mk
Normal file
8
.generated-work-records.mk
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Explicit opt-in recipe; no environment files or external make includes.
|
||||
CUSTODIAN_SOURCE ?= /home/worsch/the-custodian
|
||||
GENERATED_REPO_railiance-apps ?= railiance-apps
|
||||
.PHONY: generated-work-records generated-work-records-check
|
||||
generated-work-records:
|
||||
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_railiance-apps)"
|
||||
generated-work-records-check:
|
||||
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_railiance-apps)" --check
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
name: Inter-Hub production deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image_tag:
|
||||
description: "Immutable inter-hub image tag to deploy, for example 5101eb5"
|
||||
required: true
|
||||
confirm:
|
||||
description: "Type deploy-inter-hub-production to confirm Railiance01 production deploy"
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: [self-hosted, haskelseed, linux_amd64]
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
INTER_HUB_IMAGE_TAG: ${{ inputs.image_tag }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate dispatch inputs
|
||||
env:
|
||||
CONFIRM: ${{ inputs.confirm }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ "$CONFIRM" != "deploy-inter-hub-production" ]; then
|
||||
echo "Refusing deploy: confirmation text did not match." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$INTER_HUB_IMAGE_TAG" =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
echo "Refusing deploy: image_tag contains unsupported characters." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify image manifest exists
|
||||
run: make check-inter-hub-image
|
||||
|
||||
- name: Helm server dry-run
|
||||
run: make inter-hub-server-dry-run
|
||||
|
||||
- name: Deploy Inter-Hub
|
||||
run: make inter-hub-deploy
|
||||
|
||||
- name: Show release status
|
||||
run: make inter-hub-status
|
||||
|
||||
- name: Run public smoke checks
|
||||
run: make inter-hub-smoke
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
|
||||
# Local State Hub orientation snapshot; authored work records remain tracked.
|
||||
/.custodian-brief.md
|
||||
110
Makefile
110
Makefile
|
|
@ -24,14 +24,6 @@ COULOMB_SOCIAL_IMAGE_REPOSITORY ?= forgejo.coulomb.social/coulomb/coulomb-social
|
|||
COULOMB_SOCIAL_INGRESS ?= manifests/coulomb-social-ingress.yaml
|
||||
COULOMB_SOCIAL_ENV_SECRET ?= coulomb-social-env
|
||||
|
||||
INTER_HUB_RELEASE ?= inter-hub
|
||||
INTER_HUB_NAMESPACE ?= inter-hub
|
||||
INTER_HUB_CHART ?= charts/inter-hub
|
||||
INTER_HUB_VALUES ?= helm/inter-hub-values.yaml
|
||||
INTER_HUB_IMAGE_REPOSITORY ?= forgejo.coulomb.social/coulomb/inter-hub
|
||||
INTER_HUB_IMAGE_TAG ?=
|
||||
INTER_HUB_BASE_URL ?= https://hub.coulomb.social
|
||||
|
||||
REUSE_RELEASE ?= reuse
|
||||
REUSE_NAMESPACE ?= reuse
|
||||
REUSE_CHART ?= charts/reuse-surface
|
||||
|
|
@ -50,9 +42,8 @@ POLICY_NEXUS_SOURCE_SET_DIGEST ?= $(shell python3 -c 'import json; print(json.lo
|
|||
POLICY_NEXUS_REVISION ?=
|
||||
|
||||
COULOMBCORE_KUBECONFIG ?= $(HOME)/.kube/config
|
||||
RAILIANCE01_KUBECONFIG ?= $(HOME)/.kube/config-hosteurope
|
||||
RAILIANCE01_KUBECONFIG ?= $(HOME)/.kube/config-railiance01
|
||||
PRODUCTION_KUBECONFIG ?= $(COULOMBCORE_KUBECONFIG)
|
||||
INTER_HUB_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
|
||||
REUSE_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
|
||||
FORGEJO_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
|
||||
FORGEJO_RELEASE ?= forgejo
|
||||
|
|
@ -63,6 +54,7 @@ FORGEJO_CHART_VERSION ?= 12.5.0
|
|||
FORGEJO_VALUES ?= helm/forgejo-values.yaml
|
||||
FORGEJO_REGISTRY_VALUES ?= helm/forgejo-registry-values.yaml
|
||||
FORGEJO_SECRETS ?= helm/forgejo-secrets.sops.yaml
|
||||
FORGEJO_SECRET_SCAN_TEMPLATE ?= manifests/forgejo-secret-scan-template.yaml
|
||||
FORGEJO_INGRESS ?= manifests/forgejo-ingress.yaml
|
||||
FORGEJO_SSH_NODEPORT ?= manifests/forgejo-ssh-nodeport.yaml
|
||||
FORGEJO_BASE_URL ?= https://forgejo.coulomb.social
|
||||
|
|
@ -74,8 +66,6 @@ FORGEJO_MAILER_EXTERNAL_SECRET ?= manifests/forgejo-mailer-externalsecret.yaml
|
|||
REUSE_RUNTIME_EXTERNAL_SECRET ?= manifests/reuse-surface-runtime-externalsecret.yaml
|
||||
PLATFORM_REPO ?= $(HOME)/railiance-platform
|
||||
ESO_CHART_VERSION ?= 0.16.1
|
||||
INTER_HUB_IMAGE_REF = $(INTER_HUB_IMAGE_REPOSITORY):$(INTER_HUB_IMAGE_TAG)
|
||||
INTER_HUB_IMAGE_SET_ARG = $(if $(strip $(INTER_HUB_IMAGE_TAG)),--set image.tag=$(INTER_HUB_IMAGE_TAG),)
|
||||
|
||||
CORE_HUB_RELEASE ?= core-hub
|
||||
CORE_HUB_NAMESPACE ?= core-hub
|
||||
|
|
@ -138,16 +128,6 @@ check-coulombcore-kubeconfig: ## Verify CoulombCore kubeconfig and k3s API tunne
|
|||
|
||||
check-production-kubeconfig: check-coulombcore-kubeconfig ## Verify production app cluster API (CoulombCore)
|
||||
|
||||
check-inter-hub-image-tag: ## Require an explicit inter-hub image tag for production deploys
|
||||
@test -n "$(INTER_HUB_IMAGE_TAG)" || { \
|
||||
echo "Set INTER_HUB_IMAGE_TAG=<sha> for inter-hub production deploys." >&2; \
|
||||
echo "Example: INTER_HUB_IMAGE_TAG=91037a4 make inter-hub-deploy" >&2; \
|
||||
exit 1; \
|
||||
}
|
||||
|
||||
check-inter-hub-image: check-inter-hub-image-tag ## Verify the inter-hub OCI image tag exists before deploy
|
||||
tools/check-oci-image.sh "$(INTER_HUB_IMAGE_REF)"
|
||||
|
||||
check-core-hub-image-tag: ## Require an explicit core-hub image tag for production deploys
|
||||
@test -n "$(CORE_HUB_IMAGE_TAG)" || { \
|
||||
echo "Set CORE_HUB_IMAGE_TAG=<sha> for core-hub production deploys." >&2; \
|
||||
|
|
@ -314,43 +294,6 @@ coulomb-social-env-secret: ## Create/patch coulomb-social-env (SECRET_KEY, DATAB
|
|||
|
||||
coulomb-social-env-secret-dry-run: ## Plan coulomb-social-env keys without applying
|
||||
@bash tools/create-coulomb-social-env-secret.sh --dry-run $(COULOMB_SOCIAL_ENV_SECRET_ARGS)
|
||||
##@ Inter-Hub
|
||||
|
||||
inter-hub-render-baseline: ## Render checked-in inter-hub values for chart validation only
|
||||
helm template $(INTER_HUB_RELEASE) $(INTER_HUB_CHART) \
|
||||
--namespace $(INTER_HUB_NAMESPACE) \
|
||||
-f $(INTER_HUB_VALUES)
|
||||
|
||||
inter-hub-dry-run: check-railiance01-kubeconfig check-inter-hub-image-tag ## helm template render with an explicit production image tag
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" helm template $(INTER_HUB_RELEASE) $(INTER_HUB_CHART) \
|
||||
--namespace $(INTER_HUB_NAMESPACE) \
|
||||
-f $(INTER_HUB_VALUES) $(INTER_HUB_IMAGE_SET_ARG)
|
||||
|
||||
inter-hub-server-dry-run: check-railiance01-kubeconfig check-inter-hub-image ## Helm server dry-run inter-hub upgrade on Railiance01
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" helm upgrade --install $(INTER_HUB_RELEASE) $(INTER_HUB_CHART) \
|
||||
--namespace $(INTER_HUB_NAMESPACE) --create-namespace \
|
||||
-f $(INTER_HUB_VALUES) $(INTER_HUB_IMAGE_SET_ARG) --dry-run=server --timeout 5m
|
||||
|
||||
inter-hub-deploy: check-railiance01-kubeconfig check-inter-hub-image ## Deploy / upgrade inter-hub Helm release on Railiance01
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" helm upgrade --install $(INTER_HUB_RELEASE) $(INTER_HUB_CHART) \
|
||||
--namespace $(INTER_HUB_NAMESPACE) --create-namespace \
|
||||
-f $(INTER_HUB_VALUES) $(INTER_HUB_IMAGE_SET_ARG) --wait --timeout 5m
|
||||
|
||||
inter-hub-status: check-railiance01-kubeconfig ## Show inter-hub pod / svc / ingress / cert state on Railiance01
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" kubectl get pods,svc,ingress,certificate -n $(INTER_HUB_NAMESPACE) -l app=$(INTER_HUB_RELEASE) --ignore-not-found
|
||||
|
||||
inter-hub-release-info: check-railiance01-kubeconfig ## Show inter-hub Helm history, values, deployment, and pods
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" helm history $(INTER_HUB_RELEASE) -n $(INTER_HUB_NAMESPACE)
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" helm get values $(INTER_HUB_RELEASE) -n $(INTER_HUB_NAMESPACE)
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" kubectl describe deploy/$(INTER_HUB_RELEASE) -n $(INTER_HUB_NAMESPACE)
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" kubectl get pods -n $(INTER_HUB_NAMESPACE) -o wide
|
||||
|
||||
inter-hub-smoke: ## Verify public inter-hub v2 route and OpenAPI surface after rollout
|
||||
INTER_HUB_BASE_URL="$(INTER_HUB_BASE_URL)" tools/inter-hub-smoke.sh
|
||||
|
||||
inter-hub-logs: check-railiance01-kubeconfig ## Tail inter-hub app logs from Railiance01
|
||||
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" kubectl logs -n $(INTER_HUB_NAMESPACE) -l app=$(INTER_HUB_RELEASE) -f --tail=50
|
||||
|
||||
##@ Core Hub
|
||||
|
||||
core-hub-helm-adopt: check-production-kubeconfig ## Annotate live Core Hub resources for Helm ownership
|
||||
|
|
@ -469,7 +412,10 @@ forgejo-server-dry-run: check-railiance01-kubeconfig ## Helm server dry-run Forg
|
|||
-f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) \
|
||||
--dry-run=server --timeout 5m
|
||||
|
||||
forgejo-deploy: check-railiance01-kubeconfig forgejo-mailer-es-deploy ## Deploy / upgrade Forgejo on railiance01
|
||||
forgejo-secret-scan-template-deploy: check-railiance01-kubeconfig ## Install the immutable, non-secret receive-hook template
|
||||
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply --server-side -f $(FORGEJO_SECRET_SCAN_TEMPLATE)
|
||||
|
||||
forgejo-deploy: check-railiance01-kubeconfig forgejo-mailer-es-deploy forgejo-secret-scan-template-deploy ## Deploy / upgrade Forgejo on railiance01
|
||||
@test -n "$$(KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get secret forgejo-mailer -n $(FORGEJO_NAMESPACE) -o name 2>/dev/null)" || { \
|
||||
echo "Missing secret forgejo-mailer in $(FORGEJO_NAMESPACE)." >&2; \
|
||||
echo "Ensure ExternalSecret is Ready and OpenBao role external-secrets-forgejo is configured." >&2; \
|
||||
|
|
@ -510,6 +456,12 @@ forgejo-verify: check-railiance01-kubeconfig ## T05 acceptance: smoke, TLS, DB,
|
|||
forgejo-operator-bootstrap: ## Idempotent operator user + SSH key bootstrap (no secrets in Git)
|
||||
FORGEJO_BASE_URL="$(FORGEJO_BASE_URL)" tools/forgejo-operator-bootstrap.sh
|
||||
|
||||
forgejo-breakglass-seal: check-railiance01-kubeconfig ## Founder: rotate break-glass admin password into OpenBao (interactive, production-touching)
|
||||
FORGEJO_KUBECONFIG="$(FORGEJO_KUBECONFIG)" FORGEJO_NAMESPACE="$(FORGEJO_NAMESPACE)" tools/forgejo-breakglass-seal.sh
|
||||
|
||||
forgejo-breakglass-verify: ## Founder: verify one break-glass sign-in and write value-free evidence
|
||||
FORGEJO_BASE_URL="$(FORGEJO_BASE_URL)" tools/forgejo-breakglass-verify.sh
|
||||
|
||||
forgejo-runner-registration-sops-bootstrap: check-railiance01-kubeconfig ## Capture cluster runner token into SOPS (operator age key)
|
||||
KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-runner-registration-sops-bootstrap.sh
|
||||
|
||||
|
|
@ -642,4 +594,40 @@ help: ## Show this help
|
|||
/^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
|
||||
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
|
||||
|
||||
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status apps-pg-restore-drill check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image check-vergabe-image-tag check-vergabe-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret check-coulomb-social-image-tag coulomb-social-dry-run coulomb-social-deploy coulomb-social-ingress-deploy coulomb-social-status coulomb-social-logs coulomb-social-env-secret coulomb-social-env-secret-dry-run eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status reuse-webhook-smoke policy-nexus-check-binding policy-nexus-check-image policy-nexus-render policy-nexus-server-dry-run policy-nexus-deploy policy-nexus-status policy-nexus-smoke policy-nexus-rollback help
|
||||
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status apps-pg-restore-drill check-railiance01-kubeconfig check-vergabe-image-tag check-vergabe-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret check-coulomb-social-image-tag coulomb-social-dry-run coulomb-social-deploy coulomb-social-ingress-deploy coulomb-social-status coulomb-social-logs coulomb-social-env-secret coulomb-social-env-secret-dry-run eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status reuse-webhook-smoke policy-nexus-check-binding policy-nexus-check-image policy-nexus-render policy-nexus-server-dry-run policy-nexus-deploy policy-nexus-status policy-nexus-smoke policy-nexus-rollback help
|
||||
|
||||
.PHONY: forgejo-secret-scan-template-deploy
|
||||
|
||||
|
||||
##@ Isolated Forgejo source release preparation (no deployment/publication)
|
||||
FORGEJO_BUILD_WORKSPACE ?= /tmp/railiance-apps-forgejo-release
|
||||
FORGEJO_SOURCE_ARCHIVE ?=
|
||||
FORGEJO_GO_ARCHIVE ?=
|
||||
FORGEJO_NODE_ARCHIVE ?=
|
||||
FORGEJO_BUILD_GOMODCACHE ?=
|
||||
FORGEJO_BUILD_GOCACHE_SEED ?=
|
||||
FORGEJO_BUILD_NPM_CACHE_SEED ?=
|
||||
|
||||
.PHONY: forgejo-release-prepare forgejo-release-acquire-npm forgejo-release-build forgejo-release-verify forgejo-release-container
|
||||
forgejo-release-prepare: ## Verify archives/patch/locks and prepare a new isolated workspace
|
||||
/usr/bin/python3 -I tools/forgejo-release-build.py prepare --workspace "$(FORGEJO_BUILD_WORKSPACE)" --source-archive "$(FORGEJO_SOURCE_ARCHIVE)" --go-archive "$(FORGEJO_GO_ARCHIVE)" --node-archive "$(FORGEJO_NODE_ARCHIVE)" --module-cache "$(FORGEJO_BUILD_GOMODCACHE)" $(if $(FORGEJO_BUILD_GOCACHE_SEED),--go-cache-seed "$(FORGEJO_BUILD_GOCACHE_SEED)") $(if $(FORGEJO_BUILD_NPM_CACHE_SEED),--npm-cache-seed "$(FORGEJO_BUILD_NPM_CACHE_SEED)")
|
||||
|
||||
forgejo-release-acquire-npm: ## Explicit public registry dependency acquisition; ignores lifecycle scripts
|
||||
/usr/bin/python3 -I tools/forgejo-release-build.py acquire-npm --workspace "$(FORGEJO_BUILD_WORKSPACE)"
|
||||
|
||||
forgejo-release-build: ## Offline full frontend, bindata and static backend via upstream targets
|
||||
/usr/bin/python3 -I tools/forgejo-release-build.py build --workspace "$(FORGEJO_BUILD_WORKSPACE)"
|
||||
|
||||
forgejo-release-verify: ## Verify all embedded assets, static executable and new CLI shape offline
|
||||
/usr/bin/python3 -I tools/forgejo-release-build.py verify --workspace "$(FORGEJO_BUILD_WORKSPACE)"
|
||||
|
||||
forgejo-release-container: ## Build/verify a deterministic local rootless OCI archive; never publish or deploy
|
||||
/usr/bin/python3 -I tools/forgejo-release-build.py container --workspace "$(FORGEJO_BUILD_WORKSPACE)"
|
||||
|
||||
|
||||
# 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
|
||||
|
|
|
|||
33
SCOPE.md
33
SCOPE.md
|
|
@ -270,3 +270,36 @@ keywords: [operator, runbook, sops, cnpg, dry-run, smoke-test, deployment]
|
|||
9. Run `make check-tools` before deploy work. Run
|
||||
`SOPS_SENTINEL=<encrypted-file> make check-sops` when app release work
|
||||
touches encrypted SOPS files.
|
||||
|
||||
|
||||
## Generated work-record index
|
||||
|
||||
This repository opts into staged-source WORK-RECORDS.md generation through
|
||||
`.generated-work-records.json` and the isolated `.generated-work-records.mk`
|
||||
recipe. `make generated-work-records` reuses the State Hub renderer through
|
||||
the Custodian adapter; `make generated-work-records-check` verifies byte-exact
|
||||
agreement with staged Markdown. The pre-commit hook uses the isolated recipe,
|
||||
preserves unstaged index edits by refusing them, and retains the generated-only
|
||||
commit guard and existing security checks. It does not contact the hub, alter
|
||||
workplan statuses or grant deployment authority. This consumes shared tooling;
|
||||
it does not make this repository the provider of the generator capability.
|
||||
|
||||
## Local orientation brief policy — 2026-10-19
|
||||
|
||||
`.custodian-brief.md` is a local, ignored State Hub orientation snapshot. Its
|
||||
existing bytes remain available for offline session startup; refreshing it does
|
||||
not require a Git commit. Authored workplans and the generated WORK-RECORDS index
|
||||
retain their existing source and staged-generation contracts. No runtime or
|
||||
credential change follows from this repository hygiene policy.
|
||||
|
||||
|
||||
## Synthetic pilot sizing follow-up
|
||||
|
||||
Application-owner local evidence and configurable workloads live in
|
||||
`vergabe-teilnahme/docs/evidence/2026-10-19-synthetic-sizing/` and that repository's
|
||||
`config/synthetic-pilot*.json`. RAPPS-WP-0014-T04 owns fresh exact release/hardware
|
||||
metadata and a co-timed isolated replay/shared-contention assessment feeding
|
||||
CUST-WP-0071-T02 and RESOURCE-WP-0008-T02. Routine read-only capture/preparation is
|
||||
authorized; no benchmark writes to customer datasets or unreviewed deployment
|
||||
changes follow from the local synthetic result. The existing T03 human/recovery
|
||||
gate remains separate and unchanged.
|
||||
|
|
|
|||
2
STATE.md
2
STATE.md
|
|
@ -44,7 +44,7 @@ export KUBECONFIG=~/.kube/config
|
|||
make cnpg-backup-status
|
||||
|
||||
# railiance01 activity
|
||||
export KUBECONFIG=~/.kube/config-hosteurope
|
||||
export KUBECONFIG=~/.kube/config-railiance01
|
||||
kubectl -n activity-core get externalsecret actcore-backup-offsite
|
||||
# inventory should list Daily CNPG Option A Backup enabled
|
||||
```
|
||||
|
|
|
|||
|
|
@ -11,8 +11,12 @@
|
|||
| workplan | RAPPS-WP-0001 | finished | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md |
|
||||
| workplan | RAPPS-WP-0002 | finished | — | workplans/RAPPS-WP-0002-railiance01-activity-core-backup-automation.md |
|
||||
| workplan | RAPPS-WP-0003 | finished | — | workplans/RAPPS-WP-0003-policy-nexus-production-binding.md |
|
||||
| workplan | RAPPS-WP-0014 | blocked | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| workplan | RAPPS-WP-0014 | active | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| workplan | RAPPS-WP-0015 | active | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| workplan | RAPPS-WP-0016 | active | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| workplan | RAPPS-WP-0017 | finished | — | workplans/RAPPS-WP-0017-reuse-private-reader-release.md |
|
||||
| workplan | RAPPS-WP-0018 | finished | — | workplans/RAPPS-WP-0018-wave2-resource-right-sizing.md |
|
||||
| workplan | RAPPS-WP-0019 | finished | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
| task | RAPPS-WP-0001-T01 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md |
|
||||
| task | RAPPS-WP-0001-T02 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md |
|
||||
| task | RAPPS-WP-0001-T03 | done | — | workplans/RAPPS-WP-0001-cnpg-backup-scheduledbackup-coverage.md |
|
||||
|
|
@ -32,7 +36,30 @@
|
|||
| task | RAPPS-WP-0014-T01 | done | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| task | RAPPS-WP-0014-T02 | done | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| task | RAPPS-WP-0014-T03 | wait | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| task | RAPPS-WP-0014-T04 | done | — | workplans/RAPPS-WP-0014-vergabe-invited-pilot.md |
|
||||
| task | RAPPS-WP-0015-T01 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T02 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T03 | wait | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T03 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T04 | wait | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T05 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T06 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T07 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0015-T08 | done | — | workplans/RAPPS-WP-0015-coulomb-personal-content-release.md |
|
||||
| task | RAPPS-WP-0016-T01 | done | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T02 | wait | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T03 | wait | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T04 | wait | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T05 | wait | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T06 | done | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T07 | progress | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T08 | progress | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0016-T09 | progress | — | workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md |
|
||||
| task | RAPPS-WP-0017-T01 | done | — | workplans/RAPPS-WP-0017-reuse-private-reader-release.md |
|
||||
| task | RAPPS-WP-0017-T02 | done | — | workplans/RAPPS-WP-0017-reuse-private-reader-release.md |
|
||||
| task | RAPPS-WP-0017-T03 | done | — | workplans/RAPPS-WP-0017-reuse-private-reader-release.md |
|
||||
| task | RAPPS-WP-0018-T01 | done | — | workplans/RAPPS-WP-0018-wave2-resource-right-sizing.md |
|
||||
| task | RAPPS-WP-0019-T01 | done | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
| task | RAPPS-WP-0019-T02 | done | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
| task | RAPPS-WP-0019-T03 | done | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
| task | RAPPS-WP-0019-T04 | done | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
| task | RAPPS-WP-0019-T05 | done | — | workplans/RAPPS-WP-0019-product-neutral-forge-object-names.md |
|
||||
|
|
|
|||
20
bindings/policy-nexus-production-candidate.json
Normal file
20
bindings/policy-nexus-production-candidate.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"schema_version": "policy-nexus-production-binding/v1",
|
||||
"status": "prepared-unapproved",
|
||||
"approved_on": null,
|
||||
"rapp_repo": "rapp-policy-nexus",
|
||||
"reef": "reef-railiance",
|
||||
"rail": "rail-kubernetes",
|
||||
"namespace": "policy-nexus",
|
||||
"release": "policy-nexus",
|
||||
"hostname": "policy.coulomb.social",
|
||||
"image_repository": "forgejo.coulomb.social/coulomb/policy-nexus",
|
||||
"image_digest": "sha256:4e0db6a57eb00f577159aa6ee10f910dd67b03fd0541c6ce849f5426fdb99d06",
|
||||
"publication_manifest_digest": "ba53cc4900e6bb9ab8aa4bac4e58cb5fe7a03d656bafb3f9cb3d9c8ebff7843a",
|
||||
"source_inventory_digest": "40d9e8852fe4c9e75b71f371d0fdae38664b068e7498711b0ac5671d0f41efb5",
|
||||
"source_set_digest": "15bda2e332285afc862df8974cf29de403737df5073fa362adfc6d203cf2f256",
|
||||
"residual_risk_owner": "the-custodian",
|
||||
"proposal_id": "POLICY-PUB-20261011-SECURITY-LAYER-V08-R1",
|
||||
"publication_source_revision": "b64a0c52ec1ea8d1efa99dd7f870e580d7c60df8",
|
||||
"source_lock_digest": "99c695f46a54ead4cfe8f560f3648bf4db19404fe198b156c740115c97d3ed40"
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"schema_version": "policy-nexus-production-binding/v1",
|
||||
"status": "release-approved",
|
||||
"approved_on": "2026-09-01",
|
||||
"approved_on": "2026-10-19",
|
||||
"rapp_repo": "rapp-policy-nexus",
|
||||
"reef": "reef-railiance",
|
||||
"rail": "rail-kubernetes",
|
||||
|
|
@ -9,9 +9,15 @@
|
|||
"release": "policy-nexus",
|
||||
"hostname": "policy.coulomb.social",
|
||||
"image_repository": "forgejo.coulomb.social/coulomb/policy-nexus",
|
||||
"image_digest": "sha256:23df0ab3dc80d2a92b8058eeb572c222ceb05d17434f173a24142b2450ab3cea",
|
||||
"publication_manifest_digest": "09f9f7edcc46e31d4b3f73b62d16fdf17808ae1f893f312242b7b9e2037319e0",
|
||||
"source_inventory_digest": "9658803bc79d19cef1a9c842d1a8c161f1998d59ce6e4bc4df3c88aba2f4c9c4",
|
||||
"source_set_digest": "03f04902a3559099433795453829d75470aa1490dd243b42e7703b6eab87772f",
|
||||
"residual_risk_owner": "the-custodian"
|
||||
"image_digest": "sha256:214b122c3d175563f114552fa1826dac9642433be019c736c3265d0a3ef379f1",
|
||||
"publication_manifest_digest": "8c74b4e414f7760429600b7ba0b3627fcfe438417bfe6695f55acfa6905d352e",
|
||||
"source_inventory_digest": "e84453b6b411a7d66d2b5efbf083cd0033441d6b12983326442933da87bf61fd",
|
||||
"source_set_digest": "b00535063c56ede08f0a292b301a47add18bf70f81904bf3f434c8faa5ebe682",
|
||||
"residual_risk_owner": "the-custodian",
|
||||
"proposal_id": "POLICY-PUB-20261019-BUILD-MODE-GUARDRAIL-R1",
|
||||
"publication_source_revision": "9fabcce1cb4c83165bcaba8be58d76788b4d87a1",
|
||||
"source_lock_digest": "c150c6c0745441b4b69884ecc6cf55e97db26080fcf4c2224775b1f755580529",
|
||||
"approval_ref": "conversation:call_5157a09329d34746a3fdf857d5a47b8d:item0",
|
||||
"verified_helm_revision": 10,
|
||||
"native_verification_ref": "docs/evidence/2026-10-19-build-mode-publication/receipt.json"
|
||||
}
|
||||
|
|
|
|||
172
build/forgejo/README.md
Normal file
172
build/forgejo/README.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
# Forgejo release preparation (build only)
|
||||
|
||||
The Apps Makefile targets package the digest-bound 49-path Forgejo 11.0.3
|
||||
source candidate in `forgejo-security-extension.patch`. It combines the original
|
||||
three-group any-of gate, private secret-file input, private source-operation DB
|
||||
primitives and the producer-owned Access Engine client in the OIDC callback.
|
||||
Coordinator source tests and complete release checks are recorded separately;
|
||||
independent owner review and operational admission of this revision remain open. They never deploy, push, change default values or choose
|
||||
an operational image. Third-party application source is expanded only into a new
|
||||
`/tmp` workspace; this repository retains a release input lock and exact reviewed
|
||||
patch, not a second source-code tree. Upstream source ownership and publication
|
||||
remain separate owner work.
|
||||
|
||||
Supply explicit public input archives/cache paths to `make forgejo-release-prepare`:
|
||||
`FORGEJO_SOURCE_ARCHIVE`, `FORGEJO_GO_ARCHIVE`, `FORGEJO_NODE_ARCHIVE`,
|
||||
`FORGEJO_BUILD_GOMODCACHE`, optionally `FORGEJO_BUILD_GOCACHE_SEED` and
|
||||
`FORGEJO_BUILD_NPM_CACHE_SEED`. Every archive, patch, source output, dependency
|
||||
lock and selected host compiler tool is digest-bound by `inputs.lock.json`.
|
||||
Go's official checksums and Node's official SHASUMS must be independently checked
|
||||
when acquiring those public archives. The source archive is official Forgejo
|
||||
v11.0.3; archive PAX commit b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237.
|
||||
|
||||
The workspace must not exist. Existing trials are preserved, never restarted
|
||||
because an observer timed out. The recipe's `process.json` records the exact
|
||||
active child PID, phase and command, then completion; logs and receipts stay in
|
||||
the workspace. Do not run two phases against the same workspace concurrently.
|
||||
|
||||
`make forgejo-release-acquire-npm` explicitly acquires only public registry
|
||||
packages, with fresh HOME/config paths, integrity-bound lockfile and lifecycle
|
||||
scripts disabled. With the cache ready, `make forgejo-release-build` reruns
|
||||
`npm ci --offline --ignore-scripts`, Go module verification, upstream frontend
|
||||
compilation and upstream generation/static backend targets inside a network
|
||||
namespace without connectivity. It uses the exact Go/Node distributions, local
|
||||
compiler inputs, `bindata timetzdata sqlite sqlite_unlock_notify` plus upstream
|
||||
static target's `netgo osusergo`, explicit Make GOFLAGS for trimpath/buildvcs and
|
||||
fixed file/directory timestamps. Build tags retain the upstream rootless build
|
||||
profile. `make forgejo-release-verify` compares every embedded public/options/
|
||||
template file to actual generated source bytes, checks the extension CLI and
|
||||
checks the ELF has no dynamic interpreter. These are local artifact checks,
|
||||
not application login, DB, native rollout or architecture acceptance.
|
||||
|
||||
For a **local container build** only, first acquire the locked public amd64
|
||||
base with an empty Docker client config and the explicit local socket. Then
|
||||
`make forgejo-release-container` uses that exact digest as a build input and a
|
||||
minimal context containing only the verified executable and generated Dockerfile.
|
||||
It directly exports a local OCI archive with `rewrite-timestamp=true`, no-cache,
|
||||
no image load or push. SOURCE_DATE_EPOCH alone does not fix COPY parent-directory
|
||||
timestamps. The image epoch is max(source epoch,ceil immutable base created
|
||||
instant), so the original base layers remain exact while new metadata is stable.
|
||||
The recipe verifies every OCI blob, original base layer prefix, rootless runtime
|
||||
configuration, provenance labels and exact final executable. Two no-cache exports
|
||||
must reproduce the frozen output image and archive digests before claiming
|
||||
reproducibility. The actual local proof receipts are in the accompanying source
|
||||
proposal packet, not an admission of the resulting artifact.
|
||||
|
||||
There is no deployment tag, registry publication, RUN directive, privileged
|
||||
container or Docker host/secret mount. Public base acquisition and a standard
|
||||
local Docker/buildx daemon supporting OCI export/layer timestamp rewriting are
|
||||
explicit prerequisites. An unavailable feature is a retained blocker; do not
|
||||
switch to a privileged builder or claim a successful image from a backend-only
|
||||
binary. Existing contexts/output archives refuse overwrite. Reconcile an actual
|
||||
failed export explicitly, retaining original metadata/logs/artifacts; observation
|
||||
timeouts never justify another build. Older prototype load/save behavior and
|
||||
its config-versus-manifest-ID issue are retained in the source trial history,
|
||||
not shipped as a production continuation.
|
||||
|
||||
The static host toolchain is pinned for this Linux amd64 review estate. Broader
|
||||
cross-host reproducibility or a different architecture/compiler/libc is a new
|
||||
input revision, not an implicit fallback. The glibc static-linker's optional
|
||||
SQLite `dlopen` diagnostic is retained; no SQLite-extension runtime compatibility
|
||||
or production platform admission is inferred from ELF inspection or asset checks.
|
||||
An accepted owner image/source/backend, authenticated access-engine decision,
|
||||
private secret-input boundary, type/case/descriptor re-freeze, current operation/
|
||||
inverse sessions, durable journal and application/Helm writer exclusion are
|
||||
separate gates. Helm omission/rollback does not remove persisted OAuth source.
|
||||
The existing case gate is compatibility code, not a single-PDP exception.
|
||||
|
||||
## KeyCape identity and callback admission
|
||||
|
||||
The callback independently verifies the ID token against owner-admitted public
|
||||
keys, algorithms, issuer, audience and token type. Public trust is loaded from
|
||||
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE`; only an absolute private file reference
|
||||
is used for the PDP caller token. No admission file or operational keys are
|
||||
supplied by this build. New fields `tenant_source_claim` and
|
||||
`max_authentication_age` are mandatory. The KeyCape v0.3 profile uses
|
||||
`assurance_claim: assurance` and `tenant_source_claim: tenant_source`. Maximum
|
||||
identity age and maximum authentication age are separate positive durations
|
||||
chosen by the owner, not implicit defaults.
|
||||
|
||||
The signed assurance object preserves `level`, `methods`, boolean `mfa`,
|
||||
`source: key-cape` and integer `at`. Its authentication time must not follow
|
||||
issuance or the current time, and must satisfy the admitted authentication-age
|
||||
bound. Identity validity ends at the earliest token expiration, issued-at age
|
||||
bound or authentication-age bound. Directory, registration and default tenant
|
||||
provenance remain distinct in the request. Mutable UserInfo cannot replace
|
||||
these authenticated facts. Current UserInfo subject and the Goth subject must
|
||||
match the independently verified token; the three required group alternatives
|
||||
remain policy input.
|
||||
|
||||
The parser also represents valid lower-assurance facts. Application policy
|
||||
continues to decide authorization. The Check client and unchanged canonical
|
||||
signature bytes are a generated source slice of Access Engine ca0cb757; Forgejo
|
||||
adds no policy evaluator. Exact caller/policy/registry/signer/request/lifetime
|
||||
pins, an authenticated HTTPS connection and a current ALLOW are required.
|
||||
Synthetic callback fixtures test ALLOW, DENY and adverse cases, including an ID
|
||||
token serialized by the actual pinned KeyCape handler. Those fixtures do not
|
||||
prove an actual browser, factor, custody or native rollout.
|
||||
|
||||
The current integration covers this guarded OIDC callback. Ongoing application
|
||||
HTTP/API/Git/SSH paths, current operation/inverse sessions, durable private
|
||||
evidence and retention, deployed PostgreSQL/custody acceptance and exclusion of all release
|
||||
writers remain open in RAPPS-WP-0016-T08/T09 and NK-WP-0048-T43. An old image
|
||||
approval or source test result cannot admit this candidate.
|
||||
|
||||
## PostgreSQL operation-source verification
|
||||
|
||||
The source primitive has now run on a disposable PostgreSQL 16.14 instance using
|
||||
the actual Forgejo engine, model schemas and OAuth configuration types. Tests
|
||||
cover default, explicit-public and named schemas, exact source restoration, drift
|
||||
and linked-account refusals, trigger rollback, uncooperative SQL writer exclusion
|
||||
and concurrent preparation. Independent client processes and an actual server
|
||||
restart verify prepared/applied/inverse journal recovery. The test-only
|
||||
`source_operation_postgres` tag selects the integration tests; no runtime route,
|
||||
credential, session or authority callback is added. The companion Custodian
|
||||
`forgejo-postgres-source-r1` packet holds the isolated fixture, commands, source
|
||||
pins, counterexamples and original failed attempts.
|
||||
|
||||
Actual PostgreSQL diagnostics exposed a tracing-hook composition crash. Tracing
|
||||
now publishes its task context to the shared XORM hook context so later hooks
|
||||
retain it. Ordinary and private SQLite traces and actual PostgreSQL failure
|
||||
queries exercise this path. Controlled copies without the table lock or private
|
||||
query marker fail their respective regressions. These are source/database
|
||||
mechanics checks; owner admission, current authenticated operation/inverse
|
||||
sessions, runtime/backup custody, retention, every release writer and native
|
||||
deployed-database acceptance remain separate obligations. The previous frozen
|
||||
release is historical and does not contain this context correction.
|
||||
|
||||
|
||||
## Current-token callback source continuation
|
||||
|
||||
The 49-path candidate additionally vendors the unchanged, producer-generated
|
||||
KeyCape current-token adapter from `08c083ba`; no consumer token registry or
|
||||
handwritten copy is introduced. `current_token` (explicit HTTPS issuer, resource
|
||||
audience, owning client, CA/JWKS/private-client-file references and maximum token
|
||||
age) and `application_tenant` are mandatory in the independently admitted
|
||||
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE` for this guarded callback profile. A signed
|
||||
caller cannot select the application's tenant. No operational configuration or
|
||||
credential is supplied by the build.
|
||||
|
||||
The callback requires the online KeyCape human ID token's exact RS256 `at_hash`
|
||||
access-grant binding before private caller use. Access-only `jti`/`scope` cannot
|
||||
substitute for an ID token. It joins independently verified subject/tenant/type,
|
||||
issuance, assurance/authentication time and tenant provenance to a fresh issuer
|
||||
observation before the PDP, then checks current status again after the decision.
|
||||
Source tests refuse mixed, revoked, suspended, stale, malformed and unavailable
|
||||
inputs; actual HTTPS issuer PKCE/discovery/UserInfo/status/revocation and real
|
||||
Forgejo provider/callback run in five isolated consumer processes. MFA session
|
||||
seeds and PDP admissions are synthetic. Complete release build/asset/OCI evidence
|
||||
for the preceding patch remains historical; this revision requires its own build
|
||||
and independent owner/package/type/custody admission before operational use.
|
||||
|
||||
The user selected current KeyCape checks on every ongoing API/Git/SSH operation
|
||||
for NetKingdom-linked accounts, while retaining the separate local break-glass
|
||||
path. That is an implementation requirement under RAPPS16-T09/NK48-T43, not a
|
||||
claim that the callback implements those routes or a native deployment grant.
|
||||
Ongoing resource/action/current-group decisions, protected operation/inverse and
|
||||
all release writers/journal custody remain acceptance work.
|
||||
|
||||
Final composed source validation passes 116 test/subtest results without skips.
|
||||
The initial 114-pass run skipped its private file-replacement check because a
|
||||
reused fixture was exhausted; the runner now creates fresh private/read-only
|
||||
aliases. That initial skip is retained and is not PostgreSQL acceptance.
|
||||
6112
build/forgejo/forgejo-security-extension.patch
Normal file
6112
build/forgejo/forgejo-security-extension.patch
Normal file
File diff suppressed because it is too large
Load diff
244
build/forgejo/inputs.lock.json
Normal file
244
build/forgejo/inputs.lock.json
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
{
|
||||
"schema": "railiance-apps.forgejo-release-inputs.v1",
|
||||
"forgejo_version": "11.0.3",
|
||||
"source_archive_sha256": "1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
|
||||
"source_commit": "b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237",
|
||||
"patch_sha256": "9fe3d7baa648c9347964a177befab5070e00151d1ec5ddff16c41890db84e62a",
|
||||
"go_archive_sha256": "3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8",
|
||||
"go_version": "1.24.3",
|
||||
"node_archive_sha256": "dafe2e8f82cb97de1bd10db9e2ec4c07bbf53389b0799b1e095a918951e78fd4",
|
||||
"node_version": "22.15.0",
|
||||
"source_date_epoch": 1752105237,
|
||||
"release_version": "11.0.3+cust80.9fe3d7ba",
|
||||
"tags": "bindata timetzdata sqlite sqlite_unlock_notify",
|
||||
"runtime_base": "code.forgejo.org/forgejo/forgejo@sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b",
|
||||
"runtime_index_digest": "sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba",
|
||||
"platform": "linux/amd64",
|
||||
"reviewed_outputs": [
|
||||
{
|
||||
"path": "cmd/admin_auth_oauth.go",
|
||||
"sha256": "d718d817c46f6393488a9c5a894b7535640cd0ec1a1ae3f72f8411a2bbec3b38"
|
||||
},
|
||||
{
|
||||
"path": "cmd/admin_auth_oauth_secret_linux.go",
|
||||
"sha256": "6741961f1df67c1afbf34c796e549425f0e04a0d0678c6eedb05f66d4754c64b"
|
||||
},
|
||||
{
|
||||
"path": "cmd/admin_auth_oauth_secret_linux_test.go",
|
||||
"sha256": "b28c2369dfab9bf2ca48e707e01b4394a36c8be5490cfe5bb658017bb626c4b2"
|
||||
},
|
||||
{
|
||||
"path": "cmd/admin_auth_oauth_secret_other.go",
|
||||
"sha256": "54056c989a4757e73db0252130e7c16bf16f5ec8eaaca2468f019ba2ef5c532d"
|
||||
},
|
||||
{
|
||||
"path": "go.mod",
|
||||
"sha256": "11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation.go",
|
||||
"sha256": "ef1b07ef6cf454015d00cde8588835f5118d829da91eb0303d37184859345a31"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation_internal_test.go",
|
||||
"sha256": "af0f424d3a1fd2b29793b64baa1fa07284fee7ea0b7721c503db4efac0d871cb"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation_postgres_export_test.go",
|
||||
"sha256": "b9d1553144766b15c408295b87506106935abf4ed61460598b58dc81825e808d"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation_postgres_test.go",
|
||||
"sha256": "4a37504e5af00580d114ef1bed5cb32e09e4ec2de5fd3192adef043a4ea5f7fe"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation_process_test.go",
|
||||
"sha256": "40d33b11a01cb659f0bf6f8b545c4d79910e8c082a789060bcbc9cadeee965bd"
|
||||
},
|
||||
{
|
||||
"path": "models/auth/source_operation_test.go",
|
||||
"sha256": "77d311bca42f48faf3fbdc15c53582e3d8a6d9108a72ed7bb6172e5b88951428"
|
||||
},
|
||||
{
|
||||
"path": "models/db/context.go",
|
||||
"sha256": "d4c4acfcb8987071aecf5cd957b3821c8d2619a2696398d37c2970f2527686f7"
|
||||
},
|
||||
{
|
||||
"path": "models/db/engine.go",
|
||||
"sha256": "d21a5b0b9ca37ac48222b79007467a232abc38edda99b3c5f54f1f4c5ab795e8"
|
||||
},
|
||||
{
|
||||
"path": "models/db/sensitive_sql.go",
|
||||
"sha256": "06a298d0d6e628e6920384d4ddeef449e40c090841ebaa3f6d175725a45ae584"
|
||||
},
|
||||
{
|
||||
"path": "models/db/sensitive_sql_logging_test.go",
|
||||
"sha256": "d924b35d30686473caf29601cf51cc363ba68158cb4ef0981c9e2fc7b03c04b7"
|
||||
},
|
||||
{
|
||||
"path": "models/db/sensitive_sql_test.go",
|
||||
"sha256": "de6353c955047c3311747e4777b9f9b2195e9efb19160b971dd758dfc94cf31b"
|
||||
},
|
||||
{
|
||||
"path": "models/db/tracing_context_test.go",
|
||||
"sha256": "612b60c67a2be7e2da9173b95d5ef5abd7b9e1e377360828e39a622610bba070"
|
||||
},
|
||||
{
|
||||
"path": "routers/web/admin/auths.go",
|
||||
"sha256": "3498229e06fb9ff52396bcb5e68c0cc2d2ad24e6494533d8717d7c833f3e2923"
|
||||
},
|
||||
{
|
||||
"path": "routers/web/auth/oauth.go",
|
||||
"sha256": "58bff778e0dc17a521706289bf45c3e0d76a759a0874f76c379b3d1ddb419b17"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine.go",
|
||||
"sha256": "d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_assurance.go",
|
||||
"sha256": "42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_assurance_test.go",
|
||||
"sha256": "44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_azp_test.go",
|
||||
"sha256": "5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_callback_test.go",
|
||||
"sha256": "1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_current_token.go",
|
||||
"sha256": "4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_current_token_test.go",
|
||||
"sha256": "58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_purpose_test.go",
|
||||
"sha256": "533dc3153fcf9bfcf7bfc08712c057092c7b3892882f217fb8f839fdbdd99939"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/access_engine_test.go",
|
||||
"sha256": "9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/current_issuer_fixture_test.go",
|
||||
"sha256": "24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/providers_openid.go",
|
||||
"sha256": "5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/required_claim_values.go",
|
||||
"sha256": "29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/required_claim_values_test.go",
|
||||
"sha256": "54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/source.go",
|
||||
"sha256": "44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642"
|
||||
},
|
||||
{
|
||||
"path": "services/auth/source/oauth2/source_callout.go",
|
||||
"sha256": "6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/LICENSE",
|
||||
"sha256": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/go.mod",
|
||||
"sha256": "fea700810e47c1ba5638b86a168f8531c083a6e1222ca80bd2a8947b2ea6f2c3"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/internal/sign/sign.go",
|
||||
"sha256": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/pkg/api/canonical.go",
|
||||
"sha256": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/pkg/api/caring.go",
|
||||
"sha256": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/pkg/api/resource_manifest.go",
|
||||
"sha256": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/pkg/decisionpep/client.go",
|
||||
"sha256": "0d57f6ddaca5c3f81ce426c0e8b14ba14ea5962096d3be5909aa456eef19eeba"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/pkg/decisionpep/client_test.go",
|
||||
"sha256": "1a6be3485f580f95a1f1406fb76e525f2c4bf02fe04a424cfe0095bb728e3f62"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/producer-source.json",
|
||||
"sha256": "c1d83e3d79f9860ffaa61ec19a6b3415e914d3058b11b07cdd05a7d1182cdceb"
|
||||
},
|
||||
{
|
||||
"path": "third_party/access-engine-pep/testsupport/signfixture/sign.go",
|
||||
"sha256": "fe4aa86f444dc20145aefbaca35b24f046264b2c5c8b4e3b057a4496b77a4d29"
|
||||
},
|
||||
{
|
||||
"path": "third_party/keycape-tokenstatus/LICENSE",
|
||||
"sha256": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
|
||||
},
|
||||
{
|
||||
"path": "third_party/keycape-tokenstatus/go.mod",
|
||||
"sha256": "e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54"
|
||||
},
|
||||
{
|
||||
"path": "third_party/keycape-tokenstatus/internal/jose/jose.go",
|
||||
"sha256": "a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d"
|
||||
},
|
||||
{
|
||||
"path": "third_party/keycape-tokenstatus/pkg/tokenstatus/client.go",
|
||||
"sha256": "f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575"
|
||||
},
|
||||
{
|
||||
"path": "third_party/keycape-tokenstatus/producer-source.json",
|
||||
"sha256": "3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
|
||||
}
|
||||
],
|
||||
"lockfiles": {
|
||||
"go.mod": "11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
|
||||
"go.sum": "19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
|
||||
"package.json": "ab5c54ec7077501b50acf3ab5f1fff4f63a51abe99e720b8ecdc6cc6c78f39fc",
|
||||
"package-lock.json": "fdd5c2f891311be7c01b1907b418c243ca1f0f951fd4b4ee5b5e931c43493657"
|
||||
},
|
||||
"host_tools": {
|
||||
"/usr/bin/make": "d78b8f1d099fbcfb6f2f49ab87223b9b68fb3956642f92d6ec6de812e8afa965",
|
||||
"/usr/bin/gcc": "1b99826121ae6682a634e5efe09bd3e3df58ce58e0b28f849114ab5b89139c26",
|
||||
"/usr/bin/ld": "e46a122cc3a29feba3b1d0fc2a274b6e46538be016ebe381370c0801bb98b0b8",
|
||||
"/usr/bin/ar": "534681ac11c18868cfc4fdf98770aa0ba8973eedc90c231e94e6ba96e1a04f27",
|
||||
"/usr/bin/bwrap": "e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71",
|
||||
"/usr/lib/x86_64-linux-gnu/libc.a": "a40c7eae7581f977b65eddb81cbc799213a695749e039d966db55bdee4ac9621",
|
||||
"/usr/lib/x86_64-linux-gnu/libm.a": "420cd3c406f6f8c12deb3d1291146e9c091ee015a7c39eaf2237f01e80d6f0ae"
|
||||
},
|
||||
"runtime_base_config_sha256": "bc5a3df111729582faadebd01d126ba769e2b65d0c62f306f6bc79f4e562452b",
|
||||
"patch_file": "forgejo-security-extension.patch",
|
||||
"candidate_review_status": "current-token callback source tested; complete release rebuild and independent owner review pending",
|
||||
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
|
||||
"optional_source_tests": {
|
||||
"source_operation_postgres": {
|
||||
"database_major": 16,
|
||||
"synthetic_fixture_image": "postgres@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777",
|
||||
"source_only": true,
|
||||
"production_connection_or_credentials": false
|
||||
}
|
||||
},
|
||||
"identity_client_source_revision": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
|
||||
"identity_issuer_source_revision": "git:e2c5a947829568baf837543a6708fd0c3f6a863b"
|
||||
}
|
||||
484
build/forgejo/required-claim-values.patch
Normal file
484
build/forgejo/required-claim-values.patch
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
--- a/cmd/admin_auth_oauth.go
|
||||
+++ b/cmd/admin_auth_oauth.go
|
||||
@@ -94,6 +94,10 @@
|
||||
Name: "required-claim-value",
|
||||
Value: "",
|
||||
Usage: "Claim value that has to be set to allow users to login with this source",
|
||||
+ },
|
||||
+ &cli.StringSliceFlag{
|
||||
+ Name: "required-claim-values",
|
||||
+ Usage: "Require any one exact array value from current OpenID Connect UserInfo (cannot combine with required-claim-value)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "group-claim-name",
|
||||
@@ -160,6 +164,7 @@
|
||||
Scopes: c.StringSlice("scopes"),
|
||||
RequiredClaimName: c.String("required-claim-name"),
|
||||
RequiredClaimValue: c.String("required-claim-value"),
|
||||
+ RequiredClaimValues: c.StringSlice("required-claim-values"),
|
||||
GroupClaimName: c.String("group-claim-name"),
|
||||
AdminGroup: c.String("admin-group"),
|
||||
RestrictedGroup: c.String("restricted-group"),
|
||||
@@ -177,6 +182,12 @@
|
||||
}
|
||||
|
||||
config := parseOAuth2Config(c)
|
||||
+ if err := oauth2.ValidateRequiredClaimValues(config.RequiredClaimName, config.RequiredClaimValue, config.RequiredClaimValues); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ if config.RequiredClaimValues != nil && config.Provider != "openidConnect" {
|
||||
+ return errors.New("required-claim-values requires openidConnect")
|
||||
+ }
|
||||
if config.Provider == "openidConnect" {
|
||||
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
|
||||
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
|
||||
@@ -246,6 +257,10 @@
|
||||
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
|
||||
}
|
||||
|
||||
+ if c.IsSet("required-claim-values") {
|
||||
+ oAuth2Config.RequiredClaimValues = c.StringSlice("required-claim-values")
|
||||
+ }
|
||||
+
|
||||
if c.IsSet("group-claim-name") {
|
||||
oAuth2Config.GroupClaimName = c.String("group-claim-name")
|
||||
}
|
||||
@@ -293,6 +308,12 @@
|
||||
}
|
||||
|
||||
oAuth2Config.CustomURLMapping = customURLMapping
|
||||
+ if err := oauth2.ValidateRequiredClaimValues(oAuth2Config.RequiredClaimName, oAuth2Config.RequiredClaimValue, oAuth2Config.RequiredClaimValues); err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
+ if oAuth2Config.RequiredClaimValues != nil && oAuth2Config.Provider != "openidConnect" {
|
||||
+ return errors.New("required-claim-values requires openidConnect")
|
||||
+ }
|
||||
source.Cfg = oAuth2Config
|
||||
|
||||
return auth_model.UpdateSource(ctx, source)
|
||||
--- a/routers/web/admin/auths.go
|
||||
+++ b/routers/web/admin/auths.go
|
||||
@@ -322,6 +322,13 @@
|
||||
ctx.Data["Source"] = source
|
||||
ctx.Data["HasTLS"] = source.HasTLS()
|
||||
|
||||
+ // The legacy form has no representation for the opt-in current-UserInfo gate.
|
||||
+ // Refuse rather than silently dropping it during source reconstruction.
|
||||
+ if current, ok := source.Cfg.(*oauth2.Source); ok && current.RequiredClaimValues != nil {
|
||||
+ ctx.RenderWithErr("This OAuth source uses required-claim-values; update it through the reviewed CLI.", tplAuthEdit, form)
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(http.StatusOK, tplAuthEdit)
|
||||
return
|
||||
--- a/routers/web/auth/oauth.go
|
||||
+++ b/routers/web/auth/oauth.go
|
||||
@@ -1427,7 +1427,11 @@
|
||||
return goth.User{}, err
|
||||
}
|
||||
|
||||
- if oauth2Source.RequiredClaimName != "" {
|
||||
+ if oauth2Source.RequiredClaimValues != nil {
|
||||
+ if oauth2Source.Provider != "openidConnect" || !oauth2.MatchRequiredClaimValues(gothUser.RawData, oauth2Source.RequiredClaimName, oauth2Source.RequiredClaimValue, oauth2Source.RequiredClaimValues) {
|
||||
+ return goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
|
||||
+ }
|
||||
+ } else if oauth2Source.RequiredClaimName != "" {
|
||||
claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
|
||||
if !has {
|
||||
return goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
|
||||
--- a/services/auth/source/oauth2/providers_openid.go
|
||||
+++ b/services/auth/source/oauth2/providers_openid.go
|
||||
@@ -4,6 +4,7 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
+ "errors"
|
||||
"html/template"
|
||||
|
||||
"forgejo.org/modules/log"
|
||||
@@ -43,6 +44,22 @@
|
||||
if err != nil {
|
||||
log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
|
||||
}
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ if source.RequiredClaimValues != nil {
|
||||
+ if err := ValidateRequiredClaimValues(source.RequiredClaimName, source.RequiredClaimValue, source.RequiredClaimValues); err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ if provider.OpenIDConfig == nil || provider.SkipUserInfoRequest {
|
||||
+ return nil, errors.New("required-claim-values needs current UserInfo")
|
||||
+ }
|
||||
+ guarded, err := RequireCurrentUserInfoClaim(provider.Client(), provider.OpenIDConfig.UserInfoEndpoint, source.RequiredClaimName)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
+ provider.HTTPClient = guarded
|
||||
+ }
|
||||
return provider, err
|
||||
}
|
||||
|
||||
--- /dev/null
|
||||
+++ b/services/auth/source/oauth2/required_claim_values.go
|
||||
@@ -0,0 +1,202 @@
|
||||
+// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
+// SPDX-License-Identifier: MIT
|
||||
+
|
||||
+package oauth2
|
||||
+
|
||||
+import (
|
||||
+ "bytes"
|
||||
+ "encoding/json"
|
||||
+ "errors"
|
||||
+ "io"
|
||||
+ "net/http"
|
||||
+ "net/url"
|
||||
+ "strings"
|
||||
+ "unicode"
|
||||
+ "unicode/utf8"
|
||||
+)
|
||||
+
|
||||
+const maxClaimValues = 1024
|
||||
+const maxUserInfoBytes = 1 << 20
|
||||
+
|
||||
+// ValidateRequiredClaimValues validates only the opt-in list extension. A nil
|
||||
+// list retains every legacy single-value behavior. An explicitly empty list
|
||||
+// fails closed rather than turning a configured gate into claim-presence-only.
|
||||
+func ValidateRequiredClaimValues(name, legacy string, values []string) error {
|
||||
+ if values == nil {
|
||||
+ return nil
|
||||
+ }
|
||||
+ if !validClaimLiteral(name) || legacy != "" || len(values) == 0 || len(values) > 64 {
|
||||
+ return errors.New("invalid or conflicting required-claim-values configuration")
|
||||
+ }
|
||||
+ seen := map[string]bool{}
|
||||
+ for _, value := range values {
|
||||
+ if !validClaimLiteral(value) || seen[value] {
|
||||
+ return errors.New("invalid required-claim-values literal")
|
||||
+ }
|
||||
+ seen[value] = true
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
+func validClaimLiteral(value string) bool {
|
||||
+ return value != "" && len(value) <= 256 && utf8.ValidString(value) &&
|
||||
+ value == strings.TrimSpace(value) && !strings.ContainsAny(value, "\x00\r\n,") &&
|
||||
+ !strings.ContainsFunc(value, func(r rune) bool { return unicode.IsControl(r) || r == utf8.RuneError })
|
||||
+}
|
||||
+
|
||||
+// strictClaimValues accepts only a well-formed claim array. No scalar parsing,
|
||||
+// coercion, regex or prefix matching is introduced into the new gate.
|
||||
+func strictClaimValues(raw any) ([]string, bool) {
|
||||
+ var values []string
|
||||
+ switch v := raw.(type) {
|
||||
+ case []string:
|
||||
+ values = v
|
||||
+ case []any:
|
||||
+ values = make([]string, len(v))
|
||||
+ for i, value := range v {
|
||||
+ s, ok := value.(string)
|
||||
+ if !ok {
|
||||
+ return nil, false
|
||||
+ }
|
||||
+ values[i] = s
|
||||
+ }
|
||||
+ default:
|
||||
+ return nil, false
|
||||
+ }
|
||||
+ if len(values) > maxClaimValues {
|
||||
+ return nil, false
|
||||
+ }
|
||||
+ for _, value := range values {
|
||||
+ if !validClaimLiteral(value) {
|
||||
+ return nil, false
|
||||
+ }
|
||||
+ }
|
||||
+ return values, true
|
||||
+}
|
||||
+
|
||||
+// MatchRequiredClaimValues evaluates the new consumer gate against current
|
||||
+// authenticated provider data; it does not mutate or manufacture issuer claims.
|
||||
+func MatchRequiredClaimValues(claims map[string]any, name, legacy string, required []string) bool {
|
||||
+ if required == nil || ValidateRequiredClaimValues(name, legacy, required) != nil {
|
||||
+ return false
|
||||
+ }
|
||||
+ raw, present := claims[name]
|
||||
+ if !present {
|
||||
+ return false
|
||||
+ }
|
||||
+ values, ok := strictClaimValues(raw)
|
||||
+ if !ok {
|
||||
+ return false
|
||||
+ }
|
||||
+ for _, value := range values {
|
||||
+ for _, wanted := range required {
|
||||
+ if value == wanted {
|
||||
+ return true
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return false
|
||||
+}
|
||||
+
|
||||
+// RequireCurrentUserInfoClaim preserves the existing HTTP client settings and
|
||||
+// adds a strict response guard for this opt-in source only. A current UserInfo
|
||||
+// array must overwrite any stale ID-token claim. Missing/invalid data and HTTP
|
||||
+// failures cannot fall back to the ID-token map in Goth's merging code.
|
||||
+func RequireCurrentUserInfoClaim(client *http.Client, endpoint, claim string) (*http.Client, error) {
|
||||
+ u, err := url.Parse(endpoint)
|
||||
+ if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.Fragment != "" || !validClaimLiteral(claim) {
|
||||
+ return nil, errors.New("required-claim-values needs an HTTPS UserInfo endpoint")
|
||||
+ }
|
||||
+ if client == nil {
|
||||
+ client = http.DefaultClient
|
||||
+ }
|
||||
+ result := *client
|
||||
+ base := result.Transport
|
||||
+ if base == nil {
|
||||
+ base = http.DefaultTransport
|
||||
+ }
|
||||
+ result.Transport = ¤tClaimTransport{base: base, endpoint: endpoint, claim: claim}
|
||||
+ // Redirects could evade endpoint matching or send a bearer to another origin.
|
||||
+ // They are unsupported for the new guarded provider; legacy clients are intact.
|
||||
+ result.CheckRedirect = func(*http.Request, []*http.Request) error {
|
||||
+ return errors.New("guarded OAuth redirects are not supported")
|
||||
+ }
|
||||
+ return &result, nil
|
||||
+}
|
||||
+
|
||||
+type currentClaimTransport struct {
|
||||
+ base http.RoundTripper
|
||||
+ endpoint, claim string
|
||||
+}
|
||||
+
|
||||
+func (t *currentClaimTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
+ response, err := t.base.RoundTrip(req)
|
||||
+ if req.URL.String() != t.endpoint {
|
||||
+ return response, err
|
||||
+ }
|
||||
+ if err != nil {
|
||||
+ if response != nil && response.Body != nil {
|
||||
+ response.Body.Close()
|
||||
+ }
|
||||
+ return nil, errors.New("current UserInfo lookup failed")
|
||||
+ }
|
||||
+ if response == nil || response.Body == nil {
|
||||
+ return nil, errors.New("current UserInfo response missing")
|
||||
+ }
|
||||
+ defer response.Body.Close()
|
||||
+ if req.Method != http.MethodGet || response.StatusCode != http.StatusOK {
|
||||
+ return nil, errors.New("current UserInfo refused")
|
||||
+ }
|
||||
+ body, err := io.ReadAll(io.LimitReader(response.Body, maxUserInfoBytes+1))
|
||||
+ if err != nil || len(body) > maxUserInfoBytes || !utf8.Valid(body) {
|
||||
+ return nil, errors.New("current UserInfo response invalid")
|
||||
+ }
|
||||
+ // Reject duplicate top-level keys before Unmarshal can hide a conflicting
|
||||
+ // groups or subject claim. json.RawMessage permits unrelated normal claims.
|
||||
+ decoder := json.NewDecoder(bytes.NewReader(body))
|
||||
+ token, err := decoder.Token()
|
||||
+ if err != nil || token != json.Delim('{') {
|
||||
+ return nil, errors.New("current UserInfo object required")
|
||||
+ }
|
||||
+ claims := map[string]json.RawMessage{}
|
||||
+ for decoder.More() {
|
||||
+ keyToken, err := decoder.Token()
|
||||
+ if err != nil {
|
||||
+ return nil, errors.New("current UserInfo key invalid")
|
||||
+ }
|
||||
+ key, ok := keyToken.(string)
|
||||
+ if !ok {
|
||||
+ return nil, errors.New("current UserInfo key invalid")
|
||||
+ }
|
||||
+ if _, duplicate := claims[key]; duplicate {
|
||||
+ return nil, errors.New("current UserInfo duplicate claim")
|
||||
+ }
|
||||
+ var value json.RawMessage
|
||||
+ if decoder.Decode(&value) != nil {
|
||||
+ return nil, errors.New("current UserInfo value invalid")
|
||||
+ }
|
||||
+ claims[key] = value
|
||||
+ }
|
||||
+ if token, err = decoder.Token(); err != nil || token != json.Delim('}') {
|
||||
+ return nil, errors.New("current UserInfo object invalid")
|
||||
+ }
|
||||
+ var trailing any
|
||||
+ if decoder.Decode(&trailing) != io.EOF {
|
||||
+ return nil, errors.New("current UserInfo trailing data")
|
||||
+ }
|
||||
+ raw, present := claims[t.claim]
|
||||
+ if !present {
|
||||
+ return nil, errors.New("current UserInfo required claim missing")
|
||||
+ }
|
||||
+ var value any
|
||||
+ if json.Unmarshal(raw, &value) != nil {
|
||||
+ return nil, errors.New("current UserInfo required claim invalid")
|
||||
+ }
|
||||
+ if _, ok := strictClaimValues(value); !ok {
|
||||
+ return nil, errors.New("current UserInfo required claim array invalid")
|
||||
+ }
|
||||
+ // Return the original public response bytes to Goth. Subject equality and
|
||||
+ // audience/issuer checks remain in its existing authenticated provider path.
|
||||
+ response.Body = io.NopCloser(bytes.NewReader(body))
|
||||
+ return response, nil
|
||||
+}
|
||||
--- /dev/null
|
||||
+++ b/services/auth/source/oauth2/required_claim_values_test.go
|
||||
@@ -0,0 +1,144 @@
|
||||
+// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
+// SPDX-License-Identifier: MIT
|
||||
+package oauth2
|
||||
+
|
||||
+import (
|
||||
+ "errors"
|
||||
+ "io"
|
||||
+ "net/http"
|
||||
+ "reflect"
|
||||
+ "strings"
|
||||
+ "testing"
|
||||
+)
|
||||
+
|
||||
+var caseValues = []string{"forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"}
|
||||
+
|
||||
+type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
+
|
||||
+func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
+
|
||||
+func mockUserInfo(body string, status int) *http.Client {
|
||||
+ return &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
+ return &http.Response{StatusCode: status, Header: http.Header{}, Body: io.NopCloser(strings.NewReader(body)), Request: r}, nil
|
||||
+ })}
|
||||
+}
|
||||
+
|
||||
+func TestRequiredClaimExactAnyOfAndNoMutation(t *testing.T) {
|
||||
+ for _, group := range caseValues {
|
||||
+ groups := []any{group, "unrelated"}
|
||||
+ claims := map[string]any{"groups": groups, "other": "preserved"}
|
||||
+ if !MatchRequiredClaimValues(claims, "groups", "", caseValues) {
|
||||
+ t.Fatal("individual allowed group rejected")
|
||||
+ }
|
||||
+ if !reflect.DeepEqual(claims, map[string]any{"groups": groups, "other": "preserved"}) {
|
||||
+ t.Fatal("claims mutated")
|
||||
+ }
|
||||
+ }
|
||||
+ if !MatchRequiredClaimValues(map[string]any{"groups": append([]string(nil), caseValues...)}, "groups", "", caseValues) {
|
||||
+ t.Fatal("multiple allowed groups rejected")
|
||||
+ }
|
||||
+ for _, raw := range []any{nil, true, 1, "forge-admins", "forge-admins,forge-coulomb-owners", []any{"forge-admins", 1}, []any{"forge-admins", nil}, []string{}, []any{}, []string{"forge-admins-lookalike"}, []string{" Forge-admins"}, []string{"forge-admins\n"}, []string{"Forge-admins"}, []string{"forge-admins,other"}, []string{"forge-admins", ""}} {
|
||||
+ if MatchRequiredClaimValues(map[string]any{"groups": raw}, "groups", "", caseValues) {
|
||||
+ t.Fatal("malformed/nonmatching claim accepted")
|
||||
+ }
|
||||
+ }
|
||||
+ if MatchRequiredClaimValues(map[string]any{}, "groups", "", caseValues) {
|
||||
+ t.Fatal("absent claim accepted")
|
||||
+ }
|
||||
+ if MatchRequiredClaimValues(map[string]any{"unrelated": []string{"forge-admins"}}, "groups", "", caseValues) {
|
||||
+ t.Fatal("unrelated claim used")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestRequiredClaimConfigurationRefusalsAndLegacyIsolation(t *testing.T) {
|
||||
+ if ValidateRequiredClaimValues("", "old arbitrary literal", nil) != nil {
|
||||
+ t.Fatal("legacy config changed")
|
||||
+ }
|
||||
+ for _, cfg := range []struct {
|
||||
+ name, legacy string
|
||||
+ values []string
|
||||
+ }{
|
||||
+ {"groups", "", []string{}}, {"groups", "", []string{""}}, {"groups", "old", caseValues}, {"", "", caseValues},
|
||||
+ {"groups", "", []string{"a", "a"}}, {"groups", "", []string{"a,b"}}, {"groups", "", []string{" a"}},
|
||||
+ } {
|
||||
+ if ValidateRequiredClaimValues(cfg.name, cfg.legacy, cfg.values) == nil {
|
||||
+ t.Fatal("invalid config accepted")
|
||||
+ }
|
||||
+ }
|
||||
+ if ValidateRequiredClaimValues("groups", "", caseValues) != nil {
|
||||
+ t.Fatal("case config invalid")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable(t *testing.T) {
|
||||
+ for _, body := range []string{
|
||||
+ `{}`, `{"sub":"alice"}`, `{"groups":null}`, `{"groups":true}`, `{"groups":"forge-admins"}`,
|
||||
+ `{"groups":["forge-admins",null]}`, `{"groups":["forge-admins",1]}`,
|
||||
+ `{"groups":["forge-admins"],"groups":[]}`, `{"groups":[]} {}`, `[]`, `{"groups":[]`,
|
||||
+ `{"groups":["forge-admins",""]}`, strings.Repeat(" ", maxUserInfoBytes+1),
|
||||
+ } {
|
||||
+ client, err := RequireCurrentUserInfoClaim(mockUserInfo(body, 200), "https://issuer.example/userinfo", "groups")
|
||||
+ if err != nil {
|
||||
+ t.Fatal(err)
|
||||
+ }
|
||||
+ response, err := client.Get("https://issuer.example/userinfo")
|
||||
+ if err == nil {
|
||||
+ response.Body.Close()
|
||||
+ t.Fatal("invalid current claim did not fail closed")
|
||||
+ }
|
||||
+ }
|
||||
+ client, _ := RequireCurrentUserInfoClaim(mockUserInfo(`{"groups":[]}`, 503), "https://issuer.example/userinfo", "groups")
|
||||
+ if response, err := client.Get("https://issuer.example/userinfo"); err == nil {
|
||||
+ response.Body.Close()
|
||||
+ t.Fatal("non200 accepted")
|
||||
+ }
|
||||
+ broken := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, errors.New("private backend error") })}
|
||||
+ client, _ = RequireCurrentUserInfoClaim(broken, "https://issuer.example/userinfo", "groups")
|
||||
+ if _, err := client.Get("https://issuer.example/userinfo"); err == nil || strings.Contains(err.Error(), "private backend") {
|
||||
+ t.Fatal("lookup failure not contained")
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval(t *testing.T) {
|
||||
+ for _, body := range []string{`{"sub":"alice","groups":[]}`, `{"sub":"alice","groups":["forge-coulomb-readers"]}`} {
|
||||
+ client, err := RequireCurrentUserInfoClaim(mockUserInfo(body, 200), "https://issuer.example/userinfo", "groups")
|
||||
+ if err != nil {
|
||||
+ t.Fatal(err)
|
||||
+ }
|
||||
+ response, err := client.Get("https://issuer.example/userinfo")
|
||||
+ if err != nil {
|
||||
+ t.Fatal(err)
|
||||
+ }
|
||||
+ b, _ := io.ReadAll(response.Body)
|
||||
+ response.Body.Close()
|
||||
+ if string(b) != body {
|
||||
+ t.Fatal("response changed")
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse(t *testing.T) {
|
||||
+ original := mockUserInfo(`{"unrelated":true}`, 200)
|
||||
+ guarded, err := RequireCurrentUserInfoClaim(original, "https://issuer.example/userinfo", "groups")
|
||||
+ if err != nil {
|
||||
+ t.Fatal(err)
|
||||
+ }
|
||||
+ response, err := original.Get("https://issuer.example/userinfo")
|
||||
+ if err != nil {
|
||||
+ t.Fatal("original client mutated")
|
||||
+ }
|
||||
+ response.Body.Close()
|
||||
+ response, err = guarded.Get("https://issuer.example/unrelated")
|
||||
+ if err != nil {
|
||||
+ t.Fatal("unrelated endpoint changed")
|
||||
+ }
|
||||
+ response.Body.Close()
|
||||
+ if guarded.CheckRedirect == nil || guarded.CheckRedirect(nil, nil) == nil {
|
||||
+ t.Fatal("redirect can bypass guard")
|
||||
+ }
|
||||
+ for _, endpoint := range []string{"", "http://issuer.example/userinfo", "https://user:pass@issuer.example/userinfo", "https://issuer.example/userinfo#fragment"} {
|
||||
+ if _, err := RequireCurrentUserInfoClaim(original, endpoint, "groups"); err == nil {
|
||||
+ t.Fatal("invalid endpoint accepted")
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--- a/services/auth/source/oauth2/source.go
|
||||
+++ b/services/auth/source/oauth2/source.go
|
||||
@@ -22,6 +22,7 @@
|
||||
Scopes []string
|
||||
AttributeSSHPublicKey string
|
||||
RequiredClaimName string
|
||||
+ RequiredClaimValues []string `json:",omitempty"`
|
||||
RequiredClaimValue string
|
||||
GroupClaimName string
|
||||
AdminGroup string
|
||||
1
build/forgejo/runtime-base-config.json
Normal file
1
build/forgejo/runtime-base-config.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -38,7 +38,7 @@ env:
|
|||
USER_ENGINE_APPLICATION_ID: coulomb-social
|
||||
USER_ENGINE_EXPECTED_AUDIENCE: user-engine-portal
|
||||
# USER_ENGINE_BASE_URL set via secret or here when stable
|
||||
USER_ENGINE_BASE_URL: https://users.92-205-62-239.nip.io
|
||||
USER_ENGINE_BASE_URL: https://users.coulomb.social
|
||||
NETKINGDOM_REGISTRATION_URL: https://users.92-205-62-239.nip.io/register
|
||||
|
||||
probes:
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
apiVersion: v2
|
||||
name: inter-hub
|
||||
description: |
|
||||
Interaction Hub Framework reference deployment for Railiance01.
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "0.2.0-alpha.1"
|
||||
keywords:
|
||||
- ihf
|
||||
- inter-hub
|
||||
- railiance
|
||||
home: https://forgejo.coulomb.social/coulomb/inter-hub-haskell
|
||||
sources:
|
||||
- https://forgejo.coulomb.social/coulomb/inter-hub-haskell
|
||||
maintainers:
|
||||
- name: railiance-apps
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
{{/*
|
||||
Chart name + release name produce a unique resource name.
|
||||
*/}}
|
||||
{{- define "interhub.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s" $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "interhub.labels" -}}
|
||||
app: {{ include "interhub.fullname" . }}
|
||||
app.kubernetes.io/name: {{ include "interhub.fullname" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "interhub.selectorLabels" -}}
|
||||
app: {{ include "interhub.fullname" . }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "interhub.image" -}}
|
||||
{{- if not .Values.image.tag -}}
|
||||
{{- fail "image.tag is required - pin it in helm/inter-hub-values.yaml or pass --set image.tag=<sha>" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}
|
||||
{{- end -}}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "interhub.fullname" . }}
|
||||
labels: {{- include "interhub.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels: {{- include "interhub.selectorLabels" . | nindent 6 }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels: {{- include "interhub.labels" . | nindent 8 }}
|
||||
spec:
|
||||
securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: inter-hub
|
||||
image: {{ include "interhub.image" . | quote }}
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
securityContext: {{- toYaml .Values.securityContext | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: {{ .Values.envSecretName | quote }}
|
||||
{{- if .Values.probes.enabled }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.path }}
|
||||
port: {{ .Values.probes.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: {{ .Values.probes.path }}
|
||||
port: {{ .Values.probes.port }}
|
||||
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
||||
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
|
||||
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
||||
{{- end }}
|
||||
resources: {{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "interhub.fullname" . }}
|
||||
labels: {{- include "interhub.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- toYaml .Values.ingress.annotations | nindent 4 }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ .Values.ingress.host }}
|
||||
secretName: {{ include "interhub.fullname" . }}-tls
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "interhub.fullname" . }}
|
||||
port:
|
||||
number: {{ .Values.service.port }}
|
||||
{{- end }}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "interhub.fullname" . }}
|
||||
labels: {{- include "interhub.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: {{ .Values.service.targetPort }}
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector: {{- include "interhub.selectorLabels" . | nindent 4 }}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/inter-hub
|
||||
tag: "" # required; pinned via helm/inter-hub-values.yaml or --set image.tag=<sha>
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 2Gi
|
||||
|
||||
# Env from the K8s Secret created from inter-hub's SOPS handoff file:
|
||||
# deploy/railiance/secrets/inter-hub.env.sops.yaml
|
||||
envSecretName: inter-hub-env
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik
|
||||
host: hub.coulomb.social
|
||||
tls: true
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
|
||||
probes:
|
||||
enabled: true
|
||||
path: /
|
||||
port: 8000
|
||||
liveness:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readiness:
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
|
||||
podSecurityContext: {}
|
||||
securityContext: {}
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
|
@ -32,11 +32,21 @@ app.kubernetes.io/component: landing
|
|||
{{- end -}}
|
||||
|
||||
{{- define "reuse.image" -}}
|
||||
{{- if and .Values.authSourceAllowlist (not .Values.image.digest) -}}
|
||||
{{- fail "authenticated source allowlist requires reviewed hardened image.digest" -}}
|
||||
{{- end -}}
|
||||
{{- if .Values.image.digest -}}
|
||||
{{- if not (regexMatch "^sha256:[a-f0-9]{64}$" .Values.image.digest) -}}
|
||||
{{- fail "image.digest must be sha256 plus 64 lowercase hex characters" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
|
||||
{{- else -}}
|
||||
{{- if not .Values.image.tag -}}
|
||||
{{- fail "image.tag is required - pin it in helm/reuse-surface-values.yaml" -}}
|
||||
{{- end -}}
|
||||
{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "reuse.landingImage" -}}
|
||||
{{- if not .Values.landing.image.tag -}}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ spec:
|
|||
- secretRef:
|
||||
name: {{ .Values.envSecretName | quote }}
|
||||
env:
|
||||
- name: REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST
|
||||
value: {{ .Values.authSourceAllowlist | toJson | quote }}
|
||||
- name: REUSE_SURFACE_LISTEN_HOST
|
||||
value: "0.0.0.0"
|
||||
- name: REUSE_SURFACE_LISTEN_PORT
|
||||
|
|
|
|||
30
charts/reuse-surface/values.schema.json
Normal file
30
charts/reuse-surface/values.schema.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"digest": {
|
||||
"type": "string",
|
||||
"pattern": "^(|sha256:[a-f0-9]{64})$"
|
||||
}
|
||||
}
|
||||
},
|
||||
"authSourceAllowlist": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"pattern": "^[A-Z][A-Z0-9_]*$"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^https://[^\\s]+$"
|
||||
},
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/reuse-surface
|
||||
tag: ""
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
replicaCount: 1
|
||||
|
|
@ -26,6 +27,10 @@ resources:
|
|||
|
||||
envSecretName: reuse-surface-env
|
||||
|
||||
# Non-secret exact URL bindings for authenticated remote metadata fetches.
|
||||
# Nonempty bindings require an immutable digest; use only a reviewed hardened image.
|
||||
authSourceAllowlist: {}
|
||||
|
||||
landing:
|
||||
enabled: false
|
||||
image:
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
|
||||
railiance.io/release-unit: vergabe
|
||||
{{- end -}}
|
||||
|
||||
{{- define "vergabe.selectorLabels" -}}
|
||||
|
|
|
|||
119
docs/assessments/cust81-private-reader/apps-owner-review-r2.md
Normal file
119
docs/assessments/cust81-private-reader/apps-owner-review-r2.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Railiance apps preparation review — CUST81 private metadata reader R2
|
||||
|
||||
Reviewer: delegated railiance-apps preparation worker, supervised local preparation
|
||||
assignment. No promotion evidence or existing own-scope supervision record was
|
||||
found; the descriptive preparation record grants no runtime authority. This is
|
||||
an S5 release-contract review grounded in INTENT principles 1–5 (workloads,
|
||||
release contracts, repeatable operations, explicit S3 handoff, verified health).
|
||||
It is not a founder decision, independent platform credential review or reuse
|
||||
application source review. No live changes, commits, pushes, hub writes, image
|
||||
publishes, login or credential retrieval occurred.
|
||||
|
||||
Disposition: **approved for integration as local preparation only; live promotion blocked**.
|
||||
Canonical review disposition: `approved`. This accepts the bounded S5 proposal
|
||||
and its explicit runtime gates, not live execution or another owner’s review.
|
||||
|
||||
## Proposed changes and owner boundaries
|
||||
|
||||
The chart accepts an optional immutable image digest, adds a non-secret exact
|
||||
`REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST` environment binding, and fails rendering
|
||||
when a nonempty allowlist lacks an immutable digest. Schema checks enforce
|
||||
HTTPS allowlist entries and valid digest syntax. Production values continue
|
||||
using their existing `main-6cbc862` tag; production ExternalSecret is unchanged.
|
||||
The preparation override contains exactly 14 reviewed pinned metadata URLs and
|
||||
an empty digest deliberately preventing deployment. A synthetic all-zero digest
|
||||
in local tests is not an image and must never be promoted.
|
||||
|
||||
The proposed phase-B ExternalSecret preserves existing two keys, target metadata
|
||||
and existing `openbao-reuse` ClusterSecretStore, adding only
|
||||
`REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN` from
|
||||
`workloads/reuse/reuse-surface/forgejo-source-read:FORGEJO_SOURCE_TOKEN`.
|
||||
Platform owns the dedicated KV path/policy/store read scope, CCR-2026-0046 and
|
||||
credential issuance; apps neither provisions nor consumes an admin credential.
|
||||
|
||||
Digest presence cannot prove the hardened application is included. The reuse
|
||||
owner must provide published build digest, source revision, tests and review
|
||||
binding *both* authenticated redirect rejection and exact environment-name/URL
|
||||
credential routing. A reader must not reach the existing vulnerable runtime.
|
||||
|
||||
## Ordered promotion gates — not executed
|
||||
|
||||
1. Confirm own-scope supervision and founder change approval, reviewed CCR,
|
||||
exact seven-source owner consent packet and scoped Forgejo read-only issuer
|
||||
receipt. Integrate provisional RAPPS-WP-0017 through root owner coordination.
|
||||
2. Run metadata inventory helper with an explicitly verified railiance01
|
||||
kubeconfig; compare existing authenticated registrations with newly
|
||||
fail-closed image behavior. The helper reads only SQLite registration
|
||||
metadata, never env/Secrets, captures command errors and refuses unsafe
|
||||
URLs. If it refuses, resolve via the source owner without emitting raw
|
||||
objects. Any existing authenticated source requires separately reviewed
|
||||
exact URL bindings including capability entry URLs before promotion; do not
|
||||
silently orphan sources or broaden the 14-URL map. Missing inventory blocks
|
||||
phase A. Store only safe names, counts and validated metadata URLs.
|
||||
3. Read live release image/revision, replica state, PVC storage/access modes and
|
||||
readiness. Confirm node external IP `92.205.62.239`, not retired coulombcore.
|
||||
Record requested CPU/memory headroom and pending pods before rollout. Existing
|
||||
API requests 100m CPU/256Mi, surge1 requires that much additional allocatable
|
||||
capacity; landing unchanged. Confirm the existing PVC supports simultaneous
|
||||
pod scheduling/mount on this single node; stop if RWO/RWOP or mount conflicts
|
||||
prevent it. No rollout strategy/resource/PVC changes are proposed. Do not
|
||||
change other workloads merely to obtain capacity.
|
||||
4. Once a real reviewed digest exists, fill the preparation values; render
|
||||
exact chart with production values plus proposal overrides. Server dry-run
|
||||
and client diff must cover every rendered chart object and the phase-B ESO
|
||||
manifest. Preserve stderr in memory and emit sanitized status only; never
|
||||
print live Secrets, Helm secret values, annotations or raw API failures.
|
||||
Existing release owner and any Argo adoption must be verified before choosing
|
||||
owner Helm upgrade versus GitOps sync; neither is executed here.
|
||||
5. Phase A: approved release of hardened digest plus approved allowlist, with
|
||||
existing ESO fields only. Owner Helm path must use atomic upgrade, timeout
|
||||
and wait; verify actual running imageID matches digest, API health/probes,
|
||||
public federation baseline63 and credential-routing refusal/redirect tests.
|
||||
Deployment failure blocks phase B. Database/PVC remains unchanged. Collect a
|
||||
separately approved safe backup/recovery receipt before any risky live release.
|
||||
6. Phase B: after verified phase A, execute reviewed platform credential action
|
||||
and ESO reader mapping. Verify only ESO condition/status and key-presence
|
||||
booleans via approved suppressing helpers, then restart/sync the hardened
|
||||
workload so envFrom obtains the reader. Never dump Secret or process env.
|
||||
7. Reuse owner runs native exact-byte authenticated proof for all14 URLs and
|
||||
seven capability entries using pinned consent hashes. Only then register
|
||||
seven sources through the scoped write route and verify hosted70/70,
|
||||
no duplicates/conflicting IDs and public federation preserves all63.
|
||||
|
||||
## Rollback contract
|
||||
|
||||
Before execution capture previous approved release revision, digest/tag and
|
||||
safe configuration, public discovery baseline and database recovery receipt.
|
||||
If phase A fails before credential delivery, atomic restore of previous release
|
||||
is safe because no reader has been supplied. After phase B, **never downgrade to
|
||||
vulnerable runtime with reader still available**: revoke the dedicated reader
|
||||
at Forgejo/OpenBao via credential owner, remove phase-B ESO mapping and materialized
|
||||
reader key through reviewed silent maintenance, restart current hardened pods,
|
||||
verify absence/revocation, then restore prior approved chart/image only if needed.
|
||||
Metadata registrations may remain inaccessible until re-established; record the
|
||||
regression explicitly. Do not roll back the PVC or use stale tokens as recovery.
|
||||
|
||||
## Local evidence and limitations
|
||||
|
||||
`helm lint charts/reuse-surface -f helm/reuse-surface-values.yaml` passed.
|
||||
`python3 docs/assessments/cust81-private-reader/verify_preparation.py` passed ten
|
||||
checks, including exact14 roundtrip, digest/schema refusal, retention of current
|
||||
strategy/resources/PVC, correct isolated ESO mapping, metadata URL sanitization
|
||||
and subprocess error suppression. `local-verification-r2.json` contains command
|
||||
statuses without raw output. No cluster inventory, resource headroom,
|
||||
server dry-run/diff, image publish, live approval or native fetch has been proven.
|
||||
|
||||
## Revision history — same proposal, no second trial
|
||||
|
||||
R1 root review found the inventory SQL incorrectly selected `$.index_url` rather
|
||||
than the native HubStore registration field `$.url`. R1's local checks did not
|
||||
exercise the pod SQL and therefore missed this defect. R1 must not be executed;
|
||||
it remains preserved in the root assembly archive. R2 selects `$.url` and tests
|
||||
the exact read-only pod SQL against a real SQLite registration payload containing
|
||||
`repo`, `url`, `auth_env`, timestamps and an unrelated sentinel description, plus
|
||||
an anonymous registration. Only expected names/URL emerge; rows remain unchanged.
|
||||
R2 also accepts canonical Forgejo raw branch/commit/tag index routes while still
|
||||
refusing userinfo, non-HTTPS, credentials in queries and fragments. Untrusted
|
||||
successful subprocess output is refused without raw echo, as are failures.
|
||||
No live inventory command has run. This corrective refinement is the same
|
||||
CUST81-APPS-PRIVATE-READER-RELEASE proposal revision2, not an unchanged success.
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
{
|
||||
"status": "passed",
|
||||
"local_only": true,
|
||||
"real_image_digest": null,
|
||||
"synthetic_digest_used_only_to_check_rendering": true,
|
||||
"checks": [
|
||||
"production rendering unchanged except empty fail-closed allowlist",
|
||||
"unknown/malformed digest fails",
|
||||
"HTTP allowlist fails schema",
|
||||
"exact14 URLs roundtrip",
|
||||
"strategy resources PVC retained",
|
||||
"dedicated ESO mapping preserves current fields/store",
|
||||
"metadata sanitizer excludes userinfo/query secrets/fragments",
|
||||
"kubectl failure output discarded",
|
||||
"exact pod SQL real payload URL keys readonly fixture and anonymous filtering",
|
||||
"legacy Forgejo branch URL accepted without weakening credential query refusal"
|
||||
],
|
||||
"helm_calls": [
|
||||
{
|
||||
"command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"/tmp/cust81-apps-reader-owner/charts/reuse-surface",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/helm/reuse-surface-values.yaml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"expected_success": true
|
||||
},
|
||||
{
|
||||
"command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"/tmp/cust81-apps-reader-owner/charts/reuse-surface",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/helm/reuse-surface-values.yaml",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/docs/assessments/cust81-private-reader/private-reader-values.r1.yaml"
|
||||
],
|
||||
"exit_code": 1,
|
||||
"expected_success": false
|
||||
},
|
||||
{
|
||||
"command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"/tmp/cust81-apps-reader-owner/charts/reuse-surface",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/helm/reuse-surface-values.yaml",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/docs/assessments/cust81-private-reader/private-reader-values.r1.yaml",
|
||||
"--set",
|
||||
"image.digest=sha256:bad"
|
||||
],
|
||||
"exit_code": 1,
|
||||
"expected_success": false
|
||||
},
|
||||
{
|
||||
"command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"/tmp/cust81-apps-reader-owner/charts/reuse-surface",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/helm/reuse-surface-values.yaml",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/docs/assessments/cust81-private-reader/private-reader-values.r1.yaml",
|
||||
"--set-json",
|
||||
"authSourceAllowlist={\"BAD\":[\"http://attacker.invalid\"]}",
|
||||
"--set",
|
||||
"image.digest=sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
||||
],
|
||||
"exit_code": 1,
|
||||
"expected_success": false
|
||||
},
|
||||
{
|
||||
"command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"/tmp/cust81-apps-reader-owner/charts/reuse-surface",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/helm/reuse-surface-values.yaml",
|
||||
"-f",
|
||||
"/tmp/cust81-apps-reader-owner/docs/assessments/cust81-private-reader/private-reader-values.r1.yaml",
|
||||
"--set",
|
||||
"image.digest=sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"expected_success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"reviewer_owner": "railiance-apps",
|
||||
"reviewer_assignment": "delegated-railiance-apps-cust81-preparation",
|
||||
"disposition": "approved",
|
||||
"approved_scope": "isolated S5 chart/ESO preparation integration only",
|
||||
"live_execution_approval": false,
|
||||
"workitem": "RAPPS-WP-0017",
|
||||
"intent_basis": [
|
||||
"Release Contracts over Source Internals",
|
||||
"Repeatable Operations",
|
||||
"Clear Lower-Layer Handoffs",
|
||||
"Evidence Before Confidence"
|
||||
],
|
||||
"review_artifact_sha256": "a3a2f2db6f0a8461a0d8299825c842c8ce427f59c86238884609d463837b3b75",
|
||||
"reviewed_manifest_sha256": "45d467c2bb48462a388f5df4ba5188492d406bdddbddfbf3487bd2077486dec8",
|
||||
"reviewed_patch_sha256": "39ebaa7f4050f9204fe8ee38364fbbce874903447ac45ccd408cf434aa6e4a73",
|
||||
"gates": [
|
||||
"unknown reviewed hardened image digest",
|
||||
"metadata-only existing authenticated source inventory/migration",
|
||||
"live node headroom/PVC/readiness survey",
|
||||
"server dry-run and client diff",
|
||||
"release-specific EUR estimates and founder live-change approval",
|
||||
"phase A verified before phase B credential delivery",
|
||||
"approved executed platform credential CCR",
|
||||
"native14 URL/7 metadata hash proof then70/70 hosted"
|
||||
],
|
||||
"proposal_id": "CUST81-APPS-PRIVATE-READER-RELEASE-R1",
|
||||
"revision": 2,
|
||||
"refinement_or_rescue": true
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# Apps concrete phase A proposal review
|
||||
|
||||
Disposition: `approved` for proposal preparation and local render only.
|
||||
This supplement preserves the reviewed core R2 patch unchanged. It supplies
|
||||
actual `reuse` Helm release / `reuse` namespace chart rendering with the locally
|
||||
built hardened image digest. It does not authorize publication, execute a live
|
||||
change or approve platform's CCR. INTENT basis: source-controlled release
|
||||
contracts, explicit S3 handoff and evidence before confidence.
|
||||
|
||||
The proposed image is
|
||||
`forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0`.
|
||||
The local build receipt confirms no-network/read-only health and native source
|
||||
hash smoke. Registry publication is false. Before apply the exact reviewed
|
||||
artifact must be published and independently verified by registry manifest
|
||||
digest. If publication changes manifest representation/digest, revise and review
|
||||
the bound proposal rather than silently replacing the digest.
|
||||
|
||||
Current observations are node `92.205.62.239`, CPU requests3560m of4000m (440m
|
||||
free), API100m/256Mi, replicas1 with surge1/unavailable0, PVC
|
||||
`reuse-surface-data` Bound RWO1Gi, and Helm ownership without Argo tracking.
|
||||
The proposed chart retains resources, strategy and PVC. One100m/256Mi surge fits
|
||||
observed CPU headroom; recheck memory, pending pods and node headroom immediately
|
||||
before rollout. RWO single-node does not prohibit two pods on that same node,
|
||||
but driver mount behavior and SQLite concurrent-access readiness remain
|
||||
verification gates. Do not change deployment strategy or PVC to bypass a failure.
|
||||
|
||||
Exact readonly authenticated-source inventory is0 at its receipt timestamp.
|
||||
This clears the observed migration gate, but recheck before apply; no credential
|
||||
or Secret/env was read. Existing Kubernetes-auth `openbao-reuse` store is Ready
|
||||
with namespace/ServiceAccount/role matching the consumer; its current
|
||||
ExternalSecret contains only the two old fields and remains unchanged in phase A.
|
||||
|
||||
Local full-chart render and assertions passed: exact proposed image and14 URL
|
||||
allowlist; same100m/256Mi requests; same1surge/0unavailable/replicas1; no Secret,
|
||||
ExternalSecret or store objects added. Full chart includes existing landing,
|
||||
ingress, services and PVC and must receive normal full-chart server dry-run and
|
||||
client diff, not a Deployment-only narrow review.
|
||||
|
||||
Incremental infrastructure cost is EUR0 because node, replica steady state,
|
||||
requests, services and PVC capacity remain unchanged. Build/registry execution
|
||||
cost is not independently accounted. Credible operational loss is qualitative
|
||||
and not priced in EUR: rollout failure or concurrent SQLite/PVC access may cause
|
||||
service interruption or recovery work. No autopilot grant is inferred. Exact
|
||||
human review must accept these estimates and remaining readiness gates.
|
||||
|
||||
Before founder-authorized deploy, capture current Helm revision and safe rollback
|
||||
reference (old running digest
|
||||
`sha256:2c1514cb3f5e1297ffeeac912ba36a70f8a2591588a5a4643bc58293e97132de`),
|
||||
known recovery receipt and63 public capability baseline. Use actual Helm release
|
||||
`reuse`, namespace`reuse`, owner atomic/wait/timeout path after safe dry-run/diff.
|
||||
No exact deploy command is executed by this proposal. Phase A retains the current
|
||||
runtime Secret fields and supplies no reader. Verify running imageID, readiness,
|
||||
public federation and credential refusal/redirect behavior before phase B.
|
||||
If phase A fails with no reader delivered, restore the approved previous release.
|
||||
After phase B, revoke/remove reader and verify its absence before any vulnerable
|
||||
image restoration. Phase B is independently gated by approved/executed CCR and
|
||||
native14 URL/7 entry-hash proof before70/70 hosted discovery.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"proposal_id": "CUST81-APPS-PHASE-A-LOCAL-IMAGE-R1",
|
||||
"reviewer_owner": "railiance-apps",
|
||||
"disposition": "approved",
|
||||
"approved_scope": "preparation only, local full-chart render, supervised founder review required",
|
||||
"files": {
|
||||
"docs/assessments/cust81-private-reader/phase-a-local-image-r1/apps-owner-release-review.md": "a74e96986798a727fc87c0d3c578601579778dfd8521796d0bb81df8451c751b",
|
||||
"docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-full-chart.proposed.yaml": "36c20c3e16757d3e888a3db02853032b33e791aa04a6055fe001121254345b81",
|
||||
"docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-preparation-receipt.json": "3282a17ae3724619e40dd1f34846d83b2297d9ae00030618dbd269e8e2fd7d39",
|
||||
"docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml": "35284857ec987fc7a929f5c2eecdb44c7d3367f7835904692947de09918ab8f1"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,478 @@
|
|||
# PREPARATION RENDER ONLY; no registry publication or server validation proved.
|
||||
---
|
||||
# Source: reuse-surface/templates/landing-configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: reuse-surface-landing
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
app.kubernetes.io/component: landing
|
||||
data:
|
||||
index.html: |
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<title>REUSE federation endpoint</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #1d2733;
|
||||
--muted: #536271;
|
||||
--line: #d9e0e7;
|
||||
--paper: #f8fafc;
|
||||
--accent: #2066a8;
|
||||
--accent-dark: #164b7e;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 32px 18px;
|
||||
}
|
||||
main {
|
||||
width: min(100%, 720px);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
padding: clamp(28px, 5vw, 48px);
|
||||
box-shadow: 0 18px 48px rgba(29, 39, 51, 0.08);
|
||||
}
|
||||
.eyebrow {
|
||||
margin: 0 0 12px;
|
||||
color: var(--accent-dark);
|
||||
font-size: 0.86rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(2rem, 7vw, 3rem);
|
||||
line-height: 1.05;
|
||||
}
|
||||
p {
|
||||
max-width: 62ch;
|
||||
margin: 18px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
a {
|
||||
color: var(--accent);
|
||||
}
|
||||
.button {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
background: var(--accent);
|
||||
color: #ffffff;
|
||||
padding: 0 18px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
.button:focus,
|
||||
.button:hover {
|
||||
background: var(--accent-dark);
|
||||
}
|
||||
.links {
|
||||
margin-top: 26px;
|
||||
padding-top: 22px;
|
||||
border-top: 1px solid var(--line);
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<p class="eyebrow">reuse.coulomb.social</p>
|
||||
<h1>REUSE federation endpoint</h1>
|
||||
<p>This is the Railiance REUSE capability federation service. It is an API endpoint for automated clients and operators; there is no browser login for this service.</p>
|
||||
<div class="actions">
|
||||
<a class="button" href="/v1/federated">Open federated index</a>
|
||||
</div>
|
||||
<nav class="links" aria-label="Service links">
|
||||
<a href="/health">Health check</a>
|
||||
<a href="/v1/federated">Federated capability index</a>
|
||||
<a href="https://forgejo.coulomb.social/coulomb/railiance-apps/src/branch/main/docs/reuse-surface-on-railiance01.md">Operator runbook</a>
|
||||
</nav>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
# Source: reuse-surface/templates/pvc.yaml
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: reuse-surface-data
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
# Source: reuse-surface/templates/landing-service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: reuse-surface-landing
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
app.kubernetes.io/component: landing
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/component: landing
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
---
|
||||
# Source: reuse-surface/templates/service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: reuse-surface
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/component: api
|
||||
ports:
|
||||
- name: http
|
||||
port: 8000
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
---
|
||||
# Source: reuse-surface/templates/deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: reuse-surface
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
enableServiceLinks: false
|
||||
securityContext:
|
||||
{}
|
||||
containers:
|
||||
- name: reuse-surface
|
||||
image: "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0"
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: ["reuse-surface", "serve"]
|
||||
securityContext:
|
||||
{}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: "reuse-surface-env"
|
||||
env:
|
||||
- name: REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST
|
||||
value: "{\"REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN\":[\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738\"]}"
|
||||
- name: REUSE_SURFACE_LISTEN_HOST
|
||||
value: "0.0.0.0"
|
||||
- name: REUSE_SURFACE_LISTEN_PORT
|
||||
value: "8000"
|
||||
- name: REUSE_SURFACE_DB
|
||||
value: "/data/reuse.db"
|
||||
- name: REUSE_SURFACE_CACHE_DIR
|
||||
value: "/data/cache"
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: reuse-surface-data
|
||||
---
|
||||
# Source: reuse-surface/templates/landing-deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: reuse-surface-landing
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
app.kubernetes.io/component: landing
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/component: landing
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/component: landing
|
||||
spec:
|
||||
enableServiceLinks: false
|
||||
containers:
|
||||
- name: landing
|
||||
image: "nginxinc/nginx-unprivileged:1.27-alpine"
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
limits:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
volumeMounts:
|
||||
- name: landing-page
|
||||
mountPath: /usr/share/nginx/html/index.html
|
||||
subPath: index.html
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: landing-page
|
||||
configMap:
|
||||
name: reuse-surface-landing
|
||||
---
|
||||
# Source: reuse-surface/templates/ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: reuse-surface
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- reuse.coulomb.social
|
||||
secretName: reuse-surface-tls
|
||||
rules:
|
||||
- host: reuse.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /health
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: reuse-surface
|
||||
port:
|
||||
number: 8000
|
||||
- path: /v1
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: reuse-surface
|
||||
port:
|
||||
number: 8000
|
||||
---
|
||||
# Source: reuse-surface/templates/landing-ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: reuse-surface-landing
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
traefik.ingress.kubernetes.io/router.priority: "1"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- reuse.coulomb.social
|
||||
secretName: reuse-surface-tls
|
||||
rules:
|
||||
- host: reuse.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: reuse-surface-landing
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
# Source: reuse-surface/templates/redirect-ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: reuse-surface-http-redirect
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
traefik.ingress.kubernetes.io/router.middlewares: "reuse-reuse-surface-redirect-https@kubernetescrd"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: reuse.coulomb.social
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: reuse-surface
|
||||
port:
|
||||
number: 8000
|
||||
---
|
||||
# Source: reuse-surface/templates/redirect-middleware.yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: reuse-surface-redirect-https
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/instance: reuse
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/version: "0.1.0"
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
helm.sh/chart: reuse-surface-0.1.0
|
||||
spec:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"proposal_id": "CUST81-APPS-PHASE-A-LOCAL-IMAGE-R1",
|
||||
"disposition": "approved",
|
||||
"approved_scope": "concrete proposal preparation and local render only; not publish or production deploy",
|
||||
"workitem": "RAPPS-WP-0017-T02",
|
||||
"release": "reuse",
|
||||
"namespace": "reuse",
|
||||
"deployment": "reuse-surface",
|
||||
"image_reference": "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0",
|
||||
"registry_publication_verified": false,
|
||||
"phase_b_credential_delivery_included": false,
|
||||
"incremental_infrastructure_cost_eur": 0,
|
||||
"cost_basis": "existing node, same requests and replicas, same PVC, no new service or purchased capacity; build/registry execution cost not independently accounted",
|
||||
"credible_loss_bound_eur": null,
|
||||
"risk_basis": "qualitative supervised change: transient rollout or SQLite concurrent-access outage; RWO PVC single-node mounts need preflight; no customer production admitted; data recovery and exact atomic rollback required before execution",
|
||||
"autopilot_authority": false,
|
||||
"local_render_checks": "passed",
|
||||
"evidence": {
|
||||
"hardened-reader-local-image-r1.json": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/hardened-reader-local-image-r1.json",
|
||||
"sha256": "ed7c2516925d7f16b573a70b5e48d5656a4875e9eb94bcc8760a8bdcbe07f59c"
|
||||
},
|
||||
"live-authenticated-source-inventory-r1.json": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/live-authenticated-source-inventory-r1.json",
|
||||
"sha256": "ba669d6231c8c611191c7ad28f5914d6c47332032b23b0ea40a13f753e883073"
|
||||
},
|
||||
"reuse-release-runtime-metadata-r1.json": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/reuse-release-runtime-metadata-r1.json",
|
||||
"sha256": "5ec901cf41b9f52628ff636c23b5bbc4789df6a9118f5cff0ff8518b287357e9"
|
||||
},
|
||||
"reuse-release-owner-eso-metadata-r1.json": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/reuse-release-owner-eso-metadata-r1.json",
|
||||
"sha256": "ff9aa207ae081d27d86820747a993d4bbc290ba2c354eef72388233d6733adb3"
|
||||
}
|
||||
},
|
||||
"observed_runtime": {
|
||||
"node_ip": "92.205.62.239",
|
||||
"cpu_free_m": 440,
|
||||
"api_surge_cpu_m": 100,
|
||||
"api_surge_memory_mi": 256,
|
||||
"pvc": "reuse-surface-data",
|
||||
"pvc_access": "ReadWriteOnce",
|
||||
"pvc_capacity": "1Gi",
|
||||
"previous_image_digest": "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2c1514cb3f5e1297ffeeac912ba36a70f8a2591588a5a4643bc58293e97132de",
|
||||
"authenticated_sources": 0
|
||||
},
|
||||
"gates": [
|
||||
"publish exact reviewed image and verify registry digest; local digest may require reviewed update if registry transforms manifest",
|
||||
"founder approval for exact phase A and later credential CCR/phase B",
|
||||
"recheck target headroom memory node/PVC mount scheduling and authenticated inventory immediately before apply",
|
||||
"bounded safe server dry-run/client diff and current release revision/rollback capture",
|
||||
"no ESO reader field during phase A; verify actual imageID health and63public baseline before phase B",
|
||||
"revoke/remove delivered reader before any downgrade to old vulnerable image"
|
||||
],
|
||||
"render_command": [
|
||||
"helm",
|
||||
"template",
|
||||
"reuse",
|
||||
"charts/reuse-surface",
|
||||
"--namespace",
|
||||
"reuse",
|
||||
"-f",
|
||||
"helm/reuse-surface-values.yaml",
|
||||
"-f",
|
||||
"docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,665 @@
|
|||
diff --git a/docs/assessments/cust81-private-reader/phase-a-local-image-r1/apps-owner-release-review.md b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/apps-owner-release-review.md
|
||||
new file mode 100644
|
||||
index 0000000..0079fe5
|
||||
--- /dev/null
|
||||
+++ b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/apps-owner-release-review.md
|
||||
@@ -0,0 +1,57 @@
|
||||
+# Apps concrete phase A proposal review
|
||||
+
|
||||
+Disposition: `approved` for proposal preparation and local render only.
|
||||
+This supplement preserves the reviewed core R2 patch unchanged. It supplies
|
||||
+actual `reuse` Helm release / `reuse` namespace chart rendering with the locally
|
||||
+built hardened image digest. It does not authorize publication, execute a live
|
||||
+change or approve platform's CCR. INTENT basis: source-controlled release
|
||||
+contracts, explicit S3 handoff and evidence before confidence.
|
||||
+
|
||||
+The proposed image is
|
||||
+`forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0`.
|
||||
+The local build receipt confirms no-network/read-only health and native source
|
||||
+hash smoke. Registry publication is false. Before apply the exact reviewed
|
||||
+artifact must be published and independently verified by registry manifest
|
||||
+digest. If publication changes manifest representation/digest, revise and review
|
||||
+the bound proposal rather than silently replacing the digest.
|
||||
+
|
||||
+Current observations are node `92.205.62.239`, CPU requests3560m of4000m (440m
|
||||
+free), API100m/256Mi, replicas1 with surge1/unavailable0, PVC
|
||||
+`reuse-surface-data` Bound RWO1Gi, and Helm ownership without Argo tracking.
|
||||
+The proposed chart retains resources, strategy and PVC. One100m/256Mi surge fits
|
||||
+observed CPU headroom; recheck memory, pending pods and node headroom immediately
|
||||
+before rollout. RWO single-node does not prohibit two pods on that same node,
|
||||
+but driver mount behavior and SQLite concurrent-access readiness remain
|
||||
+verification gates. Do not change deployment strategy or PVC to bypass a failure.
|
||||
+
|
||||
+Exact readonly authenticated-source inventory is0 at its receipt timestamp.
|
||||
+This clears the observed migration gate, but recheck before apply; no credential
|
||||
+or Secret/env was read. Existing Kubernetes-auth `openbao-reuse` store is Ready
|
||||
+with namespace/ServiceAccount/role matching the consumer; its current
|
||||
+ExternalSecret contains only the two old fields and remains unchanged in phase A.
|
||||
+
|
||||
+Local full-chart render and assertions passed: exact proposed image and14 URL
|
||||
+allowlist; same100m/256Mi requests; same1surge/0unavailable/replicas1; no Secret,
|
||||
+ExternalSecret or store objects added. Full chart includes existing landing,
|
||||
+ingress, services and PVC and must receive normal full-chart server dry-run and
|
||||
+client diff, not a Deployment-only narrow review.
|
||||
+
|
||||
+Incremental infrastructure cost is EUR0 because node, replica steady state,
|
||||
+requests, services and PVC capacity remain unchanged. Build/registry execution
|
||||
+cost is not independently accounted. Credible operational loss is qualitative
|
||||
+and not priced in EUR: rollout failure or concurrent SQLite/PVC access may cause
|
||||
+service interruption or recovery work. No autopilot grant is inferred. Exact
|
||||
+human review must accept these estimates and remaining readiness gates.
|
||||
+
|
||||
+Before founder-authorized deploy, capture current Helm revision and safe rollback
|
||||
+reference (old running digest
|
||||
+`sha256:2c1514cb3f5e1297ffeeac912ba36a70f8a2591588a5a4643bc58293e97132de`),
|
||||
+known recovery receipt and63 public capability baseline. Use actual Helm release
|
||||
+`reuse`, namespace`reuse`, owner atomic/wait/timeout path after safe dry-run/diff.
|
||||
+No exact deploy command is executed by this proposal. Phase A retains the current
|
||||
+runtime Secret fields and supplies no reader. Verify running imageID, readiness,
|
||||
+public federation and credential refusal/redirect behavior before phase B.
|
||||
+If phase A fails with no reader delivered, restore the approved previous release.
|
||||
+After phase B, revoke/remove reader and verify its absence before any vulnerable
|
||||
+image restoration. Phase B is independently gated by approved/executed CCR and
|
||||
+native14 URL/7 entry-hash proof before70/70 hosted discovery.
|
||||
diff --git a/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-full-chart.proposed.yaml b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-full-chart.proposed.yaml
|
||||
new file mode 100644
|
||||
index 0000000..4059c3a
|
||||
--- /dev/null
|
||||
+++ b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-full-chart.proposed.yaml
|
||||
@@ -0,0 +1,478 @@
|
||||
+# PREPARATION RENDER ONLY; no registry publication or server validation proved.
|
||||
+---
|
||||
+# Source: reuse-surface/templates/landing-configmap.yaml
|
||||
+apiVersion: v1
|
||||
+kind: ConfigMap
|
||||
+metadata:
|
||||
+ name: reuse-surface-landing
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ app.kubernetes.io/component: landing
|
||||
+data:
|
||||
+ index.html: |
|
||||
+
|
||||
+ <!doctype html>
|
||||
+ <html lang="en">
|
||||
+ <head>
|
||||
+ <meta charset="utf-8">
|
||||
+ <meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
+ <meta name="robots" content="noindex,nofollow">
|
||||
+ <title>REUSE federation endpoint</title>
|
||||
+ <style>
|
||||
+ :root {
|
||||
+ color-scheme: light;
|
||||
+ --ink: #1d2733;
|
||||
+ --muted: #536271;
|
||||
+ --line: #d9e0e7;
|
||||
+ --paper: #f8fafc;
|
||||
+ --accent: #2066a8;
|
||||
+ --accent-dark: #164b7e;
|
||||
+ }
|
||||
+ * {
|
||||
+ box-sizing: border-box;
|
||||
+ }
|
||||
+ body {
|
||||
+ margin: 0;
|
||||
+ min-height: 100vh;
|
||||
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
+ color: var(--ink);
|
||||
+ background: var(--paper);
|
||||
+ display: grid;
|
||||
+ place-items: center;
|
||||
+ padding: 32px 18px;
|
||||
+ }
|
||||
+ main {
|
||||
+ width: min(100%, 720px);
|
||||
+ border: 1px solid var(--line);
|
||||
+ border-radius: 8px;
|
||||
+ background: #ffffff;
|
||||
+ padding: clamp(28px, 5vw, 48px);
|
||||
+ box-shadow: 0 18px 48px rgba(29, 39, 51, 0.08);
|
||||
+ }
|
||||
+ .eyebrow {
|
||||
+ margin: 0 0 12px;
|
||||
+ color: var(--accent-dark);
|
||||
+ font-size: 0.86rem;
|
||||
+ font-weight: 700;
|
||||
+ text-transform: uppercase;
|
||||
+ }
|
||||
+ h1 {
|
||||
+ margin: 0;
|
||||
+ font-size: clamp(2rem, 7vw, 3rem);
|
||||
+ line-height: 1.05;
|
||||
+ }
|
||||
+ p {
|
||||
+ max-width: 62ch;
|
||||
+ margin: 18px 0 0;
|
||||
+ color: var(--muted);
|
||||
+ font-size: 1rem;
|
||||
+ line-height: 1.65;
|
||||
+ }
|
||||
+ .actions {
|
||||
+ display: flex;
|
||||
+ flex-wrap: wrap;
|
||||
+ gap: 12px;
|
||||
+ margin-top: 28px;
|
||||
+ }
|
||||
+ a {
|
||||
+ color: var(--accent);
|
||||
+ }
|
||||
+ .button {
|
||||
+ display: inline-flex;
|
||||
+ min-height: 44px;
|
||||
+ align-items: center;
|
||||
+ justify-content: center;
|
||||
+ border-radius: 6px;
|
||||
+ background: var(--accent);
|
||||
+ color: #ffffff;
|
||||
+ padding: 0 18px;
|
||||
+ font-weight: 700;
|
||||
+ text-decoration: none;
|
||||
+ }
|
||||
+ .button:focus,
|
||||
+ .button:hover {
|
||||
+ background: var(--accent-dark);
|
||||
+ }
|
||||
+ .links {
|
||||
+ margin-top: 26px;
|
||||
+ padding-top: 22px;
|
||||
+ border-top: 1px solid var(--line);
|
||||
+ display: grid;
|
||||
+ gap: 10px;
|
||||
+ }
|
||||
+ </style>
|
||||
+ </head>
|
||||
+ <body>
|
||||
+ <main>
|
||||
+ <p class="eyebrow">reuse.coulomb.social</p>
|
||||
+ <h1>REUSE federation endpoint</h1>
|
||||
+ <p>This is the Railiance REUSE capability federation service. It is an API endpoint for automated clients and operators; there is no browser login for this service.</p>
|
||||
+ <div class="actions">
|
||||
+ <a class="button" href="/v1/federated">Open federated index</a>
|
||||
+ </div>
|
||||
+ <nav class="links" aria-label="Service links">
|
||||
+ <a href="/health">Health check</a>
|
||||
+ <a href="/v1/federated">Federated capability index</a>
|
||||
+ <a href="https://forgejo.coulomb.social/coulomb/railiance-apps/src/branch/main/docs/reuse-surface-on-railiance01.md">Operator runbook</a>
|
||||
+ </nav>
|
||||
+ </main>
|
||||
+ </body>
|
||||
+ </html>
|
||||
+---
|
||||
+# Source: reuse-surface/templates/pvc.yaml
|
||||
+apiVersion: v1
|
||||
+kind: PersistentVolumeClaim
|
||||
+metadata:
|
||||
+ name: reuse-surface-data
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+spec:
|
||||
+ accessModes:
|
||||
+ - ReadWriteOnce
|
||||
+ resources:
|
||||
+ requests:
|
||||
+ storage: 1Gi
|
||||
+---
|
||||
+# Source: reuse-surface/templates/landing-service.yaml
|
||||
+apiVersion: v1
|
||||
+kind: Service
|
||||
+metadata:
|
||||
+ name: reuse-surface-landing
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ app.kubernetes.io/component: landing
|
||||
+spec:
|
||||
+ type: ClusterIP
|
||||
+ selector:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/component: landing
|
||||
+ ports:
|
||||
+ - name: http
|
||||
+ port: 8080
|
||||
+ targetPort: http
|
||||
+ protocol: TCP
|
||||
+---
|
||||
+# Source: reuse-surface/templates/service.yaml
|
||||
+apiVersion: v1
|
||||
+kind: Service
|
||||
+metadata:
|
||||
+ name: reuse-surface
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+spec:
|
||||
+ type: ClusterIP
|
||||
+ selector:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/component: api
|
||||
+ ports:
|
||||
+ - name: http
|
||||
+ port: 8000
|
||||
+ targetPort: http
|
||||
+ protocol: TCP
|
||||
+---
|
||||
+# Source: reuse-surface/templates/deployment.yaml
|
||||
+apiVersion: apps/v1
|
||||
+kind: Deployment
|
||||
+metadata:
|
||||
+ name: reuse-surface
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ app.kubernetes.io/component: api
|
||||
+spec:
|
||||
+ replicas: 1
|
||||
+ selector:
|
||||
+ matchLabels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ strategy:
|
||||
+ type: RollingUpdate
|
||||
+ rollingUpdate:
|
||||
+ maxSurge: 1
|
||||
+ maxUnavailable: 0
|
||||
+ template:
|
||||
+ metadata:
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/component: api
|
||||
+ spec:
|
||||
+ enableServiceLinks: false
|
||||
+ securityContext:
|
||||
+ {}
|
||||
+ containers:
|
||||
+ - name: reuse-surface
|
||||
+ image: "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0"
|
||||
+ imagePullPolicy: IfNotPresent
|
||||
+ command: ["reuse-surface", "serve"]
|
||||
+ securityContext:
|
||||
+ {}
|
||||
+ ports:
|
||||
+ - name: http
|
||||
+ containerPort: 8000
|
||||
+ protocol: TCP
|
||||
+ envFrom:
|
||||
+ - secretRef:
|
||||
+ name: "reuse-surface-env"
|
||||
+ env:
|
||||
+ - name: REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST
|
||||
+ value: "{\"REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN\":[\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738\",\"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738\"]}"
|
||||
+ - name: REUSE_SURFACE_LISTEN_HOST
|
||||
+ value: "0.0.0.0"
|
||||
+ - name: REUSE_SURFACE_LISTEN_PORT
|
||||
+ value: "8000"
|
||||
+ - name: REUSE_SURFACE_DB
|
||||
+ value: "/data/reuse.db"
|
||||
+ - name: REUSE_SURFACE_CACHE_DIR
|
||||
+ value: "/data/cache"
|
||||
+ volumeMounts:
|
||||
+ - name: data
|
||||
+ mountPath: /data
|
||||
+ readinessProbe:
|
||||
+ httpGet:
|
||||
+ path: /health
|
||||
+ port: 8000
|
||||
+ initialDelaySeconds: 10
|
||||
+ periodSeconds: 10
|
||||
+ timeoutSeconds: 5
|
||||
+ failureThreshold: 3
|
||||
+ livenessProbe:
|
||||
+ httpGet:
|
||||
+ path: /health
|
||||
+ port: 8000
|
||||
+ initialDelaySeconds: 15
|
||||
+ periodSeconds: 30
|
||||
+ timeoutSeconds: 5
|
||||
+ failureThreshold: 3
|
||||
+ resources:
|
||||
+ limits:
|
||||
+ cpu: 500m
|
||||
+ memory: 512Mi
|
||||
+ requests:
|
||||
+ cpu: 100m
|
||||
+ memory: 256Mi
|
||||
+ volumes:
|
||||
+ - name: data
|
||||
+ persistentVolumeClaim:
|
||||
+ claimName: reuse-surface-data
|
||||
+---
|
||||
+# Source: reuse-surface/templates/landing-deployment.yaml
|
||||
+apiVersion: apps/v1
|
||||
+kind: Deployment
|
||||
+metadata:
|
||||
+ name: reuse-surface-landing
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ app.kubernetes.io/component: landing
|
||||
+spec:
|
||||
+ replicas: 1
|
||||
+ selector:
|
||||
+ matchLabels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/component: landing
|
||||
+ strategy:
|
||||
+ type: RollingUpdate
|
||||
+ rollingUpdate:
|
||||
+ maxSurge: 1
|
||||
+ maxUnavailable: 0
|
||||
+ template:
|
||||
+ metadata:
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/component: landing
|
||||
+ spec:
|
||||
+ enableServiceLinks: false
|
||||
+ containers:
|
||||
+ - name: landing
|
||||
+ image: "nginxinc/nginx-unprivileged:1.27-alpine"
|
||||
+ imagePullPolicy: IfNotPresent
|
||||
+ ports:
|
||||
+ - name: http
|
||||
+ containerPort: 8080
|
||||
+ protocol: TCP
|
||||
+ readinessProbe:
|
||||
+ httpGet:
|
||||
+ path: /
|
||||
+ port: http
|
||||
+ initialDelaySeconds: 3
|
||||
+ periodSeconds: 10
|
||||
+ timeoutSeconds: 3
|
||||
+ failureThreshold: 3
|
||||
+ livenessProbe:
|
||||
+ httpGet:
|
||||
+ path: /
|
||||
+ port: http
|
||||
+ initialDelaySeconds: 10
|
||||
+ periodSeconds: 30
|
||||
+ timeoutSeconds: 3
|
||||
+ failureThreshold: 3
|
||||
+ resources:
|
||||
+ limits:
|
||||
+ cpu: 50m
|
||||
+ memory: 64Mi
|
||||
+ requests:
|
||||
+ cpu: 10m
|
||||
+ memory: 32Mi
|
||||
+ volumeMounts:
|
||||
+ - name: landing-page
|
||||
+ mountPath: /usr/share/nginx/html/index.html
|
||||
+ subPath: index.html
|
||||
+ readOnly: true
|
||||
+ volumes:
|
||||
+ - name: landing-page
|
||||
+ configMap:
|
||||
+ name: reuse-surface-landing
|
||||
+---
|
||||
+# Source: reuse-surface/templates/ingress.yaml
|
||||
+apiVersion: networking.k8s.io/v1
|
||||
+kind: Ingress
|
||||
+metadata:
|
||||
+ name: reuse-surface
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ annotations:
|
||||
+ cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
+ traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
+ traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
+spec:
|
||||
+ ingressClassName: traefik
|
||||
+ tls:
|
||||
+ - hosts:
|
||||
+ - reuse.coulomb.social
|
||||
+ secretName: reuse-surface-tls
|
||||
+ rules:
|
||||
+ - host: reuse.coulomb.social
|
||||
+ http:
|
||||
+ paths:
|
||||
+ - path: /health
|
||||
+ pathType: Exact
|
||||
+ backend:
|
||||
+ service:
|
||||
+ name: reuse-surface
|
||||
+ port:
|
||||
+ number: 8000
|
||||
+ - path: /v1
|
||||
+ pathType: Prefix
|
||||
+ backend:
|
||||
+ service:
|
||||
+ name: reuse-surface
|
||||
+ port:
|
||||
+ number: 8000
|
||||
+---
|
||||
+# Source: reuse-surface/templates/landing-ingress.yaml
|
||||
+apiVersion: networking.k8s.io/v1
|
||||
+kind: Ingress
|
||||
+metadata:
|
||||
+ name: reuse-surface-landing
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ annotations:
|
||||
+ traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
+ traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
+ traefik.ingress.kubernetes.io/router.priority: "1"
|
||||
+spec:
|
||||
+ ingressClassName: traefik
|
||||
+ tls:
|
||||
+ - hosts:
|
||||
+ - reuse.coulomb.social
|
||||
+ secretName: reuse-surface-tls
|
||||
+ rules:
|
||||
+ - host: reuse.coulomb.social
|
||||
+ http:
|
||||
+ paths:
|
||||
+ - path: /
|
||||
+ pathType: Prefix
|
||||
+ backend:
|
||||
+ service:
|
||||
+ name: reuse-surface-landing
|
||||
+ port:
|
||||
+ number: 8080
|
||||
+---
|
||||
+# Source: reuse-surface/templates/redirect-ingress.yaml
|
||||
+apiVersion: networking.k8s.io/v1
|
||||
+kind: Ingress
|
||||
+metadata:
|
||||
+ name: reuse-surface-http-redirect
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+ annotations:
|
||||
+ traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
+ traefik.ingress.kubernetes.io/router.middlewares: "reuse-reuse-surface-redirect-https@kubernetescrd"
|
||||
+spec:
|
||||
+ ingressClassName: traefik
|
||||
+ rules:
|
||||
+ - host: reuse.coulomb.social
|
||||
+ http:
|
||||
+ paths:
|
||||
+ - path: /
|
||||
+ pathType: Prefix
|
||||
+ backend:
|
||||
+ service:
|
||||
+ name: reuse-surface
|
||||
+ port:
|
||||
+ number: 8000
|
||||
+---
|
||||
+# Source: reuse-surface/templates/redirect-middleware.yaml
|
||||
+apiVersion: traefik.io/v1alpha1
|
||||
+kind: Middleware
|
||||
+metadata:
|
||||
+ name: reuse-surface-redirect-https
|
||||
+ labels:
|
||||
+ app.kubernetes.io/name: reuse-surface
|
||||
+ app.kubernetes.io/instance: reuse
|
||||
+ app.kubernetes.io/managed-by: Helm
|
||||
+ app.kubernetes.io/version: "0.1.0"
|
||||
+ app.kubernetes.io/part-of: railiance-apps
|
||||
+ helm.sh/chart: reuse-surface-0.1.0
|
||||
+spec:
|
||||
+ redirectScheme:
|
||||
+ scheme: https
|
||||
+ permanent: true
|
||||
diff --git a/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-preparation-receipt.json b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-preparation-receipt.json
|
||||
new file mode 100644
|
||||
index 0000000..c58f4b4
|
||||
--- /dev/null
|
||||
+++ b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-preparation-receipt.json
|
||||
@@ -0,0 +1,67 @@
|
||||
+{
|
||||
+ "proposal_id": "CUST81-APPS-PHASE-A-LOCAL-IMAGE-R1",
|
||||
+ "disposition": "approved",
|
||||
+ "approved_scope": "concrete proposal preparation and local render only; not publish or production deploy",
|
||||
+ "workitem": "RAPPS-WP-0017-T02",
|
||||
+ "release": "reuse",
|
||||
+ "namespace": "reuse",
|
||||
+ "deployment": "reuse-surface",
|
||||
+ "image_reference": "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0",
|
||||
+ "registry_publication_verified": false,
|
||||
+ "phase_b_credential_delivery_included": false,
|
||||
+ "incremental_infrastructure_cost_eur": 0,
|
||||
+ "cost_basis": "existing node, same requests and replicas, same PVC, no new service or purchased capacity; build/registry execution cost not independently accounted",
|
||||
+ "credible_loss_bound_eur": null,
|
||||
+ "risk_basis": "qualitative supervised change: transient rollout or SQLite concurrent-access outage; RWO PVC single-node mounts need preflight; no customer production admitted; data recovery and exact atomic rollback required before execution",
|
||||
+ "autopilot_authority": false,
|
||||
+ "local_render_checks": "passed",
|
||||
+ "evidence": {
|
||||
+ "hardened-reader-local-image-r1.json": {
|
||||
+ "path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/hardened-reader-local-image-r1.json",
|
||||
+ "sha256": "ed7c2516925d7f16b573a70b5e48d5656a4875e9eb94bcc8760a8bdcbe07f59c"
|
||||
+ },
|
||||
+ "live-authenticated-source-inventory-r1.json": {
|
||||
+ "path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/live-authenticated-source-inventory-r1.json",
|
||||
+ "sha256": "ba669d6231c8c611191c7ad28f5914d6c47332032b23b0ea40a13f753e883073"
|
||||
+ },
|
||||
+ "reuse-release-runtime-metadata-r1.json": {
|
||||
+ "path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/reuse-release-runtime-metadata-r1.json",
|
||||
+ "sha256": "5ec901cf41b9f52628ff636c23b5bbc4789df6a9118f5cff0ff8518b287357e9"
|
||||
+ },
|
||||
+ "reuse-release-owner-eso-metadata-r1.json": {
|
||||
+ "path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/reuse-release-owner-eso-metadata-r1.json",
|
||||
+ "sha256": "ff9aa207ae081d27d86820747a993d4bbc290ba2c354eef72388233d6733adb3"
|
||||
+ }
|
||||
+ },
|
||||
+ "observed_runtime": {
|
||||
+ "node_ip": "92.205.62.239",
|
||||
+ "cpu_free_m": 440,
|
||||
+ "api_surge_cpu_m": 100,
|
||||
+ "api_surge_memory_mi": 256,
|
||||
+ "pvc": "reuse-surface-data",
|
||||
+ "pvc_access": "ReadWriteOnce",
|
||||
+ "pvc_capacity": "1Gi",
|
||||
+ "previous_image_digest": "forgejo.coulomb.social/coulomb/reuse-surface@sha256:2c1514cb3f5e1297ffeeac912ba36a70f8a2591588a5a4643bc58293e97132de",
|
||||
+ "authenticated_sources": 0
|
||||
+ },
|
||||
+ "gates": [
|
||||
+ "publish exact reviewed image and verify registry digest; local digest may require reviewed update if registry transforms manifest",
|
||||
+ "founder approval for exact phase A and later credential CCR/phase B",
|
||||
+ "recheck target headroom memory node/PVC mount scheduling and authenticated inventory immediately before apply",
|
||||
+ "bounded safe server dry-run/client diff and current release revision/rollback capture",
|
||||
+ "no ESO reader field during phase A; verify actual imageID health and63public baseline before phase B",
|
||||
+ "revoke/remove delivered reader before any downgrade to old vulnerable image"
|
||||
+ ],
|
||||
+ "render_command": [
|
||||
+ "helm",
|
||||
+ "template",
|
||||
+ "reuse",
|
||||
+ "charts/reuse-surface",
|
||||
+ "--namespace",
|
||||
+ "reuse",
|
||||
+ "-f",
|
||||
+ "helm/reuse-surface-values.yaml",
|
||||
+ "-f",
|
||||
+ "docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml"
|
||||
+ ]
|
||||
+}
|
||||
diff --git a/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml
|
||||
new file mode 100644
|
||||
index 0000000..6e6f81e
|
||||
--- /dev/null
|
||||
+++ b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml
|
||||
@@ -0,0 +1,21 @@
|
||||
+# PROPOSAL ONLY; digest is locally built, NOT published.
|
||||
+# Publish exact reviewed artifact and verify registry digest before any apply.
|
||||
+image:
|
||||
+ repository: forgejo.coulomb.social/coulomb/reuse-surface
|
||||
+ digest: sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0
|
||||
+authSourceAllowlist:
|
||||
+ REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN:
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738
|
||||
+ - https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738
|
||||
diff --git a/docs/assessments/cust81-private-reader/phase-a-local-image-r1/manifest.json b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/manifest.json
|
||||
new file mode 100644
|
||||
index 0000000..15cc477
|
||||
--- /dev/null
|
||||
+++ b/docs/assessments/cust81-private-reader/phase-a-local-image-r1/manifest.json
|
||||
@@ -0,0 +1,12 @@
|
||||
+{
|
||||
+ "proposal_id": "CUST81-APPS-PHASE-A-LOCAL-IMAGE-R1",
|
||||
+ "reviewer_owner": "railiance-apps",
|
||||
+ "disposition": "approved",
|
||||
+ "approved_scope": "preparation only, local full-chart render, supervised founder review required",
|
||||
+ "files": {
|
||||
+ "docs/assessments/cust81-private-reader/phase-a-local-image-r1/apps-owner-release-review.md": "a74e96986798a727fc87c0d3c578601579778dfd8521796d0bb81df8451c751b",
|
||||
+ "docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-full-chart.proposed.yaml": "36c20c3e16757d3e888a3db02853032b33e791aa04a6055fe001121254345b81",
|
||||
+ "docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-preparation-receipt.json": "3282a17ae3724619e40dd1f34846d83b2297d9ae00030618dbd269e8e2fd7d39",
|
||||
+ "docs/assessments/cust81-private-reader/phase-a-local-image-r1/phase-a-values.proposed.yaml": "35284857ec987fc7a929f5c2eecdb44c7d3367f7835904692947de09918ab8f1"
|
||||
+ }
|
||||
+}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# PROPOSAL ONLY; digest is locally built, NOT published.
|
||||
# Publish exact reviewed artifact and verify registry digest before any apply.
|
||||
image:
|
||||
repository: forgejo.coulomb.social/coulomb/reuse-surface
|
||||
digest: sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0
|
||||
authSourceAllowlist:
|
||||
REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN:
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738
|
||||
- https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
# Apps phase B delivery preparation review
|
||||
|
||||
Disposition: `approved` for exact bounded preparation only. No founder credential,
|
||||
source-activation or production rollout authorization is inferred. Own workitem
|
||||
RAPPS-WP-0017-T03; INTENT basis is release contracts, explicit S3 handoffs,
|
||||
repeatable recovery and evidence before confidence.
|
||||
|
||||
Full ExternalSecret server dry-run accepted and client diff verified only the
|
||||
single new reader item plus native remoteRef defaults. Both old fields, existing
|
||||
store, target and all other source spec/metadata are unchanged. The first strict
|
||||
live equality check correctly refused on native ESO defaults, with no apply;
|
||||
subsequent projection explicitly verified those defaults. The approved source
|
||||
replacement is distinct from the current active file and must replace it only
|
||||
after consent, then pass normal owner source publication before live apply.
|
||||
Server acceptance does not prove the dedicated KV field or ESO read policy exists.
|
||||
|
||||
Phase B must follow verified phase A *and* terminal verified platform provisioning
|
||||
under the exact approved CCR. Verify current API pod ready, spec image index and
|
||||
imageID against published OCI index or its linux/amd64 child (exclude attestation
|
||||
manifest), source federation/private verification module hashes and exact14 URL
|
||||
allowlist. Confirm ESO Ready and selected reader-key presence only, then controlled
|
||||
restart with fresh headroom/PVC readiness and capture-only output. No token or env
|
||||
value enters argv, source, receipts or logs.
|
||||
|
||||
The native proof wrapper consumes only three exact-byte non-secret packets via
|
||||
stdin, verifies their compiled hashes and module/source hashes, verifies the
|
||||
complete14 URL allowlist, and invokes the application's native fetch path without
|
||||
cache. Temporary packet files are0600 and removed. Four local refusal fixtures
|
||||
passed; no real credentials or network requests were used. Its output must be
|
||||
combined with separately verified API pod UID/readiness/image metadata; a
|
||||
process receipt alone does not prove the target pod identity. Any missing,
|
||||
mis-hashed, non-exact scope or native fetch outcome refuses success. Native
|
||||
proof requires all7 sources and14 metadata fetches before registrations/final70.
|
||||
|
||||
Rollback ordering is explicit: revoke dedicated Forgejo PAT and verify; restore
|
||||
source-controlled two-field ESO mapping to stop rematerialization; remove only
|
||||
the materialized reader key through a captured exact JSON patch; restart the
|
||||
current hardened deployment and verify reader absence in new pod/materialized
|
||||
Secret plus issuer revocation. Only then may an independently approved rollback
|
||||
to vulnerable Helm revision10 occur. No old-token fallback, default DB restore,
|
||||
credential re-mint or Secret/env dump is proposed.
|
||||
|
||||
All exact predecessor/replacement digests, SSH delivery/restart/removal commands,
|
||||
proof hashes/packet paths and gate schema are in phase-b-execution-proposal-r1.json.
|
||||
No credential creation, ESO apply, Secret mutation, restart or deploy was executed.
|
||||
|
||||
Revision2 of the same proposal corrects malformed shell quoting in the original
|
||||
reader-removal command. Original proposal/review/manifest remain archived.
|
||||
The replacement is structured SSH argv with `shlex.join` over exact remote
|
||||
kubectl argv and compact JSON patch. Offline shell parsing roundtrips exactly
|
||||
one removal operation at `/data/REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN`; no
|
||||
other key is touched. This refinement is not another successful trial.
|
||||
Disposition remains approved preparation only, never founder live authority.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"id": "CUST81-PHASE-B-COMMAND-PARSER-CHECK-R2",
|
||||
"status": "passed",
|
||||
"proposal_revision": 2,
|
||||
"original_proposal_sha256": "sha256:572af68fcf56072c1fd846101a76611885acd6efedfe5dba10d96d9f77f701e0",
|
||||
"ssh_argv_count": 3,
|
||||
"remote_argv_roundtrip": true,
|
||||
"patch_operation_count": 1,
|
||||
"patch_op": "remove",
|
||||
"patch_path": "/data/REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN",
|
||||
"other_field_changes": false,
|
||||
"live_command_executed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"id": "CUST81-APPS-PHASE-B-ESO-PREFLIGHT-R1",
|
||||
"checked_at": "2026-10-14T23:55:29.993576+00:00",
|
||||
"target_node_ip": "92.205.62.239",
|
||||
"status": "verified_preparation",
|
||||
"active_predecessor_path": "/home/worsch/railiance-apps/manifests/reuse-surface-runtime-externalsecret.yaml",
|
||||
"active_predecessor_sha256": "e036084aec9fad3bea63cc0d591c3b40a392ab9f54d9226a2d2b3fd7194d8a47",
|
||||
"proposed_replacement_path": "/home/worsch/railiance-apps/docs/assessments/cust81-private-reader/reuse-surface-runtime-externalsecret.r1.yaml",
|
||||
"proposed_replacement_sha256": "adfb34c0ee4d01bc602f57cd2320f1658465fb8f8660978828fffa05a5b604b5",
|
||||
"old_two_fields_retained": true,
|
||||
"existing_store_unchanged": true,
|
||||
"live_spec_projected_matches_predecessor": true,
|
||||
"native_remoteRef_defaults_verified": true,
|
||||
"previous_attempt": "full live data equality assertion refused on native ESO defaults; no command apply executed, original refusal retained",
|
||||
"results": [
|
||||
{
|
||||
"operation": "server_dry_run",
|
||||
"exit_code": 0,
|
||||
"raw_stdout_stderr_discarded": true
|
||||
},
|
||||
{
|
||||
"operation": "client_diff",
|
||||
"exit_code": 1,
|
||||
"exact_new_reader_item_plus_native_defaults_only": true,
|
||||
"raw_values_annotations_stderr_discarded": true
|
||||
}
|
||||
],
|
||||
"credentials_provisioned": false,
|
||||
"live_apply_performed": false,
|
||||
"limitation": "server acceptance does not prove dedicated KV field exists or ESO consumer policy reader path granted"
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"proposal_id": "CUST81-APPS-PHASE-B-EXECUTION-PROPOSAL-R1",
|
||||
"revision": 2,
|
||||
"owner": "railiance-apps",
|
||||
"disposition": "approved",
|
||||
"approved_scope": "bounded source preparation only; no active ESO or live action",
|
||||
"founder_live_approval": false,
|
||||
"refinement_or_rescue": true,
|
||||
"files": {
|
||||
"apps-owner-phase-b-review.md": "sha256:496c842f4a284d6b2904c027dfd1e8aab85567f19fa74a228f27c4cc578ce026",
|
||||
"command-parser-check-r2.json": "sha256:5080bdd2ece186200aec1a37b4450c99a50d435444f0f850277b82252516d6e1",
|
||||
"eso-preflight-r1.json": "sha256:e39f07427af80fd036e6eec21202efe194fff2db67b8523bb07052e1388d8fab",
|
||||
"native-proof-wrapper-local-checks-r1.json": "sha256:c5fcee128f7b4653ecfc0c455aab57a7efa8f6a9de382af69d447d8065091bab",
|
||||
"native_runtime_proof_stdin.py": "sha256:1edc317e8563a120b62a6830eebb2875c144b45c57a491eb7da849095c5d1933",
|
||||
"phase-b-execution-proposal-r1.json": "sha256:13f19158e09b0693a64ff400ecbc3e5ec09d28673731e87733d1e2fad9bd3222",
|
||||
"run_eso_preflight.py": "sha256:fd42789518e9a6c1305fc3828bf1afb8c9a4e8238b4eeed30f7e7ec05656f09b"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "CUST81-NATIVE-RUNTIME-PROOF-PREPARATION-CHECKS-R1",
|
||||
"status": "passed",
|
||||
"wrapper_sha256": "1edc317e8563a120b62a6830eebb2875c144b45c57a491eb7da849095c5d1933",
|
||||
"fixture_cases": [
|
||||
"missing_reader",
|
||||
"malformed_allowlist",
|
||||
"tampered_packet",
|
||||
"native_refusal"
|
||||
],
|
||||
"native_network_requests": 0,
|
||||
"real_credentials_used": false,
|
||||
"failure_output_sentinel_excluded": true,
|
||||
"scope_refusal_before_any_fetch": true
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
"""Approved preparation only: execute in verified hardened API pod after phase B.
|
||||
stdin envelope contains only three base64-encoded hash-bound non-secret packets.
|
||||
No token accepted; native module consumes its reviewed runtime source credential.
|
||||
"""
|
||||
import base64,hashlib,json,os,tempfile
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from reuse_surface import federation, private_source_verify as native
|
||||
EXPECTED={'proposal':'7819eb51d249f5c7aff4df9fb520afcfb76603b05a4104439499102b6c20af99','disclosure':'46ac4fe49a08054cc215aade2fbaf3b36ee334520997857ed84f530b52da9d35','consent':'4a4602ad926e3af855d6ddba4731430d33e4a7432c507ba0a5193b9f43056b52'}
|
||||
FEDI='dbaa40f25cc0f090143578cd1c7068cb2cb00960fafd17d5239bf8a5bac514dd'
|
||||
NATIVE='c67a284091cc4dd62dca56658a14b8ea1357e2b3df1518120da4c51e0ee8d85c'
|
||||
receipt={'id':'CUST81-NATIVE-RUNTIME-PROOF-R1','status':'unverified','raw_errors_or_values_emitted':False}
|
||||
try:
|
||||
source_sha=hashlib.sha256(Path(federation.__file__).read_bytes()).hexdigest()
|
||||
helper_sha=hashlib.sha256(Path(native.__file__).read_bytes()).hexdigest()
|
||||
assert source_sha==FEDI and helper_sha==NATIVE
|
||||
envelope=json.loads(sys.stdin.buffer.read(1048576))
|
||||
assert set(envelope)==set(EXPECTED)
|
||||
with tempfile.TemporaryDirectory(prefix='cust81-native-packets-') as temporary:
|
||||
paths={}
|
||||
for name,digest in EXPECTED.items():
|
||||
content=base64.b64decode(envelope[name],validate=True)
|
||||
assert hashlib.sha256(content).hexdigest()==digest
|
||||
p=Path(temporary)/(name+'.json');p.write_bytes(content);p.chmod(0o600);paths[name]=p
|
||||
sources=list(native._prepared_sources(paths['proposal'],paths['disclosure'],paths['consent']))
|
||||
expected_urls={native._raw_url(source['repo'],source['consented_commit'],path) for source,files in sources for path in files}
|
||||
allow=json.loads(os.environ.get('REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST','{}'))
|
||||
assert set(allow)=={native.AUTH_ENV} and len(allow[native.AUTH_ENV])==14 and set(allow[native.AUTH_ENV])==expected_urls
|
||||
# Presence only: never print/read the secret value here. Native acquisition
|
||||
# is subsequently performed only at its reviewed exact URL routing boundary.
|
||||
key_present=native.AUTH_ENV in os.environ
|
||||
assert key_present
|
||||
result=native.verify(paths['proposal'],paths['disclosure'],paths['consent'])
|
||||
receipt.update(source_sha256=source_sha,helper_sha256=helper_sha,packet_sha256=EXPECTED,runtime_allowlist_exact14=True,reader_key_present=True,native_fetch=result,status='verified' if result['all_verified'] else 'native_fetch_failed')
|
||||
except Exception:
|
||||
receipt.update(status='runtime_or_native_proof_refused',error='hash_scope_or_native_validation_failed')
|
||||
print(json.dumps(receipt,sort_keys=True))
|
||||
raise SystemExit(0 if receipt['status']=='verified' else 1)
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"id": "CUST81-APPS-PHASE-B-EXECUTION-PROPOSAL-R1",
|
||||
"disposition": "approved",
|
||||
"approved_scope": "bounded preparation only; founder live go still required",
|
||||
"status": "prepared_not_executed",
|
||||
"workitem": "RAPPS-WP-0017-T03",
|
||||
"active_predecessor_sha256": "e036084aec9fad3bea63cc0d591c3b40a392ab9f54d9226a2d2b3fd7194d8a47",
|
||||
"replacement_sha256": "adfb34c0ee4d01bc602f57cd2320f1658465fb8f8660978828fffa05a5b604b5",
|
||||
"preconditions": [
|
||||
"verified phase A current API pod ready pinned hardened image/source hashes",
|
||||
"platform provisioner terminal verified and approved CCR revision executed",
|
||||
"Forgejo exact7 readonly grants and PAT receipt verified",
|
||||
"native ESO path-read policy/store issuer receipt verified",
|
||||
"founder consent to exact phase B source update and delivery"
|
||||
],
|
||||
"source_update_after_consent": {
|
||||
"command": "cp docs/assessments/cust81-private-reader/reuse-surface-runtime-externalsecret.r1.yaml manifests/reuse-surface-runtime-externalsecret.yaml",
|
||||
"publication": "owner commits only exact replacement and normal rmgr sync --push; verify primary exact source commit before liveapply",
|
||||
"not_executed": true
|
||||
},
|
||||
"apply_command": "ssh railiance01 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f /tmp/cust81-phase-b/reuse-surface-runtime-externalsecret.yaml'",
|
||||
"sync_verify_before_restart": "wait for ESO Ready and selected reader-key-presence boolean via reviewed captured-output wrapper; never raw Secret/annotation/env output",
|
||||
"restart_command": "ssh railiance01 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl rollout restart deployment/reuse-surface --namespace reuse'",
|
||||
"restart_wait_command": "ssh railiance01 'KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl rollout status deployment/reuse-surface --namespace reuse --timeout=300s'",
|
||||
"headroom_gate": "recheck100m/256Mi surge headroom/PVC and actual hardened deployment before restart",
|
||||
"cleanup_order": [
|
||||
"credential owner revokes exact dedicated Forgejo PAT and verifies revocation; no oldtoken fallback",
|
||||
"restore reviewed two-field ESO manifest through source-controlled owner lane and apply, stops rematerialization",
|
||||
"remove only REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN from reuse/reuse-surface-env with captured-output exact JSON patch",
|
||||
"restart/wait current hardened deployment, verify reader-key absence boolean in new pod and materialized Secret plus exact token revocation",
|
||||
"only then may separately approved Helm rollback reuse10 restore vulnerable priorimage; do not restore DB by default"
|
||||
],
|
||||
"raw_output_contract": "all mutating/proof child stdout/stderr captured/discarded; only whitelisted outcome/proof JSON may reach receipts",
|
||||
"runtime_proof": {
|
||||
"pod_metadata_schema": [
|
||||
"verified API pod name and UID",
|
||||
"Ready true",
|
||||
"expected spec image OCI indexsha2364b7e...",
|
||||
"actual imageID equals index or linux/amd64childsha1a69354af2bc87d4baaecc4b02e13fda468efd3746024f440cc6d138beeee2ab",
|
||||
"node92.205.62.239",
|
||||
"source/header-routing/native helper hashes verified",
|
||||
"exact14 allowlist boolean",
|
||||
"reader-key presence boolean",
|
||||
"native14fetch/7consentedhashall_verified"
|
||||
],
|
||||
"wrapper_path": "/tmp/cust81-apps-reader-owner/docs/assessments/cust81-private-reader/phase-b-r1/native_runtime_proof_stdin.py",
|
||||
"wrapper_sha256": "1edc317e8563a120b62a6830eebb2875c144b45c57a491eb7da849095c5d1933",
|
||||
"packets": {
|
||||
"proposal": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/private-reader-provisioning-preparation-r1.json",
|
||||
"sha256": "7819eb51d249f5c7aff4df9fb520afcfb76603b05a4104439499102b6c20af99"
|
||||
},
|
||||
"disclosure": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/private-source-disclosure-proposal-r1.json",
|
||||
"sha256": "46ac4fe49a08054cc215aade2fbaf3b36ee334520997857ed84f530b52da9d35"
|
||||
},
|
||||
"consent": {
|
||||
"path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/private-source-owner-consent-r1.json",
|
||||
"sha256": "4a4602ad926e3af855d6ddba4731430d33e4a7432c507ba0a5193b9f43056b52"
|
||||
}
|
||||
},
|
||||
"invocation": "capture kubectl --kubeconfig <verifiedconfig> -n reuse exec -i <metadata-verified current API pod> -c reuse-surface -- python3 -c <exact hash-bound wrapper source>; stdin JSON envelope {proposal:base64(exactbytes),disclosure:base64(exactbytes),consent:base64(exactbytes)}; no token/credential in argv or stdin; wrapper writes/removes only temporary nonsecret packet files",
|
||||
"exit_success_required": true
|
||||
},
|
||||
"no_live_credential_or_apply_action": true,
|
||||
"revision": 2,
|
||||
"refinement_or_rescue": true,
|
||||
"original_revision_sha256": "sha256:572af68fcf56072c1fd846101a76611885acd6efedfe5dba10d96d9f77f701e0",
|
||||
"reader_key_removal_ssh_argv": [
|
||||
"ssh",
|
||||
"railiance01",
|
||||
"env KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl patch secret reuse-surface-env --namespace reuse --type=json -p '[{\"op\":\"remove\",\"path\":\"/data/REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN\"}]'"
|
||||
],
|
||||
"reader_key_removal_remote_argv": [
|
||||
"env",
|
||||
"KUBECONFIG=/etc/rancher/k3s/k3s.yaml",
|
||||
"kubectl",
|
||||
"patch",
|
||||
"secret",
|
||||
"reuse-surface-env",
|
||||
"--namespace",
|
||||
"reuse",
|
||||
"--type=json",
|
||||
"-p",
|
||||
"[{\"op\":\"remove\",\"path\":\"/data/REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN\"}]"
|
||||
],
|
||||
"reader_key_removal_offline_check": "passed shell roundtrip exact single remove op on only selected reader key; no other data field/annotation/spec touched"
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from pathlib import Path
|
||||
import subprocess,json,yaml,hashlib,datetime
|
||||
art=Path(__file__).resolve().parent
|
||||
canonical=Path('/home/worsch/railiance-apps')
|
||||
old=canonical/'manifests/reuse-surface-runtime-externalsecret.yaml';new=canonical/'docs/assessments/cust81-private-reader/reuse-surface-runtime-externalsecret.r1.yaml'
|
||||
base=yaml.safe_load(old.read_text());proposal=yaml.safe_load(new.read_text())
|
||||
assert proposal['spec']['data'][:-1]==base['spec']['data']
|
||||
item=proposal['spec']['data'][-1]
|
||||
assert item=={'secretKey':'REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN','remoteRef':{'key':'workloads/reuse/reuse-surface/forgejo-source-read','property':'FORGEJO_SOURCE_TOKEN'}}
|
||||
assert {k:v for k,v in proposal.items() if k!='spec'}=={k:v for k,v in base.items() if k!='spec'}
|
||||
assert {k:v for k,v in proposal['spec'].items() if k!='data'}=={k:v for k,v in base['spec'].items() if k!='data'}
|
||||
kube=['kubectl','--kubeconfig','/home/worsch/.kube/config-railiance01']
|
||||
p=subprocess.run(kube+['get','nodes','-o','json'],capture_output=True,timeout=20);nodes=json.loads(p.stdout);assert p.returncode==0 and len(nodes['items'])==1 and any(a['address']=='92.205.62.239' for a in nodes['items'][0]['status']['addresses'])
|
||||
p=subprocess.run(kube+['-n','reuse','get','externalsecret','reuse-surface-runtime','-o','json'],capture_output=True,timeout=20);assert p.returncode==0;live=json.loads(p.stdout)
|
||||
rows=[{'secretKey':r['secretKey'],'remoteRef':{k:r['remoteRef'][k] for k in ('key','property')}} for r in live['spec']['data']]
|
||||
assert rows==base['spec']['data'] and live['spec']['secretStoreRef']==base['spec']['secretStoreRef']
|
||||
assert all({k:v for k,v in r['remoteRef'].items() if k not in ('key','property')}=={'conversionStrategy':'Default','decodingStrategy':'None','metadataPolicy':'None'} for r in live['spec']['data'])
|
||||
results=[]
|
||||
for args in [['apply','--dry-run=server','-f',str(new)],['diff','-f',str(new)]]:
|
||||
p=subprocess.run(kube+['-n','reuse']+args,capture_output=True,timeout=60)
|
||||
if args[0]=='diff':
|
||||
changed=[line[1:].strip() for line in p.stdout.decode(errors='replace').splitlines() if line.startswith(('+','-')) and not line.startswith(('+++','---'))]
|
||||
intended={'- remoteRef:', 'key: workloads/reuse/reuse-surface/forgejo-source-read','property: FORGEJO_SOURCE_TOKEN','secretKey: REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN','conversionStrategy: Default','decodingStrategy: None','metadataPolicy: None'}
|
||||
other=[line for line in changed if line not in intended and not line.startswith('generation:')]
|
||||
assert not other
|
||||
results.append({'operation':'client_diff','exit_code':p.returncode,'exact_new_reader_item_plus_native_defaults_only':True,'raw_values_annotations_stderr_discarded':True})
|
||||
else:results.append({'operation':'server_dry_run','exit_code':p.returncode,'raw_stdout_stderr_discarded':True})
|
||||
assert results[0]['exit_code']==0 and results[1]['exit_code'] in (0,1)
|
||||
r={'id':'CUST81-APPS-PHASE-B-ESO-PREFLIGHT-R1','checked_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'target_node_ip':'92.205.62.239','status':'verified_preparation','active_predecessor_path':str(old),'active_predecessor_sha256':hashlib.sha256(old.read_bytes()).hexdigest(),'proposed_replacement_path':str(new),'proposed_replacement_sha256':hashlib.sha256(new.read_bytes()).hexdigest(),'old_two_fields_retained':True,'existing_store_unchanged':True,'live_spec_projected_matches_predecessor':True,'native_remoteRef_defaults_verified':True,'previous_attempt':'full live data equality assertion refused on native ESO defaults; no command apply executed, original refusal retained','results':results,'credentials_provisioned':False,'live_apply_performed':False,'limitation':'server acceptance does not prove dedicated KV field exists or ESO consumer policy reader path granted'}
|
||||
(art/'eso-preflight-r1.json').write_text(json.dumps(r,indent=2)+'\n');print(json.dumps(r,indent=2))
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"proposal_id": "CUST81-APPS-PRIVATE-READER-RELEASE-R1",
|
||||
"status": "local_verified_live_blocked",
|
||||
"real_image_digest": null,
|
||||
"live_execution": false,
|
||||
"cost_estimate_eur": null,
|
||||
"risk_estimate_eur": null,
|
||||
"estimate_status": "live cost/loss bounds require release-specific runtime headroom, recovery and image evidence; not executable before supervisor-approved estimates",
|
||||
"paths": {
|
||||
"charts/reuse-surface/templates/_helpers.tpl": "sha256:789606a31ac8da6dfb7a5f6ace6a77269e3be3867d2e9a11e52b19aadf6977df",
|
||||
"charts/reuse-surface/templates/deployment.yaml": "sha256:2ec0be6d9aa24ed026b0c6629ab8ba003669eb0460d722fe413cd4934111540c",
|
||||
"charts/reuse-surface/values.schema.json": "sha256:ea73e027e4dcd832cc0bd48fc7ccfa29158153033ca02a8dc7b25b7df0d6c2b5",
|
||||
"charts/reuse-surface/values.yaml": "sha256:d1aeff71df0c53e4ecb3f4ebaac50ced7691b52c673fe9b49fbaeee6ca02c226",
|
||||
"docs/assessments/cust81-private-reader/apps-owner-review-r2.md": "sha256:a3a2f2db6f0a8461a0d8299825c842c8ce427f59c86238884609d463837b3b75",
|
||||
"docs/assessments/cust81-private-reader/local-verification-r2.json": "sha256:d723561845a05a76be9554596011c267fc67dd24953160f3bd7bd26a85c4812c",
|
||||
"docs/assessments/cust81-private-reader/private-reader-values.r1.yaml": "sha256:e02a319aca9c4860cdb8338009e60af6723bdb1fc17a591db0824273206eff83",
|
||||
"docs/assessments/cust81-private-reader/reuse-surface-runtime-externalsecret.r1.yaml": "sha256:adfb34c0ee4d01bc602f57cd2320f1658465fb8f8660978828fffa05a5b604b5",
|
||||
"docs/assessments/cust81-private-reader/runtime-auth-source-allowlist-r1.json": "sha256:e1948c537e206b30fc72b9e4732a3f600d92e131813e6f7ecfd4bfbb18c27286",
|
||||
"docs/assessments/cust81-private-reader/supervision-preparation-r1.json": "sha256:232f195026d704164b5973b5098002205445ff3ed6b0d2dd0c4a3c92c6ad9ca5",
|
||||
"docs/assessments/cust81-private-reader/verify_preparation.py": "sha256:c07921ea3fcd7c646aa6f28f34f7cdcb552e893341fe53dc7102ee376541d04d",
|
||||
"scripts/reuse_authenticated_source_inventory.py": "sha256:42eed5513db090d338daec39258b89a4ce2e9369d175c3c8312904ed68867ee1",
|
||||
"workplans/RAPPS-WP-0017-reuse-private-reader-release.md": "sha256:3a114a461647b46f3cc8e0ff9aa16b69aed0429cbc1578edf9b0fed5e220cc55"
|
||||
},
|
||||
"chart_patch_sha256": "39ebaa7f4050f9204fe8ee38364fbbce874903447ac45ccd408cf434aa6e4a73",
|
||||
"revision": 2,
|
||||
"refinement_or_rescue": true,
|
||||
"original_revision_preserved": "root owner-assembly/apps-r1 archive",
|
||||
"correction": "pod SQL native registration url projection plus exact SQLite readonly fixture, safe legacy routes and untrusted successful output refusal"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# PREPARATION ONLY: empty digest deliberately prevents rendering/deployment.
|
||||
# Fill only after reuse-surface publishes and reviews the fully hardened image.
|
||||
image:
|
||||
digest: ""
|
||||
authSourceAllowlist: {"REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN": ["https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738", "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738"]}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# PROPOSAL ONLY: phase B, after hardened runtime verified and CCR approved.
|
||||
# reuse-surface hub runtime secrets from OpenBao (RAILIANCE-WP-0011 / CCR-2026-0005).
|
||||
#
|
||||
# OpenBao path (platform KV v2): workloads/reuse/reuse-surface/runtime-secrets
|
||||
# Fields: REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
|
||||
#
|
||||
# Prereqs on railiance01:
|
||||
# - External Secrets Operator (make eso-deploy)
|
||||
# - ClusterSecretStore openbao-reuse (railiance-platform openbao-secretstore)
|
||||
# - OpenBao policy workload-kv-read-reuse-surface-runtime + ESO token
|
||||
# (make reuse-openbao-eso-token-apply)
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: reuse-surface-runtime
|
||||
namespace: reuse
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: openbao-reuse
|
||||
target:
|
||||
# Explicit target metadata prevents ESO copying source last-applied annotations.
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: reuse-surface
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
name: reuse-surface-env
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: REUSE_SURFACE_TOKEN
|
||||
remoteRef:
|
||||
key: workloads/reuse/reuse-surface/runtime-secrets
|
||||
property: REUSE_SURFACE_TOKEN
|
||||
- secretKey: REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
|
||||
remoteRef:
|
||||
key: workloads/reuse/reuse-surface/runtime-secrets
|
||||
property: REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET
|
||||
- secretKey: REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN
|
||||
remoteRef:
|
||||
key: workloads/reuse/reuse-surface/forgejo-source-read
|
||||
property: FORGEJO_SOURCE_TOKEN
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN": [
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/capabilities/capability.railiance.openbao-package.md?ref=35672be138036c8433a5ac5de91e068c03c2b124",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/capabilities/capability.railiance.policy-publication-package.md?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/capabilities/capability.railiance.postgres-isolation-recovery.md?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/capabilities/capability.railiance.sbom-runtime-package.md?ref=adbedd349e8df97be43a1f57db7095af45ff29d1",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/capabilities/capability.railiance.telemetry-package.md?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/capabilities/capability.railiance.tenant-engine-package.md?ref=9bdc86572728783a5ed94266f04f7b12d414c557",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/capabilities/capability.railiance.user-engine-package.md?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738",
|
||||
"https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"proposal_id": "CUST81-APPS-PRIVATE-READER-RELEASE-R1",
|
||||
"owner": "railiance-apps",
|
||||
"review_revision": 3,
|
||||
"disposition": "approved",
|
||||
"approved_scope": "ordinary source-only standard digest representation correction; no live runtime change",
|
||||
"refinement_or_rescue": true,
|
||||
"original_manifest_sha256": "45d467c2bb48462a388f5df4ba5188492d406bdddbddfbf3487bd2077486dec8",
|
||||
"qualified_manifest_sha256": "324757f27a36090e34101adf9a68feb8f19530463303e53fec46abe2092943b8",
|
||||
"source_file_digests_unchanged": true,
|
||||
"scanner_rule": "generic-api-key",
|
||||
"affected_field": "paths[docs/assessments/cust81-private-reader/reuse-surface-runtime-externalsecret.r1.yaml]",
|
||||
"scanner_version": "8.30.1",
|
||||
"pinned_scanner_sha256": "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509",
|
||||
"original_scanner_exit": 20,
|
||||
"candidate_scanner_exit": 0,
|
||||
"mechanism": "standard sha256-qualified metadata identifier; unchanged scanner/config, no ignore/exception/bypass",
|
||||
"backup_ref": "custody/cust81-apps-unpublished-r2",
|
||||
"original_commits": [
|
||||
"4e8e0f5",
|
||||
"6a8a54d"
|
||||
],
|
||||
"live_execution_approved": false
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"agent": "delegated-railiance-apps-cust81-preparation",
|
||||
"mode": "supervised",
|
||||
"scope": "isolated local chart/config/ESO proposal preparation, local verification only",
|
||||
"supervisor": "root custodian coordinating founder-authorized work",
|
||||
"promotion_evidence": null,
|
||||
"runtime_enforcement_verified": false,
|
||||
"authority_granted": false,
|
||||
"live_execution_authorized": false
|
||||
}
|
||||
94
docs/assessments/cust81-private-reader/verify_preparation.py
Normal file
94
docs/assessments/cust81-private-reader/verify_preparation.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"""Local chart and non-disclosure checks only; never contacts a cluster."""
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
ARTIFACT = Path(__file__).resolve().parent
|
||||
calls = []
|
||||
|
||||
def render(extra, success=True):
|
||||
command = ['helm', 'template', 'reuse', str(ROOT / 'charts/reuse-surface'), '-f', str(ROOT / 'helm/reuse-surface-values.yaml'), *extra]
|
||||
proc = subprocess.run(command, capture_output=True, text=True)
|
||||
calls.append({'command': command, 'exit_code': proc.returncode, 'expected_success': success})
|
||||
assert (proc.returncode == 0) == success
|
||||
return list(yaml.safe_load_all(proc.stdout)) if success else []
|
||||
|
||||
def api(docs):
|
||||
return next(d for d in docs if d and d.get('kind') == 'Deployment' and d['metadata']['name'] == 'reuse-surface')
|
||||
|
||||
current = api(render([]))
|
||||
assert current['spec']['template']['spec']['containers'][0]['image'].endswith(':main-6cbc862')
|
||||
assert next(e['value'] for e in current['spec']['template']['spec']['containers'][0]['env'] if e['name']=='REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST') == '{}'
|
||||
proposal = ['-f', str(ARTIFACT / 'private-reader-values.r1.yaml')]
|
||||
render(proposal, False) # unknown real digest is a hard gate
|
||||
# Synthetic digest is render-only, and expressly not promotion/build evidence.
|
||||
render(proposal+['--set', 'image.digest=sha256:bad'], False)
|
||||
render(proposal+['--set-json', 'authSourceAllowlist={"BAD":["http://attacker.invalid"]}', '--set', 'image.digest=sha256:'+'0'*64], False)
|
||||
docs = render(proposal+['--set', 'image.digest=sha256:'+'0'*64])
|
||||
deployment = api(docs)
|
||||
container = deployment['spec']['template']['spec']['containers'][0]
|
||||
assert container['image'].endswith('@sha256:'+'0'*64)
|
||||
allowlist=json.loads(next(e['value'] for e in container['env'] if e['name']=='REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST'))
|
||||
expected = json.loads((ARTIFACT/'runtime-auth-source-allowlist-r1.json').read_text())
|
||||
assert allowlist==expected and len(next(iter(allowlist.values())))==14
|
||||
assert deployment['spec']['strategy'] == current['spec']['strategy']
|
||||
assert container['resources']==current['spec']['template']['spec']['containers'][0]['resources']
|
||||
assert deployment['spec']['template']['spec']['volumes']==current['spec']['template']['spec']['volumes']
|
||||
assert container['volumeMounts']==current['spec']['template']['spec']['containers'][0]['volumeMounts']
|
||||
secret=yaml.safe_load((ARTIFACT/'reuse-surface-runtime-externalsecret.r1.yaml').read_text())
|
||||
baseline=yaml.safe_load((ROOT/'manifests/reuse-surface-runtime-externalsecret.yaml').read_text())
|
||||
assert secret['spec']['data'][:-1]==baseline['spec']['data']
|
||||
assert secret['spec']['secretStoreRef']==baseline['spec']['secretStoreRef']
|
||||
assert secret['spec']['data'][-1]=={'secretKey':'REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN','remoteRef':{'key':'workloads/reuse/reuse-surface/forgejo-source-read','property':'FORGEJO_SOURCE_TOKEN'}}
|
||||
spec=importlib.util.spec_from_file_location('inventory',ROOT/'scripts/reuse_authenticated_source_inventory.py')
|
||||
mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod)
|
||||
url=expected['REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN'][1]
|
||||
assert len(mod.sanitize([['rapp-openbao','FIXTURE_READ_TOKEN',url]]))==1
|
||||
legacy='https://forgejo.coulomb.social/coulomb/rapp-openbao/raw/branch/main/registry/indexes/capabilities.yaml'
|
||||
assert len(mod.sanitize([['rapp-openbao','FIXTURE_READ_TOKEN',legacy]]))==1
|
||||
# Exercise the EXACT pod code against actual native registration keys in SQLite.
|
||||
# Replacing only the fixture DB path leaves the metadata SELECT/read-only mode intact.
|
||||
import sqlite3,tempfile
|
||||
with tempfile.TemporaryDirectory(prefix='apps-reader-fixture-') as temp:
|
||||
fixture=Path(temp)/'reuse.db'
|
||||
with sqlite3.connect(fixture) as db:
|
||||
db.execute('CREATE TABLE registrations(repo TEXT PRIMARY KEY, payload TEXT NOT NULL, registered_at TEXT, updated_at TEXT)')
|
||||
db.execute('INSERT INTO registrations VALUES(?,?,?,?)', ('rapp-openbao',json.dumps({'repo':'rapp-openbao','url':url,'auth_env':'FIXTURE_READ_TOKEN','description':'fixture-secret-never-emit'}),'fixture','fixture'))
|
||||
db.execute('INSERT INTO registrations VALUES(?,?,?,?)', ('anonymous',json.dumps({'repo':'anonymous','url':legacy}),'fixture','fixture'))
|
||||
pod_code=mod.POD_CODE.replace('/data/reuse.db',str(fixture))
|
||||
proc=subprocess.run(['python3','-c',pod_code],capture_output=True,text=True,check=True)
|
||||
assert json.loads(proc.stdout)==[['rapp-openbao','FIXTURE_READ_TOKEN',url]]
|
||||
assert 'fixture-secret' not in proc.stdout
|
||||
assert len(mod.sanitize(json.loads(proc.stdout)))==1
|
||||
with sqlite3.connect(fixture) as db: assert db.execute('SELECT count(*) FROM registrations').fetchone()==(2,)
|
||||
assert 'mode=ro' in pod_code and '$.index_url' not in pod_code
|
||||
assert 'os.environ' not in pod_code and 'getenv' not in pod_code and 'kubectl' not in pod_code
|
||||
|
||||
for bad in ['https://token@forgejo.coulomb.social/x', url+'&token=fixture-secret','http://attacker.invalid',url+'#fixture-secret',legacy+'?token=fixture-secret',legacy+'/../credentials',legacy+'%3Ftoken=fixture-secret']:
|
||||
try: mod.sanitize([['rapp-openbao','FIXTURE_READ_TOKEN',bad]])
|
||||
except ValueError: pass
|
||||
else: raise AssertionError('unsafe URL accepted')
|
||||
# Errors from kubectl are discarded before output; no subprocess invoked in test.
|
||||
original=mod.subprocess.run
|
||||
class Failure:
|
||||
returncode=1;stdout=b'fixture-secret';stderr=b'fixture-secret'
|
||||
mod.subprocess.run=lambda *args,**kwargs: Failure()
|
||||
import sys,io,contextlib
|
||||
argv=sys.argv;sys.argv=['inventory','--kubeconfig','fixture-path']
|
||||
buf=io.StringIO()
|
||||
with contextlib.redirect_stdout(buf): assert mod.main()==1
|
||||
assert 'fixture-secret' not in buf.getvalue()
|
||||
class UnsafeSuccess:
|
||||
returncode=0;stdout=json.dumps([['rapp-openbao','FIXTURE_READ_TOKEN',url+'&token=fixture-secret']]).encode();stderr=b''
|
||||
mod.subprocess.run=lambda *args,**kwargs: UnsafeSuccess()
|
||||
buf=io.StringIO()
|
||||
with contextlib.redirect_stdout(buf): assert mod.main()==1
|
||||
assert 'fixture-secret' not in buf.getvalue()
|
||||
sys.argv=argv;mod.subprocess.run=original
|
||||
result={'status':'passed','local_only':True,'real_image_digest':None,'synthetic_digest_used_only_to_check_rendering':True,'checks':['production rendering unchanged except empty fail-closed allowlist','unknown/malformed digest fails','HTTP allowlist fails schema','exact14 URLs roundtrip','strategy resources PVC retained','dedicated ESO mapping preserves current fields/store','metadata sanitizer excludes userinfo/query secrets/fragments','kubectl failure output discarded','exact pod SQL real payload URL keys readonly fixture and anonymous filtering','legacy Forgejo branch URL accepted without weakening credential query refusal'],'helm_calls':calls}
|
||||
(ARTIFACT/'local-verification-r2.json').write_text(json.dumps(result,indent=2)+'\n')
|
||||
print(json.dumps({'status':'passed','checks':len(result['checks'])}))
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
| Host | Kubeconfig (operator workstation) | API |
|
||||
| --- | --- | --- |
|
||||
| CoulombCore | `~/.kube/config` | tunnel `k3s-api-coulombcore` |
|
||||
| railiance01 | `~/.kube/config-hosteurope` | local / hosteurope |
|
||||
| railiance01 | `~/.kube/config-railiance01` | local / hosteurope |
|
||||
|
||||
## Production-of-record (POR) decisions
|
||||
|
||||
|
|
|
|||
39
docs/coulomb-image-release-2026-10-31.md
Normal file
39
docs/coulomb-image-release-2026-10-31.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Completed Coulomb identity/export image release
|
||||
|
||||
The exact supervised proposal is
|
||||
`sha256:9f1daf6c5fb56b2b4fc9817f28f5ad090c884b0295f7d6f4469e42d48fa8d59f`.
|
||||
Resolved execution decision: `75b94bb6-4709-495f-91fe-43e53e2867ed`.
|
||||
The full founder packet is
|
||||
[coulomb-social release review](/home/worsch/coulomb-social/docs/release/identity-export-release-review-2026-10-31.md).
|
||||
|
||||
`tools/coulomb-image-release.py` prepares the capsule by default. `--preflight`
|
||||
checks the live target without capture or deployment. `--execute --expect-digest`
|
||||
requires the founder's own interactive terminal and the exact typed proposal.
|
||||
The node controller freezes verified source bytes into its private durable
|
||||
journal before execution. An existing attempt refuses blind replay.
|
||||
|
||||
This distinct controller adapts the previously reviewed storage controller
|
||||
from owner source 45d6178. It requires migration 0003 and the global constraint
|
||||
already present, adds exact migration-history equality, verifies installed
|
||||
helpers during preflight, verifies the desired complete spec through an image-only
|
||||
server-dry-run patch and verifies rollback readiness/specification. Unknown
|
||||
helper changes refuse overwrite. Historical storage commands/capsules remain
|
||||
unchanged and must not be replayed.
|
||||
|
||||
Candidate values live in `release/identity-export-values.yaml`; the current
|
||||
`helm/coulomb-social-values.yaml` now matches verified revision 19/image522bcd7f.
|
||||
The installed snapshot helper matches the reviewed bytes (dcc8dd2a).
|
||||
No unit/timer, sender, grant, apex or data-restore operation is included.
|
||||
|
||||
Validation: 19 release safety cases pass; Ruff, Python compile and Helm lint pass.
|
||||
The sealed native preflight is retained in
|
||||
`docs/evidence/2026-10-31-coulomb-image-release-preflight.json`.
|
||||
Four hash-bound candidate evidence files travel inside the capsule. The product
|
||||
review names source 441 tests, actual packaged 25 tests, all 80 packaged source matches
|
||||
and the retained isolated recovery/export/view checks with their limits.
|
||||
|
||||
Founder execution and independent postflight are verified at revision19. The
|
||||
product independently retrieved/restored the fresh point and verified packaged
|
||||
client export on the corrected data. T05 is complete. Native receipt, independent
|
||||
postflight and live packaged-source proof are under docs/evidence/2026-10-31-coulomb-image-release-*.json.
|
||||
The historical launcher/capsule is retained for audit and must not be replayed.
|
||||
|
|
@ -9,7 +9,7 @@ platform tooling.
|
|||
| Host | IP | Kubeconfig | Tunnel |
|
||||
| --- | --- | --- | --- |
|
||||
| CoulombCore (production apps) | `92.205.130.254` | `~/.kube/config` via `k3s-api-coulombcore` | `bridge up k3s-api-coulombcore` |
|
||||
| Railiance01 (Forgejo S5) | `92.205.62.239` | `~/.kube/config-hosteurope` | `bridge up k3s-api-railiance01` if configured |
|
||||
| Railiance01 (Forgejo S5) | `92.205.62.239` | `~/.kube/config-railiance01` | `bridge up k3s-api-railiance01` if configured |
|
||||
|
||||
Core Hub, `vergabe-teilnahme`, and `apps-pg` currently run on **CoulombCore**.
|
||||
Railiance01 hosts Forgejo and a overlapping subset of platform databases.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"stable_proposal": "NK-FORGEJO-TYPE12-SOURCE-20261011-R1",
|
||||
"memo_id": "infd-20261011-forgejo-type12-source-r1",
|
||||
"memo_version": 1,
|
||||
"review_id": "review-126c6c90-11ba-4609-b8d1-75e054c246cd",
|
||||
"approval_id": "8e9332c8-754a-420c-b8b7-445cb4fe6765",
|
||||
"disposition_id": "disp-315d54ad-20f1-40e6-862c-6c7d2e8f0ea8",
|
||||
"presentation_id": "pres-5a52f932-8c65-46cc-8054-0264f47f934a",
|
||||
"actual_human_actor": {
|
||||
"kind": "person",
|
||||
"sub": "uid=platform-root,ou=people,dc=netkingdom,dc=local"
|
||||
},
|
||||
"actual_disposition_at": "2026-10-11T06:47:39Z",
|
||||
"confirmed_nonstale": true,
|
||||
"exact_human_packet_manifest_sha256": "0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68",
|
||||
"submission_digest": "sha256:585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569",
|
||||
"canonical_case12_source_sha256": "1c868c1da7d9077fae97bc6bc029706ec3dc418cfeaae30dc0f92b5b4ba6f2ab",
|
||||
"full_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
|
||||
"kind": "human-reference-migration-design-source-receipt",
|
||||
"case": "forgejo-netkingdom-oidc",
|
||||
"type": "oidc-browser-relying-party@1.2",
|
||||
"declared_case_owner": {
|
||||
"repo": "railiance-apps",
|
||||
"person": "founder"
|
||||
},
|
||||
"actual_human_design_approval_roles_requested_in_exact_memo": [
|
||||
"declared-consumer-owner",
|
||||
"founder"
|
||||
],
|
||||
"scope": "Separate owner design receipt of exact human source approval; not an agent Apps-owner review and not canonical case acceptance. The requesting coordinator transcribes only the actual founder act matching exact memo terms.",
|
||||
"canonical_case12_status": "proposed",
|
||||
"canonical_case_acceptance_recorded": false,
|
||||
"type_implemented": false,
|
||||
"bound_claims_confirmed": false,
|
||||
"execution_permitted": false,
|
||||
"native_or_credential_or_session_authority": false,
|
||||
"existing_driver": "ORGREF-WP-0002",
|
||||
"consumer_task": "RAPPS-WP-0016-T02",
|
||||
"type_source_receipt_repo": "net-kingdom",
|
||||
"type_source_receipt_path": "security-case-type-revisions/1.2/oidc-browser-relying-party/decision/acknowledgment.json",
|
||||
"recording_act": "Transcribe confirmed exact human reference-migration design approval only; requesting coordinator records actual fulfilled roles. No agent Apps-owner review.",
|
||||
"recorded_status": "source-design-approved",
|
||||
"type_source_receipt_sha256": "2dcc220febf125a17b3769f7014f18b63979d7121e0f7056a2b0d8b1ae6031b0"
|
||||
}
|
||||
18
docs/evidence/2026-10-17-forgejo-breakglass-signin.json
Normal file
18
docs/evidence/2026-10-17-forgejo-breakglass-signin.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"schema": "railiance-apps.forgejo-breakglass-signin.v1",
|
||||
"task": "RAPPS-WP-0016-T01",
|
||||
"forgejo": "https://forgejo.coulomb.social",
|
||||
"account": "forgejo_admin",
|
||||
"auth": "local password (API basic auth)",
|
||||
"http_status": 200,
|
||||
"login_matches": true,
|
||||
"is_admin": true,
|
||||
"custody": {
|
||||
"openbao_path": "platform/workloads/forgejo/break-glass-admin",
|
||||
"field": "PASSWORD",
|
||||
"version": "1"
|
||||
},
|
||||
"verified_at": "2026-10-17T09:41:18Z",
|
||||
"result": "pass",
|
||||
"values_emitted": false
|
||||
}
|
||||
83
docs/evidence/2026-10-19-build-mode-publication/receipt.json
Normal file
83
docs/evidence/2026-10-19-build-mode-publication/receipt.json
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"proposal_id": "POLICY-PUB-20261019-BUILD-MODE-GUARDRAIL-R1",
|
||||
"approved_revision": 1,
|
||||
"approved_packet_hashlist_sha256": "309ef01650b65bc6b28e86daad9b2835a242631cf60e0896e849461bb917504f",
|
||||
"approval_ref": "conversation:call_5157a09329d34746a3fdf857d5a47b8d:item0",
|
||||
"result": "verified",
|
||||
"helm_revision": 10,
|
||||
"rollback_required": false,
|
||||
"rollback_revision": 9,
|
||||
"candidate_source_revision": "9fabcce1cb4c83165bcaba8be58d76788b4d87a1",
|
||||
"candidate_origin": "local owner-format build; native CI run89 retained failed, no new CI success claimed",
|
||||
"change_gate": {
|
||||
"decision": "the-custodian/docs/kubernetes-change-gate-decision.md",
|
||||
"target_tier": "production",
|
||||
"readiness_state": "production-approved",
|
||||
"path": "direct-administer-transition",
|
||||
"auth_mode": "ADMINISTER @ realm:kubernetes/railiance01",
|
||||
"activation": "APPROVED",
|
||||
"evidence_boundary": "target-audited",
|
||||
"transition_ends": "2026-12-21"
|
||||
},
|
||||
"native": {
|
||||
"capture_utc": "2026-10-19T13:35:06.098696+00:00",
|
||||
"cluster_mutated": false,
|
||||
"expected_node_ip": "92.205.62.239",
|
||||
"expected_node_ip_present": true,
|
||||
"helm_revision": 10,
|
||||
"helm_status": "deployed",
|
||||
"image_matches_candidate": true,
|
||||
"machine_id_sha256": "fe7367a60951922122af7ba18ed2cde65c7e0a232bbedfb045cb575de2094c2f",
|
||||
"ready_replicas": 1,
|
||||
"release_pin_matches": {
|
||||
"publication-manifest-digest": true,
|
||||
"source-inventory-digest": true,
|
||||
"source-set-digest": true
|
||||
},
|
||||
"replicas": 1,
|
||||
"secret_values_observed": false,
|
||||
"ssh_alias": "railiance01"
|
||||
},
|
||||
"public": {
|
||||
"verified": true,
|
||||
"capture_utc": "2026-10-19T13:36:20.944233+00:00",
|
||||
"hashes": {
|
||||
"publication_manifest_digest": "8c74b4e414f7760429600b7ba0b3627fcfe438417bfe6695f55acfa6905d352e",
|
||||
"source_inventory_digest": "e84453b6b411a7d66d2b5efbf083cd0033441d6b12983326442933da87bf61fd",
|
||||
"source_lock_digest": "c150c6c0745441b4b69884ecc6cf55e97db26080fcf4c2224775b1f755580529"
|
||||
},
|
||||
"source_set_digest": "b00535063c56ede08f0a292b301a47add18bf70f81904bf3f434c8faa5ebe682",
|
||||
"document_count": 74,
|
||||
"previous73_source_digests_unchanged": true,
|
||||
"added_document": "canon-build-mode-change-authorization",
|
||||
"all_canonical_and_revision_pages_byte_equal_candidate": true,
|
||||
"page_and_artifact_count": 150,
|
||||
"guardrail_expiry_banner": "Expires on: production-close-down",
|
||||
"guardrail_json_byte_equal_verified_candidate": true,
|
||||
"guardrail_json_sha256": "a86462a57bb1df5804d3088881af22569082d43ff68bcab2b67cbe56b6fc46cd",
|
||||
"tls_verified": true,
|
||||
"refinements": [
|
||||
"Initial verifier incorrectly expected hosting transition date2026-12-21 in canon expiry.",
|
||||
"Second verifier expected source expires_on label rather than rendered Expires on.",
|
||||
"Correct final assertion matches approved rendered banner; both failed checks retained, artifact and deployment unchanged."
|
||||
]
|
||||
},
|
||||
"deployment": {
|
||||
"started_utc": "2026-10-19T13:34:20.566225+00:00",
|
||||
"command": [
|
||||
"make",
|
||||
"policy-nexus-deploy",
|
||||
"POLICY_NEXUS_TARGET=railiance01"
|
||||
],
|
||||
"atomic": true,
|
||||
"attempt": 1,
|
||||
"approval_ref": "conversation:call_5157a09329d34746a3fdf857d5a47b8d:item0",
|
||||
"completed_utc": "2026-10-19T13:34:26.648713+00:00",
|
||||
"returncode": 0,
|
||||
"raw_output_suppressed": true
|
||||
},
|
||||
"owner_source_commits": {
|
||||
"rapp-policy-nexus": "6ead90ffa4ae5742c37c292485762e83113382c3",
|
||||
"railiance-apps": "3be92daf74a7572319bfe94dd882a70430528343"
|
||||
}
|
||||
}
|
||||
155
docs/evidence/2026-10-21-vergabe-target-replay/README.md
Normal file
155
docs/evidence/2026-10-21-vergabe-target-replay/README.md
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
# Vergabe target-hardware synthetic replay — 2026-10-21
|
||||
|
||||
Task: `RAPPS-WP-0014-T04` (feeds `CUST-WP-0071-T02`, `RESOURCE-WP-0008-T02`).
|
||||
Authority: founder decision of 2026-10-21
|
||||
(`the-custodian/docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md`):
|
||||
isolated replay in a throwaway namespace on railiance01, reads elsewhere only.
|
||||
No customer data was read or written; the live `vergabe-demo-company` deployment,
|
||||
its database (`apps-pg`), quotas, policies and nodes were not modified (live
|
||||
Deployment generation 4, same digest, Ready 1/1 before and after). No Secret was
|
||||
read. This is synthetic sizing evidence, not human/recovery acceptance (T03).
|
||||
|
||||
## Deployed release (read-only capture, 2026-10-21)
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Cluster / node | context `default` → 92.205.62.239, single k3s v1.35.1 control-plane node |
|
||||
| Node CPU / memory | 4 vCPU `AMD EPYC Processor (with IBPB)`, 16 GiB; requests 3605m (90%) / limits 37500m; reservation residual 395m |
|
||||
| Image | `forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91` (source `c067993`, per `docs/vergabe-demo-company-binding.md`) |
|
||||
| Chart / rollout | `vergabe-teilnahme-0.2.1`, one replica, Recreate, init `migrate`; Deployment revision 4 (rollout restart 2026-09-25). Helm release revision not re-read (release storage is a Secret) |
|
||||
| Gunicorn | no command override → image CMD `--workers 3` (sync), default 30s timeout |
|
||||
| Resources (app and migrate) | requests 60m CPU / 256Mi, limits 1000m CPU / 1Gi |
|
||||
| Settings | `vergabe_teilnahme.settings.prod`, SSO via NetKingdom, `envFrom` Secret reference `vergabe-demo-env` (not read) |
|
||||
| Database binding | namespace label `railiance.io/postgres-client=apps-pg`: shared CNPG `databases/apps-pg`, PostgreSQL 16, 100m/256Mi request, 1000m/1Gi limit, WAL archiving + `wal_level=logical` |
|
||||
| Storage | media 5Gi and app-state 1Gi PVCs, `local-path` |
|
||||
|
||||
Drift vs. the 2026-10-19 local test image (`vergabe-synthetic:local-11f7c8a`,
|
||||
`sha256:57da46…`): source `11f7c8a` is 16 commits ahead of deployed `c067993`;
|
||||
9 application files differ (+181/−10: ausschreibungen forms/views/templates,
|
||||
core views, search partial); Dockerfile, `uv.lock`, `pyproject.toml` identical.
|
||||
The local runs used 2 workers; the deployed image runs 3. This replay used the
|
||||
deployed digest itself, so the drift does not affect these results.
|
||||
|
||||
## Replay configuration (pinned)
|
||||
|
||||
Namespace `vergabe-sizing-20261021` (manifests in `harness/bench.yaml`,
|
||||
`harness/driver.yaml`), all pods on the same node:
|
||||
|
||||
- **app**: deployed digest, image CMD (3 workers), init `migrate`, 60m/256Mi request,
|
||||
1000m/1Gi limit, live securityContext/probes, Recreate; media on a fresh
|
||||
`local-path` PVC, issue state emptyDir. Settings = harness synthetic settings
|
||||
(`harness/synthetic_settings.py`: base settings, SSO off, password login).
|
||||
- **db**: fresh `docker.io/library/postgres@sha256:4b7183ac…` (PostgreSQL 16),
|
||||
emptyDir, 100m/256Mi request, 1000m/1Gi limit (matching apps-pg resources).
|
||||
- **driver**: deployed digest running `vergabe-teilnahme/scripts/synthetic_pilot.py`
|
||||
`runtime: remote` (commit `dca6683`; executed copy and config retained, digests
|
||||
`c8da867f…` / `9045f2c7…` match `raw/result.json.gz`), 100m request/1000m limit.
|
||||
In-cluster HTTP via ClusterIP Service — chosen over port-forward so latency
|
||||
excludes the WSL/SSH tunnel. Restart deletes the app pod through a namespaced
|
||||
Role (pods get/list/delete) and waits for a different ready pod.
|
||||
- NetworkPolicy: ingress/egress within the namespace, DNS, API server only;
|
||||
preflight confirmed the live Service was unreachable.
|
||||
- Profile `config/synthetic-pilot-phases.json` unchanged except runtime fields
|
||||
(`synthetic-pilot-target-remote.json`): 2 users, 100 seed tenders, 1 MiB
|
||||
documents, 30s idle, 90s ordinary (0.2s think), 30s burst (0 think), restart,
|
||||
60s recovery; 11 named operations including edits.
|
||||
- Sampling: 1s cgroup v2 `cpu.stat`/`memory.current`/`cpu.pressure` inside app,
|
||||
db and driver containers plus node-wide `/proc/stat` (`harness/sampler.sh`);
|
||||
`kubectl top` every 15s; Prometheus (cadvisor) per-namespace CPU/throttling.
|
||||
The sampler shell itself costs ~11–12m per container and is included.
|
||||
|
||||
Timeline (UTC): namespace created 06:26; pre-run idle baseline 06:27:14–06:29:26;
|
||||
harness idle 06:29:31–06:30:01; ordinary 06:30:01–06:31:32; burst –06:32:03;
|
||||
restart –06:32:22 (18.98s to ready incl. migrate); recovery –06:33:23; post
|
||||
idle –06:34:28; namespace deletion requested 06:35:25, completed 06:36:18; namespace and its PV verified absent (`raw/teardown.txt`).
|
||||
|
||||
## Results
|
||||
|
||||
Driver: complete, `provisional_pass: true`, `measurement_complete: true`, no gaps.
|
||||
|
||||
| Phase | Ordinary req / err / p95 | Transfer req / err / p95 |
|
||||
| --- | --- | --- |
|
||||
| ordinary | 1903 / 0 / 0.142s | 422 / 0 / 0.085s |
|
||||
| burst | 697 / 0 / 0.192s | 154 / 0 / 0.069s |
|
||||
| recovery | 1075 / 0 / 0.214s | 238 / 0 / 0.073s |
|
||||
| **all** | **3675 / 0 / 0.192s** | **814 / 0 / 0.076s** |
|
||||
|
||||
Per operation p95 (all phases): list 0.262s, search 0.255s, detail 0.146s,
|
||||
creates/edits 0.059–0.066s, upload 0.088s, download 0.050s; login 1.27s and
|
||||
login form 0.62s (n=6, first requests after idle). ~24.7 requests/s closed loop —
|
||||
far above two human users' pace.
|
||||
|
||||
| Window | App CPU mean / p95 / peak | Throttled periods | App memory peak | DB CPU mean / p95 | Node busy mean (free mean / free p5) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| pre-run baseline | 12m / 15m / 16m | 0% | 166 MiB | 22m / 66m | 1348m (2652m / 1499m) |
|
||||
| ordinary | 711m / 845m / 1003m | 20.4% | 403 MiB | 388m / 501m | 2979m (1021m / 78m) |
|
||||
| burst | 953m / 1004m / 1011m | 65.8% | 482 MiB | 430m / 539m | 2806m (1194m / 278m) |
|
||||
| recovery | 861m / 957m / 982m | 41.5% | 322 MiB | 335m / 399m | 2624m (1376m / 392m) |
|
||||
| all load phases | 775m / 998m / 1020m | 35.7% (22.8s throttled) | 483 MiB | 341m / 500m | 2790m (1210m / 147m) |
|
||||
| post | 12m | 0% | 323 MiB | 22m | 1413m (2587m) |
|
||||
|
||||
App memory is cgroup `memory.current` (includes page cache); no OOM, no restarts.
|
||||
CPU pressure (PSI some) on the app was 15–21% of wall time under load.
|
||||
Incremental PostgreSQL CPU ≈ 320m mean / ~480m p95 over its idle baseline, on
|
||||
the isolated DB only (live `apps-pg` stayed at ~8m during the window). Driver
|
||||
added ~84m mean.
|
||||
|
||||
Competing workloads (Prometheus, workload window): all containers 1622m mean, of
|
||||
which the replay namespace 915m; others led by databases 121m, state-hub 120m,
|
||||
telemetry 102m, forgejo 78m (max 385m), activity-core 59m, core-hub 43m; live
|
||||
vergabe ~0m. Node-wide busy includes ~0.7 core of k3s/system outside containers.
|
||||
Pre-run competing load left ~2.65 cores free on average; during the replay the
|
||||
node reached near-saturation at peaks (free p5 ~150m) largely through the replay
|
||||
itself (app + db + driver ≈ 1.2 cores). Requests during the run were 3865m of
|
||||
4000m (replay 260m of the 395m residual, released at teardown).
|
||||
|
||||
## Coverage and gaps
|
||||
|
||||
- Representative: exact deployed image/digest, worker count, CPU/memory
|
||||
requests and limits (CFS quota 100000/100000 verified), Recreate + init migrate
|
||||
restart path, target hardware and real shared node contention.
|
||||
- Not representative: synthetic settings instead of prod (no SSO/OIDC, insecure
|
||||
cookies, no whitenoise manifest storage); no Traefik ingress/TLS/browser; DB is a
|
||||
standalone PostgreSQL 16 pod, not shared CNPG `apps-pg` (WAL archiving, logical
|
||||
WAL, other tenants) — the ~320m incremental DB demand would land on apps-pg's
|
||||
1000m limit/100m request in production and is not proven there.
|
||||
- Load is a synthetic closed loop (~25 req/s), not observed human demand; one run
|
||||
with ~3.5 min of load (pinned profile), ~9 min namespace lifetime.
|
||||
- Node contention was partly self-induced; contention from other tenants was
|
||||
light at this hour. A busier moment (forgejo CI, backups) is not covered.
|
||||
- The 60m request is a scheduling weight/reservation, not a quota; the 1000m
|
||||
limit is the quota that throttled. Sampler exec overhead is in the app cgroup.
|
||||
|
||||
## Bounded recommendation (for RESOURCE-WP-0008-T02 / CUST-WP-0071-T02)
|
||||
|
||||
- **CPU limit 1000m: keep.** Under an aggressive synthetic mix it throttles
|
||||
(36% of periods, 66% in burst) yet ordinary p95 stays ≤0.22s per phase — ~9×
|
||||
margin to the 2s target, zero failed operations, including restart recovery.
|
||||
- **CPU request 60m: keep for the two-user invited pilot**, provisionally. Even
|
||||
with the node near saturation and only a 60m weight, latency stayed far inside
|
||||
target; two humans generate a small fraction of this load. Raising it (e.g.
|
||||
250m) is not required by these results and would consume most of the 395m
|
||||
residual — an allocation-owner decision, not made here.
|
||||
- **Memory limit 1Gi: keep** (peak 483 MiB incl. page cache, ~2× margin).
|
||||
**Memory request 256Mi: keep, consider 512Mi** at the next allocation review,
|
||||
since loaded working set (320–480 MiB) exceeds the request; node memory requests
|
||||
are 61%, so this is low-risk but not urgent.
|
||||
- **Database**: budget ~0.3–0.5 core incremental on apps-pg at this synthetic
|
||||
rate; acceptable against its 1000m limit for a two-user pilot, unverified on the
|
||||
shared cluster.
|
||||
- **Revisit when**: user count/companies or corpus grow, image or worker count
|
||||
changes, live p95 >1s or any 5xx, node requests >95% or sustained node free CPU
|
||||
<500m outside benchmarks, apps-pg CPU >600m sustained, or the pilot moves to
|
||||
railiance02/other hardware.
|
||||
|
||||
## Files
|
||||
|
||||
- `analysis.json` — per-window cgroup statistics, per-operation p95, phase results
|
||||
(`harness/analyze.py raw/ analysis.json` after gunzip)
|
||||
- `raw/result.json.gz` — driver output (all operations, phase UTC windows, host facts)
|
||||
- `raw/{app,db,driver}-cgroup.txt.gz` — 1s cgroup/node samples (`POD` lines mark
|
||||
the restart re-attach; the `exit code 137` exec termination is that restart)
|
||||
- `raw/kubectl-top.txt.gz`, `raw/prometheus-{baseline,workload}.json.gz`
|
||||
- `raw/windows.txt`, `raw/pod-provenance.txt`, `raw/namespace-events.txt`,
|
||||
`raw/teardown.txt`, `raw/driver-stdout.txt`
|
||||
- `harness/` — manifests, settings, samplers, orchestrator, executed driver/config
|
||||
923
docs/evidence/2026-10-21-vergabe-target-replay/analysis.json
Normal file
923
docs/evidence/2026-10-21-vergabe-target-replay/analysis.json
Normal file
|
|
@ -0,0 +1,923 @@
|
|||
{
|
||||
"windows_utc": {
|
||||
"baseline_pre": [
|
||||
"2026-10-21T06:27:14.447540Z",
|
||||
"2026-10-21T06:29:26.175778Z"
|
||||
],
|
||||
"harness_idle": [
|
||||
"2026-10-21T06:29:31.522662Z",
|
||||
"2026-10-21T06:30:01.522800Z"
|
||||
],
|
||||
"ordinary": [
|
||||
"2026-10-21T06:30:01.523056Z",
|
||||
"2026-10-21T06:31:32.589152Z"
|
||||
],
|
||||
"burst": [
|
||||
"2026-10-21T06:31:32.589194Z",
|
||||
"2026-10-21T06:32:03.275085Z"
|
||||
],
|
||||
"restart": [
|
||||
"2026-10-21T06:32:03.275117Z",
|
||||
"2026-10-21T06:32:22.256585Z"
|
||||
],
|
||||
"recovery": [
|
||||
"2026-10-21T06:32:22.256609Z",
|
||||
"2026-10-21T06:33:23.057045Z"
|
||||
],
|
||||
"workload_all_load_phases": [
|
||||
"2026-10-21T06:30:01.523056Z",
|
||||
"2026-10-21T06:33:23.057045Z"
|
||||
],
|
||||
"post": [
|
||||
"2026-10-21T06:33:22.885983Z",
|
||||
"2026-10-21T06:34:28.753426Z"
|
||||
]
|
||||
},
|
||||
"cgroup": {
|
||||
"app": {
|
||||
"pods": [
|
||||
"vergabe-app-579448bf49-2fsn2",
|
||||
"vergabe-app-579448bf49-plp4j"
|
||||
],
|
||||
"baseline_pre": {
|
||||
"samples": 126,
|
||||
"seconds": 128.0,
|
||||
"cpu_mean_m": 12.2,
|
||||
"cpu_p95_m": 14.7,
|
||||
"cpu_peak_m": 16.2,
|
||||
"cfs_periods": 317,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 165.6,
|
||||
"mem_mean_mib": 165.2,
|
||||
"node_busy_mean_m": 1348,
|
||||
"node_busy_p95_m": 2501,
|
||||
"node_free_mean_m": 2652,
|
||||
"node_free_p5_m": 1499,
|
||||
"load1_max": 4.91
|
||||
},
|
||||
"harness_idle": {
|
||||
"samples": 28,
|
||||
"seconds": 28.4,
|
||||
"cpu_mean_m": 11.5,
|
||||
"cpu_p95_m": 13.4,
|
||||
"cpu_peak_m": 13.6,
|
||||
"cfs_periods": 76,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 165.3,
|
||||
"mem_mean_mib": 165.1,
|
||||
"node_busy_mean_m": 1538,
|
||||
"node_busy_p95_m": 2800,
|
||||
"node_free_mean_m": 2462,
|
||||
"node_free_p5_m": 1200,
|
||||
"load1_max": 2.91
|
||||
},
|
||||
"ordinary": {
|
||||
"samples": 88,
|
||||
"seconds": 90.2,
|
||||
"cpu_mean_m": 711.4,
|
||||
"cpu_p95_m": 845.4,
|
||||
"cpu_peak_m": 1003.2,
|
||||
"cfs_periods": 902,
|
||||
"cfs_throttled_periods": 184,
|
||||
"throttle_ratio": 0.204,
|
||||
"throttled_seconds": 4.85,
|
||||
"psi_cpu_some_share": 0.15,
|
||||
"mem_peak_mib": 402.5,
|
||||
"mem_mean_mib": 295.2,
|
||||
"node_busy_mean_m": 2979,
|
||||
"node_busy_p95_m": 3922,
|
||||
"node_free_mean_m": 1021,
|
||||
"node_free_p5_m": 78,
|
||||
"load1_max": 7.83
|
||||
},
|
||||
"burst": {
|
||||
"samples": 29,
|
||||
"seconds": 29.9,
|
||||
"cpu_mean_m": 952.9,
|
||||
"cpu_p95_m": 1003.7,
|
||||
"cpu_peak_m": 1011.1,
|
||||
"cfs_periods": 298,
|
||||
"cfs_throttled_periods": 196,
|
||||
"throttle_ratio": 0.658,
|
||||
"throttled_seconds": 8.94,
|
||||
"psi_cpu_some_share": 0.211,
|
||||
"mem_peak_mib": 481.8,
|
||||
"mem_mean_mib": 443.4,
|
||||
"node_busy_mean_m": 2806,
|
||||
"node_busy_p95_m": 3722,
|
||||
"node_free_mean_m": 1194,
|
||||
"node_free_p5_m": 278,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"restart": {
|
||||
"samples": 10,
|
||||
"seconds": 10.3,
|
||||
"cpu_mean_m": 403.3,
|
||||
"cpu_p95_m": 1019.7,
|
||||
"cpu_peak_m": 1019.7,
|
||||
"cfs_periods": 54,
|
||||
"cfs_throttled_periods": 39,
|
||||
"throttle_ratio": 0.722,
|
||||
"throttled_seconds": 2.37,
|
||||
"psi_cpu_some_share": 0.235,
|
||||
"mem_peak_mib": 483.3,
|
||||
"mem_mean_mib": 154.0,
|
||||
"node_busy_mean_m": 1908,
|
||||
"node_busy_p95_m": 3359,
|
||||
"node_free_mean_m": 2092,
|
||||
"node_free_p5_m": 641,
|
||||
"load1_max": 7.84
|
||||
},
|
||||
"recovery": {
|
||||
"samples": 59,
|
||||
"seconds": 60.6,
|
||||
"cpu_mean_m": 860.6,
|
||||
"cpu_p95_m": 956.8,
|
||||
"cpu_peak_m": 982.3,
|
||||
"cfs_periods": 605,
|
||||
"cfs_throttled_periods": 251,
|
||||
"throttle_ratio": 0.415,
|
||||
"throttled_seconds": 6.17,
|
||||
"psi_cpu_some_share": 0.152,
|
||||
"mem_peak_mib": 322.2,
|
||||
"mem_mean_mib": 251.0,
|
||||
"node_busy_mean_m": 2624,
|
||||
"node_busy_p95_m": 3608,
|
||||
"node_free_mean_m": 1376,
|
||||
"node_free_p5_m": 392,
|
||||
"load1_max": 6.66
|
||||
},
|
||||
"workload_all_load_phases": {
|
||||
"samples": 189,
|
||||
"seconds": 194.0,
|
||||
"cpu_mean_m": 775.3,
|
||||
"cpu_p95_m": 998.3,
|
||||
"cpu_peak_m": 1019.7,
|
||||
"cfs_periods": 1889,
|
||||
"cfs_throttled_periods": 675,
|
||||
"throttle_ratio": 0.357,
|
||||
"throttled_seconds": 22.77,
|
||||
"psi_cpu_some_share": 0.166,
|
||||
"mem_peak_mib": 483.3,
|
||||
"mem_mean_mib": 295.7,
|
||||
"node_busy_mean_m": 2790,
|
||||
"node_busy_p95_m": 3853,
|
||||
"node_free_mean_m": 1210,
|
||||
"node_free_p5_m": 147,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"post": {
|
||||
"samples": 63,
|
||||
"seconds": 63.9,
|
||||
"cpu_mean_m": 12.3,
|
||||
"cpu_p95_m": 14.3,
|
||||
"cpu_peak_m": 15.1,
|
||||
"cfs_periods": 161,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.001,
|
||||
"mem_peak_mib": 322.5,
|
||||
"mem_mean_mib": 322.2,
|
||||
"node_busy_mean_m": 1413,
|
||||
"node_busy_p95_m": 2414,
|
||||
"node_free_mean_m": 2587,
|
||||
"node_free_p5_m": 1586,
|
||||
"load1_max": 5.75
|
||||
}
|
||||
},
|
||||
"db": {
|
||||
"pods": [
|
||||
"vergabe-db-7c4bffdd48-w4jz4"
|
||||
],
|
||||
"baseline_pre": {
|
||||
"samples": 127,
|
||||
"seconds": 128.5,
|
||||
"cpu_mean_m": 21.5,
|
||||
"cpu_p95_m": 65.7,
|
||||
"cpu_peak_m": 71.7,
|
||||
"cfs_periods": 333,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 91.3,
|
||||
"mem_mean_mib": 90.4,
|
||||
"node_busy_mean_m": 1346,
|
||||
"node_busy_p95_m": 2448,
|
||||
"node_free_mean_m": 2654,
|
||||
"node_free_p5_m": 1552,
|
||||
"load1_max": 4.91
|
||||
},
|
||||
"harness_idle": {
|
||||
"samples": 29,
|
||||
"seconds": 29.3,
|
||||
"cpu_mean_m": 20.9,
|
||||
"cpu_p95_m": 64.5,
|
||||
"cpu_peak_m": 64.9,
|
||||
"cfs_periods": 75,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.003,
|
||||
"mem_peak_mib": 93.0,
|
||||
"mem_mean_mib": 92.7,
|
||||
"node_busy_mean_m": 1522,
|
||||
"node_busy_p95_m": 2680,
|
||||
"node_free_mean_m": 2478,
|
||||
"node_free_p5_m": 1320,
|
||||
"load1_max": 3.15
|
||||
},
|
||||
"ordinary": {
|
||||
"samples": 88,
|
||||
"seconds": 89.9,
|
||||
"cpu_mean_m": 387.8,
|
||||
"cpu_p95_m": 501.1,
|
||||
"cpu_peak_m": 532.2,
|
||||
"cfs_periods": 892,
|
||||
"cfs_throttled_periods": 2,
|
||||
"throttle_ratio": 0.002,
|
||||
"throttled_seconds": 0.02,
|
||||
"psi_cpu_some_share": 0.067,
|
||||
"mem_peak_mib": 101.4,
|
||||
"mem_mean_mib": 95.7,
|
||||
"node_busy_mean_m": 2974,
|
||||
"node_busy_p95_m": 3895,
|
||||
"node_free_mean_m": 1026,
|
||||
"node_free_p5_m": 105,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"burst": {
|
||||
"samples": 29,
|
||||
"seconds": 29.6,
|
||||
"cpu_mean_m": 430.1,
|
||||
"cpu_p95_m": 539.1,
|
||||
"cpu_peak_m": 544.4,
|
||||
"cfs_periods": 293,
|
||||
"cfs_throttled_periods": 2,
|
||||
"throttle_ratio": 0.007,
|
||||
"throttled_seconds": 0.06,
|
||||
"psi_cpu_some_share": 0.062,
|
||||
"mem_peak_mib": 113.1,
|
||||
"mem_mean_mib": 97.1,
|
||||
"node_busy_mean_m": 2798,
|
||||
"node_busy_p95_m": 3664,
|
||||
"node_free_mean_m": 1202,
|
||||
"node_free_p5_m": 336,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"restart": {
|
||||
"samples": 18,
|
||||
"seconds": 18.3,
|
||||
"cpu_mean_m": 24.3,
|
||||
"cpu_p95_m": 67.7,
|
||||
"cpu_peak_m": 67.7,
|
||||
"cfs_periods": 55,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.009,
|
||||
"mem_peak_mib": 103.3,
|
||||
"mem_mean_mib": 101.4,
|
||||
"node_busy_mean_m": 2650,
|
||||
"node_busy_p95_m": 3961,
|
||||
"node_free_mean_m": 1350,
|
||||
"node_free_p5_m": 39,
|
||||
"load1_max": 7.84
|
||||
},
|
||||
"recovery": {
|
||||
"samples": 58,
|
||||
"seconds": 58.9,
|
||||
"cpu_mean_m": 335.0,
|
||||
"cpu_p95_m": 398.7,
|
||||
"cpu_peak_m": 408.1,
|
||||
"cfs_periods": 581,
|
||||
"cfs_throttled_periods": 1,
|
||||
"throttle_ratio": 0.002,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.041,
|
||||
"mem_peak_mib": 111.3,
|
||||
"mem_mean_mib": 106.9,
|
||||
"node_busy_mean_m": 2621,
|
||||
"node_busy_p95_m": 3524,
|
||||
"node_free_mean_m": 1379,
|
||||
"node_free_p5_m": 476,
|
||||
"load1_max": 6.66
|
||||
},
|
||||
"workload_all_load_phases": {
|
||||
"samples": 196,
|
||||
"seconds": 199.8,
|
||||
"cpu_mean_m": 340.8,
|
||||
"cpu_p95_m": 500.0,
|
||||
"cpu_peak_m": 544.4,
|
||||
"cfs_periods": 1837,
|
||||
"cfs_throttled_periods": 5,
|
||||
"throttle_ratio": 0.003,
|
||||
"throttled_seconds": 0.08,
|
||||
"psi_cpu_some_share": 0.052,
|
||||
"mem_peak_mib": 113.1,
|
||||
"mem_mean_mib": 99.8,
|
||||
"node_busy_mean_m": 2813,
|
||||
"node_busy_p95_m": 3894,
|
||||
"node_free_mean_m": 1187,
|
||||
"node_free_p5_m": 106,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"post": {
|
||||
"samples": 64,
|
||||
"seconds": 64.8,
|
||||
"cpu_mean_m": 21.7,
|
||||
"cpu_p95_m": 65.1,
|
||||
"cpu_peak_m": 79.6,
|
||||
"cfs_periods": 161,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 101.3,
|
||||
"mem_mean_mib": 98.1,
|
||||
"node_busy_mean_m": 1420,
|
||||
"node_busy_p95_m": 2509,
|
||||
"node_free_mean_m": 2580,
|
||||
"node_free_p5_m": 1491,
|
||||
"load1_max": 5.75
|
||||
}
|
||||
},
|
||||
"driver": {
|
||||
"pods": [
|
||||
"synthetic-driver"
|
||||
],
|
||||
"baseline_pre": {
|
||||
"samples": 126,
|
||||
"seconds": 127.9,
|
||||
"cpu_mean_m": 11.6,
|
||||
"cpu_p95_m": 13.2,
|
||||
"cpu_peak_m": 13.7,
|
||||
"cfs_periods": 267,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 6.6,
|
||||
"mem_mean_mib": 6.3,
|
||||
"node_busy_mean_m": 1348,
|
||||
"node_busy_p95_m": 2510,
|
||||
"node_free_mean_m": 2652,
|
||||
"node_free_p5_m": 1490,
|
||||
"load1_max": 4.91
|
||||
},
|
||||
"harness_idle": {
|
||||
"samples": 28,
|
||||
"seconds": 28.4,
|
||||
"cpu_mean_m": 11.6,
|
||||
"cpu_p95_m": 13.1,
|
||||
"cpu_peak_m": 13.1,
|
||||
"cfs_periods": 59,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 63.3,
|
||||
"mem_mean_mib": 63.0,
|
||||
"node_busy_mean_m": 1537,
|
||||
"node_busy_p95_m": 2829,
|
||||
"node_free_mean_m": 2463,
|
||||
"node_free_p5_m": 1171,
|
||||
"load1_max": 2.91
|
||||
},
|
||||
"ordinary": {
|
||||
"samples": 88,
|
||||
"seconds": 90.0,
|
||||
"cpu_mean_m": 91.7,
|
||||
"cpu_p95_m": 117.2,
|
||||
"cpu_peak_m": 124.8,
|
||||
"cfs_periods": 891,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.029,
|
||||
"mem_peak_mib": 75.9,
|
||||
"mem_mean_mib": 72.4,
|
||||
"node_busy_mean_m": 2977,
|
||||
"node_busy_p95_m": 3912,
|
||||
"node_free_mean_m": 1023,
|
||||
"node_free_p5_m": 88,
|
||||
"load1_max": 7.83
|
||||
},
|
||||
"burst": {
|
||||
"samples": 29,
|
||||
"seconds": 29.6,
|
||||
"cpu_mean_m": 98.5,
|
||||
"cpu_p95_m": 124.8,
|
||||
"cpu_peak_m": 129.3,
|
||||
"cfs_periods": 289,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.026,
|
||||
"mem_peak_mib": 78.4,
|
||||
"mem_mean_mib": 76.4,
|
||||
"node_busy_mean_m": 2790,
|
||||
"node_busy_p95_m": 3387,
|
||||
"node_free_mean_m": 1210,
|
||||
"node_free_p5_m": 613,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"restart": {
|
||||
"samples": 18,
|
||||
"seconds": 18.3,
|
||||
"cpu_mean_m": 22.4,
|
||||
"cpu_p95_m": 25.5,
|
||||
"cpu_peak_m": 25.5,
|
||||
"cfs_periods": 112,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.009,
|
||||
"mem_peak_mib": 73.7,
|
||||
"mem_mean_mib": 71.3,
|
||||
"node_busy_mean_m": 2650,
|
||||
"node_busy_p95_m": 3990,
|
||||
"node_free_mean_m": 1350,
|
||||
"node_free_p5_m": 10,
|
||||
"load1_max": 7.84
|
||||
},
|
||||
"recovery": {
|
||||
"samples": 59,
|
||||
"seconds": 60.1,
|
||||
"cpu_mean_m": 82.2,
|
||||
"cpu_p95_m": 95.4,
|
||||
"cpu_peak_m": 139.7,
|
||||
"cfs_periods": 594,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.02,
|
||||
"mem_peak_mib": 80.9,
|
||||
"mem_mean_mib": 77.2,
|
||||
"node_busy_mean_m": 2635,
|
||||
"node_busy_p95_m": 3540,
|
||||
"node_free_mean_m": 1365,
|
||||
"node_free_p5_m": 460,
|
||||
"load1_max": 6.66
|
||||
},
|
||||
"workload_all_load_phases": {
|
||||
"samples": 197,
|
||||
"seconds": 201.1,
|
||||
"cpu_mean_m": 84.4,
|
||||
"cpu_p95_m": 117.7,
|
||||
"cpu_peak_m": 200.8,
|
||||
"cfs_periods": 1911,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.024,
|
||||
"mem_peak_mib": 80.9,
|
||||
"mem_mean_mib": 74.4,
|
||||
"node_busy_mean_m": 2820,
|
||||
"node_busy_p95_m": 3912,
|
||||
"node_free_mean_m": 1180,
|
||||
"node_free_p5_m": 88,
|
||||
"load1_max": 8.19
|
||||
},
|
||||
"post": {
|
||||
"samples": 64,
|
||||
"seconds": 64.9,
|
||||
"cpu_mean_m": 16.3,
|
||||
"cpu_p95_m": 13.2,
|
||||
"cpu_peak_m": 317.3,
|
||||
"cfs_periods": 137,
|
||||
"cfs_throttled_periods": 0,
|
||||
"throttle_ratio": 0.0,
|
||||
"throttled_seconds": 0.0,
|
||||
"psi_cpu_some_share": 0.002,
|
||||
"mem_peak_mib": 79.1,
|
||||
"mem_mean_mib": 19.2,
|
||||
"node_busy_mean_m": 1423,
|
||||
"node_busy_p95_m": 2535,
|
||||
"node_free_mean_m": 2577,
|
||||
"node_free_p5_m": 1465,
|
||||
"load1_max": 5.75
|
||||
}
|
||||
}
|
||||
},
|
||||
"per_operation": {
|
||||
"burst/detail": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.1132,
|
||||
"max_s": 0.1706
|
||||
},
|
||||
"burst/document-download": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0447,
|
||||
"max_s": 0.0592
|
||||
},
|
||||
"burst/document-upload": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.075,
|
||||
"max_s": 0.1055
|
||||
},
|
||||
"burst/list": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.2974,
|
||||
"max_s": 0.3233
|
||||
},
|
||||
"burst/login": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 1.1775,
|
||||
"max_s": 1.1775
|
||||
},
|
||||
"burst/login-form": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0281,
|
||||
"max_s": 0.0281
|
||||
},
|
||||
"burst/lot-create": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0581,
|
||||
"max_s": 0.0971
|
||||
},
|
||||
"burst/lot-edit": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0536,
|
||||
"max_s": 0.0743
|
||||
},
|
||||
"burst/search": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.2652,
|
||||
"max_s": 0.3359
|
||||
},
|
||||
"burst/task-create": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0599,
|
||||
"max_s": 0.1778
|
||||
},
|
||||
"burst/task-edit": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0597,
|
||||
"max_s": 0.0759
|
||||
},
|
||||
"burst/tender-create": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0772,
|
||||
"max_s": 0.0845
|
||||
},
|
||||
"burst/tender-edit": {
|
||||
"n": 77,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0623,
|
||||
"max_s": 0.0732
|
||||
},
|
||||
"ordinary/detail": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.1758,
|
||||
"max_s": 0.3877
|
||||
},
|
||||
"ordinary/document-download": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0537,
|
||||
"max_s": 0.11
|
||||
},
|
||||
"ordinary/document-upload": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.1051,
|
||||
"max_s": 0.4418
|
||||
},
|
||||
"ordinary/list": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.194,
|
||||
"max_s": 0.3779
|
||||
},
|
||||
"ordinary/login": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 1.2574,
|
||||
"max_s": 1.2574
|
||||
},
|
||||
"ordinary/login-form": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 0.3288,
|
||||
"max_s": 0.3288
|
||||
},
|
||||
"ordinary/lot-create": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0696,
|
||||
"max_s": 0.1603
|
||||
},
|
||||
"ordinary/lot-edit": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0763,
|
||||
"max_s": 0.1017
|
||||
},
|
||||
"ordinary/search": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.2294,
|
||||
"max_s": 0.6217
|
||||
},
|
||||
"ordinary/task-create": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0697,
|
||||
"max_s": 0.1338
|
||||
},
|
||||
"ordinary/task-edit": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0686,
|
||||
"max_s": 0.1146
|
||||
},
|
||||
"ordinary/tender-create": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0626,
|
||||
"max_s": 0.1187
|
||||
},
|
||||
"ordinary/tender-edit": {
|
||||
"n": 211,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0633,
|
||||
"max_s": 0.1171
|
||||
},
|
||||
"recovery/detail": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.131,
|
||||
"max_s": 0.1379
|
||||
},
|
||||
"recovery/document-download": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0475,
|
||||
"max_s": 0.0818
|
||||
},
|
||||
"recovery/document-upload": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.08,
|
||||
"max_s": 0.1587
|
||||
},
|
||||
"recovery/list": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.3302,
|
||||
"max_s": 0.3757
|
||||
},
|
||||
"recovery/login": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 1.2653,
|
||||
"max_s": 1.2653
|
||||
},
|
||||
"recovery/login-form": {
|
||||
"n": 2,
|
||||
"errors": 0,
|
||||
"p95_s": 0.6157,
|
||||
"max_s": 0.6157
|
||||
},
|
||||
"recovery/lot-create": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0526,
|
||||
"max_s": 0.0945
|
||||
},
|
||||
"recovery/lot-edit": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0602,
|
||||
"max_s": 0.1186
|
||||
},
|
||||
"recovery/search": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.3193,
|
||||
"max_s": 0.4237
|
||||
},
|
||||
"recovery/task-create": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0558,
|
||||
"max_s": 0.0888
|
||||
},
|
||||
"recovery/task-edit": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0577,
|
||||
"max_s": 0.0959
|
||||
},
|
||||
"recovery/tender-create": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0489,
|
||||
"max_s": 0.0661
|
||||
},
|
||||
"recovery/tender-edit": {
|
||||
"n": 119,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0556,
|
||||
"max_s": 0.0753
|
||||
}
|
||||
},
|
||||
"per_operation_all_phases": {
|
||||
"detail": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.1463
|
||||
},
|
||||
"document-download": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0498
|
||||
},
|
||||
"document-upload": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0878
|
||||
},
|
||||
"list": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.2624
|
||||
},
|
||||
"login": {
|
||||
"n": 6,
|
||||
"errors": 0,
|
||||
"p95_s": 1.2653
|
||||
},
|
||||
"login-form": {
|
||||
"n": 6,
|
||||
"errors": 0,
|
||||
"p95_s": 0.6157
|
||||
},
|
||||
"lot-create": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0613
|
||||
},
|
||||
"lot-edit": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0621
|
||||
},
|
||||
"search": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.2553
|
||||
},
|
||||
"task-create": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0621
|
||||
},
|
||||
"task-edit": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0657
|
||||
},
|
||||
"tender-create": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0661
|
||||
},
|
||||
"tender-edit": {
|
||||
"n": 407,
|
||||
"errors": 0,
|
||||
"p95_s": 0.0587
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"ordinary": {
|
||||
"requests": 3675,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.19182809395715594
|
||||
},
|
||||
"document_transfer": {
|
||||
"requests": 814,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.07621732295956463
|
||||
},
|
||||
"complete": true,
|
||||
"provisional_pass": true,
|
||||
"measurement_complete": true,
|
||||
"measurement_gaps": [],
|
||||
"startup_seconds": 0.04661138809751719,
|
||||
"duration_seconds": 201.53947580303065,
|
||||
"completed_iterations": {
|
||||
"ordinary": [
|
||||
106,
|
||||
105
|
||||
],
|
||||
"burst": [
|
||||
39,
|
||||
38
|
||||
],
|
||||
"recovery": [
|
||||
59,
|
||||
60
|
||||
]
|
||||
},
|
||||
"host": {
|
||||
"logical_cpus": 4,
|
||||
"affinity_cpus": 4,
|
||||
"cpu_model": "AMD EPYC Processor (with IBPB)",
|
||||
"memory_total": "MemTotal: 16377032 kB",
|
||||
"cgroup_cpu_max": "100000 100000",
|
||||
"app_cpu_quota": 1.0
|
||||
}
|
||||
},
|
||||
"phase_results": [
|
||||
{
|
||||
"name": "ordinary",
|
||||
"kind": "load",
|
||||
"seconds": 91.06608834001236,
|
||||
"started_at": "2026-10-21T06:30:01.523056+00:00",
|
||||
"finished_at": "2026-10-21T06:31:32.589152+00:00",
|
||||
"expected_requests": 2325,
|
||||
"actual_requests": 2325,
|
||||
"complete": true,
|
||||
"ordinary": {
|
||||
"requests": 1903,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.14162223995663226
|
||||
},
|
||||
"document_transfer": {
|
||||
"requests": 422,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.08520571992266923
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "burst",
|
||||
"kind": "load",
|
||||
"seconds": 30.685886312043294,
|
||||
"started_at": "2026-10-21T06:31:32.589194+00:00",
|
||||
"finished_at": "2026-10-21T06:32:03.275085+00:00",
|
||||
"expected_requests": 851,
|
||||
"actual_requests": 851,
|
||||
"complete": true,
|
||||
"ordinary": {
|
||||
"requests": 697,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.19206618797034025
|
||||
},
|
||||
"document_transfer": {
|
||||
"requests": 154,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.06853019201662391
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "restart",
|
||||
"kind": "restart",
|
||||
"seconds": 18.981462653027847,
|
||||
"started_at": "2026-10-21T06:32:03.275117+00:00",
|
||||
"finished_at": "2026-10-21T06:32:22.256585+00:00",
|
||||
"ready": true
|
||||
},
|
||||
{
|
||||
"name": "recovery",
|
||||
"kind": "load",
|
||||
"seconds": 60.80042844603304,
|
||||
"started_at": "2026-10-21T06:32:22.256609+00:00",
|
||||
"finished_at": "2026-10-21T06:33:23.057045+00:00",
|
||||
"expected_requests": 1313,
|
||||
"actual_requests": 1313,
|
||||
"complete": true,
|
||||
"ordinary": {
|
||||
"requests": 1075,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.21410848898813128
|
||||
},
|
||||
"document_transfer": {
|
||||
"requests": 238,
|
||||
"errors": 0,
|
||||
"p95_seconds": 0.07255190098658204
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import json, math, sys, gzip, collections
|
||||
from datetime import datetime
|
||||
R = sys.argv[1]
|
||||
def ts(x): return datetime.fromisoformat(x.replace("Z", "+00:00").replace(".", ".", 1)[:26] + ("+00:00" if "+" not in x[19:] and not x.endswith("Z") else "")).timestamp() if False else _ts(x)
|
||||
def _ts(x):
|
||||
x = x.rstrip("Z")
|
||||
if "+" in x[19:]: x = x[:x.index("+", 19)]
|
||||
if "." in x: a, b = x.split("."); x = a + "." + b[:6]
|
||||
return datetime.fromisoformat(x + "+00:00").timestamp()
|
||||
def p95(v):
|
||||
v = sorted(v); return v[max(0, math.ceil(0.95 * len(v)) - 1)] if v else None
|
||||
def parse(path):
|
||||
segs, cur, s = [], None, None
|
||||
for line in open(path):
|
||||
line = line.rstrip("\n")
|
||||
if line.startswith("POD "): cur = {"pod": line.split()[1], "samples": []}; segs.append(cur)
|
||||
elif line.startswith("S "):
|
||||
s = {"t": float(line[2:])}; cur["samples"].append(s)
|
||||
elif s is None: continue
|
||||
elif line.startswith("M "): s["mem"] = int(line[2:])
|
||||
elif line.startswith("P "): s["psi_some_total"] = int(line.split("total=")[1])
|
||||
elif line.startswith("N "):
|
||||
f = [int(x) for x in line.split()[2:]]; s["node_total"] = sum(f[:8]); s["node_idle"] = f[3] + f[4]
|
||||
elif line.startswith("L "): s["load1"] = float(line.split()[1])
|
||||
elif line.split() and line.split()[0] in ("usage_usec","nr_periods","nr_throttled","throttled_usec"):
|
||||
k, v = line.split(); s[k] = int(v)
|
||||
for seg in segs: seg["samples"] = [x for x in seg["samples"] if "mem" in x and "node_total" in x and "usage_usec" in x]
|
||||
return segs
|
||||
def window(segs, a, b, cores=4):
|
||||
rates, mems, thr_p, per_p, thr_us, nodebusy, psi, load = [], [], 0, 0, 0, [], 0, []
|
||||
span = 0; used = 0
|
||||
for seg in segs:
|
||||
xs = [x for x in seg["samples"] if a <= x["t"] <= b]
|
||||
for x in xs: mems.append(x["mem"]); load.append(x["load1"])
|
||||
for p, q in zip(xs, xs[1:]):
|
||||
dt = q["t"] - p["t"]
|
||||
if dt <= 0 or dt > 5: continue
|
||||
du = q["usage_usec"] - p["usage_usec"]; used += du; span += dt
|
||||
rates.append(du / 1e3 / dt)
|
||||
per_p += q.get("nr_periods",0) - p.get("nr_periods",0); thr_p += q.get("nr_throttled",0) - p.get("nr_throttled",0)
|
||||
thr_us += q.get("throttled_usec",0) - p.get("throttled_usec",0)
|
||||
psi += q["psi_some_total"] - p["psi_some_total"]
|
||||
tot = q["node_total"] - p["node_total"]; idle = q["node_idle"] - p["node_idle"]
|
||||
if tot > 0: nodebusy.append(cores * (tot - idle) / tot * 1000)
|
||||
if not rates: return None
|
||||
return {"samples": len(rates), "seconds": round(span,1), "cpu_mean_m": round(used/1e3/span,1), "cpu_p95_m": round(p95(rates),1), "cpu_peak_m": round(max(rates),1),
|
||||
"cfs_periods": per_p, "cfs_throttled_periods": thr_p, "throttle_ratio": round(thr_p/per_p,3) if per_p else None, "throttled_seconds": round(thr_us/1e6,2),
|
||||
"psi_cpu_some_share": round(psi/1e6/span,3), "mem_peak_mib": round(max(mems)/2**20,1), "mem_mean_mib": round(sum(mems)/len(mems)/2**20,1),
|
||||
"node_busy_mean_m": round(sum(nodebusy)/len(nodebusy)), "node_busy_p95_m": round(p95(nodebusy)), "node_free_mean_m": round(cores*1000-sum(nodebusy)/len(nodebusy)),
|
||||
"node_free_p5_m": round(cores*1000-p95(nodebusy)), "load1_max": max(load)}
|
||||
win = {}
|
||||
for line in open(f"{R}/windows.txt"):
|
||||
k, *v = line.split(); win[k] = v
|
||||
res = json.load(open(f"{R}/result.json"))
|
||||
W = {"baseline_pre": (_ts(win["baseline_start"][0]), _ts(win["driver_start"][0])),
|
||||
"harness_idle": (_ts(res["idle_window"]["started_at"]), _ts(res["idle_window"]["finished_at"]))}
|
||||
for p in res["phase_results"]: W[p["name"]] = (_ts(p["started_at"]), _ts(p["finished_at"]))
|
||||
loads = [p for p in res["phase_results"] if p["kind"] == "load"]
|
||||
W["workload_all_load_phases"] = (_ts(loads[0]["started_at"]), _ts(loads[-1]["finished_at"]))
|
||||
W["post"] = (_ts(win["driver_exit"][1]), _ts(win["post_end"][0]))
|
||||
out = {"windows_utc": {k: [datetime.utcfromtimestamp(a).isoformat()+"Z", datetime.utcfromtimestamp(b).isoformat()+"Z"] for k,(a,b) in W.items()}, "cgroup": {}}
|
||||
for name in ("app", "db", "driver"):
|
||||
segs = parse(f"{R}/{name}-cgroup.txt")
|
||||
out["cgroup"][name] = {"pods": [s["pod"] for s in segs], **{k: window(segs, a, b) for k, (a, b) in W.items() if k != "workload_all_load_phases" or True}}
|
||||
ops = collections.defaultdict(list)
|
||||
for r in res["operations"]: ops[(r["phase"], r["operation"])].append(r)
|
||||
out["per_operation"] = {f"{ph}/{op}": {"n": len(v), "errors": sum(not x["ok"] for x in v), "p95_s": round(p95([x["seconds"] for x in v]),4), "max_s": round(max(x["seconds"] for x in v),4)} for (ph, op), v in sorted(ops.items())}
|
||||
allops = collections.defaultdict(list)
|
||||
for r in res["operations"]: allops[r["operation"]].append(r)
|
||||
out["per_operation_all_phases"] = {op: {"n": len(v), "errors": sum(not x["ok"] for x in v), "p95_s": round(p95([x["seconds"] for x in v]),4)} for op, v in sorted(allops.items())}
|
||||
out["summary"] = {k: res[k] for k in ("ordinary","document_transfer","complete","provisional_pass","measurement_complete","measurement_gaps","startup_seconds","duration_seconds","completed_iterations","host")}
|
||||
out["phase_results"] = [{k: v for k, v in p.items() if k != "operations"} for p in res["phase_results"]]
|
||||
json.dump(out, open(sys.argv[2], "w"), indent=1)
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: vergabe-sizing-20261021
|
||||
labels:
|
||||
railiance.io/purpose: synthetic-sizing
|
||||
railiance.io/task: rapps-wp-0014-t04
|
||||
railiance.io/ephemeral: "true"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata: {name: bench-media, namespace: vergabe-sizing-20261021}
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
storageClassName: local-path
|
||||
resources: {requests: {storage: 5Gi}}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata: {name: vergabe-db, namespace: vergabe-sizing-20261021}
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy: {type: Recreate}
|
||||
selector: {matchLabels: {app: vergabe-sizing-db}}
|
||||
template:
|
||||
metadata: {labels: {app: vergabe-sizing-db}}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: postgres
|
||||
image: docker.io/library/postgres@sha256:4b7183ac05f8ef417db21fd72d71047a4238340c261d3cc3ddb6d579ab5071ae
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- {name: POSTGRES_DB, value: synthetic}
|
||||
- {name: POSTGRES_USER, value: synthetic}
|
||||
- {name: POSTGRES_PASSWORD, value: synthetic-local-only}
|
||||
- {name: PGDATA, value: /var/lib/postgresql/data/pgdata}
|
||||
ports: [{containerPort: 5432}]
|
||||
readinessProbe: {exec: {command: [pg_isready, -U, synthetic]}, periodSeconds: 5}
|
||||
resources: {requests: {cpu: 100m, memory: 256Mi}, limits: {cpu: "1", memory: 1Gi}}
|
||||
volumeMounts: [{name: data, mountPath: /var/lib/postgresql/data}]
|
||||
volumes: [{name: data, emptyDir: {}}]
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata: {name: vergabe-db, namespace: vergabe-sizing-20261021}
|
||||
spec: {selector: {app: vergabe-sizing-db}, ports: [{port: 5432, targetPort: 5432}]}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata: {name: vergabe-app, namespace: vergabe-sizing-20261021}
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy: {type: Recreate}
|
||||
selector: {matchLabels: {app: vergabe-sizing-app}}
|
||||
template:
|
||||
metadata: {labels: {app: vergabe-sizing-app}}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
securityContext: {fsGroup: 999, runAsGroup: 999, runAsNonRoot: true, runAsUser: 999}
|
||||
initContainers:
|
||||
- name: migrate
|
||||
image: forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91
|
||||
imagePullPolicy: IfNotPresent
|
||||
command: [python, manage.py, migrate, --noinput]
|
||||
env: &env
|
||||
- {name: DJANGO_SETTINGS_MODULE, value: synthetic_settings}
|
||||
- {name: PYTHONPATH, value: /synthetic}
|
||||
- {name: SECRET_KEY, value: synthetic-disposable-not-a-deployment-key}
|
||||
- {name: DATABASE_URL, value: "postgres://synthetic:synthetic-local-only@vergabe-db:5432/synthetic"}
|
||||
resources: &res {requests: {cpu: 60m, memory: 256Mi}, limits: {cpu: "1", memory: 1Gi}}
|
||||
securityContext: &sc {allowPrivilegeEscalation: false, capabilities: {drop: [ALL]}, readOnlyRootFilesystem: false}
|
||||
volumeMounts: [{name: settings, mountPath: /synthetic}]
|
||||
containers:
|
||||
- name: app
|
||||
image: forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91
|
||||
imagePullPolicy: IfNotPresent
|
||||
env: *env
|
||||
ports: [{containerPort: 8000, name: http}]
|
||||
readinessProbe: {httpGet: {path: /health/, port: 8000, httpHeaders: [{name: Host, value: vergabe-app}]}, initialDelaySeconds: 5, periodSeconds: 10, timeoutSeconds: 5, failureThreshold: 3}
|
||||
livenessProbe: {httpGet: {path: /health/, port: 8000, httpHeaders: [{name: Host, value: vergabe-app}]}, initialDelaySeconds: 30, periodSeconds: 30, timeoutSeconds: 5, failureThreshold: 3}
|
||||
resources: *res
|
||||
securityContext: *sc
|
||||
volumeMounts:
|
||||
- {name: settings, mountPath: /synthetic}
|
||||
- {name: media, mountPath: /app/media}
|
||||
- {name: app-state, mountPath: /app/.issue-facade}
|
||||
volumes:
|
||||
- {name: settings, configMap: {name: synthetic-settings}}
|
||||
- {name: media, persistentVolumeClaim: {claimName: bench-media}}
|
||||
- {name: app-state, emptyDir: {}}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata: {name: vergabe-app, namespace: vergabe-sizing-20261021}
|
||||
spec: {selector: {app: vergabe-sizing-app}, ports: [{port: 80, targetPort: 8000}]}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata: {name: synthetic-driver, namespace: vergabe-sizing-20261021}
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata: {name: synthetic-driver-restart, namespace: vergabe-sizing-20261021}
|
||||
rules: [{apiGroups: [""], resources: [pods], verbs: [get, list, delete]}]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata: {name: synthetic-driver-restart, namespace: vergabe-sizing-20261021}
|
||||
roleRef: {apiGroup: rbac.authorization.k8s.io, kind: Role, name: synthetic-driver-restart}
|
||||
subjects: [{kind: ServiceAccount, name: synthetic-driver, namespace: vergabe-sizing-20261021}]
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata: {name: synthetic-isolation, namespace: vergabe-sizing-20261021}
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
ingress: [{from: [{podSelector: {}}]}]
|
||||
egress:
|
||||
- to: [{podSelector: {}}]
|
||||
- to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}, podSelector: {matchLabels: {k8s-app: kube-dns}}}]
|
||||
ports: [{port: 53, protocol: UDP}, {port: 53, protocol: TCP}]
|
||||
- to: [{ipBlock: {cidr: 92.205.62.239/32}}, {ipBlock: {cidr: 10.43.0.1/32}}]
|
||||
ports: [{port: 6443, protocol: TCP}, {port: 443, protocol: TCP}]
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"users": 2,
|
||||
"workers": 3,
|
||||
"iterations": 100000,
|
||||
"tenders": 100,
|
||||
"document_bytes": 1048576,
|
||||
"think_seconds": 0.2,
|
||||
"timeout_seconds": 30,
|
||||
"ordinary_p95_seconds": 2.0,
|
||||
"duration_seconds": 180,
|
||||
"idle_seconds": 30,
|
||||
"runtime": "remote",
|
||||
"app_image": "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91",
|
||||
"app_cpu_limit": 1.0,
|
||||
"app_memory_limit": "1Gi",
|
||||
"operations": [
|
||||
"list",
|
||||
"search",
|
||||
"tender-create",
|
||||
"detail",
|
||||
"tender-edit",
|
||||
"lot-create",
|
||||
"lot-edit",
|
||||
"task-create",
|
||||
"task-edit",
|
||||
"document-upload",
|
||||
"document-download"
|
||||
],
|
||||
"phases": [
|
||||
{
|
||||
"name": "ordinary",
|
||||
"kind": "load",
|
||||
"duration_seconds": 90,
|
||||
"think_seconds": 0.2
|
||||
},
|
||||
{
|
||||
"name": "burst",
|
||||
"kind": "load",
|
||||
"duration_seconds": 30,
|
||||
"think_seconds": 0
|
||||
},
|
||||
{
|
||||
"name": "restart",
|
||||
"kind": "restart"
|
||||
},
|
||||
{
|
||||
"name": "recovery",
|
||||
"kind": "load",
|
||||
"duration_seconds": 60,
|
||||
"think_seconds": 0.2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata: {name: synthetic-driver, namespace: vergabe-sizing-20261021, labels: {app: vergabe-sizing-driver}}
|
||||
spec:
|
||||
serviceAccountName: synthetic-driver
|
||||
restartPolicy: Never
|
||||
securityContext: {runAsGroup: 999, runAsNonRoot: true, runAsUser: 999}
|
||||
containers:
|
||||
- name: driver
|
||||
image: forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91
|
||||
imagePullPolicy: IfNotPresent
|
||||
workingDir: /app
|
||||
command: [sleep, "7200"]
|
||||
env:
|
||||
- {name: PYTHONPATH, value: /app}
|
||||
- {name: SYNTHETIC_BASE_URL, value: "http://vergabe-app"}
|
||||
- {name: SYNTHETIC_DATABASE_URL, value: "postgres://synthetic:synthetic-local-only@vergabe-db:5432/synthetic"}
|
||||
- {name: SYNTHETIC_RESTART_SELECTOR, value: "app=vergabe-sizing-app"}
|
||||
- {name: SYNTHETIC_SOURCE_REVISION, value: "deployed image c067993037f1cdeeb7b22d5600b51c649bc239db; driver from vergabe-teilnahme working tree"}
|
||||
- {name: SYNTHETIC_DATABASE_DESCRIPTION, value: "fresh PostgreSQL 16 pod in throwaway namespace (docker.io/library/postgres:16 pinned digest)"}
|
||||
- {name: SYNTHETIC_TRANSPORT_DESCRIPTION, value: "in-cluster HTTP via ClusterIP Service to Gunicorn (image CMD, 3 workers)"}
|
||||
resources: {requests: {cpu: 100m, memory: 256Mi}, limits: {cpu: "1", memory: 1Gi}}
|
||||
securityContext: {allowPrivilegeEscalation: false, capabilities: {drop: [ALL]}}
|
||||
volumeMounts:
|
||||
- {name: driver, mountPath: /driver}
|
||||
- {name: out, mountPath: /out}
|
||||
volumes:
|
||||
- {name: driver, configMap: {name: synthetic-driver}}
|
||||
- {name: out, emptyDir: {}}
|
||||
File diff suppressed because it is too large
Load diff
15
docs/evidence/2026-10-21-vergabe-target-replay/harness/orchestrate.sh
Executable file
15
docs/evidence/2026-10-21-vergabe-target-replay/harness/orchestrate.sh
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
NS=vergabe-sizing-20261021; W=$SCRATCH; R=$W/raw; STOP=$R/stop; rm -f $STOP
|
||||
$SCRATCH/sampler.sh app=vergabe-sizing-app app $R/app-cgroup.txt $STOP &
|
||||
$SCRATCH/sampler.sh app=vergabe-sizing-db postgres $R/db-cgroup.txt $STOP &
|
||||
$SCRATCH/sampler.sh app=vergabe-sizing-driver driver $R/driver-cgroup.txt $STOP &
|
||||
$SCRATCH/top.sh $R/kubectl-top.txt $STOP &
|
||||
echo "baseline_start $(date -u +%FT%T.%NZ)" > $R/windows.txt
|
||||
sleep 120
|
||||
echo "driver_start $(date -u +%FT%T.%NZ)" >> $R/windows.txt
|
||||
kubectl -n $NS exec synthetic-driver -- python /driver/synthetic_pilot.py --config /driver/config.json --output /out/result.json > $R/driver-stdout.txt 2>&1
|
||||
echo "driver_exit $? $(date -u +%FT%T.%NZ)" >> $R/windows.txt
|
||||
sleep 60
|
||||
echo "post_end $(date -u +%FT%T.%NZ)" >> $R/windows.txt
|
||||
touch $STOP; sleep 3; wait
|
||||
echo done
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import json, subprocess, sys, urllib.parse
|
||||
start, end, out = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
P = "/api/v1/namespaces/telemetry/services/telemetry-prometheus:9090/proxy/api/v1/query_range?"
|
||||
def q(expr, step="15s"):
|
||||
url = P + urllib.parse.urlencode({"query": expr, "start": start, "end": end, "step": step})
|
||||
return json.loads(subprocess.check_output(["kubectl", "get", "--raw", url]))
|
||||
res = {"window": [start, end], "queries": {}}
|
||||
for name, expr in {
|
||||
"cpu_by_namespace_cores": 'sum by (namespace) (rate(container_cpu_usage_seconds_total{container!="",image!=""}[1m]))',
|
||||
"throttled_ratio_by_namespace": 'sum by (namespace) (rate(container_cpu_cfs_throttled_periods_total{container!=""}[1m])) / sum by (namespace) (rate(container_cpu_cfs_periods_total{container!=""}[1m]))',
|
||||
"memory_working_set_by_namespace_bytes": 'sum by (namespace) (container_memory_working_set_bytes{container!="",image!=""})',
|
||||
"apps_pg_cpu_cores": 'sum by (pod) (rate(container_cpu_usage_seconds_total{namespace="databases",container!="",image!=""}[1m]))',
|
||||
"live_vergabe_cpu_cores": 'sum (rate(container_cpu_usage_seconds_total{namespace="vergabe-demo-company",container!="",image!=""}[1m]))',
|
||||
}.items():
|
||||
r = q(expr)
|
||||
res["queries"][name] = {"expr": expr, "result": r["data"]["result"]}
|
||||
json.dump(res, open(out, "w"))
|
||||
# summary: mean per namespace cpu
|
||||
s = {}
|
||||
for series in res["queries"]["cpu_by_namespace_cores"]["result"]:
|
||||
v = [float(x[1]) for x in series["values"]]
|
||||
s[series["metric"].get("namespace", "?")] = (round(1000*sum(v)/len(v)), round(1000*max(v)))
|
||||
tot = sum(a for a, b in s.values())
|
||||
print("container_total_mean_m", tot)
|
||||
for k, v in sorted(s.items(), key=lambda kv: -kv[1][0])[:15]: print(k, "mean_m", v[0], "max_m", v[1])
|
||||
16
docs/evidence/2026-10-21-vergabe-target-replay/harness/sampler.sh
Executable file
16
docs/evidence/2026-10-21-vergabe-target-replay/harness/sampler.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
# usage: sampler.sh <label-selector> <container> <outfile> <stopfile>
|
||||
NS=vergabe-sizing-20261021; SEL=$1; C=$2; OUT=$3; STOP=$4
|
||||
LOOP='while :; do echo "S $(date +%s.%N)"; cat /sys/fs/cgroup/cpu.stat; echo "M $(cat /sys/fs/cgroup/memory.current)"; echo "P $(head -1 /sys/fs/cgroup/cpu.pressure)"; echo "N $(head -1 /proc/stat)"; echo "L $(cat /proc/loadavg)"; sleep 1; done'
|
||||
while [ ! -e "$STOP" ]; do
|
||||
POD=$(kubectl -n $NS get pod -l "$SEL" --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||
if [ -n "$POD" ]; then
|
||||
echo "POD $POD $(date +%s.%N)" >> "$OUT"
|
||||
timeout 1500 kubectl -n $NS exec "$POD" -c "$C" -- sh -c "$LOOP" >> "$OUT" 2>>"$OUT.err" &
|
||||
PID=$!
|
||||
while kill -0 $PID 2>/dev/null && [ ! -e "$STOP" ]; do sleep 1; done
|
||||
kill $PID 2>/dev/null
|
||||
echo "DETACH $(date +%s.%N)" >> "$OUT"
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
from vergabe_teilnahme.settings.base import *
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = ['vergabe-app']
|
||||
NETKINGDOM_ENABLED = False
|
||||
MEDIA_ROOT = '/app/media'
|
||||
ISSUE_FACADE_LOCAL_DB = '/app/.issue-facade/issues.db'
|
||||
ISSUE_FACADE_GITEA = None
|
||||
FORCE_SCRIPT_NAME = None
|
||||
MEDIA_URL = "/media/"
|
||||
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||
SESSION_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_SECURE = False
|
||||
3
docs/evidence/2026-10-21-vergabe-target-replay/harness/top.sh
Executable file
3
docs/evidence/2026-10-21-vergabe-target-replay/harness/top.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
OUT=$1; STOP=$2
|
||||
while [ ! -e "$STOP" ]; do echo "T $(date -u +%s)"; kubectl top pods -A --no-headers 2>&1; kubectl top node --no-headers 2>&1; sleep 15; done >> $OUT
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"ordinary": {"requests": 3675, "errors": 0, "p95_seconds": 0.19182809395715594}, "document_transfer": {"requests": 814, "errors": 0, "p95_seconds": 0.07621732295956463}, "complete": true, "provisional_pass": true}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,36 @@
|
|||
T R O M
|
||||
<nil> Scheduled vergabe-app-579448bf49-2fsn2 Successfully assigned vergabe-sizing-20261021/vergabe-app-579448bf49-2fsn2 to 239.62.205.92.host.secureserver.net
|
||||
<nil> Scheduled vergabe-db-7c4bffdd48-w4jz4 Successfully assigned vergabe-sizing-20261021/vergabe-db-7c4bffdd48-w4jz4 to 239.62.205.92.host.secureserver.net
|
||||
<nil> Scheduled synthetic-driver Successfully assigned vergabe-sizing-20261021/synthetic-driver to 239.62.205.92.host.secureserver.net
|
||||
<nil> Scheduled vergabe-app-579448bf49-plp4j Successfully assigned vergabe-sizing-20261021/vergabe-app-579448bf49-plp4j to 239.62.205.92.host.secureserver.net
|
||||
2026-10-21T06:26:14Z ScalingReplicaSet vergabe-db Scaled up replica set vergabe-db-7c4bffdd48 from 0 to 1
|
||||
2026-10-21T06:26:14Z SuccessfulCreate vergabe-db-7c4bffdd48 Created pod: vergabe-db-7c4bffdd48-w4jz4
|
||||
2026-10-21T06:26:14Z WaitForFirstConsumer bench-media waiting for first consumer to be created before binding
|
||||
2026-10-21T06:26:16Z SuccessfulCreate vergabe-app-579448bf49 Created pod: vergabe-app-579448bf49-2fsn2
|
||||
2026-10-21T06:26:16Z ScalingReplicaSet vergabe-app Scaled up replica set vergabe-app-579448bf49 from 0 to 1
|
||||
2026-10-21T06:26:16Z Pulled vergabe-db-7c4bffdd48-w4jz4 Container image "docker.io/library/postgres@sha256:4b7183ac05f8ef417db21fd72d71047a4238340c261d3cc3ddb6d579ab5071ae" already present on machine and can be accessed by the pod
|
||||
2026-10-21T06:26:16Z Created vergabe-db-7c4bffdd48-w4jz4 Container created
|
||||
2026-10-21T06:26:16Z Provisioning bench-media External provisioner is provisioning volume for claim "vergabe-sizing-20261021/bench-media"
|
||||
2026-10-21T06:26:16Z Started vergabe-db-7c4bffdd48-w4jz4 Container started
|
||||
2026-10-21T06:26:19Z Unhealthy vergabe-db-7c4bffdd48-w4jz4 Readiness probe failed: /var/run/postgresql:5432 - no response
|
||||
|
||||
2026-10-21T06:26:21Z Created synthetic-driver Container created
|
||||
2026-10-21T06:26:21Z ExternalProvisioning bench-media Waiting for a volume to be created either by the external provisioner 'rancher.io/local-path' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
|
||||
2026-10-21T06:26:21Z Pulled synthetic-driver Container image "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91" already present on machine and can be accessed by the pod
|
||||
2026-10-21T06:26:22Z Started synthetic-driver Container started
|
||||
2026-10-21T06:26:25Z ProvisioningSucceeded bench-media Successfully provisioned volume pvc-a23ccb84-ecd8-47af-9832-6d8b09c48e38
|
||||
2026-10-21T06:26:26Z Started vergabe-app-579448bf49-2fsn2 Container started
|
||||
2026-10-21T06:26:26Z Created vergabe-app-579448bf49-2fsn2 Container created
|
||||
2026-10-21T06:26:26Z Pulled vergabe-app-579448bf49-2fsn2 Container image "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91" already present on machine and can be accessed by the pod
|
||||
2026-10-21T06:26:32Z Started vergabe-app-579448bf49-2fsn2 Container started
|
||||
2026-10-21T06:26:32Z Created vergabe-app-579448bf49-2fsn2 Container created
|
||||
2026-10-21T06:26:32Z Pulled vergabe-app-579448bf49-2fsn2 Container image "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91" already present on machine and can be accessed by the pod
|
||||
2026-10-21T06:32:03Z SuccessfulCreate vergabe-app-579448bf49 Created pod: vergabe-app-579448bf49-plp4j
|
||||
2026-10-21T06:32:03Z Killing vergabe-app-579448bf49-2fsn2 Stopping container app
|
||||
2026-10-21T06:32:04Z Started vergabe-app-579448bf49-plp4j Container started
|
||||
2026-10-21T06:32:04Z Created vergabe-app-579448bf49-plp4j Container created
|
||||
2026-10-21T06:32:04Z Pulled vergabe-app-579448bf49-plp4j Container image "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91" already present on machine and can be accessed by the pod
|
||||
2026-10-21T06:32:04Z Unhealthy vergabe-app-579448bf49-2fsn2 Readiness probe failed: Get "http://10.42.0.96:8000/health/": dial tcp 10.42.0.96:8000: connect: connection refused
|
||||
2026-10-21T06:32:09Z Started vergabe-app-579448bf49-plp4j Container started
|
||||
2026-10-21T06:32:09Z Created vergabe-app-579448bf49-plp4j Container created
|
||||
2026-10-21T06:32:09Z Pulled vergabe-app-579448bf49-plp4j Container image "forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91" already present on machine and can be accessed by the pod
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
synthetic-driver driver forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91 0 {} ['running']
|
||||
vergabe-app-579448bf49-plp4j migrate forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91 0 {} ['terminated']
|
||||
vergabe-app-579448bf49-plp4j app forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91 0 {} ['running']
|
||||
vergabe-db-7c4bffdd48-w4jz4 postgres docker.io/library/postgres@sha256:4b7183ac05f8ef417db21fd72d71047a4238340c261d3cc3ddb6d579ab5071ae 0 {} ['running']
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
|||
delete_requested 2026-10-21T06:35:25Z
|
||||
namespace "vergabe-sizing-20261021" deleted
|
||||
delete_returned 2026-10-21T06:36:18Z
|
||||
Error from server (NotFound): namespaces "vergabe-sizing-20261021" not found
|
||||
Error from server (NotFound): persistentvolumes "pvc-a23ccb84-ecd8-47af-9832-6d8b09c48e38" not found
|
||||
0
|
||||
live generation=4 image=forgejo.coulomb.social/coulomb/vergabe-teilnahme@sha256:a26444f59c259698159c69ccb96f73dc648a261ece4c86bb2037a9d977870d91 ready=1
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
baseline_start 2026-10-21T06:27:14.447540583Z
|
||||
driver_start 2026-10-21T06:29:26.175778371Z
|
||||
driver_exit 0 2026-10-21T06:33:22.885983171Z
|
||||
post_end 2026-10-21T06:34:28.753426744Z
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"schema": "coulomb.independent-image-release-postflight.v1",
|
||||
"status": "live_image_release_verified",
|
||||
"observed_at": "2026-10-31T07:31:19.932895+00:00",
|
||||
"proposal_digest": "sha256:9f1daf6c5fb56b2b4fc9817f28f5ad090c884b0295f7d6f4469e42d48fa8d59f",
|
||||
"helm_revision": 19,
|
||||
"image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:522bcd7fce92436765a60c1e4afa2598f3cc40913352153ff1c4319a99ca92ed",
|
||||
"source": "23ffe2cf4b214666754a2420558943438e54251a",
|
||||
"cluster_deployment_pvc_identity_verified": true,
|
||||
"complete_deployment_spec_verified": true,
|
||||
"sole_ready_pvc_writer": true,
|
||||
"data": {
|
||||
"business_tables": 12,
|
||||
"migration_rows_sha256": "43471516387c0e2a3d35971033ed6af330e2963efe4da93293d97633825d04af",
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "6243955479f7861fcdcd79b62f7f1e9f9a27c05a17bd2c17b792ebc98b2fa7b6",
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "ed535246f4e8f46fcd7ce01f936127996aff873c6936b297f7272194b594aeda",
|
||||
"global_slug_constraint": true,
|
||||
"tenant_slug_constraint": true,
|
||||
"migration_0003_applied": true,
|
||||
"duplicate_slug_groups": 0,
|
||||
"actual_owner_pages_rendered": 40,
|
||||
"unsaved_outsider_page_denials": 40,
|
||||
"production_identity_or_grant_created": false
|
||||
},
|
||||
"installed_helper_sha256": {
|
||||
"/opt/coulomb-social-backup/paired-backup.py": "43e119acb6a4933b4e144c349a091e074e1a91ab61dbe15a65c8264c1d47661a",
|
||||
"/opt/coulomb-social-backup/snapshot-content-and-db.py": "dcc8dd2a3989f279597595f9260b31b24fcc9b6dc787f04c17ef0491c53d830d",
|
||||
"/opt/railiance-platform/scripts/primary_application_archive.py": "a3f0a61e869a6012edb4b5288be14e3b0c4a9a1273b090320ab146d12e5aa94d"
|
||||
},
|
||||
"snapshot_server_dry_run": "passed",
|
||||
"public_status": {
|
||||
"/healthz": 200,
|
||||
"/": 200,
|
||||
"/static/spaces/copy-content.js": 200
|
||||
},
|
||||
"timers": {
|
||||
"coulomb-social-paired-backup.timer": {
|
||||
"LoadState": "not-found",
|
||||
"ActiveState": "inactive"
|
||||
},
|
||||
"coulomb-social-backup-freshness.timer": {
|
||||
"LoadState": "not-found",
|
||||
"ActiveState": "inactive"
|
||||
}
|
||||
},
|
||||
"production_data_modified": false
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"schema": "coulomb.guarded-image-release.v1",
|
||||
"proposal_digest": "sha256:9f1daf6c5fb56b2b4fc9817f28f5ad090c884b0295f7d6f4469e42d48fa8d59f",
|
||||
"started_at": "2026-10-31T03:41:35.362323+00:00",
|
||||
"status": "live_release_preflight_verified",
|
||||
"production_data_restored": false,
|
||||
"timers_activated": false,
|
||||
"node_request_percent": {
|
||||
"cpu": 68,
|
||||
"memory": 76
|
||||
},
|
||||
"preflight": {
|
||||
"business_tables": 12,
|
||||
"migration_rows_sha256": "43471516387c0e2a3d35971033ed6af330e2963efe4da93293d97633825d04af",
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "6243955479f7861fcdcd79b62f7f1e9f9a27c05a17bd2c17b792ebc98b2fa7b6",
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "ed535246f4e8f46fcd7ce01f936127996aff873c6936b297f7272194b594aeda",
|
||||
"global_slug_constraint": true,
|
||||
"tenant_slug_constraint": true,
|
||||
"migration_0003_applied": true,
|
||||
"duplicate_slug_groups": 0
|
||||
},
|
||||
"live_changes": false
|
||||
}
|
||||
70
docs/evidence/2026-10-31-coulomb-image-release-receipt.json
Normal file
70
docs/evidence/2026-10-31-coulomb-image-release-receipt.json
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"completed_at": "2026-10-31T07:15:24.659887+00:00",
|
||||
"elapsed_seconds": 99.14,
|
||||
"existing_global_constraint_preserved": true,
|
||||
"fresh_paired_point": {
|
||||
"age_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3",
|
||||
"application": "coulomb-social",
|
||||
"application_restore_verified": false,
|
||||
"captured_at": "2026-10-31T07:13:52Z",
|
||||
"ciphertext_bytes": 2181848,
|
||||
"ciphertext_sha256": "840e633047fddd88c6e6a1a3d6909d9af1ad80eaba8c90f83d8e81188dfc17a4",
|
||||
"completed_at": "2026-10-31T07:14:50.236959+00:00",
|
||||
"content_sha256": "644db8f09684bd4a79c126a01a329904351c30bb340295b29c8bb0ca71092dfe",
|
||||
"database_sha256": "498a11e221dcdef38075d23ef89a26f213316c6164addb70d47bfcb98a1999a7",
|
||||
"destination": "s3://railiance-platform-pg-backup/platform-pg/application-archives/coulomb-social/2026/10/31/20261031T071352Z-abf5997f01a14d9cb05e4bba.tar.age",
|
||||
"encrypted_bytes": 2181848,
|
||||
"encrypted_sha256": "840e633047fddd88c6e6a1a3d6909d9af1ad80eaba8c90f83d8e81188dfc17a4",
|
||||
"image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:512819463a1902b43a615beee14be7bf97e6a05e016385082b9380692ad1a8cd",
|
||||
"immutable_create": true,
|
||||
"local_archive": "20261031T071352Z-abf5997f01a14d9cb05e4bba.tar.age",
|
||||
"object_key": "platform-pg/application-archives/coulomb-social/2026/10/31/20261031T071352Z-abf5997f01a14d9cb05e4bba.tar.age",
|
||||
"pause_and_capture_seconds": 56.34,
|
||||
"remote_retrieval_verified": true,
|
||||
"schema": "platform.application-primary-archive.v1",
|
||||
"status": "offsite_fetched_verified",
|
||||
"version_id": "1793430889823443"
|
||||
},
|
||||
"helm_revision": 19,
|
||||
"node_request_percent": {
|
||||
"cpu": 68,
|
||||
"memory": 76
|
||||
},
|
||||
"pid": 3639216,
|
||||
"postflight": {
|
||||
"actual_owner_pages_rendered": 40,
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "6243955479f7861fcdcd79b62f7f1e9f9a27c05a17bd2c17b792ebc98b2fa7b6",
|
||||
"business_tables": 12,
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "ed535246f4e8f46fcd7ce01f936127996aff873c6936b297f7272194b594aeda",
|
||||
"duplicate_slug_groups": 0,
|
||||
"global_slug_constraint": true,
|
||||
"migration_0003_applied": true,
|
||||
"migration_rows_sha256": "43471516387c0e2a3d35971033ed6af330e2963efe4da93293d97633825d04af",
|
||||
"production_identity_or_grant_created": false,
|
||||
"tenant_slug_constraint": true,
|
||||
"unsaved_outsider_page_denials": 40
|
||||
},
|
||||
"preflight": {
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "6243955479f7861fcdcd79b62f7f1e9f9a27c05a17bd2c17b792ebc98b2fa7b6",
|
||||
"business_tables": 12,
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "ed535246f4e8f46fcd7ce01f936127996aff873c6936b297f7272194b594aeda",
|
||||
"duplicate_slug_groups": 0,
|
||||
"global_slug_constraint": true,
|
||||
"migration_0003_applied": true,
|
||||
"migration_rows_sha256": "43471516387c0e2a3d35971033ed6af330e2963efe4da93293d97633825d04af",
|
||||
"tenant_slug_constraint": true
|
||||
},
|
||||
"private_values_printed": false,
|
||||
"production_data_restored": false,
|
||||
"proposal_digest": "sha256:9f1daf6c5fb56b2b4fc9817f28f5ad090c884b0295f7d6f4469e42d48fa8d59f",
|
||||
"public_health_landing_static_verified": true,
|
||||
"schema": "coulomb.guarded-image-release.v1",
|
||||
"snapshot_pin_refreshed": true,
|
||||
"started_at": "2026-10-31T07:13:45.511369+00:00",
|
||||
"status": "image_candidate_deployed_verified",
|
||||
"timers_activated": false
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"schema": "coulomb.live-source-provenance.v1",
|
||||
"image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:522bcd7fce92436765a60c1e4afa2598f3cc40913352153ff1c4319a99ca92ed",
|
||||
"source": "23ffe2cf4b214666754a2420558943438e54251a",
|
||||
"packaged_files": 80,
|
||||
"all_packaged_source_bytes_match": true,
|
||||
"packaged_manifest_sha256": "6b5a7b339d88f3c5eafb70a33aa28a7d3972988645b246ae1a92a3f159d72528",
|
||||
"live_read_only_probe": true,
|
||||
"production_data_changed": false
|
||||
}
|
||||
68
docs/evidence/2026-10-31-coulomb-storage-release.json
Normal file
68
docs/evidence/2026-10-31-coulomb-storage-release.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"completed_at": "2026-10-31T00:29:28.113460+00:00",
|
||||
"elapsed_seconds": 99.527,
|
||||
"fresh_paired_point": {
|
||||
"age_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3",
|
||||
"application": "coulomb-social",
|
||||
"application_restore_verified": false,
|
||||
"captured_at": "2026-10-31T00:27:56Z",
|
||||
"ciphertext_bytes": 2181848,
|
||||
"ciphertext_sha256": "f7be502c7e122283e70b10d2fd8a2cbf9550d2261192ba11f94d945a9f39af51",
|
||||
"completed_at": "2026-10-31T00:28:53.504056+00:00",
|
||||
"content_sha256": "22b694a56178a70274df55bdfc60f2cc0fe24ec66563c2e952058e5d8cf2154a",
|
||||
"database_sha256": "99e704023d0832aa29dcfd7695dd436dea58038ae48e3ab142d0ec2b916eb574",
|
||||
"destination": "s3://railiance-platform-pg-backup/platform-pg/application-archives/coulomb-social/2026/10/31/20261031T002756Z-c31b84b48b3874bdaa87cbf5.tar.age",
|
||||
"encrypted_bytes": 2181848,
|
||||
"encrypted_sha256": "f7be502c7e122283e70b10d2fd8a2cbf9550d2261192ba11f94d945a9f39af51",
|
||||
"image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:9a646d8e01a61ba5316d3c2eb0273064e17f2d0300ad07af766ccedda65ecb18",
|
||||
"immutable_create": true,
|
||||
"local_archive": "20261031T002756Z-c31b84b48b3874bdaa87cbf5.tar.age",
|
||||
"object_key": "platform-pg/application-archives/coulomb-social/2026/10/31/20261031T002756Z-c31b84b48b3874bdaa87cbf5.tar.age",
|
||||
"pause_and_capture_seconds": 56.63,
|
||||
"remote_retrieval_verified": true,
|
||||
"schema": "platform.application-primary-archive.v1",
|
||||
"status": "offsite_fetched_verified",
|
||||
"version_id": "1793406532815253"
|
||||
},
|
||||
"global_constraint_applied": true,
|
||||
"helm_revision": 18,
|
||||
"node_request_percent": {
|
||||
"cpu": 68,
|
||||
"memory": 76
|
||||
},
|
||||
"pid": 2724118,
|
||||
"postflight": {
|
||||
"actual_owner_pages_rendered": 40,
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "b65c4a32d554d741569df5622c09f31423f6c18aa1c4edc5154c4a95e3511508",
|
||||
"business_tables": 12,
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "fd9ea2fa6890ad960e45dff308713ccd1aa303c502dd0ceb0cc26908326216a4",
|
||||
"duplicate_slug_groups": 0,
|
||||
"global_slug_constraint": true,
|
||||
"migration_0003_applied": true,
|
||||
"production_identity_or_grant_created": false,
|
||||
"tenant_slug_constraint": true,
|
||||
"unsaved_outsider_page_denials": 40
|
||||
},
|
||||
"preflight": {
|
||||
"business_rows": 92,
|
||||
"business_rows_sha256": "b65c4a32d554d741569df5622c09f31423f6c18aa1c4edc5154c4a95e3511508",
|
||||
"business_tables": 12,
|
||||
"content_files": 86,
|
||||
"content_files_sha256": "fd9ea2fa6890ad960e45dff308713ccd1aa303c502dd0ceb0cc26908326216a4",
|
||||
"duplicate_slug_groups": 0,
|
||||
"global_slug_constraint": false,
|
||||
"migration_0003_applied": false,
|
||||
"tenant_slug_constraint": true
|
||||
},
|
||||
"private_values_printed": false,
|
||||
"production_data_restored": false,
|
||||
"proposal_digest": "sha256:5fd7ccd8f68057346367d81efb82b48280a16dd31d87683a5bd83e60e3d1837e",
|
||||
"public_health_landing_static_verified": true,
|
||||
"schema": "coulomb.guarded-storage-release.v1",
|
||||
"snapshot_pin_refreshed": true,
|
||||
"started_at": "2026-10-31T00:27:48.578472+00:00",
|
||||
"status": "storage_candidate_deployed_verified",
|
||||
"timers_activated": false
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"schema": "custodian.owner-review.v1",
|
||||
"review": "NK-WP-0048-T16",
|
||||
"owner": "railiance-apps",
|
||||
"reviewed_at": "2026-10-17",
|
||||
"reviewer": "railiance-apps (Claude Code session, source-only)",
|
||||
"verdict": "needs-change",
|
||||
"grants": "nothing; no admission, pin, dispatch or native action",
|
||||
"request": "the-custodian docs/changes/netkingdom-target-2026-10-17/nk48-t16-owner-review/README.md @ bffeee9",
|
||||
"frozen_input": {
|
||||
"repo": "net-kingdom",
|
||||
"commit": "ff8f597d5f2f32ce8e82e370c0ad43aa98dc31b7",
|
||||
"files": {
|
||||
"sso-mfa/k8s/keycape/deliver-consumer-secret-adapter.py": "sha256:d046eed63090d4e0849a931e2650ccfd89d3b686843aed9e5cb35a0e18e513ee",
|
||||
"sso-mfa/k8s/keycape/handover-adapter.py": "sha256:0efc300a7b2765bce8da140d5c4666d9cb30daf02b4cbf0e0df96cba7c33d026",
|
||||
"sso-mfa/k8s/keycape/deliver-consumer-secret-adapter.md": "sha256:4662cff37f2a445f450d5d9d9255cd4315f9d4e4a9adde2cb8d52bd8f1f1212b",
|
||||
"sso-mfa/k8s/keycape/handover-adapter.md": "sha256:9756921a9a51eea3d9b2dd04b9f135bf0712dfe0c0cff638173da85ce7f5ddaa",
|
||||
"sso-mfa/k8s/keycape/descriptor-composition-1.2.candidate.json": "sha256:745bb053e6776e8b96224563188405920227e4a17d7783ffa6b36c122be41b09"
|
||||
},
|
||||
"hash_check": "both adapter .py hashes equal coordinator-review-r3.json hashes_verified"
|
||||
},
|
||||
"option_preference": {
|
||||
"choice": "C",
|
||||
"reason": "Both Apps steps change the production forgejo namespace and stay owner-run with the founder's go per change. Apps does not want Warden to dispatch them; Warden verifying Apps receipts is acceptable. Apps does not object to B for mint-secret, which is Platform's step."
|
||||
},
|
||||
"will_implement": {
|
||||
"deliver-consumer-secret": {
|
||||
"answer": "yes, as an owner-run operator path, not a Warden-dispatched one",
|
||||
"owner_apply": "Commit the consumer ServiceAccount (automount false), SecretStore (serviceAccountRef, audiences [openbao], role workload-kv-read-keycape-forgejo-forgejo) and ExternalSecret (Owner creation, Retain deletion, path workloads/keycape/forgejo, key CLIENT_SECRET only) as manifests in this repo, apply them under RAPPS-WP-0016-T02, and emit the value-free netkingdom.consumer-secret-delivery.v1 receipt from kubectl metadata (uid, created, Ready).",
|
||||
"owner_remove": "Revert the Forgejo oauth source secret reference first (helm rollback to the noted revision), then remove only the created objects in the order ExternalSecret, SecretStore, ServiceAccount, and report consumer_reference_reverted=true.",
|
||||
"accepted_as_is": ["receipt schema and per-object metadata", "value-free rule", "created-only rollback", "custody path platform/workloads/keycape/forgejo field CLIENT_SECRET (net-kingdom 074b3a0)"]
|
||||
},
|
||||
"hand-over": {
|
||||
"answer": "yes, attended (actor human), lane name apps-consumer-handover accepted",
|
||||
"record_handover": "Receipt store is this repo: a committed evidence file docs/evidence/<date>-forgejo-netkingdom-handover.json; identifier is path@commit; created=false when a file with the same observation digest is already committed.",
|
||||
"remove_handover": "A revert commit of that evidence file; confirmed only when the revert is on main."
|
||||
}
|
||||
},
|
||||
"consumer_verification": {
|
||||
"principle": "Login and group enforcement are observed at the consumer, never inferred from registry CAS, the client registration or the ExternalSecret being Ready.",
|
||||
"login_verified": "An attended browser sign-in through 'Sign in with netkingdom' on Forgejo by a member of the case access group succeeds, and Forgejo shows the account linked to auth source 'netkingdom' (admin API: user's login_source / external login entry).",
|
||||
"enforces_groups": "Positive and negative probe: (1) a NetKingdom test user that is not in any case access group is refused or gets no team access; (2) adding that user to the mapped group grants the mapped Forgejo team membership on next sign-in; (3) removing it revokes the team membership. This is the RAPPS-WP-0016-T03 acceptance test.",
|
||||
"observation_body": "The digest passed to record_handover covers the client_id, the groups probed, the Forgejo user ids and team ids observed, and the timestamps of the three probes. No tokens, cookies or secret values."
|
||||
},
|
||||
"needs_change": [
|
||||
{
|
||||
"file": "sso-mfa/k8s/keycape/handover-adapter.py",
|
||||
"finding": "verify_consumer returns bare booleans (login_verified, enforces_groups). The adapter cannot tell an observed consumer result from a self-asserted one.",
|
||||
"requested": "Require the verifier result to carry the observation evidence (or its authenticated identifier) for a positive login, a negative non-member probe and an add/remove membership probe, and include that in the digest."
|
||||
},
|
||||
{
|
||||
"file": "sso-mfa/k8s/keycape/deliver-consumer-secret-adapter.py",
|
||||
"finding": "Descriptor actor is 'agent' for a step that changes a production consumer namespace. Under railiance-apps rules, production-touching applies need the founder's go per change.",
|
||||
"requested": "Actor 'human' (attended), or an explicit per-change founder approval reference in the binding. Lane name consumer-owner-deployment is otherwise accepted."
|
||||
}
|
||||
],
|
||||
"carried_limits": {
|
||||
"5_injected_stand_ins": "Accepted as a known limit; turned into task RAPPS-WP-0016-T05 (real Apps owner_apply/owner_remove, verifier and receipt store).",
|
||||
"6_mint_then_revoke_failure": "Platform-owned; no Apps position.",
|
||||
"others": "1-4 are net-kingdom / ops-warden concerns; Apps accepts them as known limits for its two steps."
|
||||
},
|
||||
"not_reviewed": ["mint-secret", "deliver-secret", "ensure-groups", "bind-env", "register-client", "composer", "checker artifact", "kernel admission"]
|
||||
}
|
||||
|
|
@ -23,11 +23,11 @@ Hostname decision: `the-custodian/docs/forgejo-production-decisions.md`.
|
|||
| Server | railiance01 `92.205.62.239` |
|
||||
| Namespace | `forgejo` |
|
||||
| Helm release | `forgejo` |
|
||||
| HTTP service | `forgejo-gitea-http` (chart naming; ingress must target this) |
|
||||
| HTTP service | `forge-http` (chart naming; ingress must target this) |
|
||||
| Chart | `gitea-charts/gitea` **12.5.0** (Forgejo-compatible; 12.6+ needs Gitea 1.26 `config edit-ini`) |
|
||||
| Image | `code.forgejo.org/forgejo/forgejo:11.0.3` |
|
||||
| Database | `forgejo-db-rw.databases.svc.cluster.local:5432` |
|
||||
| Kubeconfig | `~/.kube/config-hosteurope` |
|
||||
| Kubeconfig | `~/.kube/config-railiance01` |
|
||||
|
||||
## Bootstrap (first deploy)
|
||||
|
||||
|
|
@ -36,8 +36,8 @@ Hostname decision: `the-custodian/docs/forgejo-production-decisions.md`.
|
|||
```bash
|
||||
cd ~/railiance-platform
|
||||
# One-time: create and SOPS-encrypt helm/forgejo-db-secret.sops.yaml from template
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-deploy
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-status
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-db-deploy
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-db-status
|
||||
```
|
||||
|
||||
### 2. Application secrets (apps)
|
||||
|
|
@ -71,7 +71,7 @@ make forgejo-runner-status
|
|||
- `strategy.type: Recreate` in `helm/forgejo-values.yaml` — avoids leveldb queue lock
|
||||
on the shared RWO PVC during rolling updates.
|
||||
- Actions enabled via `gitea.config.actions.ENABLED: true`.
|
||||
- Ingress backend service name is `forgejo-gitea-http` (Helm release naming).
|
||||
- Ingress backend service name is `forge-http` (Helm release naming).
|
||||
|
||||
## Day-2 operator targets
|
||||
|
||||
|
|
@ -104,6 +104,46 @@ make forgejo-runner-registration-sops-bootstrap
|
|||
make check-sops SOPS_SENTINEL=helm/forgejo-runner-registration.sops.yaml
|
||||
```
|
||||
|
||||
## Break-glass administrator (RAPPS-WP-0016-T01)
|
||||
|
||||
The floor under every sign-in change: one local site admin that works even when
|
||||
NetKingdom, KeyCape or the OAuth source is broken.
|
||||
|
||||
| Item | Value |
|
||||
| --- | --- |
|
||||
| Account | `forgejo_admin` (chart bootstrap admin, local password, site admin) |
|
||||
| Custody | OpenBao coulombcore `platform/workloads/forgejo/break-glass-admin`, field `PASSWORD` |
|
||||
| Delivered to cluster | No. Not synced by ESO; only a human retrieves it |
|
||||
| Who | The founder. Agents never read or hold the value |
|
||||
|
||||
The SOPS admin password in `helm/forgejo-secrets.sops.yaml` is only the chart's
|
||||
initial value (`passwordMode: initialOnlyRequireReset`); Helm upgrades do not
|
||||
reset the account. It is not the break-glass credential.
|
||||
|
||||
Seal or rotate, then verify (founder, interactive):
|
||||
|
||||
```bash
|
||||
make forgejo-breakglass-seal # new password -> OpenBao first, then Forgejo
|
||||
make forgejo-breakglass-verify # one local sign-in; writes docs/evidence/<date>-forgejo-breakglass-signin.json
|
||||
```
|
||||
|
||||
Recovery when normal sign-in is broken:
|
||||
|
||||
1. `bao kv get -field=PASSWORD platform/workloads/forgejo/break-glass-admin`
|
||||
(operator token on `https://bao.coulomb.social`).
|
||||
2. Sign in at `https://forgejo.coulomb.social/user/login` as `forgejo_admin`
|
||||
with local credentials. Local password sign-in stays enabled for this account
|
||||
even if RAPPS-WP-0016-T04 restricts it for everyone else.
|
||||
3. Repair the authentication source or users from Site Administration.
|
||||
4. If the password itself is lost: with cluster access, run
|
||||
`make forgejo-breakglass-seal` again (it sets the password through the
|
||||
Forgejo CLI in the pod, so it needs no working sign-in).
|
||||
5. Afterwards rotate (`make forgejo-breakglass-seal`), verify, and record the use.
|
||||
|
||||
Where it is logged: OpenBao audit log (retrieval), Forgejo log and the account's
|
||||
last-login time (sign-in), the evidence file in `docs/evidence/`, and a State Hub
|
||||
progress event naming why break-glass was used.
|
||||
|
||||
## SMTP (IONOS) — password reset / notifications
|
||||
|
||||
Decision: `the-custodian/docs/forgejo-production-decisions.md` (IONOS, 2026-07-07).
|
||||
|
|
@ -145,10 +185,10 @@ Bootstrap (railiance01):
|
|||
|
||||
```bash
|
||||
cd ~/railiance-apps
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-openbao-eso-token-apply
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-deploy
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-status # expect SecretSynced
|
||||
KUBECONFIG=~/.kube/config-hosteurope make forgejo-deploy
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-openbao-eso-token-apply
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-mailer-es-deploy
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-mailer-es-status # expect SecretSynced
|
||||
KUBECONFIG=~/.kube/config-railiance01 make forgejo-deploy
|
||||
```
|
||||
|
||||
Do not put the SMTP password in `helm/forgejo-secrets.sops.yaml`.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ docker push forgejo.coulomb.social/coulomb/REPO:TAG
|
|||
Cluster pull (railiance01):
|
||||
|
||||
```bash
|
||||
KUBECONFIG=~/.kube/config-hosteurope crictl pull forgejo.coulomb.social/coulomb/REPO:TAG
|
||||
KUBECONFIG=~/.kube/config-railiance01 crictl pull forgejo.coulomb.social/coulomb/REPO:TAG
|
||||
```
|
||||
|
||||
Smoke: `make forgejo-smoke`
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ see the correction above.
|
|||
## Redeploy / recovery
|
||||
|
||||
```bash
|
||||
export KUBECONFIG=$HOME/.kube/config-hosteurope
|
||||
export KUBECONFIG=$HOME/.kube/config-railiance01
|
||||
kubectl apply -f manifests/informed-decision-origin.yaml
|
||||
kubectl apply -f manifests/informed-decision-ingress.yaml
|
||||
kubectl apply -f manifests/informed-decision-http-redirect.yaml
|
||||
|
|
@ -132,11 +132,10 @@ curl -sSI https://decisions.coulomb.social/ | head -1
|
|||
That `curl` succeeded on 2026-09-10, which is the gate `informed-decision` was
|
||||
waiting on.
|
||||
|
||||
**Note on the kubeconfig:** `~/.kube/config-hosteurope` names port `16443`, but
|
||||
the `k3s-api-railiance01` ops-bridge tunnel currently listens on `16444`.
|
||||
Override with `--server https://127.0.0.1:16444` or fix the kubeconfig; `bridge
|
||||
check` reports the tunnel healthy either way, so it does not surface the
|
||||
mismatch.
|
||||
**Note on the kubeconfig:** use `~/.kube/config-railiance01`, which targets the
|
||||
`k3s-api-railiance01` ops-bridge tunnel on `16444`. The retired
|
||||
`~/.kube/config-hosteurope` names port `16443`; `bridge check` reports the tunnel
|
||||
healthy either way, so it does not surface that mismatch.
|
||||
|
||||
## HTTP → HTTPS
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ deployments on CoulombCore and railiance01 are scaled to `0`.
|
|||
This document preserves the Railiance01 Helm surface for audit only. Do not
|
||||
deploy Inter-Hub to production.
|
||||
|
||||
**Removed 2026-10-21 (`RAPPS-WP-0018`, founder-approved wave 2).** The
|
||||
`charts/inter-hub` chart, `helm/inter-hub-values.yaml`, the `inter-hub-*` Make
|
||||
targets, `tools/inter-hub-smoke.sh` and the
|
||||
`.gitea/workflows/inter-hub-production-deploy.yaml` workflow are deleted. The
|
||||
release (0 replicas, latent 250m/512Mi request) is uninstalled with
|
||||
`helm uninstall inter-hub -n inter-hub`. The commands below no longer exist;
|
||||
recover them from git history if ever needed.
|
||||
|
||||
## Hosts and release surface
|
||||
|
||||
| Server | IP | Role |
|
||||
|
|
@ -22,7 +30,7 @@ deploy Inter-Hub to production.
|
|||
| Values | `helm/inter-hub-values.yaml` |
|
||||
| Image | `forgejo.coulomb.social/coulomb/inter-hub:<tag>` |
|
||||
| Secret | `inter-hub-env` |
|
||||
| Production kubeconfig | `~/.kube/config-hosteurope` |
|
||||
| Production kubeconfig | `~/.kube/config-railiance01` |
|
||||
|
||||
## Current evidence
|
||||
|
||||
|
|
@ -50,7 +58,7 @@ Read-only checks on 2026-06-15 showed:
|
|||
## Deploy
|
||||
|
||||
Use the Railiance01 kubeconfig. The Makefile defaults to
|
||||
`~/.kube/config-hosteurope` and fails fast when it is missing.
|
||||
`~/.kube/config-railiance01` and fails fast when it is missing.
|
||||
|
||||
```bash
|
||||
make inter-hub-render-baseline
|
||||
|
|
@ -86,7 +94,7 @@ production trigger. It requires:
|
|||
- `image_tag`, the immutable inter-hub image tag to deploy;
|
||||
- `confirm`, with the exact value `deploy-inter-hub-production`;
|
||||
- an approved self-hosted runner with Railiance01 kubeconfig access at
|
||||
`~/.kube/config-hosteurope` or an equivalent `RAILIANCE01_KUBECONFIG`
|
||||
`~/.kube/config-railiance01` or an equivalent `RAILIANCE01_KUBECONFIG`
|
||||
override;
|
||||
- registry access through `skopeo`, `crane`, `docker manifest inspect`, or the
|
||||
registry HTTP API.
|
||||
|
|
|
|||
|
|
@ -51,23 +51,26 @@ ambient `kubectl` context for production app deploys.
|
|||
The production Makefile targets default to:
|
||||
|
||||
```text
|
||||
~/.kube/config-hosteurope
|
||||
~/.kube/config-railiance01
|
||||
```
|
||||
|
||||
It reaches the API through the `k3s-api-railiance01` ops-bridge tunnel on
|
||||
`127.0.0.1:16444` (`bridge up k3s-api-railiance01` if it is down). The older
|
||||
`~/.kube/config-hosteurope` names port `16443` and is retired.
|
||||
|
||||
Restore it from Railiance01 when missing:
|
||||
|
||||
```bash
|
||||
ssh tegwick@92.205.62.239 'sudo cat /etc/rancher/k3s/k3s.yaml' \
|
||||
| sed 's|127.0.0.1|92.205.62.239|' > ~/.kube/config-hosteurope
|
||||
chmod 600 ~/.kube/config-hosteurope
|
||||
export KUBECONFIG=~/.kube/config-hosteurope
|
||||
| sed 's|127.0.0.1:6443|127.0.0.1:16444|' > ~/.kube/config-railiance01
|
||||
chmod 600 ~/.kube/config-railiance01
|
||||
export KUBECONFIG=~/.kube/config-railiance01
|
||||
```
|
||||
|
||||
The app-specific targets also accept explicit overrides:
|
||||
|
||||
```bash
|
||||
REUSE_KUBECONFIG=~/.kube/config-hosteurope make reuse-status
|
||||
INTER_HUB_KUBECONFIG=~/.kube/config-hosteurope make inter-hub-status
|
||||
REUSE_KUBECONFIG=~/.kube/config-railiance01 make reuse-status
|
||||
```
|
||||
|
||||
## SOPS Age Key Bootstrap
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: forgejo-keycape-forgejo-eso
|
||||
namespace: forgejo
|
||||
labels:
|
||||
app.kubernetes.io/name: forgejo
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
automountServiceAccountToken: false
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: openbao-keycape-forgejo-consumer
|
||||
namespace: forgejo
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: http://openbao.openbao.svc:8200
|
||||
path: platform
|
||||
version: v2
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: kubernetes
|
||||
role: external-secrets-keycape-forgejo-forgejo
|
||||
serviceAccountRef:
|
||||
name: forgejo-keycape-forgejo-eso
|
||||
audiences:
|
||||
- openbao
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: forgejo-netkingdom-oidc-client
|
||||
namespace: forgejo
|
||||
labels:
|
||||
app.kubernetes.io/name: forgejo
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
spec:
|
||||
refreshInterval: 15m
|
||||
secretStoreRef:
|
||||
name: openbao-keycape-forgejo-consumer
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: forgejo-netkingdom-oidc-client
|
||||
creationPolicy: Owner
|
||||
deletionPolicy: Retain
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: forgejo
|
||||
app.kubernetes.io/part-of: railiance-apps
|
||||
data:
|
||||
- secretKey: CLIENT_SECRET
|
||||
remoteRef:
|
||||
key: workloads/keycape/forgejo
|
||||
property: CLIENT_SECRET
|
||||
|
|
@ -0,0 +1,666 @@
|
|||
{
|
||||
"eso_rbac_bindings": [
|
||||
{
|
||||
"kind": "ClusterRoleBinding",
|
||||
"name": "external-secrets-cert-controller",
|
||||
"namespace": null,
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "external-secrets-cert-controller"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"apiextensions.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"customresourcedefinitions"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"admissionregistration.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"validatingwebhookconfigurations"
|
||||
],
|
||||
"verbs": [
|
||||
"list",
|
||||
"watch",
|
||||
"get"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"admissionregistration.k8s.io"
|
||||
],
|
||||
"resourceNames": [
|
||||
"secretstore-validate",
|
||||
"externalsecret-validate"
|
||||
],
|
||||
"resources": [
|
||||
"validatingwebhookconfigurations"
|
||||
],
|
||||
"verbs": [
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"endpoints"
|
||||
],
|
||||
"verbs": [
|
||||
"list",
|
||||
"get",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"events"
|
||||
],
|
||||
"verbs": [
|
||||
"create",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"secrets"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"coordination.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"leases"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"create",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
}
|
||||
],
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "external-secrets-cert-controller",
|
||||
"namespace": "external-secrets"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "ClusterRoleBinding",
|
||||
"name": "external-secrets-controller",
|
||||
"namespace": null,
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "external-secrets-controller"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
"external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"secretstores",
|
||||
"clustersecretstores",
|
||||
"externalsecrets",
|
||||
"clusterexternalsecrets",
|
||||
"pushsecrets",
|
||||
"clusterpushsecrets"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"externalsecrets",
|
||||
"externalsecrets/status",
|
||||
"externalsecrets/finalizers",
|
||||
"secretstores",
|
||||
"secretstores/status",
|
||||
"secretstores/finalizers",
|
||||
"clustersecretstores",
|
||||
"clustersecretstores/status",
|
||||
"clustersecretstores/finalizers",
|
||||
"clusterexternalsecrets",
|
||||
"clusterexternalsecrets/status",
|
||||
"clusterexternalsecrets/finalizers",
|
||||
"pushsecrets",
|
||||
"pushsecrets/status",
|
||||
"pushsecrets/finalizers",
|
||||
"clusterpushsecrets",
|
||||
"clusterpushsecrets/status",
|
||||
"clusterpushsecrets/finalizers"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"generators.external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"generatorstates"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch",
|
||||
"create",
|
||||
"update",
|
||||
"patch",
|
||||
"delete",
|
||||
"deletecollection"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"generators.external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"acraccesstokens",
|
||||
"clustergenerators",
|
||||
"ecrauthorizationtokens",
|
||||
"fakes",
|
||||
"gcraccesstokens",
|
||||
"githubaccesstokens",
|
||||
"quayaccesstokens",
|
||||
"passwords",
|
||||
"stssessiontokens",
|
||||
"uuids",
|
||||
"vaultdynamicsecrets",
|
||||
"webhooks",
|
||||
"grafanas"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"serviceaccounts",
|
||||
"namespaces"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"configmaps"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"secrets"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"watch",
|
||||
"create",
|
||||
"update",
|
||||
"delete",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"serviceaccounts/token"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"events"
|
||||
],
|
||||
"verbs": [
|
||||
"create",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"externalsecrets"
|
||||
],
|
||||
"verbs": [
|
||||
"create",
|
||||
"update",
|
||||
"delete"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"external-secrets.io"
|
||||
],
|
||||
"resources": [
|
||||
"pushsecrets"
|
||||
],
|
||||
"verbs": [
|
||||
"create",
|
||||
"update",
|
||||
"delete"
|
||||
]
|
||||
}
|
||||
],
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "external-secrets",
|
||||
"namespace": "external-secrets"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "RoleBinding",
|
||||
"name": "external-secrets-leaderelection",
|
||||
"namespace": "external-secrets",
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "Role",
|
||||
"name": "external-secrets-leaderelection"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resourceNames": [
|
||||
"external-secrets-controller"
|
||||
],
|
||||
"resources": [
|
||||
"configmaps"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"configmaps"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"coordination.k8s.io"
|
||||
],
|
||||
"resources": [
|
||||
"leases"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"create",
|
||||
"update",
|
||||
"patch"
|
||||
]
|
||||
}
|
||||
],
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "external-secrets",
|
||||
"namespace": "external-secrets"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"external-secrets_deployments": [
|
||||
{
|
||||
"images": [
|
||||
"oci.external-secrets.io/external-secrets/external-secrets:v0.16.1"
|
||||
],
|
||||
"name": "external-secrets",
|
||||
"serviceAccount": "external-secrets",
|
||||
"uid": "e6017556-523a-4b26-b9d1-4c0c90cef483"
|
||||
},
|
||||
{
|
||||
"images": [
|
||||
"oci.external-secrets.io/external-secrets/external-secrets:v0.16.1"
|
||||
],
|
||||
"name": "external-secrets-cert-controller",
|
||||
"serviceAccount": "external-secrets-cert-controller",
|
||||
"uid": "5134a56f-aa05-4764-9b27-c56e92203c7d"
|
||||
},
|
||||
{
|
||||
"images": [
|
||||
"oci.external-secrets.io/external-secrets/external-secrets:v0.16.1"
|
||||
],
|
||||
"name": "external-secrets-webhook",
|
||||
"serviceAccount": "external-secrets-webhook",
|
||||
"uid": "6e6e4a6e-4c2c-4659-af56-d4281f2fe229"
|
||||
}
|
||||
],
|
||||
"external-secrets_externalsecrets": [],
|
||||
"external-secrets_secretstores": [],
|
||||
"external-secrets_serviceaccounts": [
|
||||
{
|
||||
"automountServiceAccountToken": null,
|
||||
"name": "default",
|
||||
"uid": "cc29abe8-5ace-4cc9-b479-cf8db84df42d"
|
||||
},
|
||||
{
|
||||
"automountServiceAccountToken": false,
|
||||
"name": "eso-token-renewer",
|
||||
"uid": "4d44afd7-59e9-44de-9e99-8cd620be41de"
|
||||
},
|
||||
{
|
||||
"automountServiceAccountToken": null,
|
||||
"name": "external-secrets",
|
||||
"uid": "c918dc22-b1ee-4e1f-a8f8-1d15ac0d5a10"
|
||||
},
|
||||
{
|
||||
"automountServiceAccountToken": null,
|
||||
"name": "external-secrets-cert-controller",
|
||||
"uid": "3c78bb78-a3c5-4953-93d4-cf6621e1200a"
|
||||
},
|
||||
{
|
||||
"automountServiceAccountToken": null,
|
||||
"name": "external-secrets-webhook",
|
||||
"uid": "c75dc3ba-42db-4a59-8952-6c94d85d054c"
|
||||
}
|
||||
],
|
||||
"forgejo_deployments": [
|
||||
{
|
||||
"images": [
|
||||
"code.forgejo.org/forgejo/forgejo:11.0.3-rootless"
|
||||
],
|
||||
"name": "forgejo-gitea",
|
||||
"serviceAccount": "default",
|
||||
"uid": "e8eff0aa-64bb-4b44-9a80-a66612986355"
|
||||
},
|
||||
{
|
||||
"images": [
|
||||
"docker:27-dind",
|
||||
"code.forgejo.org/forgejo/runner:6.3.1"
|
||||
],
|
||||
"name": "forgejo-runner",
|
||||
"serviceAccount": "default",
|
||||
"uid": "89c8a056-bcae-4940-acdd-2eb2768e108a"
|
||||
}
|
||||
],
|
||||
"forgejo_externalsecrets": [
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": "SecretSynced"
|
||||
}
|
||||
],
|
||||
"data": [
|
||||
{
|
||||
"key": "workloads/forgejo/forgejo-mailer",
|
||||
"property": "PASSWD",
|
||||
"secretKey": "MAILER_PASSWD"
|
||||
}
|
||||
],
|
||||
"name": "forgejo-mailer",
|
||||
"store": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-forgejo"
|
||||
},
|
||||
"target": "forgejo-mailer",
|
||||
"uid": "cbbea71e-70a8-4010-aca9-9136920f2f58"
|
||||
}
|
||||
],
|
||||
"forgejo_secretstores": [],
|
||||
"forgejo_serviceaccounts": [
|
||||
{
|
||||
"automountServiceAccountToken": null,
|
||||
"name": "default",
|
||||
"uid": "2ae15248-4700-46a6-84c6-e3bb3cd33323"
|
||||
},
|
||||
{
|
||||
"automountServiceAccountToken": false,
|
||||
"name": "forgejo-mailer-eso",
|
||||
"uid": "e2214835-32dd-4333-a9c2-6d248ab8bafe"
|
||||
}
|
||||
],
|
||||
"native_mutation": false,
|
||||
"observed_at": "2026-10-11T01:20:37.519031+00:00",
|
||||
"secret_value_reads": false,
|
||||
"secretstore_crd": {
|
||||
"uid": "1eefa5c9-6c2a-4a3b-84c6-3941488201c2",
|
||||
"versions": [
|
||||
{
|
||||
"kubernetes_auth_schema": {
|
||||
"description": "Kubernetes authenticates with Vault by passing the ServiceAccount\ntoken stored in the named Secret resource to the Vault server.",
|
||||
"properties": {
|
||||
"mountPath": {
|
||||
"default": "kubernetes",
|
||||
"description": "Path where the Kubernetes authentication backend is mounted in Vault, e.g:\n\"kubernetes\"",
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"description": "A required field containing the Vault Role to assume. A Role binds a\nKubernetes ServiceAccount with a set of Vault policies.",
|
||||
"type": "string"
|
||||
},
|
||||
"secretRef": {
|
||||
"description": "Optional secret field containing a Kubernetes ServiceAccount JWT used\nfor authenticating with Vault. If a name is specified without a key,\n`token` is the default. If one is not specified, the one bound to\nthe controller will be used.",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "A key in the referenced Secret.\nSome instances of this field may be defaulted, in others it may be required.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[-._a-zA-Z0-9]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the Secret resource being referred to.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "The namespace of the Secret resource being referred to.\nIgnored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.",
|
||||
"maxLength": 63,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"serviceAccountRef": {
|
||||
"description": "Optional service account field containing the name of a kubernetes ServiceAccount.\nIf the service account is specified, the service account secret token JWT will be used\nfor authenticating with Vault. If the service account selector is not supplied,\nthe secretRef will be used instead.",
|
||||
"properties": {
|
||||
"audiences": {
|
||||
"description": "Audience specifies the `aud` claim for the service account token\nIf the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity\nthen this audiences will be appended to the list",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the ServiceAccount resource being referred to.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "Namespace of the resource being referred to.\nIgnored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.",
|
||||
"maxLength": 63,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mountPath",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": "v1",
|
||||
"served": true,
|
||||
"storage": true
|
||||
},
|
||||
{
|
||||
"kubernetes_auth_schema": {
|
||||
"description": "Kubernetes authenticates with Vault by passing the ServiceAccount\ntoken stored in the named Secret resource to the Vault server.",
|
||||
"properties": {
|
||||
"mountPath": {
|
||||
"default": "kubernetes",
|
||||
"description": "Path where the Kubernetes authentication backend is mounted in Vault, e.g:\n\"kubernetes\"",
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"description": "A required field containing the Vault Role to assume. A Role binds a\nKubernetes ServiceAccount with a set of Vault policies.",
|
||||
"type": "string"
|
||||
},
|
||||
"secretRef": {
|
||||
"description": "Optional secret field containing a Kubernetes ServiceAccount JWT used\nfor authenticating with Vault. If a name is specified without a key,\n`token` is the default. If one is not specified, the one bound to\nthe controller will be used.",
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "A key in the referenced Secret.\nSome instances of this field may be defaulted, in others it may be required.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[-._a-zA-Z0-9]+$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the Secret resource being referred to.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "The namespace of the Secret resource being referred to.\nIgnored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.",
|
||||
"maxLength": 63,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"serviceAccountRef": {
|
||||
"description": "Optional service account field containing the name of a kubernetes ServiceAccount.\nIf the service account is specified, the service account secret token JWT will be used\nfor authenticating with Vault. If the service account selector is not supplied,\nthe secretRef will be used instead.",
|
||||
"properties": {
|
||||
"audiences": {
|
||||
"description": "Audience specifies the `aud` claim for the service account token\nIf the service account uses a well-known annotation for e.g. IRSA or GCP Workload Identity\nthen this audiences will be appended to the list",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of the ServiceAccount resource being referred to.",
|
||||
"maxLength": 253,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$",
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "Namespace of the resource being referred to.\nIgnored if referent is not cluster-scoped, otherwise defaults to the namespace of the referent.",
|
||||
"maxLength": 63,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mountPath",
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"name": "v1beta1",
|
||||
"served": true,
|
||||
"storage": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import json,subprocess,sys,hashlib,datetime
|
||||
out={'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'native_mutation':False,'secret_value_reads':False}
|
||||
def get(args):
|
||||
r=subprocess.run(['kubectl',*args,'-o','json'],capture_output=True,text=True)
|
||||
if r.returncode:return None
|
||||
try:return json.loads(r.stdout)
|
||||
except Exception:return None
|
||||
for ns in ['external-secrets','forgejo']:
|
||||
deps=get(['get','deployments','-n',ns]);out[ns+'_deployments']=None if deps is None else [{'name':d['metadata']['name'],'uid':d['metadata']['uid'],'serviceAccount':d['spec']['template']['spec'].get('serviceAccountName','default'),'images':[c['image'] for c in d['spec']['template']['spec']['containers']]} for d in deps['items']]
|
||||
sas=get(['get','serviceaccounts','-n',ns]);out[ns+'_serviceaccounts']=None if sas is None else [{'name':s['metadata']['name'],'uid':s['metadata']['uid'],'automountServiceAccountToken':s.get('automountServiceAccountToken')} for s in sas['items']]
|
||||
stores=get(['get','secretstores','-n',ns]);out[ns+'_secretstores']=None if stores is None else [{'name':s['metadata']['name'],'uid':s['metadata']['uid'],'vault':{k:v for k,v in s['spec'].get('provider',{}).get('vault',{}).items() if k in ['server','path','version','auth']},'conditions':[{k:c[k] for k in ['type','status','reason'] if k in c} for c in s.get('status',{}).get('conditions',[])]} for s in stores['items']]
|
||||
es=get(['get','externalsecrets','-n',ns]);out[ns+'_externalsecrets']=None if es is None else [{'name':s['metadata']['name'],'uid':s['metadata']['uid'],'store':s['spec']['secretStoreRef'],'target':s['spec'].get('target',{}).get('name'),'data':[{'secretKey':x.get('secretKey'),'key':x.get('remoteRef',{}).get('key'),'property':x.get('remoteRef',{}).get('property')} for x in s['spec'].get('data',[])],'conditions':[{k:c[k] for k in ['type','status','reason'] if k in c} for c in s.get('status',{}).get('conditions',[])]} for s in es['items']]
|
||||
crd=get(['get','crd','secretstores.external-secrets.io'])
|
||||
if crd:
|
||||
versions=[]
|
||||
for v in crd['spec']['versions']:
|
||||
a=v['schema']['openAPIV3Schema']['properties']['spec']['properties']['provider']['properties']['vault']['properties']['auth']['properties']['kubernetes']
|
||||
versions.append({'name':v['name'],'served':v['served'],'storage':v['storage'],'kubernetes_auth_schema':a})
|
||||
out['secretstore_crd']={'uid':crd['metadata']['uid'],'versions':versions}
|
||||
bindings=[]
|
||||
for resource in ['clusterrolebindings','rolebindings']:
|
||||
obj=get(['get',resource,'-A'])
|
||||
if obj is None:continue
|
||||
for b in obj['items']:
|
||||
if any(s.get('kind')=='ServiceAccount' and s.get('namespace')=='external-secrets' for s in b.get('subjects',[])):
|
||||
ref=b['roleRef'];args=['get',ref['kind'].lower(),ref['name']]
|
||||
if ref['kind']=='Role':args+=['-n',b['metadata']['namespace']]
|
||||
role=get(args)
|
||||
bindings.append({'kind':b['kind'],'name':b['metadata']['name'],'namespace':b['metadata'].get('namespace'),'subjects':b.get('subjects',[]),'roleRef':ref,'rules':None if role is None else role.get('rules',[])})
|
||||
out['eso_rbac_bindings']=bindings
|
||||
print(json.dumps(out,sort_keys=True))
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Forgejo consumer ESO reader — concrete source proposal
|
||||
|
||||
RAPPS-WP-0016 already chooses ESO delivery for the OIDC client secret. This proposal fills its missing reader identity without asking the founder to make that same source choice again. It is not a live apply, a new credential grant, or first-case success. Existing ORGREF-WP-0002 remains the project driver; CCR42, MASON-WP-0010-T03 and NK-WP-0048-T06 remain the owner work, rather than another workplan.
|
||||
|
||||
Actual read-only railiance01 metadata shows ESO v0.16.1 running as external-secrets/external-secrets, served v1/v1beta1 SecretStore CRDs supporting serviceAccountRef.audiences, and controller RBAC permitting serviceaccounts/token create. Forgejo and its runner currently share forgejo/default; they must not become the OpenBao reader identity. No consumer namespace SecretStore or dedicated OIDC-delivery SA exists in the observed metadata. Existing forgejo-mailer-eso is a separate SMTP lane and remains unchanged. No OpenBao login, capability check, auth operation, Secret value, Secret object retrieval, or native materialization was performed.
|
||||
|
||||
## Exact supported reader
|
||||
|
||||
`consumer-delivery.yaml` prepares exactly three namespaced objects: ServiceAccount forgejo/forgejo-keycape-forgejo-eso with automount=false and no legacy token refs; SecretStore forgejo/openbao-keycape-forgejo-consumer; ExternalSecret forgejo/forgejo-netkingdom-oidc-client. The store requests a token for that dedicated serviceAccountRef, audiences=[openbao], and authenticates to OpenBao Kubernetes role external-secrets-keycape-forgejo-forgejo. The role binds exactly that SA and namespace, only workload-kv-read-keycape-forgejo-forgejo, no default policy, and a 900-second maximum OpenBao token lifetime. ESO's installed source requests a 600-second Kubernetes token and can fall back to legacy SA tokens; owner admission must ensure this new SA has no legacy tokens, never create one to bypass a failed TokenRequest.
|
||||
|
||||
The namespace SecretStore cannot select another namespace's SA; serviceAccountRef.namespace is omitted. No extra RBAC, projected workload token, Deployment SA replacement or controller identity change is proposed. Namespace/default is not an OpenBao-bound reader. ESO remains the shared trusted controller: it already has clusterwide TokenRequest and Secret permissions and can request this SA's token. This separates reader binding/revocation and avoids default identity reuse; it does not isolate this secret from controller compromise or grant new cryptographic isolation.
|
||||
|
||||
The exact KV data ACL is platform/data/workloads/keycape/forgejo, read only, plus existing self-token lookup/revoke stanzas. No metadata/list/parent/sibling access is added. Delivery maps only remote workloads/keycape/forgejo field CLIENT_SECRET to target key CLIENT_SECRET. Target creationPolicy=Owner and deletionPolicy=Retain preserve fail-safe remote-deletion behavior. Refresh is 15m; the value is never present in this source. Existing internal OpenBao HTTP endpoint and controller custody behavior are retained, not upgraded into a TLS/isolation claim.
|
||||
|
||||
## Type and structural boundary
|
||||
|
||||
NetKingdom's proposed successor type 1.2 adds explicit case parameter consumer_namespace, one sixth structural object consumer-eso-role, and separate owner deliver-consumer-secret follow-through/readiness/rollback. All original five rendered structural bodies compare equal to current1.1. Existing acknowledged type 1.1 and accepted case 1.1 remain byte-preserved; the proposed 1.2 case has status proposed and no copied acceptance. Full proposed render is c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35. Existing acceptance does not silently extend to the new role/runtime delivery. Owner review and exact type/case acknowledgment precede any build, metadata apply or materialized-status receipt.
|
||||
|
||||
The current Mason builder gives every role-backed reader the ESO draft suffix. With two reader roles it silently overwrites one CCR. The new regression proves that defect against current source; the minimal staged fix retains ESO for the KeyCape reader and Forgejo for consumer-reader-policy, plus refuses duplicate output identities.42 actual focused builder/plan/structure tests pass with corrected source, including both independent CCRs, platform validation and a complete six-object structural request. Existing1.1 tests remain passing. No fake proposed 1.2 acceptance is used: Mason correctly refuses to build a merely proposed successor. Test fixtures alone temporarily acknowledge synthetic roles to exercise builder semantics.
|
||||
|
||||
The proposed CCR42 body uses proposed status, bound_claims_confirmed=false, explicit pending1.2 acknowledgment and old CCR's exact historical hash/provenance. Owner validation passes with the expected apply-blocking warning. It is stored separately from the actual in-flight CCR42; no historical review or applied CCR41 is rewritten. Folding this reviewed body into CCR42 remains an owner reconciliation with retained original revision, not a fresh parallel request.
|
||||
|
||||
## Admission, native proof and recovery
|
||||
|
||||
Before an exact scoped native proposal, refresh ESO image/CRD/RBAC, Forgejo/default and runner identity, the three proposed object identities and target metadata through Kubernetes PartialObjectMetadata only. Classify absent, exact preexisting-matched or conflicting; refuse a legacy-token SA, unknown ownership, changed role/policy, audience mismatch or shared target. Never read Secret data. Preserve current UIDs/resourceVersions, authenticated owner change receipts and the exact type/case/render/source pins. No placeholder SA claim substitutes for actual owner admission.
|
||||
|
||||
Platform materialization applies only the reviewed exact-path policy and dedicated Kubernetes-auth role after its own approved CCR path. Fresh owner metadata/capability receipts must prove exact binding and distinguish case-created from preexisting; no minted value or successful delivery is required to prove structure. Mason's existing typed structural contract then covers all six objects, including source layout and catalog pointer; source-only bodies are not authenticated native evidence. Warden native dispatch/descriptors/session receipt and per-owner provenance still need their existing accepted integration review.
|
||||
|
||||
Consumer follow-through applies the three admitted objects, then checks SecretStore/ExternalSecret Ready status and exact target UID/ownerReference through metadata only. Shared/matched objects never acquire delete authority. If ESO reports failure, stop; no manual Secret seeding or controller/static-token fallback is admitted. Minting, KeyCape client/env/groups, and Forgejo Helm OIDC/group enforcement stay distinct existing first-case steps. Forgejo gitea.oauth secret-consumption settings were not changed or Helm-render-verified here; they must be prepared/reviewed before T02 handover, with current local login intact and T01 break-glass acceptance still a prerequisite. Delivery Ready alone does not complete browser/MFA/group checks or forge closure.
|
||||
|
||||
Created-only rollback first reverts the consumer secret reference/auth source through its owner Helm revision. Remove only the admitted trial-created ExternalSecret and independently proven case-created target UID/ownerReference; preserve matched/shared targets. Then remove only created namespace SecretStore and SA. Remove only the created OpenBao role after revoking its issued tokens or proving expiry, then created policy. Role/SA/store deletion does not revoke already issued OpenBao tokens; deletion alone is not deactivation proof. Retain native/audit/source receipts and stop if references, UIDs or ownership changed. Do not infer delete authority from Mason's legacy planned rollback inventory.
|
||||
|
||||
RAPPS-WP-0016-T02, current CCR42, MASON-WP-0010-T03 and first-case acceptance remain unfinished. ORGREF-WP-0002 holds this owner return. Human-only break-glass/account-link/MFA/first-case acknowledgment gates remain separate from source preparation; no new human login is requested by this packet.
|
||||
|
||||
## Supplementary generated-CCR refinement after Platform conditional review
|
||||
|
||||
Exact prior 271 packet is preserved at /tmp/forgejo-consumer-eso-refinement-original-271, alongside original c48. Platform found two additional generator gaps: hardcoded keycape target for the consumer, and delete-only consumer-role lifecycle. Both meaningful counterexamples fail against 271 source before the fix. Current staged generator derives target.workload from consuming_service only for consumer-reader-policy, preserving all KeyCape ESO output fields. A role-backed consumer CCR requires reverting consumer reference, authenticated case-created ownership, preservation of preexisting/shared objects, issued-token owner revocation or independently verified expiry before deactivation, and no new login/remint during recovery. Actual owner native authority remains required; generated wording is not an implemented revocation adapter.
|
||||
|
||||
Forty-four focused builder/plan/structure tests now pass. Earlier 42 and original counterexample/failure history remain retained. The old committed 1.1 bundle still mislabels the consumer workload keycape and therefore correctly fails fresh exact target matching after this correction. That historical bundle is preserved; the checker is not weakened or grandfathered. Local fixtures alone use corrected/rehashed consumer metadata, with a separate regression proving old bundle refusal. Current 1.1 unbound consumer generation changes only its previously wrong target.workload; KeyCape reader output remains unchanged. The proposed 1.2 render/type/case/source remains pending exact acknowledgment, with identical six-object structural body and no native completion claim. Fresh source build/bundle owner reconciliation must occur after exact acceptance before any native apply/materialization; old CCR or bundle approval is not silently reassigned. New source/test hashes are in validation.json and generated projection receipt.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
[
|
||||
{
|
||||
"repo": "net-kingdom",
|
||||
"path": "security-case-types/oidc-browser-relying-party/type.yaml",
|
||||
"sha256": "f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c"
|
||||
},
|
||||
{
|
||||
"repo": "net-kingdom",
|
||||
"path": "security-cases/forgejo-netkingdom-oidc.yaml",
|
||||
"sha256": "bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d"
|
||||
},
|
||||
{
|
||||
"repo": "ops-mason",
|
||||
"path": "src/ops_mason/case_build.py",
|
||||
"sha256": "249cc8574939d2c3193f968309781c472c96c199e04206c336463b4d65ea6659"
|
||||
},
|
||||
{
|
||||
"repo": "ops-mason",
|
||||
"path": "src/ops_mason/case_structure.py",
|
||||
"sha256": "71032a5ea613f5226f552f796656599c1a7447a38428f890595d8f252831ce80"
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"path": "credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml",
|
||||
"sha256": "a36ab567839c12660c8ca468c8e4c6a67e367c92ae2ae51851b8e34c35a277f3"
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"path": "credential-change-requests/CCR-2026-0041-forgejo-netkingdom-oidc-keycape-eso-reader.yaml",
|
||||
"sha256": "e69077ad29166770e4a57b4c4105615e272822d8fde11df591d0c127b158ac67"
|
||||
},
|
||||
{
|
||||
"repo": "railiance-apps",
|
||||
"path": "workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md",
|
||||
"sha256": "a74f9fbc3300739bc0d8e31f1de88c0b7226ff2f6990e7cbb3ae433ac84fdb5e"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[
|
||||
{
|
||||
"url": "https://github.com/external-secrets/external-secrets/blob/v0.16.1/pkg/provider/vault/auth_kubernetes.go",
|
||||
"sha256": "e53f43c0748e43f408b5a7c5323c12c0fb5fb4299a33718181f6d70945112071",
|
||||
"finding": "serviceAccountRef requests TokenRequest with600second lifetime, then legacy-token fallback if unavailable; namespace local for SecretStore; no serviceAccountRef means mounted controller token path"
|
||||
},
|
||||
{
|
||||
"url": "https://external-secrets.io/latest/provider/hashicorp-vault/",
|
||||
"finding": "Official current documentation cross-check; actual runtime compatibility established by v0.16.1 source and installed CRD/RBAC instead of assuming latest behavior."
|
||||
}
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue