Skip to content

FR: @lenient on field.uri/field.inet — keep the type, drop the well-formedness assertion #234

Description

@dmealing

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions