activity-core/docs/issue-core-emission-boundary.md
tegwick 4f5399df84
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s
Build and Publish Container Image / build-and-push (push) Successful in 28s
Implement ACTIVITY-WP-0022/0023: safe sink default and gap closures
Default ISSUE_SINK_TYPE to state-hub (no silent Forgejo issues), hard-fail
prune apply without live-images protection, refresh-live-images script,
disable TaskExecutor stub by default, and document consumer/sink contracts.
2026-07-21 21:40:08 +02:00

97 lines
3.2 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) — rest path
`POST /issues/` returned **HTTP 503**:
```text
Failed to connect to backend 'forgejo-inbox': Failed to connect to Gitea API
```
`GITEA_BACKEND_TOKEN` on issue-core rejected by Forgejo. Fix is issue-core
token rotation (`warden route show issue-core-ingestion-api-key`), not
activity-core defaults.
## 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.