Skip to content

feat(metadata): field.map subtype — open-keyed maps (Record<string,V> / dict[str,V]) #64

Description

@dmealing

Summary

Add a first-class field.map field subtype for open-keyed maps — a value whose keys are data (e.g. wsd_senses {word→sense}, agent opinions {topic→stance}, relationships {name→relation}). Today these can only be modeled as opaque jsonb (unknown) or as a {key,value}[] pair-array workaround (which changes the stored shape + forces boundary transforms). field.map models them properly.

Design

  • Keys are always strings — a hard JSON/jsonb constraint (JSON object keys are strings). So no @keyType for v1.
  • Value type is parameterized, exactly one of:
    • @valueType — a scalar value subtype (string/int/long/double/boolean/…), or
    • @objectRef — a value-object name (VO-valued map).
  • Storage: a single jsonb column holding the JSON object (no .array()).
  • Generated types:
    • TS Drizzle: jsonb("col").$type<Record<string, V>>()
    • TS Zod: z.record(z.string(), <V>)
    • TS inferred: Record<string, V>
    • Python: dict[str, V]
    • (JVM/C#: Map<String,V> / Dictionary<string,V> when each port's persistence codegen lands)
field.map: { name: wsdSenses, valueType: string }   # Record<string,string>
field.map: { name: scores,    valueType: double }   # Record<string,number>
field.map: { name: tools,     objectRef: ToolSpec } # Record<string, ToolSpec>

Scope

  1. Metamodel (spec/metamodel/field.json): new field.map subtype (dataType: object, extendsBase: true) with @valueType + @objectRef attrs (mutually exclusive; exactly one required). Regenerate embedded specs + the registry-conformance manifest + metamodel docs.
  2. Registration — all 5 ports (TS/Python/Java/Kotlin/C#): the subtype registers via the bundled spec; add the FIELD_SUBTYPE_MAP / value-type constants where each port's code references them. Registry-conformance manifest byte-matches across ports.
  3. Codegen — TS complete: column-mapper (jsonb + a map dollarTypeRef variant), inferred-types, zod-validators. Value = scalar OR a hoisted VO import.
  4. Cross-port codegen emit (Python first, then JVM/C#): map → dict/Map/Dictionary in each port's persistence/runtime codegen.
  5. Conformance: a field.map fixture (scalar-valued + VO-valued) gated across all ports; loader validation (exactly-one-of @valueType/@objectRef; reject on a non-object/array misuse).

Acceptance

  • field.map loads + validates in all 5 ports; registry-conformance green cross-port.
  • TS emits correct Drizzle .$type<Record<string,V>>() + Zod z.record + inferred type (scalar + VO values).
  • Python emits dict[str, V].
  • Conformance corpus covers scalar + VO value maps; loader rejects both-attrs / neither-attr.

Out of scope (follow-ons)

  • @keyType semantic key validation (uuid/enum-keyed maps).
  • Full JVM/C# persistence emit (registration lands now; emit as each port's codegen matures).

Motivation / context

Closes the "map gap" surfaced while modeling the draagon platform's wsd_senses and agent identityJson (opinions/relationships). Pair-array workarounds change stored shape + need transforms; this is the correct primitive — the open-keyed analogue of the field.object+isArray jsonb-VO support already shipped.

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