Skip to content

Patterns

Auto-generated from knowledge base TOML files by docs_gen.py. Do not edit manually; run dazzle docs generate to regenerate.

Patterns are reusable DSL recipes that combine multiple constructs into proven solutions. Each pattern includes a complete, copy-paste-ready example demonstrating how entities, surfaces, workspaces, and services work together for common use cases.


Adapter Result

Result type for adapter operations using success/failure pattern instead of exceptions for expected errors.

Syntax

AdapterResult[T] = Success with data or Failure with error

result = await adapter.get_data(id)
if result.is_success:
    data = result.data  # Access the data
else:
    error = result.error  # Handle the error

Example

async def fetch_customer_data(customer_id: str):
    result = await crm_adapter.get_customer(customer_id)

    if result.is_success:
        return result.data

    # Handle specific error types
    if result.error.status_code == 404:
        return None  # Customer not found

    # Re-raise unexpected errors
    raise result.error

# Or use unwrap_or for defaults:
data = result.unwrap_or(default_data)

# Or map the result:
names = result.map(lambda data: data["name"])

Related: External Adapter, Error Normalization


Audit Trail

Track who changed what and when

Example

# Entity with full audit fields
entity Document "Document":
  id: uuid pk
  title: str(200) required
  content: text
  # Audit fields
  created_by: ref User required
  created_at: datetime auto_add
  updated_by: ref User optional
  updated_at: datetime auto_update
  version: int=1

surface document_history "Document History":
  uses entity Document
  mode: view
  section main:
    field title
    field version
  section audit "Audit Trail":
    field created_by.name "Created By"
    field created_at "Created At"
    field updated_by.name "Last Updated By"
    field updated_at "Last Updated At"

Business Logic Pattern

Complete entity with state machine, invariants, computed fields, and access rules

Example

# Complete v0.7.0 Entity with Business Logic
entity Ticket "Support Ticket":
  id: uuid pk
  ticket_number: str(20) unique
  title: str(200) required
  description: text required
  status: enum[open,in_progress,resolved,closed]=open
  priority: enum[low,medium,high,critical]=medium
  created_by: ref User required
  assigned_to: ref User
  resolution: text
  created_at: datetime auto_add
  updated_at: datetime auto_update

  # Computed field: days since opened
  days_open: computed days_since(created_at)

  # State machine: ticket lifecycle
  transitions:
    open -> in_progress: requires assigned_to
    in_progress -> resolved: requires resolution
    in_progress -> open
    resolved -> closed
    resolved -> in_progress
    closed -> open: role(manager)

  # Invariants: data integrity rules
  # IMPORTANT: Use single = for equality (not ==)
  invariant: status != resolved or resolution != null
  invariant: status != closed or resolution != null
  invariant: priority != critical or assigned_to != null

  # Access rules: visibility and permissions
  access:
    read: created_by = current_user or role(agent) or role(manager)
    write: role(agent) or role(manager)

  # Indexes for performance
  index status, priority
  index assigned_to

Related: State Machine, Invariant, Computed Field, Access Rules


Cedar Rbac

Role-based entity access using permit/forbid/audit blocks with NIST SP 800-162 alignment. Supports separation of duty, least privilege, default-deny, and audit trail requirements.

Example

# Cedar-style RBAC for a medical clinic
entity Prescription "Prescription":
  id: uuid pk
  patient: ref Patient required
  prescriber: ref Doctor required
  medication: str(200) required
  dosage: str(100) required
  status: enum[draft,active,dispensed,cancelled]=draft

  # Permit: who CAN perform actions
  permit:
    read: role(doctor) or role(pharmacist) or role(nurse)
    prescribe: role(doctor)
    dispense: role(pharmacist)
    cancel: role(doctor) or role(pharmacist)

  # Forbid: separation of duty overrides
  forbid:
    prescribe: role(pharmacist)
    dispense: role(doctor)

  # Audit: compliance logging
  audit:
    read: role(admin)
    prescribe: role(compliance_officer)
    dispense: role(compliance_officer)
    cancel: role(compliance_officer)

  transitions:
    draft -> active: role(doctor)
    active -> dispensed: role(pharmacist)
    active -> cancelled: role(doctor) or role(pharmacist)

Related: Access Rules, Role Based Access, Business Logic Pattern, Rbac Validation


Crud

Complete create-read-update-delete interface for an entity

Example

# Complete CRUD for Task entity
entity Task "Task":
  id: uuid pk
  title: str(200) required
  description: text optional
  status: enum[todo,in_progress,done]=todo
  created_at: datetime auto_add

surface task_list "Tasks":
  uses entity Task
  mode: list
  section main:
    field title
    field status
  ux:
    purpose: "View and manage all tasks"
    sort: created_at desc
    filter: status

