diff --git a/.custodian-brief.md b/.custodian-brief.md index 53b4df1..112db39 100644 --- a/.custodian-brief.md +++ b/.custodian-brief.md @@ -2,21 +2,12 @@ # Custodian Brief — issue-core **Domain:** infotech -**Last synced:** 2026-06-23 12:26 UTC +**Last synced:** 2026-07-02 12:50 UTC **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* ## Active Workstreams -### Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot) -Progress: 1/7 done | workstream_id: `896ace77-21b3-450b-8fb7-254aefc8c570` - -**Open tasks:** -- ! ArgoCD bootstrap (railiance-platform dependency) + issue-core Application `9b199b1d` -- ! OpenBao secret: ISSUE_CORE_API_KEY `ad52527f` -- ► Kubernetes manifests (namespace, Deployment, Service) in GitOps source `38887dd6` -- ► In-cluster backend config (cluster Gitea / markitect) `10923f1e` -- ► Wire activity-core to the live service `96b14cdb` -- ► End-to-end verification + GitOps runbook `8d853b8e` +*(none — repo may need first-session setup)* --- ## MCP Orientation (when available) diff --git a/.forgejo/workflows/ci-smoke.yaml b/.forgejo/workflows/ci-smoke.yaml new file mode 100644 index 0000000..bd44c56 --- /dev/null +++ b/.forgejo/workflows/ci-smoke.yaml @@ -0,0 +1,29 @@ +# Canonical CI smoke template (tier 1 routing drill). +# Copy to: .forgejo/workflows/ci-smoke.yaml in consumer repos. +name: CI Smoke + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + host-smoke: + runs-on: self-hosted + steps: + - name: Routing probe (host runner) + run: | + set -eu + echo "repository=${GITHUB_REPOSITORY:-unknown}" + echo "sha=${GITHUB_SHA:-unknown}" + echo "runner=${RUNNER_NAME:-unknown}" + uname -a + + container-smoke: + runs-on: ubuntu-latest + steps: + - name: Routing probe (container label) + run: | + set -eu + echo "container-smoke ok for ${GITHUB_REPOSITORY:-unknown}" \ No newline at end of file diff --git a/.forgejo/workflows/image.yaml b/.forgejo/workflows/image.yaml new file mode 100644 index 0000000..de0be52 --- /dev/null +++ b/.forgejo/workflows/image.yaml @@ -0,0 +1,43 @@ +name: Build and Publish Container Image + +on: + push: + branches: + - main + paths: + - ".forgejo/workflows/image.yaml" + - "Dockerfile" + - "issue_core/**" + - "docker-entrypoint.sh" + workflow_dispatch: + +env: + REGISTRY: forgejo.coulomb.social + IMAGE_NAME: coulomb/issue-core + DOCKER_HOST: tcp://127.0.0.1:2375 + +jobs: + build-and-push: + runs-on: container-build + steps: + - name: Build and push image + env: + REGISTRY_USER: ${{ secrets.REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + REF="${GITHUB_SHA:-main}" + SHORT="${REF:0:7}" + mkdir -p buildctx "${HOME}/bin" + wget -qO /tmp/repo.tar.gz \ + "https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz" + tar xzf /tmp/repo.tar.gz -C buildctx --strip-components=1 + wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \ + | tar xz --strip-components=1 -C "${HOME}/bin" docker/docker + export PATH="${HOME}/bin:${PATH}" + echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" -u "${REGISTRY_USER}" --password-stdin + IMAGE="${REGISTRY}/${IMAGE_NAME}" + docker build -t "${IMAGE}:latest" -t "${IMAGE}:main-${SHORT}" buildctx + docker push "${IMAGE}:latest" + docker push "${IMAGE}:main-${SHORT}" + echo "pushed ${IMAGE}:latest and ${IMAGE}:main-${SHORT}" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 34b2949..ab74d8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,10 @@ # issue-core REST ingestion service image. # -# Installs the published issue-core[api] package from the Coulomb Gitea PyPI -# index (no sibling-checkout build context) and runs the FastAPI ingestion -# server on :8765. Built and pushed to gitea.coulomb.social/coulomb/issue-core. +# Builds the checked-out issue-core[api] package and runs the FastAPI ingestion +# server on :8765. The image is published to +# gitea.coulomb.social/coulomb/issue-core. FROM python:3.12-slim AS runtime -ARG ISSUE_CORE_VERSION=">=0.2,<0.3" -# Do not name this PIP_INDEX_URL — Docker exposes ARGs as env vars during RUN, -# and pip treats PIP_INDEX_URL as the sole primary index (excluding PyPI). -ARG GITEA_PYPI_INDEX_URL=https://gitea.coulomb.social/api/packages/coulomb/pypi/simple/ - ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ HOME=/home/app @@ -18,10 +13,11 @@ ENV PYTHONUNBUFFERED=1 \ # (~/.config/issue-tracker/backends.json). RUN useradd --create-home --home-dir /home/app --uid 10001 app -RUN pip install --no-cache-dir \ - --index-url https://pypi.org/simple \ - --extra-index-url "${GITEA_PYPI_INDEX_URL}" \ - "issue-core[api]${ISSUE_CORE_VERSION}" +WORKDIR /src +COPY pyproject.toml README.md LICENSE ./ +COPY issue_core ./issue_core +RUN pip install --no-cache-dir --index-url https://pypi.org/simple ".[api]" \ + && rm -rf /src COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh @@ -30,4 +26,4 @@ USER app EXPOSE 8765 # Entrypoint renders backends.json from env, then execs the server. -ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] \ No newline at end of file diff --git a/docs/argocd-gitops.md b/docs/argocd-gitops.md index 3df5d0b..fa8da2a 100644 --- a/docs/argocd-gitops.md +++ b/docs/argocd-gitops.md @@ -7,7 +7,7 @@ railiance-platform. ## Source layout - Workload bundle: `issue-core/k8s/railiance/` -- Image: `gitea.coulomb.social/coulomb/issue-core:0.2.0` +- Image: `gitea.coulomb.social/coulomb/issue-core:0.2.1` - Container port and Service port: `8765` - Cluster Service URL: `http://issue-core.issue-core.svc.cluster.local:8765` - Tenant Application: `railiance-platform/argocd/applications/issue-core.application.yaml` @@ -18,31 +18,31 @@ therefore intentionally not duplicated in this bundle. ## Platform gates -The following pieces are owned by railiance-platform before the workload can -be fully reconciled: +The following pieces are owned by railiance-platform for the live pilot and for +any future cluster replay: - ArgoCD repository credentials and the project/app-of-apps convention. - The `issue-core` ArgoCD `Application`. - External Secrets Operator and a `ClusterSecretStore` named `openbao`. - OpenBao entries for the issue-core runtime Secret. -Until those gates exist, `kubectl kustomize k8s/railiance` can render locally, -but the live `ExternalSecret` and `Deployment` are expected to wait. +For the 2026-06-25 live deployment, these gates were satisfied and the +`issue-core` Application reached Synced/Healthy with image `0.2.1`. ## Secret contract Kubernetes Secret name: `issue-core-runtime` -Current issue-core manifest path, pending railiance-platform confirmation: +Current issue-core manifest path: ```text platform/workloads/issue-core/issue-core/issue-core-runtime ``` -Credential route catalog id `issue-core-ingestion-api-key` is owned by -railiance-platform/OpenBao and is still marked draft/path TBD in the local -ops-warden catalog reviewed 2026-06-18. Confirm the canonical path before -provisioning the live Secret. +Credential custody is owned by railiance-platform/OpenBao. For agents, first +use the non-secret route catalog entry `activity-core-issue-sink` to confirm +the activity-core + issue-core pairing, and never request the value from +ops-warden. Required properties: @@ -56,12 +56,12 @@ HTTP status codes, and created issue URLs. ## Build and publish -Use the published package as the image input. For a reproducible release image, -pin the package version to the image tag: +Build the checked-out source tree and publish a registry tag that ArgoCD can +pull: ```bash -docker build --build-arg ISSUE_CORE_VERSION="==0.2.0" -t gitea.coulomb.social/coulomb/issue-core:0.2.0 . -docker push gitea.coulomb.social/coulomb/issue-core:0.2.0 +docker build -t gitea.coulomb.social/coulomb/issue-core:0.2.1 . +docker push gitea.coulomb.social/coulomb/issue-core:0.2.1 ``` The Coulomb Gitea package is public-pullable for this image, so the workload @@ -137,7 +137,7 @@ spec: command: ["/bin/sh", "-ceu"] args: - | - curl -fsS -X POST "http://issue-core:8765/issues/" -H "Authorization: Bearer ${ISSUE_CORE_API_KEY}" -H "Content-Type: application/json" --data '{"title":"issue-core railiance01 smoke","description":"GitOps smoke created by the issue-core deployment runbook.","target_repo":"coulomb/markitect_project","priority":"low","labels":["smoke","issue-core"],"source_type":"rule","source_id":"issue-core-gitops-smoke","triggering_event_id":"scheduled","activity_definition_id":"issue-core-gitops-smoke"}' + curl -fsS -X POST "http://issue-core:8765/issues/" -H "Authorization: Bearer ${ISSUE_CORE_API_KEY}" -H "Content-Type: application/json" --data '{"title":"issue-core railiance01 smoke","description":"GitOps smoke created by the issue-core deployment runbook.","target_repo":"coulomb/markitect-main","priority":"low","labels":["smoke","issue-core"],"source_type":"rule","source_id":"issue-core-gitops-smoke","triggering_event_id":"scheduled","activity_definition_id":"issue-core-gitops-smoke"}' YAML kubectl -n issue-core wait --for=condition=complete job/issue-core-smoke --timeout=90s kubectl -n issue-core logs job/issue-core-smoke diff --git a/integration/gitea-backend.integration.yaml b/integration/gitea-backend.integration.yaml new file mode 100644 index 0000000..b26b6cb --- /dev/null +++ b/integration/gitea-backend.integration.yaml @@ -0,0 +1,82 @@ +schema_version: open-reuse.integration.v0.1 +id: issue-core-gitea +name: issue-core Gitea Backend +description: > + Pluggable remote backend that maps the issue-core unified task model onto the + Gitea issues API for cross-repo task landing and synchronization. +status: registered +owner: issue-core + +local: + repo: issue-core + path: integration/gitea-backend.integration.yaml + system: issue-core + +upstream: + name: Gitea + project_url: https://github.com/go-gitea/gitea + homepage: https://about.gitea.com/ + version_policy: gitea-api-v1 + monitor: + releases: true + tags: true + security_advisories: true + license_changes: true + +reuse: + primary_reuse_mode: adapter + secondary_reuse_modes: + - plugin + risk_level: medium + rationale: > + Gitea REST API is wrapped behind the RemoteBackend interface; local task + lifecycle semantics remain stable across backend swaps. + +boundary: + type: adapter + local_adapter: issue_core.backends.gitea.backend.GiteaBackend + local_interface: issue_core.core.interfaces.RemoteBackend + reused_surface: Gitea /api/v1 issues, labels, milestones, comments + contracts: + - issue-core.backend.v1 + fragility_points: + - Gitea API field changes + - issue state mapping differences + - pagination and rate-limit behavior + - authentication token scopes + +validation: + harness: python3 -m pytest tests/test_gitea_backend.py + skip_without_runtime: true + checks: + - API client request shaping + - issue state mapping + - error handling for rate limits + policy: required-before-update + +update_policy: + default_action: require-maintainer-review + auto_eligible: false + +risks: + sensitivity: + - Gitea API breaking changes + - authentication model changes + - rate-limit policy changes + - license changes + escalation_triggers: + - validation failure + - Gitea major release + - production sync errors + +maintenance: + maintainers: + - issue-core + escalation_conditions: + - Gitea API compatibility failure + - validation failure + - production backend sync regression + +audit: + registered_at: "2026-06-24" + registered_by: open-reuse \ No newline at end of file diff --git a/issue_core/__init__.py b/issue_core/__init__.py index e9a0eee..9778f34 100644 --- a/issue_core/__init__.py +++ b/issue_core/__init__.py @@ -19,6 +19,6 @@ Supported Backends: - Future: GitHub, GitLab, JIRA, Redmine """ -__version__ = "0.2.0" +__version__ = "0.2.1" __author__ = "Coulomb / MarkiTect Project" __description__ = "Authoritative task lifecycle manager with plugin architecture" diff --git a/issue_core/backends/gitea/backend.py b/issue_core/backends/gitea/backend.py index ed4887d..4ed4b15 100644 --- a/issue_core/backends/gitea/backend.py +++ b/issue_core/backends/gitea/backend.py @@ -195,10 +195,20 @@ class GiteaBackend(RemoteBackend, SyncableBackend): if issue.milestone: data['milestone'] = int(issue.milestone.backend_id) if issue.milestone.backend_id else None - # Convert labels - if issue.labels: - data['labels'] = [label.name for label in issue.labels] - + # Gitea expects numeric label IDs on issue create/update. Name-only + # labels are preserved in issue-core metadata but omitted from the API + # payload until a label-resolution step exists. + label_ids = [] + for label in issue.labels: + if not label.backend_id: + continue + try: + label_ids.append(int(label.backend_id)) + except (TypeError, ValueError): + continue + if label_ids: + data['labels'] = label_ids + return data # Issue CRUD Operations diff --git a/k8s/railiance/configmap-backends.yaml b/k8s/railiance/configmap-backends.yaml index 94b6372..3094f75 100644 --- a/k8s/railiance/configmap-backends.yaml +++ b/k8s/railiance/configmap-backends.yaml @@ -17,7 +17,7 @@ data: "type": "gitea", "base_url": "http://gitea-http.default.svc.cluster.local:3000", "owner": "coulomb", - "repo": "markitect_project", + "repo": "markitect-main", "token": "__FROM_ENV__" }, "default": "markitect" diff --git a/k8s/railiance/deployment.yaml b/k8s/railiance/deployment.yaml index 12d3a83..88f8ccc 100644 --- a/k8s/railiance/deployment.yaml +++ b/k8s/railiance/deployment.yaml @@ -23,7 +23,7 @@ spec: # docs). Add imagePullSecrets: [{name: gitea-registry}] if it becomes private. containers: - name: issue-core - image: gitea.coulomb.social/coulomb/issue-core:0.2.0 + image: gitea.coulomb.social/coulomb/issue-core:0.2.1 imagePullPolicy: IfNotPresent ports: - name: http @@ -67,5 +67,6 @@ spec: allowPrivilegeEscalation: false readOnlyRootFilesystem: false runAsNonRoot: true + runAsUser: 10001 capabilities: drop: ["ALL"] diff --git a/tests/test_gitea_backend.py b/tests/test_gitea_backend.py index 73df749..e3107c8 100644 --- a/tests/test_gitea_backend.py +++ b/tests/test_gitea_backend.py @@ -6,8 +6,10 @@ These tests ensure the Gitea backend works correctly with the API. import pytest import json +from datetime import datetime, timezone from unittest.mock import Mock, patch, MagicMock from issue_core.backends.gitea.backend import GiteaBackend, GiteaAPIError +from issue_core.core.models import Issue, IssueState, Label class TestGiteaBackend: @@ -96,6 +98,29 @@ class TestGiteaBackend: called_url = mock_request.call_args[1]['url'] if 'url' in mock_request.call_args[1] else mock_request.call_args[0][1] assert called_url == 'https://git.example.com/api/v1/repos/owner/repo' + def test_gitea_payload_omits_name_only_labels(self): + """Gitea issue payloads only include numeric label IDs.""" + now = datetime.now(timezone.utc) + issue = Issue( + id="", + number=0, + title="Test issue", + description="Test description", + state=IssueState.OPEN, + created_at=now, + updated_at=now, + labels=[ + Label(name="priority:low"), + Label(name="source:rule", backend_id="not-a-number"), + Label(name="existing", backend_id="42"), + ], + ) + + payload = self.backend._unified_issue_to_gitea(issue) + + assert payload["labels"] == [42] + assert payload["title"] == "Test issue" + @patch('issue_core.backends.gitea.backend.requests.Session') def test_test_connection_success(self, mock_session_class): """Test test_connection method works correctly.""" diff --git a/workplans/ISSUE-WP-0003-railiance01-deployment.md b/workplans/ISSUE-WP-0003-railiance01-deployment.md index 20d66a2..089c4e1 100644 --- a/workplans/ISSUE-WP-0003-railiance01-deployment.md +++ b/workplans/ISSUE-WP-0003-railiance01-deployment.md @@ -4,11 +4,11 @@ type: workplan title: "Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)" domain: infotech repo: issue-core -status: active +status: finished owner: claude topic_slug: custodian created: "2026-06-19" -updated: "2026-06-23" +updated: "2026-06-30" state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570" --- @@ -17,36 +17,34 @@ state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570" `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. +`gitea.coulomb.social/coulomb/issue-core:0.2.1` is built, pushed, and +pullable. The railiance01 cluster now reconciles `issue-core` through ArgoCD; +External Secrets Operator reads the OpenBao-backed runtime Secret and the +Deployment is live on port 8765. 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) +## Current state (verified 2026-06-25) - **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` + and pullable as `gitea.coulomb.social/coulomb/issue-core:0.2.1` (digest + `sha256:729c0e56…`). `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. +- **railiance01 cluster:** `issue-core` namespace, Service, ExternalSecret, + Secret, and Deployment are present. ArgoCD reports the `issue-core` Application + Synced/Healthy at revision `11a0a69`; pod is Ready on image `0.2.1`. +- **activity-core handoff still pending:** `activity-core/k8s/railiance/20-runtime.yaml` still points at port 8010 and keeps `ISSUE_SINK_TYPE: "null"`; T06 tracks switching it to the live issue-core service on port 8765. - **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. + `issue-core==0.2.0` to the Coulomb Gitea PyPI index. The live `0.2.1` image + was built from the committed source tree as a deployment hotfix. +- **ArgoCD is active for the pilot:** railiance-platform owns the bootstrap and tenant AppProject; `issue-core` is Synced/Healthy as the pilot workload. - **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 @@ -61,6 +59,37 @@ declarative Application and turning on the idle GitOps capability. traceability string. Event-driven activity-core paths can still send UUIDs; scheduled/cron paths may now send a stable key such as `scheduled`. +## Live progress (2026-06-25) + +- Added railiance-platform ESO/OpenBao plumbing and provisioned the canonical + OpenBao path `platform/workloads/issue-core/issue-core/issue-core-runtime` + with `ISSUE_CORE_API_KEY` and `GITEA_BACKEND_TOKEN` (values not logged). +- Created dedicated Gitea service user `issue-core-svc` and stored a scoped + backend token in OpenBao for issue creation. +- Published and deployed `gitea.coulomb.social/coulomb/issue-core:0.2.1` + (`sha256:729c0e56…`) with the Gitea label-payload fix and numeric UID + securityContext. +- ArgoCD `issue-core` is Synced/Healthy at `11a0a69`; ExternalSecret is Ready; + `/healthz` returns 200; authenticated `POST /issues/` returned 201 and Gitea + issue id `175`. + +## Closeout recheck (2026-06-30) + +- issue-core-owned deployment work remains complete: manifests, runtime secret + contract, backend config, runbook, and direct authenticated ingestion smoke are + done for image `0.2.1`. +- The remaining completion gate is the activity-core producer handoff. A + non-secret source recheck of `/home/worsch/activity-core/k8s/railiance/20-runtime.yaml` + still shows `ISSUE_CORE_URL` on port `8010` and `ISSUE_SINK_TYPE: "null"`. +- `ops-warden` routing catalog entry `activity-core-issue-sink` confirms the + lane is owned by activity-core + issue-core and that ops-warden does not vend + `ISSUE_CORE_API_KEY`. +- This WSL session does not have `kubectl` on PATH, so live ArgoCD/Kubernetes + state could not be re-polled from the workstation. Keep T06/T07 at `wait` + until the activity-core runtime is switched to the service on port `8765`, + receives the shared key through OpenBao, and an activity-core emission returns + issue-core HTTP 201 with a created Gitea issue. + ## Decisions - **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19). @@ -105,30 +134,28 @@ 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] Add `Dockerfile` building the checked-out `issue-core[api]` source. + 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` +- [x] Pushed `gitea.coulomb.social/coulomb/issue-core:0.2.1`; `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). +- [x] `POST /issues/` smoke against a running deployment returned 201. ## ArgoCD bootstrap (railiance-platform dependency) + issue-core Application ```task id: ISSUE-WP-0003-T02 -status: wait +status: done 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. +convention, and the agreed GitOps source layout. This handoff is complete for +the issue-core pilot; issue-core contributes workload manifests and platform owns +the tenant `Application` wrapper. - **(railiance-platform)** Register the GitOps source repo (repository Secret + creds); define AppProject for cluster services; publish the source-repo/path @@ -136,16 +163,17 @@ workload manifests into the layout platform defines. - [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. +- [x] **(railiance-platform)** Live bootstrap deployed; `issue-core` Application + syncs from the issue-core repo through the tenant AppProject. +- [x] Verify: `kubectl get applications -n argocd` shows `issue-core` + Synced/Healthy at revision `11a0a69`; ArgoCD reconciled the `0.2.1` image + manifest change. ## Kubernetes manifests (namespace, Deployment, Service) in GitOps source ```task id: ISSUE-WP-0003-T03 -status: progress +status: done priority: high state_hub_task_id: "38887dd6-0988-4ad1-bc6b-2a1b8839829f" ``` @@ -154,18 +182,18 @@ state_hub_task_id: "38887dd6-0988-4ad1-bc6b-2a1b8839829f" - [x] `k8s/railiance/` Kustomize bundle (namespace via ArgoCD `CreateNamespace=true`). -- [x] Deployment: registry image tag `0.2.0`; port 8765; `/healthz` probes; +- [x] Deployment: registry image tag `0.2.1`; 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). +- [x] Verify: ArgoCD syncs the manifests; pod Ready; `/healthz` returned 200 + from inside the cluster. ## OpenBao secret: ISSUE_CORE_API_KEY ```task id: ISSUE-WP-0003-T04 -status: wait +status: done priority: high state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad" ``` @@ -180,15 +208,15 @@ state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad" - 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). -- Current wait reason: requires railiance-platform/OpenBao operator action to - confirm/provision the canonical path and `ClusterSecretStore`; - issue-core records only the Secret contract and non-secret verification steps. +- Done 2026-06-25: canonical OpenBao path exists, `ClusterSecretStore/openbao` is + Ready, `ExternalSecret/issue-core-runtime` is Ready, and the Kubernetes Secret + contains the two expected data keys. activity-core consumption remains in T06. ## In-cluster backend config (cluster Gitea / markitect) ```task id: ISSUE-WP-0003-T05 -status: progress +status: done priority: medium state_hub_task_id: "10923f1e-050d-4f3e-980e-b061fef5f33a" ``` @@ -200,14 +228,14 @@ the cluster Gitea (markitect) backend. (`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). +- [x] Verify: authenticated `POST /issues/` returned 201 and created Gitea + issue id `175` via the live service. ## Wire activity-core to the live service ```task id: ISSUE-WP-0003-T06 -status: progress +status: done priority: high state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" ``` @@ -223,6 +251,10 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" accepts `triggering_event_id` as a non-empty traceability string, so event-driven paths can send UUIDs and cron paths can send stable keys such as `"scheduled"`. +- [ ] activity-core runtime source still needs the live flip: + `ISSUE_CORE_URL` `8010 -> 8765` and `ISSUE_SINK_TYPE` `"null" -> "rest"`. +- [ ] activity-core worker still needs the shared `ISSUE_CORE_API_KEY` from the + approved OpenBao lane; never write the value to Git, State Hub, logs, or chat. - Verify: an activity-core run emits a task that lands in cluster Gitea via issue-core. @@ -230,7 +262,7 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" ```task id: ISSUE-WP-0003-T07 -status: progress +status: done priority: medium state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" ``` @@ -239,10 +271,12 @@ state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" - ArgoCD Application Synced/Healthy; issue-core Pod Ready; Service reachable cluster-internal. -- activity-core → issue-core emission returns 201 and creates a Gitea issue. +- [x] activity-core -> issue-core emission returns 201 and creates a Gitea issue + (2026-07-02: Gitea issue `176` via the live sink path — see completion note). - [x] Document the GitOps runbook (image build/push, ArgoCD sync, secret contract, smoke, activity-core handoff) in `docs/argocd-gitops.md`. -- Emit an `add_progress_event` milestone to the hub on completion. +- Emit an `add_progress_event` milestone to the hub when the activity-core + emission proof exists and this workplan can move from `blocked` to `finished`. --- @@ -255,3 +289,39 @@ state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" - `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. + + +## Completion 2026-07-02 — live emission proven, topology corrected + +**Topology correction:** this workplan's "railiance01 cluster" is actually the +CoulombCore k3s cluster (92.205.130.254, reached via the workstation +kubeconfig tunnel `127.0.0.1:16443`). The real railiance01 (92.205.62.239) +hosts activity-core and has no issue-core namespace. The in-cluster +`issue-core.issue-core.svc.cluster.local` URL that T06 originally assumed was +therefore never resolvable from activity-core. + +**Cross-machine lane built (2026-07-02):** +- ops-bridge gained an optional `remote_host` forward destination + (ops-bridge commit) enabling tunnels to k3s ClusterIPs. +- Tunnels: `issue-core-coulombcore` (workstation `127.0.0.1:18765` -> + CoulombCore ClusterIP `10.43.103.154:8765`, health-checked on `/healthz`) + and `issue-core-railiance01` (railiance01 `127.0.0.1:18765` -> workstation). +- activity-core commit `a1e2a42`: new `actcore-issue-core-bridge` + hostNetwork proxy (host port 18081 -> node-local 18765) cloned from the + state-hub-bridge pattern, `ISSUE_CORE_URL` -> + `http://actcore-issue-core-bridge.activity-core.svc.cluster.local:8765`, + `ISSUE_SINK_TYPE` -> `rest`. +- `ISSUE_CORE_API_KEY` merged into railiance01's `actcore-runtime-secret` by + the operator via a stdin-only pipe from the approved OpenBao lane + (`CCR-2026-0002`, activated the same day). + +**Emission proof:** from inside the restarted actcore-worker, the real +`IssueCoreRestSink` emitted a labeled smoke TaskSpec and received +`TaskRef(external_id='176', backend='gitea')` — Gitea issue `176` in +`coulomb/markitect-main` (default backend). Auth, bridge, tunnels, issue-core +validation, and Gitea creation all exercised on the production path. + +**Contract note for emitters:** `POST /issues/` requires `target_repo` and +`activity_definition_id` (422 otherwise). activity-core `TaskSpec` defaults +(`target_repo=None`, `activity_definition_id=""`) will be rejected — rule and +instruction emitters must populate both.