The subscriber is deliberately not named here, and their registry hostnames,
project names and dates are not in this repository. Access to this repository
is granted per person and the next person may be another customer’s engineer —
see ADR-0008. Every technical finding below is
intact; only the identity is gone.
The target setup, in their own terms
How the station lands there
One merge into their config repo — the same motion as every deploy they make:- Bootstrap by commit: an Argo
Application(the station root) + a repo secret for the Harbor-proxied OCI helm repo. Site facts (Harbor paths, pinned channel public key) ride inline. Seestation/profiles/TEMPLATE/for the profile shape; filled per-customer profiles are not committed (ADR-0008). - The root pulls the station bundle (
scope: namespace,admission.enabled: false): the subscription ApplicationSet, their contract ConfigMap, the substrate floor CronJob. No Kyverno exists there — the verify gate is the enforcement point; cluster admission is the later ops-hardening step. - The subscription pulls Vexa releases through their Harbor. Staging tier follows automatically; the prod pin moves only when their approver moves it.
- The floor re-checks the tenant preconditions every 10 minutes and reports cluster-level facts as UNKNOWN rather than guessing — a tenant never pretends to see the substrate.
kit/platform/render.sh
renders the Argo CRDs, Kyverno and the two ClusterPolicies, the read
ClusterRole the controller needs, and the projects with their LimitRange and
ResourceQuota into a single platform-<provider>.yaml a platform team applies
once — after which kit/install.sh --cluster-scope platform-pack does
namespace-scoped work only, and refuses by name if the pack is not there.
kit/platform/README.md is the page written for the platform engineer. This
matters for the same reason the list exists: an ask expressed as five
sentences is five tickets, and one file is one.
One-time platform-team asks (their bucket, pre-authorized as “special
configuration later”): namespace + runtime RBAC + LimitRange + ResourceQuota
(already requested by the customer’s own engineer), and — only for the private
channel lane — one Harbor registry endpoint holding our read-only subscriber
token plus a proxy-cache project. The public lane (vexaai on Docker Hub)
needs nothing.
Three more, found by attempting the verbs as the tenant on the 2026-09-06
rehearsal (receipt). Each is
cluster-scoped, so a project holder cannot self-serve it, and each is a hard
stop rather than a degradation — worth naming before this reaches their
platform team, because they will hit all three:
And one correction to the LimitRange row above: the ceiling must be ≥ the
largest container limit in the delivered chart, not merely ≥2560Mi. The
recorded 2560Mi is sized for the bot’s memory-backed
/dev/shm; the chart’s
own postgres asks a 4Gi limit and is refused at admission
(maximum memory usage per Container is 2560Mi, but limit is 4Gi). A project
admin is read-only on quota and limits, so this is theirs to set.
How we reproduce the environment for validation
Two rigs plus the standing station:- Transport rig — a literal Harbor (v2.15.2) configured exactly as theirs:
an anonymous
dockerhub-proxyand a credentialed proxy-cache project in front of an authenticated private registry playing our channel host. The endpoint + project are four Harbor API calls (recorded in the runbook). - Tenant rig — a restricted platform with their constraint shape: enforced ResourceQuota + LimitRange (their defaults), a ServiceAccount kubeconfig scoped to one namespace (every app action as tenant), Argo installed with a strict ledger of ops-acts vs tenant-acts, a config repo served over git-http, bootstrap by commit. Platform ladder: k3s + PSA-restricted (always available) → MicroShift (real SCC admission, no VM) → genuine OKD (needs KVM; cloud VMs without nested virtualization cannot run it — validated the hard way).
- The standing station — the channel’s own staging/prod pair, running the same bundle in cluster scope with admission enabled.
Parity matrix — where validation matches the target
Findings the genuine-OpenShift run added (2026-08-21)
- HOME under a random UID is a convergence blocker, not just a spawn defect:
the minio-init sync hook fails (
mkdir /.mc: permission denied), so the Application never converges and no later release can be delivered — the single gating fix for OpenShift GitOps delivery, tracked upstream. - Upstream Argo CD’s own images hard-code UIDs (dex 1001, redis 999) and
are rejected by restricted-v2 — use the OpenShift GitOps operator (the house
standard in regulated estates) or strip
runAsUserin a namespace install. - Namespace-scoped Argo needs a namespaced
*/*/*Role beyondadmin, created by the platform team (escalation prevention blocks the tenant), and its ~7 pods consume real quota — size the project for app + Argo, or run Argo in its own namespace.