Skip to content

ADR-013: Schema Boundary Contracts on Responder Output (Proposed)

Status: Proposed
Date: 2026-06-06

Context

The ESAA paper requires JSON schema contracts per event type. Violations should emit output.rejected events rather than crashing. Currently, KandoEvent.data is dict[str, Any] — no validation occurs at dispatch time. A responder that emits an OBJECT_CREATED event missing the "id" key will cause apply() to raise KeyError, crashing the runtime loop.

Proposed Decision

Add per-type payload validation in runtime._dispatch() before dispatching to responders:

def _dispatch(self, event, world, queue):
    validation_error = validate_event(event)
    if validation_error:
        queue.append(make_rejection_event(event, validation_error))
        return
    # ... existing dispatch logic

validate_event(event) checks required keys per event type:

Event type Required payload keys
object.created id, type
object.patched id, patch
relation.created id, type, source_id, target_id
llm.request messages, model, max_tokens
llm.response text, model, cost_usd

Violations emit output.rejected with {original_event_id, reason} and continue processing. They do NOT crash the loop.

Also add __post_init__ validation to KandoEvent itself for top-level required fields (id, type, source, actor, cause, timestamp).

Consequences

Positive: - Runtime loop never crashes on malformed responder output — it emits output.rejected and continues. - output.rejected events are auditable in the ledger: debugging malformed events is possible post-run. - Satisfies the ESAA boundary contract requirement.

Negative: - Validation adds per-event overhead (dict key lookups). - Schema must be kept in sync with apply() — a mismatch between what validation allows and what apply() expects is a new failure mode. - Validation only covers required keys, not value types. Full JSON Schema validation would be more correct but adds a dependency.

Not Yet Implemented