← All white papers

Research & Education

ADL v0.3 for Research Administration

A HERAS grants-management ADL domain implementing 2 CFR §200 uniform-guidance controls, built to the regulation and the ADL v0.3 specification.

Research administration turns on the fine print of federal award terms: the 2 CFR §200 uniform-guidance thresholds on budget transfers, the period-of-performance boundaries on every expenditure, effort commitments that cannot exceed 100% across awards. This paper works a research-administration grants domain (HERAS) in the Application Definition Language (ADL) as a complete, runnable definition, with the §200 controls computed against the total approved award budget the regulation actually specifies.

Infographic comparing the unchanged core of 2 CFR Part 200 (period-of-performance boundaries, budget transfer thresholds, and the three-part cost test) with the 2026 expansion: elimination of fixed-amount awards (§200.201), mandatory E-Verify and pre-payment screening (§200.303), dynamic mid-award condition management (§200.208), prior-approval cost gates (§200.432/442/454), and national-interest termination (§200.340).
The regulatory landscape the domain implements: 2 CFR Part 200's stable administrative core, and the 2026 revision that reframes it from non-binding guidance into binding regulation with new screening mandates and termination authority.

The domain below shows how those award-level rules become prevention rather than after-the-fact detection, declared in ADL and enforced by the Kalos runtime at the moment a transaction would breach them.

Diagram contrasting traditional detective audits, which find violations after the money is spent, with the Kalos preventative control loop: a transaction is intercepted before commit, evaluated against ADL-defined §200 controls, and either written to the database as a compliant action or blocked with a 422 RULE_VIOLATION error. Triggers for the error include budget transfer thresholds, period-of-performance boundaries, 2026 cost restrictions, and payee verification failures.
Prevention, not detection: the Kalos runtime evaluates each transaction against the ADL-declared controls at pre-commit. A compliant write proceeds; a breach is rejected with 422 RULE_VIOLATION before it can commit, and the rules log becomes the audit evidence.

Domain: grants

One domain, five resources. The cross-resource compliance rules (§200.308 spans Award + Transfer; §200.430 spans Commitments across awards) require the resources to share a domain. The rules engine and relationships operate within a domain, and nothing in the v0.3 spec defines a cross-domain foreign key.

