generated from coulomb/repo-seed
99 lines
4.7 KiB
Markdown
99 lines
4.7 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.workstream.completed` | A workstream transitions to canonical status `finished` | `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.
|