activity-core/docs/issue-core-emission-boundary.md
tegwick 21d46927f6
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 7s
Finish ACTIVITY-WP-0023: ESO FORGEJO_TOKEN and gap dispositions
Ship ExternalSecret actcore-forgejo-admin and multi-policy ESO token
bootstrap so weekly prune credentials refresh without bootstrap inject.
Disposition issue-core path A and MarkiTect ROS as external owners;
close workplan finished.
2026-07-21 23:19:10 +02:00

108 lines
3.6 KiB
Markdown

# Task emission boundary (IssueSink)
activity-core owns the decision to spawn a task and the audit trail that says
why it spawned. It does not own downstream task lifecycle state after emission.
## Sink matrix (ACTIVITY-WP-0022)
| `ISSUE_SINK_TYPE` | Destination | Default? |
| --- | --- | --- |
| **`state-hub`** | State Hub progress `activity_task_spawn` | **Yes** (safe for internal findings) |
| **`null`** | Synthetic `null-*` refs only | Dry-run / tests |
| **`rest`** | issue-core `POST /issues/` (may → Forgejo) | **Explicit opt-in only** |
Code default when unset: **`state-hub`** (`DEFAULT_ISSUE_SINK_TYPE`).
Unknown values fall back to `state-hub` and log a warning.
Consumer contract (fields, idempotency, Binky completion):
`docs/task-emission-consumer-contract.md`.
## REST endpoint (opt-in)
```text
POST {ISSUE_CORE_URL}/issues/
```
`IssueCoreRestSink` uses `ISSUE_CORE_API_KEY` via `Authorization: Bearer <key>`:
```json
{
"title": "Run SBOM rescan for activity-core",
"description": "",
"target_repo": "activity-core",
"priority": "medium",
"labels": ["sbom", "security", "automated"],
"due_in_days": null,
"source_type": "rule",
"source_id": "flag-stale-sbom",
"triggering_event_id": "event-or-schedule-key",
"activity_definition_id": "activity-definition-uuid"
}
```
Response: `issue_id` (required), optional `issue_url`, `backend`. Stored in
`task_spawn_log` only as a reference.
## State Hub sink (default)
Each TaskSpec becomes a progress event (`activity_task_spawn` by default).
No Forgejo issue is created. Suitable for Binky rhythm, internal hygiene, and
any definition that should not spam trackers.
## REST versus NATS
Keep REST as the issue-core transport until issue-core owns a durable NATS
command consumer. NATS remains appropriate for **event intake** into
activity-core.
## Operating modes
- **`state-hub` (default):** fleet-visible spawns without Forgejo.
- **`null`:** dry-run / audit.
- **`rest`:** live issue-core; requires healthy backend and intentional policy.
Railiance production uses `state-hub` unless an overlay explicitly sets
`rest` for an experiment.
### Known production failure (2026-07-21, reconfirmed 2026-07-21 T06) — rest path
`POST /issues/` returns **HTTP 503**:
```text
Failed to connect to backend 'forgejo-inbox': Failed to connect to Gitea API
```
**Disposition (ACTIVITY-WP-0023-T06):** activity-core keeps global default
`state-hub` and does **not** flip production to `rest`. Path A is owned by
**issue-core**: rotate/fix `GITEA_BACKEND_TOKEN` (Forgejo backend PAT for the
forgejo-inbox connector — not the activity-core `ISSUE_CORE_API_KEY`
ingestion key). After issue-core proves `POST /issues/`**201**, operators may
opt in per definition / overlay only (WP-0022).
Smoke from worker (does not change sink env):
```bash
kubectl -n activity-core exec deploy/actcore-worker -- python -c '
# valid IssueCore body → expect 201 when path A is healthy, else 503 forgejo-inbox
'
```
## Promotion to rest (one definition at a time)
1. Dry-run with `null` or observe `state-hub` spawns.
2. Confirm issue-core smoke returns **201**.
3. Temporarily set `ISSUE_SINK_TYPE=rest` only if policy allows external issues.
4. Prefer per-definition future opt-in over global rest (WP-0022).
5. Roll back: `ISSUE_SINK_TYPE=state-hub` or `null` + worker restart.
## Verification
```bash
uv run pytest tests/test_issue_sink.py tests/test_integration_event_bridge.py -q
```
## Side-effect resolvers (not IssueSink)
`forgejo_package_prune` with `apply: true` is a **declared platform side-effect**,
not task emission. It requires a non-empty `live_images_file` (ACTIVITY-WP-0023-T03).
See runbook § Weekly maintenance.