activity-core/docs/issue-core-emission-boundary.md
tegwick 15eb3a2066
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 52s
Implement ACTIVITY-WP-0026 ops_run claim queue (T01–T06).
Add durable claimable ops_runs table, emit dual-write on TaskSpec, REST
claim/lease/complete/fail API, ops status visibility, and consumer docs
aligned with ACT-ADR-005. T07 railiance rollout remains deploy-side.
2026-08-03 19:22:50 +02:00

4.5 KiB

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)

POST {ISSUE_CORE_URL}/issues/

IssueCoreRestSink uses ISSUE_CORE_API_KEY via Authorization: Bearer <key>:

{
  "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:

Failed to connect to backend 'forgejo-inbox': Failed to connect to Gitea API

(Historical error string from the Forgejo-compatible client library — the deployed forge is Forgejo, not Gitea.)

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).

Internal scheduled automation should use ops_run claim (WP-0026), not wait on this rest path.

Smoke from worker (does not change sink env):

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

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.