# SpecVerse Architecture Guide

This is the **internal architecture reference** for SpecVerse — for engineers contributing to the engines, evaluators auditing the design, and readers (investor or technical) who want to understand how the pieces compose. If you're a SPEC AUTHOR rather than a CONTRIBUTOR, read [SPECVERSE-SPECIFYING.md](SPECVERSE-SPECIFYING.md) first; this doc assumes you've seen the language and now want to know what's under the hood.

## Overview

SpecVerse is a specification language ecosystem. You write `.specly` files that describe **WHAT** a system does. The engines figure out **HOW** to implement it. The technology choices live in a separate **manifest** file, so swapping Fastify for NestJS or PostgreSQL for MongoDB never touches the spec.

The shortest possible mental model:

```
.specly file (WHAT)
    |
    v
[Parser Engine] --> SpecVerseAST
    |
    v
[Inference Engine] --> Controllers, Services, Events, Views, Deployments
    |
    v
[Realize Engine] --> Generated Code (Prisma, Fastify, React, CLI, etc.)
```

The architecture rests on three load-bearing claims:

- **Define Once, Implement Anywhere™.** The same `.specly` spec produces a Fastify+Prisma+SQLite app, a NestJS+TypeORM app, or a runtime-interpreted dynamic app. The spec is the source of truth; the manifest controls the technology choice.
- **The entity-module abstraction is the unit of extensibility.** Adding a new entity type (the language gets a new noun like `workflows` or `policies`) is one new directory of nine facets — every existing engine + composer picks it up automatically. Codified as **R12** in the [Golden Rules](../GOLDEN-RULES.md).
- **Self-hosting is the proof.** SpecVerse specified itself, generated its own CLI, and that generated output is the production release `@specverse/self` on npm. The bootstrap promotion cycle (R30–R36 + R31a) keeps the regeneration safe.

### What this guide covers

- **Architecture Diagrams** — three views of the architecture (end-to-end flow, language extension, test lifecycle), each answering a distinct question. Start here if you're new.
- **Repository Structure** — the four-package npm workspace layout and what lives where.
- **The Three Layers** — Specification (what users write) / Entity Modules (extensible building blocks) / Engines (independent processors).
- **How the Pipeline Works** — parse → infer → realize, in detail.
- **How Things Connect** + **Composition Pipelines** — wiring reference: which facet feeds which engine; which composer emits which artifact.
- **Key Design Decisions** — the rationale: why double-validation, why explicit engine registration, why self-hosting.
- **Current Implementation** — what's wired today + the short list of open work.

### Companion guides

| When you want to | Read |
|---|---|
| Write a `.specly` spec (language reference) | [SPECVERSE-SPECIFYING.md](SPECVERSE-SPECIFYING.md) |
| Run the toolchain (`spv` CLI) | [SPECVERSE-TOOLING.md](SPECVERSE-TOOLING.md) |
| Generate code from a spec | [SPECVERSE-REALIZING.md](SPECVERSE-REALIZING.md) |
| Add a new entity type / engine / instance factory / LLM provider | [SPECVERSE-EXTENDING.md](SPECVERSE-EXTENDING.md) |
| Understand the AI subsystem (four-mode provider, prompt partials) | [SPECVERSE-AI.md](SPECVERSE-AI.md) + [SPECVERSE-AI-ARCHITECTURE.md](SPECVERSE-AI-ARCHITECTURE.md) |
| Understand bootstrap and self-hosting | [SPECVERSE-SELF-HOSTING.md](SPECVERSE-SELF-HOSTING.md) |
| Read the 44 golden rules | [GOLDEN-RULES.md](../GOLDEN-RULES.md) |

## End-to-End Flow

This is the visual entry-point — how a SpecVerse-based solution comes into existence, from either input path (natural-language requirements OR an existing codebase) through every architectural layer to a running application. Three more diagrams live alongside the sections they elaborate:

