Contracts & Packs
Streams are the raw building block; contracts are how models stay readable.
Streams vs contracts
| Situation | Use |
|---|---|
| Formal agreement with another party (lease, loan, PPA) | Contract |
| Individual expense/revenue line items | Stream |
| If in doubt | Start with a stream |
A stream is explicit about everything:
stream ops.revenue on entity operating.company inflow currency USD {
schedule every monthly from 2026-01 to 2027-12
amount = 30000
}A contract declares business terms and lets the pack's templates expand them into streams at compile time:
use pack "cre" version "0.1.0"
contract cre.lease on entity asset.tower {
term 2026-07..2031-12
terms {
base_rent = 25000
}
}What the pack does with it
At compile time the pack's lowering rules fill in schedule and amount
expressions from the terms (with validated defaults), producing ordinary
streams in the IR — there is no runtime magic, and cfdl compile output
shows exactly what was generated. Missing or malformed terms fail
compilation with named diagnostics.
Instances
Templates support instances via a dotted id — each gets its own streams:
contract cre.lease_unit.tenant_a on entity asset.tower { ... }
contract cre.lease_unit.tenant_b on entity asset.tower { ... }Migrating a hand-built model onto a pack
- Keep the timeline and entities unchanged.
- Add
use pack "<id>" version "<ver>". - Replace stream groups with the equivalent contract, one at a time.
- Compile with
--packsand resolve diagnostics. - Diff the IR/results and confirm the deltas are intended.
Reference links
- Domain Packs overview and the four pack guides
- Pack Interface spec