← All white papers

Defense & Aerospace

ADL v0.3 for Defense & Aerospace

Reference ADL domains for defense and aerospace — engineering-change management, export control / ITAR, requirements and certification traceability, and CMMC — built to the ADL v0.3 specification.

Defense and aerospace programs live and die by control: who may change a design, who may see export-controlled data, whether every requirement traces to a verified test, and whether a contractor can prove its security posture on demand. This paper works four such domains in the Application Definition Language (ADL): engineering change management, ITAR access control, DO-178C requirements traceability, and CMMC compliance tracking. It shows how authorization, separation of duties, lifecycle state, and audit history are declared as data and enforced by the Kalos runtime rather than hand-written per application.

Each domain below is a complete, runnable ADL definition. Together they demonstrate a single thesis: the controls a program office actually cares about are structural properties of the application, and structure is exactly what ADL makes explicit.

Companion Podcast Episode

Structural Control for Defense & Aerospace

Listen to our podcast episode covering engineering-change management, export control (ITAR), and CMMC compliance tracking for this whitepaper.

Domain 1: Engineering Change Management (changemgmt)

domain:
  name: changemgmt
  version: "1.0.0"
  description: >
    Engineering Change Proposal lifecycle with CCB approval, structural
    segregation of duties, and a complete audit narrative. Independence is
    enforced by AEL guards, not policy: the originator cannot approve, the
    implementer cannot verify, and admin cannot bypass either gate.

  channels:
    email:
      provider: smtp
      host: ${SMTP_HOST}
      port: ${SMTP_PORT:587}
      from: "cm-noreply@example.com"

  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

  enums:
    EcpStatus:
      values:
        draft:            { label: "Draft",            color: "#94a3b8" }
        submitted:        { label: "Submitted",        color: "#3b82f6" }
        technical_review: { label: "Technical Review", color: "#8b5cf6" }
        ccb_review:       { label: "CCB Review",       color: "#f59e0b" }
        approved:         { label: "Approved",         color: "#10b981" }
        implementing:     { label: "Implementing",     color: "#f97316" }
        verification:     { label: "Verification",     color: "#06b6d4" }
        closed:           { label: "Closed",           color: "#059669", final: true }
        rejected:         { label: "Rejected",         color: "#ef4444", final: true }
        deferred:         { label: "Deferred",         color: "#64748b" }
    EcpClass:
      values:
        class_i:  { label: "Class I - Major Change",
                    description: "Affects form, fit, or function. Requires government approval." }
        class_ii: { label: "Class II - Minor Change",
                    description: "Does not affect form, fit, or function. Contractor authority." }
    EcpPriority:
      values:
        routine:   { label: "Routine",   color: "#94a3b8" }
        urgent:    { label: "Urgent",    color: "#f59e0b" }
        emergency: { label: "Emergency", color: "#ef4444" }
    ControlMarking:
      values:
        cui:       { label: "CUI - Controlled Unclassified" }
        fouo:      { label: "FOUO - For Official Use Only" }
        itar_usml: { label: "ITAR - USML" }
        ear_ccl:   { label: "EAR - CCL" }

  resources:
    Proposal:
      description: >
        An Engineering Change Proposal (ECP). Single-word resource name so the
        generated route segment is unambiguous: /api/v1/changemgmt/proposals.
      object:
        id:                  { type: uuid, primaryKey: true }
        ecpNumber:           { type: string, required: true, unique: true, immutable: true, maxLength: 40 }
        title:               { type: string, required: true, minLength: 10, maxLength: 200 }
        configurationItem:   { type: string, required: true, maxLength: 120 }
        ciBaseline:          { type: string, required: true, maxLength: 60 }
        ecpClass:            { $ref: '#/enums/EcpClass', required: true }
        priority:            { $ref: '#/enums/EcpPriority', default: routine }
        status:              { $ref: '#/enums/EcpStatus', default: draft }
        description:         { type: markdown, required: true }
        justification:       { type: markdown, required: true }
        impactAssessment:    { type: markdown }
        rejectionReason:     { type: text }                     # was required by transitions but missing from the model
        affectedDocuments:   { type: json }
        affectedHardware:    { type: json }
        affectedSoftware:    { type: json }
        costImpact:          { type: money }
        scheduleImpactDays:  { type: integer, min: 0 }
        weightImpactKg:      { type: float }
        # createdById matches the owner-resolution convention (createdById → userId → authorId),
        # so the `owner` symbol resolves.
        createdById:         { type: string, required: true, immutable: true, maxLength: 100 }
        originatorName:      { type: string, required: true, maxLength: 120 }
        leadEngineerId:      { type: string, maxLength: 100 }
        leadEngineerName:    { type: string, maxLength: 120 }
        implementerId:       { type: string, maxLength: 100 }   # set by beginImplementation; consumed by the verify SoD guard
        ccbChairId:          { type: string, maxLength: 100 }
        ccbChairName:        { type: string, maxLength: 120 }
        ccbDecision:         { type: text }
        ccbMeetingDate:      { type: date }
        implementationPlan:  { type: markdown }
        verificationMethod:  { type: text }
        verificationResult:  { type: text }
        closedAt:            { type: datetime }
        contractNumber:      { type: string, maxLength: 60 }
        programName:         { type: string, required: true, maxLength: 120 }
        classificationLevel: { $ref: '#/enums/ControlMarking', default: cui }
        itarControlled:      { type: boolean, default: false }
        $merge: { $ref: '#/objects/Timestamp' }

      indexes:
        - { fields: [status] }
        - { fields: [programName] }
        - { fields: [createdById] }
        - { fields: [priority] }

      stateMachine:
        field: status
        initial: draft
        states:
          draft:
            transitions:
              submit:
                to: submitted
                guards: [owner, engineer, admin]
                requiredFields: [description, justification, configurationItem]
          submitted:
            transitions:
              assignReviewer:
                to: technical_review
                guards: [chiefEngineer, programManager, admin]
                requiredFields: [leadEngineerId]
              reject:
                to: rejected
                guards: [chiefEngineer, admin]
                requiredFields: [rejectionReason]
          technical_review:
            transitions:
              recommendApproval:
                to: ccb_review
                guard: "field('leadEngineerId') == userId() || hasRole('chiefEngineer')"
                requiredFields: [impactAssessment]
              requestRevision:
                to: draft
                guards: [owner, chiefEngineer, admin]
              reject:
                to: rejected
                guards: [chiefEngineer, admin]
                requiredFields: [rejectionReason]
          ccb_review:
            transitions:
              approve:
                to: approved
                # Structural SoD: the CCB chair approves, and cannot be the
                # originator or the assigned lead engineer. No admin bypass.
                guard: >
                  hasRole('ccbChair')
                  && field('createdById') != userId()
                  && field('leadEngineerId') != userId()
                requiredFields: [ccbDecision, ccbMeetingDate]
                sets:
                  ccbChairId: "$user.id"
                  ccbChairName: "$user.username"
              defer:
                to: deferred
                guards: [ccbChair, admin]
              reject:
                to: rejected
                guards: [ccbChair, admin]
                requiredFields: [ccbDecision]
          deferred:
            transitions:
              resubmitToCcb:
                to: ccb_review
                guards: [chiefEngineer, programManager, admin]
          approved:
            transitions:
              beginImplementation:
                to: implementing
                guards: [leadEngineer, chiefEngineer, admin]
                requiredFields: [implementationPlan]
                sets:
                  implementerId: "$user.id"
          implementing:
            transitions:
              completeImplementation:
                to: verification
                guards: [leadEngineer, chiefEngineer, admin]
                requiredFields: [verificationMethod]
          verification:
            transitions:
              verify:
                to: closed
                # Structural SoD: verifier must be independent of both the
                # implementer and the originator. No admin bypass.
                guard: >
                  (hasRole('qualityEngineer') || hasRole('chiefEngineer'))
                  && field('implementerId') != userId()
                  && field('createdById') != userId()
                requiredFields: [verificationResult]
                sets:
                  closedAt: "now()"
              failVerification:
                to: implementing
                guards: [qualityEngineer, admin]
          rejected:
            final: true
          closed:
            final: true

      validation:
        classOneNeedsContract:
          rule: "ecpClass != 'class_i' || contractNumber != null"
          message: "Class I changes require a contract number for government approval routing"
        rejectionNeedsReason:
          rule: "status != 'rejected' || rejectionReason != null"
          message: "A rejected ECP must record the rejection rationale"

      features:
        audit: true
        versioning: true          # full pre-update snapshots
        search: true
      audit:
        exclude: [updatedAt]

      history:
        events:
          created:
            on: create
            capture: [ecpNumber, title, configurationItem, ecpClass, priority, originatorName, programName]
            label: "ECP {ecpNumber} created - {title}"
            detail: "CI: {configurationItem}. Class: {ecpClass}. Priority: {priority}. Originator: {originatorName}"
            icon: lucide:file-plus
            severity: info
          submitted:
            on: transition
            transition: submit
            label: "ECP submitted for review"
            icon: lucide:send
            severity: info
          technicalReviewStarted:
            on: transition
            transition: assignReviewer
            capture: [leadEngineerName]
            label: "Technical review assigned to {leadEngineerName}"
            icon: lucide:user-plus
            severity: info
          recommendedForCcb:
            on: transition
            transition: recommendApproval
            capture: [impactAssessment, costImpact, scheduleImpactDays]
            label: "Recommended for CCB - cost impact {costImpact}, schedule {scheduleImpactDays} days"
            icon: lucide:arrow-up-circle
            severity: info
          ccbApproved:
            on: transition
            transition: approve
            capture: [ccbChairName, ccbDecision, ccbMeetingDate]
            label: "CCB APPROVED by {ccbChairName}"
            detail: "Decision: {ccbDecision}. Meeting date: {ccbMeetingDate}"
            icon: lucide:check-circle
            severity: success
          ccbRejected:
            on: transition
            transition: reject
            capture: [ccbDecision, rejectionReason]
            label: "REJECTED"
            detail: "{ccbDecision}"
            icon: lucide:x-circle
            severity: error
          ccbDeferred:
            on: transition
            transition: defer
            label: "Deferred by CCB"
            icon: lucide:pause
            severity: warning
          implementationStarted:
            on: transition
            transition: beginImplementation
            capture: [implementationPlan]
            label: "Implementation started"
            icon: lucide:hammer
            severity: info
          implementationComplete:
            on: transition
            transition: completeImplementation
            capture: [verificationMethod]
            label: "Implementation complete - verification pending"
            detail: "Verification method: {verificationMethod}"
            icon: lucide:check
            severity: info
          verificationPassed:
            on: transition
            transition: verify
            capture: [verificationResult]
            label: "Verification PASSED - ECP closed"
            detail: "{verificationResult}"
            icon: lucide:shield-check
            severity: success
          verificationFailed:
            on: transition
            transition: failVerification
            label: "Verification FAILED - returned to implementation"
            icon: lucide:shield-x
            severity: error
          emergencyEscalation:
            on: fieldChange
            field: priority
            condition: "priority == 'emergency'"
            capture: [ecpNumber, title, priority]
            label: "EMERGENCY PRIORITY - {ecpNumber}: {title}"
            icon: lucide:alert-octagon
            severity: error
          classIChange:
            on: create
            condition: "ecpClass == 'class_i'"
            capture: [ecpNumber, title, configurationItem]
            label: "CLASS I CHANGE - government approval required"
            detail: "ECP {ecpNumber}: {title}. CI: {configurationItem}"
            icon: lucide:alert-triangle
            severity: warning
            roles: [programManager, ccbChair, contractingOfficer, admin]
          itarDataInvolved:
            on: create
            condition: "itarControlled == true"
            capture: [ecpNumber, classificationLevel]
            label: "ITAR-CONTROLLED DATA - export control applies"
            icon: lucide:lock
            severity: warning
            roles: [exportControlOfficer, securityManager, admin]
          specificationChanged:
            on: update
            fields: [description, impactAssessment, implementationPlan]
            capture: [title]
            label: "ECP specification updated"
            icon: lucide:edit
            severity: info

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [engineer, seniorEngineer, chiefEngineer, admin]
        update: [engineer, seniorEngineer, chiefEngineer, programManager, qualityEngineer, ccbChair, admin]
        delete: [admin]
        fields:
          ccbDecision:         { write: [ccbChair, admin] }
          ccbMeetingDate:      { write: [ccbChair, admin] }
          verificationResult:  { write: [qualityEngineer, chiefEngineer, admin] }
          classificationLevel: { write: [securityManager, admin] }
          itarControlled:      { write: [exportControlOfficer, securityManager, admin] }
          implementerId:       { write: [admin] }   # normally set only via the beginImplementation transition

