generated from coulomb/repo-seed
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.
This commit is contained in:
8
SCOPE.md
8
SCOPE.md
@@ -135,11 +135,17 @@ user directory.
|
||||
"due_in_days": 7,
|
||||
"source_type": "rule | instruction",
|
||||
"source_id": "string",
|
||||
"triggering_event_id": "uuid",
|
||||
"triggering_event_id": "event uuid or stable source key",
|
||||
"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
|
||||
|
||||
```json
|
||||
|
||||
168
docs/argocd-gitops.md
Normal file
168
docs/argocd-gitops.md
Normal 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.0`
|
||||
- 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 before the workload can
|
||||
be fully reconciled:
|
||||
|
||||
- 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.
|
||||
|
||||
## Secret contract
|
||||
|
||||
Kubernetes Secret name: `issue-core-runtime`
|
||||
|
||||
Current issue-core manifest path, pending railiance-platform confirmation:
|
||||
|
||||
```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.
|
||||
|
||||
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
|
||||
|
||||
Use the published package as the image input. For a reproducible release image,
|
||||
pin the package version to the image tag:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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_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"}'
|
||||
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.
|
||||
@@ -2,14 +2,12 @@
|
||||
Pydantic schemas for the issue-core REST API.
|
||||
|
||||
The TaskIngestionRequest schema matches activity-core's IssueSink TaskSpec
|
||||
payload exactly. See:
|
||||
payload. See:
|
||||
- SCOPE.md "TaskSpec payload" section
|
||||
- activity-core docs/adr/adr-001-event-bridge-architecture.md
|
||||
"""
|
||||
|
||||
from typing import List, Literal, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
@@ -31,7 +29,14 @@ class TaskIngestionRequest(BaseModel):
|
||||
due_in_days: Optional[int] = Field(default=None, ge=0)
|
||||
source_type: SourceType
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -112,6 +112,30 @@ def test_ingest_creates_issue_with_x_api_key(client, valid_payload):
|
||||
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
|
||||
def test_ingest_rejects_invalid_payload(client):
|
||||
bad = {"title": "no required fields"}
|
||||
|
||||
@@ -8,7 +8,7 @@ status: active
|
||||
owner: claude
|
||||
topic_slug: custodian
|
||||
created: "2026-06-19"
|
||||
updated: "2026-06-19"
|
||||
updated: "2026-06-23"
|
||||
state_hub_workstream_id: "896ace77-21b3-450b-8fb7-254aefc8c570"
|
||||
---
|
||||
|
||||
@@ -52,6 +52,15 @@ declarative Application and turning on the idle GitOps capability.
|
||||
registry) → `rsync` manifests → `kubectl apply` (see
|
||||
`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`.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19).
|
||||
@@ -156,7 +165,7 @@ state_hub_task_id: "38887dd6-0988-4ad1-bc6b-2a1b8839829f"
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T04
|
||||
status: todo
|
||||
status: wait
|
||||
priority: high
|
||||
state_hub_task_id: "ad52527f-6222-4c11-9284-d8a3ed3b49ad"
|
||||
```
|
||||
@@ -171,6 +180,9 @@ 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.
|
||||
|
||||
## In-cluster backend config (cluster Gitea / markitect)
|
||||
|
||||
@@ -195,7 +207,7 @@ the cluster Gitea (markitect) backend.
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T06
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
|
||||
```
|
||||
@@ -207,11 +219,10 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
|
||||
once issue-core is Ready.
|
||||
- Inject `ISSUE_CORE_API_KEY` into the activity-core worker from the same
|
||||
OpenBao secret (T04).
|
||||
- **Contract gap:** issue-core requires `triggering_event_id` as a UUID;
|
||||
activity-core cron paths may send non-UUID keys (e.g. `"scheduled"`).
|
||||
Event-driven emission with real event UUIDs works (the `str()` guard in
|
||||
`issue_sink.py`, commit f05c56e, handles UUID objects). Align schemas before
|
||||
enabling `rest` for cron-triggered rules.
|
||||
- [x] **Contract gap closed on the issue-core side:** `POST /issues/` now
|
||||
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"`.
|
||||
- Verify: an activity-core run emits a task that lands in cluster Gitea via
|
||||
issue-core.
|
||||
|
||||
@@ -219,7 +230,7 @@ state_hub_task_id: "96b14cdb-364f-4eab-a80e-dd8b3859c694"
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T07
|
||||
status: todo
|
||||
status: progress
|
||||
priority: medium
|
||||
state_hub_task_id: "8d853b8e-cfca-441d-b817-0a29e37bd66e"
|
||||
```
|
||||
@@ -229,8 +240,8 @@ 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.
|
||||
- Document the GitOps runbook (image build/push, ArgoCD sync, secret rotation,
|
||||
rollback) in `docs/`.
|
||||
- [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.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user