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

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

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

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

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.