Compare commits

...

11 Commits

Author SHA1 Message Date
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
Some checks failed
Publish Python package / publish (push) Has been cancelled
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
7693ef8680 Accept non-UUID triggering_event_id and add GitOps runbook
Broaden POST /issues/ so triggering_event_id is any non-empty traceability
string, enabling cron/scheduled activity-core emissions with stable keys like
"scheduled" while event-driven paths still send UUIDs. Document the railiance01
ArgoCD deployment path in docs/argocd-gitops.md and update ISSUE-WP-0003 task
status to reflect repo-side progress.
2026-06-24 14:52:47 +02:00
4854bda118 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-23:
  - update .custodian-brief.md for issue-core
2026-06-23 14:26:56 +02:00
19d4bc96df Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:25 +02:00
d50ab96a8a Mark .repo-classification.yaml human-reviewed (CUST-WP-0050 T02)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:40:43 +02:00
21 changed files with 484 additions and 135 deletions

View File

@@ -1,11 +1,11 @@
## First Session Protocol ## First Session Protocol
Triggered when `get_domain_summary("custodian")` shows **no workstreams**. Triggered when `get_domain_summary("infotech")` shows **no workstreams**.
The project is registered but work has not yet been structured. The project is registered but work has not yet been structured.
**Step 1 — Read, don't write** **Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/custodian/project_charter_v0.1.md` — purpose, scope - `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/custodian/roadmap_v0.1.md` — planned phases - `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs - Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work** **Step 2 — Survey in-progress work**
@@ -17,7 +17,7 @@ roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)** **Step 4 — Create workplan file first, then DB record (ADR-001)**
``` ```
workplans/issue-core-WP-NNNN-<slug>.md ← write this first workplans/ISSUE-WP-NNNN-<slug>.md ← write this first
``` ```
Then register in the hub: Then register in the hub:
``` ```
@@ -28,7 +28,7 @@ create_task(workstream_id="<id>", title="...", priority="high|medium|low")
**Step 5 — Record the setup** **Step 5 — Record the setup**
``` ```
add_progress_event( add_progress_event(
summary="First session: structured custodian into N workstreams, M tasks", summary="First session: structured infotech into N workstreams, M tasks",
event_type="milestone", event_type="milestone",
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
detail={"workstreams": [...], "tasks_created": M} detail={"workstreams": [...], "tasks_created": M}

View File

@@ -1,5 +1,5 @@
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17. **Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Domain:** custodian **Domain:** infotech
**Repo slug:** issue-core **Repo slug:** issue-core
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a **Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a

View File

@@ -1,6 +1,7 @@
## Session Protocol ## Session Protocol
State Hub: http://127.0.0.1:8000 Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient** **Step 1 — Orient**
@@ -10,7 +11,7 @@ cat .custodian-brief.md
``` ```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed: Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
``` ```
get_domain_summary("custodian") get_domain_summary("infotech")
``` ```
If MCP tools are unavailable in the current agent session, use the REST API: If MCP tools are unavailable in the current agent session, use the REST API:
```bash ```bash
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/ ls workplans/
``` ```
For each file with `status: ready`, `active`, or `blocked`, note pending For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks. `wait`/`todo`/`progress` tasks.
**Step 4 — Present brief** **Step 4 — Present brief**
1. **Active workstreams** for `custodian` — title, task counts, blocking decisions 1. **Active workstreams** for `infotech` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:issue-core]` hub tasks 2. **Pending tasks** from `workplans/` + any `[repo:issue-core]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary: 3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"* - `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*

View File

@@ -1,7 +1,7 @@
## Workplan Convention (ADR-001) ## Workplan Convention (ADR-001)
File location: `workplans/issue-core-WP-NNNN-<slug>.md` File location: `workplans/ISSUE-WP-NNNN-<slug>.md`
ID prefix: `ISSUE-WP` ID prefix: `ISSUE-WP-`
Work items originate as files in this repo **before** being registered in the hub. Work items originate as files in this repo **before** being registered in the hub.
@@ -12,7 +12,7 @@ repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses. `needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-issue-core-WP-NNNN-<slug>.md`. The frontmatter id remains prefix: `YYMMDD-ISSUE-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference. unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**: Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
@@ -25,4 +25,16 @@ Ecosystem todos from other agents arrive as `[repo:issue-core]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering visible at session start. Pick one up by creating the workplan file, then registering
the workstream. the workstream.
Task blocks use this shape:
```task
id: ISSUE-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here --> <!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

View File

@@ -1,27 +1,23 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually --> <!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — issue-core # Custodian Brief — issue-core
**Domain:** custodian **Domain:** infotech
**Last synced:** 2026-06-19 19:09 UTC **Last synced:** 2026-07-01 17:39 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) ### Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)
Progress: 1/7 done | workstream_id: `896ace77-21b3-450b-8fb7-254aefc8c570` Progress: 5/7 done | workstream_id: `896ace77-21b3-450b-8fb7-254aefc8c570`
**Open tasks:** **Open tasks:**
- ! ArgoCD bootstrap (railiance-platform dependency) + issue-core Application `9b199b1d` - ! Wire activity-core to the live service `96b14cdb`
- ► Kubernetes manifests (namespace, Deployment, Service) in GitOps source `38887dd6` - ! End-to-end verification + GitOps runbook `8d853b8e`
- ► In-cluster backend config (cluster Gitea / markitect) `10923f1e`
- · OpenBao secret: ISSUE_CORE_API_KEY `ad52527f`
- · Wire activity-core to the live service `96b14cdb`
- · End-to-end verification + GitOps runbook `8d853b8e`
--- ---
## MCP Orientation (when available) ## MCP Orientation (when available)
If the state-hub MCP server is reachable, call: If the state-hub MCP server is reachable, call:
`get_domain_summary("custodian")` `get_domain_summary("infotech")`
This provides richer cross-domain context. This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source. If the MCP call fails, use this file as your orientation source.

View File

@@ -1,11 +1,10 @@
# Repo classification (Repo Classification Standard v1.0). # Repo classification (Repo Classification Standard v1.0).
# First-pass agent classification (CUST-WP-0050 T02) — pending human review.
repo_classification: repo_classification:
standard: Repo Classification Standard standard: Repo Classification Standard
version: '1.0' version: '1.0'
classified_at: '2026-06-22' classified_at: '2026-06-22'
classified_by: agent classified_by: human
category: tooling category: tooling
domain: infotech domain: infotech
secondary_domains: secondary_domains:

View File

@@ -4,7 +4,7 @@
**Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17. **Purpose:** Authoritative task lifecycle manager for the Coulomb org. Backend-agnostic CLI + REST ingestion endpoint for tasks from activity-core's IssueSink. Pluggable backends (Gitea, SQLite, GitHub). Renamed from issue-facade on 2026-05-17.
**Domain:** custodian **Domain:** infotech
**Repo slug:** issue-core **Repo slug:** issue-core
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a` **Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `ISSUE-WP-` **Workplan prefix:** `ISSUE-WP-`
@@ -63,8 +63,8 @@ Omit `workstream_id` / `task_id` when not applicable.
```bash ```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"status": "in_progress"}' -d '{"status": "progress"}'
# values: todo | in_progress | done | blocked # values: wait | todo | progress | done | cancel
``` ```
### Flag a task for human review ### Flag a task for human review
@@ -83,7 +83,7 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe) 1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
2. Check inbox: `GET /messages/?to_agent=issue-core&unread_only=true`; mark read 2. Check inbox: `GET /messages/?to_agent=issue-core&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks 3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check blocked tasks: `GET /tasks/?needs_human=true` 4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:** **During work:**
- Update task statuses in workplan files as tasks progress - Update task statuses in workplan files as tasks progress
@@ -151,31 +151,10 @@ every repo's agent instructions because it is high-frequency, high-risk, and eas
get wrong. get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml` **Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
---
## REST ingestion API key <!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
`POST /issues/` requires a shared key in `ISSUE_CORE_API_KEY`. The server The state-hub template sync preserves content after this line. -->
refuses to start without it. activity-core's `IssueCoreRestSink` sends the same
value as `Authorization: Bearer <key>` (also accepts `X-API-Key`).
**Do not request this key from ops-warden** — pair env vars locally or via
OpenBao/K8s on both repos. Routing lookup:
`warden route show activity-core-issue-sink --json`.
**Local dev:**
```bash
export ISSUE_CORE_API_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
issue serve --host 127.0.0.1 --port 8765
```
Set the same `ISSUE_CORE_API_KEY` in activity-core when `ISSUE_SINK_TYPE=rest`.
For local ingest smoke, set `default: local` in `~/.config/issue-tracker/backends.json`
— a remote Gitea default backend will hang on `POST /issues/`.
See `README.md` (REST Ingestion Server) and activity-core
`docs/issue-core-emission-boundary.md`.
--- ---
@@ -202,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: ISSUE-WP-NNNN id: ISSUE-WP-NNNN
type: workplan type: workplan
title: "..." title: "..."
domain: custodian domain: infotech
repo: issue-core repo: issue-core
status: proposed | ready | active | blocked | backlog | finished | archived status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex owner: codex
@@ -224,7 +203,7 @@ derived health labels, not frontmatter statuses.
` ` `task ` ` `task
id: ISSUE-WP-NNNN-T01 id: ISSUE-WP-NNNN-T01
status: todo | in_progress | done | blocked status: wait | todo | progress | done | cancel
priority: high | medium | low priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` ` ` ` `
@@ -232,7 +211,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
Task description text. Task description text.
``` ```
Status progression: `todo` → `in_progress` → `done` (or `blocked`) Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
To create a new workplan: To create a new workplan:
1. Write the file following the format above 1. Write the file following the format above

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

View File

@@ -135,11 +135,17 @@ user directory.
"due_in_days": 7, "due_in_days": 7,
"source_type": "rule | instruction", "source_type": "rule | instruction",
"source_id": "string", "source_id": "string",
"triggering_event_id": "uuid", "triggering_event_id": "event uuid or stable source key",
"activity_definition_id": "string" "activity_definition_id": "string"
} }
``` ```
`triggering_event_id` is accepted as a non-empty string. Event-driven
emissions should send the upstream activity event UUID. Scheduled or cron
emissions that do not have a concrete event row may send a stable source key
such as `scheduled`; issue-core stores the value verbatim in ingestion
metadata for traceability.
### `POST /issues/` response ### `POST /issues/` response
```json ```json

168
docs/argocd-gitops.md Normal file
View File

@@ -0,0 +1,168 @@
# ArgoCD GitOps deployment - railiance01
This runbook captures the issue-core side of the railiance01 GitOps pilot.
It keeps secrets out of Git and leaves platform-owned bootstrap steps in
railiance-platform.
## Source layout
- Workload bundle: `issue-core/k8s/railiance/`
- 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`
The `Application` should point at this repo's `k8s/railiance` path and use
`CreateNamespace=true` for the `issue-core` namespace. The namespace itself is
therefore intentionally not duplicated in this bundle.
## Platform gates
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.
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:
```text
platform/workloads/issue-core/issue-core/issue-core-runtime
```
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:
- `ISSUE_CORE_API_KEY` - shared ingestion key used by issue-core and
activity-core.
- `GITEA_BACKEND_TOKEN` - token for creating issues in cluster Gitea.
Never write either value to Git, State Hub, workplans, logs, or chat. Record
only non-secret evidence such as Secret key count, ExternalSecret readiness,
HTTP status codes, and created issue URLs.
## Build and publish
Build the checked-out source tree and publish a registry tag that ArgoCD can
pull:
```bash
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
does not use an `imagePullSecret`.
## Pre-sync validation
From the issue-core repo:
```bash
kubectl kustomize k8s/railiance
```
The rendered resources should be:
- `ExternalSecret/issue-core-runtime`
- `ConfigMap/issue-core-backends`
- `Deployment/issue-core`
- `Service/issue-core`
## Sync verification
After railiance-platform syncs the tenant `Application`:
```bash
kubectl get application issue-core -n argocd
kubectl -n issue-core get externalsecret issue-core-runtime
kubectl -n issue-core get secret issue-core-runtime
kubectl -n issue-core get deploy,pod,svc
```
Expected non-secret evidence:
- ArgoCD Application reports `Synced` and `Healthy`.
- `ExternalSecret/issue-core-runtime` reports Ready.
- `Secret/issue-core-runtime` exists with two data keys.
- `Deployment/issue-core` has one available replica.
- `Service/issue-core` exposes port `8765`.
Health check from inside the cluster:
```bash
kubectl -n issue-core run issue-core-health --rm -i --restart=Never --image=curlimages/curl:8.8.0 -- http://issue-core:8765/healthz
```
## Ingestion smoke
Run the authenticated smoke from a short-lived Job so the API key is mounted
from the Kubernetes Secret without printing it:
```bash
kubectl -n issue-core delete job issue-core-smoke --ignore-not-found
kubectl -n issue-core apply -f - <<'YAML'
apiVersion: batch/v1
kind: Job
metadata:
name: issue-core-smoke
spec:
ttlSecondsAfterFinished: 600
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: smoke
image: curlimages/curl:8.8.0
env:
- name: ISSUE_CORE_API_KEY
valueFrom:
secretKeyRef:
name: issue-core-runtime
key: ISSUE_CORE_API_KEY
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-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
```
Acceptance evidence is HTTP 201 plus a response body containing `issue_id`,
`backend: "gitea"`, and an `issue_url` for cluster Gitea.
Cleanup:
```bash
kubectl -n issue-core delete job issue-core-smoke
```
## Activity-core handoff
After issue-core is Ready and the shared `ISSUE_CORE_API_KEY` is available to
activity-core from the same approved OpenBao source:
- Set `ISSUE_CORE_URL=http://issue-core.issue-core.svc.cluster.local:8765`.
- Set `ISSUE_SINK_TYPE=rest`.
- Inject the same `ISSUE_CORE_API_KEY` into the activity-core worker.
- Keep cron-triggered emissions explicit: `triggering_event_id` may be a stable
non-empty scheduler key such as `scheduled`; event-driven emissions should
continue to send the event UUID.
Verify by running an activity-core emission and confirming that issue-core
returns HTTP 201 and creates a Gitea issue.

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

