Skip to content

feat: ADR-0036 Wave 3 — field.uri + field.inet subtypes + @stringFormat (all 5 ports) - #130

Merged
dmealing merged 6 commits into
mainfrom
feat/wave3-uri-inet
Jun 30, 2026
Merged

feat: ADR-0036 Wave 3 — field.uri + field.inet subtypes + @stringFormat (all 5 ports)#130
dmealing merged 6 commits into
mainfrom
feat/wave3-uri-inet

Conversation

@dmealing

Copy link
Copy Markdown
Member

Wave 3 of ADR-0036, resolving the 'string formats' set by behavior (ADR-0037): url/uri and ip have native types + behavior → subtypes; only email/hostname are plain validated strings → the @stringformat attribute.

  • field.uri — native URI/Uri/AnyUrl (TS string), text column, URL validation.
  • field.inet — native InetAddress/IPAddress/IPvAnyAddress, Postgres-native inet column, IP validation.
  • @stringformat on field.string — {email, hostname} (gated via Wave-1 allowedValues), idiomatic per-port validation (Zod .email() / Jakarta @Email / EmailStr / [EmailAddress]; canonical hostname matcher in codegen, not author regex).
  • Both subtypes ship without @kind in v1 (native type subsumes url/urn, ipv4/ipv6 — reserved).

Registry byte-matches + descriptions identical across 5 ports; new field-uri-inet-stringformat conformance fixture loads strict everywhere. TS 4b905001 · C# f0451126 · Py 80209022 · JVM 351362c0. (Follow-up: add a uri/inet persistence roundtrip scenario to gate the native binding end-to-end.)

🤖 Generated with Claude Code

