Compare commits

...

10 commits

Author SHA1 Message Date
6718618568 Add Forgejo CI smoke and image workflow (tier 3 T04)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 1m29s
2026-07-06 14:38:33 +02:00
54a9749616 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-02:
  - update .custodian-brief.md for issue-core
2026-07-02 14:50:19 +02:00
a691f93f16 ISSUE-WP-0003 finished: live REST emission proven (Gitea issue 176), topology corrected
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:49:57 +02:00
8d34c6d468 docs: update issue-core deployment closeout 2026-07-01 20:04:37 +02:00
e5172611ec chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-01:
  - update .custodian-brief.md for issue-core
2026-07-01 19:39:41 +02:00
9e46004961 Update ISSUE-WP-0003 deployment progress 2026-06-25 19:59:53 +02:00
11a0a69870 Deploy issue-core 0.2.1 image 2026-06-25 19:47:58 +02:00
3c66148205 Fix Gitea issue label payloads 2026-06-25 19:40:15 +02:00
2f40dea6a1 Fix railiance issue-core GitOps runtime config 2026-06-25 19:23:34 +02:00
8c01f07c2d feat(integration): add open-reuse Integration Definition for Gitea backend
Register the Gitea API adapter boundary for issue-core maintenance tracking.
2026-06-24 18:25:13 +02:00
12 changed files with 340 additions and 93 deletions

View file

@ -2,21 +2,12 @@
# Custodian Brief — issue-core # Custodian Brief — issue-core
**Domain:** infotech **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)* **State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams ## Active Workstreams
### Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot) *(none — repo may need first-session setup)*
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`
--- ---
## MCP Orientation (when available) ## MCP Orientation (when available)

View file

@ -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}"

View file

@ -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}"

View file

@ -1,15 +1,10 @@
# issue-core REST ingestion service image. # issue-core REST ingestion service image.
# #
# Installs the published issue-core[api] package from the Coulomb Gitea PyPI # Builds the checked-out issue-core[api] package and runs the FastAPI ingestion
# index (no sibling-checkout build context) and runs the FastAPI ingestion # server on :8765. The image is published to
# server on :8765. Built and pushed to gitea.coulomb.social/coulomb/issue-core. # gitea.coulomb.social/coulomb/issue-core.
FROM python:3.12-slim AS runtime 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 \ ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
HOME=/home/app HOME=/home/app
@ -18,10 +13,11 @@ ENV PYTHONUNBUFFERED=1 \
# (~/.config/issue-tracker/backends.json). # (~/.config/issue-tracker/backends.json).
RUN useradd --create-home --home-dir /home/app --uid 10001 app RUN useradd --create-home --home-dir /home/app --uid 10001 app
RUN pip install --no-cache-dir \ WORKDIR /src
--index-url https://pypi.org/simple \ COPY pyproject.toml README.md LICENSE ./
--extra-index-url "${GITEA_PYPI_INDEX_URL}" \ COPY issue_core ./issue_core
"issue-core[api]${ISSUE_CORE_VERSION}" 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 COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh
@ -30,4 +26,4 @@ USER app
EXPOSE 8765 EXPOSE 8765
# Entrypoint renders backends.json from env, then execs the server. # Entrypoint renders backends.json from env, then execs the server.
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

View file

@ -7,7 +7,7 @@ railiance-platform.
## Source layout ## Source layout
- Workload bundle: `issue-core/k8s/railiance/` - 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` - Container port and Service port: `8765`
- Cluster Service URL: `http://issue-core.issue-core.svc.cluster.local:8765` - Cluster Service URL: `http://issue-core.issue-core.svc.cluster.local:8765`
- Tenant Application: `railiance-platform/argocd/applications/issue-core.application.yaml` - Tenant Application: `railiance-platform/argocd/applications/issue-core.application.yaml`
@ -18,31 +18,31 @@ therefore intentionally not duplicated in this bundle.
## Platform gates ## Platform gates
The following pieces are owned by railiance-platform before the workload can The following pieces are owned by railiance-platform for the live pilot and for
be fully reconciled: any future cluster replay:
- ArgoCD repository credentials and the project/app-of-apps convention. - ArgoCD repository credentials and the project/app-of-apps convention.
- The `issue-core` ArgoCD `Application`. - The `issue-core` ArgoCD `Application`.
- External Secrets Operator and a `ClusterSecretStore` named `openbao`. - External Secrets Operator and a `ClusterSecretStore` named `openbao`.
- OpenBao entries for the issue-core runtime Secret. - OpenBao entries for the issue-core runtime Secret.
Until those gates exist, `kubectl kustomize k8s/railiance` can render locally, For the 2026-06-25 live deployment, these gates were satisfied and the
but the live `ExternalSecret` and `Deployment` are expected to wait. `issue-core` Application reached Synced/Healthy with image `0.2.1`.
## Secret contract ## Secret contract
Kubernetes Secret name: `issue-core-runtime` Kubernetes Secret name: `issue-core-runtime`
Current issue-core manifest path, pending railiance-platform confirmation: Current issue-core manifest path:
```text ```text
platform/workloads/issue-core/issue-core/issue-core-runtime platform/workloads/issue-core/issue-core/issue-core-runtime
``` ```
Credential route catalog id `issue-core-ingestion-api-key` is owned by Credential custody is owned by railiance-platform/OpenBao. For agents, first
railiance-platform/OpenBao and is still marked draft/path TBD in the local use the non-secret route catalog entry `activity-core-issue-sink` to confirm
ops-warden catalog reviewed 2026-06-18. Confirm the canonical path before the activity-core + issue-core pairing, and never request the value from
provisioning the live Secret. ops-warden.
Required properties: Required properties:
@ -56,12 +56,12 @@ HTTP status codes, and created issue URLs.
## Build and publish ## Build and publish
Use the published package as the image input. For a reproducible release image, Build the checked-out source tree and publish a registry tag that ArgoCD can
pin the package version to the image tag: pull:
```bash ```bash
docker build --build-arg ISSUE_CORE_VERSION="==0.2.0" -t 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.0 docker push gitea.coulomb.social/coulomb/issue-core:0.2.1
``` ```
The Coulomb Gitea package is public-pullable for this image, so the workload The Coulomb Gitea package is public-pullable for this image, so the workload
@ -137,7 +137,7 @@ spec:
command: ["/bin/sh", "-ceu"] command: ["/bin/sh", "-ceu"]
args: 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 YAML
kubectl -n issue-core wait --for=condition=complete job/issue-core-smoke --timeout=90s kubectl -n issue-core wait --for=condition=complete job/issue-core-smoke --timeout=90s
kubectl -n issue-core logs job/issue-core-smoke kubectl -n issue-core logs job/issue-core-smoke