@@ -2,14 +2,12 @@
Pydantic schemas for the issue-core REST API. Pydantic schemas for the issue-core REST API.
The TaskIngestionRequest schema matches activity-core's IssueSink TaskSpec The TaskIngestionRequest schema matches activity-core's IssueSink TaskSpec
payload exactly. See: payload. See:
- SCOPE.md "TaskSpec payload" section - SCOPE.md "TaskSpec payload" section
- activity-core docs/adr/adr-001-event-bridge-architecture.md - activity-core docs/adr/adr-001-event-bridge-architecture.md
""" """
from typing import List, Literal, Optional from typing import List, Literal, Optional
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
@@ -31,7 +29,14 @@ class TaskIngestionRequest(BaseModel):
due_in_days: Optional[int] = Field(default=None, ge=0) due_in_days: Optional[int] = Field(default=None, ge=0)
source_type: SourceType source_type: SourceType
source_id: str = Field(..., min_length=1) source_id: str = Field(..., min_length=1)
triggering_event_id: UUID triggering_event_id: str = Field(
...,
min_length=1,
description=(
"Activity event UUID, or a stable scheduler/source key when no "
"event row exists."
),
)
activity_definition_id: str = Field(..., min_length=1) activity_definition_id: str = Field(..., min_length=1)