claude added 6 commits June 30, 2026 10:02
…stringformat (TS reference)
Implements the TypeScript reference for ADR-0036 decision #2 / ADR-0037:
two new field subtypes (concepts with a native type + behavior) and one new
validation attribute (a plain validated string).
Metamodel (spec/metamodel/field.json + regenerated embed):
- field.uri — a URI/URL string. TS binds to `string` (no native URI type, same
as field.uuid); other ports bind native URI. DB column = text. Ships WITHOUT
@kind in v1 (native type subsumes url/urn).
- field.inet — an IP-address string (v4/v6). TS binds to `string`; other ports
bind native IP. DB column = Postgres-native `inet`. Ships WITHOUT @kind in v1.
- @stringformat on field.string — closed set {email|hostname}; the field stays a
plain string, codegen owns the canonical matcher. Named distinctly to avoid
colliding with template.* @Format.
Constants + runtime: FIELD_SUBTYPE_URI/INET, FIELD_ATTR_STRING_FORMAT +
STRING_FORMAT_EMAIL/HOSTNAME; FIELD_DATA_TYPE binds uri/inet → string; op-bands
(uri = string-like eq/ne/in/like/isNull; inet = uuid-like eq/ne/in/isNull).
codegen-ts:
- column-mapper: uri → text, inet → inet (Drizzle pg-core native inet()),
both text on SQLite; native TS binding stays string.
- Zod: field.uri → z.string().url(); field.inet → z.string().ip();
@stringformat email → z.string().email(); hostname → codegen-owned canonical
hostname regex (not author validator.regex).
- inferred-types / field-meta / fr010 field-mapping: uri/inet → string scalar.
migrate-ts (schema DDL stays TS-owned): SqlType gains `inet`; uri → text,
inet → INET (Postgres) / TEXT (SQLite); introspect maps inet back.
Goldens regenerated: registry-conformance expected-registry.json (uri/inet
entries + @stringformat allowedValues), coverage-report.json baseline,
metamodel-docs. New conformance fixture field-uri-inet-stringformat gates the
new vocab through the loader under strict mode.
Other 4 ports must replicate the shared golden + bind the NATIVE type per port
(Java java.net.URI / InetAddress, C# System.Uri / IPAddress, Python urllib /
ipaddress, Kotlin via JVM) and emit idiomatic validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n
…ingformat
Implements the C# port of ADR-0036 decision #2 / ADR-0037, matching the TS
reference golden byte-for-byte (registry-conformance + descriptions).
Metamodel / registry (matches the shared golden):
- field.uri — a URI/URL string. Native CLR binding = System.Uri; DB column = text.
- field.inet — an IP-address string (v4/v6). Native CLR = System.Net.IPAddress;
DB column = Postgres-native inet. Both string-backed on the wire (DataType.String).
- @stringformat on field.string — closed set {email|hostname}, emitted as
allowedValues in the registry manifest (Wave-1 gate). Strict attr scoping keeps
it on field.string only.
- Descriptions / whenToUse byte-match the golden via the embedded
SpecMetamodel/field.json copy (refreshed from spec/metamodel/field.json).
Codegen (EntityGenerator / DbContextGenerator / CSharpNaming):
- field.uri → Uri property; DbContext HasColumnType("text").HasConversion(Uri↔string).
- field.inet → IPAddress property (+ using System.Net;); DbContext HasColumnType("inet").
- @stringformat email → [EmailAddress]; hostname → codegen-owned canonical RFC-1123
[RegularExpression] (byte-identical to the TS Zod source), never author regex.
- Op-bands: uri = string-like (eq/ne/in/like/isNull); inet = uuid-like
(eq/ne/in/isNull, no like/ordering).
Tests: MetaObjects Conformance 670/670 (incl. new field-uri-inet-stringformat
fixture + registry-manifest byte-match), Codegen 252/252, Render 290, CLI 42,
IntegrationTests 82/82 (Testcontainers PG). Generated entity compile-verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n
…stringformat (Python port)
Ports the TS reference (4b90500) for ADR-0036 decision #2 / ADR-0037 to the
Python port, matching the shared cross-port golden byte-for-byte and binding the
idiomatic NATIVE Python types.
Metamodel:
- Sync the embedded spec_metamodel/field.json from the repo-root shared source
(field.uri / field.inet subtype entries + @stringformat allowedValues on
field.string) — the byte-identity embed gate passes. Descriptions / whenToUse /
allowedValues / strict per-subtype attr scoping are all single-sourced from this
file via apply_spec_descriptions, so no hand-copied prose.
- Constants: FIELD_SUBTYPE_URI / FIELD_SUBTYPE_INET added to FIELD_SUBTYPES;
FIELD_ATTR_STRING_FORMAT + STRING_FORMAT_EMAIL / STRING_FORMAT_HOSTNAME /
STRING_FORMAT_VALUES.
- meta_field: field.uri / field.inet → DataType.STRING (string-backed on the wire,
same as field.uuid).
- core_types: @stringformat registered on field.string (closed value-set
{email|hostname} → allowedValues in the manifest + loader value check), mirroring
the @Currency / @valueType per-subtype append pattern.
- Op-bands (loader validation_passes + codegen filter allowlist): field.uri is
string-like (eq/ne/in/like/isNull); field.inet is uuid-like (eq/ne/in/isNull —
no like, no ordering).
Codegen (Pydantic model + extract field-mapping):
- type_map: field.uri → pydantic AnyUrl; field.inet → pydantic IPvAnyAddress (the
Python analogues of Zod .url() / .ip()). Wire/storage form stays a string.
- entity_model: @stringformat: email → pydantic EmailStr (a type); @stringformat:
hostname → a codegen-OWNED canonical hostname pattern= (RFC-1123 labels, byte-
identical to the TS HOSTNAME_REGEX_LITERAL — the matcher lives in codegen, never
author validator.regex, so every port replicates the same form).
- fr010 field-mapping: field.uri / field.inet → STRING scalar kind.
The runtime write coercer needs no change — uri (text) and inet pass through as
str; pg8000 binds a Python str to a Postgres inet column natively.
Registry-conformance byte-match + the new field-uri-inet-stringformat conformance
fixture both pass under strict mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n
@stringformat (Java + Kotlin)
Replicate the TS reference (ADR-0036 decision #2 / ADR-0037) Wave 3 on the
shared JVM metadata + the Java (codegen-spring) and Kotlin (codegen-kotlin)
codegen ports, byte-matching the cross-port goldens (expected-registry.json,
field-uri-inet-stringformat conformance fixture — neither modified here).
Metadata (shared by Java + Kotlin):
- field.uri — a URI/URL string. Bare scalar inheriting field.base; native JVM
binding java.net.URI; DB column text. New UriField class + registration.
- field.inet — an IP-address string (v4/v6). Bare scalar; native JVM binding
java.net.InetAddress; DB column Postgres-native inet. New InetField class.
- @stringformat on field.string — closed {email|hostname} string-typed attr
(mirrors @maxlength registration). allowedValues + description sourced from
spec/metamodel/field.json via applySpecDescriptions, so both ports' registry
manifests byte-match the golden with no hand-written descriptions.
- FilterOps op-bands: uri = string band (incl. like); inet = uuid band (no
like/ordering), in canonical subtype order after uuid.
Codegen — Java (codegen-spring):
- SpringTypeMapper: field.uri → java.net.URI, field.inet → java.net.InetAddress.
- SpringDtoGenerator: @stringformat:email → Jakarta @Email; hostname → a
codegen-owned canonical DNS-hostname @pattern (not author validator.regex).
Codegen — Kotlin (codegen-kotlin):
- KotlinTypeMapper: field.uri → java.net.URI + file-local uriColumn(...) Exposed
column; field.inet → java.net.InetAddress + inetColumn(...). Both backed by a
package-shared MetaInetUriColumnType.kt support file (internal custom
ColumnType<URI> over text + ColumnType<InetAddress> over native inet), emitted
once per package — mirrors the Wave 2 MetaInstantWithTimeZoneColumnType pattern.
- KotlinEntityGenerator: @stringformat:email → @field:Email; hostname → the same
canonical @field:Pattern hostname check (byte-identical regex to the Spring port).
OMDB (runtime persistence JDBC seam):
- field.uri ⇄ StringCodec (text column). field.inet ⇄ new InetCodec, binding the
String via setObject(.., Types.OTHER) so the driver coerces it into the native
inet column (mirrors UuidCodec); read back as String.
Tests: SpringTypeMapperTest (+4: uri/inet type mapping, @stringformat email/hostname
validation), KotlinTypeMapperTest (+2), KotlinExposedTableGeneratorTest (+1: custom
columns + shared support file). Both registry-conformance byte-matches green; the new
metamodel conformance fixture loads under strict mode in both ports.
The persistence-conformance roundtrip corpus does not yet carry uri/inet (the TS
reference did not add them), so Testcontainers roundtrips are not exercised for these
subtypes — CI-gated/not-applicable for this wave.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GF9xLEQZaPus5Y6opk398n
@dmealing
dmealing merged commit 09ceba2 into mainJun 30, 2026
18 checks passed
@dmealing
dmealing deleted the feat/wave3-uri-inet branch June 30, 2026 14:36
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@dmealing@claude