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

    ---
    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:

    ---
    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_advisory: 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:

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