Domain 2: ITAR Access Control (exportcontrol)

domain:
  name: exportcontrol
  version: "1.0.0"
  description: >
    ITAR/EAR access authorization lifecycle. The US-person determination is a
    controlled enum written only by security roles — not a free-text citizenship
    string. Expiration is driven by a tiered schedule, so the 30-day warning and
    the expiry transition actually fire.

  channels:
    email:
      provider: smtp
      host: ${SMTP_HOST}
      port: ${SMTP_PORT:587}
      from: "exportcontrol-noreply@example.com"

  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

  enums:
    AuthorizationStatus:
      values:
        requested: { label: "Requested",       color: "#3b82f6" }
        screening: { label: "Under Screening", color: "#f59e0b" }
        approved:  { label: "Approved",        color: "#10b981" }
        denied:    { label: "Denied",          color: "#ef4444", final: true }
        revoked:   { label: "Revoked",         color: "#64748b", final: true }
        expired:   { label: "Expired",         color: "#94a3b8", final: true }
    ControlCategory:
      values:
        itar_usml: { label: "ITAR - USML" }
        ear_ccl:   { label: "EAR - CCL" }
        cui:       { label: "CUI - Controlled Unclassified" }
        fouo:      { label: "FOUO - For Official Use Only" }
    PersonStatus:
      values:
        us_person:     { label: "U.S. Person",
                         description: "U.S. citizen, lawful permanent resident, or protected individual under 8 U.S.C. 1324b(a)(3)" }
        non_us_person: { label: "Non-U.S. Person",
                         description: "Requires an export license or exemption before access to ITAR-controlled data" }

  resources:
    Authorization:
      description: >
        A request to grant one person access to controlled technical data on one
        program. Route: /api/v1/exportcontrol/authorizations.
      object:
        id:                    { type: uuid, primaryKey: true }
        requestNumber:         { type: string, required: true, unique: true, immutable: true, maxLength: 40 }
        personnelId:           { type: uuid, required: true, immutable: true }
        personnelName:         { type: string, required: true, maxLength: 120 }
        # The legal determination, distinct from citizenship. A green-card holder
        # is a US person; citizenship alone does not decide ITAR access.
        personStatus:          { $ref: '#/enums/PersonStatus', required: true }
        citizenshipCountry:    { type: country, required: true }
        clearanceLevel:        { type: string, maxLength: 40 }
        programName:           { type: string, required: true, maxLength: 120 }
        controlCategory:       { $ref: '#/enums/ControlCategory', required: true }
        usmlCategory:          { type: string, maxLength: 20 }
        status:                { $ref: '#/enums/AuthorizationStatus', default: requested }
        justification:         { type: text, required: true }
        technologyControlPlan: { type: text }
        screeningResult:       { type: text }
        denialReason:          { type: text }
        createdById:           { type: string, required: true, immutable: true, maxLength: 100 }
        approvedById:          { type: string, maxLength: 100 }
        approvedByName:        { type: string, maxLength: 120 }
        approvedAt:            { type: datetime }
        expirationDate:        { type: date }
        revocationReason:      { type: text }
        $merge: { $ref: '#/objects/Timestamp' }

      indexes:
        - { fields: [status] }
        - { fields: [programName] }
        - { fields: [personnelId] }
        - { fields: [personStatus] }

      stateMachine:
        field: status
        initial: requested
        states:
          requested:
            transitions:
              screen:
                to: screening
                guards: [exportControlOfficer, admin]
          screening:
            transitions:
              approve:
                to: approved
                # SoD: the approving export control officer cannot be the requester.
                guard: "hasRole('exportControlOfficer') && field('createdById') != userId()"
                requiredFields: [screeningResult, expirationDate]
                sets:
                  approvedAt: "now()"
                  approvedById: "$user.id"
                  approvedByName: "$user.username"
              deny:
                to: denied
                guards: [exportControlOfficer, admin]
                requiredFields: [denialReason]
          approved:
            transitions:
              revoke:
                to: revoked
                guards: [exportControlOfficer, securityManager, admin]
                requiredFields: [revocationReason]
              expire:
                to: expired
                # Executed automatically by the accessExpiry schedule (system actor).
                # Manual execution restricted to admin.
                guards: [admin]
          denied:
            final: true
          revoked:
            final: true
          expired:
            final: true

      validation:
        nonUsApprovalNeedsTcp:
          rule: "status != 'approved' || personStatus != 'non_us_person' || technologyControlPlan != null"
          message: "Approving a non-U.S. person requires a Technology Control Plan (license or exemption basis)"
        approvalNeedsExpiry:
          rule: "status != 'approved' || expirationDate != null"
          message: "An approved authorization must carry an expiration date"

      features:
        audit: true

      history:
        events:
          accessRequested:
            on: create
            capture: [requestNumber, personnelName, personStatus, citizenshipCountry, programName, controlCategory, justification]
            label: "Access requested - {personnelName} ({personStatus})"
            detail: "Program: {programName}. Category: {controlCategory}. Justification: {justification}"
            icon: lucide:user-plus
            severity: info
          screeningStarted:
            on: transition
            transition: screen
            label: "Export control screening initiated"
            icon: lucide:search
            severity: info
          accessGranted:
            on: transition
            transition: approve
            capture: [approvedByName, screeningResult, expirationDate]
            label: "Access APPROVED by {approvedByName}"
            detail: "Screening: {screeningResult}. Expires: {expirationDate}"
            icon: lucide:shield-check
            severity: success
          accessDenied:
            on: transition
            transition: deny
            capture: [denialReason]
            label: "Access DENIED"
            detail: "{denialReason}"
            icon: lucide:shield-x
            severity: error
          accessRevoked:
            on: transition
            transition: revoke
            capture: [revocationReason]
            label: "Access REVOKED"
            detail: "{revocationReason}"
            icon: lucide:lock
            severity: error
          accessExpired:
            on: transition
            transition: expire
            label: "Access authorization expired"
            icon: lucide:clock
            severity: warning
          nonUsPersonRequest:
            on: create
            condition: "personStatus == 'non_us_person'"
            capture: [personnelName, citizenshipCountry, controlCategory, programName]
            label: "NON-US PERSON ACCESS REQUEST - {personnelName} ({citizenshipCountry})"
            detail: "Category: {controlCategory}. Program: {programName}. License or exemption required."
            icon: lucide:alert-octagon
            severity: error
            roles: [exportControlOfficer, securityManager, admin]

      permissions:
        list:   [exportControlOfficer, securityManager, programManager, admin]
        get:    [exportControlOfficer, securityManager, programManager, admin]
        create: [programManager, admin]
        update: [exportControlOfficer, securityManager, admin]
        delete: [admin]
        fields:
          personStatus:          { write: [exportControlOfficer, securityManager, admin] }
          screeningResult:       { write: [exportControlOfficer, admin] }
          denialReason:          { write: [exportControlOfficer, admin] }
          approvedById:          { write: [admin] }   # normally set only via the approve transition
          revocationReason:      { write: [exportControlOfficer, securityManager, admin] }
          clearanceLevel:        { write: [securityManager, admin] }
          technologyControlPlan: { write: [exportControlOfficer, securityManager, admin] }

  # Time-based behavior lives here, not in fieldChange events.
  schedules:
    accessExpiry:
      description: "Warn 30 days before authorization expiry; expire on the date"
      type: relative
      source:
        resource: Authorization
        filter: "status == 'approved'"
      relativeTo: expirationDate
      tiers:
        - daysUntil: 30
          action: notify
          tag: 30day
          channels: [email]
          recipients: { roles: [exportControlOfficer, securityManager] }
          template:
            subject: "Access authorization expires in 30 days"
            body: "Authorization {requestNumber} for {personnelName} on {programName} expires {expirationDate}."
        - daysUntil: 0
          action: transition
          transition: expire
          tag: expire

