2026-05-17 05:49:29 +02:00
# 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?` |
2026-08-29 15:05:09 +02:00
| `org.statehub.repo.rename.phase` | A rename operation starts, advances, or is safely retried | `operation_id` , `repo_id` , `forge_repository_id` , `old_slug` , `new_slug` , `phase` , `actor` , `expected_source_commit` , `outcome` , `no_op` , phase metrics, `evidence_ref` |
| `org.statehub.repo.rename.failed` | A rename or rollback phase fails closed | Rename phase attributes plus `error_code` ; never error details or supplied evidence |
| `org.statehub.repo.rename.verified` | An operator/API verification evaluates continuity | Rename phase attributes plus `verification_outcome` |
| `org.statehub.repo.rename.rolled_back` | A rename operation reaches `rolled-back` | Rename phase attributes plus rollback attempts and outcome |
| `org.statehub.repo.renamed` | A rename operation reaches `completed` | Rename phase attributes; repository and Forge immutable IDs remain unchanged |
2026-07-08 23:36:58 +02:00
| `org.statehub.workplan.completed` | A workplan transitions to canonical status `finished` | `workplan_id` , `slug` , `title` , `topic_id` , `repo_id?` , `repo_goal_id?` |
| ~~`org.statehub.workstream.completed`~~ | **Retired 2026-07-08** (`STATE-WP-0069` T05). Use `org.statehub.workplan.completed` . | — |
2026-05-17 05:49:29 +02:00
| `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",
2026-05-17 20:01:21 +02:00
"publisher": "state-hub",
2026-05-17 05:49:29 +02:00
"attributes": { "...": "event-specific" }
}
```
`type` matches the subject. `publisher` is always
2026-05-17 20:01:21 +02:00
`state-hub` for events emitted from this repo.
2026-05-17 05:49:29 +02:00
---
## 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.