- **[`.specly` Schema](#specly-schema--what-a-spec-actually-contains)** (under Layer 1: Specification) — what a spec actually contains
- **[Entity-module facets exploded](#entity-module-facets-exploded)** (under Layer 2: Entity Modules) — how the language is extended
- **[Release & Quality Lifecycle](#release--quality-lifecycle)** (its own section near the end) — how code reaches users

```mermaid
flowchart TD
    subgraph INPUTS["Input paths"]
        NL["Natural-language<br/>requirements<br/>(user prose)"]
        SRC["Existing codebase<br/>(source dir)"]
    end

    subgraph AI["AI workflows (LLM-mediated)"]
        CREATE["spv ai create<br/>(+ verify-create)"]
        ANALYSE["spv ai analyse<br/>(+ verify-analyse)"]
        PREPASS["Structural prepass<br/>3 backends:<br/>grep-only / CodeGraph / GitNexus"]
    end

    SRC --> PREPASS
    PREPASS -->|"deterministic facts<br/>(entities, relationships,<br/>method fact sheets)"| ANALYSE
    NL --> CREATE

    SPEC[".specly spec<br/>(structured intent)<br/>+ manifest<br/>+ deployments"]
    CREATE --> SPEC
    ANALYSE --> SPEC

    subgraph PACKAGES["@specverse npm packages"]
        TYPES["@specverse/types<br/>AST + interfaces<br/>+ spec-rules"]
        ENTITIES["@specverse/entities<br/>11 entity modules × 9 facets<br/>(schema, conventions, inference,<br/>generators, behaviour, examples,<br/>tests, docs, behavioural grammar)"]
        ASSETS["@specverse/assets<br/>6 prompts + 10 partials<br/>+ canonical examples"]
        ENGINES["@specverse/engines<br/>parser / inference / realize<br/>/ generators / ai / registry"]
        RUNTIME["@specverse/runtime<br/>walker + React/Tailwind<br/>view adapters"]
    end

    SPEC --> ENGINES
    ENTITIES -.->|"discovered at<br/>runtime via registry"| ENGINES
    TYPES -.->|"shared types"| ENGINES
    ASSETS -.->|"prompts +<br/>schema +<br/>examples"| ENGINES

    subgraph PIPELINE["Engine pipeline"]
        PARSE["Parser engine<br/>schema-validates +<br/>expands conventions"]
        INFER["Inference engine<br/>fills in CURVED ops,<br/>events, views,<br/>deployment shells"]
        REALIZE["Realize engine<br/>per-component fan-out;<br/>resolves manifest →<br/>capability → factory"]
    end

    ENGINES --> PARSE
    PARSE -->|"AST"| INFER
    INFER -->|"expanded spec"| REALIZE

    subgraph FACTORIES["Instance factories (HOW) — 13 categories, swap any without spec changes"]
        F_DB["<b>Storage (DB)</b><br/>PostgreSQL15<br/>MongoDB6<br/>Redis7"]
        F_ORM["<b>ORM</b><br/>PrismaORM"]
        F_CTRL["<b>Controller (API/RPC)</b><br/>FastifyAPI"]
        F_SVC["<b>Service</b><br/>PrismaServices"]
        F_VIEW["<b>View</b><br/>ReactAppRuntime<br/>ReactAppStarter"]
        F_CMD["<b>Command (CLI)</b><br/>CommanderJS"]
        F_TOOLS["<b>Tools</b><br/>VSCodeExtension<br/>MCPServer"]
        F_COMM["<b>Communication</b><br/>EventEmitter<br/>RabbitMQEvents"]
        F_VAL["<b>Validation</b><br/>ZodValidation"]
        F_TEST["<b>Testing</b><br/>VitestTests"]
        F_SDK["<b>SDK</b><br/>TypeScriptSDK<br/>PythonSDK"]
        F_SCAF["<b>Scaffolding</b><br/>GenericScaffold"]
        F_INFRA["<b>Infrastructure</b><br/>DockerKubernetes"]
    end

    MANIFEST["manifest<br/>(capability → factory<br/>mappings)"]
    SPEC --> MANIFEST
    MANIFEST --> REALIZE
    REALIZE --> F_DB
    REALIZE --> F_ORM
    REALIZE --> F_CTRL
    REALIZE --> F_SVC
    REALIZE --> F_VIEW
    REALIZE --> F_CMD
    REALIZE --> F_TOOLS
    REALIZE --> F_COMM
    REALIZE --> F_VAL
    REALIZE --> F_TEST
    REALIZE --> F_SDK
    REALIZE --> F_SCAF
    REALIZE --> F_INFRA

    subgraph CODEGEN["Generated code (L1 / L2 / L3 / L4)"]
        L1["L1 Templates<br/>(scaffolding,<br/>no LLM)"]
        L2["L2 Convention<br/>(CURVED ops,<br/>15 patterns)"]
        L3["L3 constraint guards<br/>(runtime, fail-open;<br/>only if constraints:)"]
        L4["L4 *.ai.ts<br/>(LLM-generated<br/>from steps)"]
    end

    F_DB --> L1
    F_ORM --> L1
    F_CTRL --> L1
    F_SVC --> L1
    F_VIEW --> L1
    F_CMD --> L1
    F_TOOLS --> L1
    F_COMM --> L1
    F_VAL --> L1
    F_TEST --> L1
    F_SDK --> L1
    F_SCAF --> L1
    F_INFRA --> L1
    REALIZE --> L2
    REALIZE --> L3
    REALIZE --> L4

    OUTPUT["Generated bundle<br/>backend + frontend +<br/>CLI + MCP + VSCode<br/>+ contract tests"]
    L1 --> OUTPUT
    L2 --> OUTPUT
    L3 --> OUTPUT
    L4 --> OUTPUT

    subgraph DELIVERY["Running solution"]
        APP["Production app<br/>(Fastify + Prisma +<br/>React etc.)"]
        DYNAMIC["app-demo<br/>(runtime interpreter,<br/>same spec, no codegen)"]
    end

    OUTPUT -->|"npm install +<br/>prisma push +<br/>spin up"| APP
    SPEC -.->|"alternative path:<br/>load + execute"| DYNAMIC
    RUNTIME -.->|"view rendering"| APP
    RUNTIME -.->|"view rendering"| DYNAMIC

    style NL fill:#2874a6,stroke:#1a5276,stroke-width:2px,color:#fff
    style SRC fill:#2874a6,stroke:#1a5276,stroke-width:2px,color:#fff
    style SPEC fill:#7d3c98,stroke:#4a235a,stroke-width:3px,color:#fff
    style MANIFEST fill:#b9770e,stroke:#7d6608,stroke-width:2px,color:#fff

    style F_DB fill:#a04000,stroke:#6e2c00,color:#fff
    style F_ORM fill:#a04000,stroke:#6e2c00,color:#fff
    style F_CTRL fill:#a04000,stroke:#6e2c00,color:#fff
    style F_SVC fill:#a04000,stroke:#6e2c00,color:#fff
    style F_VIEW fill:#a04000,stroke:#6e2c00,color:#fff
    style F_CMD fill:#a04000,stroke:#6e2c00,color:#fff
    style F_TOOLS fill:#a04000,stroke:#6e2c00,color:#fff
    style F_COMM fill:#a04000,stroke:#6e2c00,color:#fff
    style F_VAL fill:#a04000,stroke:#6e2c00,color:#fff
    style F_TEST fill:#a04000,stroke:#6e2c00,color:#fff
    style F_SDK fill:#a04000,stroke:#6e2c00,color:#fff
    style F_SCAF fill:#a04000,stroke:#6e2c00,color:#fff
    style F_INFRA fill:#a04000,stroke:#6e2c00,color:#fff

    style OUTPUT fill:#1e8449,stroke:#0e6655,stroke-width:2px,color:#fff
    style APP fill:#229954,stroke:#0e6655,stroke-width:2px,color:#fff
    style DYNAMIC fill:#229954,stroke:#0e6655,stroke-width:2px,color:#fff
```

**Reading the diagram:**

- **Two input paths converge on the spec** — users either describe a system in prose (`spv ai create`) or point at an existing codebase (`spv ai analyse`, with a deterministic structural-prepass step injecting facts before the LLM sees source). Both end at a `.specly` spec + a manifest + a deployments block.
- **The five `@specverse/*` packages** form the toolchain. `engines` is the runtime; it discovers `entities` via registry, pulls AST types from `types`, and reads prompts + canonical content from `assets`. `runtime` ships independently and is consumed by both the generated frontend and the dynamic interpreter.
- **The engine pipeline is three stages** — parser (validates + expands), inference (fills in everything the user didn't write), realize (matches capabilities to factories and emits code).
- **Instance factories are the technology layer** — 13 first-class categories (Storage / ORM / Controller / Service / View / Command / Tools / Communication / Validation / Testing / SDK / Scaffolding / Infrastructure). Each category has one or more concrete factories (e.g. Storage has PostgreSQL15 + MongoDB6 + Redis7; View has ReactAppRuntime + ReactAppStarter). The manifest maps capabilities to specific factories — swap any factory in any column and the spec stays unchanged.
- **Generated code is layered** — L1 from templates (no LLM), L2 from convention patterns (no LLM), L3 from Quint-verified guards (formally checked), L4 from per-step LLM calls (the smallest sliver, ~5% of typical output).
- **Two delivery modes** — generate code once and ship a self-contained app, OR load the spec into `app-demo` and execute it as a live interpreter (no code generation). Both render UI through the same `@specverse/runtime` walker (R2: one pattern library, three consumers).

The dotted arrows are runtime / data-only relationships (configuration, types, registry lookup); solid arrows are forward execution flow.

## Repository Structure

```
specverse-engines/       -- 4-package npm workspace (source of truth)
  types/                 -- Shared types (AST, engine interfaces, spec-rules)
  entities/              -- Entity module system + EngineRegistry + schema composition
  engines/               -- Toolchain — subpath exports:
    src/
      parser/            -- Parse .specly --> AST
      inference/         -- Models --> full architecture (Handlebars templates)
      realize/           -- Spec + manifest --> generated code
      generators/        -- Diagrams, UML, documentation
      ai/                -- LLM providers + orchestration
      registry/          -- Explicit engine registration (R36)
    libs/
      instance-factories/  -- Code generators per technology
  runtime/               -- Walker + React + Tailwind + test-harness

specverse-self/          -- Production release (@specverse/self): CLI, templates, self-spec
specverse-app-demo/      -- Dynamic runtime interpreter (consumer of @specverse/engines + /runtime)
```

`specverse-lang` is the historical CLI orchestrator — fully superseded by specverse-self and archived.

### Documentation Infrastructure

Documentation freshness is maintained by four standalone scripts under `specverse-self/scripts/indexing/` — the R28 + R29 compliance tooling. They hash-index every file in every repo, audit guide coverage against discovered content areas, compare indexes across repos for drift, and generate the canonical `docs/DOCUMENTATION-INDEX.md` map. All four are idempotent, fast, and safe to run any time. See [`scripts/indexing/README.md`](../../scripts/indexing/README.md) for usage.

## The Three Layers

### Layer 1: Specification (what users write)

A `.specly` file has three sections:

```yaml
components:        # WHAT the system does
  MyApp:
    models: ...
    controllers: ...
    services: ...
    events: ...
    views: ...
    commands: ...

deployments:       # WHERE it runs
  development:
    instances: ...

manifests:         # HOW it's built (technology choices)
  implementation:
    capabilityMappings: ...
```

#### `.specly` Schema — what a spec actually contains

The diagram below is the hierarchical tree of every concept the `.specly` language admits — generated from the canonical JSON Schema at `entities/schema/SPECVERSE-SCHEMA.json` (62 `$defs`, composed from 15 entity-module fragments). Three top-level sections, each with its own deep child shape. Names of sub-fields are exact — anything you can write in a `.specly` file appears below.

```mermaid
flowchart LR
    SPECLY[".specly file<br/>(structured intent — top-level)"]

    SPECLY --> COMPONENTS["<b>components</b>: { ComponentName: { ... } }<br/>WHAT the system does"]
    SPECLY --> DEPLOYMENTS["<b>deployments</b>: { DeploymentName: { ... } }<br/>WHERE it runs"]
    SPECLY --> MANIFESTS["<b>manifests</b>: { ManifestName: { ... } }<br/>HOW it's built (technology choices)"]

    subgraph COMP_SHAPE["Component-level keys"]
        direction LR
        COMP_META["version / description / tags<br/>import / export<br/>primitives / constraints"]
        ENTITIES["models · controllers · services · views<br/>events · commands · measures<br/>conventions · promotions · distributions"]
    end
    COMPONENTS --> COMP_META
    COMPONENTS --> ENTITIES

    subgraph MODEL_SHAPE["Model entity (most-used)"]
        direction TB
        M_ATTR["<b>attributes</b><br/>typed fields with conventions:<br/>'String required unique min=1 max=20'<br/>'UUID auto=uuid4'"]
        M_REL["<b>relationships</b><br/>hasMany / hasOne /<br/>belongsTo / manyToMany<br/>+ cascade / dependent / through"]
        M_LIFE["<b>lifecycles</b><br/>oneOf:<br/>{ flow: 'a -> b -> c' }<br/>{ states: [], transitions: { name: 'from -> to' } }"]
        M_BHV["<b>behaviors</b><br/>{ description, parameters, returns,<br/>requires, ensures, publishes,<br/><b>steps: []</b> }<br/>(L3 generation source)"]
        M_OTHER["metadata / profiles<br/>profile-attachment / extends"]
    end
    ENTITIES --> MODEL_SHAPE

    subgraph CTRL_SHAPE["Controller entity"]
        direction TB
        C_MODEL["<b>model</b><br/>(entity reference)"]
        C_CURED["<b>cured</b><br/>{ create / update / retrieve /<br/>retrieve_many / validate /<br/>evolve / delete }<br/>(CURVED operations)"]
        C_ACTIONS["<b>actions</b><br/>(custom ops, same shape<br/>as model behaviors —<br/>steps drive L3)"]
        C_SUB["<b>subscribes_to</b><br/>{ EventName: handlerMethod }"]
    end
    ENTITIES --> CTRL_SHAPE

    subgraph SVC_SHAPE["Service entity"]
        direction TB
        S_OPS["<b>operations</b><br/>(same ExecutableProperties:<br/>parameters / returns / requires /<br/>ensures / publishes / steps)"]
        S_SUB["<b>subscribes_to</b><br/>(event handlers)"]
    end
    ENTITIES --> SVC_SHAPE

    subgraph VIEW_SHAPE["View entity"]
        direction TB
        V_TYPE["<b>type</b><br/>list / detail / form / dashboard /<br/>board / timeline / calendar / analytics"]
        V_LAYOUT["<b>layout</b><br/>responsive grid spec"]
        V_UI["<b>uiComponents</b><br/>(walker pattern instances)"]
        V_PROPS["<b>properties</b><br/>{ sortable, filterable,<br/>pagination, exportable, ... }"]
    end
    ENTITIES --> VIEW_SHAPE

    subgraph EVT_SHAPE["Event entity"]
        direction TB
        E_ATTR["<b>attributes</b><br/>(payload schema, same shape<br/>as model attributes)"]
        E_VER["<b>version</b> / <b>previousVersions</b><br/>(event versioning)"]
    end
    ENTITIES --> EVT_SHAPE

    subgraph CMD_SHAPE["Command entity"]
        direction TB
        CMD_ARG["<b>arguments</b> / <b>flags</b><br/>(positional + named options)"]
        CMD_RET["<b>returns</b> / <b>exitCodes</b><br/>(structured CLI output)"]
        CMD_SUB["<b>subcommands</b><br/>(nested command trees)"]
    end
    ENTITIES --> CMD_SHAPE

    subgraph DEP_SHAPE["Deployment-level keys"]
        direction LR
        D_META["version / description / environment"]
        D_INST["<b>instances</b>:<br/>controllers · services · views ·<br/>communications · storage · security ·<br/>infrastructure · monitoring<br/>(8 instance categories with<br/>policy blocks: rateLimit, retry,<br/>circuitBreaker, idempotency, cache, ...)"]
    end
    DEPLOYMENTS --> D_META
    DEPLOYMENTS --> D_INST

    subgraph MAN_SHAPE["Manifest-level keys"]
        direction TB
        MAN_DEP["<b>deployment</b><br/>(deploymentSource +<br/>deploymentName)"]
        MAN_DEFAULTS["<b>defaultMappings</b>:<br/>storage / orm / routing / controller /<br/>service / communication / validation /<br/>frontend / cache / authentication"]
        MAN_CAPS["<b>capabilityMappings</b><br/>[{ capability, instanceFactory }]<br/>e.g. api.rest → FastifyAPI"]
    end
    MANIFESTS --> MAN_DEP
    MANIFESTS --> MAN_DEFAULTS
    MANIFESTS --> MAN_CAPS

    style SPECLY fill:#7d3c98,stroke:#4a235a,stroke-width:3px,color:#fff
    style COMPONENTS fill:#6c3483,stroke:#4a235a,color:#fff,stroke-width:2px
    style DEPLOYMENTS fill:#6c3483,stroke:#4a235a,color:#fff,stroke-width:2px
    style MANIFESTS fill:#6c3483,stroke:#4a235a,color:#fff,stroke-width:2px

    style COMP_META fill:#1f618d,stroke:#0e3a5c,color:#fff
    style ENTITIES fill:#1f618d,stroke:#0e3a5c,color:#fff,stroke-width:2px

    style M_ATTR fill:#117864,stroke:#0e6655,color:#fff
    style M_REL fill:#117864,stroke:#0e6655,color:#fff
    style M_LIFE fill:#117864,stroke:#0e6655,color:#fff
    style M_BHV fill:#117864,stroke:#0e6655,color:#fff,stroke-width:2px
    style M_OTHER fill:#117864,stroke:#0e6655,color:#fff

    style C_MODEL fill:#117864,stroke:#0e6655,color:#fff
    style C_CURED fill:#117864,stroke:#0e6655,color:#fff
    style C_ACTIONS fill:#117864,stroke:#0e6655,color:#fff,stroke-width:2px
    style C_SUB fill:#117864,stroke:#0e6655,color:#fff

    style S_OPS fill:#117864,stroke:#0e6655,color:#fff,stroke-width:2px
    style S_SUB fill:#117864,stroke:#0e6655,color:#fff

    style V_TYPE fill:#117864,stroke:#0e6655,color:#fff
    style V_LAYOUT fill:#117864,stroke:#0e6655,color:#fff
    style V_UI fill:#117864,stroke:#0e6655,color:#fff
    style V_PROPS fill:#117864,stroke:#0e6655,color:#fff

    style E_ATTR fill:#117864,stroke:#0e6655,color:#fff
    style E_VER fill:#117864,stroke:#0e6655,color:#fff

    style CMD_ARG fill:#117864,stroke:#0e6655,color:#fff
    style CMD_RET fill:#117864,stroke:#0e6655,color:#fff
    style CMD_SUB fill:#117864,stroke:#0e6655,color:#fff

    style D_META fill:#1f618d,stroke:#0e3a5c,color:#fff
    style D_INST fill:#1f618d,stroke:#0e3a5c,color:#fff,stroke-width:2px

    style MAN_DEP fill:#1f618d,stroke:#0e3a5c,color:#fff
    style MAN_DEFAULTS fill:#1f618d,stroke:#0e3a5c,color:#fff
    style MAN_CAPS fill:#1f618d,stroke:#0e3a5c,color:#fff,stroke-width:2px
```

**Reading the schema diagram:**

- **Three top-level sections** (purple): `components` / `deployments` / `manifests`. Every `.specly` file has these three keys at the root.
- **Component-level keys** (blue): metadata (version / description / tags / import / export) plus the eleven **entity types** the component declares. Models / controllers / services / views / events are the most-used; commands / measures / conventions / promotions / distributions cover meta-language and analytics needs.
- **Per-entity sub-fields** (teal): each entity type has its own deep shape. Models have attributes + relationships + lifecycles + behaviors. Controllers have CURVED operations + actions. Services have operations. Views have type + layout + uiComponents + properties. Events have attributes (payload) + versioning. Commands have arguments + flags + subcommands.
- **Behaviors / actions / operations all share one shape** — `ExecutableProperties` with `parameters / returns / requires / ensures / publishes / steps`. The `steps` array is what drives L3 AI behavior generation; everything else is contract surface (preconditions / postconditions / event emissions).
- **Two valid lifecycle shapes** (oneOf in the schema): a simple `flow: "a -> b -> c"` string OR an explicit `states: [] + transitions: { name: "from -> to" }` map. Mixing them or adding extra keys (`initial:`, `final:`) fails schema validation.
- **Deployment.instances** is where operational policy lives — eight instance categories (controllers / services / views / communications / storage / security / infrastructure / monitoring) each accept policy blocks like `rateLimit`, `retry`, `circuitBreaker`, `idempotency`, `cache` with concrete fields (`requestsPerMinute`, `backoffMs`, `halfOpenAfterMs`, etc.).
- **Manifests** are the "swap any technology" layer — `defaultMappings` give a category-level shorthand (e.g. `storage: PostgreSQL15`) and `capabilityMappings` give explicit capability→factory edges (e.g. `api.rest → FastifyAPI`). Realize resolves capabilities through this layer to pick which instance factory to invoke.

**Why this matters:** every word in the diagram is a real key the parser accepts. Spec authors writing `.specly` are filling in this tree. The 9-facet entity-module shape (see Layer 2 below) is what makes adding a new branch to this tree a one-directory change.

### Layer 2: Entity Modules (extensible building blocks)

Every concept in a `.specly` file (models, controllers, services, events, views, commands, etc.) is an **entity type**. Each entity type is defined by an **entity module** with up to 9 facets:

```
entity-module/
  module.yaml          -- Manifest: name, version, dependencies, facet paths
  schema/              -- JSON Schema: what's valid in .specly syntax
  conventions/         -- Convention processor: shorthand expansion
  inference/           -- Inference rules: what to generate from this entity
  generators/          -- Code generators: instance factory templates
  behaviour/           -- Quint specs: formal invariants and rules
    conventions/       -- Behavioural conventions: human-readable -> Quint
  __examples__/        -- Canonical example .specly snippets
  __tests__/           -- Test references (regression + parity)
  __behaviour__/       -- Quint .qnt files (invariants + rules + verify + test)
```

**Core entities** ship with SpecVerse: models, controllers, services, events, views, deployments.
**Extension entities** are added via packages: commands, conventions, measures.

#### Entity-module facets exploded

The block above shows entity modules as nine directory names. The diagram below expands one entity module to make each facet's flow concrete: which engine reads it, which composer writes it, what tracked artifact comes out. The same nine-facet shape applies to all 11 entity types.

```mermaid
flowchart LR
    subgraph EMOD["Entity module (1 of 11 — same shape applies to each)"]
        F_SCH["schema/<br/>JSON Schema fragments"]
        F_CONV["conventions/<br/>shorthand processor (.ts)"]
        F_INF["inference/<br/>rules (Handlebars JSON)"]
        F_GEN["generators/<br/>factory templates (.ts/.hbs)"]
        F_BHV["__behaviour__/<br/>Quint specs (.qnt)"]
        F_BGRAM["conventions/behavioural-grammar.yaml<br/>natural-language → Quint"]
        F_EX["__examples__/<br/>canonical .specly snippets"]
        F_TST["__tests__/<br/>vitest references"]
        F_DOC["docs/<br/>md references"]
    end

    subgraph COMPOSERS["Composition pipelines (deterministic)"]
        COMP_SCH["compose-schema.cjs<br/>walks every entity's schema/<br/>15 fragments → 62 $defs"]
        COMP_EX["compose-examples.mjs<br/>walks every entity's __examples__/<br/>+ engines/assets/examples-*"]
        COMP_DOC["docs-index.mjs<br/>walks every entity's docs/<br/>+ guides/ + plans/"]
        COMP_BUNDLE["validateBundle v2<br/>walks every facet<br/>(schema + tests + behaviour)"]
    end

    F_SCH --> COMP_SCH
    F_EX --> COMP_EX
    F_DOC --> COMP_DOC
    F_SCH --> COMP_BUNDLE
    F_TST --> COMP_BUNDLE
    F_BHV --> COMP_BUNDLE
    F_EX --> COMP_BUNDLE

    SCH_OUT["SPECVERSE-SCHEMA.json<br/>(tracked artifact —<br/>parser AJV input)"]
    EX_OUT["self/examples/<br/>(tracked, ~120 files —<br/>shipped to spv init)"]
    DOC_OUT["DOCUMENTATION-INDEX.md<br/>(tracked, every .md mapped)"]
    BUNDLE_OUT["BundleReport<br/>(facet × status × duration)"]

    COMP_SCH --> SCH_OUT
    COMP_EX --> EX_OUT
    COMP_DOC --> DOC_OUT
    COMP_BUNDLE --> BUNDLE_OUT

    subgraph ENGINES_V2["Engines — runtime aggregators (no explicit composer; aggregated at load time)"]
        REG["EntityRegistry<br/>(bootstrap from each<br/>module's index.ts)"]
        UPARSER["UnifiedSpecVerseParser<br/>+ ConventionProcessor<br/>per-entity dispatch"]
        IRENG["Inference engine<br/>Handlebars rule renderer<br/>+ rule loader"]
        REALIZE_V2["Realize engine<br/>per-component fan-out;<br/>resolves manifest→capability→factory"]
        BGEN["BehavioralConventionProcessor<br/>(reads behavioural grammars)"]
        QGUARDS["Quint transpiler<br/>(validate-time spec<br/>invariants)"]
        DGRAM["Diagram engine<br/>(per-entity ManifestPlugin)"]
    end

    F_CONV -.->|"discovered<br/>via registry"| UPARSER
    F_INF -.->|"loaded by<br/>rule-loader"| IRENG
    F_GEN -.->|"resolved by<br/>capability lookup"| REALIZE_V2
    F_BHV -.->|"transpiled at<br/>build time"| QGUARDS
    F_BGRAM -.->|"loaded by<br/>BehaviouralConventionProcessor"| BGEN
    EMOD -.->|"each module's<br/>index.ts"| REG

    REG --> UPARSER
    REG --> IRENG
    REG --> REALIZE_V2
    REG --> DGRAM

    SPEC_V2[".specly spec<br/>(structured intent)"]
    SCH_OUT --> UPARSER
    SPEC_V2 --> UPARSER
    UPARSER -->|"AST"| IRENG
    BGEN --> IRENG
    QGUARDS -->|"validate-time<br/>spec invariants"| IRENG
    IRENG -->|"expanded spec"| REALIZE_V2

    OUTPUT_V2["Generated bundle<br/>(L1 templates / L2 convention /<br/>L3 Quint guards / L4 *.ai.ts)"]
    REALIZE_V2 --> OUTPUT_V2

    style F_SCH fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_CONV fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_INF fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_GEN fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_BHV fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_BGRAM fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_EX fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_TST fill:#1f618d,stroke:#0e3a5c,color:#fff
    style F_DOC fill:#1f618d,stroke:#0e3a5c,color:#fff

    style COMP_SCH fill:#b9770e,stroke:#7d6608,color:#fff
    style COMP_EX fill:#b9770e,stroke:#7d6608,color:#fff
    style COMP_DOC fill:#b9770e,stroke:#7d6608,color:#fff
    style COMP_BUNDLE fill:#b9770e,stroke:#7d6608,color:#fff

    style SCH_OUT fill:#7d3c98,stroke:#4a235a,color:#fff
    style EX_OUT fill:#7d3c98,stroke:#4a235a,color:#fff
    style DOC_OUT fill:#7d3c98,stroke:#4a235a,color:#fff
    style BUNDLE_OUT fill:#7d3c98,stroke:#4a235a,color:#fff

    style REG fill:#6c3483,stroke:#4a235a,color:#fff
    style UPARSER fill:#6c3483,stroke:#4a235a,color:#fff
    style IRENG fill:#6c3483,stroke:#4a235a,color:#fff
    style REALIZE_V2 fill:#6c3483,stroke:#4a235a,color:#fff,stroke-width:3px
    style BGEN fill:#6c3483,stroke:#4a235a,color:#fff
    style QGUARDS fill:#6c3483,stroke:#4a235a,color:#fff
    style DGRAM fill:#6c3483,stroke:#4a235a,color:#fff

    style SPEC_V2 fill:#7d3c98,stroke:#4a235a,stroke-width:3px,color:#fff
    style OUTPUT_V2 fill:#1e8449,stroke:#0e6655,stroke-width:2px,color:#fff
```

**Reading the diagram:**

- **Blue boxes** are the nine facets every entity module ships. The same nine names show up under every entity type (`models/`, `controllers/`, `services/`, etc.).
- **Amber boxes** are the four explicit composition pipelines (each is a tracked walker script — `compose-schema.cjs`, `compose-examples.mjs`, `docs-index.mjs`, `validateBundle v2`). Each walks the tree, gathers its facet of interest from every entity module, and emits a deterministic artifact. R12 (adding a new entity type = 3 changes) and R16d (decision points belong to the user) both rely on these composers being side-effect-free and re-runnable.
- **Purple "tracked artifact" boxes** are the deterministic outputs of those composers. They're the concrete files committed to the repo (parser sees `SPECVERSE-SCHEMA.json`, `spv init` ships `self/examples/`, the docs site reads `DOCUMENTATION-INDEX.md`).
- **Dark-purple "engine" boxes** are the runtime aggregators — they don't have an explicit composer step, they just walk the tree at load time. `UnifiedSpecVerseParser` discovers convention processors per-entity-type, the inference rule loader pulls every entity's `inference/*.json`, and the realize engine resolves capability mappings to factories at runtime.
- **Solid arrows** are forward execution; **dotted arrows** are runtime discovery (registry lookups, dynamic imports, plugin loading).

**The R12 punchline:** to add a new entity type, you create one new directory under `entities/src/extensions/` with the same nine facets, and register it in bootstrap. Every composer + every engine picks it up automatically because they all walk the tree by convention. No engine code changes. No parser changes. Just three edits (the directory + an entry in `_bootstrap.js` + a one-line schema enum addition). That's the load-bearing test of whether the architecture's claim "Define Once, Implement Anywhere" actually holds.

### Layer 3: Engines (independent, discoverable processors)

Each engine is an npm package implementing the `SpecVerseEngine` interface:

```typescript
interface SpecVerseEngine {
  name: string;              // 'parser', 'inference', 'realize', etc.
  version: string;
  capabilities: string[];    // ['parse', 'validate', 'import-resolution']
  initialize(config?: any): Promise<void>;
  getInfo(): EngineInfo;
}
```

Engines are discovered at runtime by `EngineRegistry`:

```typescript
const registry = new EngineRegistry();
await registry.discover();  // finds @specverse/engine-* packages

const parser = registry.getEngineForCapability('parse');
await parser.initialize({ schema });
const result = parser.parseContent(content, filename);
```

## How the Pipeline Works

### Step 1: Parse

The parser engine takes `.specly` content and produces a `SpecVerseAST`.

```
Raw YAML
  --> YAML parser (js-yaml)
  --> JSON Schema validation (pre-processing)
  --> Convention processor (entity modules expand shorthand)
  --> JSON Schema validation (post-processing)
  --> Semantic validation (cross-entity checks)
  --> SpecVerseAST
```

Key: The convention processor discovers entity types from the **entity registry**. Each entity module provides a convention processor that knows how to expand its shorthand syntax. When you add a new entity type, the parser automatically processes it.

### Step 2: Infer

The inference engine takes models from the AST and generates full architecture.

```
Models (from AST)
  --> Rule loader (loads JSON rules from entity modules)
  --> For each registered generator:
      --> Pattern match models against rules
      --> Generate controllers/services/events/views
  --> Deployment generator (instances, channels)
  --> ComprehensiveInferenceResult
```

Key: Generators are registered in a `Map<string, generator>`. Each generator loads rules from its entity module. Adding a new generator means adding it to the map and providing rules.

### Step 3: Realize

The realize engine takes an inferred spec + manifest and generates code.

```
AI-optimized spec + Manifest
  --> Instance factory library (loads factory YAMLs)
  --> Capability resolver (maps capabilities to factories)
  --> For each capability:
      --> Resolve factory
      --> Load template generator (TypeScript file)
      --> Execute generator with context
      --> Write output file
  --> Generated code (Prisma, Fastify, React, etc.)
```

Key: Instance factories are YAML files that declare what they generate. Template generators are TypeScript files that produce code. The manifest controls which factories are used. Adding a new technology means adding a new instance factory.

### Step 3b: Constraint resolution (Phase 2 IR pipeline)

When a model declares `constraints: [{on, requires}]`, the realize pipeline runs a parallel four-stage IR pipeline before the controller-generator emits code. The author's natural-language predicate becomes a Quint `pure def` becomes a TypeScript guard function:

```
"Poll is open"  (author input, in spec)
  --> BehaviouralConventionProcessor.expand()       [entities/src/_shared/behaviour/convention-processor.ts]
        match against 9 sugar conventions; emit Quint AST node
  --> { name, params, body: 'self.poll.votingStatus == "open"', source }
  --> transpilePhase2Guard()                        [engines/src/inference/quint-transpiler.ts]
        Quint --> TS function body; auto-upgrades '==' to '===' strict equality
  --> guard: (self, actor) => self.poll.votingStatus === "open"
  --> guards-generator.ts                            [engines/libs/instance-factories/services/templates/_shared/]
        wraps the guard in MODEL_CONSTRAINTS + runGuards runtime
  --> <Model>.guards.ts file emitted, sibling to <Model>Controller.ts
```

The shared `_shared/guards-generator.ts` backs **all three ORM controllers** (prisma / mongodb-native / postgres-native) — the constraint runtime is ORM-agnostic. ORM-specific controllers add the `import` + a `runConstraintGuards(_data, _context.operation, _actor)` call inside their `validate()` method.

**The five enforcement modes** consume the same `MODEL_CONSTRAINTS` table:

| Mode | Layer | Mechanism |
|---|---|---|
| α — FK dropdown filter | Browser (`@specverse/runtime/views/react`) | `annotateFkOptions` evaluates `on:[create]` constraints against each FK option's parent entity. Adds `disabled` + reason tooltip to ineligible `<option>` elements. |
| γ — Server preflight | Server (`/api/<plural>/validate` route) | `POST /validate` invokes the controller's `validate()` (which calls `runGuards`); browser fires this before any mutation. |
| δ — Button disable | Browser | `checkLocalPermission` evaluates constraints locally; "+ Add Vote" / Edit / Delete / Evolve buttons get `disabled` + tooltip. |
| ε — Error display | Browser consumes server-side violations | `FormViolationsPanel` (form-top), inline `<FieldError />` (per-field), `<ValidationToast />` (general). |
| ζ — Pending checks | Browser | `collectPendingChecks` lists constraints whose local 3-valued evaluator returned `undefined` (subqueries, actor refs that need server context). Blue informational panel. |

The local 3-valued evaluator (`true` / `false` / `undefined`) is the **honesty contract** for modes α/δ/ζ — when the evaluator can't decide locally (because a constraint references a subquery or unknown actor), it returns `undefined` and the caller doesn't block. The server-side `runGuards` (mode γ) is the source of truth.

**Actor wiring** (Slice 14, 2026-05-18): Fastify routes extract `const _actor = (request as any).user ?? null;` from each mutation request and thread it as the last positional arg to every controller method. Guards reference `actor` via the same path syntax as `self`. Throws during guard evaluation are **fail-OPEN** — logged via `console.error([runGuards] ... — treating as PASS:)` and skipped. Rationale: a throw indicates a guard-internal defect (transpile bug, undefined-path traversal), not a real constraint violation; blocking would deny legitimate user actions for a bug we already log loudly.

**Slice 15 runtime completeness** (2026-05-19): closes the runtime gaps that made the "headline" Phase 2 sugars actually work end-to-end.

- **15a Create-time relation loading**: each ORM controller loads belongsTo rels from input FKs before validate, so guards traversing `self.poll.votingStatus` see the loaded Poll. Pre-fix: `self.poll` was undefined on Create → throw → fail-open masked the bug.
- **15b Async guards + ctx threading**: `runGuards` becomes async, threads `ctx?: GuardContext` (4th arg) that exposes a per-model `query.exists()` helper. The verb subquery sugar (`Vote.exists(...)`) is rewritten by `guards-generator.ts`'s `rewriteSubqueriesAsync` post-processor — detects bare-Capitalized `.some(...)` patterns, marks guard async, prepends ctx-bound shim consts.
- **15c App-demo interpreter enforcement**: separate JIT evaluator (`constraint-evaluator.ts`) for the dynamic interpreter — same Quint→TS transpile pipeline + same subquery shim shape, wrapped in `new Function` instead of compiled file emit. `DynamicModelStore.enforceConstraints` runs on every mutation; in-memory ctx scans store entities for subqueries.
- **15d FK + .id subquery comparison**: verb sugar emits `__v.voterId == actor.id and __v.pollId == self.pollId` instead of object-ref comparison. New `__pathToId:<capture>` derived placeholder (self.id cross-model, self.<rel>Id same-model). Matches the FK shape ORMs and in-memory stores use natively.
- **15e Hard-fail parser on unresolvable constraints**: was silent warn + drop. Now hard error rolled into `parseResult.errors` so spec loaders refuse to start with a broken spec. Message names model + constraint + reason.

Plus a 9-fix UI cascade discovered during manual testing — see implementation notes for the full list.

## How Things Connect

This is the textual reference for the [Language Extension diagram](#2-language-extension--entity-module-facets-exploded) above. Same per-facet wiring, but listed by entity module rather than by composer/engine.

```
Entity Module (e.g., "models")
  |
  |-- Implementation facets --
  |
  |--> schema/models.schema.json
  |     Used by: Parser (JSON Schema validation), schema composer
  |
  |--> conventions/model-processor.ts
  |     Used by: Parser (structural convention expansion)
  |
  |--> conventions/behavioural-grammar.yaml
  |     Used by: Behavioural convention processor (expands natural-
  |              language conventions into Quint)
  |
  |--> inference/{controller,service}-rules.json
  |     Used by: Inference engine (Handlebars-compiled rules)
  |
  |--> generators/index.ts
  |     Used by: Realize engine (factory lookup)
  |
  |-- Content facets (pluggable-bundle convention) --
  |
  |--> __examples__/*.specly (+ colocated .md + .example.yaml)
  |     Used by: examples composer, docs composer, `spv realize`
  |              (shipped into generated projects), deriveCatalog
  |
  |--> __tests__/*.test.ts
  |     Used by: Vitest; deriveCatalog
  |
  |--> __behaviour__/{invariants,rules,test,verify}.qnt
  |     Used by: `quint verify` / `quint test`; deriveCatalog
  |
  |-- Manifest + runtime surface --
  |
  |--> module.yaml
  |     Used by: Entity registry (validates the facet declarations)
  |
  |--> index.ts
        Used by: Entity registry (registers the module); catalog
        is derived from __examples__/ + __tests__/ + __behaviour__/
        at import time (no hand-maintained barrels).
```

Each facet connects to at least one pipeline. The entity module is the unit of extensibility — add a module, and every pipeline that walks the tree discovers it automatically.

## Composition Pipelines

SpecVerse's extensibility model is **"tree-in, composed-artifact-out."** Each pipeline walks the same entity/engines tree and emits one well-defined artifact that either ships to users or feeds a downstream pipeline. This is what makes `Define Once, Implement Anywhere™` concrete at the build-system level: adding a new entity bundle plugs into every pipeline for free.

The four explicit composition pipelines below are also the **amber boxes** in the [Language Extension diagram](#2-language-extension--entity-module-facets-exploded). The runtime aggregators in the table after that are the **dark-purple engine boxes**.

### Composition pipelines (explicit composers + tracked output)

| Pipeline | Source in the tree | Composer | Output | Consumer |
|---|---|---|---|---|
| **Schema** | `entities/src/**/schema/*.json` fragments + `_shared/schema/` | `entities/scripts/compose-schema.cjs` | `entities/schema/SPECVERSE-SCHEMA.json` | Parser, validator, MCP resource, VSCode extension |
| **Examples** | `entities/src/**/__examples__/` + `engines/assets/examples-*` + `_shared/examples/` | `self/scripts/compose-examples.mjs` | `specverse-self/examples/` (tracked, ~120 files, 14 categories) | Docs pipeline + `spv realize` (shipped into every generated project) |
| **Docs** | `self/examples/` (the composed output above) | `self/documentation/scripts/{generate-diagrams, generate-sidebar}.js` | `self/documentation/generated-{md,mdx}/` + `examples-sidebar.js` (tracked) | `specverse-lang-doc` Docusaurus site |
| **Realize** | `specs/main.specly` + `manifests/implementation.yaml` + the composed schema/examples | `spv realize all` | `self/generated/code/` (backend + frontend + CLI + tools) | End users who install `@specverse/self` globally |

### Runtime compositions (no explicit script; aggregated at load/call time)

| Aggregator | Source | How it composes |
|---|---|---|
| **EntityRegistry** | Per-entity `index.ts` exports across `entities/src/**` | `bootstrapEntityModules()` calls `registry.register()` on each |
| **Inference rule set** | `entities/src/**/inference/*.json` | `@specverse/engines/inference`'s `RuleEngine` loads all rule files at runtime |
| **Generator registry** | Per-entity `generators/index.ts` + `engines/libs/instance-factories/**` | `@specverse/engines/realize`'s factory registry |
| **Behaviour grammar** | Per-entity `conventions/behavioural-grammar.yaml` | `@specverse/entities/_shared/behaviour/BehaviouralConventionProcessor.loadGrammarsFromEntities()` |
| **Per-bundle catalog** | `<bundle>/__examples__/` + `__tests__/` + `__behaviour__/` | `@specverse/engines/bundles.deriveCatalog(bundleDir)` — runtime scan, cached |
| **Workspace validation report** | All 11 entity bundles | `@specverse/engines/bundles.validateBundles(bundleDirs)` |

### Test execution

Vitest is the parallel non-composing walker: its `include` globs (`entities/src/**/*.test.ts` + `engines/src/**/*.test.ts` + `runtime/src/**/*.test.{ts,tsx}` + selected factory tests) discover + execute every test file in the tree. Same "tree is the source of truth" pattern, different output (assertions + exit code instead of a composed artifact).

### Why this pattern matters

- **Adding a new entity bundle costs one subtree**, not one change per pipeline. Schema fragments, inference rules, examples, tests, behaviour — each lands in its facet dir and every pipeline picks it up on next run. This is R12 ("adding an entity type = 3 changes") generalised.
- **Drift is shallow.** When a composer's output looks wrong, the failure localises to (a) a source fragment in the tree or (b) the composer itself. There's no hand-edited combined output that can silently go stale.
- **Adding a new pipeline is additive.** Any new pipeline that walks `entities/src/**/<facet>/` benefits from every existing + future entity automatically. The validator we added at `@specverse/engines/bundles` is the latest example — walks the same tree, emits structured reports.

See [SPECVERSE-EXTENDING.md](SPECVERSE-EXTENDING.md) → "Adding a New Composition Pipeline" for the concrete pattern.

## Release & Quality Lifecycle

How SpecVerse code reaches users — twelve concentric quality gates between "saved a file" and "this code is in a user's `npm install`." The diagram below is the full lifecycle, ordered fastest-cheapest first (top → bottom). Each gate is a contract — a failure at gate N must be fixable before reaching gate N+1.

```mermaid
flowchart TD
    subgraph DEV["DEV CYCLE — milliseconds to seconds"]
        direction LR
        TS["tsc<br/>type check"]
        VITEST["vitest<br/>1,588 unit tests<br/>(engines workspace)"]
        VBUNDLE["validateBundle v2<br/>per-entity 6-facet check:<br/>catalog / schema / examples /<br/>tests / docs / behaviour"]
        QUINT["quint typecheck<br/>per-bundle .qnt files<br/>(skip if binary missing)"]
    end

    subgraph LOCAL["LOCAL SMOKE — seconds to minutes"]
        direction LR
        REALIZE["spv realize all<br/>fixture spec → generated/code"]
        SMOKE["scripts/smoke.sh<br/>install deps + prisma push +<br/>start backend + curl POST/GET<br/>+ tear down"]
        TEMPLATE["per-template smoke<br/>spv init default / full-stack /<br/>backend-only / frontend-only"]
        UICONTRACT["UI contract tests<br/>11 files / 550 cases<br/>(Playwright assertions)"]
    end

    subgraph PUBLISH_GATE["PRE-PUBLISH — minutes"]
        direction LR
        TARBALL["npm pack +<br/>verify:tarball<br/>(global install of local tarball<br/>+ spv smoke from /tmp)"]
        VERDACCIO["Verdaccio dress-rehearsal<br/>(localhost:4873)<br/>publish → install global →<br/>spv smoke from clean /tmp<br/>R36 fresh-install gate"]
        SMOKE_ALL["smoke-all<br/>(full release gate:<br/>engines tests + tarball verify<br/>+ contract UI + per-template)"]
    end

    subgraph REMOTE["REMOTE — minutes (post-publish)"]
        direction LR
        REALNPM["Real npm publish<br/>(types + entities + engines<br/>+ runtime + assets + self)"]
        VPUB["verify:published<br/>npm install -g @specverse/self@latest<br/>+ spv smoke<br/>(post-publish gate)"]
        CI["GitHub Actions CI<br/>(pinned Node 20,<br/>cold cache, fresh install<br/>R36a — catches what local doesn't)"]
        CODESPACE["GitHub Codespace<br/>(.devcontainer.json,<br/>Node 20 image,<br/>cloud machine,<br/>spv smoke from /tmp)"]
    end

    subgraph CORPUS["EVAL — minutes to hours (continuous)"]
        direction LR
        EVAL_CREATE["specverse-demo-self<br/>create corpus<br/>(8 cases — booking-system,<br/>ecommerce, invoicing, etc.)"]
        EVAL_ANALYSE["specverse-demo-self<br/>analyse corpus<br/>(10+ cases — class 1 +<br/>class 2: cal-com, outline,<br/>twenty, dub, idle-meta,<br/>realworld-nest, JobHunter)"]
        SCORING["scoring/<br/>domain-coverage,<br/>round-trip fidelity,<br/>alias-aware scoring,<br/>Jaccard step matching"]
    end

    DEV --> LOCAL
    LOCAL --> PUBLISH_GATE
    PUBLISH_GATE --> REALNPM
    REALNPM --> VPUB
    VPUB --> CI
    REALNPM -.->|"new Codespace<br/>boots from devcontainer<br/>+ pulls @latest"| CODESPACE
    REALNPM -.->|"latest version<br/>installed in harness"| EVAL_CREATE
    REALNPM -.->|"latest version<br/>installed in harness"| EVAL_ANALYSE
    EVAL_CREATE --> SCORING
    EVAL_ANALYSE --> SCORING

    SHADOW["Shadow / regression replay<br/>(re-run last green CI run +<br/>last green eval corpus on every<br/>main-branch push;<br/>flags REGRESSIONS, not just<br/>new failures)"]
    CI -.-> SHADOW
    SCORING -.-> SHADOW

    USER_OUT["User installs<br/>npm install -g @specverse/self<br/>or downloads template"]
    VPUB --> USER_OUT
    CI -.->|"green CI<br/>certifies push"| USER_OUT

    SHADOW -.->|"regression<br/>signal"| DEV

    style TS fill:#1f618d,stroke:#0e3a5c,color:#fff
    style VITEST fill:#1f618d,stroke:#0e3a5c,color:#fff
    style VBUNDLE fill:#1f618d,stroke:#0e3a5c,color:#fff
    style QUINT fill:#1f618d,stroke:#0e3a5c,color:#fff

    style REALIZE fill:#2874a6,stroke:#1a5276,color:#fff
    style SMOKE fill:#2874a6,stroke:#1a5276,color:#fff
    style TEMPLATE fill:#2874a6,stroke:#1a5276,color:#fff
    style UICONTRACT fill:#2874a6,stroke:#1a5276,color:#fff

    style TARBALL fill:#b9770e,stroke:#7d6608,color:#fff
    style VERDACCIO fill:#b9770e,stroke:#7d6608,color:#fff,stroke-width:2px
    style SMOKE_ALL fill:#b9770e,stroke:#7d6608,color:#fff

    style REALNPM fill:#7d3c98,stroke:#4a235a,color:#fff,stroke-width:3px
    style VPUB fill:#6c3483,stroke:#4a235a,color:#fff
    style CI fill:#6c3483,stroke:#4a235a,color:#fff,stroke-width:2px
    style CODESPACE fill:#6c3483,stroke:#4a235a,color:#fff

    style EVAL_CREATE fill:#5b2c6f,stroke:#4a235a,color:#fff
    style EVAL_ANALYSE fill:#5b2c6f,stroke:#4a235a,color:#fff
    style SCORING fill:#5b2c6f,stroke:#4a235a,color:#fff

    style SHADOW fill:#922b21,stroke:#641e16,color:#fff,stroke-width:2px

    style USER_OUT fill:#1e8449,stroke:#0e6655,color:#fff,stroke-width:2px
```

**Reading the test-lifecycle diagram (top to bottom = fastest to slowest; left-to-right inside each band groups related gates):**

| Gate | Trigger | What it catches | Codified by |
|---|---|---|---|
| **tsc + vitest** | every save | type errors + unit-test regressions; engines suite is 1,588 tests across 96 files | normal CI build step |
| **validateBundle v2** | every entity change | per-entity facet sanity (catalog ↔ schema ↔ examples ↔ tests ↔ docs ↔ Quint behaviour) | TODO #18 (shipped); validateBundle v2 |
| **quint typecheck** | per .qnt file | formal-spec type errors (subprocess-based, skips if binary not installed) | the same pattern is being extended to per-spec Quint by TODO #31 |
| **spv realize + smoke.sh** | local engine change | round-trip from spec → realized backend → live HTTP request → CRUD response | R36b — required before publish |
| **per-template smoke** | adding/changing a template | `spv init` works for default / full-stack / backend-only / frontend-only | smoke-all release gate |
| **UI contract tests** | view-rendering change | Playwright assertions across 11 files, 550 cases | R2 — one pattern library, three consumers |
| **verify:tarball + Verdaccio** | pre-publish | "works on a clean install" (R36); catches dependency declarations missing from package.json or imports that resolve via hoisting | R36 — published packages must work on clean installs; the Verdaccio dress-rehearsal protocol |
| **Real npm publish + verify:published** | publish | post-publish global install actually works for an end user; flags propagation lag | Standard publish ritual |
| **GitHub Actions CI** | every push | catches what local doesn't — pinned Node 20, cold npm cache, fresh install from registry | R36a |
| **GitHub Codespace** | post-publish, manual today (planned one-click via specverse-starter, TODO #5) | end-to-end install on a Cloud-hosted machine using only the published packages — closest thing to a real user's first experience. `.devcontainer.json` ships with Node 20 image + port-forwards for backend/frontend/app-demo | R36 (clean-install integrity) |
| **Eval harness corpus** | continuous | LLM-workflow quality (create + analyse) against real codebases — measured drift, alias-aware scoring, round-trip fidelity | TODO #22 — Spec Quality Benchmark |
| **Shadow / regression replay** | every main push | re-runs last green CI run + last green eval corpus to flag REGRESSIONS specifically (not just new failures) | aspirational; partly covered by CI's full re-run |

**The "shadow remote" layer at far right** is the regression-detection net. Two channels feed it:
1. **CI replay** — every main-branch push re-runs the full CI matrix; deltas vs the last known-green run are flagged as regressions (vs. new failures from new code).
2. **Eval corpus replay** — the same `analyse` and `create` corpora run against the freshly-published `@specverse/self`, with scores compared to the previous publish. A drop in domain coverage or round-trip fidelity is a regression signal even when CI is green.

Both signals route back to the **DEV CYCLE** (the dotted red arrow back to `DEV`) — a regression doesn't need to wait for a user report; it surfaces as a CI / corpus comparator failure.

**Design intent:** the lifecycle is a **funnel**. Each gate is cheaper than the next; each gate catches a class of bug the cheaper gates can't see. The investor takeaway: SpecVerse doesn't ship on hope — it ships through twelve progressively-stricter quality gates, with formal-verification (Quint) and empirical-eval (corpus) at the extremes, and Codespace fresh-cloud-install at the realism end.

## Key Design Decisions

### Double Validation
The parser validates twice: once on the raw YAML (before convention processing) and once on the expanded YAML (after convention processing). Both must pass. This means the JSON Schema must accept both the shorthand form and the expanded form for core entity types.

Extension entity types (like commands) are only included in the processed output when present in the source YAML -- they don't get empty defaults. This avoids schema type mismatches where the processed form (array) differs from the schema form (object).

### Entity Type Discovery
The convention processor derives its entity type list from the entity registry, filtered to types that have completed schema integration. The filter set is:

```typescript
const COMPONENT_ENTITY_TYPES = new Set([
  'models', 'controllers', 'services', 'views', 'events', 'commands'
]);
```

When a new entity type completes schema integration (added to root.schema.json AND ComponentSpec type), it's added to this set.

### Engine Registration

Engines are **explicitly registered** at CLI startup (per R36) — not dynamically discovered. Each consumer imports the engine singletons it needs by name and registers them with the `EngineRegistry`:

```typescript
import { EngineRegistry } from '@specverse/entities';
import { engine as parserEngine }    from '@specverse/engines/parser';
import { engine as inferenceEngine } from '@specverse/engines/inference';
import { engine as realizeEngine }   from '@specverse/engines/realize';

const registry = new EngineRegistry({ disableAutoDiscovery: true });
registry.register(parserEngine);
registry.register(inferenceEngine);
registry.register(realizeEngine);

// Later, look up by capability
const parser = registry.getEngineForCapability('parse');
await parser?.initialize();
```

Explicit registration ensures Node resolves each engine from the **consumer's** `node_modules/`, eliminating a class of phantom-dependency failures that dynamic discovery exposed in published packages. See [SPECVERSE-EXTENDING.md](SPECVERSE-EXTENDING.md) for the full pattern.

### Circular Dependency Prevention
Parser and entities had a circular dependency (parser imported entity types, entities imported parser processors). This was broken by moving shared interfaces (`ProcessorContext`, `AbstractProcessor`, `EntityModule`) to `@specverse/types`. Both packages import from types, neither imports from the other at compile time.

### Instance Factory Architecture
Instance factories are YAML files that declare:
- What capabilities they provide (`orm.schema`, `api.rest`, etc.)
- What code templates they generate
- What dependencies they require

The manifest maps capabilities to factories. The resolver finds the right factory for each capability. The code generator executes the factory's templates.

### Self-Hosting
SpecVerse is self-hosting: the self-spec (921 lines, 5 components, 9 CLI commands) generates a CLI that ships as the production release `@specverse/self`. The generated CLI discovers engines via EngineRegistry and calls them through the standard interface. The bootstrap promotion cycle (R30–R36 + R31a) keeps regeneration safe: the previous-generation CLI under `bootstrap/cli/` produces the next-generation CLI, tests run, then promote replaces the bootstrap.

The generated CLI is what users install with `npm install -g @specverse/self`. There is no longer a hand-written CLI — that was retired once the generated CLI reached parity (engines 4.x). See [SPECVERSE-SELF-HOSTING.md](SPECVERSE-SELF-HOSTING.md) for the cycle.

## Current Implementation

### Repositories

| Repo | Purpose | npm packages |
|------|---------|--------------|
| specverse-engines | Engine source of truth — workspace with 4 packages | `@specverse/types`, `@specverse/entities`, `@specverse/engines` (6 subpath exports), `@specverse/runtime` |
| specverse-self | Production CLI release + canonical content + self-specification | `@specverse/self`, `@specverse/assets` (workspace) |
| specverse-app-demo | Dynamic runtime interpreter (complement to static generation) | (not published) |
| specverse-demo-ai | Eval harness for `create` + `analyse` LLM workflows | (not published) |
| specverse-lang-registry | Community library platform | `@specverse/reg` |

(`specverse-lang` is archived legacy; superseded by specverse-self + the `@specverse/engines` subpath model.)

### What's Wired

- **Entity modules → Parser**: convention processors discovered from registry at parse time
- **Entity modules → Schema**: 15 fragments composed into SPECVERSE-SCHEMA.json at build time (62 `$defs`)
- **Entity modules → Inference**: rules loaded from entity modules via registry; 100% Handlebars rule engine since the post-2026-04-22 rewrite
- **Entity modules → Diagrams**: plugins discovered from entity module declarations
- **Entity modules → Quint**: 25 .qnt files, 21 invariants, 52 behavioural convention grammars; `validateBundle v2` runs `quint typecheck` per file
- **Entity modules → L3 Behaviors**: 15 convention patterns + AI behavior generation from declarative `steps:` blocks. Two Quint surfaces: (a) the entity `.qnt` invariants (above) are transpiled and run **at validate time** by `spv validate --verify` to check the spec is well-formed (in-process; nothing emitted to a backend); (b) model `constraints:` transpile to a per-model `<Model>.guards.ts` enforced **at runtime** in the generated backend (narrow, fail-open). The author-Quint-action transpiler (`transpileActions`) exists but is **not wired** into realize. (Formal tooling = `quint typecheck`; Apalache model-checking is not integrated.)
- **AI engine fully wired (engines 6.0+)**: four-mode provider (`claude-cli` / `anthropic` / `openai-compatible` / `stub`) via Vercel AI SDK; six active prompts (create / verify-create / analyse / verify-analyse / behavior / app-demo) sharing 10 DRY partials; `spv ai analyse` extracts spec + manifest + deployments triple from real codebases
- **Structural prepass wired**: three pluggable backends (grep-only / CodeGraph / GitNexus) extract deterministic facts before LLM call; per-framework adapter layer (typescript-prisma multi-file, typescript-decorators); 8-component analyse on idle-meta empirically validates end-to-end
- **Controller engine → app-demo**: preconditions → steps → postconditions → events; custom operations execute full behavior pipeline
- **Engine packages → CLI**: generated CLI discovers engines via EngineRegistry; 12 top-level commands engine-wired (smoke, init, validate, validate-bundle, infer, realize, gen, dev, cache, ai, skill, session)
- **Multi-component realize fan-out**: specs with multiple components generate code for ALL components (engines 6.3.0)

### Open work (not yet shipped)

- Quint 5b deterministic gate inside `spv ai analyse` (TODO #31 — design at `docs/plans/2026-04-30-DETERMINISTIC-VERIFICATION-GATES.md`)
- MethodFactSheet surfacing into verify-analyse prompt (TODO #32 — same plan doc)
- MongoDB native-driver realize template (TODO #43 — Prisma realize is currently the only fully-working backend)
- Class 2 corpus growth — NestJS+TypeORM, Django+DRF, Spring+JPA (TODO #28)
- Open-source LLM evaluation (Qwen3-Coder-Next via openai-compatible — TODO #47)

See [TODO.md](../TODO.md) for the full backlog with priorities and estimates.