surface task_detail "Task Details":
  uses entity Task
  mode: view
  section main:
    field title
    field description
    field status
    field created_at

surface task_create "New Task":
  uses entity Task
  mode: create
  section main:
    field title
    field description

surface task_edit "Edit Task":
  uses entity Task
  mode: edit
  section main:
    field title
    field description
    field status

Dashboard

Workspace aggregating multiple data views with metrics

Example

# Team dashboard with metrics and activity
workspace team_dashboard "Team Dashboard":
  purpose: "Real-time team overview and key metrics"

  # Urgent items needing attention
  urgent_items:
    source: Task
    filter: priority = high and status != done
    sort: due_date asc
    limit: 5
    action: task_edit
    empty: "All caught up!"

  # Recent completions
  recent_done:
    source: Task
    filter: status = done
    sort: completed_at desc
    limit: 10
    display: timeline

  # Key performance metrics
  metrics:
    aggregate:
      total: count(Task)
      completed: count(Task where status = done)
      in_progress: count(Task where status = in_progress)
      completion_rate: round(count(Task where status = done) * 100 / count(Task), 1)

Direct One To Many

The foundational relational shape. A parent entity has many children; each child carries a ref Parent field. Reach for this first — it's the default relational expression in Dazzle and underpins almost every other pattern.

Authors sometimes flatten into a json[] list on the parent or embed entity-shaped data inline. Resist that: the relational shape composes with scope rules, surfaces, aggregates, FK validation, and lifecycle in ways JSON arrays can't.

Example

entity Order "Order":
  id: uuid pk
  customer: ref Customer required
  placed_at: datetime auto_add

entity LineItem "Line Item":
  id: uuid pk
  order: ref Order required        # 1:N parent FK — the canonical shape
  product_name: str(200) required
  quantity: int required
  unit_price: decimal(10,2) required

# Surfaces compose naturally — list LineItem rows for one Order via filter,
# aggregate via primary_aggregate: (see #1242), or scope via the FK chain.
surface order_lines "Order Lines":
  uses entity LineItem
  mode: list
  section main:
    field order.placed_at "Order Date"
    field product_name "Product"
    field quantity "Qty"

Related: Primary Aggregate N To One, Junction Many To Many, Subtype Of


Domain Service Pattern

Custom business logic with DSL declaration and stub implementation

Example

# DSL declaration in app.dsl
entity Invoice "Invoice":
  id: uuid pk
  total: decimal(10,2) required
  country: str(2) required
  vat_amount: decimal(10,2) optional

service calculate_vat "Calculate VAT for Invoice":
  kind: domain_logic
  input:
    invoice_id: uuid required
    country_code: str(2)
  output:
    vat_amount: decimal(10,2)
    breakdown: json
  guarantees:
    - "Must not mutate the invoice record"
    - "Must raise domain error if config incomplete"
  stub: python

# Generate stub file:
# $ dazzle stubs generate --service calculate_vat

# Implement in stubs/calculate_vat.py:
def calculate_vat(invoice_id: str, country_code: str | None = None) -> CalculateVatResult:
    invoice = db.get_invoice(invoice_id)
    country = country_code or invoice.country

    # VAT rates by country
    rates = {"GB": 0.20, "DE": 0.19, "FR": 0.20, "US": 0.0}
    rate = rates.get(country, 0.0)

    return {
        "vat_amount": float(invoice.total) * rate,
        "breakdown": {
            "rate": rate,
            "country": country,
            "net": float(invoice.total),
            "gross": float(invoice.total) * (1 + rate)
        }
    }

Ejection Pattern

Generate standalone application code from DAZZLE specification for custom deployment

Example

# Step 1: Add ejection configuration to dazzle.toml
[ejection]
enabled = true

[ejection.backend]
framework = "fastapi"
models = "sqlalchemy"
async_handlers = true

[ejection.frontend]
framework = "react"
api_client = "tanstack_query"

[ejection.testing]
contract = "schemathesis"
unit = "pytest"

[ejection.ci]
template = "github_actions"

[ejection.output]
directory = "generated"


# Step 2: Run the generated application
cd generated
docker compose -f docker-compose.dev.yml up

# Step 3: Run the generated tests
cd generated
pytest tests/

# Optional: Generate OpenAPI spec
dazzle specs openapi -o openapi.yaml

# Generated file structure:
generated/
├── README.md
├── docker-compose.yml
├── docker-compose.dev.yml
├── Makefile
├── .gitignore
├── backend/
   ├── main.py
   ├── models/
      └── {entity}.py
   ├── schemas/
      └── {entity}.py
   ├── routes/
      └── {entity}.py
   ├── guards/
      └── {entity}_guards.py
   ├── validators/
      └── {entity}_validators.py
   └── access/
       └── {entity}_access.py
