Summary
| Layer | What It Defines | Who Writes It |
|---|---|---|
| M3 | Primitives for building DSLs | Framework authors (once) |
| M2 | Six CMF DSLs (DDD, Content, Admin, Pages, Workflow, Requirements) | DSL authors |
| M1 | Domain models using the DSLs | Application developers |
| M0 | Runtime instances | The program at runtime |
This series and Modeling, Metamodeling, and Meta-Metamodeling in C# form a pair. The first establishes the theory -- M3, attributes as DSL surfaces, multi-staged Roslyn generators. This series demonstrates the application -- a Content Management Framework that turns decorated classes into a full-stack DDD application with pages, workflows, audit, search, caching, and a shared Blazor WebAssembly kernel. The Diem project started this journey in PHP in 2010. The meta-metamodel completes it in C# in 2026.
What the Nineteen Parts Built
In total this series describes a CMF whose surface is six DSLs (extensible to N), whose pipeline is five stages plus reporting, and whose runtime is a Blazor-native full stack with type safety end-to-end. The mental model that links every part is a single sentence: the developer writes attributes, the compiler writes everything else, and the analyzers fail the build when anything drifts.
| Part | Layer | Headline |
|---|---|---|
| 01 Introduction | Context | CMF vs CMS, the Diem heritage |
| 02 Architecture | Pipeline | Two axes, multi-stage Roslyn |
| 03 DDD DSL | M2 | Aggregates, entities, value objects, commands, sagas |
| 04 Composition | M2 | Composition vs association, EF Core mapping |
| 05 Admin DSL | M2 | One attribute, full Blazor CRUD |
| 06 Content Parts | M2 | Parts, blocks, StreamFields, versioning |
| 07 Pages DSL | M2 | Page tree, layouts, dynamic routing |
| 08 Workflow DSL | M2 | Editorial state machines, gates, per-locale |
| 09 Generated output | Pipeline | Stage 2+3 walkthrough, 1 : 41 ratio |
| 10 Shared kernel | Runtime | One C# kernel, two compilation targets |
| 11 CLI tooling | Surface | cmf new, add, generate, validate, report, design |
| 12 E-commerce walkthrough | Application | Day-by-day from cmf new to running storefront |
| 13 Requirements DSL | M2 | Features, ACs, gates, traceability |
| 14 Landscape | Context | Why Diem, Wagtail, Orchard, Drupal, Symfony CMF, Strapi |
| 15 Testing | Practice | Seven layers from invariants to E2E |
| 16 Security & audit | Cross-cutting | RBAC, claims, multi-tenancy, audit trail |
| 17 Extending M3 | Extension | A seventh DSL as a NuGet package |
| 18 Performance | Runtime | Eager loading, caching, read models, search |
| 19 Summary | — | This page |
Two Properties Worth Restating
Two emergent properties of the design are worth pulling out, because they explain why the cost-benefit of all this generator infrastructure is positive:
Single source of truth, propagated mechanically. Any change a developer makes to the M1 model — renaming a property, adding a workflow stage, changing a required role, marking a feature as Done — cascades through the entire generated stack on the next build. There is no second place where the change has to be remembered. The most expensive class of CMS bug, "the database, the API, and the UI disagree about what a thing is", is impossible by construction.
Failure visible at compile time, not in production. The analyzer families CMF1xx through CMF6xx cover aggregate shape, composition cycles, kernel hygiene, requirement coverage, workflow reachability, and performance budgets. Anything that would surprise the developer at 3 a.m. instead surprises them in the IDE squiggle, three seconds after the offending keystroke. The cost is a slower compile; the savings are measured in the absence of incidents.
What the CMF Does Not Cover
The series is a design specification for a content management framework, which is a deliberately bounded scope. The following concerns are outside it and either belong to neighboring DSLs or remain as hand-written code:
| Concern | Belongs to | Notes |
|---|---|---|
| Build pipelines, deployment, infrastructure | Ops DSL ecosystem | The CMF emits artifacts; the Ops DSL ships them |
| SDLC pipelines as types | SDLC DSL (in the broader cmf/ series) | Local-first build pipelines |
| ALM (services, flags, SLOs) | ALM DSL | Aspire wiring, OpenTelemetry, dashboards |
| PLM (products, releases, roadmap) | PLM DSL | Build-time-only metadata |
| Authentication providers (OIDC, JWT, cookie) | Hand-written Startup.cs |
The CMF generates authorization, not authentication |
| Distributed transactions across bounded contexts | Distributed Task series | Sagas with idempotency keys |
| Long-form editorial workflows that span humans and time | The Editorial workflow DSL — bounded | Year-long approval chains belong elsewhere |
| Real-time collaboration (operational transforms, CRDTs) | Out of scope | A deliberate non-goal |
| Static-site generation | Out of scope | The CMF is dynamic-first |
| AI-assisted content generation | Out of scope | A separate concern with its own lifecycle |
The list of non-goals is as important as the list of goals. A framework that promises everything delivers a Drupal — a system whose features are individually adequate and collectively a maintenance burden. The CMF promises exactly six DSLs plus the M3 surface for adding more, and stops there.
Roadmap: Self-Hosting
The series describes a CMF whose own source has not yet been written. The roadmap is short:
- Bootstrap. Implement the M3 primitives and the DDD DSL by hand. This is the only code that cannot be generated, because it is the generator.
- Self-hosting. Re-express the CMF's own type registry, content blocks, and admin pages using the DSLs. The fixed point — the CMF used to define itself — is the moment the M3 design is validated.
- Six DSLs. Implement Content, Admin, Pages, Workflow, Requirements as separate generator packages on top of DDD. Each is a few thousand lines of generator code plus templates.
- Cross-cutting. Add the audit, security, performance, and testing scaffolds described in parts 15–18. These are the additive generators that run after the six core ones.
- First real application. Port a small Diem instance into the CMF and measure the gap. The migration is the truth-test for the design.
A reasonable estimate for steps 1–3 is the same order of magnitude as a small open-source ORM project — months, not years, for a small team. The gating factor is not LOC but the analyzer coverage and the snapshot tests for the generators, which take longer than the generators themselves. This is the right shape: the framework's quality is measured by the quality of its own test suite, not its feature count.
Learning Resources
For a reader who wants to understand the foundations more deeply or apply the same patterns to a different domain:
| Resource | Why |
|---|---|
| Modeling, Metamodeling, and Meta-Metamodeling in C# | The M3 theory the entire CMF rests on |
| DDD as a Modeling Discipline | The domain modeling vocabulary used throughout parts 03–04 |
| Feature Tracking: A Type-Safe Requirements Chain | The requirements layer in depth (nine-part series) |
| Entity DSL | A standalone DSL for entities and value objects, which informed the DDD DSL design |
| Builder Pattern | The pattern every generated aggregate uses |
| Result Pattern | The error-handling discipline shared by command handlers and validators |
| Finite State Machines | The library powering the workflow and requirement lifecycle state machines |
| Quality Gates | The post-test quality gates the CMF integrates with |
| Ops DSL Ecosystem | The deployment counterpart to the CMF — same pattern, different domain |
Closing
The Diem project, in 2010, proved that a Content Management Framework — empty by default, generated from declarations, type-checked at runtime — was a viable alternative to a Content Management System. It was held back by the languages and tools of its day: dynamic PHP, YAML schemas, runtime reflection. Sixteen years of progress in compilers, type systems, and Roslyn-style code generation make the original idea executable in a way it could not have been then. This series is what that execution looks like in design form. The next step is to write it.