--- id: ISSUE-WP-0003 type: workplan title: "Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)" domain: infotech repo: issue-core status: active owner: claude topic_slug: custodian created: "2026-06-19" updated: "2026-06-19" state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570" --- # Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot) `issue-core` is the authoritative task-lifecycle manager and the REST ingestion target for activity-core's `IssueSink`. Deployment artifacts are on `main` (`Dockerfile`, `docker-entrypoint.sh`, `k8s/railiance/`); image `gitea.coulomb.social/coulomb/issue-core:0.2.0` is built, pushed, and pullable. The railiance01 cluster still has no `issue-core` workload until T02 live ArgoCD bootstrap (RAILIANCE-WP-0004-T05) and T04 OpenBao secrets land. This workplan stands up `issue-core` as a first-class in-cluster service on railiance01 **via ArgoCD GitOps** — making issue-core the cluster's first declarative Application and turning on the idle GitOps capability. ## Current state (verified 2026-06-19) - **Deployment artifacts in-repo:** `Dockerfile`, `docker-entrypoint.sh`, and `k8s/railiance/` (Kustomize: ExternalSecret, ConfigMap, Deployment, Service). Image builds locally; `docker run` + `GET /healthz` returns 200. Image pushed and pullable as `gitea.coulomb.social/coulomb/issue-core:0.2.0` (digest `sha256:153fbe43…`). `coulomb` org packages are public — no `imagePullSecret` required per `railiance-forge/docs/gitea-container-registry.md`. - **Dockerfile fix (2026-06-19):** build arg renamed `GITEA_PYPI_INDEX_URL` — `ARG PIP_INDEX_URL` leaked into the build env and pip used Gitea as the sole index, so dependencies like `click` were not found. - **railiance01 cluster:** no `issue-core` namespace; no issue-core Deployment/Service/Pod in any namespace. - **Dangling reference:** `activity-core/k8s/railiance/20-runtime.yaml` sets `ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8010` — a service that does not exist, on the **wrong port** (issue-core serves 8765) — with `ISSUE_SINK_TYPE: "null"` so emission is disabled. It is a placeholder. - **Packaging precursor is done:** `ISSUE-WP-0002` published `issue-core==0.2.0` to the Coulomb Gitea PyPI index. - **ArgoCD is installed but unused:** all 7 components healthy (~290d), but **0 Applications, 0 ApplicationSets, 0 registered git repos**, only the stock `default` AppProject. No `kind: Application` manifests exist in any infra repo. - **Existing deploy pattern is imperative** (the path we are *replacing* for this service): local `docker build` → `k3s ctr images import` (side-load, no registry) → `rsync` manifests → `kubectl apply` (see `activity-core/k8s/railiance/README.md`). ## Decisions - **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19). issue-core is the pilot Application; the imperative side-load pattern is not used for this service. - **ArgoCD bootstrap owned by `railiance-platform`** (operator decision 2026-06-19). Platform owns repo registration, AppProject/app-of-apps conventions, and the External-Secrets/OpenBao plumbing. issue-core only **contributes** its `Application` manifest + workload manifests into the agreed GitOps source. T02 is therefore a cross-repo dependency, not issue-core work — see handoff to railiance-platform. - **Backend = cluster Gitea (markitect)** (operator decision 2026-06-19). Ingested tasks route to the existing Gitea backend; no new Postgres/PVC. - **Secret management = OpenBao.** `ISSUE_CORE_API_KEY` is a shared ingestion key injected from OpenBao on both issue-core and the activity-core worker. ops-warden does **not** vend it (see `~/ops-warden/wiki/playbooks/activity-core-issue-sink.md`). Coordinate the canonical path with `railiance-platform` (`issue-core-ingestion-api-key`). - **Image delivery = container registry, not side-load.** GitOps requires a pullable image tag in a registry the cluster can reach (the Coulomb Gitea container registry); side-loading defeats declarative reproducibility. ## Open questions - **GitOps source repo.** Resolved by `railiance-platform` as part of the bootstrap (T02 dependency): where issue-core's `Application` + manifests are expected to live (its own `issue-core/k8s/` vs. a platform GitOps repo) and the AppProject/app-of-apps convention to follow. - **Registry path & pull secret.** Resolved: `gitea.coulomb.social/coulomb/issue-core:`; public org packages need no pull secret (see `railiance-forge` container-registry docs). --- ## Container image published to a pullable registry ```task id: ISSUE-WP-0003-T01 status: done priority: high state_hub_task_id: "3723e896-3ec9-49b8-86f8-403993444da3" ``` **Goal.** A reproducible, registry-hosted image ArgoCD-managed pods can pull. - [x] Add `Dockerfile` installing `issue-core[api]>=0.2,<0.3` from the Gitea PyPI index (with explicit PyPI primary index). Entrypoint renders `backends.json` then `issue serve --host 0.0.0.0 --port 8765`. - [x] Local build succeeds; `docker run` + `GET /healthz` returns 200. - [x] Pushed `gitea.coulomb.social/coulomb/issue-core:0.2.0`; `docker pull` succeeds. - [x] No cluster pull secret needed (`coulomb` org packages are public). - [ ] `POST /issues/` smoke against a running deployment (deferred to T03/T04 cluster verification). ## ArgoCD bootstrap (railiance-platform dependency) + issue-core Application ```task id: ISSUE-WP-0003-T02 status: wait priority: high state_hub_task_id: "9b199b1d-d3c0-4621-b8f8-58c376cbf878" ``` **Owner split.** ArgoCD bootstrap is **railiance-platform's** (operator decision 2026-06-19): repo registration in ArgoCD, AppProject/app-of-apps convention, and the agreed GitOps source layout. This task is `wait` on that handoff. issue-core's part is to **contribute** the `Application` manifest + workload manifests into the layout platform defines. - **(railiance-platform)** Register the GitOps source repo (repository Secret + creds); define AppProject for cluster services; publish the source-repo/path convention and sync policy. - [x] **(issue-core)** Workload manifests in `k8s/railiance/` on `main` per platform contract (`docs/argocd-gitops.md`). Tenant `Application` lives in `railiance-platform/argocd/applications/issue-core.application.yaml`. - [ ] **(railiance-platform)** RAILIANCE-WP-0004-T05 live bootstrap: register repo creds, deploy bootstrap, sync `issue-core` Application. - [ ] Verify: `kubectl get applications -n argocd` shows `issue-core` Synced/Healthy; ArgoCD reconciles a trivial manifest change. ## Kubernetes manifests (namespace, Deployment, Service) in GitOps source ```task id: ISSUE-WP-0003-T03 status: progress priority: high state_hub_task_id: "38887dd6-0988-4ad1-bc6b-2a1b8839829f" ``` **Goal.** Declarative manifests in the GitOps source repo, synced by T02. - [x] `k8s/railiance/` Kustomize bundle (namespace via ArgoCD `CreateNamespace=true`). - [x] Deployment: registry image tag `0.2.0`; port 8765; `/healthz` probes; resource requests/limits; env from ExternalSecret (T04) and ConfigMap (T05). - [x] Service: ClusterIP on **8765** as `issue-core.issue-core.svc.cluster.local`. - [ ] Verify: ArgoCD syncs the manifests; Pod Ready; `/healthz` 200 from a debug pod (blocked on T01 push + T02 bootstrap + T04 secrets). ## OpenBao secret: ISSUE_CORE_API_KEY ```task id: ISSUE-WP-0003-T04 status: todo priority: high state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad" ``` **Goal.** The shared ingestion key delivered to both sides from OpenBao. - Provision `ISSUE_CORE_API_KEY` in OpenBao at the canonical path (coordinate with `railiance-platform`; catalog id `issue-core-ingestion-api-key`). - Deliver into the issue-core Deployment (T03) and the activity-core worker (T06) with the **same** value (External Secrets / Bao injector — match the cluster's established mechanism). - Never write the value to Git, manifests, State Hub, or logs. - Verify: both pods resolve a non-empty key; auth round-trip (401 without, 201 with). ## In-cluster backend config (cluster Gitea / markitect) ```task id: ISSUE-WP-0003-T05 status: progress priority: medium state_hub_task_id: "10923f1e-050d-4f3e-980e-b061fef5f33a" ``` **Goal.** issue-core's `backends.json` inside the cluster points `default` at the cluster Gitea (markitect) backend. - [x] ConfigMap `issue-core-backends` with in-cluster Gitea URL (`gitea-http.default.svc.cluster.local:3000`); token sentinel `__FROM_ENV__`. - [x] `docker-entrypoint.sh` renders `~/.config/issue-tracker/backends.json` from `BACKENDS_TEMPLATE` + `GITEA_BACKEND_TOKEN` at startup. - [ ] Verify: a `POST /issues/` creates a real Gitea issue and returns `issue_url` (blocked on T04 secrets + in-cluster deployment). ## Wire activity-core to the live service ```task id: ISSUE-WP-0003-T06 status: todo priority: high state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" ``` **Goal.** activity-core emits to the live issue-core Service. - Fix `activity-core/k8s/railiance/20-runtime.yaml`: `ISSUE_CORE_URL` port `8010 -> 8765`; flip `ISSUE_SINK_TYPE` `null -> rest` once issue-core is Ready. - Inject `ISSUE_CORE_API_KEY` into the activity-core worker from the same OpenBao secret (T04). - **Contract gap:** issue-core requires `triggering_event_id` as a UUID; activity-core cron paths may send non-UUID keys (e.g. `"scheduled"`). Event-driven emission with real event UUIDs works (the `str()` guard in `issue_sink.py`, commit f05c56e, handles UUID objects). Align schemas before enabling `rest` for cron-triggered rules. - Verify: an activity-core run emits a task that lands in cluster Gitea via issue-core. ## End-to-end verification + GitOps runbook ```task id: ISSUE-WP-0003-T07 status: todo priority: medium state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" ``` **Goal.** Confirm the deployed service is healthy and document the new path. - ArgoCD Application Synced/Healthy; issue-core Pod Ready; Service reachable cluster-internal. - activity-core → issue-core emission returns 201 and creates a Gitea issue. - Document the GitOps runbook (image build/push, ArgoCD sync, secret rotation, rollback) in `docs/`. - Emit an `add_progress_event` milestone to the hub on completion. --- ## See also - `ISSUE-WP-0002` — Gitea PyPI publication (packaging precursor, finished). - `railiance-apps-WP-0004` I03 — issue-core packaging/image enablement notes. - `railiance-forge` — Gitea container registry docs. - `activity-core/docs/issue-core-emission-boundary.md` — emission contract. - `activity-core/k8s/railiance/README.md` — the imperative pattern being superseded for this service. - `~/ops-warden/wiki/playbooks/activity-core-issue-sink.md` — key routing.