Files
activity-core/docs/adr/adr-002-definition-format.md
tegwick 617b2420d3 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>
2026-05-14 16:48:42 +02:00

357 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: ACT-ADR-002
type: architecture-decision-record
title: "Markdown-as-Definition Format for Event Types and ActivityDefinitions"
status: accepted
decided_by: Bernd Worsch
date: "2026-05-14"
scope: cross-repo
affects:
- activity-core
- any event publisher registering event types
tags: ["architecture", "format", "event-type", "activity-definition", "markdown", "documentation"]
---
# ACT-ADR-002: Markdown-as-Definition Format
## Status
Accepted.
## Context
Event type schemas and ActivityDefinition rules need to be understood and authored
by three distinct audiences simultaneously: humans reviewing and debugging automation,
agents creating and modifying definitions at runtime, and machines parsing and
evaluating them. Traditional approaches split these concerns — schemas go in JSON
Schema or YAML, documentation goes in a wiki, logic goes in code — and they drift
apart. A bug in a rule requires cross-referencing three places to understand intent,
check the schema, and read the condition.
The Custodian ecosystem already uses markdown files with YAML frontmatter as the
authoritative format for workplans, ADRs, SCOPE.md, and INTENT.md — all understood
by humans and agents without additional tooling. The same pattern should apply here.
## Decision
**Event type definitions and ActivityDefinitions are markdown files** where machine-
parseable structure (frontmatter YAML and fenced definition blocks) is embedded within
human-readable narrative. Intent, schema, logic, and debugging notes live in one file.
### Event Type Definition Files
**Location**: `event-types/{namespace}.{event-name}.md` within the activity-core repo
(or a registered event-types registry repo if volumes justify separation).
**Naming convention**: `{publisher-domain}.{noun}.{verb}.md`, e.g.:
- `org.repo.registered.md`
- `org.security.cve.published.md`
- `org.workstream.completed.md`
**Structure**:
```markdown
---
id: org.repo.registered
type: event-type
version: "1.0"
publisher: the-custodian/state-hub
governance: publisher-declared # publisher-declared | curated
status: active # active | deprecated | draft
introduced: "2026-05-14"
---
# Event: org.repo.registered
## Intent
One-paragraph statement of why this event exists and what it signals.
Written for an agent or human who has never seen it before.
## When Published
Bulleted list of the exact conditions under which the publisher fires this event.
Be precise — ambiguity here causes missed or duplicate activations.
## Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
| `repo_slug` | string | yes | URL-safe repository identifier |
| `domain` | string | yes | Domain slug the repo is assigned to |
| `tags` | string[] | no | Capability tags set at registration time |
| `registered_at` | datetime | yes | ISO 8601 UTC timestamp |
## Example Payload
```json
{
"id": "evt-7f3a1b2c",
"type": "org.repo.registered",
"version": "1.0",
"timestamp": "2026-05-14T10:00:00Z",
"publisher": "the-custodian/state-hub",
"attributes": {
"repo_slug": "new-python-service",
"domain": "railiance",
"tags": ["python-service", "fastapi"],
"registered_at": "2026-05-14T10:00:00Z"
}
}
```
## Consumer Notes
Guidance for agents and humans writing rules against this event type:
- Which attributes are safe for instruction prompts (trusted fields)
- Common misuses or gotchas
- Related events that are often used together
## Debugging
What to check when an activity that subscribes to this event does not fire:
- How to verify the event was published (NATS subject, log entry)
- How to inspect the event payload in the registry
- Common schema validation failures
```
### Attribute Types
The type system for event attributes is intentionally small:
| Type | Notes |
|---|---|
| `string` | UTF-8 string |
| `integer` | 64-bit signed integer |
| `float` | 64-bit float |
| `boolean` | true / false |
| `datetime` | ISO 8601 UTC string in payload, parsed to datetime in evaluator |
| `uuid` | String in payload, validated as UUID v4 |
| `string[]` | JSON array of strings |
| `integer[]` | JSON array of integers |
| `object` | Freeform JSON object — cannot be used in rule conditions; instruction-only |
`object` type attributes are available to instructions but excluded from rule
conditions deliberately — rules must be deterministic and schema-validatable.
### ActivityDefinition Files
**Location**: `activity-definitions/{slug}.md` within the repo that owns the automation.
For org-wide automations: `activity-core/activity-definitions/`.
For domain-specific automations: `{domain-repo}/activity-definitions/`.
**Structure**:
```markdown
---
id: ACT-DEF-onboard-python-repo
type: activity-definition
version: "1.0"
status: active
trigger:
type: event # event | cron | scheduled
event_type: org.repo.registered # for type: event
# cron: "0 9 * * 1" # for type: cron (5-field, UTC)
# timezone: "Europe/Berlin" # optional, cron only
# misfire_policy: skip # skip | catchup | compress (cron only)
# at: "2026-06-01T09:00:00Z" # for type: scheduled (one-off)
context_sources:
- type: repo-scoping
query: repo_profile
bind_to: context.repo_profile
- type: state-hub
query: domain_summary
bind_to: context.domain_summary
governance: publisher-declared
owner: custodian-agent
created: "2026-05-14"
---
# ActivityDefinition: Onboard New Python Service
## Purpose
One paragraph. What does this automation do and why does it exist? What problem
would accumulate if this automation were turned off?
## Trigger
Which event type fires this activity, and under what conditions does it apply?
Cross-reference the event type definition file.
## Context Sources
What context is resolved before rules are evaluated? Explain what each source
provides and why it is needed.
## Rules
Each rule is a fenced block tagged `rule`. Rules are evaluated in order; all
matching rules fire (not first-match-only). See ACT-ADR-003 for the expression
language specification.
```rule
id: create-sbom-scan
condition: '"python-service" in event.attributes.tags'
action:
task_template: tasks/sbom-initial-scan.md
target_repo: event.attributes.repo_slug
priority: high
labels: ["onboarding", "security"]
```
```rule
id: create-scope-generation
condition: '"python-service" in event.attributes.tags and context.repo_profile.scope_md_exists == false'
action:
task_template: tasks/generate-scope-md.md
target_repo: event.attributes.repo_slug
priority: medium
labels: ["onboarding", "documentation"]
```
## Instructions
Instructions are evaluated after all rules. An instruction asks an LLM to decide
what additional tasks (if any) to create. See ACT-ADR-003 for safety requirements.
```instruction
id: domain-specific-onboarding
condition: 'event.attributes.domain != "test_domain_v2"'
trusted_fields:
- event.attributes.repo_slug
- event.attributes.domain
- event.attributes.tags
model: claude-sonnet-4-6
review_required: false
prompt: |
A new repository has been registered in the Coulomb organization.
Repository: {event.attributes.repo_slug}
Domain: {event.attributes.domain}
Tags: {event.attributes.tags}
Based on the domain's current standards and the repository profile above,
determine what additional domain-specific onboarding tasks should be created
beyond the standard SBOM scan and SCOPE.md generation. Return an empty list
if no additional tasks are warranted.
output_schema: tasks/task-template-list-schema.json
```
## Task Templates
References to task template files used in rule actions. Each template is a
separate markdown file under `tasks/` that defines the task title, description
template, default labels, and default assignee logic.
- `tasks/sbom-initial-scan.md`
- `tasks/generate-scope-md.md`
## Notes
Operational notes, edge cases, and context that does not fit elsewhere.
## Debugging
Checklist for when this ActivityDefinition fires but produces unexpected output:
1. Was the triggering event published with the correct type and attributes?
2. Do the rule conditions evaluate as expected? (Use `make eval-rule` with a fixture)
3. Is issue-core reachable and configured for the target domain?
4. For instructions: check the audit log for the model response and output validation result.
## Change History
- v1.0 (2026-05-14): Initial definition
```
### Governance model
The `governance` field on an event type definition determines how the registry
runtime handles it:
| Value | Behaviour |
|---|---|
| `publisher-declared` | Accepted immediately on publish; no review required |
| `curated` | Held in `pending` state until a curator approves via registry API |
The runtime checks the **environment's curator gate configuration** — not just the
file's governance field. An environment configured with `curator_gate: disabled`
treats all event types as `publisher-declared` regardless of the field value.
An environment with `curator_gate: required` treats all event types as `curated`
regardless of the field value. The field is the publisher's declared preference;
the environment config is the enforcement point.
This means:
- **Dev / integration**: `curator_gate: disabled` — developers and agents iterate
freely; new event types take effect immediately.
- **Staging / production**: `curator_gate: required` — all new event types queue
for curator review before the runtime accepts events of that type.
### File as source of truth
Following CUST-ADR-001 (Workplans as Repository Artefacts), definition files are
the canonical source of truth. The activity-core runtime indexes them into its
database on startup and via a sync command. The database is a queryable cache,
not the origin. A definition deleted from the filesystem is disabled at next sync.
### Task Templates
Task templates are separate markdown files (`tasks/{slug}.md`) referenced from
ActivityDefinition action blocks. They define:
```markdown
---
id: tasks/sbom-initial-scan
type: task-template
---
# Task: Run Initial SBOM Scan
## Title template
`Run SBOM scan — {target_repo}`
## Description template
Initial SBOM scan required for newly registered repository `{target_repo}`.
Run: `make ingest-sbom REPO={target_repo} SCAN=1`
## Default labels
["sbom", "security", "automated"]
## Default assignee
None (unassigned)
```
This keeps task content editable separately from the routing logic in
ActivityDefinitions.
## Consequences
- A new `event-types/` directory in activity-core (and eventually a shared registry)
holds all org event type definitions.
- A new `activity-definitions/` directory in activity-core holds org-wide automations.
- Domain repos may hold their own `activity-definitions/` for domain-specific
automations, scanned by activity-core at sync time.
- The runtime requires a parser for the `rule` and `instruction` fenced blocks.
- SCOPE.md for activity-core must be updated to list these directories.
## Alternatives Considered
**Pure JSON Schema for event types, separate wiki for docs**: rejected — documentation
and schema diverge immediately; agents must cross-reference two systems to author
a rule correctly.
**OpenAPI / AsyncAPI specification**: rejected — those formats are excellent for
API and broker documentation but not designed for co-locating operational intent
and debugging guidance. They are also less readable for non-specialists.
**Code-only (Python dataclasses for event schemas, Python functions for rules)**:
rejected — requires code deployment for any definition change; agents cannot modify
definitions without write access to the codebase; non-technical stakeholders cannot
review or understand automation policies.
## Related
- ACT-ADR-001 — Event Bridge Architecture
- ACT-ADR-003 — Rule vs. Instruction model and DSL
- CUST-ADR-001 — Workplans as repository artefacts