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:
- Reference descriptors (
objectRef/references) — declarative data, but constructed in code per port (same rule written 5×).
- ~20 free-floating imperative passes — hand-coded per port.
- 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)
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
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:
objectRef/references) — declarative data, but constructed in code per port (same rule written 5×).validatehook 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
referenceobjectRef,references,payloadRef/responseRefexistenceallowedValues@format,@kind,@promptStyle(closed sets)requiressubjectRef+htmlBodyRef; document needstextReffieldPathRefrequiredSlotsare fields on thepayloadReftargetThese cover the entire current reference + template corpus. Anything that fits no shape falls back to the
validatehook (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)
599a4aed): align all ports' reference validation (revert the TS payloadRef descriptor split).referenceshape 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.allowedValues(format/kind/promptStyle).requires+fieldPathRef; delete the bespoke template pass in all ports.validatehook for the rest.Risks
reference/rulesfields (guard first).Open questions
rulesinline-on-type vs a siblingvalidationblock (lean inline for locality).