Constraint Engine Design

Cross-domain engineering constraint validation — separate from OPA business policies

Table of Contents

  1. Overview
  2. Architecture
  3. Constraint Definition Format
    1. Electrical Constraints
    2. Mechanical Constraints
    3. Firmware Constraints
    4. Cross-Domain Constraints
  4. Evaluation API
    1. ConstraintEngine
    2. ConstraintViolation
    3. Integration Points
  5. Related Documents

Overview

The Constraint Engine validates engineering and physics constraints against the graph. It is deliberately separate from OPA, which handles business/process policies (gate governance, approval rules, segregation of duties).

Concern Engine Examples
Engineering constraints Constraint Engine Power budget, pin count, thermal limits, cross-domain consistency
Business policies OPA Gate progression guards, approval requirements, role permissions

Architecture

digital_twin/thread/constraint_engine/
├── engine.py            # Constraint evaluation orchestrator
├── registry.py          # Loads and indexes constraint definitions
├── evaluators/
│   ├── arithmetic.py    # SUM, MAX, MIN, comparison operators
│   ├── coverage.py      # Percentage coverage checks
│   └── existence.py     # "Must have" / "Must not have" checks
└── rules/
    ├── electrical.yaml  # Power budget, pin count, EMC
    ├── mechanical.yaml  # Thermal, tolerance, clearance
    ├── firmware.yaml    # Flash size, RAM, timing
    └── cross_domain.yaml # Pin assignment consistency, connector-PCB alignment

Constraint Definition Format

Constraints are defined as YAML files containing Cypher queries that run against the Neo4j graph. Each constraint specifies what constitutes a violation and how to remediate it.

Electrical Constraints

# rules/electrical.yaml
constraints:
  - id: "elec-power-budget"
    name: "Power budget must balance"
    domain: "electrical"
    severity: "error"
    description: "Total component power draw must not exceed supply capacity"
    query: |
      MATCH (supply:DesignElement {type: 'power_supply'})-[:POWERS]->(rail:DesignElement {type: 'power_rail'})
      MATCH (rail)-[:POWERS]->(consumer:BOMItem)
      WITH rail, supply.max_current_a AS supply_current, SUM(consumer.current_draw_a) AS total_draw
      WHERE total_draw > supply_current
      RETURN rail.name AS rail, supply_current, total_draw
    violation_message: "Rail {rail} draws {total_draw}A but supply provides {supply_current}A"
    remediation: "Reduce load on rail or increase supply capacity"

  - id: "elec-pin-count"
    name: "MCU has sufficient I/O"
    domain: "electrical"
    severity: "error"
    description: "MCU must have enough GPIO pins for all connected peripherals"
    query: |
      MATCH (mcu:BOMItem {category: 'MCU'})
      MATCH (mcu)-[:CONNECTS_TO]->(peripheral:BOMItem)
      WITH mcu, mcu.gpio_count AS available, COUNT(peripheral) AS required
      WHERE required > available
      RETURN mcu.mpn AS mcu, available, required
    violation_message: "MCU {mcu} has {available} GPIOs but {required} are required"
    remediation: "Select MCU with more I/O or reduce peripheral count"

Mechanical Constraints

# rules/mechanical.yaml
constraints:
  - id: "mech-thermal-budget"
    name: "Thermal budget within limits"
    domain: "mechanical"
    severity: "error"
    description: "Total power dissipation must not exceed thermal capacity"
    query: |
      MATCH (enc:DesignElement {type: 'enclosure'})
      MATCH (comp:BOMItem)-[:MOUNTED_IN]->(enc)
      WITH enc, enc.thermal_capacity_w AS capacity, SUM(comp.power_dissipation_w) AS total_heat
      WHERE total_heat > capacity
      RETURN enc.name AS enclosure, capacity, total_heat
    violation_message: "Enclosure {enclosure} dissipates {total_heat}W but capacity is {capacity}W"
    remediation: "Add heatsinking, ventilation, or reduce power dissipation"

  - id: "mech-clearance"
    name: "Component clearance check"
    domain: "mechanical"
    severity: "warning"
    description: "Components must maintain minimum clearance for assembly"
    query: |
      MATCH (a:BOMItem)-[:ADJACENT_TO]->(b:BOMItem)
      WHERE a.clearance_mm < 1.0
      RETURN a.mpn AS partA, b.mpn AS partB, a.clearance_mm AS clearance
    violation_message: "Clearance between {partA} and {partB} is {clearance}mm (minimum 1.0mm)"
    remediation: "Increase component spacing in PCB layout"

Firmware Constraints