domain:
  name: grants
  version: "1.0.0"
  description: >
    Sponsored-programs administration: awards, budget categories, budget
    transfers, expenditures, and effort commitments. The 2 CFR 200.308 transfer
    threshold and the 200.430 effort cap are enforced by reject rules BEFORE
    the write commits — prevention, with the rules log as the audit evidence.

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

  # FERPA: student workers appear as grant personnel.
  privacy:
    enabled: true
    classifications:
      personalData: { description: "Personnel and student-worker identifiers (FERPA)", retention: 7 years }
    subjectIdentifier: { primary: personnelId }
    anonymization:
      method: pseudonymize
      rules:
        default: "Redacted"
        personnelName: "$pseudonym"

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

  enums:
    AwardStatus:
      values:
        pending:     { label: "Pending",      color: "#94a3b8" }
        active:      { label: "Active",        color: "#10b981" }
        nce_pending: { label: "NCE Pending",   color: "#f59e0b" }
        suspended:   { label: "Suspended",     color: "#ef4444" }
        in_closeout: { label: "In Closeout",   color: "#6366f1" }
        closed:      { label: "Closed",        color: "#059669", final: true }
        terminated:  { label: "Terminated",    color: "#7f1d1d", final: true }
    AwardType:
      values: [grant, cooperative_agreement, contract, subaward_in]
    CategoryType:
      values:
        senior_personnel:    { label: "Senior Personnel" }
        other_personnel:     { label: "Other Personnel" }
        fringe_benefits:     { label: "Fringe Benefits" }
        equipment:           { label: "Equipment" }
        travel:              { label: "Travel" }
        participant_support: { label: "Participant Support" }
        other_direct:        { label: "Other Direct Costs" }
        indirect:            { label: "Indirect Costs (F&A)" }
    TransferStatus:
      values:
        requested:    { label: "Requested",     color: "#3b82f6" }
        under_review: { label: "Under Review",  color: "#8b5cf6" }
        approved:     { label: "Approved",      color: "#10b981" }
        completed:    { label: "Completed",     color: "#059669", final: true }
        rejected:     { label: "Rejected",      color: "#ef4444", final: true }
    ExpenditureStatus:
      values:
        pending:      { label: "Pending",       color: "#94a3b8" }
        under_review: { label: "Under Review",  color: "#8b5cf6" }
        approved:     { label: "Approved",      color: "#10b981" }
        flagged:      { label: "Flagged",       color: "#f59e0b" }
        processed:    { label: "Processed",     color: "#059669", final: true }
        rejected:     { label: "Rejected",      color: "#ef4444" }
        disallowed:   { label: "Disallowed",    color: "#7f1d1d", final: true }
    CostType:
      values: [salary, fringe, supplies, equipment, travel, participant, subaward, tuition, other]
    PersonnelRole:
      values: [pi, co_pi, postdoc, grad_student, undergrad, staff, other]
    EffortStatus:
      values:
        committed: { label: "Committed", color: "#3b82f6" }
        adjusted:  { label: "Adjusted",  color: "#f59e0b" }
        certified: { label: "Certified", color: "#10b981", final: true }

  # ── Domain-level compliance policy: prevention, not post-hoc detection ──────
  rules:
    federalTransferThreshold:
      description: >
        2 CFR 200.308: cumulative budget transfers on an award may not exceed
        10% of the TOTAL approved budget or $25,000, whichever is less, without
        sponsor prior approval. Blocks completion of a breaching transfer that
        carries no sponsor approval reference. Basis is the AWARD total — not
        the category budget.
      trigger:
        resource: Transfer
        on: [update]
        fields: [status]
        condition: "$record.status == 'completed' && $record.sponsorApprovalRef == null"
      condition: >
        sum(Transfer, 'amount', awardId == $record.awardId && status == 'completed' && id != $record.id)
          + $record.amount
        > if(findOne(Award, id == $record.awardId).totalAmount * 0.10 < 25000,
             findOne(Award, id == $record.awardId).totalAmount * 0.10,
             25000)
      action: reject
      severity: error
      message: "Cumulative transfers on award {awardNumber} would exceed the federal threshold ({threshold}) — sponsor prior approval required"
      messageData:
        awardNumber: "findOne(Award, id == $record.awardId).awardNumber"
        threshold: >
          if(findOne(Award, id == $record.awardId).totalAmount * 0.10 < 25000,
             findOne(Award, id == $record.awardId).totalAmount * 0.10, 25000)
      regulation: "2 CFR §200.308"
      priority: 100
      enabled: true

    transferThresholdWithApproval:
      description: >
        Evidence trail: a breaching transfer that DOES carry sponsor approval is
        allowed but logged — the rules log entry is the auditor's answer to
        "where is the sponsor approval?"
      trigger:
        resource: Transfer
        on: [update]
        fields: [status]
        condition: "$record.status == 'completed' && $record.sponsorApprovalRef != null"
      condition: >
        sum(Transfer, 'amount', awardId == $record.awardId && status == 'completed' && id != $record.id)
          + $record.amount
        > if(findOne(Award, id == $record.awardId).totalAmount * 0.10 < 25000,
             findOne(Award, id == $record.awardId).totalAmount * 0.10,
             25000)
      action: log
      severity: info
      message: "Threshold exceeded with sponsor approval on file ({sponsorApprovalRef})"
      messageData:
        sponsorApprovalRef: "$record.sponsorApprovalRef"
      regulation: "2 CFR §200.308"
      priority: 110
      enabled: true

    effortOvercommitment:
      description: >
        2 CFR 200.430: a person's committed effort across ALL awards cannot
        exceed 100%. This is the check every SPO performs by spreadsheet today.
      trigger:
        resource: Commitment
        on: [create, update]
        fields: [committedPercent]
      condition: >
        sum(Commitment, 'committedPercent',
            personnelId == $record.personnelId && id != $record.id && status != 'certified')
          + $record.committedPercent > 100
      action: reject
      severity: error
      message: "Total committed effort for {personnelName} would exceed 100%"
      messageData:
        personnelName: "$record.personnelName"
      regulation: "2 CFR §200.430"
      priority: 100
      enabled: true

  # ── Time-based obligations live here, not in fieldChange events ─────────────
  schedules:
    awardEnding:
      description: "Closeout early warning at 90/60/30 days before award end"
      type: relative
      source:
        resource: Award
        filter: "status == 'active'"
      relativeTo: endDate
      tiers:
        - daysUntil: 90
          action: notify
          tag: 90day
          channels: [email]
          recipients: { roles: [spoOfficer] }
          template:
            subject: "Award {awardNumber} ends in 90 days"
            body: "'{title}' ({sponsor}) ends {endDate}. Begin closeout planning or NCE discussion."
        - daysUntil: 60
          action: notify
          tag: 60day
          channels: [email]
          recipients: { roles: [spoOfficer] }
          template:
            subject: "Award {awardNumber} ends in 60 days"
            body: "'{title}' ends {endDate}."
        - daysUntil: 30
          action: notify
          tag: 30day
          channels: [email]
          recipients: { roles: [spoOfficer, grantAccountant] }
          template:
            subject: "Award {awardNumber} ends in 30 days — final expenditure review"
            body: "'{title}' ends {endDate}. Outstanding encumbrances must clear before the FFR."

  resources:
    # ─────────────────────────────────────────────────────────────────────────
    Award:
      object:
        id:               { type: uuid, primaryKey: true }
        awardNumber:      { type: string, required: true, unique: true, immutable: true, maxLength: 40 }
        title:            { type: string, required: true, maxLength: 255 }
        sponsor:          { type: string, required: true, maxLength: 120 }
        sponsorAwardId:   { type: string, maxLength: 60 }
        piId:             { type: uuid, required: true }
        piName:           { type: string, required: true, maxLength: 120 }
        department:       { type: string, required: true, maxLength: 120 }
        status:           { $ref: '#/enums/AwardStatus', default: pending }
        awardType:        { $ref: '#/enums/AwardType' }
        totalAmount:      { type: money, required: true, min: 0 }
        startDate:        { type: date, required: true }
        endDate:          { type: date, required: true }
        cfda:             { type: string, maxLength: 20 }
        fain:             { type: string, maxLength: 40 }
        indirectCostRate: { type: percentage }
        costShareRequired: { type: boolean, default: false }
        costShareAmount:  { type: money }
        accountNumber:    { type: string, maxLength: 40 }
        proposalId:       { type: uuid }
        nceJustification: { type: text }
        finalFFRDate:     { type: date }
        activatedAt:      { type: datetime }
        closedAt:         { type: datetime }
        notes:            { type: text }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        categories:   { type: hasMany, target: Category,    foreignKey: awardId }
        transfers:    { type: hasMany, target: Transfer,    foreignKey: awardId }
        expenditures: { type: hasMany, target: Expenditure, foreignKey: awardId }
        commitments:  { type: hasMany, target: Commitment,  foreignKey: awardId }

      indexes:
        - { fields: [status] }
        - { fields: [piId] }
        - { fields: [department] }
        - { fields: [endDate] }

      stateMachine:
        field: status
        initial: pending
        states:
          pending:
            transitions:
              activate:
                to: active
                guards: [spoOfficer, admin]
                requiredFields: [accountNumber]
                sets:
                  activatedAt: "now()"
          active:
            transitions:
              suspend:
                to: suspended
                guards: [spoOfficer, admin]
              closeout:
                to: in_closeout
                guards: [spoOfficer, admin]
              requestNCE:
                to: nce_pending
                guards: [owner, spoOfficer, admin]
                requiredFields: [nceJustification]
          nce_pending:
            transitions:
              approveNCE:
                to: active
                guards: [spoOfficer, admin]
                # The approver supplies the extended endDate in the transition
                # body; extra fields are applied with the transition, so the new
                # date lands on endDate directly and the extension takes effect.
              denyNCE:
                to: active
                guards: [spoOfficer, admin]
          suspended:
            transitions:
              reactivate:
                to: active
                guards: [spoOfficer, admin]
              terminate:
                to: terminated
                guards: [admin]
          in_closeout:
            transitions:
              complete:
                to: closed
                guards: [spoOfficer, admin]
                requiredFields: [finalFFRDate]
                sets:
                  closedAt: "now()"
          closed:
            final: true
          terminated:
            final: true

      validation:
        periodSane:
          rule: "endDate == null || startDate == null || endDate > startDate"
          message: "Award end date must be after the start date"
        costShareNeedsAmount:
          rule: "costShareRequired == false || costShareAmount != null"
          message: "Cost sharing is required but no committed amount is recorded"

      features:
        audit: true
        search: true
      audit:
        exclude: [updatedAt]

      history:
        events:
          created:
            on: create
            capture: [awardNumber, title, sponsor, piName, totalAmount, startDate, endDate]
            label: "Award {awardNumber} created"
            detail: "'{title}' — {sponsor}, {totalAmount}, {startDate} to {endDate}"
            icon: lucide:file-plus
            severity: info
          activated:
            on: transition
            transition: activate
            capture: [accountNumber, awardNumber]
            label: "Award activated — Account {accountNumber}"
            icon: lucide:check-circle
            severity: success
          nceRequested:
            on: transition
            transition: requestNCE
            capture: [nceJustification, endDate]
            label: "No-cost extension requested"
            detail: "Current end date: {endDate}. Justification: {nceJustification}"
            icon: lucide:calendar-plus
            severity: warning
          nceApproved:
            on: transition
            transition: approveNCE
            capture: [endDate]
            label: "No-cost extension approved — new end date {endDate}"
            icon: lucide:check
            severity: success
          nceDenied:
            on: transition
            transition: denyNCE
            label: "No-cost extension denied"
            icon: lucide:x
            severity: warning
          suspended:
            on: transition
            transition: suspend
            label: "Award suspended"
            icon: lucide:pause-circle
            severity: error
          reactivated:
            on: transition
            transition: reactivate
            label: "Award reactivated"
            icon: lucide:play-circle
            severity: success
          closeoutStarted:
            on: transition
            transition: closeout
            label: "Closeout initiated"
            icon: lucide:archive
            severity: info
          closed:
            on: transition
            transition: complete
            capture: [finalFFRDate]
            label: "Award closed — Final FFR submitted {finalFFRDate}"
            icon: lucide:check-circle-2
            severity: success
          piChanged:
            on: fieldChange
            field: piId
            capture: [piName]
            label: "PI changed to {piName}"
            icon: lucide:user-check
            severity: warning

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [spoOfficer, admin]
        update: [spoOfficer, admin]
        delete: [admin]
        fields:
          accountNumber:    { write: [spoOfficer, admin] }
          totalAmount:      { write: [spoOfficer, admin] }
          indirectCostRate: { write: [spoOfficer, admin] }

    # ─────────────────────────────────────────────────────────────────────────
    Category:
      mode: sheet
      description: >
        A budget line within an award. Encumbered and expended amounts are
        AGGREGATIONS over expenditures — not hand-entered numbers — so the
        available balance is true by construction, not by reconciliation.
        No per-category "federal threshold": 2 CFR 200.308 is award-level and
        enforced by the domain rule.
      object:
        id:             { type: uuid, primaryKey: true }
        awardId:        { type: uuid, required: true, immutable: true }
        category:       { $ref: '#/enums/CategoryType', required: true }
        fiscalYear:     { type: integer, required: true, min: 2000, max: 2100 }
        budgetedAmount: { type: money, required: true, min: 0 }
        notes:          { type: text }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        award:        { type: belongsTo, target: Award, foreignKey: awardId, onDelete: restrict }
        expenditures: { type: hasMany, target: Expenditure, foreignKey: categoryId }

      indexes:
        - { fields: [awardId] }
        - { fields: [category] }
        - { fields: [fiscalYear] }

      computed:
        encumberedAmount:
          type: money
          formula: "sum(Expenditure, 'amount', categoryId == $this.id && status == 'approved')"
          stored: true
          reactive: true
          recomputeOn: [Expenditure.create, Expenditure.update, Expenditure.delete]
        expendedAmount:
          type: money
          formula: "sum(Expenditure, 'amount', categoryId == $this.id && status == 'processed')"
          stored: true
          reactive: true
          recomputeOn: [Expenditure.create, Expenditure.update, Expenditure.delete]
        transfersIn:
          type: money
          formula: "sum(Transfer, 'amount', toCategoryId == $this.id && status == 'completed')"
          stored: true
          reactive: true
          recomputeOn: [Transfer.create, Transfer.update, Transfer.delete]
        transfersOut:
          type: money
          formula: "sum(Transfer, 'amount', fromCategoryId == $this.id && status == 'completed')"
          stored: true
          reactive: true
          recomputeOn: [Transfer.create, Transfer.update, Transfer.delete]
        availableBalance:
          type: money
          formula: "budgetedAmount + transfersIn - transfersOut - encumberedAmount - expendedAmount"
          stored: true
          updateOn: [budgetedAmount, transfersIn, transfersOut, encumberedAmount, expendedAmount]

      dynamicFields:
        enabled: true
        allowTypes: [money, string, text, date]
        maxColumns: 20
        permissions:
          addColumn:    [spoOfficer, admin]
          deleteColumn: [admin]

      features:
        audit: true

      history:
        events:
          allocated:
            on: create
            capture: [category, budgetedAmount, fiscalYear]
            label: "{category} — {budgetedAmount} allocated for FY{fiscalYear}"
            icon: lucide:wallet
            severity: info
          budgetRevised:
            on: fieldChange
            field: budgetedAmount
            capture: [category, budgetedAmount]
            label: "{category} budget revised to {budgetedAmount}"
            icon: lucide:edit
            severity: warning
          balanceWarning:
            on: fieldChange
            field: availableBalance
            condition: "availableBalance < budgetedAmount * 0.10 && availableBalance > 0"
            capture: [category, availableBalance, budgetedAmount]
            label: "{category} — less than 10% balance remaining"
            detail: "Available: {availableBalance} of {budgetedAmount}"
            icon: lucide:alert-triangle
            severity: warning
          overExpended:
            on: fieldChange
            field: availableBalance
            condition: "availableBalance < 0"
            capture: [category, availableBalance, expendedAmount, budgetedAmount]
            label: "OVER-EXPENDITURE — {category}"
            detail: "Expended {expendedAmount} against {budgetedAmount}. Over by {availableBalance}"
            icon: lucide:alert-octagon
            severity: error

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [spoOfficer, admin]
        update: [spoOfficer, grantAccountant, admin]
        delete: [admin]
        fields:
          budgetedAmount: { write: [spoOfficer, admin] }

    # ─────────────────────────────────────────────────────────────────────────
    Transfer:
      description: >
        A budget transfer between two categories of one award. Two-sided by
        construction (from/to are FKs), atomic
        (one record), approval-linked (sponsorApprovalRef), and threshold-gated
        by the domain rule BEFORE completion commits.
      object:
        id:                  { type: uuid, primaryKey: true }
        awardId:             { type: uuid, required: true, immutable: true }
        fromCategoryId:      { type: uuid, required: true, immutable: true }
        toCategoryId:        { type: uuid, required: true, immutable: true }
        amount:              { type: money, required: true, min: 0.01 }
        justification:       { type: text, required: true }
        status:              { $ref: '#/enums/TransferStatus', default: requested }
        sponsorApprovalRef:  { type: string, maxLength: 80 }
        sponsorApprovalDate: { type: date }
        createdById:         { type: string, required: true, immutable: true, maxLength: 100 }
        approvedById:        { type: string, maxLength: 100 }
        rejectionReason:     { type: text }
        completedAt:         { type: datetime }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        award:        { type: belongsTo, target: Award,    foreignKey: awardId,        onDelete: restrict }
        fromCategory: { type: belongsTo, target: Category, foreignKey: fromCategoryId, onDelete: restrict }
        toCategory:   { type: belongsTo, target: Category, foreignKey: toCategoryId,   onDelete: restrict }

      indexes:
        - { fields: [awardId] }
        - { fields: [fromCategoryId] }
        - { fields: [toCategoryId] }
        - { fields: [status] }

      stateMachine:
        field: status
        initial: requested
        states:
          requested:
            transitions:
              review:
                to: under_review
                guards: [grantAccountant, spoOfficer, admin]
          under_review:
            transitions:
              approve:
                to: approved
                # SoD: the approving SPO officer is not the requester.
                guard: "hasRole('spoOfficer') && field('createdById') != userId()"
                sets:
                  approvedById: "$user.id"
              reject:
                to: rejected
                guards: [spoOfficer, admin]
                requiredFields: [rejectionReason]
          approved:
            transitions:
              complete:
                to: completed
                # The federalTransferThreshold rule evaluates here, before the
                # write commits. A breaching transfer without sponsorApprovalRef
                # is rejected at 422 RULE_VIOLATION — prevention, not detection.
                guards: [grantAccountant, admin]
                sets:
                  completedAt: "now()"
          completed:
            final: true
          rejected:
            final: true

      validation:
        distinctCategories:
          rule: "fromCategoryId != toCategoryId"
          message: "A transfer must move funds between two different categories"
        sameAward:
          rule: >
            query('Category', { id: fromCategoryId })[0].awardId == awardId
            && query('Category', { id: toCategoryId })[0].awardId == awardId
          message: "Both categories must belong to the transfer's award"
        approvalRefNeedsDate:
          rule: "sponsorApprovalRef == null || sponsorApprovalDate != null"
          message: "A sponsor approval reference must carry the approval date"

      features:
        audit: true

      history:
        events:
          requested:
            on: create
            capture: [amount, justification]
            label: "Transfer requested — {amount}"
            detail: "{justification}"
            icon: lucide:arrow-right-left
            severity: info
          transferApproved:
            on: transition
            transition: approve
            capture: [amount, sponsorApprovalRef, sponsorApprovalDate]
            label: "Transfer approved"
            detail: "Sponsor approval: {sponsorApprovalRef} ({sponsorApprovalDate})"
            icon: lucide:check
            severity: success
          transferCompleted:
            on: transition
            transition: complete
            capture: [amount, completedAt]
            label: "Transfer completed — {amount}"
            icon: lucide:check-circle
            severity: success
          transferRejected:
            on: transition
            transition: reject
            capture: [rejectionReason]
            label: "Transfer rejected"
            detail: "{rejectionReason}"
            icon: lucide:x-circle
            severity: error

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [grantAccountant, spoOfficer, admin]
        update: [grantAccountant, spoOfficer, admin]
        delete: [admin]
        fields:
          sponsorApprovalRef:  { write: [spoOfficer, admin] }
          sponsorApprovalDate: { write: [spoOfficer, admin] }
          approvedById:        { write: [admin] }   # normally set only via the approve transition

    # ─────────────────────────────────────────────────────────────────────────
    Expenditure:
      object:
        id:               { type: uuid, primaryKey: true }
        awardId:          { type: uuid, required: true, immutable: true }
        categoryId:       { type: uuid, required: true, immutable: true }
        transactionDate:  { type: date, required: true }
        amount:           { type: money, required: true, min: 0.01 }
        vendor:           { type: string, maxLength: 120 }
        description:      { type: text, required: true }
        costType:         { $ref: '#/enums/CostType' }
        status:           { $ref: '#/enums/ExpenditureStatus', default: pending }
        submittedById:    { type: string, required: true, immutable: true, maxLength: 100 }
        submittedByName:  { type: string, required: true, maxLength: 120 }
        purchaseOrderNum: { type: string, maxLength: 40 }
        invoiceNum:       { type: string, maxLength: 40 }
        allowable:        { type: boolean }
        allocable:        { type: boolean }
        reasonable:       { type: boolean }
        flagReason:       { type: text }
        rejectionReason:  { type: text }
        disallowReason:   { type: text }
        processedAt:      { type: datetime }
        notes:            { type: text }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        award:    { type: belongsTo, target: Award,    foreignKey: awardId,    onDelete: restrict }
        category: { type: belongsTo, target: Category, foreignKey: categoryId, onDelete: restrict }

      indexes:
        - { fields: [awardId] }
        - { fields: [categoryId] }
        - { fields: [status] }
        - { fields: [transactionDate] }

      stateMachine:
        field: status
        initial: pending
        states:
          pending:
            transitions:
              review:
                to: under_review
                guards: [grantAccountant, admin]
          under_review:
            transitions:
              approve:
                to: approved
                guards: [spoOfficer, admin]
                requiredFields: [allowable, allocable, reasonable]
              flag:
                to: flagged
                guards: [grantAccountant, spoOfficer, admin]
                requiredFields: [flagReason]
              reject:
                to: rejected
                guards: [spoOfficer, admin]
                requiredFields: [rejectionReason]
          flagged:
            transitions:
              resolve:
                to: under_review
                guards: [grantAccountant, spoOfficer, admin]
              reject:
                to: rejected
                guards: [spoOfficer, admin]
                requiredFields: [rejectionReason]
          approved:
            transitions:
              process:
                to: processed
                guards: [grantAccountant, admin]
                sets:
                  processedAt: "now()"
              disallow:
                to: disallowed
                guards: [spoOfficer, admin]
                requiredFields: [disallowReason]
          processed:
            final: true
          rejected:
            transitions:
              resubmit:
                to: pending
                guards: [owner, admin]
          disallowed:
            final: true

      validation:
        # Period of performance — one of the twelve single-audit areas the
        # paper listed and then never checked.
        withinPeriod:
          rule: >
            transactionDate >= query('Award', { id: awardId })[0].startDate
            && transactionDate <= query('Award', { id: awardId })[0].endDate
          message: "Transaction date is outside the award's period of performance (2 CFR Subpart F audit area)"
        categoryBelongsToAward:
          rule: "query('Category', { id: categoryId })[0].awardId == awardId"
          message: "The budget category must belong to the expenditure's award"
        costTestConsistent:
          rule: "status != 'approved' || (allowable == true && allocable == true && reasonable == true)"
          message: "An approved expenditure must pass all three cost tests (allowable, allocable, reasonable)"

      features:
        audit: true

      history:
        events:
          submitted:
            on: create
            capture: [amount, vendor, description, costType, submittedByName]
            label: "Expenditure submitted — {amount} to {vendor}"
            detail: "{description}. Type: {costType}"
            icon: lucide:receipt
            severity: info
          approved:
            on: transition
            transition: approve
            capture: [allowable, allocable, reasonable]
            label: "Expenditure approved"
            detail: "Allowable: {allowable}, Allocable: {allocable}, Reasonable: {reasonable}"
            icon: lucide:check
            severity: success
          flagged:
            on: transition
            transition: flag
            capture: [flagReason]
            label: "Expenditure flagged"
            detail: "{flagReason}"
            icon: lucide:flag
            severity: warning
          rejected:
            on: transition
            transition: reject
            capture: [rejectionReason]
            label: "Expenditure rejected"
            detail: "{rejectionReason}"
            icon: lucide:x-circle
            severity: error
          processed:
            on: transition
            transition: process
            label: "Payment processed"
            icon: lucide:credit-card
            severity: success
          disallowed:
            on: transition
            transition: disallow
            capture: [disallowReason]
            label: "Cost disallowed"
            detail: "{disallowReason}"
            icon: lucide:ban
            severity: error
          costTestFailed:
            on: fieldChange
            field: allowable
            condition: "allowable == false"
            capture: [amount, description]
            label: "COST TEST FAILURE — not allowable"
            detail: "{amount}: {description}"
            icon: lucide:alert-octagon
            severity: error
            roles: [spoOfficer, grantAccountant, admin]

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [pi, departmentAdmin, grantAccountant, admin]
        update: [grantAccountant, spoOfficer, admin]
        delete: [admin]
        fields:
          allowable:  { write: [spoOfficer, grantAccountant, admin] }
          allocable:  { write: [spoOfficer, grantAccountant, admin] }
          reasonable: { write: [spoOfficer, grantAccountant, admin] }

    # ─────────────────────────────────────────────────────────────────────────
    Commitment:
      description: >
        An effort commitment for one person on one award for one period. The
        cross-award 100% cap is enforced by the effortOvercommitment reject rule.
      object:
        id:               { type: uuid, primaryKey: true }
        awardId:          { type: uuid, required: true, immutable: true }
        personnelId:      { type: uuid, required: true, immutable: true,
                            privacy: { classification: personalData, pii: true, subjectIdentifier: true } }
        personnelName:    { type: string, required: true, maxLength: 120,
                            privacy: { classification: personalData, pii: true } }
        role:             { $ref: '#/enums/PersonnelRole' }
        committedPercent: { type: percentage, required: true, min: 0, max: 100 }
        actualPercent:    { type: percentage, min: 0, max: 100 }
        period:           { type: string, required: true, maxLength: 40 }
        status:           { $ref: '#/enums/EffortStatus', default: committed }
        certifiedById:    { type: string, maxLength: 100 }
        certifiedByName:  { type: string, maxLength: 120 }
        certifiedAt:      { type: datetime }
        $merge: { $ref: '#/objects/Timestamp' }

      relationships:
        award: { type: belongsTo, target: Award, foreignKey: awardId, onDelete: restrict }

      indexes:
        - { fields: [awardId] }
        - { fields: [personnelId] }
        - { fields: [status] }

      computed:
        variance:
          type: percentage
          formula: "if(actualPercent == null, 0, abs(committedPercent - actualPercent))"
          stored: true
          updateOn: [committedPercent, actualPercent]
        varianceExcessive:
          type: boolean
          formula: "variance > 5"
          stored: true
          updateOn: [variance]

      stateMachine:
        field: status
        initial: committed
        states:
          committed:
            transitions:
              adjust:
                to: adjusted
                guards: [pi, departmentAdmin, admin]
                requiredFields: [actualPercent]
          adjusted:
            transitions:
              certify:
                to: certified
                guards: [pi, admin]
                # Certifier identity is captured from the authenticated session,
                # not typed in by the caller.
                sets:
                  certifiedAt: "now()"
                  certifiedById: "$user.id"
                  certifiedByName: "$user.username"
              adjust:
                to: adjusted
                guards: [pi, departmentAdmin, admin]
                requiredFields: [actualPercent]
          certified:
            final: true

      features:
        audit: true

      history:
        events:
          committed:
            on: create
            capture: [personnelName, committedPercent, period, role]
            label: "{personnelName} — {committedPercent}% effort committed"
            detail: "Role: {role}, Period: {period}"
            icon: lucide:user-plus
            severity: info
          adjusted:
            on: transition
            transition: adjust
            capture: [personnelName, actualPercent, committedPercent]
            label: "{personnelName} effort adjusted to {actualPercent}%"
            detail: "Original commitment: {committedPercent}%"
            icon: lucide:edit
            severity: info
          certified:
            on: transition
            transition: certify
            capture: [personnelName, actualPercent, certifiedByName, certifiedAt]
            label: "Effort certified for {personnelName} at {actualPercent}%"
            detail: "Certified by {certifiedByName}"
            icon: lucide:check-circle
            severity: success
          varianceDetected:
            on: fieldChange
            field: varianceExcessive
            condition: "varianceExcessive == true"
            capture: [personnelName, committedPercent, actualPercent, variance]
            label: "EFFORT VARIANCE — {personnelName}"
            detail: "Committed {committedPercent}%, actual {actualPercent}% (variance: {variance}%). Cost transfer may be required per 2 CFR §200.430."
            icon: lucide:alert-triangle
            severity: error

      permissions:
        list:   [authenticated]
        get:    [authenticated]
        create: [spoOfficer, departmentAdmin, admin]
        update: [pi, departmentAdmin, spoOfficer, admin]
        delete: [admin]
        fields:
          actualPercent:   { write: [pi, departmentAdmin, admin] }
          certifiedById:   { write: [] }   # transition-set only
          certifiedByName: { write: [] }
          certifiedAt:     { write: [] }