├── frontend/
   └── src/
       ├── types/
       ├── schemas/
       ├── hooks/
       └── components/
├── tests/
   ├── conftest.py
   ├── contract/
   └── unit/
└── .github/
    └── workflows/
        ├── ci.yml
        ├── contract.yml
        └── deploy.yml

Related: Ejection, Ejection Config, Ejection Adapter, Openapi Generation


Error Category

High-level error categories for routing and handling decisions in the GraphQL layer.

Syntax

ErrorCategory.AUTHENTICATION  # Redirect to login
ErrorCategory.AUTHORIZATION   # Show forbidden message
ErrorCategory.VALIDATION      # Show field-level errors
ErrorCategory.RATE_LIMIT      # Implement backoff
ErrorCategory.TIMEOUT         # Retry or show timeout
ErrorCategory.NOT_FOUND       # Show 404 message
ErrorCategory.EXTERNAL_SERVICE # Show service unavailable
ErrorCategory.INTERNAL        # Log and show generic error

Example

# Use categories to route error handling:
def handle_adapter_error(normalized: NormalizedError):
    match normalized.category:
        case ErrorCategory.AUTHENTICATION:
            return redirect_to_login()
        case ErrorCategory.RATE_LIMIT:
            return show_retry_message(normalized.retry_after)
        case ErrorCategory.VALIDATION:
            return show_field_errors(normalized.field_errors)
        case _:
            return show_generic_error(normalized.user_message)

Related: Error Normalization, Error Severity


Error Normalization

System for converting diverse external API errors into a consistent format for GraphQL responses.

Syntax

from dazzle_dnr_back.graphql.adapters import (
    normalize_error,
    NormalizedError,
    ErrorCategory,
    ErrorSeverity,
)

normalized = normalize_error(error, service_name="hmrc")

Example

from dazzle_dnr_back.graphql.adapters import (
    normalize_error,
    ErrorCategory,
    ErrorSeverity,
)

try:
    result = await hmrc_adapter.get_vat_obligations(vrn)
except AdapterError as e:
    normalized = normalize_error(e, request_id="req-123")

    # Access normalized error properties
    print(normalized.code)           # "HMRC_RATE_LIMIT_EXCEEDED"
    print(normalized.category)       # ErrorCategory.RATE_LIMIT
    print(normalized.severity)       # ErrorSeverity.WARNING
    print(normalized.user_message)   # "Too many requests. Please try again in 30 seconds."
    print(normalized.retry_after)    # 30.0

    # Convert to GraphQL error extensions
    extensions = normalized.to_graphql_extensions()
    raise GraphQLError(normalized.user_message, extensions=extensions)

Related: External Adapter, Adapter Result, Error Category


Error Severity

Error severity levels for logging and alerting decisions.

Syntax

ErrorSeverity.INFO      # Expected errors (validation, not found)
ErrorSeverity.WARNING   # Recoverable errors (rate limits, timeouts)
ErrorSeverity.ERROR     # Unexpected errors needing attention
ErrorSeverity.CRITICAL  # System errors requiring immediate action

Example

# Log based on severity:
def log_error(normalized: NormalizedError):
    log_data = normalized.to_log_dict()

    match normalized.severity:
        case ErrorSeverity.INFO:
            logger.info("Expected error", extra=log_data)
        case ErrorSeverity.WARNING:
            logger.warning("Recoverable error", extra=log_data)
        case ErrorSeverity.ERROR:
            logger.error("Unexpected error", extra=log_data)
        case ErrorSeverity.CRITICAL:
            logger.critical("System error", extra=log_data)
            alert_on_call_team(normalized)

Related: Error Normalization, Error Category


External Adapter

Abstract base class for integrating with external APIs (HMRC, banks, payment providers, etc.) with built-in retry logic, rate limiting, and error normalization.

Syntax

from dazzle_dnr_back.graphql.adapters import (
    BaseExternalAdapter,
    AdapterConfig,
    AdapterResult,
)

class MyServiceAdapter(BaseExternalAdapter[AdapterConfig]):
    async def get_data(self, id: str) -> AdapterResult[dict]:
        return await self._get(f"/api/data/{id}")

Example

from dazzle_dnr_back.graphql.adapters import (
    BaseExternalAdapter,
    AdapterConfig,
    RetryConfig,
    RateLimitConfig,
    AdapterResult,
)