# rules/firmware.yaml
constraints:
  - id: "fw-flash-budget"
    name: "Firmware fits in flash"
    domain: "firmware"
    severity: "error"
    description: "Total firmware binary size must fit within MCU flash"
    query: |
      MATCH (mcu:BOMItem {category: 'MCU'})
      MATCH (fw:DesignElement {domain: 'firmware', type: 'binary'})
      WHERE fw.size_bytes > mcu.flash_bytes
      RETURN mcu.mpn AS mcu, mcu.flash_bytes AS flash, fw.size_bytes AS binary_size
    violation_message: "Firmware ({binary_size} bytes) exceeds MCU flash ({flash} bytes)"
    remediation: "Optimize firmware size or select MCU with more flash"

  - id: "fw-ram-budget"
    name: "RAM allocation within limits"
    domain: "firmware"
    severity: "error"
    description: "Total RAM usage must not exceed MCU SRAM"
    query: |
      MATCH (mcu:BOMItem {category: 'MCU'})
      MATCH (alloc:DesignElement {domain: 'firmware', type: 'ram_allocation'})
      WITH mcu, SUM(alloc.size_bytes) AS total_ram
      WHERE total_ram > mcu.sram_bytes
      RETURN mcu.mpn AS mcu, mcu.sram_bytes AS available, total_ram AS required
    violation_message: "RAM usage ({required} bytes) exceeds MCU SRAM ({available} bytes)"
    remediation: "Reduce buffer sizes, optimize data structures, or select MCU with more RAM"

Cross-Domain Constraints

# rules/cross_domain.yaml
constraints:
  - id: "xd-pin-firmware-match"
    name: "Pin assignments match firmware HAL"
    domain: "cross-domain"
    severity: "error"
    description: "Hardware pin assignments must match firmware HAL configuration"
    query: |
      MATCH (pin:DesignElement {domain: 'electrical', type: 'pin_assignment'})
      MATCH (hal:DesignElement {domain: 'firmware', type: 'hal_config'})
      WHERE pin.signal = hal.signal AND pin.gpio != hal.gpio
      RETURN pin.signal AS signal, pin.gpio AS schematic_pin, hal.gpio AS firmware_pin
    violation_message: "Signal {signal}: schematic pin {schematic_pin} != firmware pin {firmware_pin}"
    remediation: "Align pin assignments between schematic and firmware HAL configuration"

  - id: "xd-connector-pcb-match"
    name: "Connector pinout matches PCB footprint"
    domain: "cross-domain"
    severity: "error"
    description: "Mechanical connector pin assignments must match PCB footprint"
    query: |
      MATCH (conn:DesignElement {domain: 'mechanical', type: 'connector'})
      MATCH (fp:DesignElement {domain: 'electrical', type: 'footprint'})
      WHERE conn.part_id = fp.part_id AND conn.pin_count != fp.pin_count
      RETURN conn.name AS connector, conn.pin_count AS mech_pins, fp.pin_count AS pcb_pins
    violation_message: "Connector {connector}: mechanical pins ({mech_pins}) != PCB pins ({pcb_pins})"
    remediation: "Verify connector datasheet and update footprint or mechanical model"

  - id: "xd-requirement-coverage"
    name: "All requirements have test coverage"
    domain: "cross-domain"
    severity: "warning"
    description: "Active requirements must have at least one linked test procedure"
    query: |
      MATCH (r:Requirement {status: 'active'})
      WHERE NOT (r)-[:SATISFIED_BY]->()-[:TESTED_BY]->()
      RETURN r.id AS requirement_id, r.title AS title
    violation_message: "Requirement '{title}' ({requirement_id}) has no test coverage"
    remediation: "Create a test procedure that validates this requirement"

Evaluation API

ConstraintEngine

class ConstraintEngine:
    async def evaluate_all(self) -> list[ConstraintViolation]:
        """Evaluate all constraints against current graph state."""

    async def evaluate_domain(self, domain: str) -> list[ConstraintViolation]:
        """Evaluate constraints for a specific domain."""

    async def evaluate_change(
        self, events: list[GraphEvent]
    ) -> list[ConstraintViolation]:
        """Pre-commit validation: evaluate constraints affected by proposed changes."""

ConstraintViolation

class ConstraintViolation(BaseModel):
    constraint_id: str
    severity: Literal["error", "warning", "info"]
    message: str
    affected_nodes: list[str]
    remediation: Optional[str]

Integration Points

The constraint engine is invoked at three points:

  1. Pre-commit validation — Before a GraphEvent is applied to Neo4j, evaluate_change() checks whether the proposed change would introduce violations. Events with error-severity violations are rejected.

  2. Periodic full evaluation — A scheduled job runs evaluate_all() and publishes results to the dashboard. This catches violations that emerge from combinations of individually-valid changes.

  3. Gate readiness — When checking gate readiness (EVT/DVT/PVT), evaluate_domain() is called for all relevant domains. Gates cannot pass with unresolved error-severity violations.


Document Description
Graph Schema Node types and relationships that constraints query against
Event Sourcing Event pipeline where pre-commit validation occurs
Digital Twin Evolution Full twin architecture including constraint propagation
MVP Roadmap Gate engine and OPA policy integration

Document Version: v1.0 Last Updated: 2026-02-28 Status: Technical Architecture Document

← Event Sourcing Digital Twin Evolution →