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:
| Dimension | What It Specifies | Business Context |
|---|---|---|
| Earning | When revenue accrues | Revenue recognition, accounting treatment |
| Receipt | When cash is received | Cash 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)
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)
{
"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:
clause {
amount: base_fee_total
schedule: by_schedule base_fee_earning
payable: by_receipt_schedule base_fee_receipts
}Instance Data:
{
"schedules": {
"base_fee_earning": { "pattern": "straight_line", ... }
},
"receipt_schedules": {
"base_fee_receipts": { "pattern": "equal_periodic_installments", ... }
}
}Rationale:
- Learning phase: Still discovering patterns across departments (Film, Music, Sports, etc.)
- Flexibility: Different departments have unique schedule requirements
- Iteration speed: Easier to evolve JSON Schema than grammar
- Future path: Can promote common patterns to DSL keywords later without breaking existing models
- 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>andby_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:
{
"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:
- Should DSL support iteration syntax for dynamic collections? (
foreach post in social_posts) - Should pattern library handle this? (predefined collection patterns)
- 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:
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:
{
"financial_terms": {
"sequel_fee": null // To be determined
}
}Runtime Behavior:
- Computations skip null amounts (don't cause errors)
- Forecasting reports show "N/A" or "To Be Determined"
- Once negotiated, update instance:
"sequel_fee": 5000000 - All dependent calculations re-evaluate
Amendment Pattern:
- Contract created with option:
sequel_fee: null - If option is exercised, amendment updates amount to actual negotiated value
- 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:
- Simple Clauses - Non-financial terms (text only, no computation)
- 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:
| Kind | Type | Example |
|---|---|---|
| guarantee | earning | Base fee (certain income) |
| guarantee | reimbursement | Per diem (certain expense) |
| guarantee | third_party | Publicist fee (certain service payment) |
| guarantee | in_kind | Wardrobe allowance (certain benefit) |
| contingent | earning | Performance bonus (conditional income) |
| contingent | reimbursement | Additional travel if extra services |
| contingent | third_party | Additional stylist if awards show |
| contingent | in_kind | Extra wardrobe if option exercised |
Grammar Structure
Simple Clauses (non-financial):
clause {
name: exclusivity
kind: simple
description: "Non-compete clause text"
template: """{{ input("/simple_clauses/exclusivity_text") }}"""
}Financial Clauses:
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:
| Field | Required | Purpose |
|---|---|---|
kind | Yes | Certainty: guarantee (certain) or contingent (conditional) |
type | Yes (financial) | Nature: earning, reimbursement, third_party, or in_kind |
amount | Yes (financial) | Variable reference for the financial value |
schedule | Optional | Schedule ID for earning period (typically for earning type) |
payable | Yes (financial) | Receipt schedule ID for when received/paid |
when | Optional | Event 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
event {
name: <identifier>
description: <string>
condition: <boolean_expression>
}Runtime state (managed by system, not authored):
state: "unknown" | "true" | "false"transition_date: <date> | nulltransition_history: [<transition_record>]
Integration with Clauses
Events serve as guards on clauses via when: attribute:
# 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:
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):
{
"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
- Initial: All events start
state: "unknown" - Evaluation: System monitors dependent variables/events
- Transition: When condition becomes true, state changes to
"true"withtransition_date - History: All transitions recorded in
transition_history - 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:
var gross_receipts
var distribution_fee_percentage
var uncapped_costs
var advance_amount_total2. Computations - Build calculation waterfall:
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:
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
metric distributor_fee = gross_receipts * fee_percentage
metric net_to_producer = gross_receipts - distributor_feePattern 2: Sequential Recoupment
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_advancePattern 3: Threshold Escalation
metric base_share = min(gross, threshold) * base_rate
metric escalated_share = max(0, gross - threshold) * escalated_rate
output total_share = base_share + escalated_sharePattern 4: Multi-Party Split
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_shareIntegration with Events
Computed metrics can drive event conditions:
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.
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:
- Intuitive - Mirrors contract language: "For each show, Artist receives..."
- Complete encapsulation - Everything related to one instance grouped together
- Instance-driven - Count determined by JSON collection size (1 to N)
- State tracking - Each generated instance maintains independent state
- 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):
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):
{
"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):
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):
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:
shows[*].gross_box_office # Returns array of all show gross valuesAggregation functions:
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):
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:
| Vertical | Collection | Per-Instance | Aggregate | Versus/Comparison |
|---|---|---|---|---|
| Music Touring | Shows | Show guarantee vs. show percentage | Tour aggregate net | Tour guarantee vs. tour percentage |
| Production | Optional services | Service fee (rate × days) | Sum services | Guaranteed fee vs. services total |
Core pattern elements:
- Collection variable declaration (
var shows) - Collection definition in JSON instance data
- Per-instance optionality (may or may not occur)
- Per-instance value calculation
- Per-instance state tracking (occurred, settled, paid)
- Aggregate calculation across instances
- 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 patternRule 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_amountRule 3: Clause Amount Match
IF clause has receipt_schedule
THEN receipt_schedule.total_amount MUST equal clause.amountRule 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 orderRule 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 existRule 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 SchemaRule 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 uniqueRule 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 fieldsFor complete validation rules, see pattern specification documents.
Summary: Recommended Approach
| Issue | Decision | Implementation |
|---|---|---|
| Schedule patterns | Instance data (JSON Schema) | Pattern types in core specs; JSON Schema validates; DSL references by ID |
| Pattern taxonomy location | Three-tier system | Core spec (authoritative) + JSON Schema (validation) + DSL docs (guidance) |
| Schedule placement | Inline in instance data | schedules and receipt_schedules sections in JSON |
| Layer 1 patterns | Three core time patterns | event_triggered, periodic, straight_line |
| Layer 2 patterns | Two core receipt patterns | equal_periodic_installments, event_installments |
| Variable events (loops) | Deferred | Distinct from Issue 7; needs more contract examples for dynamic occurrences |
| TBD amounts | Use amount: null | Computations skip nulls; reports show "TBD" |
| Clause structure | Two orthogonal dimensions | Kind (guarantee/contingent) × Type (earning/reimbursement/third_party/in_kind) |
| Event system | Three-state condition model | State (unknown/true/false) + transition tracking + condition expressions |
| Event types | Three types unified | Calculated (computed), external (boolean var), compound (event refs) |
| Waterfall computations | Separate computations section | Variables → computations (metric/output) → clauses |
| Recurring clause instances | Use for_each construct | Collection variables; per-instance: events/clauses/blocks in for_each; aggregate: separate events/clauses/blocks with collection operations |
| Terminology | Receipts vs payments | "Receipts" = buyer to talent; "payments" = agent to talent |