View file

@ -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

View file

@ -19,6 +19,6 @@ Supported Backends:
- Future: GitHub, GitLab, JIRA, Redmine - Future: GitHub, GitLab, JIRA, Redmine
""" """
__version__ = "0.2.0" __version__ = "0.2.1"
__author__ = "Coulomb / MarkiTect Project" __author__ = "Coulomb / MarkiTect Project"
__description__ = "Authoritative task lifecycle manager with plugin architecture" __description__ = "Authoritative task lifecycle manager with plugin architecture"

View file

@ -195,10 +195,20 @@ class GiteaBackend(RemoteBackend, SyncableBackend):
if issue.milestone: if issue.milestone:
data['milestone'] = int(issue.milestone.backend_id) if issue.milestone.backend_id else None data['milestone'] = int(issue.milestone.backend_id) if issue.milestone.backend_id else None
# Convert labels # Gitea expects numeric label IDs on issue create/update. Name-only
if issue.labels: # labels are preserved in issue-core metadata but omitted from the API
data['labels'] = [label.name for label in issue.labels] # 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 return data
# Issue CRUD Operations # Issue CRUD Operations

View file

@ -17,7 +17,7 @@ data:
"type": "gitea", "type": "gitea",
"base_url": "http://gitea-http.default.svc.cluster.local:3000", "base_url": "http://gitea-http.default.svc.cluster.local:3000",
"owner": "coulomb", "owner": "coulomb",
"repo": "markitect_project", "repo": "markitect-main",
"token": "__FROM_ENV__" "token": "__FROM_ENV__"
}, },
"default": "markitect" "default": "markitect"

View file

