--- id: RPF-WP-0044 type: workplan title: "ArgoCD phase B: adopt the four existing Applications on railiance01" domain: financials repo: railiance-platform status: active flavor: planning owner: railiance-platform topic_slug: railiance created: "2026-09-21" updated: "2026-09-21" related: [RPF-WP-0043, RPF-WP-0022] state_hub_workstream_id: "98140775-3b9a-5cf9-9af6-722502d487dc" --- The founder, Bernd Worsch, decided ArchitectureBlueprint §5.6 on 2026-09-21, exercising `GOVERN @ estate`: option 1, **adopt properly**. Phase A installed Argo CD Core v3.5.3 on railiance01 (record: `the-custodian/docs/kubernetes-change-gate-decision.md`). This plan is phase B: bring the four Applications in `argocd/applications/` under that ArgoCD, **one at a time, each with its own founder go-ahead**. Terms follow SecurityCanon Mode of Authority v0.2.0 (draft). **This plan changes nothing live.** Every task that would change railiance01 is `wait` on the founder. Each live step is `ADMINISTER @ realm:kubernetes/railiance01` (bootstrap) or `CONSTRUCT @ railiance-platform` with the merge as the gate, both `activation=APPROVED`. Related, kept separate: `RPF-WP-0043` onboards `rapp-policy-nexus`, which is not one of these four. It needs T02 of this plan (a working root app on railiance01) before its own T04. Phase C, retiring coulombcore's ArgoCD, is not planned here (T08). ## Read-only evidence, 2026-09-21 Full record: `docs/evidence/argocd-phase-b-adoption-diff-2026-09-21.json`. Rendered locally (`helm template`, `kubectl kustomize`), then `kubectl diff` client-side and `--server-side` dry run over `ssh railiance01`. Nothing was applied. | App | Live spec diff | Would prune | Repo credential | Blocker | | --- | --- | --- | --- | --- | | openbao-secretstore | none (1 ClusterSecretStore) | nothing | none, public | none | | issue-core | none **after repointing** | nothing | **new**: `rapp-issue-core` is private | declared path `issue-core.git k8s/railiance` no longer exists | | target-revenue | none on Deployment/Service/Ingress/ExternalSecret/Cluster | nothing | none, public | CNPG `Cluster` not in `railiance-tenants` whitelist; two Sync-hook Jobs | | external-secrets | none (39 objects, 20 CRDs) | nothing now | none, public Helm repo | cluster-wide blast radius; CRDs must never be pruned | Nothing is pruned at adoption because no live object carries an ArgoCD tracking annotation; ArgoCD only prunes what it tracks. The one change every adoption makes is that tracking annotation, on metadata only, so no rollout. Sync settings found: `railiance-apps-root` and all four children declare `automated: {prune: true, selfHeal: true}`; none has a finalizer. Applying `argocd/bootstrap/` as committed would therefore adopt all four at once. T01 removes that before anything is applied. Also found: the `external-secrets` Helm release is stuck at revision 7, `pending-upgrade`, since 2026-07-07 (values identical to the Application); `issue-core` is now server-side applied by `rapp-issue-core`; `target-revenue` runs a mutable image tag (`0.1.4`). ArgoCD on railiance01 has 0 AppProjects, 0 Applications, 0 repository Secrets, and there is no `argocd` CLI on the node, so the commands below use `kubectl` against the Application resource. Not verified: repo-server egress to Forgejo and `charts.external-secrets.io`; T02 proves it. ## Neutralise automated sync and fix the declared state ```task id: RPF-WP-0044-T01 status: wait priority: high state_hub_task_id: "2d7230f5-50c8-5eee-a050-1501ce5af62a" ``` **Waits on the founder's choice of layout.** Repository-only; applying nothing. But coulombcore's ArgoCD still syncs `argocd/applications/` on `main`, so an edit there is a live change on coulombcore. - **Option A (recommended):** a railiance01-only source path, `argocd/railiance01/applications/`, starting empty, and the railiance01 root pointing at it. `argocd/applications/` is left untouched for coulombcore until phase C. No commit in this plan then reaches coulombcore. - **Option B:** edit `argocd/applications/` in place. Every change also lands on coulombcore's ArgoCD; needs its own go-ahead and a coulombcore read first. Declared changes (Option A): 1. Root app for railiance01 with **no `automated` block** and path `argocd/railiance01/applications`. 2. `railiance-tenants`: add `postgresql.cnpg.io/Cluster` to `namespaceResourceWhitelist` (target-revenue). 3. Child Application drafts kept in this workplan until their task; each without `automated`, without finalizers, with `targetRevision` pinned to a commit or chart version. 4. `issue-core` draft repointed to `rapp-issue-core.git`, path `manifests`. 5. Add `argocd/repositories/rapp-issue-core.repository.sops.yaml.template` (no value). Mark `issue-core.repository.sops.yaml.template` obsolete. AppProjects and the root are applied by hand (T02), not synced by the root, so these edits change nothing live until T02. ## Apply the AppProjects and the root app, automated sync off (live) ```task id: RPF-WP-0044-T02 status: wait priority: high state_hub_task_id: "7d97ee9c-b2fb-520f-8468-3ecf7eea6c01" ``` **Waits on the founder's go-ahead.** Depends on T01. ```bash ssh railiance01 'kubectl apply --dry-run=server -f -' < <(kubectl kustomize argocd/bootstrap) ssh railiance01 'kubectl apply -f -' < <(kubectl kustomize argocd/bootstrap) ssh railiance01 'kubectl -n argocd get appprojects,applications' ``` Expect three AppProjects and `railiance-apps-root` Synced with zero children (the path is empty). A `ComparisonError` means the repo-server cannot reach Forgejo: stop and fix egress before any app task. No repository Secret is needed; `coulomb/railiance-platform` is public. Rollback: `kubectl -n argocd delete application railiance-apps-root` (no finalizer, no children), then delete the three AppProjects. Nothing else is touched. ## Per-app procedure (T03–T06) Every adoption task follows the same steps. `` and `` vary. 1. **Diff.** Re-run the evidence render and `kubectl diff --server-side` at the commit or chart version being pinned. Any spec change, any missing whitelist kind, or a different image: stop. 2. **Merge** the child Application (no `automated`) into the railiance01 path, then sync the root by hand so it creates the child object only: ```bash ssh railiance01 'kubectl -n argocd patch application railiance-apps-root --type merge -p "{\"operation\":{\"initiatedBy\":{\"username\":\"founder-approved\"},\"sync\":{\"revision\":\"\",\"prune\":false}}}"' ``` 3. **ArgoCD's own diff:** read `kubectl -n argocd get application -o jsonpath='{.status.sync.status} {.status.health.status}'` and `.status.resources`. Only metadata differences are acceptable. 4. **Manual sync, prune off, hooks skipped** (`apply` strategy): ```bash ssh railiance01 'kubectl -n argocd patch application --type merge -p "{\"operation\":{\"initiatedBy\":{\"username\":\"founder-approved\"},\"sync\":{\"revision\":\"\",\"prune\":false,\"syncStrategy\":{\"apply\":{}}}}}"' ``` Then verify Synced/Healthy and run the owner's live check. 5. **Prove it:** Synced and Healthy for at least 24 hours (7 days for external-secrets). Only then, with a **second** go-ahead, a merge adds `automated: {selfHeal: true}`; `prune: true` is a third step once the app's tracked set is confirmed complete. 6. Record `docs/evidence/--argocd-adoption.json`. Rollback: - **R1, before automated sync:** revert the merge, then `kubectl -n argocd delete application `. No finalizer, so nothing cascades; the workload keeps running. Tracking annotations stay and are harmless. - **R2, after automated sync:** revert the automated-sync commit first and sync the root by hand, then R1. Never hand-patch a child while the root self-heals; that is `BREAK_GLASS`, recorded and reconciled into git. - **Workload restore:** only once ArgoCD no longer manages the app, with the previous deploy method named in the task. ## Adopt openbao-secretstore (lowest risk) ```task id: RPF-WP-0044-T03 status: wait priority: high state_hub_task_id: "6d5fc801-e361-579b-bcd8-6fe719a82e94" ``` **Waits on the founder's go-ahead.** Depends on T02. One object, `ClusterSecretStore/openbao`, zero diff, public source. It is the store issue-core's `ExternalSecret` reads, so adopt it before issue-core. The 24 other ClusterSecretStores on railiance01 are not in the kustomization and stay untracked (declared gap, RPF-WP-0043-T05). Live check: `kubectl get clustersecretstore openbao` stays `Valid`, and `externalsecret/issue-core-runtime` stays `SecretSynced`. Workload restore: `kubectl apply -f argocd/platform-addons/openbao-secretstore/openbao.clustersecretstore.yaml`. ## Adopt issue-core (from rapp-issue-core) ```task id: RPF-WP-0044-T04 status: wait priority: high state_hub_task_id: "0e619658-0453-51ed-b9a0-bed28b5544fd" ``` **Waits on the founder's go-ahead**, and on two preconditions: - `rapp-issue-core` agrees (hub message) that ArgoCD owns production and its `make deploy` stops being a production path; it would fight self-heal. Image releases become commits to `manifests/30-runtime.yaml`. - A CCR for the ArgoCD repository credential, source at `platform/operators/argocd/repositories/rapp-issue-core`, read-only deploy token. Applied with `make argocd-repo-apply`; no value is handled in this plan. Zero diff against live for both the raw `manifests/` and the rendered output (digest `sha256:a56c80cc…b92cf`). All kinds are in the `railiance-tenants` whitelist. Live check: `rapp-issue-core make verify-live`. Workload restore: `rapp-issue-core make rollback`. ## Adopt target-revenue ```task id: RPF-WP-0044-T05 status: wait priority: medium state_hub_task_id: "d418068a-6fb0-5416-aac5-d23c93924d9c" ``` **Waits on the founder's go-ahead.** Preconditions: T01 item 2 (CNPG `Cluster` whitelisted) applied to the AppProject by hand, and `target-revenue` confirms the two Sync-hook Jobs (`target-revenue-migrate`, `target-revenue-bootstrap-binky`) are safe to re-run against production. They are absent live and would run on every sync with the default hook strategy. The adoption sync uses the `apply` strategy, which skips them. Once automated sync is enabled they run on each sync, so the second go-ahead depends on that confirmation. Ask `target-revenue` to pin an image digest instead of `0.1.4`. Live check: `https://revenue.coulomb.social` health and the CNPG cluster healthy. Workload restore: `kubectl apply -k k8s/railiance` from the previous target-revenue commit. ## Adopt external-secrets (highest risk) ```task id: RPF-WP-0044-T06 status: wait priority: medium state_hub_task_id: "4ca54868-c31f-5e3d-b7d3-a15b84c15f80" ``` **Waits on the founder's go-ahead.** Zero diff, but ESO serves every `ExternalSecret` on the cluster (25 ClusterSecretStores). Before the merge, add `crds.annotations: {argocd.argoproj.io/sync-options: "Prune=false,Delete=false"}` to the values and re-run the diff; pruning a CRD would delete every `ExternalSecret` and store. Adopt with `ServerSideApply=true` as declared. Watch for drift on `caBundle` fields injected by the cert-controller; if ArgoCD shows it, add `ignoreDifferences` before enabling self-heal. After adoption, retire the stale `pending-upgrade` Helm release record (its `sh.helm.release.v1.external-secrets.v*` Secrets) under a separate go-ahead; ArgoCD does not track them. Live check: all ClusterSecretStores keep their current `Valid` state (two, `openbao-activity-core` and `openbao-email-connect`, are already `ValidationFailed`). Workload restore: `helm template` 0.16.1 with the same values, `kubectl apply --server-side`. ## Turn the root back to the production lane ```task id: RPF-WP-0044-T07 status: wait priority: medium state_hub_task_id: "844ff115-def2-55ca-ab01-413ba7e6857d" ``` **Waits on the founder's go-ahead.** After T03–T06 are proven, restore `automated: {prune: true, selfHeal: true}` on `railiance-apps-root`, as the change-gate decision's production row expects. From then on a merge to the railiance01 path is a live production change, and `RPF-WP-0043-T04` (policy-nexus) can use it. ## Plan phase C: retire coulombcore's ArgoCD ```task id: RPF-WP-0044-T08 status: todo priority: low state_hub_task_id: "55d1382f-5862-5321-a1c9-96767764ba43" ``` Planning only. Needs a read-only check of coulombcore's ArgoCD, outside this session's scope. Under Option A, retiring it also removes `argocd/applications/`. Also hand back to the cluster layer: the phase A install is not declared in any repository and its pods have no resource requests (BestEffort).