class HMRCAdapter(BaseExternalAdapter[AdapterConfig]):
    """Adapter for HMRC VAT API."""

    def __init__(self, bearer_token: str):
        config = AdapterConfig(
            base_url="https://api.service.hmrc.gov.uk",
            timeout=30.0,
            headers={"Authorization": f"Bearer {bearer_token}"},
            retry=RetryConfig(max_retries=3, base_delay=1.0),
            rate_limit=RateLimitConfig(requests_per_second=4),
        )
        super().__init__(config)

    async def get_vat_obligations(
        self, vrn: str, from_date: str, to_date: str
    ) -> AdapterResult[list[dict]]:
        """Fetch VAT obligations for a business."""
        return await self._get(
            f"/organisations/vat/{vrn}/obligations",
            params={"from": from_date, "to": to_date, "status": "O"},
        )

Related: Error Normalization, Adapter Result, Graphql Bff Pattern


Graphql Bff Pattern

Backend-for-Frontend using GraphQL as the API layer between UI and backend services

Example

# GraphQL schema is auto-generated from your entities:
# entity Task → GraphQL type Task with Query/Mutation

# Inspect the generated schema:
# $ dazzle dnr inspect --schema

# Mount GraphQL endpoint in your app:
from dazzle_dnr_back.graphql import mount_graphql
mount_graphql(app, backend_spec)

# Query example:
query {
  tasks(status: "todo") {
    id
    title
    status
  }
}

# Mutation example:
mutation {
  createTask(input: {title: "New task"}) {
    id
    title
  }
}

Graphql Schema Inspection

CLI command to inspect the auto-generated GraphQL schema from entity specs.

Syntax

# Display GraphQL SDL
dazzle dnr inspect --schema

# Get schema info as JSON
dazzle dnr inspect --schema --format json

Example

$ dazzle dnr inspect --schema
📊 GraphQL Schema

type Query {
  task(id: ID!): Task
  tasks(status: String, limit: Int): [Task!]!
}

type Mutation {
  createTask(input: TaskInput!): Task!
  updateTask(id: ID!, input: TaskInput!): Task!
  deleteTask(id: ID!): Boolean!
}

type Task {
  id: ID!
  title: String!
  status: String!
  createdAt: DateTime!
}

input TaskInput {
  title: String!
  status: String
}

Related: Graphql Bff Pattern, External Adapter


Junction Many To Many

Many-to-many relationships are modelled with an explicit junction entity that carries the FKs to both sides plus any relationship-specific data (timestamps, role flags, revocation). The via: keyword expresses aggregates / scope rules that walk through the junction without manual JOIN composition.

The junction is a first-class entity — it can have its own surfaces, scope rules, RBAC, and audit trail. That matters because the relationship itself is often the thing the user cares about (e.g. "when was Alice assigned to Project X?").

Example

entity User "User":
  id: uuid pk
  email: email required unique

entity Role "Role":
  id: uuid pk
  name: str(80) required unique

entity UserRole "User Role":
  id: uuid pk
  user: ref User required
  role: ref Role required
  granted_at: datetime auto_add
  revoked_at: datetime optional   # revocation = tombstone, not deletion

# `via:` aggregate — count active roles per user
workspace admin_dash "Admin Dashboard":
  user_role_counts:
    source: User
    display: cohort_strip
    cohort_strip_config:
      member_via: id
      lenses:
        - id: active_roles
          label: "Active Roles"
          primary_aggregate:
            aggregate:
              entity: Role
              func: count
              via: UserRole
            where: revoked_at = null

# `via:` scope rule — only Users you've co-rated can see your details
scope:
  read: via UserRole(user = current_user, role.code = co_rater)
    as: rater

Related: Direct One To Many, Primary Aggregate N To One, Shared Parent Join, Soft Delete


Kanban Board

Status-based workflow visualization

Example

# Kanban-style task board
entity Task "Task":
  id: uuid pk
  title: str(200) required
  status: enum[backlog,todo,in_progress,review,done]=backlog
  priority: enum[low,medium,high]=medium
  assigned_to: ref User optional

workspace kanban "Task Board":
  purpose: "Visual workflow management"

  backlog:
    source: Task
    filter: status = backlog
    sort: priority desc
    display: grid
    action: task_edit

  in_progress:
    source: Task
    filter: status = in_progress
    sort: priority desc
    display: grid

  review:
    source: Task
    filter: status = review
    sort: priority desc
    display: grid

  done:
    source: Task
    filter: status = done
    sort: completed_at desc
    limit: 20
    display: grid

Llm Cognition Pattern

Entity design optimized for LLM understanding with intent, semantic tags, archetypes, and example data

Example

# v0.7.1 LLM-Optimized Entity Design

# First, define reusable archetypes
archetype Timestamped:
  created_at: datetime auto_add
  updated_at: datetime auto_update

archetype Auditable:
  created_by: ref User
  updated_by: ref User

