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

100 lines
4.9 KiB
Markdown

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