generated from coulomb/repo-seed
Add railiance01 deployment artifacts and fix container image build
Introduce Dockerfile, entrypoint, and k8s/railiance manifests for the ArgoCD GitOps pilot (ISSUE-WP-0003). Rename the Gitea PyPI build arg to GITEA_PYPI_INDEX_URL so pip still resolves dependencies from PyPI.
This commit is contained in:
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# issue-core REST ingestion service image.
|
||||
#
|
||||
# Installs the published issue-core[api] package from the Coulomb Gitea PyPI
|
||||
# index (no sibling-checkout build context) and runs the FastAPI ingestion
|
||||
# server on :8765. Built and pushed to gitea.coulomb.social/coulomb/issue-core.
|
||||
FROM python:3.12-slim AS runtime
|
||||
|
||||
ARG ISSUE_CORE_VERSION=">=0.2,<0.3"
|
||||
# Do not name this PIP_INDEX_URL — Docker exposes ARGs as env vars during RUN,
|
||||
# and pip treats PIP_INDEX_URL as the sole primary index (excluding PyPI).
|
||||
ARG GITEA_PYPI_INDEX_URL=https://gitea.coulomb.social/api/packages/coulomb/pypi/simple/
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
HOME=/home/app
|
||||
|
||||
# Non-root runtime user; HOME drives issue-core's config dir
|
||||
# (~/.config/issue-tracker/backends.json).
|
||||
RUN useradd --create-home --home-dir /home/app --uid 10001 app
|
||||
|
||||
RUN pip install --no-cache-dir \
|
||||
--index-url https://pypi.org/simple \
|
||||
--extra-index-url "${GITEA_PYPI_INDEX_URL}" \
|
||||
"issue-core[api]${ISSUE_CORE_VERSION}"
|
||||
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
USER app
|
||||
EXPOSE 8765
|
||||
|
||||
# Entrypoint renders backends.json from env, then execs the server.
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
30
docker-entrypoint.sh
Normal file
30
docker-entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
# Render issue-core backends.json from environment, then start the API.
|
||||
#
|
||||
# The backend structure (host/owner/repo/default) is non-secret and supplied
|
||||
# via the BACKENDS_TEMPLATE env (a ConfigMap), with the Gitea token injected
|
||||
# from GITEA_BACKEND_TOKEN (an ExternalSecret-materialized Secret). The token
|
||||
# is never baked into the image or committed to Git.
|
||||
set -eu
|
||||
|
||||
CONFIG_DIR="${HOME}/.config/issue-tracker"
|
||||
mkdir -p "${CONFIG_DIR}"
|
||||
|
||||
: "${BACKENDS_TEMPLATE:?BACKENDS_TEMPLATE env is required}"
|
||||
|
||||
# Substitute the token placeholder using python (always present in the image)
|
||||
# to avoid shell-escaping issues with the secret value.
|
||||
GITEA_BACKEND_TOKEN="${GITEA_BACKEND_TOKEN:-}" \
|
||||
BACKENDS_TEMPLATE="${BACKENDS_TEMPLATE}" \
|
||||
python - "${CONFIG_DIR}/backends.json" <<'PY'
|
||||
import json, os, sys
|
||||
tmpl = json.loads(os.environ["BACKENDS_TEMPLATE"])
|
||||
token = os.environ.get("GITEA_BACKEND_TOKEN", "")
|
||||
for cfg in tmpl.values():
|
||||
if isinstance(cfg, dict) and cfg.get("token") == "__FROM_ENV__":
|
||||
cfg["token"] = token
|
||||
with open(sys.argv[1], "w") as fh:
|
||||
json.dump(tmpl, fh, indent=2)
|
||||
PY
|
||||
|
||||
exec issue serve --host 0.0.0.0 --port 8765 --log-level "${LOG_LEVEL:-info}"
|
||||
24
k8s/railiance/configmap-backends.yaml
Normal file
24
k8s/railiance/configmap-backends.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Non-secret backend structure for issue-core inside railiance01.
|
||||
# Default backend = cluster Gitea (markitect). The Gitea token is NOT here;
|
||||
# it is injected at startup from GITEA_BACKEND_TOKEN (ExternalSecret) where the
|
||||
# template carries the sentinel "__FROM_ENV__".
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: issue-core-backends
|
||||
namespace: issue-core
|
||||
labels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
data:
|
||||
backends.json: |
|
||||
{
|
||||
"markitect": {
|
||||
"type": "gitea",
|
||||
"base_url": "http://gitea-http.default.svc.cluster.local:3000",
|
||||
"owner": "coulomb",
|
||||
"repo": "markitect_project",
|
||||
"token": "__FROM_ENV__"
|
||||
},
|
||||
"default": "markitect"
|
||||
}
|
||||
71
k8s/railiance/deployment.yaml
Normal file
71
k8s/railiance/deployment.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: issue-core
|
||||
namespace: issue-core
|
||||
labels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1" # after the ExternalSecret (wave 0)
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
spec:
|
||||
# Image is public-pullable from the Gitea registry (per railiance-forge
|
||||
# docs). Add imagePullSecrets: [{name: gitea-registry}] if it becomes private.
|
||||
containers:
|
||||
- name: issue-core
|
||||
image: gitea.coulomb.social/coulomb/issue-core:0.2.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8765
|
||||
env:
|
||||
- name: ISSUE_CORE_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: issue-core-runtime
|
||||
key: ISSUE_CORE_API_KEY
|
||||
- name: GITEA_BACKEND_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: issue-core-runtime
|
||||
key: GITEA_BACKEND_TOKEN
|
||||
- name: BACKENDS_TEMPLATE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: issue-core-backends
|
||||
key: backends.json
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
37
k8s/railiance/externalsecret.yaml
Normal file
37
k8s/railiance/externalsecret.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
# Runtime secrets for issue-core, materialized from OpenBao by External Secrets
|
||||
# Operator (cluster default per railiance-platform docs/argocd-gitops.md).
|
||||
#
|
||||
# DEPENDENCY: External Secrets Operator is not yet installed on railiance01 and
|
||||
# the OpenBao path below must be provisioned by railiance-platform. Until then
|
||||
# this resource will not reconcile and the Deployment stays Pending the Secret.
|
||||
#
|
||||
# OpenBao path: platform/workloads/issue-core/issue-core/issue-core-runtime
|
||||
# properties: ISSUE_CORE_API_KEY, GITEA_BACKEND_TOKEN
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: issue-core-runtime
|
||||
namespace: issue-core
|
||||
labels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0" # before the Deployment (wave 1)
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
# Provisioned by railiance-platform during ESO install; name TBC on bootstrap.
|
||||
name: openbao
|
||||
kind: ClusterSecretStore
|
||||
target:
|
||||
name: issue-core-runtime
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: ISSUE_CORE_API_KEY
|
||||
remoteRef:
|
||||
key: platform/workloads/issue-core/issue-core/issue-core-runtime
|
||||
property: ISSUE_CORE_API_KEY
|
||||
- secretKey: GITEA_BACKEND_TOKEN
|
||||
remoteRef:
|
||||
key: platform/workloads/issue-core/issue-core/issue-core-runtime
|
||||
property: GITEA_BACKEND_TOKEN
|
||||
12
k8s/railiance/kustomization.yaml
Normal file
12
k8s/railiance/kustomization.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# issue-core workload manifests, synced by the ArgoCD `issue-core` Application
|
||||
# (path k8s/railiance, destination namespace issue-core, CreateNamespace=true).
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: issue-core
|
||||
|
||||
resources:
|
||||
- externalsecret.yaml
|
||||
- configmap-backends.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
19
k8s/railiance/service.yaml
Normal file
19
k8s/railiance/service.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
# ClusterIP exposing issue-core on 8765 as
|
||||
# issue-core.issue-core.svc.cluster.local:8765 — the address activity-core's
|
||||
# ISSUE_CORE_URL points at once its k8s runtime port is corrected (8010 -> 8765).
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: issue-core
|
||||
namespace: issue-core
|
||||
labels:
|
||||
app.kubernetes.io/name: issue-core
|
||||
app.kubernetes.io/part-of: railiance-gitops
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: issue-core
|
||||
ports:
|
||||
- name: http
|
||||
port: 8765
|
||||
targetPort: http
|
||||
235
workplans/ISSUE-WP-0003-railiance01-deployment.md
Normal file
235
workplans/ISSUE-WP-0003-railiance01-deployment.md
Normal file
@@ -0,0 +1,235 @@
|
||||
---
|
||||
id: ISSUE-WP-0003
|
||||
type: workplan
|
||||
title: "Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)"
|
||||
domain: custodian
|
||||
repo: issue-core
|
||||
status: active
|
||||
owner: claude
|
||||
topic_slug: custodian
|
||||
created: "2026-06-19"
|
||||
updated: "2026-06-19"
|
||||
state_hub_workstream_id: ""
|
||||
---
|
||||
|
||||
# Deploy issue-core as a service on railiance01 (ArgoCD GitOps pilot)
|
||||
|
||||
`issue-core` is the authoritative task-lifecycle manager and the REST ingestion
|
||||
target for activity-core's `IssueSink`. Deployment artifacts (`Dockerfile`,
|
||||
`docker-entrypoint.sh`, `k8s/railiance/`) are now in-repo; the image builds
|
||||
locally and `/healthz` returns 200. The railiance01 cluster still has no
|
||||
`issue-core` namespace or workload — nothing is deployed until T01 push and
|
||||
T02 ArgoCD bootstrap complete.
|
||||
|
||||
This workplan stands up `issue-core` as a first-class in-cluster service on
|
||||
railiance01 **via ArgoCD GitOps** — making issue-core the cluster's first
|
||||
declarative Application and turning on the idle GitOps capability.
|
||||
|
||||
## Current state (verified 2026-06-19)
|
||||
|
||||
- **Deployment artifacts in-repo:** `Dockerfile`, `docker-entrypoint.sh`, and
|
||||
`k8s/railiance/` (Kustomize: ExternalSecret, ConfigMap, Deployment, Service).
|
||||
Image builds locally; `docker run` + `GET /healthz` returns 200. Image **not
|
||||
yet pushed** to `gitea.coulomb.social/coulomb/issue-core:0.2.0`.
|
||||
- **Dockerfile fix (2026-06-19):** build arg renamed `GITEA_PYPI_INDEX_URL` —
|
||||
`ARG PIP_INDEX_URL` leaked into the build env and pip used Gitea as the sole
|
||||
index, so dependencies like `click` were not found.
|
||||
- **railiance01 cluster:** no `issue-core` namespace; no issue-core
|
||||
Deployment/Service/Pod in any namespace.
|
||||
- **Dangling reference:** `activity-core/k8s/railiance/20-runtime.yaml` sets
|
||||
`ISSUE_CORE_URL: http://issue-core.issue-core.svc.cluster.local:8010` — a
|
||||
service that does not exist, on the **wrong port** (issue-core serves 8765) —
|
||||
with `ISSUE_SINK_TYPE: "null"` so emission is disabled. It is a placeholder.
|
||||
- **Packaging precursor is done:** `ISSUE-WP-0002` published
|
||||
`issue-core==0.2.0` to the Coulomb Gitea PyPI index.
|
||||
- **ArgoCD is installed but unused:** all 7 components healthy (~290d), but
|
||||
**0 Applications, 0 ApplicationSets, 0 registered git repos**, only the stock
|
||||
`default` AppProject. No `kind: Application` manifests exist in any infra repo.
|
||||
- **Existing deploy pattern is imperative** (the path we are *replacing* for
|
||||
this service): local `docker build` → `k3s ctr images import` (side-load, no
|
||||
registry) → `rsync` manifests → `kubectl apply` (see
|
||||
`activity-core/k8s/railiance/README.md`).
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Deployment method = ArgoCD GitOps** (operator decision 2026-06-19).
|
||||
issue-core is the pilot Application; the imperative side-load pattern is not
|
||||
used for this service.
|
||||
- **ArgoCD bootstrap owned by `railiance-platform`** (operator decision
|
||||
2026-06-19). Platform owns repo registration, AppProject/app-of-apps
|
||||
conventions, and the External-Secrets/OpenBao plumbing. issue-core only
|
||||
**contributes** its `Application` manifest + workload manifests into the
|
||||
agreed GitOps source. T02 is therefore a cross-repo dependency, not
|
||||
issue-core work — see handoff to railiance-platform.
|
||||
- **Backend = cluster Gitea (markitect)** (operator decision 2026-06-19).
|
||||
Ingested tasks route to the existing Gitea backend; no new Postgres/PVC.
|
||||
- **Secret management = OpenBao.** `ISSUE_CORE_API_KEY` is a shared ingestion
|
||||
key injected from OpenBao on both issue-core and the activity-core worker.
|
||||
ops-warden does **not** vend it (see
|
||||
`~/ops-warden/wiki/playbooks/activity-core-issue-sink.md`). Coordinate the
|
||||
canonical path with `railiance-platform` (`issue-core-ingestion-api-key`).
|
||||
- **Image delivery = container registry, not side-load.** GitOps requires a
|
||||
pullable image tag in a registry the cluster can reach (the Coulomb Gitea
|
||||
container registry); side-loading defeats declarative reproducibility.
|
||||
|
||||
## Open questions
|
||||
|
||||
- **GitOps source repo.** Resolved by `railiance-platform` as part of the
|
||||
bootstrap (T02 dependency): where issue-core's `Application` + manifests are
|
||||
expected to live (its own `issue-core/k8s/` vs. a platform GitOps repo) and
|
||||
the AppProject/app-of-apps convention to follow.
|
||||
- **Registry path & pull secret.** Confirm the Coulomb Gitea container registry
|
||||
path and the cluster pull-secret posture (tracked in `railiance-forge`
|
||||
container-registry docs and `railiance-apps-WP-0004` I03).
|
||||
|
||||
---
|
||||
|
||||
## Container image published to a pullable registry
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T01
|
||||
status: in_progress
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Goal.** A reproducible, registry-hosted image ArgoCD-managed pods can pull.
|
||||
|
||||
- [x] Add `Dockerfile` installing `issue-core[api]>=0.2,<0.3` from the Gitea
|
||||
PyPI index (with explicit PyPI primary index). Entrypoint renders
|
||||
`backends.json` then `issue serve --host 0.0.0.0 --port 8765`.
|
||||
- [x] Local build succeeds; `docker run` + `GET /healthz` returns 200.
|
||||
- [ ] Build and **push to the Coulomb Gitea container registry** (confirm path
|
||||
per Open questions); tag `0.2.0`.
|
||||
- [ ] Configure the cluster pull secret so `issue-core` namespace pods can pull.
|
||||
- [ ] Verify: `POST /issues/` smoke; pushed tag pullable from the cluster.
|
||||
|
||||
## ArgoCD bootstrap (railiance-platform dependency) + issue-core Application
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T02
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Owner split.** ArgoCD bootstrap is **railiance-platform's** (operator
|
||||
decision 2026-06-19): repo registration in ArgoCD, AppProject/app-of-apps
|
||||
convention, and the agreed GitOps source layout. This task is `wait` on that
|
||||
handoff. issue-core's part is to **contribute** the `Application` manifest +
|
||||
workload manifests into the layout platform defines.
|
||||
|
||||
- **(railiance-platform)** Register the GitOps source repo (repository Secret +
|
||||
creds); define AppProject for cluster services; publish the source-repo/path
|
||||
convention and sync policy.
|
||||
- **(issue-core)** Once the convention is known: author the `issue-core` ArgoCD
|
||||
`Application` manifest (source repo/path/revision → destination `issue-core`
|
||||
namespace) per the platform layout.
|
||||
- Verify: `kubectl get applications -n argocd` shows `issue-core`
|
||||
Synced/Healthy; ArgoCD reconciles a trivial manifest change.
|
||||
|
||||
## Kubernetes manifests (namespace, Deployment, Service) in GitOps source
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T03
|
||||
status: in_progress
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Goal.** Declarative manifests in the GitOps source repo, synced by T02.
|
||||
|
||||
- [x] `k8s/railiance/` Kustomize bundle (namespace via ArgoCD
|
||||
`CreateNamespace=true`).
|
||||
- [x] Deployment: registry image tag `0.2.0`; port 8765; `/healthz` probes;
|
||||
resource requests/limits; env from ExternalSecret (T04) and ConfigMap (T05).
|
||||
- [x] Service: ClusterIP on **8765** as
|
||||
`issue-core.issue-core.svc.cluster.local`.
|
||||
- [ ] Verify: ArgoCD syncs the manifests; Pod Ready; `/healthz` 200 from a debug
|
||||
pod (blocked on T01 push + T02 bootstrap + T04 secrets).
|
||||
|
||||
## OpenBao secret: ISSUE_CORE_API_KEY
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T04
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Goal.** The shared ingestion key delivered to both sides from OpenBao.
|
||||
|
||||
- Provision `ISSUE_CORE_API_KEY` in OpenBao at the canonical path (coordinate
|
||||
with `railiance-platform`; catalog id `issue-core-ingestion-api-key`).
|
||||
- Deliver into the issue-core Deployment (T03) and the activity-core worker
|
||||
(T06) with the **same** value (External Secrets / Bao injector — match the
|
||||
cluster's established mechanism).
|
||||
- Never write the value to Git, manifests, State Hub, or logs.
|
||||
- Verify: both pods resolve a non-empty key; auth round-trip (401 without,
|
||||
201 with).
|
||||
|
||||
## In-cluster backend config (cluster Gitea / markitect)
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T05
|
||||
status: in_progress
|
||||
priority: medium
|
||||
```
|
||||
|
||||
**Goal.** issue-core's `backends.json` inside the cluster points `default` at
|
||||
the cluster Gitea (markitect) backend.
|
||||
|
||||
- [x] ConfigMap `issue-core-backends` with in-cluster Gitea URL
|
||||
(`gitea-http.default.svc.cluster.local:3000`); token sentinel `__FROM_ENV__`.
|
||||
- [x] `docker-entrypoint.sh` renders `~/.config/issue-tracker/backends.json`
|
||||
from `BACKENDS_TEMPLATE` + `GITEA_BACKEND_TOKEN` at startup.
|
||||
- [ ] Verify: a `POST /issues/` creates a real Gitea issue and returns
|
||||
`issue_url` (blocked on T04 secrets + in-cluster deployment).
|
||||
|
||||
## Wire activity-core to the live service
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T06
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
**Goal.** activity-core emits to the live issue-core Service.
|
||||
|
||||
- Fix `activity-core/k8s/railiance/20-runtime.yaml`:
|
||||
`ISSUE_CORE_URL` port `8010 -> 8765`; flip `ISSUE_SINK_TYPE` `null -> rest`
|
||||
once issue-core is Ready.
|
||||
- Inject `ISSUE_CORE_API_KEY` into the activity-core worker from the same
|
||||
OpenBao secret (T04).
|
||||
- **Contract gap:** issue-core requires `triggering_event_id` as a UUID;
|
||||
activity-core cron paths may send non-UUID keys (e.g. `"scheduled"`).
|
||||
Event-driven emission with real event UUIDs works (the `str()` guard in
|
||||
`issue_sink.py`, commit f05c56e, handles UUID objects). Align schemas before
|
||||
enabling `rest` for cron-triggered rules.
|
||||
- Verify: an activity-core run emits a task that lands in cluster Gitea via
|
||||
issue-core.
|
||||
|
||||
## End-to-end verification + GitOps runbook
|
||||
|
||||
```task
|
||||
id: ISSUE-WP-0003-T07
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
**Goal.** Confirm the deployed service is healthy and document the new path.
|
||||
|
||||
- ArgoCD Application Synced/Healthy; issue-core Pod Ready; Service reachable
|
||||
cluster-internal.
|
||||
- activity-core → issue-core emission returns 201 and creates a Gitea issue.
|
||||
- Document the GitOps runbook (image build/push, ArgoCD sync, secret rotation,
|
||||
rollback) in `docs/`.
|
||||
- Emit an `add_progress_event` milestone to the hub on completion.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `ISSUE-WP-0002` — Gitea PyPI publication (packaging precursor, finished).
|
||||
- `railiance-apps-WP-0004` I03 — issue-core packaging/image enablement notes.
|
||||
- `railiance-forge` — Gitea container registry docs.
|
||||
- `activity-core/docs/issue-core-emission-boundary.md` — emission contract.
|
||||
- `activity-core/k8s/railiance/README.md` — the imperative pattern being
|
||||
superseded for this service.
|
||||
- `~/ops-warden/wiki/playbooks/activity-core-issue-sink.md` — key routing.
|
||||
Reference in New Issue
Block a user