# Main entity with full LLM cognition features
entity Invoice "Invoice":
  intent: "Represent a finalized billing request from vendor to customer"
  domain: billing
  patterns: lifecycle, line_items, audit
  extends: Timestamped, Auditable

  id: uuid pk
  invoice_number: str(50) unique required
  status: enum[draft,sent,paid,overdue,cancelled] = draft
  customer: ref Customer required
  items: has_many InvoiceItem cascade
  subtotal: decimal(10,2) required
  tax_amount: decimal(10,2)
  total: decimal(10,2) required
  due_date: date required
  paid_at: datetime

  # Computed fields
  days_until_due: computed days_since(due_date)
  is_overdue: computed due_date < today and status != paid

  # State machine
  transitions:
    draft -> sent: requires customer
    sent -> paid: requires paid_at
    sent -> overdue: auto after 30 days
    sent -> cancelled: role(admin)
    overdue -> paid: requires paid_at
    * -> cancelled: role(admin)

  # Invariants with messages
  invariant: total = subtotal + tax_amount
    message: "Total must equal subtotal plus tax"
    code: INVOICE_TOTAL_MISMATCH

  invariant: status != paid or paid_at != null
    message: "Paid invoices must have a payment date"
    code: INVOICE_MISSING_PAYMENT_DATE

  # Access rules
  access:
    read: role(accountant) or role(admin) or customer = current_user.company
    write: role(accountant) or role(admin)

  # Example data
  examples:
    {invoice_number: "INV-2024-001", status: draft, subtotal: 1000.00, tax_amount: 200.00, total: 1200.00}
    {invoice_number: "INV-2024-002", status: sent, subtotal: 500.00, tax_amount: 100.00, total: 600.00}
    {invoice_number: "INV-2024-003", status: paid, subtotal: 750.00, tax_amount: 150.00, total: 900.00}

entity InvoiceItem "Invoice Line Item":
  intent: "Single line item on an invoice with quantity and pricing"
  domain: billing
  patterns: line_items
  extends: Timestamped

  id: uuid pk
  invoice: belongs_to Invoice
  description: str(500) required
  quantity: int required
  unit_price: decimal(10,2) required
  line_total: computed quantity * unit_price

  invariant: quantity > 0
    message: "Quantity must be positive"
    code: ITEM_INVALID_QUANTITY

  examples:
    {description: "Consulting hours", quantity: 10, unit_price: 150.00}
    {description: "Software license", quantity: 5, unit_price: 99.00}

Related: Intent, Archetype, Domain Patterns, Examples, Invariant Message, Relationships


Master Detail

Parent-child relationship with nested views

Example

# Project with nested tasks
entity Project "Project":
  id: uuid pk
  name: str(200) required
  status: enum[active,completed,archived]=active

entity Task "Task":
  id: uuid pk
  title: str(200) required
  project: ref Project required
  status: enum[todo,done]=todo

surface project_list "Projects":
  uses entity Project
  mode: list
  section main:
    field name
    field status

surface project_detail "Project":
  uses entity Project
  mode: view
  section info:
    field name
    field status
  section tasks "Tasks":
    uses entity Task
    filter: project = this
    field title
    field status

Notifications

Alert users to important events

Example

# Surface with attention signals
surface order_list "Orders":
  uses entity Order
  mode: list

  section main:
    field order_number
    field customer.name
    field status
    field total

  ux:
    purpose: "Process and fulfill orders"

    # Payment failed - critical
    attention critical:
      when: payment_status = failed
      message: "Payment failed!"
      action: order_retry_payment

    # Shipping delayed
    attention warning:
      when: days_since(shipped_at) > 5 and status = shipped
      message: "Delayed shipment"

    # New order
    attention notice:
      when: status = new and created_at > today
      message: "New order today"

Primary Aggregate N To One

Show a per-parent summary statistic computed over the parent's child rows — e.g. "total revenue per Customer", "open Issue count per Repository", "average response time per Ticket". Use primary_aggregate: on a cohort_strip / bar_chart / metrics region: one scope-aware GROUP BY query replaces N+1 enumeration.