Audit scenarios

Diagram of the Kalos runtime performing real-time grant compliance and enforcement: it intercepts transaction requests at the runtime level before the database write commits, replacing after-the-fact detective audits with programmatic preventative enforcement. Non-compliant transactions trigger a 422 RULE_VIOLATION. Critical compliance triggers shown are budget and timing boundaries, 2026 cost restrictions, and mandatory identity verification.
Enforcement in practice: because interception happens before the write, a breaching transaction cannot complete. The queries below read the rules log and history streams that record every control that fired — the auditor's evidence that the violation was made impossible, not merely detected.

Single Audit: budget transfer threshold

# Resolve the award
curl -s "$BASE/api/v1/grants/awards?filter[awardNumber]=NSF-2024-1234" \
  -H "Authorization: Bearer $TOKEN" | jq -r '.data.items[0].id'

# Question 1: "Were all transfers within the threshold?"
# The rules log IS the compliance evidence trail (§22). Every evaluation of the
# federalTransferThreshold rule — fired or not — is recorded.
curl -s "$BASE/api/v1/grants/rules/federalTransferThreshold/log?fired=true" \
  -H "Authorization: Bearer $TOKEN"

# Question 2: "Where is the sponsor approval?"
# Breaching-but-approved transfers are logged by the companion rule, and the
# transferApproved history event carries the approval reference and date.
curl -s "$BASE/api/v1/grants/rules/transferThresholdWithApproval/log" \
  -H "Authorization: Bearer $TOKEN"
