4.3 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 usesstate-hubunless an overlay explicitly setsrestfor an experiment.
Production direct path (verified 2026-08-19)
Railiance uses the private cluster-local endpoint:
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):
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)
- Dry-run with
nullor observestate-hubspawns. - Confirm issue-core smoke returns 201.
- Temporarily set
ISSUE_SINK_TYPE=restonly if policy allows external issues. - Prefer per-definition future opt-in over global rest (WP-0022).
- Roll back:
ISSUE_SINK_TYPE=state-hubornull+ 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.