Pairs with the direct 1:N shape (#1241). The aggregated entity carries a ref Parent field; the region declares the aggregate against that FK.

Example

entity Customer "Customer":
  id: uuid pk
  name: str(200) required

entity Order "Order":
  id: uuid pk
  customer: ref Customer required
  total: decimal(12,2) required
  placed_at: datetime auto_add

workspace customer_overview "Customer Overview":
  customer_revenue:
    source: Customer
    display: cohort_strip
    cohort_strip_config:
      member_via: id
      lenses:
        - id: total_revenue
          label: "Total Revenue"
          primary_aggregate:
            aggregate:
              entity: Order
              func: sum
              column: total

Related: Direct One To Many, Junction Many To Many, Shared Parent Join


Role Based Access

Persona variants controlling scope and capabilities

Example

# Role-based access with personas
surface ticket_list "Support Tickets":
  uses entity Ticket
  mode: list

  section main:
    field subject
    field status
    field assigned_to.name

  ux:
    purpose: "Manage support tickets by role"

    # Admins see everything, can reassign
    for admin:
      scope: all
      action_primary: ticket_assign
      show_aggregate: total, open, resolved_today

    # Agents see assigned + unassigned
    for agent:
      scope: assigned_to = current_user or assigned_to = null
      action_primary: ticket_respond

    # Customers see only their tickets
    for customer:
      scope: created_by = current_user
      hide: internal_notes, assigned_to
      read_only: true

Search Filter

Full-text search with faceted filtering

Example

# Searchable product catalog
entity Product "Product":
  id: uuid pk
  name: str(200) required
  description: text
  category: enum[electronics,clothing,home,other]
  price: decimal(10,2)
  in_stock: bool=true

surface product_catalog "Products":
  uses entity Product
  mode: list

  section main:
    field name
    field category
    field price
    field in_stock

  ux:
    purpose: "Browse and find products"
    search: name, description
    filter: category, in_stock
    sort: name asc
    empty: "No products match your search"

Self Referencing Hierarchy

Tree-shaped data where each row points at a parent of the same entity: Category → parent Category, Department → parent Department, manager → report chains. The descendants_of / ancestors_of field types resolve the recursive walk in one CTE rather than per-level Python loops.

Two flavours: direct self-FK (parent: ref self) and chains-through- junction (descendants_of self via ManagerLink.manager). The declaration form field all_reports: descendants_of self via ManagerLink.manager exposes the resolved descendant set as a virtual field usable in surfaces, scope rules, and aggregates.

Example

entity Department "Department":
  id: uuid pk
  name: str(120) required
  parent: ref self optional    # nullable root → forest of trees

  # #1227 Phase 3(b): resolved virtual field via recursive CTE.
  all_descendants: descendants_of self via parent
  all_ancestors: ancestors_of self via parent

# Scope rule using the resolved set: a manager sees themselves + descendants.
surface visible_departments "My Departments":
  uses entity Department
  mode: list
  section main:
    field name "Department"

scope:
  read: id in current_user.department.all_descendants
    as: department_head

Related: Direct One To Many, Temporal, Subtype Of


Settings Archetype

System-wide configuration using the settings semantic archetype for admin-only singleton entities

Example

# v0.10.3: System-wide settings entity
entity AppSettings "Application Settings":
    archetype: settings
    id: uuid pk
    timezone: timezone = "UTC"          # IANA timezone (e.g., "Europe/London")
    default_currency: str(3) = "GBP"
    maintenance_mode: bool = false
    max_upload_size_mb: int = 10
    support_email: email

# What happens automatically:
# 1. Entity is marked as singleton (is_singleton = true)
# 2. Admin-only access rules are applied
# 3. Settings surface is auto-generated (if not explicitly defined):
#    - Name: app_settings_settings
#    - Mode: edit
#    - Access: admin only
#    - Route: /admin/settings/app_settings
#
# The singleton ensures only one record exists in the database.
# First access will create the record; subsequent accesses update it.

Related: Tenant Archetype, Tenant Settings Archetype, Timezone Field


Shared Parent Join

The cohort source rows and the aggregated rows both reference a common pivot entity, but there is no direct FK between them. share: bridges the diamond with a single GROUP BY query keyed on the source row's primary key — the pivot itself doesn't appear in the FROM clause.

Surfaced concretely by AegisMark in #1213-#1216. Before share: shipped, the only expression was a Python override route — agents went through 2-3 design rounds before landing on the canonical shape, which is why discoverability (Phase 2) matters as much as the feature itself.

Example

# AegisMark's canonical surface: per-enrolment average marking score,
# where MarkingResult is keyed on StudentProfile, not ClassEnrolment.
entity StudentProfile "Student":
  id: uuid pk
  display_name: str(120) required

entity ClassEnrolment "Class Enrolment":
  id: uuid pk
  student_profile: ref StudentProfile required
  teaching_group: ref TeachingGroup required

entity MarkingResult "Marking Result":
  id: uuid pk
  student_profile: ref StudentProfile required   # shares the pivot
  score: decimal(4,2) required

workspace class_view "Class View":
  cohort_attainment:
    source: ClassEnrolment
    display: cohort_strip
    cohort_strip_config:
      member_via: id
      lenses:
        - id: attainment
          label: "Avg Score"
          primary_aggregate:
            aggregate:
              entity: MarkingResult
              func: avg
              column: score
              share: StudentProfile     # the diamond pivot

Related: Primary Aggregate N To One, Junction Many To Many, Subtype Of


Soft Delete

Mark rows as deleted without physically removing them (tombstone pattern). Set soft_delete: true on the entity; the framework auto-injects a nullable deleted_at: datetime tombstone field, auto-filters deleted_at IS NULL on list / read / aggregate, and converts DELETE requests into an UPDATE that stamps deleted_at = NOW().

Composes with scope rules and aggregates through the same QueryBuilder chain. include_deleted=true opt-out is available for admin / audit views.

Example

entity User "User":
  id: uuid pk
  email: email required unique
  display_name: str(120) required
  soft_delete: true
  # Framework auto-injects: `deleted_at: datetime optional` if missing.

# List / read / aggregate paths auto-filter deleted_at IS NULL — authors
# don't write the predicate. DELETE handler stamps deleted_at = NOW()
# instead of physically removing.

# Opt-out is per-call (e.g. admin audit view):
#   repo.list(include_deleted=True)  # returns ALL rows including tombstoned

Related: Temporal, Direct One To Many, Subtype Of


Subtype Of

Declare an IS-A relationship to a base entity. The child shares the base's primary key via a shared-PK FK; the framework auto-adds a kind enum on the base table as the discriminator and writes rows atomically across both tables on create / update. Polymorphic detail surfaces dispatch via subtype_panel:.

Escape hatch, not the default. See inference_kb: subtype_of_only_for_true_isa for the four cheaper alternatives to try first (separate entities, state machine, nullable fields on one entity, has_many / via:). Reach for subtype_of: only when all of: true IS-A relationship, subtype-specific fields need NOT NULL at the schema level, and you need polymorphic queries ("show me all assets, mixed kinds").

Example

module assets
app asset_registry "Asset Registry"

# Base — shared fields + the synthesised `kind` discriminator enum.
entity Asset "Asset":
  id: uuid pk
  acquired_at: date required
  acquired_value: decimal(12,2) required
  location: str(120)

# Each subtype: declare subtype_of: <Base>. Do NOT redeclare `id` (linker rule
# E_SUBTYPE_DUPLICATE_PK). Do NOT shadow base field names (#1236
# E_SUBTYPE_FIELD_NAME_OVERLAP). Multi-level (A subtype_of B subtype_of C) is
# rejected at linker time.
entity Vehicle "Vehicle":
  subtype_of: Asset
  wheels: int required
  vin: str(17) required unique

entity Building "Building":
  subtype_of: Asset
  floors: int required
  postcode: str(10) required

# Polymorphic detail view dispatches inline by row.kind. The `when` branches
# name snake_case discriminator values (lowercased child entity names).
surface asset_card "Asset Card":
  uses entity Asset
  mode: view
  section main:
    field acquired_at "Acquired"
    field location "Location"
    subtype_panel:
      when kind = vehicle: include surface vehicle_detail
      when kind = building: include surface building_detail

# A subtype detail surface receives the JOIN'd base columns automatically
# (Repository.read injects them when subtype_join_sql is active).
surface vehicle_detail "Vehicle":
  uses entity Vehicle
  mode: view
  section main:
    field wheels "Wheels"
    field vin "VIN"

Related: Subtype Of Only For True Isa, State Machine Pattern, Audit Trail


Temporal

Each row is an open or closed interval (start_date, end_date) and most queries want the row that is currently active. The temporal: block auto-injects the tombstone-style filter on read paths, threads an ?as_of=YYYY-MM-DD URL parameter for historical lookups, enforces "at most one active row per key" via a partial unique index, and exposes a synthesised active computed field for use in scope predicates.

Real surfaces: Employment, Salary, ManagerLink (all in examples/hr_records/), lease terms, price lists, GDPR consent records, feature flags with effective dates, exchange-rate snapshots.

Example

# From examples/hr_records — the canonical surface for `temporal:`.
entity Person "Person":
  id: uuid pk
  display_name: str(120) required

entity Employment "Employment":
  id: uuid pk
  person: ref Person required
  role: ref Role required
  start_date: date required
  end_date: date              # implicit when temporal: is set

  temporal:
    start_field: start_date
    end_field: end_date
    key_field: person          # constrains 'at most one active row per person'
    default_filter: active     # list/read/aggregate auto-filter end IS NULL
    as_of_param: as_of         # workspaces accept ?as_of=YYYY-MM-DD

# Workspaces composed against the temporal entity automatically get the
# 'currently active' filter and the ?as_of= URL parameter. Manager scope
# rules can traverse Person → current Employment without hand-rolling
# `end_date = null` filters.

Related: Soft Delete, Self Referencing Hierarchy, Junction Many To Many


Tenant Archetype

Multi-tenant root entity that defines tenant boundaries and automatically injects tenant FK into other entities

Example

# v0.10.3: Multi-tenant application
entity Company "Company":
    archetype: tenant
    id: uuid pk
    name: str(200) required
    slug: str(50) required unique
    plan: enum[free,pro,enterprise] = free
    created_at: datetime auto_add

# Other entities automatically get tenant FK injected
entity Contact "Contact":
    id: uuid pk
    name: str(200) required
    email: email required
    # company: ref Company  <-- auto-injected by archetype expander

entity Project "Project":
    id: uuid pk
    name: str(200) required
    status: enum[active,completed,archived] = active
    # company: ref Company  <-- auto-injected

# What happens automatically:
# 1. Company is marked as tenant root (is_tenant_root = true)
# 2. All other entities (except archetype: settings) get company FK
# 3. Tenant admin surface is auto-generated:
#    - Name: company_admin
#    - Mode: list
#    - Access: admin only
#    - Route: /admin/tenants

Related: Settings Archetype, Tenant Settings Archetype


Tenant Settings Archetype

Per-tenant configuration using tenant_settings archetype for tenant-scoped singleton entities

Example

# v0.10.3: Complete multi-tenant app with settings
entity Company "Company":
    archetype: tenant
    id: uuid pk
    name: str(200) required
    slug: str(50) required unique

entity CompanySettings "Company Settings":
    archetype: tenant_settings
    id: uuid pk
    company: ref Company              # Explicit tenant ref
    timezone: timezone                # Override system default
    logo_url: url
    invoice_prefix: str(10) = "INV"
    default_payment_terms: int = 30   # days

# System-wide settings (not tenant-scoped)
entity SystemSettings "System Settings":
    archetype: settings
    id: uuid pk
    timezone: timezone = "UTC"
    maintenance_mode: bool = false

# What happens automatically:
# 1. CompanySettings is marked as singleton (per-tenant)
# 2. Tenant admin access rules are applied
# 3. Settings surface is auto-generated:
#    - Name: company_settings_settings
#    - Mode: edit
#    - Access: tenant admin only
#    - Route: /settings/company_settings
#
# Note: SystemSettings is NOT tenant-scoped because it has
# archetype: settings (not tenant_settings)

Related: Settings Archetype, Tenant Archetype, Timezone Field


User Archetype

Core user entity with built-in authentication fields for local auth and OAuth/SSO support

Example

# v0.10.4: User entity with automatic auth fields
entity User "User":
    archetype: user
    id: uuid pk
    email: email required unique
    name: str(200) required
    avatar_url: url optional

# What gets auto-injected:
# - password_hash: str(255) optional      # For local auth
# - email_verified: bool = false
# - email_verify_token: str(100) optional
# - password_reset_token: str(100) optional
# - password_reset_expires: datetime optional
# - is_active: bool = true
# - last_login: datetime optional
# - auth_provider: enum[local,google,apple,github] = local
# - auth_provider_id: str(255) optional    # External provider ID
# - created_at: datetime auto_add

# Auto-generated surfaces (admin-only):
# - user_list: List all users
# - user_detail: View user details
# - user_create: Create new user
# - user_edit: Edit user

# Important: User entity is system-wide (not tenant-scoped)
# Use UserMembership for tenant-user relationships

Related: User Membership Archetype, Tenant Archetype


User Membership Archetype

User-tenant relationship with per-tenant personas (roles) for multi-tenant applications

Example

# v0.10.4: Complete multi-tenant user management
entity Company "Company":
    archetype: tenant
    id: uuid pk
    name: str(200) required
    slug: str(50) required unique

entity User "User":
    archetype: user
    id: uuid pk
    email: email required unique
    name: str(200) required

entity UserMembership "User Membership":
    archetype: user_membership
    id: uuid pk
    # Optionally define explicit refs (otherwise auto-injected):
    # user: ref User required
    # company: ref Company required

# What gets auto-injected:
# - user: ref User required              # Link to user
# - company: ref Company required        # Link to tenant
# - personas: json = []                  # ["admin", "member", "billing"]
# - is_primary: bool = false             # Primary membership flag
# - invited_by: ref User optional        # Who sent the invite
# - invited_at: datetime auto_add        # When invited
# - accepted_at: datetime optional       # When accepted

# Auto-generated surfaces (admin-only):
# - user_membership_list: List all memberships
# - user_membership_edit: Edit membership (assign personas)

# Usage: Assign personas to control per-tenant access
# personas: ["admin"]           -> Full tenant admin
# personas: ["member"]          -> Standard member
# personas: ["billing", "member"] -> Member with billing access

Related: User Archetype, Tenant Archetype