# 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. ## Ops claim vs issue-core (ACT-ADR-005 / ACTIVITY-WP-0026) | Concern | Home | | ------- | ---- | | **Claimable automation run** | `ops_runs` table + `POST /ops-runs/claim` in activity-core | | **Fleet visibility / progress** | IssueSink (`state-hub` default) | | **External tracker ticket** | issue-core โ†’ **Forgejo** (opt-in `ISSUE_SINK_TYPE=rest` only) | **`ops_run` is not issue-core.** Harnesses must claim ops runs; they must not poll issue-core or Forgejo for scheduled FI/Binky-style work. Full contract: `docs/ops-run-queue.md` and `docs/task-emission-consumer-contract.md`. Self-hosted forge product name: **Forgejo** only (no Gitea support path). ## 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 `: ```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. ### Production direct path (verified 2026-08-19) Railiance uses the private cluster-local endpoint: ```text http://issue-core.issue-core.svc.cluster.local:8765 ``` `rapp-issue-core` verification proved authenticated create **201**, close **200**, dependency failure **503**, and recovery **201/200** at immutable image digest `sha256:a56c80ccbd1690bc0bcea384165b1967704f0fdbaf1c506ce1082f56190b92cf`. activity-core still keeps the global default `state-hub`; a healthy transport does not change the opt-in policy for external Forgejo projection. Internal scheduled automation should use **ops_run claim** (WP-0026), not wait on this rest path. Smoke from worker (does not change sink env): ```bash kubectl -n activity-core exec deploy/actcore-worker -- python -c ' # valid IssueCore body โ†’ expect 201; close the controlled issue after the check ' ``` ## 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.