@ -23,7 +23,7 @@ spec:
# docs). Add imagePullSecrets: [{name: gitea-registry}] if it becomes private. # docs). Add imagePullSecrets: [{name: gitea-registry}] if it becomes private.
containers: containers:
- name: issue-core - 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 imagePullPolicy: IfNotPresent
ports: ports:
- name: http - name: http
@ -67,5 +67,6 @@ spec:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
readOnlyRootFilesystem: false readOnlyRootFilesystem: false
runAsNonRoot: true runAsNonRoot: true
runAsUser: 10001
capabilities: capabilities:
drop: ["ALL"] drop: ["ALL"]

View file

@ -6,8 +6,10 @@ These tests ensure the Gitea backend works correctly with the API.
import pytest import pytest
import json import json
from datetime import datetime, timezone
from unittest.mock import Mock, patch, MagicMock from unittest.mock import Mock, patch, MagicMock
from issue_core.backends.gitea.backend import GiteaBackend, GiteaAPIError from issue_core.backends.gitea.backend import GiteaBackend, GiteaAPIError
from issue_core.core.models import Issue, IssueState, Label
class TestGiteaBackend: 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] 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' 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') @patch('issue_core.backends.gitea.backend.requests.Session')
def test_test_connection_success(self, mock_session_class): def test_test_connection_success(self, mock_session_class):
"""Test test_connection method works correctly.""" """Test test_connection method works correctly."""

View file

