# 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`: ```json { "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.