Modelable Architecture and System Specification
Authority: This is the product source of truth for Modelable concepts and contract semantics. Sections describing runtime adapters, materialization, or external services are deferred unless the root roadmap and an accepted issue say otherwise.
1. Purpose
Modelable is a meta-model framework for defining, tracing, and governing domain-owned data models across disparate systems. It acts as a semantic layer on top of existing infrastructure (databases, APIs, message brokers) to ensure maximum traceability and understandability of every single data property.
The framework ensures that any property—whether it appears in a database table, an API response, or a streaming event—can be traced back to the specific domain and canonical model that owns it.
Modelable provides:
- Universal Lineage: Tracking the origin and transformation of every field across system boundaries.
- Domain-Owned Contracts: Explicit ownership and lifecycle for canonical models.
- Explicit Mapping: A declarative way to project, subset, and join models while maintaining property-level "back-references."
- Platform-Agnostic Governance: Applying policies (PII, security, retention) at the source and propagating them to all consumers.
Modelable is not a database; it is the lineage backbone that makes data movement and consumption predictable and auditable.
2. Design Principles
2.1 Domain Ownership
Each model is owned by exactly one domain. The owning domain controls the canonical definition, lifecycle, versioning, access policy, and deprecation policy for that model.
Other domains may consume source models only through explicitly declared projections or subscriptions.
2.2 Property-Level Traceability
Every single property in the system must be traceable. If a field exists in a consumer's projection, the framework must be able to answer: - Which source model and field did this come from? - Who owns that source model? - What transformations (if any) were applied? - What are the governance constraints (e.g., PII) inherited from the source?
2.3 Immutable Contracts
Published model versions and projection versions are immutable. Any incompatible change must create a new version.
Mutable drafts may exist before publication, but published contracts must be stable so downstream systems can rely on them.
2.4 Platform-Neutral Definitions
Model and projection definitions must not depend on a specific database or streaming platform.
Database and stream integrations are expressed through adapter bindings. The same logical model should be usable with PostgreSQL, MongoDB, Kafka, Pulsar, NATS, or other supported systems when the adapter capabilities are sufficient.
2.5 Explicit Derivation
All derived data must be declared. Field renames, type conversions, computed fields, filters, joins, aggregations, and materialized replicas must be traceable back to their source models and source fields.
2.6 Framework-First Integration
Modelable is designed to wrap existing systems. It should not require a "rip and replace" of current infrastructure, but rather provide the mapping layer that makes existing data "modelable" and traceable.
2.7 Compatibility Before Runtime
The system should reject invalid or incompatible definitions before runtime when possible. Runtime failures should be reserved for operational issues such as unavailable streams, write conflicts, bad source payloads, or adapter outages.
3. Core Concepts
3.1 Domain
A domain is an ownership boundary for models and projections.
Required properties:
name: Unique domain identifier.owner: Team, service, or organization responsible for the domain.description: Human-readable purpose.policies: Optional governance, privacy, and access defaults.
Example:
domain customer {
owner: "customer-platform"
description: "Customer identity and lifecycle data."
}
3.2 Model
A model is a canonical business entity, event, value object, or aggregate owned by a domain.
Required properties:
domain: Owning domain.name: Unique model name within the domain.kind:entity,event,value, oraggregate.identity: Key definition for addressable records when applicable. Thekeyfield accepts either a single field name (string) or an ordered list of field names for composite keys.versions: Published model versions.
Example:
domain customer {
entity Customer @ 1 (additive) {
@key customerId: uuid
}
}
3.3 Model Version
A model version is an immutable schema and semantic contract for a model.
Required properties:
version: Integer version number. Must be greater than the previous published version for the same model.changeKind:additiveorbreaking. Required whenstatusispublished. Omit fordraft. See section 8.1 for enforcement rules.status:draft,published,deprecated, orretired.fields: Field definitions.identity: Identity fields for entities and aggregates.constraints: Optional validation constraints.metadata: Optional classification, documentation, and ownership metadata.
Example:
domain customer {
entity Customer @ 2 (additive) {
@key customerId: uuid
legalName: string
@pii email?: string
status: enum(active, blocked, deleted)
createdAt: timestamp
}
}
Composite key example:
domain orders {
entity OrderLineItem @ 1 (additive) {
@key orderId: uuid
@key lineItemId: uuid
sku: string
quantity: int
}
}
3.4 Projection
A projection is a versioned derived contract based on one or more source model versions.
Projections may be used for:
- Consumer-specific contracts.
- Read models.
- API response models.
- Stream output contracts.
- Materialized database replicas.
- Analytics-ready datasets.
- Aggregated views.
Required properties:
domain: Domain that owns the projection.name: Projection name.version: Projection version.sources: Source model versions with optional joins and filters.identity: Target identity, if materializable.fields: Target fields and derivation rules (map-based).materialisation: Optional strategy for persistence (strategy, key, partitionBy, binding, etc.).subscription: Optional stream/change source configuration (source, adapter, fromOffset, filter, etc.).
Example:
domain billing {
projection BillingCustomer @ 1
from customer.Customer @ 2 as c
{
billingCustomerId <- c.customerId
name <- c.legalName
@pii invoiceEmail <- c.email
isBillable = c.status == "active"
}
}
3.5 Auto Projections
An auto projection is a shorthand declaration that instructs the compiler to generate four standard derived models from a single entity or aggregate definition. The four generated models cover the most common use cases for any addressable entity:
| Kind | Generated name | Purpose |
|---|---|---|
db |
{Entity}Db |
Persistence contract — the full entity schema used for SQL DDL and storage bindings |
request |
{Entity}Request |
Write model — fields a client provides when creating or updating an entity; @server-assigned fields are excluded by default |
reply |
{Entity}Reply |
Read model — fields returned in API responses |
event |
{Entity}Event |
Change event — emitted on entity state transitions (created, updated, deleted) |
Auto projections require no hand-authored field mappings. The compiler expands each kind into a fully explicit projection with complete property-level lineage, identical in semantics to a hand-authored projection. The expansion appears in the plan document and is inspectable.
Example:
domain customer {
entity Customer @ 1 (additive) {
@key customerId: uuid
legalName: string
@pii email: string
phoneNumber?: string
status: enum(active, suspended, deleted)
@server createdAt: timestamp
@server updatedAt?: timestamp
}
auto projections Customer @ 1 {
db
request
reply
event
}
}
The compiler generates CustomerDb @ 1, CustomerRequest @ 1, CustomerReply @ 1, and CustomerEvent @ 1. Each is an immutable, versioned projection registered in the lineage graph.
Individual kinds support inline customisation:
auto projections Customer @ 1 {
db
request exclude [status]
reply exclude [@pii]
event on [created, deleted]
}
exclude accepts field names, annotation filters (@pii, @classification("confidential")), or both. on accepts any subset of [created, updated, deleted].
Auto projections may only target entity or aggregate models. The four generated names are reserved; defining an explicit projection with the same name for the same entity version is a compile error. For use cases requiring joins, aggregations, or computed fields, hand-authored projections remain necessary.
For the full IDL syntax, compiler expansion rules, and inline customization options, see language-reference.md §3.7.
3.6 Subscription
A subscription declares how a projection is kept up to date from one or more source streams or change sources.
Required properties:
name: Unique subscription name.projection: Target projection version.source: Stream, change data capture source, or model source.target: Stream or database target.delivery: Delivery and retry semantics.state: Optional state management for joins and aggregations.
Example (Phase 5 — subscription IDL syntax is defined in the Phase 5 spec):
subscription billing-customer-replica {
projection: billing.BillingCustomer @ 1
source {
type: stream
model: customer.Customer @ 2
}
target {
type: database
adapter: postgres
table: "billing_customers"
}
delivery {
mode: at_least_once
idempotencyKey: billingCustomerId
deadLetter: "billing-customer-replica-dlq"
}
}
3.7 Adapter Binding
An adapter binding connects a logical model, projection, subscription, or materialization to a concrete backend.
Adapter bindings must be separate from model definitions.
Example:
binding customer-postgres {
model: customer.Customer @ 2
adapter: postgres
table: "customers"
fields: {
customerId -> customer_id
legalName -> legal_name
createdAt -> created_at
}
}
4. Type System
The core type system must support:
| IDL type | Description |
|---|---|
string |
UTF-8 string |
bool |
Boolean |
int |
64-bit integer |
float |
64-bit float |
decimal(p,s) |
Arbitrary-precision decimal |
uuid |
UUID v4 |
timestamp |
UTC datetime with microsecond precision |
date |
Calendar date (no time component) |
time |
Time of day (no date component) |
duration |
ISO 8601 duration |
binary |
Raw bytes |
enum(a, b, c) |
Inline enumeration |
array<T> |
Ordered list of type T |
map<K, V> |
Key-value map |
ref<Domain.Model> |
Cross-domain model reference |
Named type (bare IDENT) |
Reference to a value object in the same domain |
Field modifiers:
?suffix — optional field (nullable / not required)@key— identity field (required forentityandaggregatemodels)@pii— marks field as personally identifiable information@classification("level")— governance classification level (open,internal,confidential,secret), ordered from least to most restricted@deprecated(replacedBy: "field")— marks field as deprecated@owner("team")— field-level ownership override@server— field is assigned by the server at write time (e.g. auto-generated identifiers, audit timestamps). Excluded fromrequestauto projections by default.
Example:
@pii
@classification("confidential")
email?: string
5. Projection Semantics
5.1 Field Selection
A projection may expose a subset of source fields. The <- operator declares a direct mapping and makes lineage unambiguous.
customerId <- c.customerId
name <- c.legalName
5.2 Field Rename
Renames are expressed as projection mappings with a different target name on the left.
invoiceEmail <- c.email
5.3 Computed Fields
Computed fields use the = operator with a CEL expression over source fields. The compiler records which source fields appear in the expression for lineage tracking.
isActive = c.status == "active"
The expression language must be deterministic, side-effect free, and validateable by the planner.
5.4 Filters
Row-level filters are expressed as join conditions or as boolean computed fields. A dedicated where clause may be added in a future version.
5.5 Joins
A projection may join multiple source models. The on expression is a CEL equality comparison between aliased fields.
projection OrderWithCustomer @ 1
from orders.Order @ 3 as o
join customer.Customer @ 2 as c on o.customerId == c.customerId
{
orderId <- o.orderId
customerName <- c.legalName
total <- o.totalAmount
}
The planner must reject joins that cannot be executed by the selected runtime or adapters.
5.6 Aggregations
Aggregations use group by on the source alias and aggregate functions (count, sum, min, max, avg) in computed fields.
domain analytics {
projection CustomerOrderSummary @ 1
from orders.Order @ 3 as o
group by o.customerId
{
customerId <- o.customerId
totalOrders = count(o.orderId)
lifetimeValue = sum(o.totalAmount)
lastOrderAt = max(o.createdAt)
}
}
Supported initial aggregate functions:
countsumminmaxavg
Windowed aggregations may be added later.
6. Streaming Semantics
6.1 Change Event Envelope
Streaming model data must use a stable envelope.
{
"domain": "customer",
"model": "Customer",
"version": 2,
"operation": "upsert",
"key": "cust_123",
"sequence": "00000000042",
"timestamp": "2026-05-12T10:00:00Z",
"payload": {
"customerId": "cust_123",
"legalName": "Acme AB",
"email": "billing@acme.test",
"status": "active",
"createdAt": "2026-05-12T09:55:00Z"
}
}
Required envelope fields:
domainmodelversionoperationkeytimestamppayload
Recommended envelope fields:
sequencetraceIdcorrelationIdproducerschemaIdsourceOffset
Supported operations:
insertupdateupsertdeletesnapshot
6.2 Delivery Modes
The platform must support:
at_most_onceat_least_onceeffectively_once
effectively_once means the runtime provides idempotent writes and deterministic replay, not that the underlying broker necessarily provides global exactly-once delivery.
6.3 Ordering
Ordering is guaranteed per model identity key when the selected stream adapter supports partitioning by key.
The system must not promise global ordering across all records or all domains.
6.4 Replay and Backfill
Subscriptions must support replay where the source adapter supports retained events or change logs.
Materialized projections must support rebuild from:
- Source streams.
- Source database snapshots.
- A combination of snapshot plus stream catch-up.
6.5 Dead Letter Handling
Invalid or unprocessable events must be routed to a dead-letter target when configured.
Dead-letter records should include:
- Original event.
- Validation error.
- Projection name and version.
- Subscription name.
- Processing timestamp.
- Retry count.
7. Runtime Architecture
7.1 Model Registry
The model registry stores:
- Domains.
- Models.
- Model versions.
- Projections.
- Projection versions.
- Subscriptions.
- Adapter bindings.
- Compatibility reports.
- Lineage metadata.
- Access policies.
The registry must expose APIs for:
- Create draft model.
- Publish model version.
- Deprecate model version.
- Register projection.
- Validate projection.
- Query lineage.
- Query compatibility.
- Export schemas and generated artifacts.
7.2 Compiler and Planner
The compiler normalizes definitions into an internal representation.
The planner validates whether a model, projection, or subscription can be executed against the selected adapters.
Planner responsibilities:
- Resolve source references.
- Validate field mappings.
- Validate expression types.
- Validate access permissions.
- Validate adapter capabilities.
- Determine whether execution is pushdown, runtime-based, or unsupported.
- Produce executable projection plans.
The planner's primary output is a plan document — a structured, serialisable artifact (JSON) that the runtime engine (Phase 5) interprets at execution time. Plan documents are not generated executable code; they are data that describes how to execute a projection. They are human-readable, diffable in git, and inspectable for debugging.
A plan document contains:
- Resolved source model versions (exact version numbers, not ranges).
- Field mapping table: each target field mapped to its source field and optional transformation expression.
- Filter expression in CEL string form.
- Join descriptors: type (
left,inner), left key, right key, and declared cardinality. - Aggregation descriptors: group-by fields and aggregate function per output field.
- Adapter capability assertions evaluated during planning.
- Planner metadata: validation timestamp and planner version.
Plan documents are written to .modelable/plans/<domain>.<Projection>.v<version>.plan.json by the compile command.
7.3 Runtime Engine
The runtime engine executes plans produced by the planner.
Execution modes:
batch: Run projection over bounded data.query: Resolve projection on demand.stream: Transform events continuously.materialized: Maintain projected replica in target storage.
7.4 Materializer
The materializer keeps a target projection synchronized with source data.
It must support:
- Idempotent writes.
- Offset tracking.
- Retry policy.
- Dead-letter routing.
- Rebuild.
- Snapshot plus stream catch-up.
- Health and lag reporting.
7.5 Adapter Layer
Adapters isolate backend-specific behavior.
Adapter categories:
- Storage adapters.
- Stream adapters.
- Schema adapters.
- CDC adapters.
Each adapter must publish its capabilities so the planner can determine support.
Adapter capabilities are declared internally by each adapter implementation and are not authored in .mdl files. The planner queries adapter capability metadata at plan time. Example capability shape (internal representation):
{
"adapter": "postgres",
"capabilities": {
"storage": true,
"transactions": true,
"joins": true,
"aggregations": true,
"jsonFields": true,
"cdc": "logical_decoding"
}
}
8. Versioning and Compatibility
8.1 Model Versioning
Model versions are immutable once published.
Compatible changes:
- Add optional field.
- Add field with default.
- Add documentation.
- Add metadata.
- Mark field as deprecated.
Potentially incompatible changes:
- Add required field.
- Remove field.
- Rename field.
- Change field type.
- Change enum semantics.
- Change identity.
- Change nullability from nullable to non-nullable.
- Change validation constraints in a stricter way.
8.1.1 changeKind Declaration and Enforcement
When publishing a new model version (status: published), authors must declare changeKind:
additive— only backward-compatible changes were made. The set of compatible changes is defined in section 8.1 above. Existing projections that pin an earlier version or use a compatible version range remain valid without re-publication.breaking— at least one incompatible change was made. The set of potentially incompatible changes is defined in section 8.1 above.
Planner enforcement for breaking versions:
When a new version with changeKind: breaking is published, the planner marks all projections that reference any version of that model as requiring re-validation. Subscriptions backed by an affected projection are blocked from planning until the projection author explicitly re-publishes a new projection version that references a valid source version. The registry must expose a listAffectedProjections(domain, model, breakingVersion) query to support this workflow.
Planner enforcement for additive versions:
Projections with exact version pins are unaffected. Projections using version ranges are automatically re-validated against the new version (see section 8.2). If re-validation passes, no author action is required.
Draft versions: changeKind is not required and is ignored for draft status versions.
8.2 Projection Versioning
Projection versions are immutable once published.
A projection version must declare exact source model versions unless explicitly configured to accept a compatible version range.
Example:
projection BillingCustomer @ 1
from customer.Customer @ >=2 <3 as c
{
...
}
Version range resolution rules:
- Ranges are resolved to the highest published version that satisfies the constraint at plan time.
- Since model versions are integers, range syntax uses integer comparisons:
>=2 <3means "version 2 only",>=2means "version 2 or higher". - Exact version pins (
version: 2) are resolved immediately and are not affected by future publications. They are recommended for production projections that require maximum stability. - When a new compatible (
changeKind: additive) version is published within the declared range, the planner automatically re-validates the projection against the new resolved version. If re-validation passes, no projection author action is required. - When a new version with
changeKind: breakingis published and falls within the declared range, the planner raises a validation error and blocks the subscription. The projection author must update the version range and re-publish. - The resolved concrete version is recorded in the plan document (see section 7.2). Re-planning uses the latest resolved version, not the version that was resolved at the last plan time.
8.3 Deprecation
Deprecation must be explicit and traceable.
@deprecated(replacedBy: "primaryEmail")
email?: string
The removalAfter date is tracked in registry metadata outside the IDL field declaration.
The registry must be able to list consumers affected by a planned deprecation.
9. Governance and Access Control
The platform must support field-level classification.
Classification levels form an ordered hierarchy from least to most restricted:
| Level | Meaning |
|---|---|
open |
No access restriction. Safe to expose to any consumer. |
internal |
Restricted to internal consumers within the organisation. |
confidential |
Restricted to explicitly authorised consumers. |
secret |
Highest restriction. Requires explicit governance approval to project. |
@pii is an orthogonal annotation that marks personally identifiable information. A field may carry both @pii and a classification level — they govern different aspects (data sensitivity versus access tier).
Governance checks must apply when:
- Creating projections.
- Creating subscriptions.
- Exporting schemas.
- Materializing projections.
- Reading registry metadata where sensitive fields are exposed.
In Phase 1, the planner reports governance findings for projections that expose secret, insufficiently documented, or classification-lowering fields. Later policy layers may promote those findings to blocking authorization decisions.
10. Lineage
The registry must store lineage from target fields back to source fields.
Example lineage:
billing.BillingCustomer.v1.invoiceEmail
<- customer.Customer.v2.email
The system must answer:
- Which models depend on this source model?
- Which projections use this field?
- Which subscriptions materialize this projection?
- Which downstream systems are affected by a breaking change?
- Which fields contain data derived from PII?
11. Generated Artifacts
The system generates artifacts from the normalized model graph. External tools consume these artifacts; they do not feed back into the internal model.
Modelable IDL (.mdl files)
|
v
Lark Parser + Semantic Validator
|
v
Normalized Model Graph (Pydantic IR)
|
|-- JSON Schema
|-- Markdown docs
|-- TypeScript types
|-- OpenMetadata metadata
|-- ODCS export
`-- Registry artifacts (Apicurio)
Incorporation Order
Artifacts are introduced in phases. Later phases depend on the normalized graph being stable.
Phase 1 — Local modelling compiler:
- JSON Schema 2020-12 (first generated contract format)
- Markdown documentation
- TypeScript types (via
json-schema-to-typescript)
Phase 2 — Artifact registry:
- Apicurio Registry (stores and versions generated JSON Schema artifacts)
Phase 3 — Catalog / governance sync:
- OpenMetadata export (domains, assets, lineage, classification tags)
Phase 4 — Contract interchange:
- Open Data Contract Standard (ODCS) export
- Data Contract CLI compatibility
Phase 5 — Event and API targets:
- Avro (event schemas, Kafka contracts)
- Protobuf + Buf (gRPC, binary wire format)
- OpenAPI (REST API contracts)
- AsyncAPI (event contract documentation)
External Tool Boundaries
| External Tool | Role | What Modelable Does Not Delegate |
|---|---|---|
| JSON Schema / jsonschema | Generated contract format and validation | Internal DSL definition |
| Apicurio Registry | Artifact storage and versioning | Source of truth |
| OpenMetadata | Catalog UI, ownership, lineage visualization | Projection resolution |
| ODCS / Data Contract CLI | Interchange and CI validation | Internal model shape |
| json-schema-to-typescript | TypeScript type generation | Custom TS generator |
JSON Schema Extensions
Generated JSON Schema documents use x-modelable-* vendor extensions to carry Modelable-specific metadata:
| Extension | Purpose |
|---|---|
x-modelable |
Model kind, domain, name, and version block |
x-modelable-field |
Fully qualified field reference for lineage |
x-modelable-classification |
Field classification level: open, internal, confidential, or secret. Set only when @classification is declared; @pii is carried separately in x-modelable-field. |
x-modelable-lineage |
Source field reference for derived fields |
x-modelable-ref |
Cross-model reference |
x-modelable-por |
Portable ownership record reference |
All generated artifacts must include model version metadata.
12. Storage Model for Registry
The registry uses a file-first, SQLite-indexed storage model.
Source of truth: .mdl files on disk. Authors write and version-control .mdl definition files using the Modelable IDL. The registry never modifies these files. All definitions live in source control alongside application code.
Derived index: SQLite. The modelable compile command reads all .mdl files and writes a derived registry.db (SQLite) file to the .modelable/ output directory. The database is a build artifact — never edited directly. Deleting it and re-running compile must produce an identical result.
SQLite is used because it provides efficient relational queries for lineage traversal, consumer lookup, and compatibility checks without requiring a server or any setup for local use.
Output layout (post-compile, local mode):
.modelable/
registry.db # derived — rebuilt by `modelable compile`
plans/
customer.Customer.v2.plan.json # interpreted plan document
artifacts/
customer/
Customer.v2.json # generated JSON Schema
Customer.v2.md # generated Markdown
Customer.v2.ts # generated TypeScript types
Minimum logical entities in registry.db:
domainsmodelsmodel_versionsfieldsprojectionsprojection_versionsprojection_sourcesprojection_fieldsfield_mappingsaggregationssubscriptionsadapter_bindingscompatibility_reportslineage_edgesaccess_policies
Published definitions are stored as complete immutable .mdl documents within the source files to preserve exact historical contracts. The SQLite index is derived from these documents, not the other way around.
12.1 Distributed Mode
When a registry block is present in workspace.mdl, the compiler operates in distributed mode. Peers are other git repositories. The CLI owns graph traversal and sync — no running server is required.
Output layout (post-compile, distributed mode):
<workspace>/ # source-controlled
workspace.mdl
*.mdl
consumers/
<peer-registry-id>/
<Projection>@<v>.mdl # written by peer compilers (two-way write-back)
.modelable/ # build artifacts — all rebuildable by modelable compile
registry.db # single derived database (local + mirrored models, lineage, peers)
mirror/
<peer-registry-id>/ # sparse checkout of peer .mdl files
*.mdl
plans/
billing.BillingCustomer.v1.plan.json
artifacts/
billing/
BillingCustomer.v1.json
BillingCustomer.v1.ts
BillingCustomer.v1.md
Sources of truth that must be committed to git:
- All
.mdlsource files. consumers/entries (incoming write-backs from downstream registries).
Everything under .modelable/ is a build artifact. Deleting it and running modelable compile reproduces it.
registry.db additions for distributed mode:
registry_peers— declared peer nodes, git remotes, sync and writeback modes, last-fetched git SHA.mirrored_model_versions— cached foreign model versions with content signatures.consumers— downstream dependents derived from theconsumers/directory.- Two new columns on
lineage_edges:source_content_signature,is_cross_registry,source_registry_id.
Every published model version receives a content signature — a SHA-256 hash of its canonical definition — stored in registry.db and written into all cross-registry references. Git's SHA chain provides tamper evidence for the source files themselves; content signatures provide it for derived cross-registry references in plan documents and consumers/ entries.
See compiler-reference.md for registry, graph export, and distributed-lineage behavior.
13. APIs
13.1 Registry API
Required operations:
createDomaincreateModelDraftpublishModelVersiondeprecateModelVersioncreateProjectionDraftpublishProjectionVersionvalidateProjectiongetModelVersiongetProjectionVersionlistDependencieslistConsumerscheckCompatibilityexportArtifact
13.2 Runtime API
Required operations:
planProjectionrunBatchProjectionstartSubscriptionstopSubscriptionrebuildMaterializationgetSubscriptionStatusgetProcessingErrorsretryDeadLetter
14. Error Handling
14.1 Definition Errors
Definition errors must prevent publication.
Examples:
- Unknown source model.
- Unknown source field.
- Type mismatch.
- Invalid expression.
- Unsupported aggregation.
- Unauthorized field access.
- Adapter capability mismatch.
14.2 Runtime Errors
Runtime errors must be observable and recoverable where possible.
Examples:
- Source stream unavailable.
- Target database unavailable.
- Invalid source payload.
- Write conflict.
- Offset commit failure.
- Dead-letter write failure.
Runtime errors must include enough context to identify:
- Subscription.
- Projection.
- Source event.
- Target operation.
- Error category.
- Retry state.
15. Observability
The system must expose:
- Subscription health.
- Processing lag.
- Last processed offset.
- Throughput.
- Error count.
- Dead-letter count.
- Rebuild progress.
- Adapter health.
- Projection version currently deployed.
Logs and metrics must include:
- Domain.
- Model.
- Model version.
- Projection.
- Projection version.
- Subscription.
16. Security Requirements
The system must support:
- Authentication for registry and runtime APIs.
- Authorization at domain, model, projection, and field level.
- Audit logs for publication, deprecation, access policy changes, and subscription changes.
- Optional encryption metadata for sensitive fields.
- Redaction rules for logs and dead-letter payloads.
PII and restricted fields must not be exposed to projections unless explicitly permitted.
17. MVP Scope (Phase 1)
The first version implements the local modelling compiler. Apicurio JSON Schema artifact publish/pull is available as a derived-artifact integration. Runtime materialization, live catalog sync, and distributed registry services remain deferred.
17.1 Implementation Stack
- Parser:
lark>=1.1(Earley parser, EBNF grammar incli/src/modelable/grammar/modelable.lark). - IR:
pydantic>=2.0(typed internal model graph; not exposed as the external contract format). - Output validation:
jsonschema>=4.23,referencing>=0.35. - Output: JSON Schema 2020-12, Markdown, TypeScript (via
json-schema-to-typescript). - CLI:
click>=8.1,rich>=13.0.
17.2 CLI Commands
See the Modelable Tooling Reference for the full command reference.
17.3 Included in MVP
- Domain registry.
- Model definition and immutable publishing.
@serverfield annotation.- Projection definition with field selection, rename, simple expressions (CEL), and filters.
- Auto projections (
db,request,reply,event) with compiler expansion and full lineage tracking. - Exact source version references and compatible version ranges.
- Compatibility checks for additive and breaking changes.
- Lineage tracking.
- JSON Schema 2020-12 generation with
x-modelable-*extensions. - TypeScript type generation via
json-schema-to-typescript. - Markdown documentation generation.
- Basic CLI for publishing, validating, compiling, and exporting definitions.
modelable inspect <Entity>@<v> --autocommand to display the compiler-expanded auto projections.
Shipped beyond the original MVP scope
- OpenMetadata and OpenLineage local export (
compile --target openmetadata|openlineage) plus Marquez-compatible OpenLineage event sync (sync --lineage marquez). - ODCS / Data Contract CLI local import/export and lint validation.
- dbt
schema.ymlexport/import, FHIR R4StructureDefinitionexport/import. - Multi-source joins in projections (composite-key joins across domains).
- C#, Java, Python, Rust, and Go native code generation.
Deferred
- PostgreSQL storage adapter (Phase 5).
- Kafka stream adapter (Phase 5).
- Materialized projection into PostgreSQL (Phase 5).
- Live OpenMetadata catalog synchronization (local export only is shipped; see integrations.md).
- Runtime OpenLineage event collection beyond design-time Modelable events.
- Avro, Protobuf, OpenAPI, AsyncAPI generation (Phase 5) — import-only support exists via LLM-assisted generators.
- Stateful aggregations.
- Windowed aggregations.
- Multiple stream backends.
- Multiple database backends.
- Advanced policy engine.
- Visual modeling UI.
- Automatic migration generation.
- Kafka runtime provisioning, Redis materialisers, ClickHouse loaders, Feast, API gateways, dbt, Great Expectations, Soda.
- Distributed registry peer server (HTTP API for runtime lineage queries) — Phase 2, if needed.
18. Non-Goals
The platform is not initially:
- A replacement for all database schema migration tools.
- A universal query engine.
- A complete data catalog.
- A full data warehouse transformation platform.
- A generic ETL tool.
- A business intelligence semantic layer.
It may integrate with those systems, but its primary responsibility is versioned domain model contracts and derived projections.
19. Open Design Decisions
System-level design decisions have been resolved. Phase-specific documents may still track implementation choices that are deferred until their phase.
Resolved:
- Definition IDL: Custom text IDL (
.mdlfiles), parsed with Lark (Earley grammar). See language-reference.md for the full design rationale and syntax reference. - Expression language for computed fields: CEL (Common Expression Language). Deterministic, non-Turing-complete, sandboxable.
- Internal parser models:
pydantic. Not exposed as the external contract format. - First generated artifact: JSON Schema 2020-12.
- Codegen architecture: Codegen is a first-class extensible boundary. TypeScript generation is delegated to
json-schema-to-typescriptin Phase 1; C#, Java, Python, Rust, and Go are implemented locally as native generated-language backends, and additional future framework targets remain open. - Future generated-language targets: Additional generated-language targets beyond the implemented C#, Java, Python, Rust, and Go backends remain deferred.
- Version scheme: Integer versions with a required
changeKind: additive | breakingdeclaration on publish. See section 8.1. - Composite keys: Supported in MVP.
identity.keyaccepts a string (single field) or a list (composite). See section 3.3. - Version ranges in projections: Allowed in MVP. The planner resolves to the highest satisfying published version at plan time. See section 8.2.
- Registry storage: File-first (
.mdlsource of truth) with a singleregistry.dbSQLite derived index written bycompile. In distributed mode peers are git remotes;mirror/holds sparse checkouts of foreign.mdlfiles;consumers/holds incoming write-backs from downstream registries. All derived data is inregistry.db; all source of truth is in git. See section 12 and compiler-reference.md. - Runtime plan execution: Interpreted plan documents (structured JSON artifacts). Not generated code. See section 7.2.
- Sample scope: Sample scenarios may include future-phase constructs such as
materialisation, subscriptions, and runtime adapter bindings when they are clearly examples of deferred runtime behavior. - AI model configuration: LLM-assisted CLI commands use configurable model selection rather than a hard-coded model. See cli-reference.md.
20. Acceptance Criteria
Phase 1 is acceptable when:
- A domain can publish a model version.
- Another domain can define and publish a projection over that model.
- The system can validate the projection before runtime.
- The system can detect whether a model change breaks existing projections.
- The system can show lineage from projection fields to source fields.
- The model and projection can be exported as JSON Schema and TypeScript types.
- Projection of restricted or insufficiently governed fields is detected and reported as governance findings. Phase 1 does not claim to enforce real-world organizational authorization; enforcement remains a governance process or future policy layer.
Runtime acceptance criteria for Phase 5:
- A subscription can stream source changes into a materialized projected PostgreSQL table.
- The materialization can be replayed or rebuilt.