Skip to content

Config-driven metadata validation — checks as data, one engine per port (write-once across 5 languages) #51

Description

@dmealing

Summary

Make metadata validation data, not per-port code: declare each check as data on the type's registration, and give each port one generic interpreter per rule-shape. A rule is then authored once (not re-coded in TS/Java/C#/Python/Kotlin), and a downstream provider's new type validates itself with zero new validation code in any port.

Design doc: docs/superpowers/specs/2026-06-21-config-driven-validation-design.md.

Sibling to #23 (FR-033, "Provider definitions as declarative data") — same philosophy; if provider definitions become declarative data, validation rules are just more fields in that data.

Why

Validation today is split across three mechanisms, inconsistently:

  1. Reference descriptors (objectRef/references) — declarative data, but constructed in code per port (same rule written 5×).
  2. ~20 free-floating imperative passes — hand-coded per port.
  3. A validate hook on the TypeDefinition — invoked in all ports but registered by no core type (an extension point core doesn't use itself).

Net: every rule is implemented N=5 times, and a downstream provider must write validators in every language it targets — contrary to the metadata-is-the-spine thesis and ADR-0023 (providers add types via registered data).

The four closed rule-shapes

Shape Covers today
reference objectRef, references, payloadRef/responseRef existence
allowedValues @format, @kind, @promptStyle (closed sets)
requires email needs subjectRef+htmlBodyRef; document needs textRef
fieldPathRef requiredSlots are fields on the payloadRef target

These cover the entire current reference + template corpus. Anything that fits no shape falls back to the validate hook (rare escape hatch). No speculative shapes — a fifth requires a real rule + the ADR-0023 can't-be-computed justification.

Phasing (earn it; conformance-gated; behavior-neutral)

  • Phase 0 — cleanup (done, 599a4aed): align all ports' reference validation (revert the TS payloadRef descriptor split).
  • Phase 1 — proof slice: move the reference shape from per-port code into shared JSON; one generic interpreter per port; conformance unchanged. De-risks "is config-driven byte-identical across 5 ports" before extending.
  • Phase 2allowedValues (format/kind/promptStyle).
  • Phase 3requires + fieldPathRef; delete the bespoke template pass in all ports.
  • Phase 4 — opportunistically migrate remaining datafiable passes; keep the validate hook for the rest.

Risks

  • 4 interpreters × 5 ports (bounded, fixed-size — not proportional to type count).
  • FR5d resolved-source envelope fidelity (template fixtures gate it).
  • ADR-0023 strict provenance: rule data is provider data; spec readers must tolerate the new reference/rules fields (guard first).

Open questions

Metadata

Metadata

Assignees

No one assigned

    Labels

    FRRoadmap feature request tracked in spec/roadmap.mdarea:metamodelMetamodel vocabulary / loader

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions