Skip to content

Deal Engine DSL Design Decisions v2.4

Document Purpose: This document captures architectural decisions for the Deal Engine's schedule system, focusing on the separation between deal type definitions (DSL models) and deal instances (JSON data). It provides rationale for key design choices to guide contract validation and architecture refinement.

Status: Current architectural specification for validating additional contracts.

Target Audience: Technical stakeholders validating the architecture against real entertainment contracts.

Key Principle: DSL models define deal types using generic variable names. JSON instances provide specific values and schedule configurations for individual deals.

Issue 1: Schedule Specifications

Business Requirements

Terminology Note: In agency deals, "receipts" refers to money flowing from the buyer to the talent, while "payments" refers to money flowing from the agent to the talent. This document uses "receipt" for buyer-to-talent cash flows.

Entertainment contracts involve two distinct temporal dimensions:

DimensionWhat It SpecifiesBusiness Context
EarningWhen revenue accruesRevenue recognition, accounting treatment
ReceiptWhen cash is receivedCash flow, accounts receivable

These dimensions are always separate in agency deals and require different schedule constructs.

Three-Layer Architecture Overview

The Deal Engine separates schedule concerns into three distinct layers:

Layer 1: Time Schedules - Pure timing patterns

  • Define WHEN events occur or periods begin/end
  • No amounts, just temporal logic
  • Reusable across multiple clauses

Layer 2: Receipt Schedules - Amounts + timing references

  • Define WHAT amount is received WHEN
  • Reference Layer 1 time schedules for timing
  • Validate amounts sum to clause total

Layer 3: Clause Structure - Total value + references

  • DSL model defines structure with variables (deal TYPE)
  • JSON instance provides actual values (THIS deal)
  • References Layers 1 and 2 via schedule IDs

Example: Fashion Endorsement Base Fee

Contract language: "$3.1M earned over 3-year term, paid in equal quarterly installments"

Layer 3: DSL Model (Fashion Endorsement deal TYPE)

antlr
var base_fee_total
var effective_date
var term_end_date

clause {
  name: base_compensation
  kind: guarantee
  type: earning
  amount: base_fee_total
  schedule: by_schedule base_fee_earning      # Generic schedule name
  payable: by_receipt_schedule base_fee_receipts  # Generic receipt schedule name
}

Layers 1 & 2: JSON Instance (Fashion deal specifics)

json
{
  "financial_terms": {
    "base_fee_total": 3100000
  },
  "dates": {
    "effective_date": "2022-09-23",
    "term_end_date": "2025-09-23"
  },
  
  // Layer 1: Time schedules (pure timing)
  "schedules": {
    "base_fee_earning": {
      "pattern": "straight_line",
      "start_date": "{{effective_date}}",
      "end_date": "{{term_end_date}}",
      "description": "Continuous earning over 3-year term"
    },
    "quarterly_timing": {
      "pattern": "periodic",
      "frequency": "quarterly",
      "period_count": 12,
      "start_date": "{{effective_date}}"
    }
  },
  
  // Layer 2: Receipt schedules (amounts + timing)
  "receipt_schedules": {
    "base_fee_receipts": {
      "pattern": "equal_periodic_installments",
      "total_amount": 3100000,
      "timing": "quarterly_timing",
      "description": "12 equal quarterly receipts"
    }
  }
}

Result: $3.1M earned continuously over 3 years (straight_line), received in 12 equal quarterly installments of $258,333.33.

Note: Another instance of "Fashion Endorsement Deal" type could define base_fee_receipts as monthly or semi-annual—same DSL model, different instance data.


Design Question 1A: Where Should Schedule Patterns Be Defined?

Decision: ✅ Instance Data (JSON Schema Validation)

Schedule patterns are defined as JSON constructs in instance data, referenced by ID in DSL models.

DSL Syntax:

antlr
clause {
  amount: base_fee_total
  schedule: by_schedule base_fee_earning
  payable: by_receipt_schedule base_fee_receipts
}

Instance Data:

json
{
  "schedules": {
    "base_fee_earning": { "pattern": "straight_line", ... }
  },
  "receipt_schedules": {
    "base_fee_receipts": { "pattern": "equal_periodic_installments", ... }
  }
}

Rationale:

  1. Learning phase: Still discovering patterns across departments (Film, Music, Sports, etc.)
  2. Flexibility: Different departments have unique schedule requirements
  3. Iteration speed: Easier to evolve JSON Schema than grammar
  4. Future path: Can promote common patterns to DSL keywords later without breaking existing models
  5. Separation of concerns: Deal types define structure; deal instances provide specifics

Implementation:

  • Time patterns: schedule-patterns-spec-v0.4.md
  • Receipt patterns: receipt-schedule-patterns-v0.5.md
  • Validation via JSON Schema at instance creation
  • DSL uses simple reference syntax: by_schedule <id> and by_receipt_schedule <id>

Design Question 1B: Inline vs Separate Schedule Blocks?

Decision: ✅ Inline schedules in instance data

Schedules are defined directly in the instance JSON's schedules and receipt_schedules sections, not as separate top-level blocks in the DSL.

Rationale:

  • Schedules are specific to deal instances, not deal types
  • Keeps instance data self-contained
  • Simpler mental model: all temporal logic in JSON, structural logic in DSL
  • DSL remains focused on deal type structure and composition

Design Question 1C: Where Should Canonical Pattern Taxonomy Live?

Decision: ✅ Three-tier specification system

Tier 1: Core Specification (Authoritative)

Location:

  • schedule-patterns-spec-v0.4.md (time patterns)
  • /receipt-schedule-patterns-v0.5.md (receipt patterns)

Purpose: Single source of truth defining:

  • All pattern types for both layers
  • Required/optional fields per pattern
  • Validation rules
  • Semantic descriptions
  • Usage contexts

Tier 2: JSON Schema Validation (Runtime)

Referenced in each deal type's JSON Schema for instance validation.

Example:

json
{
  "schedules": {
    "$ref": "https://schemas.uta.com/common/schedule-patterns/1.0.0.json"
  },
  "receipt_schedules": {
    "$ref": "https://schemas.uta.com/common/receipt-schedule-patterns/1.0.0.json"
  }
}

Tier 3: DSL Documentation (Author Guidance)

Grammar comments or companion markdown providing inline help.

Benefits:

  • Core specs versioned independently
  • JSON Schema enforces validation at runtime
  • DSL authors have inline guidance
  • Single source of truth prevents divergence between layers

Issue 2: Variable Event Counts ("Loop" Problem)

Business Context

Status: ⏸️ DEFERRED - Distinct from Issue 7 (Recurring Clause Instances)

Some deals involve dynamic occurrence counts that emerge during execution rather than being pre-defined in the contract:

Social posts: "Up to 10 optional posts at talent's discretion" - contract defines potential, actual count emerges as posts are published

Overage weeks: "Additional weeks beyond 10-week guarantee" - unknown until production wraps

Performance appearances: "Up to 5 promotional events if requested" - buyer exercises option over time

Key distinction from Issue 7:

  • Issue 7 (Recurring Clause Instances): Collection exists in JSON at deal creation (shows: [...])
  • Issue 2 (Variable Event Counts): Occurrences emerge during deal lifecycle

Current approach: Use programmatic generation or instance data updates as events occur.

Questions under exploration:

  1. Should DSL support iteration syntax for dynamic collections? (foreach post in social_posts)
  2. Should pattern library handle this? (predefined collection patterns)
  3. Sufficient to define via instance data amendments as events occur?

Blocked on: Need 3-5 additional contracts with variable event patterns across departments to determine:

  • How common is this pattern?
  • Are there consistent sub-patterns?
  • Does dynamic iteration warrant DSL syntax or remain instance generation?

Issue 3: To-Be-Determined (TBD) Amounts

Business Context

Contracts often include terms with amounts determined later:

  • Contract options: "$TBD salary if option exercised"
  • Backend participation: Percentage of profits (profit amount unknown upfront)
  • Performance bonuses: Threshold triggers defined, amounts negotiated later

Decision

✅ Use amount: null in variables

DSL Example:

antlr
var sequel_fee  # Value undefined at contract creation

clause {
  name: sequel_option
  kind: contingent
  type: earning
  amount: sequel_fee  # References variable with null value
  when: sequel_option_exercised
  payable: on sequel_payment_date
}