Domain 3: DO-178C Requirements Traceability (certtrace)

domain:
  name: certtrace
  version: "1.0.0"
  description: >
    Bidirectional requirements traceability for DO-178C. Parent traces and test
    linkage are real relationships with referential integrity, not free-text IDs
    — a typo cannot silently satisfy a trace. Gap detection uses stored computed
    fields, so the auditor query is a filtered list, not a log reconstruction.

  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

  enums:
    RequirementLevel:
      values:
        system:  { label: "System Requirement" }
        hlr:     { label: "High-Level Requirement" }
        llr:     { label: "Low-Level Requirement" }
        derived: { label: "Derived Requirement" }
    RequirementStatus:
      values:
        draft:       { label: "Draft",       color: "#94a3b8" }
        baselined:   { label: "Baselined",   color: "#3b82f6" }
        implemented: { label: "Implemented", color: "#f59e0b" }
        verified:    { label: "Verified",    color: "#10b981" }
        closed:      { label: "Closed",      color: "#059669", final: true }
    VerificationMethod:
      values:
        test:       { label: "Test" }
        analysis:   { label: "Analysis" }
        inspection: { label: "Inspection" }
        review:     { label: "Review of Design" }
    Dal:
      values:
        a: { label: "Level A - Catastrophic" }
        b: { label: "Level B - Hazardous" }
        c: { label: "Level C - Major" }
        d: { label: "Level D - Minor" }
        e: { label: "Level E - No Effect" }

  resources:
    Requirement:
      object:
        id:                { type: uuid, primaryKey: true }
        reqId:             { type: string, required: true, unique: true, immutable: true, maxLength: 40 }
        title:             { type: string, required: true, maxLength: 200 }
        description:       { type: markdown, required: true }
        level:             { $ref: '#/enums/RequirementLevel', required: true }
        dal:               { $ref: '#/enums/Dal', required: true }
        status:            { $ref: '#/enums/RequirementStatus', default: draft }
        parentId:          { type: uuid }        # FK — mutable by design: requirements are re-parented during decomposition
        sourceDocument:    { type: string, maxLength: 120 }
        rationale:         { type: text }
        verificationMethod: { $ref: '#/enums/VerificationMethod' }
        sourceCodeRef:     { type: string, maxLength: 255 }
        implementedById:   { type: string, maxLength: 100 }   # set by implement; consumed by the verify SoD guard
        verifiedById:      { type: string, maxLength: 100 }
        verifiedByName:    { type: string, maxLength: 120 }
        verifiedAt:        { type: datetime }
        programName:       { type: string, required: true, maxLength: 120 }
        configurationItem: { type: string, required: true, maxLength: 120 }
        baselineVersion:   { type: string, maxLength: 40 }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        parent:    { type: belongsTo, target: Requirement, foreignKey: parentId, onDelete: restrict }
        children:  { type: hasMany,   target: Requirement, foreignKey: parentId }
        testCases:
          type: belongsToMany
          target: TestCase
          through: RequirementTestCase
          foreignKey: requirementId
          otherKey: testCaseId

      indexes:
        - { fields: [parentId] }
        - { fields: [status] }
        - { fields: [level] }
        - { fields: [programName] }

      computed:
        # Stored so they are filterable: the auditor gap query is a list filter.
        # A system requirement legitimately has no parent; a derived requirement
        # by definition lacks an upward trace (DO-178C 5.2.1) and is handled by
        # the derivedRequirementCreated event instead of a gap flag.
        hasTraceUp:
          type: boolean
          formula: "level == 'system' || level == 'derived' || parentId != null"
          stored: true
          updateOn: [parentId, level]
        hasTraceDown:
          type: boolean
          formula: "sourceCodeRef != null && sourceCodeRef != ''"
          stored: true
          updateOn: [sourceCodeRef]
        testCaseCount:
          type: integer
          formula: "count(testCases)"
          stored: true
          updateOn: [testCases.create, testCases.update, testCases.delete]

      stateMachine:
        field: status
        initial: draft
        states:
          draft:
            transitions:
              baseline:
                to: baselined
                guards: [systemsEngineer, chiefEngineer, admin]
                requiredFields: [description, level, dal, baselineVersion]
          baselined:
            transitions:
              implement:
                to: implemented
                guards: [softwareEngineer, admin]
                requiredFields: [sourceCodeRef]
                sets:
                  implementedById: "$user.id"
              revise:
                to: draft
                guards: [systemsEngineer, admin]
          implemented:
            transitions:
              verify:
                to: verified
                # Structural SoD: the verifier cannot be the implementer. No admin bypass.
                guard: "hasRole('verificationEngineer') && field('implementedById') != userId()"
                requiredFields: [verificationMethod]
                sets:
                  verifiedAt: "now()"
                  verifiedById: "$user.id"
                  verifiedByName: "$user.username"
              revise:
                to: draft
                guards: [systemsEngineer, chiefEngineer, admin]
          verified:
            transitions:
              close:
                to: closed
                guards: [chiefEngineer, certificationLiaison, admin]
              failVerification:
                to: implemented
                guards: [verificationEngineer, admin]
          closed:
            final: true

      validation:
        testMethodNeedsTests:
          rule: "status != 'verified' || verificationMethod != 'test' || testCaseCount > 0"
          message: "Verification by test requires at least one linked test case"
        derivedNeedsRationale:
          rule: "level != 'derived' || rationale != null"
          message: "Derived requirements must record a rationale for the system safety assessment (DO-178C 5.2.1)"

      features:
        audit: true
        versioning: true

      history:
        events:
          created:
            on: create
            capture: [reqId, title, level, dal, programName, configurationItem]
            label: "Requirement {reqId} created - {title}"
            detail: "Level: {level}. DAL: {dal}. CI: {configurationItem}"
            icon: lucide:file-plus
            severity: info
          baselined:
            on: transition
            transition: baseline
            capture: [reqId, baselineVersion]
            label: "Requirement {reqId} baselined"
            detail: "Baseline: {baselineVersion}"
            icon: lucide:bookmark
            severity: info
          implemented:
            on: transition
            transition: implement
            capture: [reqId, sourceCodeRef]
            label: "Implemented - {sourceCodeRef}"
            icon: lucide:code
            severity: info
          verified:
            on: transition
            transition: verify
            capture: [reqId, verificationMethod, verifiedByName]
            label: "Verified by {verifiedByName} via {verificationMethod}"
            icon: lucide:check-circle
            severity: success
          verificationFailed:
            on: transition
            transition: failVerification
            label: "Verification FAILED - returned for rework"
            icon: lucide:x-circle
            severity: error
          closed:
            on: transition
            transition: close
            capture: [reqId]
            label: "Requirement {reqId} closed"
            icon: lucide:lock
            severity: success
          specificationChanged:
            on: update
            fields: [description, rationale]
            capture: [reqId, title]
            label: "Requirement {reqId} specification updated"
            icon: lucide:edit
            severity: warning
          traceabilityGap:
            # Legitimate fieldChange: status genuinely changes to 'implemented'.
            on: fieldChange
            field: status
            condition: "status == 'implemented' && (hasTraceUp == false || hasTraceDown == false)"
            capture: [reqId, hasTraceUp, hasTraceDown]
            label: "TRACEABILITY GAP - {reqId}"
            detail: "Trace up: {hasTraceUp}. Trace down: {hasTraceDown}. DO-178C requires bidirectional traceability."
            icon: lucide:alert-octagon
            severity: error
            roles: [certificationLiaison, qualityEngineer, chiefEngineer, admin]
          derivedRequirementCreated:
            on: create
            condition: "level == 'derived'"
            capture: [reqId, title, rationale]
            label: "DERIVED REQUIREMENT - {reqId}: {title}"
            detail: "Rationale: {rationale}. DO-178C 5.2.1 requires derived requirements be provided to the system safety assessment."
            icon: lucide:alert-triangle
            severity: warning
            roles: [systemsEngineer, certificationLiaison, admin]
          dalARequirement:
            on: create
            condition: "dal == 'a'"
            capture: [reqId, title, configurationItem]
            label: "DAL A REQUIREMENT - {reqId}: {title}"
            detail: "CI: {configurationItem}. Maximum verification rigor required. Independent verification mandatory."
            icon: lucide:shield
            severity: warning
            roles: [certificationLiaison, chiefEngineer, qualityEngineer, admin]

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [systemsEngineer, softwareEngineer, chiefEngineer, admin]
        update: [systemsEngineer, softwareEngineer, verificationEngineer, chiefEngineer, certificationLiaison, admin]
        delete: [admin]
        fields:
          dal:                { write: [systemsEngineer, chiefEngineer, admin] }
          verificationMethod: { write: [verificationEngineer, chiefEngineer, admin] }
          baselineVersion:    { write: [chiefEngineer, certificationLiaison, admin] }
          sourceCodeRef:      { write: [softwareEngineer, admin] }
          implementedById:    { write: [admin] }   # normally set only via the implement transition

    TestCase:
      description: >
        A verification test case. Linked to requirements through the
        RequirementTestCase junction — link/unlink via the relationship endpoints.
      object:
        id:          { type: uuid, primaryKey: true }
        testId:      { type: string, required: true, unique: true, immutable: true, maxLength: 40 }
        title:       { type: string, required: true, maxLength: 200 }
        procedure:   { type: markdown, required: true }
        expected:    { type: text, required: true }
        lastResult:  { type: string, enum: [pass, fail, blocked, not_run], default: not_run }
        lastRunAt:   { type: datetime }
        programName: { type: string, required: true, maxLength: 120 }
        $merge: { $ref: '#/objects/Timestamp' }
      relationships:
        requirements:
          type: belongsToMany
          target: Requirement
          through: RequirementTestCase
          foreignKey: testCaseId
          otherKey: requirementId
      indexes:
        - { fields: [programName] }
        - { fields: [lastResult] }
      features:
        audit: true
      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [verificationEngineer, softwareEngineer, admin]
        update: [verificationEngineer, admin]
        delete: [admin]

