Scope: Compiler outputs, emitters, compatibility metadata, lineage, and generated-artifact guarantees.
Status: Approved for Phase 1 targets, selected local integration emitters, and Apicurio JSON Schema artifact publish/pull. Catalog and runtime integrations remain deferred by phase.
Scope: Output target generation from the normalized Modelable model graph.
Emitters transform validated Modelable definitions into external artifacts. They do not define contract semantics. The normalized model graph remains the source for lineage, compatibility, governance, and generated output.
Emitters must be deterministic: the same normalized graph and emitter options produce byte-for-byte equivalent artifacts, apart from documented formatting differences.
| Target | Phase | Status |
|---|---|---|
| JSON Schema 2020-12 | 1 | Required |
TypeScript via json-schema-to-typescript |
1 | Required |
| Markdown documentation | 1 | Required |
| C#, Java, Python, Rust, and Go | 1 | Implemented |
| SQL DDL | 5 | Implemented local artifact |
dbt schema.yml |
4 | Implemented local artifact |
| FHIR R4 profile | 4b | Implemented local artifact for Patient, Observation, and Encounter profile bases |
| Apicurio Registry | 2 | Implemented JSON Schema artifact publish/pull |
| OpenMetadata export | 3 | Implemented local artifact; live catalog sync deferred |
| OpenLineage sync | 3 | Implemented for Marquez-compatible /api/v1/lineage endpoints |
| OpenLineage export | 3 | Implemented local artifact; runtime collection deferred |
| ODCS export | 4 | Implemented local artifact |
| Protobuf | 5 | Implemented local artifact with opt-in descriptor artifacts, native supported maps, source reservations, and manifest-based compatibility validation |
| Scalable gRPC profile | 5 | Implemented local artifact with opt-in service descriptors, declared read-index metadata, and manifest-based compatibility validation |
| Avro | 5 | Deferred |
| OpenAPI | 5 | Deferred |
| AsyncAPI | 5 | Deferred |
Phase 1 must not require runtime adapters.
Each emitter receives:
- Normalized model graph.
- Selected model/projection references, or all definitions.
- Output directory.
- Emitter options.
- Registry metadata needed for lineage and governance annotations.
Conceptual interface:
emit(graph, selection, options) -> emitted_artifacts
Each emitted artifact records:
- Target format.
- Logical reference (
domain.Name@version). - Artifact ID (
domain.Name.v<version>). - Relative output path.
- Content hash.
- Warnings.
Artifact IDs use:
<domain>.<name>.v<version>
Examples:
customer.Customer.v2
billing.BillingCustomer.v1
commerce.OrderPlaced.v3
Model references in CLI arguments remain domain.Name@version; artifact IDs and filenames use .v<version>.
Every generated artifact must carry:
- Domain.
- Name.
- Kind.
- Version.
- Change kind.
- Source
.mdlreference when available. - Field-level classifications.
- Field-level lineage for projections.
- POR reference metadata when available.
For formats that do not support vendor extensions, emitters must generate a companion metadata document or document the loss explicitly.
JSON Schema is the first canonical output format.
Requirements:
- Emit draft 2020-12 schemas.
- Use
type: objectfor models and projections. - Map required fields from non-optional
.mdlfields. - Preserve nullable fields according to JSON Schema 2020-12 conventions.
- Emit
x-modelable,x-modelable-field,x-modelable-classification,x-modelable-lineage,x-modelable-ref, andx-modelable-porwhere applicable. - Use
$defsfor local value objects when needed. - Validate every generated schema with
jsonschema.
Type mapping:
| Modelable | JSON Schema |
|---|---|
string |
{ "type": "string" } |
bool |
{ "type": "boolean" } |
int |
{ "type": "integer", "format": "int64" } |
u8..u128, i8..i128 |
{ "type": "integer", "minimum": ..., "maximum": ... } bounds per width |
float |
{ "type": "number" } |
decimal(p,s) |
{ "type": "string", "pattern": "^-?\\d+(\\.\\d+)?$" } |
uuid |
{ "type": "string", "format": "uuid" } |
uuid(7) |
{ "type": "string", "format": "uuid", "x-modelable-uuid-version": 7 } |
timestamp |
{ "type": "string", "format": "date-time" } |
date |
{ "type": "string", "format": "date" } |
time |
{ "type": "string", "format": "time" } |
duration |
{ "type": "string", "format": "duration" } |
binary |
{ "type": "string", "contentEncoding": "base64" } |
binary(N) |
{ "type": "string", "contentEncoding": "base64", "x-modelable-fixed-length": N } |
enum(a,b) |
{ "type": "string", "enum": ["a", "b"] } |
array<T> |
{ "type": "array", "items": <T schema> } |
map<K,V> |
{ "type": "object", "additionalProperties": <V schema> } |
| Named value object | $ref into $defs |
ref<Domain.Model> |
String or object reference plus x-modelable-ref |
The TypeScript emitter delegates type generation to json-schema-to-typescript.
Requirements:
- Generate JSON Schema first.
- Generate one
.tsfile per schema. - Preserve
x-modelable-*metadata as JSDoc where supported. - Use stable interface names derived from
<Domain><Name>V<version>. - Do not hand-roll a separate TypeScript type mapper in Phase 1.
Markdown docs are for human review in source control.
Each document should include:
- Domain, owner, model/projection name, version, kind, and change kind.
- Field table with type, optionality, annotations, classification, and owner.
- Projection source table.
- Lineage table for projected fields.
- Compatibility notes when generated from a diff.
- Generated artifact references.
Markdown must avoid embedding secrets from bindings.
modelable publish apicurio publishes generated JSON Schema 2020-12 artifacts
to Apicurio Registry 3.x Core Registry API v3. Artifact IDs are deterministic:
domain.Name.vVersion. Apicurio is derived artifact storage and versioning; it
does not replace .mdl source files, the local normalized graph, registry.db,
lineage, compatibility, or governance validation.
modelable pull apicurio retrieves a JSON Schema artifact by Modelable
reference (domain.Name@version) from the Apicurio version content endpoint
and writes it under the requested output directory as
domain/Name.vVersion.json.
Deferred and integration emitters must preserve Modelable semantics when implemented:
-
Avro: preserve logical types and field defaults; avoid incompatible schema evolution.
-
Scalable gRPC profile:
compile --target grpcemits the Protobuf payload schemas, generic Scalable command/read service definitions, schema manifests, and service manifests for models and projections. The target exposesCommandService,EntityReadService, envelope contracts, schema identity, andread_indexesfrom declared primary/secondary indexes, with an inferred primary fallback from existing@keyfields when no index declaration exists. When--descriptor-setis used, each service emits a compiled<Name>.v<version>.grpc.descriptor.pbdescriptor artifact and records it inservice-manifest.json. Manifest-based compatibility validation compares generated service metadata and reports read-index changes asrequires_read_rebuild. Descriptor-binary diffing, explicit rebuild/migration declarations, and Scalable registration fixtures remain follow-up work before gRPC is considered stable for long-lived external transport contracts. -
Protobuf:
compile --target protobufemits deterministic.protofiles and schema manifests for models and projections. Supportedmap<K,V>fields render as native Protobuf maps, unsupported map shapes fail clearly, and model schema manifests include declared primary/secondaryindexesmetadata. Field manifest entries for maps recordmap.key_type,map.value_type, and optionalmap.value_fixed_lengthormap.value_semantic_type. When--descriptor-setis used, each schema emits a compiled<Name>.v<version>.descriptor.pbdescriptor artifact and records it inschema-manifest.json.reserved protobufdeclarations render deleted-field reservations and manifest-based compatibility validation guards field-number reuse, deleted-field reservations, target type changes, requiredness changes, and inline enum value reuse. Descriptor-binary diffing, field-number pinning, and enum reservations remain follow-up work before protobuf is considered stable for long-lived external wire contracts. -
SQL DDL: treat SQL as a binding/materialization artifact, not canonical model truth.
indexdeclarations (see Language Reference §3.9) generateCREATE INDEX IF NOT EXISTS/CREATE UNIQUE INDEX IF NOT EXISTSstatements after the table'sCREATE TABLE, Postgres only — thesecondaryblock'skeyfields form the leading index columns, itssortfields are appended withDESCwhere declared (ascneeds no suffix). Every field name is resolved from the indexed model through to the emitting projection's own column name via itsDirectMappings; a referenced field the projection doesn't include is skipped (with atype_losswarning) rather than emitted as a broken column reference. ClickHouse index DDL is deferred — ClickHouse's data-skipping-index model doesn't map mechanically onto the same statement shape. -
dbt YAML: describe schemas and model/source metadata without making dbt the source of truth.
-
FHIR R4 profiles: emit R4
StructureDefinitionconstraint profiles for projection artifacts. The current hardening boundary supports projections whose source model name isPatient,Observation, orEncounter; other source models fall back to the R4Basicresource with an emitter warning. Profile output includes root and fieldElementDefinitionentries, direct Modelable lineage mappings, required/optional and repeating cardinality, primitive type mappings, enum bindings to deterministic Modelable ValueSet URLs, FHIRReferencetarget profiles, and Modelable classification/PII extensions. The FHIR export gate includes an opt-in smoke against the HL7-maintained Java FHIR Validator (validator_cli.jar) for representative R4 profiles. Fields that are not legal base-resource children are mapped to FHIR extension slices on the resource'sextensionelement, paired with companionExtension-type StructureDefinitions that fix the extension URL and value type. Unsupported nested/complex FHIR representation remains a warning/follow-up boundary rather than a silent lossy mapping. -
OpenAPI: generate schemas from projections, not necessarily canonical entities.
-
AsyncAPI: generate event channels from event projections and change event envelopes.
-
ODCS: export data contracts while keeping
.mdlas source of truth. -
OpenMetadata: export ownership, lineage, and classification metadata. Live catalog publishing remains outside the local emitter boundary.
-
OpenLineage: emit design-time run events with schema and column-lineage facets from the local graph. Runtime event collection remains outside the local emitter boundary.
-
Generated-language targets beyond TypeScript: C#, Java, Python, Rust, and Go. These targets are implemented in the local codegen boundary; additional future targets stay deferred.
-
Semantic types (
semantic Name: Underlying, see Language Reference §3.8): the Rust emitter generates a#[serde(transparent)]tuple-struct newtype per declaration (pub struct Name(pub Underlying);) withFrom/From(reverse) conversions andDerefto the underlying type. Derives (Debug,Clone, plusCopy/Eq/Hashwhere the underlying Rust type supports them) are chosen per underlying kind — e.g. integers anduuid::UuidderiveCopy + Eq + Hash,Stringandf64-backed types omitCopy, andf64additionally omitsEq/Hash. Fields that reference a semantic type use the newtype directly, with auseimport generated the same way cross-domain model references are resolved. When a declaration isregistry: trueand has an entry inregistry-ids.lock, the generated struct retains its/// registry id: Ndoc comment and exposes the allocation as an associatedu32constant such asRuntimeKernelConfigSchemaId::REGISTRY_ID. A direct emitter call without supplied allocations omits the constant.The Protobuf and gRPC targets preserve semantic types nominally as shared declaring-domain wrapper messages in
<domain>/semantic-types.proto. Chained aliases flatten to their terminal scalar inside each wrapper. Consuming schema manifests recordsemantic_type, optional allocatedregistry_id, and the canonicalmodelable_signatureseparately fromschema_fingerprint. Other non-Rust targets continue to resolve semantic references structurally.
Every generated Rust model and projection exposes its declared version as an
associated u32 constant such as
RuntimeKernelConfigV1::SCHEMA_VERSION. It also exposes the canonical
Modelable signature as
RuntimeKernelConfigV1::SCHEMA_CONTENT_SIGNATURE, a dependency-free
[u8; 32] produced from compute_version_signature(). This canonical
contract identity is distinct from the generated Rust artifact's text hash
and from target-specific wire fingerprints such as Protobuf
schema_fingerprint.
Emitter diagnostics are warnings unless the artifact cannot be generated correctly.
| Code | Meaning |
|---|---|
EMIT001 |
Unsupported target |
EMIT002 |
Type cannot be represented without loss |
EMIT003 |
Missing metadata required by target |
EMIT004 |
Generated artifact failed validation |
EMIT005 |
Deferred target requested in current phase |
- Whether emitters become third-party plugins through Python entry points.
- How target-specific annotations are represented without polluting canonical models.
- Whether large domains require streaming artifact generation APIs.
- Phase 1 emits JSON Schema, TypeScript, and Markdown for models and projections.
- Implemented local emitters are deterministic and appear in
modelable codegen formats. - JSON Schema output validates against draft 2020-12.
- Generated artifacts include version metadata and
x-modelable-*extensions where supported. - Projection artifacts include field-level lineage.
- Deferred targets fail with clear diagnostics rather than partial output.
- Language reference for the type system, governance annotations, and target declarations.
- Tooling reference for
compile,docs,codegen, and graph-export commands. - Architecture for adapter boundaries and product semantics.
.mdl files are the source of truth. registry.db, plan documents, generated
artifacts, mirrors, and exported graphs are reproducible derived state.
The local compiler records model and projection identities, compatibility reports, ownership, classifications, content signatures, and property-level lineage. Distributed workspaces may declare git-backed peers. Foreign source is resolved from local mirrors, while downstream consumer registrations are stored as explicit source-controlled records. Rebuilding the registry from source must produce the same graph.
Cross-registry references include logical identity, concrete version, source registry, and content signature. A signature mismatch is an integrity error, not a warning. Fetch and write-back failures must be explicit and must not leave partially updated registry state.
The graph export command emits deterministic canonical JSON suitable for
catalogs and lineage tools. External systems may visualize or enrich that graph, but they do
not replace Modelable's source definitions or compiler validation.