--- id: RPF-WP-0043 type: workplan title: "Onboard rapp-policy-nexus to the ArgoCD production lane" domain: financials repo: railiance-platform status: ready flavor: planning owner: railiance-platform topic_slug: railiance created: "2026-09-21" updated: "2026-09-21" due: "2026-12-21" related: [RPF-WP-0022] state_hub_workstream_id: "ec41a4bd-df18-5b07-9b63-ccb80d9f001c" --- The founder, Bernd Worsch, decided on 2026-09-21, exercising `GOVERN @ estate`, that changes to `production-approved` workloads go through `CONSTRUCT @` the manifest repository, reconciled by ArgoCD through `railiance-apps-root`. The record is `the-custodian/docs/kubernetes-change-gate-decision.md`. Terms follow SecurityCanon Mode of Authority v0.2.0, which is still a draft. `rapp-policy-nexus` is `production-approved` (reef-railiance `bindings/rapps.yaml`), but no ArgoCD Application manages it. Its changes today are Helm releases, `make deploy` in rapp-policy-nexus: `helm upgrade --install policy-nexus` on railiance01, namespace `policy-nexus`. Until 2026-12-21 those changes keep `activation=APPROVED` by founder plan approval, and each one is recorded as production-tier. This plan onboards policy-nexus by 2026-12-21. **This plan only prepares the change. It does not perform it.** Adding `argocd/applications/policy-nexus.application.yaml` is a live production change. Once it is on `main`, `railiance-apps-root` syncs it with prune and self-heal, and ArgoCD takes ownership of running resources. Nothing in this plan is applied, pushed as an Application, or synced without the founder's go-ahead (T04). Out of scope: the legacy whynot-design npm location (RPF-WP-0035-T07). It stays untouched and unwrapped while this lane moves. ## Confirm ArgoCD actually reconciles on railiance01 ```task id: RPF-WP-0043-T01 status: todo priority: high state_hub_task_id: "d6a7a480-6251-504a-aa5f-ab9c04fe1e79" ``` This repository cannot confirm it; see `docs/argocd-gitops.md` ยง Production Change Path. The only record of ArgoCD's health is from 2026-06 and predates the correction of the railiance01/coulombcore naming. Later records call the controller "coulombcore ArgoCD", and all Applications target `kubernetes.default.svc`. Attended, read-only, against a kubeconfig whose server is verified to be 92.205.62.239: 1. `kubectl get nodes -o wide`: confirm the node IP. 2. `kubectl -n argocd get deploy,sts` and `make argocd-status`. 3. `kubectl -n argocd get application railiance-apps-root -o jsonpath='{.status.sync.status} {.status.health.status} {.status.sync.revision}'`. Compare the revision with Forgejo `main`. Record the output as `docs/evidence/-argocd-railiance01-status.json`. If ArgoCD is absent or not syncing on railiance01, stop this plan. Report to the founder and the-custodian that the production row has no working path. An ArgoCD install belongs to the cluster layer and needs its own go-ahead. ## Agree where policy-nexus's manifests live ```task id: RPF-WP-0043-T02 status: wait priority: high state_hub_task_id: "49143c5e-a455-526e-a26f-403dc2fb075a" ``` Per the tenant contract, workload manifests stay in the tenant repository. `rapp-policy-nexus` owns that repository (`forgejo.coulomb.social/coulomb/rapp-policy-nexus`) and decides this itself. We coordinate through the hub and do not decide it here. Proposal to put to rapp-policy-nexus: - The source is the existing chart `helm/policy-nexus`. - A committed values file, for example `k8s/railiance/values-railiance01.yaml`, pins `image.digest`, `publicationManifestDigest`, `sourceInventoryDigest` and `sourceSetDigest`. Today they are passed as `--set-string` at deploy time. After adoption, a release is a commit that changes these pins. - `helm.releaseName: policy-nexus`, so that rendered names and the `app.kubernetes.io/instance` label match the live release. - Once adopted, `make deploy` / `helm upgrade` stops being a production path. It would fight self-heal. Whether to keep it for pre-production is for rapp-policy-nexus to decide. Platform checks: - The chart renders Deployment, Service, ServiceAccount, Ingress and NetworkPolicy. All five are in the `railiance-tenants` whitelist. - Repository access: if the repository is private, it needs an ArgoCD repository credential at `platform/operators/argocd/repositories/rapp-policy-nexus`. That credential goes through its own CCR. No value is handled in this plan. **Unblock:** rapp-policy-nexus replies with the path, the values file, and whether the repository is public. ## Prepare the Application and the dry-run evidence ```task id: RPF-WP-0043-T03 status: wait priority: high state_hub_task_id: "2afb1aac-66dd-5e1e-92e1-fc020a59a927" ``` Draft the Application in this workplan or in a review branch, **not** under `argocd/applications/` on `main`. Draft shape for adoption: ```yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: policy-nexus # no finalizers: deletion must not cascade namespace: argocd labels: app.kubernetes.io/part-of: railiance-gitops spec: project: railiance-tenants source: repoURL: https://forgejo.coulomb.social/coulomb/rapp-policy-nexus.git targetRevision: path: helm/policy-nexus helm: releaseName: policy-nexus valueFiles: [] destination: server: https://kubernetes.default.svc namespace: policy-nexus syncPolicy: # adoption stage: no `automated` block syncOptions: - CreateNamespace=false - ApplyOutOfSyncOnly=true - PruneLast=true ``` Read-only evidence to collect before T04, in an attended session: 1. `helm -n policy-nexus get values policy-nexus` and `helm history`: the live digests and the current revision number, which is the rollback target. Do not print anything else from the release. 2. Render the chart with the agreed values file. Run `kubectl diff --server-side` of the render against the live namespace. Expected result: no spec change, and only label or annotation differences. 3. List every object in `policy-nexus`. Mark which ones the render does not contain, for example Helm release Secrets `sh.helm.release.v1.*`. Only resources carrying ArgoCD's tracking label are pruned. Confirm that none of the unrendered objects carry it. Stop conditions: any Deployment spec or image change, any object that the render lacks but that carries the tracking label, or a digest mismatch. ## Adopt policy-nexus (live, founder go-ahead) ```task id: RPF-WP-0043-T04 status: wait priority: high state_hub_task_id: "8638fa73-439d-5dcd-85c7-81aa125170e1" ``` **Waits on the founder's go-ahead.** This is a `CONSTRUCT @ railiance-platform` change with `activation=APPROVED`. The merge is the gate, and it is a live production change. Do not take it on the basis of this plan alone. 1. Merge the Application exactly as in T03, with no `automated` sync. `railiance-apps-root` creates the Application object. It does not touch policy-nexus resources, because the child has no automated sync. 2. `argocd app diff policy-nexus`, or read the UI diff. It must match T03 step 2. Any other result means stop, then roll back (step R1). 3. `argocd app sync policy-nexus` with pruning left off, which is the default for a manual sync. Then run rapp-policy-nexus `make verify-live` with the pinned digests. 4. After the workload has been Synced and Healthy for at least 24 hours, a second merge adds `automated: {prune: true, selfHeal: true}`, matching the tenant contract. This needs a second go-ahead. 5. Record evidence as `docs/evidence/-policy-nexus-argocd-adoption.json`. Notify rapp-policy-nexus and ops-mason that policy-nexus is now in the production row. Its transition exception ends. Rollback: - **R1, before step 4:** revert the merge commit. The root app prunes the child Application object. The child has no finalizer, so the deletion does not cascade and the workload keeps running unchanged. - **R2, after step 4:** revert the automated-sync commit first, then R1. Do not patch the child Application by hand, because the root app's self-heal restores it from git. A hand patch is a direct `ADMINISTER` under `BREAK_GLASS`. It is recorded and reconciled back into git. - **Workload restore:** only after ArgoCD no longer manages policy-nexus, run rapp-policy-nexus `make rollback REVISION=`. Otherwise self-heal fights the rollback. ## Declare the platform's own direct-apply gap ```task id: RPF-WP-0043-T05 status: todo priority: medium state_hub_task_id: "91431560-79d4-5352-a6b5-f12850315dd2" ``` Under the decision, platform objects without a readiness state default to the production tier. This repository still changes several of them through direct Make targets. Examples are the railiance01 ClusterSecretStores, OpenBao configuration and the ArgoCD bootstrap. That is `target-audited` evidence only. Inventory those targets, declare the gap as not conforming, and propose to the founder which ones move to a reconciled lane and which stay direct under `APPROVED`. Do not change a target's behaviour as part of the inventory.