Domain 4: CMMC Compliance Tracking (cmmc)

domain:
  name: cmmc
  version: "1.0.0"
  description: >
    NIST SP 800-171 control assessment with POA&M tracking. Sheet mode enables
    assessment-specific evidence columns. Overdue POA&Ms and review reminders
    are driven by schedules — a due date passing is not a field change and
    cannot be detected by fieldChange events.

  channels:
    email:
      provider: smtp
      host: ${SMTP_HOST}
      port: ${SMTP_PORT:587}
      from: "cmmc-noreply@example.com"

  objects:
    Timestamp:
      createdAt: { type: datetime, autoNow: create }
      updatedAt: { type: datetime, autoNow: update }

  enums:
    ControlStatus:
      values:
        not_assessed:        { label: "Not Assessed",        color: "#94a3b8" }
        under_assessment:    { label: "Under Assessment",    color: "#3b82f6" }
        compliant:           { label: "Compliant",           color: "#10b981" }
        non_compliant:       { label: "Non-Compliant",       color: "#ef4444" }
        partially_compliant: { label: "Partially Compliant", color: "#f59e0b" }
    ImplementationStatus:
      values:
        implemented:           { label: "Implemented" }
        partially_implemented: { label: "Partially Implemented" }
        planned:               { label: "Planned" }
        not_applicable:        { label: "Not Applicable" }
    RiskLevel:
      values: [low, moderate, high, critical]

  resources:
    Assessment:
      mode: sheet
      description: >
        An assessment of one NIST SP 800-171 control. Dynamic columns hold
        assessment-cycle-specific evidence links and notes.
      object:
        id:                        { type: uuid, primaryKey: true }
        controlId:                 { type: string, required: true, unique: true, immutable: true, maxLength: 20 }
        controlFamily:             { type: string, required: true, maxLength: 60 }
        controlTitle:              { type: string, required: true, maxLength: 200 }
        cmmcLevel:                 { type: integer, required: true, min: 1, max: 3 }
        status:                    { $ref: '#/enums/ControlStatus', default: not_assessed }
        implementationStatus:      { $ref: '#/enums/ImplementationStatus' }
        implementationDescription: { type: text }
        evidenceLinks:             { type: json }
        assessorId:                { type: string, maxLength: 100 }
        assessorName:              { type: string, maxLength: 120 }
        assessmentDate:            { type: date }
        nextReviewDate:            { type: date }
        poamRequired:              { type: boolean, default: false }
        poamDescription:           { type: text }
        poamDueDate:               { type: date }
        riskLevel:                 { $ref: '#/enums/RiskLevel' }
        sprsScore:                 { type: integer, min: -203, max: 110 }
        $merge: { $ref: '#/objects/Timestamp' }

      indexes:
        - { fields: [status] }
        - { fields: [controlFamily] }
        - { fields: [poamDueDate] }
        - { fields: [nextReviewDate] }

      dynamicFields:
        enabled: true
        allowTypes: [string, text, date, boolean]
        maxColumns: 30
        permissions:
          addColumn:    [ciso, assessor, admin]
          deleteColumn: [admin]

      stateMachine:
        field: status
        initial: not_assessed
        states:
          not_assessed:
            transitions:
              assess:
                to: under_assessment
                guards: [assessor, ciso, admin]
                sets:
                  assessorId: "$user.id"
                  assessorName: "$user.username"
          under_assessment:
            transitions:
              markCompliant:
                to: compliant
                guards: [assessor, ciso, admin]
                requiredFields: [implementationDescription, evidenceLinks]
                sets:
                  poamRequired: false
              markNonCompliant:
                to: non_compliant
                guards: [assessor, ciso, admin]
                requiredFields: [implementationDescription]
              markPartiallyCompliant:
                to: partially_compliant
                guards: [assessor, ciso, admin]
                requiredFields: [poamDescription, poamDueDate]
                sets:
                  poamRequired: true
          compliant:
            transitions:
              reassess:
                to: under_assessment
                guards: [assessor, ciso, admin]
          non_compliant:
            transitions:
              remediate:
                to: under_assessment
                guards: [assessor, itSecurity, admin]
          partially_compliant:
            transitions:
              closePoam:
                to: compliant
                guards: [assessor, ciso, admin]
                requiredFields: [implementationDescription, evidenceLinks]
                sets:
                  poamRequired: false
              reassess:
                to: under_assessment
                guards: [assessor, ciso, admin]

      validation:
        compliantNeedsReviewDate:
          rule: "status != 'compliant' || nextReviewDate != null"
          message: "A compliant control must schedule its next review"

      features:
        audit: true

      history:
        events:
          assessmentStarted:
            on: transition
            transition: assess
            capture: [controlId, controlTitle, assessorName]
            label: "{controlId} - assessment started by {assessorName}"
            icon: lucide:clipboard-check
            severity: info
          foundCompliant:
            on: transition
            transition: markCompliant
            capture: [controlId, controlTitle, assessorName]
            label: "{controlId} - COMPLIANT"
            icon: lucide:check-circle
            severity: success
          foundNonCompliant:
            on: transition
            transition: markNonCompliant
            capture: [controlId, controlTitle, implementationDescription]
            label: "{controlId} - NON-COMPLIANT"
            detail: "{implementationDescription}"
            icon: lucide:x-circle
            severity: error
          poamCreated:
            on: transition
            transition: markPartiallyCompliant
            capture: [controlId, poamDescription, poamDueDate]
            label: "{controlId} - POA&M created"
            detail: "{poamDescription}. Due: {poamDueDate}"
            icon: lucide:alert-triangle
            severity: warning
          poamClosed:
            on: transition
            transition: closePoam
            capture: [controlId, controlTitle]
            label: "{controlId} - POA&M closed, now compliant"
            icon: lucide:check-circle
            severity: success

      permissions:
        list:   [assessor, itSecurity, ciso, admin]
        get:    [assessor, itSecurity, ciso, admin]
        create: [ciso, admin]
        update: [assessor, itSecurity, ciso, admin]
        delete: [admin]
        fields:
          sprsScore: { write: [ciso, admin] }
          riskLevel: { write: [assessor, ciso, admin] }

  schedules:
    poamEscalation:
      description: "Warn 14 days before a POA&M due date; escalate when overdue"
      type: relative
      source:
        resource: Assessment
        filter: "status == 'partially_compliant'"
      relativeTo: poamDueDate
      tiers:
        - daysUntil: 14
          action: notify
          tag: 14day
          channels: [email]
          recipients: { roles: [assessor, itSecurity] }
          template:
            subject: "POA&M due in 14 days - {controlId}"
            body: "{poamDescription}. Due: {poamDueDate}."
        - daysUntil: 0
          action: notify
          tag: overdue
          channels: [email]
          recipients: { roles: [ciso, assessor, itSecurity] }
          template:
            subject: "POA&M OVERDUE - {controlId}"
            body: "{poamDescription}. Was due: {poamDueDate}."
    reviewReminder:
      description: "Remind 30 days before a compliant control's review date"
      type: relative
      source:
        resource: Assessment
        filter: "status == 'compliant'"
      relativeTo: nextReviewDate
      tiers:
        - daysUntil: 30
          action: notify
          tag: 30day
          channels: [email]
          recipients: { roles: [assessor, ciso] }
          template:
            subject: "Control review due in 30 days - {controlId}"
            body: "Next review: {nextReviewDate}."