Instance Data:

json
{
  "financial_terms": {
    "sequel_fee": null  // To be determined
  }
}

Runtime Behavior:

  1. Computations skip null amounts (don't cause errors)
  2. Forecasting reports show "N/A" or "To Be Determined"
  3. Once negotiated, update instance: "sequel_fee": 5000000
  4. All dependent calculations re-evaluate

Amendment Pattern:

  1. Contract created with option: sequel_fee: null
  2. If option is exercised, amendment updates amount to actual negotiated value
  3. Forecasting reports null as "N/A" or "To Be Determined"

Issue 4: Clause Structure - Two Orthogonal Dimensions

Key Insight from Team Review

"Receipts are an attribute of earnings" - receipt timing is a property of financial value, not a parallel concept.

All clauses fall into one of two categories:

  1. Simple Clauses - Non-financial terms (text only, no computation)
  2. Financial Clauses - Have two independent dimensions:

Financial Clause Dimensions

Dimension 1: Kind (Certainty of obligation)

  • guarantee - Certain payment if conditions met (committed for forecasting)
  • contingent - Conditional on event occurrence (possible for forecasting)

Dimension 2: Type (Nature of value)

  • earning - Earned income (commissionable, revenue recognition)
  • reimbursement - Expense reimbursement (not commissioned, expense treatment)
  • third_party - Payment to service provider (not AR, different payee)
  • in_kind - Non-cash benefit (not cash flow, separate tracking)

All combinations are valid:

KindTypeExample
guaranteeearningBase fee (certain income)
guaranteereimbursementPer diem (certain expense)
guaranteethird_partyPublicist fee (certain service payment)
guaranteein_kindWardrobe allowance (certain benefit)
contingentearningPerformance bonus (conditional income)
contingentreimbursementAdditional travel if extra services
contingentthird_partyAdditional stylist if awards show
contingentin_kindExtra wardrobe if option exercised

Grammar Structure

Simple Clauses (non-financial):

antlr
clause {
  name: exclusivity
  kind: simple
  description: "Non-compete clause text"
  template: """{{ input("/simple_clauses/exclusivity_text") }}"""
}

Financial Clauses:

antlr
clause {
  name: base_compensation
  kind: guarantee              // or contingent
  type: earning                // or reimbursement, third_party, in_kind
  description: "Base fee over term"
  amount: base_fee_total       // Variable reference
  schedule: by_schedule base_fee_earning      // Layer 1 (earning period)
  payable: by_receipt_schedule base_fee_receipts  // Layer 2 (receipt timing)
  template: """..."""
}

Key attributes:

FieldRequiredPurpose
kindYesCertainty: guarantee (certain) or contingent (conditional)
typeYes (financial)Nature: earning, reimbursement, third_party, or in_kind
amountYes (financial)Variable reference for the financial value
scheduleOptionalSchedule ID for earning period (typically for earning type)
payableYes (financial)Receipt schedule ID for when received/paid
whenOptionalEvent guard (typically for contingent clauses)

Schedule Usage by Clause Type

Earning clauses - Both schedules typically needed:

  • schedule: When revenue is earned (accounting/revenue recognition)
  • payable: When cash is received (AR/cash flow)

Reimbursement clauses - Usually only payable:

  • No schedule: (reimbursed when incurred, not accrued)
  • payable: When reimbursement is received

Third-party clauses - Usually only payable:

  • No schedule: (not talent's revenue, no accrual needed)
  • payable: When buyer pays service provider

In-kind clauses - May have schedule or just availability:

  • schedule: Optional (could track annual allocation)
  • payable: Describes when/how benefit is accessed

Issue 5: Event System and State Management

Design Rationale

Decision: ✅ Three-state event model with condition-based evaluation

All events in the Deal Engine track state (unknown, true, false) and use condition expressions to determine truth value. This unified approach handles both calculated events (threshold-based) and external events (business milestones).

Event Types

Type 1: Calculated Events

  • Condition uses computational expressions
  • State derived from evaluating condition against data
  • Example: condition: worldwide_box_office >= box_office_threshold

Type 2: External Events

  • Condition references boolean variable set by external system
  • State updated via API/user action
  • Example: condition: production_services_completed

Type 3: Compound Events

  • Condition references other events
  • Enables hierarchical event logic
  • Example: condition: picture_released && adjusted_gross_proceeds_positive

Complete specification: event-syntax-specification-v1_0.md

Grammar Structure

antlr
event {
  name: <identifier>
  description: <string>
  condition: <boolean_expression>
}

Runtime state (managed by system, not authored):

  • state: "unknown" | "true" | "false"
  • transition_date: <date> | null
  • transition_history: [<transition_record>]

Integration with Clauses

Events serve as guards on clauses via when: attribute:

antlr
# Simple event guard
clause {
  name: backend_participation
  kind: contingent
  type: earning
  when: backend_payment_eligible
  amount: backend_share
  payable: by_receipt_schedule backend_receipts
}

# Compound event guard (inline)
clause {
  name: theatrical_bonus
  kind: contingent
  type: earning
  when: picture_released && !svod_exclusive_release
  amount: 500000
  payable: on theatrical_release_date
}

Key capability: when: supports compound conditions, enabling complex business rules without creating separate event definitions for every combination.

Integration with Schedules

Events can trigger schedule timing:

DSL Model:

antlr
var production_services_completed  # Boolean variable

event {
  name: production_completed
  description: "All required services delivered"
  condition: production_services_completed
}

clause {
  name: overage_compensation
  kind: guarantee
  type: earning
  when: production_completed
  amount: overage_weeks * weekly_rate
  payable: by_receipt_schedule overage_payment
}

Instance Data (Layer 2):

json
{
  "receipt_schedules": {
    "overage_payment": {
      "pattern": "event_installments",
      "timing_reference": "production_completed",  // References event
      "installments": [
        {
          "trigger": "production_completed",
          "relative_timing": "+7 days",
          "amount": "full"
        }
      ]
    }
  }
}

Result: Payment scheduled 7 days after production_completed event transitions to true.

State Transition Flow

  1. Initial: All events start state: "unknown"
  2. Evaluation: System monitors dependent variables/events
  3. Transition: When condition becomes true, state changes to "true" with transition_date
  4. History: All transitions recorded in transition_history
  5. Publication: State changes published to event stream for downstream systems

Architectural principle: Deal Engine publishes state changes; consuming systems (accounting, workflow, notifications) decide how to respond.


Issue 6: Waterfall Computations

Design Rationale

Decision: ✅ Separate computations section in DSL

Complex revenue calculations (sequential deductions, multi-party splits, conditional allocations) are modeled using a dedicated computations section that sits between variables and clauses.

Three-Section Architecture

1. Variables - Define inputs:

antlr
var gross_receipts
var distribution_fee_percentage
var uncapped_costs
var advance_amount_total

2. Computations - Build calculation waterfall:

antlr
computations {
  metric distributor_fee = gross_receipts * distribution_fee_percentage
  metric advance_recoupment_balance = min(
    cumulative(gross_receipts - distributor_fee - uncapped_costs),
    advance_amount_total
  )
  output net_receipts = gross_receipts - distributor_fee - uncapped_costs - advance_recoupment_balance
}

3. Clauses - Reference computed values:

antlr
clause {
  name: net_receipts
  kind: contingent
  type: earning
  amount: net_receipts  # References computed output
  when: advance_recouped
  payable: by_receipt_schedule statement_payments
}

Rationale:

  • Separation of concerns: Variables name inputs, computations define logic, clauses use results
  • Sequential flow: Each metric can reference prior metrics, creating waterfall
  • Maintainability: Complex calculations explicit and auditable
  • Reusability: Computed metrics can be referenced by multiple clauses

Common Waterfall Patterns

Pattern 1: Distributor Fee

antlr
metric distributor_fee = gross_receipts * fee_percentage
metric net_to_producer = gross_receipts - distributor_fee

Pattern 2: Sequential Recoupment

antlr
metric after_capped_costs = gross - capped_costs
metric after_uncapped_costs = after_capped_costs - uncapped_costs
metric after_advance = after_uncapped_costs - advance_recoupment
output net = after_advance

Pattern 3: Threshold Escalation

antlr
metric base_share = min(gross, threshold) * base_rate
metric escalated_share = max(0, gross - threshold) * escalated_rate
output total_share = base_share + escalated_share

Pattern 4: Multi-Party Split

antlr
metric net_after_fees = gross - fees - costs
metric party_a_share = net_after_fees * party_a_percentage
metric party_b_share = net_after_fees * party_b_percentage
output party_c_share = net_after_fees - party_a_share - party_b_share

Integration with Events

Computed metrics can drive event conditions:

antlr
computations {
  metric cumulative_recoupment = cumulative(gross_receipts - distributor_fee - costs)
}

event {
  name: advance_recouped
  description: "Distribution advance fully recouped"
  condition: cumulative_recoupment >= advance_amount_total
}

clause {
  name: net_receipts
  kind: contingent
  type: earning
  when: advance_recouped  # Clause activates when computed threshold reached
  amount: net_receipts
  payable: by_receipt_schedule statement_payments
}

Flow: Variable updates → Computation evaluation → Event state transition → Clause activation

Example: Distribution Deal Waterfall

Business logic: Distributor receives license fees, keeps percentage as fee, recoups costs and advance, then pays net receipts to producer.

antlr
model { deal_type: "Distribution Deal", version: 1.0.0 }

# ========== VARIABLES ==========
var advance_amount_total
var distributor_fee_percentage
var gross_receipts
var uncapped_costs

# ========== COMPUTATIONS ==========
computations {
  # Step 1: Calculate distributor's fee
  metric distributor_fee = gross_receipts * distributor_fee_percentage
  
  # Step 2: Track advance recoupment (cumulative, capped at advance)
  metric advance_recoupment_balance = min(
    cumulative(gross_receipts - distributor_fee - uncapped_costs),
    advance_amount_total
  )
  
  # Step 3: Calculate net to producer (after waterfall)
  output net_receipts = max(0,
    gross_receipts - distributor_fee - uncapped_costs - advance_recoupment_balance
  )
}

# ========== EVENTS ==========
event {
  name: advance_recouped
  description: "Distribution advance fully recouped from gross receipts"
  condition: advance_recoupment_balance >= advance_amount_total
}

# ========== CLAUSES ==========
clause {
  name: guaranteed_advance
  kind: guarantee
  type: earning
  amount: advance_amount_total
  schedule: by_schedule advance_earning
  payable: by_receipt_schedule advance_payments
}

clause {
  name: net_receipts
  kind: contingent
  type: earning
  when: advance_recouped  # Guard: only activates after advance recouped
  amount: net_receipts     # References computed value
  schedule: by_schedule earnings_per_statement
  payable: by_receipt_schedule statement_payments
}

Waterfall visualization:

Gross Receipts

    ├─ Distributor Fee (%)       → Distributor keeps

    ├─ Uncapped Costs            → Distributor recoups

    ├─ Advance Recoupment        → Distributor recoups (capped)

    └─ Net Receipts              → Producer receives (once advance recouped)

Issue 7: Recurring Clause Instances (Collection-Based Structures)

Business Context

Entertainment contracts frequently involve financial structures that repeat across multiple occurrences with the same template logic but different instance data:

Music touring: One deal covers N shows, each with identical compensation structure (guarantee vs. percentage) but varying ticket sales, expenses, and settlement dates.

Production deals: One contract includes N optional services, each potentially triggered with service-specific rates and dates.

Fundamental pattern: One template definition → N runtime instances, where:

  • Count (N) is unknown at modeling time
  • Each instance has its own state (occurred, settled, paid)
  • Each instance requires individual tracking for statements and verification
  • Instances aggregate for final compensation calculations
  • Cross-instance dependencies exist (tour-level versus, territory recoupment)

Design Decision

Decision: ✅ Introduce for_each iteration construct

Rationale:

  1. Intuitive - Mirrors contract language: "For each show, Artist receives..."
  2. Complete encapsulation - Everything related to one instance grouped together
  3. Instance-driven - Count determined by JSON collection size (1 to N)
  4. State tracking - Each generated instance maintains independent state
  5. Reusable - Same DSL model works for 1-show or 100-show deals

Pattern Structure

All recurring clause scenarios share three structural levels:

Level 1: Per-Instance (repeating)

  • Events: Has this instance occurred? Been settled?
  • Clauses: Instance-specific earning amounts
  • Blocks: Instance-level composition (e.g., per-show versus)

Level 2: Aggregate Computations

  • Metrics: Sum revenues, expenses, nets across instances
  • Derived values: Calculate aggregate shares, thresholds
  • Outputs: Total compensation components

Level 3: Aggregate Financial Structure

  • Events: All instances complete? All settled?
  • Clauses: Aggregate-level earnings (tour-level versus sides)
  • Blocks: Aggregate composition (tour-level versus, final settlement)

Collection Variables

Collections are declared as variables that reference arrays in JSON instance data.

DSL Model (Deal Type):

antlr
model { deal_type: "Music Touring Deal", version: 1.0.0 }

var shows  # References collection in JSON instance data

for_each show in shows {
  event {
    name: show_occurred_{show.id}
    condition: show.occurred == true
  }
  
  clause {
    name: show_guarantee_{show.id}
    amount: show.guarantee
    when: show_occurred_{show.id}
  }
}

JSON Instance (Specific Deal):

json
{
  "shows": [
    {
      "show_id": "show_01",
      "date": "2022-09-02",
      "venue": "Major Venue",
      "gross_box_office": 619737,
      "total_expenses": 333883,
      "occurred": true,
      "settled": true
    }
  ]
}

Syntax Examples

Per-instance structure (everything in one for_each block):

antlr
for_each show in shows {
  event {
    name: show_settled_{show.id}
    condition: show.settled == true
  }
  
  clause {
    name: show_guarantee_{show.id}
    kind: guarantee
    type: earning
    amount: show.guarantee
    when: show_occurred_{show.id}
  }
  
  clause {
    name: show_percentage_{show.id}
    kind: contingent
    type: earning
    amount: (show.gross_box_office - show.total_expenses) * artist_percentage
    when: show_settled_{show.id}
  }
  
  block {
    name: show_compensation_{show.id}
    expr: show_guarantee_{show.id} versus show_percentage_{show.id}
  }
}

Aggregate-level structure (separate from for_each):

antlr
event {
  name: tour_settled
  description: "All shows settled, final versus ready"
  condition: count(shows where show.settled == true) == count(shows)
}

computations {
  metric tour_aggregate_net = sum(shows[*].gross_box_office - shows[*].total_expenses)
  metric artist_aggregate_share = tour_aggregate_net * artist_percentage
}

clause {
  name: tour_percentage_clause
  kind: contingent
  type: earning
  amount: artist_aggregate_share
  when: tour_settled
}

block {
  name: tour_versus
  expr: tour_guarantee_clause versus tour_percentage_clause
  when: tour_settled
}

Collection Operations

Access all values of a field:

antlr
shows[*].gross_box_office  # Returns array of all show gross values

Aggregation functions:

antlr
metric total_gross = sum(shows[*].gross_box_office)
metric show_count = count(shows)
metric max_gross = max(shows[*].gross_box_office)

Conditional aggregation (with where clause):

antlr
metric settled_count = count(shows where show.settled == true)
metric occurred_gross = sum(shows[*].gross_box_office where show.occurred == true)

Settlement: Per-Instance vs. Aggregate

Per-Instance Settlement:

  • When: After each occurrence (show, game, service delivery)
  • What: Verify actual results vs. estimates
  • Output: Instance-level statement signed by parties

Aggregate Settlement:

  • When: After all instances complete
  • What: Calculate deal-level totals, apply deal-level formulas (versus, recoupment)
  • Output: Deal-level statement

Integration with Event System (Issue 5):

  • Per-instance events: Each occurrence has state (show_settled_{show.id})
  • Aggregate events: Depend on collection state (tour_settled)
  • Event conditions can reference collection: count(shows where ...)

Pattern Generalization

The for_each pattern applies across entertainment verticals:

VerticalCollectionPer-InstanceAggregateVersus/Comparison
Music TouringShowsShow guarantee vs. show percentageTour aggregate netTour guarantee vs. tour percentage
ProductionOptional servicesService fee (rate × days)Sum servicesGuaranteed fee vs. services total

Core pattern elements:

  1. Collection variable declaration (var shows)
  2. Collection definition in JSON instance data
  3. Per-instance optionality (may or may not occur)
  4. Per-instance value calculation
  5. Per-instance state tracking (occurred, settled, paid)
  6. Aggregate calculation across instances
  7. Aggregate comparison (versus, threshold)

Relationship to Variable Event Counts (Issue 2)

Important distinction:

  • Issue 7 solves: Pre-defined collections where N items exist in JSON at deal creation
  • Issue 2 remains open: Dynamic occurrences that emerge during execution (optional social posts, overage weeks)

Key difference:

  • Issue 7: Collection exists in JSON upfront (shows: [...])
  • Issue 2: Occurrences emerge during deal lifecycle

Issue 7 and Issue 2 are complementary patterns addressing different scenarios.


Validation Rules

Cross-Layer Validation

Rule 1: Receipt Schedule Reference Validity

IF clause.payable: by_receipt_schedule <id>
  AND receipt_schedules[id].timing references time_schedule_id
THEN schedules[time_schedule_id] MUST exist
  AND schedules[time_schedule_id].pattern MUST be appropriate for receipt pattern

Rule 2: Total Amount Match (Receipt Schedules)

IF receipt_schedule.pattern == equal_periodic_installments
THEN (total_amount / period_count) × period_count MUST equal total_amount
  (accounting for rounding in final installment)

IF receipt_schedule.pattern == event_installments AND amount_method == "percentage"
THEN sum(percentages) MUST equal 100

IF receipt_schedule.pattern == event_installments AND amount_method == "explicit"
THEN sum(installments[].amount) MUST equal total_amount

Rule 3: Clause Amount Match

IF clause has receipt_schedule
THEN receipt_schedule.total_amount MUST equal clause.amount

Rule 4: Event Dependencies

IF event.condition references other events
THEN no circular dependencies allowed
  AND all referenced events must exist
  AND topological sort must be possible for evaluation order

Rule 5: Computation References

IF clause.amount references computed metric/output
THEN metric/output must be defined in computations section
  AND all variables referenced in computation must exist

Rule 6: Collection Variable Declaration (Issue 7)

IF for_each references collection
THEN collection must be declared as variable in variables section
  AND variable must resolve to array in JSON Schema

Rule 7: Instance Naming Uniqueness (Issue 7)

IF for_each generates construct with name pattern {construct}_{item.id}
THEN all item.id values in collection must be unique

Rule 8: Collection State Consistency (Issue 7)

IF aggregate event depends on collection state
  (e.g., count(shows where show.settled == true) == count(shows))
THEN all collection items must have required state fields

For complete validation rules, see pattern specification documents.

IssueDecisionImplementation
Schedule patternsInstance data (JSON Schema)Pattern types in core specs; JSON Schema validates; DSL references by ID
Pattern taxonomy locationThree-tier systemCore spec (authoritative) + JSON Schema (validation) + DSL docs (guidance)
Schedule placementInline in instance dataschedules and receipt_schedules sections in JSON
Layer 1 patternsThree core time patternsevent_triggered, periodic, straight_line
Layer 2 patternsTwo core receipt patternsequal_periodic_installments, event_installments
Variable events (loops)DeferredDistinct from Issue 7; needs more contract examples for dynamic occurrences
TBD amountsUse amount: nullComputations skip nulls; reports show "TBD"
Clause structureTwo orthogonal dimensionsKind (guarantee/contingent) × Type (earning/reimbursement/third_party/in_kind)
Event systemThree-state condition modelState (unknown/true/false) + transition tracking + condition expressions
Event typesThree types unifiedCalculated (computed), external (boolean var), compound (event refs)
Waterfall computationsSeparate computations sectionVariables → computations (metric/output) → clauses
Recurring clause instancesUse for_each constructCollection variables; per-instance: events/clauses/blocks in for_each; aggregate: separate events/clauses/blocks with collection operations
TerminologyReceipts vs payments"Receipts" = buyer to talent; "payments" = agent to talent

Confidential. For internal use only.