--- id: ISSUE-WP-0007 type: workplan title: "rapp-issue-core: move the runtime off CoulombCore onto railiance01" domain: infotech repo: issue-core status: finished owner: codex lane: yellow topic_slug: issue-core priority: high created: "2026-08-19" updated: "2026-08-19" reviewed_at: "2026-08-19" quality_dor: DoR-Ok quality_dor_at: "2026-08-19" quality_dor_by: codex quality_dor_note: "Goal, ownership, task order, dependencies, safety gates, and verification signals reviewed against the issue-core repo and current rapp family." depends_on: [] related: - ISSUE-WP-0003 - ACTIVITY-WP-0023 - RMGR-WP-0006 - RMGR-WP-0007 state_hub_workstream_id: "a2bc717a-64cf-45fa-8a9d-59d3bc9e96c7" --- # ISSUE-WP-0007 — rapp-issue-core: move the runtime onto railiance01 CoulombCore is being decommissioned. `issue-core` is one of three services still running there, and it is the load-bearing one: the ops-bridge tunnel `issue-core-railiance01` is a **reverse** forward that re-exports this workstation's `18765` to railiance01, so railiance01 reaches issue-core *through a laptop* that is forwarding to CoulombCore. When CoulombCore goes, issue-core goes with it, and deleting the tunnel is not a migration. ## Start here: what ISSUE-WP-0003 actually proved `ISSUE-WP-0003` is `status: finished` and its opening summary incorrectly says that railiance01 reconciles `issue-core` through ArgoCD. Its own 2026-07-02 completion section records the topology correction: the Deployment and ArgoCD Application were on **CoulombCore**, while railiance01 reached them through the two-hop ops-bridge lane. Checked on 2026-08-19 against the railiance01 cluster (`~/.kube/config-railiance01`, bridge tunnel `k3s-api-railiance01`, local `16444`): | Claim | Observed | | --- | --- | | Deployment live in `issue-core` namespace | Namespace exists, **empty** — `No resources found` | | Reconciled by ArgoCD | **No ArgoCD** — the cluster has no `applications` resource type | | issue-core serving | Serving from **CoulombCore**: `-L 18765:10.43.103.154:8765`, `/healthz` → `{"status":"ok","version":"0.2.1"}` | No railiance01 deployment was silently lost: the first deployment landed on the wrong cluster and was then deliberately bridged to railiance01. The correction is recorded in `history/2026-08-19-wp-0003-topology-correction.md` and at the top of WP-0003. Treat its container, secret, service, and authenticated-ingest evidence as prior art, but do not resume its ArgoCD topology. ## Why a `rapp-` wrapper rather than `k8s/railiance/` in this repo `issue-core` currently carries its own `Dockerfile` and `k8s/railiance/` Kustomize tree. The estate has since settled on a different split, visible in `rapp-postgres`, `rapp-qonto`, `rapp-user-engine`, `rapp-tenant-engine`, `rapp-openbao`, `rapp-secrets-engine`, `rapp-policy-nexus`: **domain logic stays in the app repo; runtime packaging, bindings, secret references, network policy, placement, and rollout evidence live in a `rapp-` repo.** `rapp-qonto/README.md` states the boundary plainly: "Domain logic and financial policy stay in `qonto-assistant`. This repo owns runtime packaging, the Knative binding, secret references, network policy, and workload-specific rollout evidence." That split is also what makes the loss above visible next time: rollout evidence is a deliverable of the wrapper, not a side effect of a cluster being up. `repo-manager` scaffolds these — do not hand-build one: ```bash rmgr rapp wrap --path ../rapp-issue-core --app issue-core \ --ownership-repo issue-core --from-app ../issue-core \ --rail rail-kubernetes \ --package-type manifest-managed-platform-service rmgr rapp validate --path ../rapp-issue-core --family-root .. rmgr rapp place --path ../rapp-issue-core --reef reef-railiance ``` The reviewed values for this workload are `rail-kubernetes`, `manifest-managed-platform-service`, `confidential`, and `high`. It remains a private cluster service. See `repo-manager/docs/RailianceAppDeploymentGuide.md` (RMGR-WP-0006) and `RMGR-WP-0007` (greenfield rapp wrap efficiency). ## Consumers to carry across Migration is not done when a pod is Running. These reach issue-core today: - **activity-core** — `IssueSink` emission. ops-warden routes this need as catalog id `activity-core-issue-sink`. - **ops-bridge** — tunnels `issue-core-coulombcore` (`-L 18765` → `10.43.103.154:8765`) and `issue-core-railiance01` (reverse re-export). Both are retired by this work; the second one should not be recreated in any form. - Anything reading `http://127.0.0.1:18765` on the workstation. ## Scope and human gate In scope: create and register `rapp-issue-core`; transfer the runtime package; deploy it privately on railiance01; prove health, auth, Forgejo creation, failure, and rollback; cut optional REST consumers over; then remove the legacy bridge and tunnels. Out of scope: changing issue-core domain behaviour, adding Postgres, selecting Knative, granting public exposure, changing activity-core's fleet-wide default away from State Hub, or storing secret values in repo/Hub evidence. This is a yellow-lane production migration. T02/T03 are reversible file work. The T04 live cluster apply and the T05 removal of bridge/tunnel infrastructure require explicit implementation authorization; tunnel removal additionally requires the recorded direct-path 201 gate. ## T00 — Correct the historical topology record ```task id: ISSUE-WP-0007-T00 status: done priority: high state_hub_task_id: "a195fe94-0231-4dd8-b75e-94f50fec86bc" ``` **Correct the WP-0003 topology record.** Done 2026-08-19. WP-0003's own completion evidence shows that ArgoCD and the `issue-core` Deployment were on CoulombCore, not railiance01; railiance01 consumed the service through a two-hop bridge. Added a prominent correction to WP-0003 and recorded the finding in `history/2026-08-19-wp-0003-topology-correction.md`. This was a recording/topology error, not evidence of a railiance01 workload being pruned. ## T01 — Settle the packaging decisions ```task id: ISSUE-WP-0007-T01 status: done priority: high state_hub_task_id: "2ab2dd49-1c69-4f8e-a807-ce078d2899ce" ``` **Settle the packaging decisions.** Done 2026-08-19 against the current repo, the live rapp family, and `RailianceAppDeploymentGuide.md`: - primary rail: `rail-kubernetes` — this is a long-running private API; it has no request-activation, scale-to-zero, or revision-traffic requirement; - package type: `manifest-managed-platform-service` — absorb the current Kustomize manifests rather than inventing a Helm chart; - data classification: `confidential` — issue titles/bodies and mapping metadata may contain internal work context; - criticality: `high` — external tracker projection fails when it is down, but the file/State-Hub work-record spine continues; - database: **none for the production wrapper** — the production backend is Forgejo; SQLite is an optional offline/local backend, so do not pass `--dedicated-postgres` and remove any generated Postgres-consumer draft; - exposure: private; no Ingress or public grant. Reviewed scaffold command: ```bash UV_CACHE_DIR=/tmp/issue-core-rmgr-cache uv run --directory ../repo-manager \ rmgr rapp wrap --path ../rapp-issue-core --app issue-core \ --ownership-repo issue-core --from-app ../issue-core \ --rail rail-kubernetes \ --package-type manifest-managed-platform-service \ --classification confidential --criticality high \ --purpose "Package and operate the issue-core external-tracker connector on Railiance." ``` ## T02 — Scaffold and register rapp-issue-core ```task id: ISSUE-WP-0007-T02 status: done priority: high state_hub_task_id: "e0427de0-dfac-4848-ba2d-1421167eb704" ``` **Scaffold `rapp-issue-core` via `rmgr rapp wrap`,** then `rmgr rapp validate --path ../rapp-issue-core --family-root ..`. Remove the generated Postgres-consumer draft because this deployment uses Forgejo, then revalidate. Do not hand-author the baseline layout; the validator encodes the family conventions and will reject a bespoke tree. Register the new repo the normal file-first way and run its consistency check rather than creating Hub records by hand. ## T03 — Transfer the runtime package ```task id: ISSUE-WP-0007-T03 status: done priority: high state_hub_task_id: "d6e5e2ac-7f42-412f-8143-91531f5155e8" ``` **Port the runtime package.** Keep `Dockerfile`, `docker-entrypoint.sh`, source, tests, and `.forgejo/workflows/image.yaml` in `issue-core`; the app repo builds and publishes the image. Move `k8s/railiance/` into the wrapper's manifest shape, including the private Service, ServiceAccount, default-deny plus explicit NetworkPolicy, and OpenBao-backed secret references. Pin a Forgejo OCI image by immutable digest; do not reuse the retired Gitea registry tag as the release contract. Delete `issue-core/k8s/railiance/` and retire `docs/argocd-gitops.md` only after the wrapper contains an equivalent, validated runtime contract and retained handoff. Two active sources of deployment truth are not allowed. ## T04 — Place, deploy, and prove the direct service ```task id: ISSUE-WP-0007-T04 status: done priority: high state_hub_task_id: "21974fbd-4962-4732-b407-c531bb2ebfe6" ``` **Place, deploy, and prove it on railiance01.** Run `rmgr rapp place --path ../rapp-issue-core --reef reef-railiance --family-root ..`, then deploy through the wrapper's reviewed apply surface. Credential custody remains in OpenBao: route `ISSUE_CORE_API_KEY` through catalog `issue-core-ingestion-api-key` and route the Forgejo PAT through the concrete platform/OpenBao lane (never log or commit either value). Record secret-free live evidence in the wrapper for: rollout and Ready state; image digest match; `/healthz` 200 from inside the cluster; unauthenticated `POST /issues/` rejected; authenticated controlled `POST /issues/` returning **201 through `forgejo-inbox`**; dependency failure returning a bounded 503; NetworkPolicy presence; and rollback to the previous immutable digest. Follow the reversible `rapp-qonto/tools/verify_live.sh` evidence style. Evidence that the service answers only via an ops-bridge tunnel does not count. That is the condition being retired. ## T05 — Cut consumers over and retire tunnels ```task id: ISSUE-WP-0007-T05 status: done priority: high state_hub_task_id: "3fdbd9ab-3e3f-4ac0-8763-77278a1cc6e6" ``` **Cut consumers over, then retire the tunnels.** Point activity-core's optional REST `IssueSink` endpoint directly at the new in-cluster Service and remove its bridge Deployment/Service. Keep the fleet default `ISSUE_SINK_TYPE=state-hub`; `rest` remains an explicit external-projection opt-in, not a global cutover. Prove one controlled activity-core-shaped request returns 201 before asking ops-warden to update catalog id `activity-core-issue-sink`. Inventory any other consumers of workstation port `18765`. Only once they are cut over, ask ops-bridge to remove `issue-core-coulombcore` and `issue-core-railiance01` from `~/.config/bridge/tunnels.yaml`. Order matters: removing the tunnels first takes issue-core down for everyone still pointed at `127.0.0.1:18765`. ## T06 — Close out and notify dependants ```task id: ISSUE-WP-0007-T06 status: done priority: medium state_hub_task_id: "668e37dc-8ebe-46cc-9a27-905440b1240c" ``` **Close out.** Update `SCOPE.md`, retire the obsolete ArgoCD runbook, sync both repos' work records, and tell activity-core, ops-warden, and ops-bridge that the direct Service is authoritative and CoulombCore has one fewer dependency. Remaining CoulombCore services after this: `core-hub-staging` and `inter-hub`. ## Implementation order and gates 1. T02 may create/register the wrapper, but does not bind compute or apply. 2. T03 must leave a schema-valid, digest-pinned, secret-free runtime package. 3. T04 binds and deploys only after T03 validation and the OpenBao references are confirmed; its 201 smoke is the promotion gate. 4. T05 changes consumers only after T04; tunnel retirement is last. 5. T06 closes only after direct-path evidence and tunnel removal are recorded. ## Notes for whoever picks this up - ops-bridge tunnel map header (`~/.config/bridge/tunnels.yaml`) lists what is left on CoulombCore and why. - railiance01 cluster access: `kubectl --kubeconfig ~/.kube/config-railiance01` (tunnel `k3s-api-railiance01`, local `16444`). There is no CoulombCore kubeconfig any more — its `k3s-api` tunnel was removed on 2026-08-19, so inspecting the *current* deployment needs that tunnel restored first. - Reviewed 2026-08-19 in issue-core and promoted from `proposed` to `ready`. ## Completion evidence — 2026-08-19 - Created, registered, validated, and published private repo `coulomb/rapp-issue-core`; wrapper commit `3003c92`, State Hub workstream `b4c39759-6073-4558-a70c-2f869a31cfca`. - Fixed the Forgejo lifecycle identifier contract in issue-core commit `3523a7d`; published and deployed immutable digest `sha256:a56c80ccbd1690bc0bcea384165b1967704f0fdbaf1c506ce1082f56190b92cf`. - Full wrapper evidence passed: health 200, unauthenticated 401, authenticated create 201 + close 200, bounded dependency 503, recovery 201 + close 200, ExternalSecret ready with two keys, both NetworkPolicies present, previous digest rollback health 200, and restored-digest re-verification. - activity-core commit `213e810` now uses `http://issue-core.issue-core.svc.cluster.local:8765` directly while keeping `ISSUE_SINK_TYPE=state-hub`; a controlled worker-side request returned create 201 and close 200. - Deleted the legacy activity-core bridge resources, stopped and removed both port-18765 ops-bridge entries, and confirmed the local listener was gone. - Retired this repo's duplicate `k8s/railiance/` source and obsolete ArgoCD runbook after the wrapper was pushed and its evidence retained.