Auditor queries

These queries use the documented /api/v1/ prefix, id-based paths, and supported history filters:

Configuration audit: complete ECP lifecycle

# Resolve the business key to the record id, then fetch its timeline
curl -s "$BASE/api/v1/changemgmt/proposals?filter[ecpNumber]=ECP-2026-0047" \
  -H "Authorization: Bearer $TOKEN" | jq -r '.data.items[0].id'

curl -s "$BASE/api/v1/changemgmt/proposals/{id}/history" \
  -H "Authorization: Bearer $TOKEN"

ITAR audit: non-US person access on a program

# Resource-level filters (programName is not a history query param)
curl -s "$BASE/api/v1/exportcontrol/authorizations?filter[programName]=MERIDIAN&filter[personStatus]=non_us_person" \
  -H "Authorization: Bearer $TOKEN"

# Cross-record event stream for the flagged requests
curl -s "$BASE/api/v1/exportcontrol/authorizations/history?event=nonUsPersonRequest" \
  -H "Authorization: Bearer $TOKEN"

DO-178C audit: traceability gaps

# Stored computed fields are directly filterable — no log reconstruction
curl -s "$BASE/api/v1/certtrace/requirements?filter[status]=implemented&filter[hasTraceUp]=false" \
  -H "Authorization: Bearer $TOKEN"
curl -s "$BASE/api/v1/certtrace/requirements?filter[status]=implemented&filter[hasTraceDown]=false" \
  -H "Authorization: Bearer $TOKEN"

CMMC audit: POA&M status

# Open POA&Ms by due date
curl -s "$BASE/api/v1/cmmc/assessments?filter[status]=partially_compliant&sort=poamDueDate" \
  -H "Authorization: Bearer $TOKEN"

# Evidence that the overdue detection ran (scheduler execution log)
curl -s "$BASE/api/v1/cmmc/schedules/poamEscalation/log?since=2025-11-01" \
  -H "Authorization: Bearer $TOKEN"

# POA&Ms closed since the last assessment
curl -s "$BASE/api/v1/cmmc/assessments/history?event=poamClosed&after=2025-11-01" \
  -H "Authorization: Bearer $TOKEN"