View File

@@ -195,9 +195,19 @@ 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

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

@@ -112,6 +112,30 @@ def test_ingest_creates_issue_with_x_api_key(client, valid_payload):
assert response.status_code == 201, response.text assert response.status_code == 201, response.text
@pytest.mark.unit
def test_ingest_accepts_non_uuid_triggering_event_id(client, valid_payload, tmp_issue_store):
valid_payload["triggering_event_id"] = "scheduled"
response = client.post(
"/issues/",
json=valid_payload,
headers={"Authorization": f"Bearer {API_KEY}"},
)
assert response.status_code == 201, response.text
issue_id = response.json()["issue_id"]
from issue_core.backends.local import LocalSQLiteBackend
backend = LocalSQLiteBackend()
backend.connect({"type": "local", "db_path": str(tmp_issue_store / "issues.db")})
try:
stored = backend.get_issue(issue_id)
assert stored is not None
ingestion = stored.sync_metadata.get("ingestion") or {}
assert ingestion["triggering_event_id"] == "scheduled"
finally:
backend.disconnect()
@pytest.mark.unit @pytest.mark.unit
def test_ingest_rejects_invalid_payload(client): def test_ingest_rejects_invalid_payload(client):
bad = {"title": "no required fields"} bad = {"title": "no required fields"}

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

