Files
state-hub/docs/nats-event-subjects.md
tegwick 166aedfa8d feat: add workplan aliases and legacy meter
Adds preferred workplan REST/event surfaces, legacy-meter telemetry and weekly review summaries, documentation/dashboard terminology updates, dashboard API loading fixes, and close-out sync for STATE-WP-0052 and STATE-WP-0054.
2026-06-04 08:25:31 +02:00

4.9 KiB

NATS Event Subjects — State Hub

Part of CUST-WP-0040. Cross-reference: activity-core's event-types/ registry and ADR-001 (event bridge architecture).

The state hub publishes lifecycle events to NATS JetStream so that activity-core can drive maintenance and reaction automation declaratively, via ActivityDefinition rules — rather than the state hub creating tasks itself.

This document is the authoritative subject naming convention for state hub events. When adding a new event, add a row to the table below first and keep the activity-core event-types/ registry in sync.


Naming convention

org.{producer}.{noun}.{verb}[.{qualifier}]
  • org — top-level namespace shared with activity-core (org.>)
  • {producer} — the publisher subsystem; the state hub uses statehub
  • {noun} — entity the event is about (repo, workstream, task, …)
  • {verb} — past-tense state transition (registered, completed, resolved, …)
  • {qualifier} — optional refinement (e.g. goal.activated)

All segments are lowercase ASCII. No camelCase, no dashes inside segments.

Why a statehub namespace?

Activity-core listens to activity.> for its internal task lifecycle and org.> for org-wide lifecycle events. Multiple publishers will eventually share org.> (e.g. railiance, kaizen). The {producer} segment keeps those publishers from colliding on the same {noun}.{verb} shape.


Published subjects (v1.0)

Subject When Required attributes
org.statehub.repo.registered A new repo is registered via POST /repos/ repo_id, repo_slug, domain_slug, remote_url?, local_path?
org.statehub.workplan.completed A workplan transitions to canonical status finished workplan_id, legacy_workstream_id, slug, title, topic_id, repo_id?, repo_goal_id?
org.statehub.workstream.completed Legacy compatibility subject for completed workplans workstream_id, slug, title, topic_id, repo_id?, repo_goal_id?
org.statehub.decision.resolved A decision is resolved via POST /decisions/{id}/resolve decision_id, title, topic_id?, workstream_id?, decided_by, rationale_snippet
org.statehub.domain.goal.activated A domain goal transitions to active goal_id, domain_id, domain_slug, title, superseded_goal_ids[]
org.statehub.task.stale scripts/cleanup_stale_tasks.py cancels an out-of-date task task_id, workstream_id, workstream_status, task_title, task_status_before

Envelope shape

Each message body conforms to the EventEnvelope schema in api/events/envelope.py, mirrored from activity-core/src/activity_core/models.py:

{
  "id": "uuid v4 — stable, used for at-least-once dedup",
  "type": "org.statehub.repo.registered",
  "version": "1.0",
  "timestamp": "2026-05-17T14:00:00Z",
  "publisher": "state-hub",
  "attributes": { "...": "event-specific" }
}

type matches the subject. publisher is always state-hub for events emitted from this repo.


Stream

State hub events are published into the ACTIVITY_EVENTS JetStream (subject filter org.>). The stream is owned by activity-core; the state hub will auto-create it on first publish if it does not exist, so the publisher works in dev environments without bootstrapping activity-core first. In production both services point at the same NATS cluster and activity-core's EventRouter consumes the stream durably.


Adding a new event

  1. Pick a subject following the convention above.
  2. Add a row to the table in this file (subject, trigger, attributes).
  3. Add a matching event-types/ entry in activity-core.
  4. Wire publish_event(subject, EventEnvelope.new(subject, attributes)) at the site of the state transition (inside the same DB transaction only after await session.commit() — never publish optimistically).
  5. Verify locally: run nats sub 'org.statehub.>' while triggering the transition.

Versioning

version is bumped only when an attribute is removed or its semantics change. Adding optional attributes does not require a version bump. Activity-core consumers must tolerate unknown attribute keys.