Files
activity-core/workplans/custodian-WP-0003b-event-bridge-parser-workflow-triggers.md

266 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: custodian-WP-0003b
type: workplan
domain: custodian
repo: activity-core
status: done
state_hub_workstream_id: b4eb45a9-69e3-4ab0-b00c-67a53c3117c5
split_from: custodian-WP-0003
split_part: 2 of 3
depends_on:
- custodian-WP-0003a # model, rules, registry must be done first
tasks:
- id: T44
title: ActivityDefinition file parser
status: done
priority: high
state_hub_task_id: a86dcffe-789a-482e-bc5f-a5ac9db9608e
- id: T45
title: ActivityDefinition sync command
status: done
priority: medium
state_hub_task_id: a714c511-9f93-4594-b745-9330bc645384
- id: T46
title: Wire rule/instruction evaluator into RunActivityWorkflow
status: done
priority: high
state_hub_task_id: 615dec10-7e63-46b3-80ad-83ff2e51d6ba
- id: T47
title: Add ScheduledTriggerConfig (one-off future datetime)
status: done
priority: medium
state_hub_task_id: 1e06cb43-d0e3-4af5-9216-0877cac2082d
- id: T48
title: Implement one-off Temporal Schedule
status: done
priority: medium
state_hub_task_id: 01b507e3-6420-422f-83ed-90c7b4d49bb7
- id: T49
title: Implement webhook receiver
status: done
priority: medium
state_hub_task_id: 673ca7b2-49cc-4314-8be2-9848f686da37
- id: T50
title: Write Gitea webhook event type definitions
status: done
priority: medium
state_hub_task_id: 7ba289e9-fef6-469a-9504-c3664227a2f7
created: "2026-05-14"
---
# activity-core WP-0003b — Parser, Workflow Wiring, Triggers & Webhooks
**Split from:** custodian-WP-0003 (part 2 of 3)
**Hub workstream:** `b4eb45a9-69e3-4ab0-b00c-67a53c3117c5`
**Depends on:** custodian-WP-0003a (model, rules, registry)
**Next part:** custodian-WP-0003c (context adapters, integration)
**Architecture:** ACT-ADR-001, ACT-ADR-002, ACT-ADR-003
## Purpose
Phases 9 and 10 of the Event Bridge implementation. With the rules module,
IssueSink, and event type registry in place (WP-0003a), this part wires
everything into the workflow: markdown-format ActivityDefinition parser,
sync command, rule/instruction pipeline in RunActivityWorkflow, one-off
scheduled trigger type, and Gitea webhook receiver.
## Prerequisites
All tasks from custodian-WP-0003a must be done:
- `src/activity_core/rules/` module with `evaluate_condition` and `execute_instruction`
- `IssueSink` ABC + `NullSink` + `IssueCoreRestSink`
- `EventEnvelope` model finalized
- `event_types` table and `sync-event-types` command working
## Build Order
```
Phase 9:
T44 (parser) → T45 (sync) → T46 (wire workflow)
T46 also needs: T36 (RuleEvaluator), T37 (InstructionExecutor), T39 (IssueSink) from 0003a
Phase 10 (parallel with phase 9):
T47 (model) → T48 (Temporal schedule)
T49, T50 (parallel — need T40, T41 from 0003a)
```
---
## Phase 9 — ActivityDefinition as Markdown
### T44: ActivityDefinition file parser
`src/activity_core/definition_parser.py`
Scans `activity-definitions/*.md` in the local repo and any registered domain
repos listed in `ACTIVITY_DEFINITION_DIRS` env var (colon-separated paths).
Parses:
- YAML frontmatter → trigger config, context_sources, governance, owner, status.
- Fenced ` ```rule ` blocks → `RuleDef` list (YAML body).
- Fenced ` ```instruction ` blocks → `InstructionDef` list (YAML body).
Returns `ActivityDefinitionDef`. Raises `ParseError(file, line, message)` on
malformed input — never silently ignores a broken definition.
---
### T45: ActivityDefinition sync command
`make sync-activity-definitions` (also in `worker.py` startup sequence, before
the Temporal run loop).
Extend `activity_definitions` table: add `rules_json` (JSONB) and
`instructions_json` (JSONB) columns. On sync: parse all definition files,
upsert rows. Definitions absent from filesystem set `status = 'inactive'`
(soft delete — Temporal Schedules for those definitions are paused, not deleted).
Alembic migration for new columns included in this task.
---
### T46: Wire rule/instruction evaluator into RunActivityWorkflow
Replace `RunActivityWorkflow`'s current unconditional task-spawning logic:
```
load_activity_definition()
→ resolve_context() [calls each context_source adapter]
→ evaluate_rules() [RuleEvaluator — all rules, all-match]
→ execute_instructions() [InstructionExecutor — all passing pre-filters]
→ emit_tasks() [IssueSink.emit() for each TaskSpec]
→ log_run() [activity_runs row + task_spawn_log rows]
```
A rule that raises `UnsafeExpression` or any other error is skipped and logged
as `rule_eval_error`; other rules in the same run still execute. The run is not
failed by a single bad rule.
---
## Phase 10 — One-off Scheduled Trigger and Webhook Receiver
### T47: Add ScheduledTriggerConfig
`src/activity_core/models.py`
```python
@dataclass
class ScheduledTriggerConfig:
at: datetime # UTC; warns if in the past
timezone: str = "UTC"
TriggerConfig = CronTriggerConfig | EventTriggerConfig | ScheduledTriggerConfig
```
Add to ActivityDefinition frontmatter schema:
```yaml
trigger:
type: scheduled
at: "2026-09-01T09:00:00Z"
timezone: "Europe/Berlin"
```
---
### T48: Implement one-off Temporal Schedule
`src/activity_core/schedule_manager.py`
```python
async def upsert_schedule(client, defn: ActivityDefinition):
if isinstance(defn.trigger_config, ScheduledTriggerConfig):
# Create Schedule with remaining_actions=1
# Schedule ID: activity-schedule-{defn.id}-once
# start_at = defn.trigger_config.at
...
```
`sync_schedules.py`: detect `ScheduledTriggerConfig`, use one-off path.
Add `cancel_scheduled(client, activity_id)` for admin cancellation of pending
one-off definitions.
---
### T49: Implement webhook receiver
`src/activity_core/webhook_receiver.py`
FastAPI `APIRouter` mounted at `/webhooks/{source}` in `api.py`.
```
POST /webhooks/gitea → validate X-Gitea-Signature (HMAC-SHA256)
POST /webhooks/github → validate X-Hub-Signature-256 (HMAC-SHA256)
```
Per-source normalisers convert raw webhook payload to `EventEnvelope`:
- Gitea `repository` event → `gitea.repo.created`
- Gitea `push` event → `gitea.push`
- Gitea `issues` event (action=closed) → `gitea.issue.closed`
After normalisation: validate type against event type registry; publish to
NATS subject `activity.events`. Discard unknown event types with HTTP 422
(not 500 — unknown type is not a server error).
Config: `WEBHOOK_SECRET_GITEA` and `WEBHOOK_SECRET_GITHUB` env vars.
---
### T50: Write Gitea webhook event type definitions
Three files under `event-types/`:
- `gitea.repo.created.md` — publisher: gitea/webhook; attrs: repo_full_name
(string), repo_slug (string, derived from repo name), owner (string),
html_url (string), created_at (datetime).
- `gitea.push.md` — publisher: gitea/webhook; attrs: repo_full_name (string),
branch (string), pusher (string), commits_count (integer), compare_url
(string), pushed_at (datetime).
- `gitea.issue.closed.md` — publisher: gitea/webhook; attrs: repo_full_name
(string), issue_number (integer), issue_title (string), closer (string),
closed_at (datetime).
Each includes a "Normaliser mapping" subsection in Consumer Notes showing the
raw Gitea webhook field → EventEnvelope attribute mapping.
---
## Completion Criteria for This Part
1. `make sync-activity-definitions` loads definitions from `activity-definitions/`
and upserts them (with `rules_json`, `instructions_json`) cleanly
2. `RunActivityWorkflow` uses the rule/instruction pipeline end-to-end (no
unconditional task spawning)
3. One-off `scheduled` trigger type creates a Temporal Schedule with
`remaining_actions=1`
4. `POST /webhooks/gitea` validates HMAC, normalises payload, publishes to NATS
5. Three Gitea event type definitions committed and loaded by `sync-event-types`
## New Files Produced
| Path | Task |
|---|---|
| `src/activity_core/definition_parser.py` | T44 |
| `src/activity_core/webhook_receiver.py` | T49 |
| `event-types/gitea.repo.created.md` | T50 |
| `event-types/gitea.push.md` | T50 |
| `event-types/gitea.issue.closed.md` | T50 |
## Modified Files
| Path | Task | Change |
|---|---|---|
| `src/activity_core/models.py` | T47 | ScheduledTriggerConfig, TriggerConfig union |
| `src/activity_core/workflows.py` | T46 | Rule/instruction pipeline replaces unconditional spawn |
| `src/activity_core/activities.py` | T46 | resolve_context calls adapter chain |
| `src/activity_core/schedule_manager.py` | T48 | One-off schedule path |
| `src/activity_core/sync_schedules.py` | T48 | Detect ScheduledTriggerConfig |
| `src/activity_core/api.py` | T49 | Mount webhook router |
| `src/activity_core/worker.py` | T45 | sync-activity-definitions at startup |
| `alembic/versions/` | T45 | Migration: rules_json, instructions_json columns |
| `Makefile` | T45 | sync-activity-definitions target |
## Change History
- v1.0 (2026-05-14): Split from custodian-WP-0003 (phases 910).