@@ -1,7 +1,7 @@
--- ---
id: ISSC-WP-0001 id: ISSC-WP-0001
type: workplan type: workplan
domain: custodian domain: infotech
repo: issue-core repo: issue-core
status: done status: done
state_hub_workstream_id: 1135fc1d-1f46-4e35-886d-04cc3b8050b6 state_hub_workstream_id: 1135fc1d-1f46-4e35-886d-04cc3b8050b6

View File

@@ -2,7 +2,7 @@
id: ISSUE-WP-0002 id: ISSUE-WP-0002
type: workplan type: workplan
title: "Publish issue-core to Gitea PyPI" title: "Publish issue-core to Gitea PyPI"
domain: custodian domain: infotech
repo: issue-core repo: issue-core
status: finished status: finished
owner: codex owner: codex

View File

@@ -2,13 +2,13 @@
id: ISSUE-WP-0003 id: ISSUE-WP-0003
type: workplan 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: custodian domain: infotech
repo: issue-core repo: issue-core
status: active status: blocked
owner: claude owner: claude
topic_slug: custodian topic_slug: custodian
created: "2026-06-19" created: "2026-06-19"
updated: "2026-06-19" updated: "2026-06-30"
state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570" state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570"
--- ---
@@ -17,41 +17,79 @@ 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
`activity-core/k8s/railiance/README.md`). `activity-core/k8s/railiance/README.md`).
## Repo-side progress (2026-06-23)
- Added `docs/argocd-gitops.md` with the issue-core GitOps runbook, including
image publish, ArgoCD sync checks, OpenBao/ExternalSecret contract, health
probe, authenticated ingestion smoke, cleanup, and activity-core handoff.
- Broadened `POST /issues/` so `triggering_event_id` accepts any non-empty
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 ## Decisions
- **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19). - **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19).
@@ -96,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
@@ -127,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: in_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"
``` ```
@@ -145,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: todo status: done
priority: high priority: high
state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad" state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad"
``` ```
@@ -171,12 +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).
- 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) ## In-cluster backend config (cluster Gitea / markitect)
```task ```task
id: ISSUE-WP-0003-T05 id: ISSUE-WP-0003-T05
status: in_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"
``` ```
@@ -188,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: todo status: wait
priority: high priority: high
state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694" state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
``` ```
@@ -207,11 +247,14 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
once issue-core is Ready. once issue-core is Ready.
- Inject `ISSUE_CORE_API_KEY` into the activity-core worker from the same - Inject `ISSUE_CORE_API_KEY` into the activity-core worker from the same
OpenBao secret (T04). OpenBao secret (T04).
- **Contract gap:** issue-core requires `triggering_event_id` as a UUID; - [x] **Contract gap closed on the issue-core side:** `POST /issues/` now
activity-core cron paths may send non-UUID keys (e.g. `"scheduled"`). accepts `triggering_event_id` as a non-empty traceability string, so
Event-driven emission with real event UUIDs works (the `str()` guard in event-driven paths can send UUIDs and cron paths can send stable keys such as
`issue_sink.py`, commit f05c56e, handles UUID objects). Align schemas before `"scheduled"`.
enabling `rest` for cron-triggered rules. - [ ] 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.
@@ -219,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: todo status: wait
priority: medium priority: medium
state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e" state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e"
``` ```
@@ -228,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. - [ ] activity-core -> issue-core emission returns 201 and creates a Gitea issue
- Document the GitOps runbook (image build/push, ArgoCD sync, secret rotation, (remaining T06 handoff).
rollback) in `docs/`. - [x] Document the GitOps runbook (image build/push, ArgoCD sync, secret
- Emit an `add_progress_event` milestone to the hub on completion. contract, smoke, activity-core handoff) in `docs/argocd-gitops.md`.
- 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`.
--- ---