@ -4,11 +4,11 @@ type: workplan
title: "Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)" title: "Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)"
domain: infotech domain: infotech
repo: issue-core repo: issue-core
status: active status: finished
owner: claude owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-06-19" created: "2026-06-19"
updated: "2026-06-23" updated: "2026-06-30"
state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570" 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 `issue-core` is the authoritative task-lifecycle manager and the REST ingestion
target for activity-core's `IssueSink`. Deployment artifacts are on `main` target for activity-core's `IssueSink`. Deployment artifacts are on `main`
(`Dockerfile`, `docker-entrypoint.sh`, `k8s/railiance/`); image (`Dockerfile`, `docker-entrypoint.sh`, `k8s/railiance/`); image
`gitea.coulomb.social/coulomb/issue-core:0.2.0` is built, pushed, and `gitea.coulomb.social/coulomb/issue-core:0.2.1` is built, pushed, and
pullable. The railiance01 cluster still has no `issue-core` workload until pullable. The railiance01 cluster now reconciles `issue-core` through ArgoCD;
T02 live ArgoCD bootstrap (RAILIANCE-WP-0004-T05) and T04 OpenBao secrets land. 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 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 railiance01 **via ArgoCD GitOps** — making issue-core the cluster's first
declarative Application and turning on the idle GitOps capability. 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 - **Deployment artifacts in-repo:** `Dockerfile`, `docker-entrypoint.sh`, and
`k8s/railiance/` (Kustomize: ExternalSecret, ConfigMap, Deployment, Service). `k8s/railiance/` (Kustomize: ExternalSecret, ConfigMap, Deployment, Service).
Image builds locally; `docker run` + `GET /healthz` returns 200. Image pushed Image builds locally; `docker run` + `GET /healthz` returns 200. Image pushed
and pullable as `gitea.coulomb.social/coulomb/issue-core:0.2.0` (digest and pullable as `gitea.coulomb.social/coulomb/issue-core:0.2.1` (digest
`sha256:153fbe43…`). `coulomb` org packages are public — no `imagePullSecret` `sha256:729c0e56…`). `coulomb` org packages are public — no `imagePullSecret`
required per `railiance-forge/docs/gitea-container-registry.md`. required per `railiance-forge/docs/gitea-container-registry.md`.
- **Dockerfile fix (2026-06-19):** build arg renamed `GITEA_PYPI_INDEX_URL` - **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 `ARG PIP_INDEX_URL` leaked into the build env and pip used Gitea as the sole
index, so dependencies like `click` were not found. index, so dependencies like `click` were not found.
- **railiance01 cluster:** no `issue-core` namespace; no issue-core - **railiance01 cluster:** `issue-core` namespace, Service, ExternalSecret,
Deployment/Service/Pod in any namespace. Secret, and Deployment are present. ArgoCD reports the `issue-core` Application
- **Dangling reference:** `activity-core/k8s/railiance/20-runtime.yaml` sets Synced/Healthy at revision `11a0a69`; pod is Ready on image `0.2.1`.
`ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8010` — a - **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.
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 - **Packaging precursor is done:** `ISSUE-WP-0002` published
`issue-core==0.2.0` to the Coulomb Gitea PyPI index. `issue-core==0.2.0` to the Coulomb Gitea PyPI index. The live `0.2.1` image
- **ArgoCD is installed but unused:** all 7 components healthy (~290d), but was built from the committed source tree as a deployment hotfix.
**0 Applications, 0 ApplicationSets, 0 registered git repos**, only the stock - **ArgoCD is active for the pilot:** railiance-platform owns the bootstrap and tenant AppProject; `issue-core` is Synced/Healthy as the pilot workload.
`default` AppProject. No `kind: Application` manifests exist in any infra repo.
- **Existing deploy pattern is imperative** (the path we are *replacing* for - **Existing deploy pattern is imperative** (the path we are *replacing* for
this service): local `docker build``k3s ctr images import` (side-load, no this service): local `docker build``k3s ctr images import` (side-load, no
registry) → `rsync` manifests → `kubectl apply` (see 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; traceability string. Event-driven activity-core paths can still send UUIDs;
scheduled/cron paths may now send a stable key such as `scheduled`. 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 ## Decisions
- **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19). - **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. **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 - [x] Add `Dockerfile` building the checked-out `issue-core[api]` source.
PyPI index (with explicit PyPI primary index). Entrypoint renders Entrypoint renders `backends.json` then `issue serve --host 0.0.0.0 --port 8765`.
`backends.json` then `issue serve --host 0.0.0.0 --port 8765`.
- [x] Local build succeeds; `docker run` + `GET /healthz` returns 200. - [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. succeeds.
- [x] No cluster pull secret needed (`coulomb` org packages are public). - [x] No cluster pull secret needed (`coulomb` org packages are public).
- [ ] `POST /issues/` smoke against a running deployment (deferred to T03/T04 - [x] `POST /issues/` smoke against a running deployment returned 201.
cluster verification).
## ArgoCD bootstrap (railiance-platform dependency) + issue-core Application ## ArgoCD bootstrap (railiance-platform dependency) + issue-core Application
```task ```task
id: ISSUE-WP-0003-T02 id: ISSUE-WP-0003-T02
status: wait status: done
priority: high priority: high
state_hub_task_id: "9b199b1d-d3c0-4621-b8f8-58c376cbf878" state_hub_task_id: "9b199b1d-d3c0-4621-b8f8-58c376cbf878"
``` ```
**Owner split.** ArgoCD bootstrap is **railiance-platform's** (operator **Owner split.** ArgoCD bootstrap is **railiance-platform's** (operator
decision 2026-06-19): repo registration in ArgoCD, AppProject/app-of-apps 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 convention, and the agreed GitOps source layout. This handoff is complete for
handoff. issue-core's part is to **contribute** the `Application` manifest + the issue-core pilot; issue-core contributes workload manifests and platform owns
workload manifests into the layout platform defines. the tenant `Application` wrapper.
- **(railiance-platform)** Register the GitOps source repo (repository Secret + - **(railiance-platform)** Register the GitOps source repo (repository Secret +
creds); define AppProject for cluster services; publish the source-repo/path 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 - [x] **(issue-core)** Workload manifests in `k8s/railiance/` on `main` per
platform contract (`docs/argocd-gitops.md`). Tenant `Application` lives in platform contract (`docs/argocd-gitops.md`). Tenant `Application` lives in
`railiance-platform/argocd/applications/issue-core.application.yaml`. `railiance-platform/argocd/applications/issue-core.application.yaml`.
- [ ] **(railiance-platform)** RAILIANCE-WP-0004-T05 live bootstrap: register - [x] **(railiance-platform)** Live bootstrap deployed; `issue-core` Application
repo creds, deploy bootstrap, sync `issue-core` Application. syncs from the issue-core repo through the tenant AppProject.
- [ ] Verify: `kubectl get applications -n argocd` shows `issue-core` - [x] Verify: `kubectl get applications -n argocd` shows `issue-core`
Synced/Healthy; ArgoCD reconciles a trivial manifest change. Synced/Healthy at revision `11a0a69`; ArgoCD reconciled the `0.2.1` image
manifest change.
## Kubernetes manifests (namespace, Deployment, Service) in GitOps source ## Kubernetes manifests (namespace, Deployment, Service) in GitOps source
```task ```task
id: ISSUE-WP-0003-T03 id: ISSUE-WP-0003-T03
status: progress status: done
priority: high priority: high
state_hub_task_id: "38887dd6-0988-4ad1-bc6b-2a1b8839829f" 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 - [x] `k8s/railiance/` Kustomize bundle (namespace via ArgoCD
`CreateNamespace=true`). `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). resource requests/limits; env from ExternalSecret (T04) and ConfigMap (T05).
- [x] Service: ClusterIP on **8765** as - [x] Service: ClusterIP on **8765** as
`issue-core.issue-core.svc.cluster.local`. `issue-core.issue-core.svc.cluster.local`.
- [ ] Verify: ArgoCD syncs the manifests; Pod Ready; `/healthz` 200 from a debug - [x] Verify: ArgoCD syncs the manifests; pod Ready; `/healthz` returned 200
pod (blocked on T01 push + T02 bootstrap + T04 secrets). from inside the cluster.
## OpenBao secret: ISSUE_CORE_API_KEY ## OpenBao secret: ISSUE_CORE_API_KEY
```task ```task
id: ISSUE-WP-0003-T04 id: ISSUE-WP-0003-T04
status: wait status: done
priority: high priority: high
state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad" 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. - Never write the value to Git, manifests, State Hub, or logs.
- Verify: both pods resolve a non-empty key; auth round-trip (401 without, - Verify: both pods resolve a non-empty key; auth round-trip (401 without,
201 with). 201 with).
- Current wait reason: requires railiance-platform/OpenBao operator action to - Done 2026-06-25: canonical OpenBao path exists, `ClusterSecretStore/openbao` is
confirm/provision the canonical path and `ClusterSecretStore`; Ready, `ExternalSecret/issue-core-runtime` is Ready, and the Kubernetes Secret
issue-core records only the Secret contract and non-secret verification steps. contains the two expected data keys. activity-core consumption remains in T06.
## In-cluster backend config (cluster Gitea / markitect) ## In-cluster backend config (cluster Gitea / markitect)
```task ```task
id: ISSUE-WP-0003-T05 id: ISSUE-WP-0003-T05
status: progress status: done
priority: medium priority: medium
state_hub_task_id: "10923f1e-050d-4f3e-980e-b061fef5f33a" 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__`. (`gitea-http.default.svc.cluster.local:3000`); token sentinel `__FROM_ENV__`.
- [x] `docker-entrypoint.sh` renders `~/.config/issue-tracker/backends.json` - [x] `docker-entrypoint.sh` renders `~/.config/issue-tracker/backends.json`
from `BACKENDS_TEMPLATE` + `GITEA_BACKEND_TOKEN` at startup. from `BACKENDS_TEMPLATE` + `GITEA_BACKEND_TOKEN` at startup.
- [ ] Verify: a `POST /issues/` creates a real Gitea issue and returns - [x] Verify: authenticated `POST /issues/` returned 201 and created Gitea
`issue_url` (blocked on T04 secrets + in-cluster deployment). issue id `175` via the live service.
## Wire activity-core to the live service ## Wire activity-core to the live service
```task ```task
id: ISSUE-WP-0003-T06 id: ISSUE-WP-0003-T06
status: progress status: done
priority: high priority: high
state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" 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 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 event-driven paths can send UUIDs and cron paths can send stable keys such as
`"scheduled"`. `"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 - Verify: an activity-core run emits a task that lands in cluster Gitea via
issue-core. issue-core.
@ -230,7 +262,7 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
```task ```task
id: ISSUE-WP-0003-T07 id: ISSUE-WP-0003-T07
status: progress status: done
priority: medium priority: medium
state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" 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 - ArgoCD Application Synced/Healthy; issue-core Pod Ready; Service reachable
cluster-internal. 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 - [x] Document the GitOps runbook (image build/push, ArgoCD sync, secret
contract, smoke, activity-core handoff) in `docs/argocd-gitops.md`. 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 - `activity-core/k8s/railiance/README.md` — the imperative pattern being
superseded for this service. superseded for this service.
- `~/ops-warden/wiki/playbooks/activity-core-issue-sink.md` — key routing. - `~/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.