generated from coulomb/repo-seed
docs(adr): establish three foundational ADRs for Event Bridge architecture
ADR-001: activity-core as org-wide Event Bridge — boundaries, NATS as org infrastructure, state hub delegation, rules-core module-first, issue-core adapter interface, capabilities domain assignment. ADR-002: markdown-as-definition format for event types and ActivityDefinitions — co-located intent/schema/logic/debugging, publisher-declared governance with environment-configurable curator gate, attribute type system, task template files. ADR-003: Rule vs. Instruction model and expression DSL — sandboxed Python-like AST evaluator for Rules, trusted-fields prompt injection protection for Instructions, output schema enforcement, audit trail, testing strategy, rules-core module boundary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
190
docs/adr/adr-001-event-bridge-architecture.md
Normal file
190
docs/adr/adr-001-event-bridge-architecture.md
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
id: ACT-ADR-001
|
||||
type: architecture-decision-record
|
||||
title: "Activity-Core as Coulomb Org Event Bridge"
|
||||
status: accepted
|
||||
decided_by: Bernd Worsch
|
||||
date: "2026-05-14"
|
||||
scope: cross-repo
|
||||
affects:
|
||||
- activity-core
|
||||
- the-custodian/state-hub
|
||||
- issue-facade (→ issue-core)
|
||||
- repo-scoping
|
||||
tags: ["architecture", "event-bridge", "activity-core", "orchestration", "event-loop"]
|
||||
---
|
||||
|
||||
# ACT-ADR-001: Activity-Core as Coulomb Org Event Bridge
|
||||
|
||||
## Status
|
||||
|
||||
Accepted.
|
||||
|
||||
## Context
|
||||
|
||||
The Coulomb organization's set of repositories, services, and deployments is growing
|
||||
beyond what a single person can coordinate manually. The state hub tracks cross-domain
|
||||
state but has no mechanism to automatically respond to it. Recurring maintenance
|
||||
(dependency scans, SBOM staleness checks, consistency audits) is implemented as
|
||||
bespoke cron jobs baked into individual services — scattered, hard to audit, and
|
||||
impossible to govern from a single vantage point.
|
||||
|
||||
Three forces drive the need for a dedicated orchestration layer:
|
||||
|
||||
1. **Scale**: as the repo count grows, manual coordination becomes the bottleneck.
|
||||
2. **Reactivity**: org-level events (new repo registered, CVE published, deployment
|
||||
completed) should trigger coordinated responses without human intervention.
|
||||
3. **Separation of concerns**: the state hub is a read model and should remain one.
|
||||
It must not accumulate automation logic to avoid becoming a God object.
|
||||
|
||||
## Decision
|
||||
|
||||
**activity-core is the org-wide Event Bridge for the Coulomb organization.**
|
||||
|
||||
Its responsibility is exactly three things:
|
||||
1. **Receive events** — time-based (cron, one-off scheduled) and domain events (NATS,
|
||||
Gitea webhooks, state hub lifecycle signals).
|
||||
2. **Evaluate rules and instructions** — given event payload and resolved context,
|
||||
determine what work must be created.
|
||||
3. **Emit task sets** — publish structured task creation requests to issue-core.
|
||||
|
||||
It does not execute work. It does not track task lifecycle. It does not manage projects.
|
||||
|
||||
### Boundary rules
|
||||
|
||||
| Concern | Owner |
|
||||
|---|---|
|
||||
| Cross-org task scheduling and reactive automation | **activity-core** |
|
||||
| Task lifecycle (create, assign, track, close) | **issue-core** |
|
||||
| Project and initiative management (phased, completion-gated) | **project-core** (future) |
|
||||
| Repository capability profiling | **repo-scoping** |
|
||||
| Cross-domain coordination state | **state hub** |
|
||||
| Execution of automatable tasks | Temporal workers (per-repo) |
|
||||
|
||||
### Event type registry
|
||||
|
||||
Event types are declared by publishers as markdown definition files (see ACT-ADR-002).
|
||||
Governance is **publisher-declared by default**: a publisher registers its event types
|
||||
by committing definition files to the event-types registry. In production environments,
|
||||
a curator gate can be enabled — registry entries must be reviewed before the runtime
|
||||
accepts events of that type. This is a configuration flag per runtime scope (dev,
|
||||
staging, prod), not a hard-coded rule.
|
||||
|
||||
### State hub relationship
|
||||
|
||||
The state hub **delegates automation to activity-core** rather than implementing it
|
||||
internally. Concretely:
|
||||
|
||||
- Maintenance jobs currently baked into the state hub (consistency sync, SBOM
|
||||
staleness checks) are migrated to ActivityDefinitions in activity-core.
|
||||
- The state hub becomes a **publisher** of lifecycle events on NATS
|
||||
(`org.workstream.created`, `org.decision.resolved`, `org.repo.registered`, etc.).
|
||||
- The state hub does not subscribe to activity-core's output directly; it reads
|
||||
task state from issue-core when needed.
|
||||
|
||||
This preserves the state hub as a read model and makes activity-core the single
|
||||
home for automation policy.
|
||||
|
||||
### rules-core: module-first
|
||||
|
||||
The rule and instruction evaluation engine starts as `src/activity_core/rules/` — a
|
||||
module with a clean internal boundary (no imports from Temporal, Postgres, or FastAPI
|
||||
within the module). Extraction to a standalone `rules-core` repository happens when a
|
||||
**second consumer** (e.g. state hub governance, project-core) needs the engine. This
|
||||
follows the same discipline as the task-flow-engine extraction plan (CUST-TFE-SCOPE).
|
||||
|
||||
### NATS as org infrastructure
|
||||
|
||||
NATS JetStream is promoted from an activity-core internal component to **org-wide
|
||||
event bus infrastructure**. It runs as a standalone service (not bundled in
|
||||
activity-core's docker-compose) with its own lifecycle. All services that publish
|
||||
or subscribe to org events do so via NATS streams.
|
||||
|
||||
### issue-core integration
|
||||
|
||||
activity-core communicates with issue-core via a **task emission adapter** — an
|
||||
abstraction layer that, in the initial implementation, calls issue-core's REST API.
|
||||
The adapter interface is defined now; the transport can migrate to NATS subscription
|
||||
(issue-core subscribes to `task.spawned` events) once issue-core adds that capability.
|
||||
This avoids hardcoding REST coupling throughout the codebase.
|
||||
|
||||
### Webhook receiver
|
||||
|
||||
A new HTTP endpoint within activity-core accepts inbound webhooks from Gitea (and
|
||||
later GitHub, other services). It normalises payloads to the canonical EventEnvelope
|
||||
format, validates against the event type registry, and publishes to NATS. This runs
|
||||
alongside the existing FastAPI `api.py`.
|
||||
|
||||
### Domain assignment
|
||||
|
||||
activity-core and issue-core are assigned to the **`capabilities`** domain — the
|
||||
same domain as repo-scoping. These are org-wide infrastructure tools that serve all
|
||||
domains equally, not artefacts of any single project or custodian's personal workflow.
|
||||
issue-core is explicitly disassociated from the markitect domain.
|
||||
|
||||
## Trigger types
|
||||
|
||||
Three trigger types are supported:
|
||||
|
||||
| Type | Description | Temporal mechanism |
|
||||
|---|---|---|
|
||||
| `cron` | Recurring schedule (5-field cron + timezone + misfire policy) | Temporal Schedule (implemented WP-0002) |
|
||||
| `event` | React to a named event type on NATS | Temporal workflow started by Event Router |
|
||||
| `scheduled` | One-off at a future datetime | Temporal Schedule with `remaining_actions: 1` |
|
||||
|
||||
`scheduled` is a new trigger type added in WP-0003.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Immediate
|
||||
|
||||
- activity-core's `INTENT.md` and `SCOPE.md` are rewritten to reflect this architecture.
|
||||
- The `task_instances` Postgres table is reclassified as a **spawn audit trail** —
|
||||
it records the act of spawning (what was created, when, which issue-core reference)
|
||||
but is not the authoritative task record. Authoritative lifecycle state lives in
|
||||
issue-core.
|
||||
- A task emission adapter interface (`src/activity_core/issue_sink.py`) replaces any
|
||||
direct Postgres writes to `task_instances` with calls through the adapter.
|
||||
- The `TaskExecutorWorkflow` stub from WP-0001 is replaced with the actual adapter
|
||||
call in WP-0003.
|
||||
|
||||
### Medium term
|
||||
|
||||
- State hub adds NATS publishing to its lifecycle operations.
|
||||
- Gitea webhook receiver added to activity-core as a new HTTP router.
|
||||
- Existing state hub maintenance crons are migrated to ActivityDefinitions.
|
||||
- issue-facade is renamed issue-core and re-registered under the `capabilities` domain.
|
||||
|
||||
### Long term
|
||||
|
||||
- rules-core extracted as a standalone package when a second consumer appears.
|
||||
- project-core created (depends on task-flow-engine extraction) for multi-phase
|
||||
initiative management — explicitly out of scope for activity-core.
|
||||
- NATS gets its own operational runbook and monitoring as org infrastructure.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
**State hub absorbs activity-core functionality**: rejected — turns the state hub into
|
||||
a God object, violates the read-model boundary, and makes automation logic impossible
|
||||
to test independently.
|
||||
|
||||
**Per-repo automation (GitHub Actions style)**: rejected — cross-repo coordination
|
||||
requires a single vantage point that can see all repos; per-repo actions can't express
|
||||
org-level triggers or context.
|
||||
|
||||
**Activity-core as a thin Temporal wrapper only**: rejected — without the event type
|
||||
registry and rule model, it's just a scheduler. The governance and introspection
|
||||
properties are the point.
|
||||
|
||||
**Separate rules-core from day one**: rejected — premature extraction adds dependency
|
||||
management overhead before a second consumer exists. Module-first with a clean boundary
|
||||
costs nothing and preserves the extraction option.
|
||||
|
||||
## Related
|
||||
|
||||
- ACT-ADR-002 — Event type and ActivityDefinition definition format
|
||||
- ACT-ADR-003 — Rule vs. Instruction model and DSL
|
||||
- CUST-ADR-001 — Workplans as repository artefacts (canon/architecture/)
|
||||
- CUST-TFE-SCOPE-2026-000001 — task-flow-engine extraction plan (canon/projects/)
|
||||
- activity-core INTENT.md (to be written)
|
||||
- activity-core WP-0003 (to be written)
|
||||
Reference in New Issue
Block a user