curl -s "$BASE/api/v1/grants/transfers/history?event=transferApproved" \
  -H "Authorization: Bearer $TOKEN"

# All transfers on the award, with completion status
curl -s "$BASE/api/v1/grants/transfers?filter[awardId]={id}&sort=-createdAt" \
  -H "Authorization: Bearer $TOKEN"

The stronger claim this enables: a transfer that would breach without approval cannot complete. The rule rejects it at 422 RULE_VIOLATION before the write commits. The auditor doesn’t find a detected violation; they find a control that made the violation impossible, plus a log proving the control ran.

Effort certification variance

# Documented history stream params are event / after / before (not since/until)
curl -s "$BASE/api/v1/grants/commitments/history?event=varianceDetected&after=2025-07-01&before=2026-06-30" \
  -H "Authorization: Bearer $TOKEN"

# Overcommitment attempts that were blocked (evidence the 100% cap is enforced)
curl -s "$BASE/api/v1/grants/rules/effortOvercommitment/log?fired=true" \
  -H "Authorization: Bearer $TOKEN"

Expenditure allowability (three-part cost test)

curl -s "$BASE/api/v1/grants/expenditures/history?event=approved" \
  -H "Authorization: Bearer $TOKEN"
curl -s "$BASE/api/v1/grants/expenditures?filter[allowable]=false" \
  -H "Authorization: Bearer $TOKEN"