Problem
field.uri lowers to a strict well-formedness validator in every port (TS z.string().url(); Python AnyUrl; C# System.Uri; Java/Kotlin java.net.URI). That is correct for author-controlled URLs, but wrong for a URL that arrives inside an LLM-emitted structured payload (a citation from a model or a web search): one malformed URL fail-validates the entire payload — a whole structured response lost to one bad link.
A public reference-impl consumer hit exactly this: a SourceLens.url citation field had to stay field.string (losing the honest URL modeling) purely to avoid the fail-the-whole-take cost.
Proposed: a @lenient boolean attribute on field.uri (and field.inet)
Default absent = strict (today's behavior). @lenient: true keeps the field's semantic type — doc-gen, link rendering, a future @kind: url|urn, api-docs all still know it is a URL — but codegen emits no well-formedness assertion and binds plain string:
- TS
z.string() (drop .url()); Python str (drop AnyUrl); C# string (drop Uri); Java/Kotlin String. DB column (text) + wire format unchanged; filter-op set = the string set.
Per ADR-0037's decision procedure this is a §2c attribute (a boolean exception-flag that modifies validation of an existing type), not a @kind (ADR-0037 forbids @kind absorbing a validation constraint) and not a default-flip (ADR-0037: "never a default-true opt-out"). The precedent that an attribute may flip native binding within a subtype is @localTime on field.timestamp; ADR-0037:30 even names this exact extension point ("a field.uri can grow a @requireAbsolute attr").
Prerequisite (do first): pin field.uri STRICT reject-semantics cross-port
Today field.uri's rejection behavior is not conformance-pinned anywhere — validation-conformance/cases.json has zero uri cases, and the persistence roundtrip only round-trips well-formed URIs. The five validators factually disagree on what they reject (z.url() is WHATWG-strict and rejects scheme-less example.com; java.net.URI accepts relative refs; System.Uri is lenient; Pydantic AnyUrl is strict). So "strict field.uri" currently means different rejections per port. Stage 1: add validation-conformance cases defining what a strict field.uri MUST reject cross-port (ports may need small alignment). Stage 2: add @lenient with accept-malformed cases.
Gates (ADR-0023)
Registered-provider entries in all five ports (+ sync the committed C#/Python SpecMetamodel copies) + expected-registry.json + an extension of the field-uri-inet-stringformat conformance fixture. Additive, non-breaking → a minor.
Problem
field.urilowers to a strict well-formedness validator in every port (TSz.string().url(); PythonAnyUrl; C#System.Uri; Java/Kotlinjava.net.URI). That is correct for author-controlled URLs, but wrong for a URL that arrives inside an LLM-emitted structured payload (a citation from a model or a web search): one malformed URL fail-validates the entire payload — a whole structured response lost to one bad link.A public reference-impl consumer hit exactly this: a
SourceLens.urlcitation field had to stayfield.string(losing the honest URL modeling) purely to avoid the fail-the-whole-take cost.Proposed: a
@lenientboolean attribute onfield.uri(andfield.inet)Default absent = strict (today's behavior).
@lenient: truekeeps the field's semantic type — doc-gen, link rendering, a future@kind: url|urn, api-docs all still know it is a URL — but codegen emits no well-formedness assertion and binds plain string:z.string()(drop.url()); Pythonstr(dropAnyUrl); C#string(dropUri); Java/KotlinString. DB column (text) + wire format unchanged; filter-op set = the string set.Per ADR-0037's decision procedure this is a §2c attribute (a boolean exception-flag that modifies validation of an existing type), not a
@kind(ADR-0037 forbids@kindabsorbing a validation constraint) and not a default-flip (ADR-0037: "never a default-true opt-out"). The precedent that an attribute may flip native binding within a subtype is@localTimeonfield.timestamp; ADR-0037:30 even names this exact extension point ("afield.urican grow a@requireAbsoluteattr").Prerequisite (do first): pin
field.uriSTRICT reject-semantics cross-portToday
field.uri's rejection behavior is not conformance-pinned anywhere —validation-conformance/cases.jsonhas zero uri cases, and the persistence roundtrip only round-trips well-formed URIs. The five validators factually disagree on what they reject (z.url()is WHATWG-strict and rejects scheme-lessexample.com;java.net.URIaccepts relative refs;System.Uriis lenient; PydanticAnyUrlis strict). So "strictfield.uri" currently means different rejections per port. Stage 1: add validation-conformance cases defining what a strictfield.uriMUST reject cross-port (ports may need small alignment). Stage 2: add@lenientwith accept-malformed cases.Gates (ADR-0023)
Registered-provider entries in all five ports (+ sync the committed C#/Python SpecMetamodel copies) +
expected-registry.json+ an extension of thefield-uri-inet-stringformatconformance fixture. Additive, non-breaking → a minor.