CFDL Diagnostics Specification v0.1
Status: Draft
This document defines the canonical diagnostics format, conventions, and error-code taxonomy for CFDL tooling:
- Rust compiler/CLI
- TypeScript editor integration
- Python notebook tooling
Diagnostics must be stable, machine-readable, and suitable for:
- inline editor annotations
- CI test assertions (golden diagnostics)
- user-friendly CLI output
1) Goals
- Actionable: diagnostics should tell the user what happened, where, and what to do.
- Stable codes: error codes must be stable across versions (with deprecation policy).
- Precise locations: diagnostics should include file + span (line/col).
- Non-duplicative: avoid flooding the user with cascading errors; prefer root-cause.
- Composable: same schema for parser, validator, pack validation, and lowering.
2) Diagnostic object (canonical)
2.1 JSON schema (informative)
Tooling SHOULD represent diagnostics in this JSON shape:
{
"code": "E2103_SCHEDULE_OUT_OF_BOUNDS",
"severity": "error",
"message": "Schedule occurrence 2032-01-31 is outside the model timeline (ends 2031-12-31).",
"file": "behavior.cfdl",
"span": { "start_line": 18, "start_col": 7, "end_line": 18, "end_col": 64 },
"path": "contracts[L1].effects.streams[rent].schedule",
"hint": "Update the schedule 'to' date or extend the model time horizon.",
"notes": ["Model timeline: monthly from 2026-01-01 for 72 periods."],
"related": [
{
"message": "Timeline defined here.",
"file": "time.cfdl",
"span": { "start_line": 1, "start_col": 1, "end_line": 1, "end_col": 44 }
}
]
}2.2 Required fields
A diagnostic MUST include:
code(string)severity(enum)message(string)
A diagnostic SHOULD include:
file+spanfor any error tied to source location
2.3 Field definitions
code: stable code (see §6)severity: one oferror,warning,infomessage: concise, user-facing descriptionfile: relative path within model root when applicablespan: source span (1-based line/col)path: optional machine path to IR/AST node for toolinghint: optional “how to fix it” guidancenotes: optional list of additional context linesrelated: optional list of secondary locations
2.4 Span definition
span MUST be:
start_line,start_col,end_line,end_col(all integers ≥ 1)- inclusive start; inclusive end
3) Severity semantics
3.1 error
- Indicates the model cannot be compiled to IR.
- The compiler MUST fail compilation if any
errordiagnostics exist.
3.2 warning
- Indicates a potential issue, ambiguity, or best-practice violation.
- Compilation MAY proceed.
- A warning a successful compile raises is KEPT: it rides in the IR
(
warnings), the CLI prints it, the MCPcompilereturns it besideok: true, and the engine republishes it in the results'warnings, so a run that started from a questioned model says so. A pack states a convention this way —severity = "warning"on a validation, under aWcode with the pack's digit. - A warning cannot be silenced: the language has no allowlist, and the
benchmark harness fails any run that carries one. So a warning is drawn
to fire on the mistake and never on the intended model, which always has a
spelling that does not trigger it — an interest-only strip is a type that
does not allocate principal, not a note missing a step (
W1389). A pack convention is the one exception: a value that is meant and outside the convention keeps its warning, which says what was questioned.
3.3 info
- Non-problem informational messages, e.g., pack hints.
4) Reporting conventions
4.1 Prefer root-cause errors
- When a failure would cascade, report the earliest/root issue and suppress downstream diagnostics.
Example: If time statement is missing, do not additionally report “phase out of bounds”.
4.2 Avoid duplicates
- Same logical issue should produce at most one diagnostic.
4.3 Provide hints for common fixes
- For errors in core DSL structure (missing
term, missingschedule), ahintSHOULD be provided.
4.4 Provide related locations when helpful
related is optional (§2.3), and published only where it is set. An import
cycle (E1201) sets it: one entry per import in the loop, each naming the
file and the span of the import that continues it. The CLI prints each as
= related: <file>:<line>:<col> <message>.
5) Parser and recovery guidance
5.1 Parser behavior
- Parser SHOULD attempt recovery to continue parsing and emit multiple diagnostics.
5.2 Recovery strategies
Recommended recovery points:
- End of statement (newline/keyword boundary)
- Block boundary (
})
5.3 Parser diagnostic codes
Parser errors MUST use E0xxx_... codes.
6) Code taxonomy
6.1 Prefixes
E0xxx_*Parse errorsE1xxx_*Module/import/symbol errorsE2xxx_*Validation errors (required fields, schedule bounds)E3xxx_*Type-check / expression contract errorsE4xxx_*Pack-related validation errorsE5xxx_*Lowering/IR emission errorsE6xxx_*Pack lowering-time domain errors
A warning or an informational diagnostic takes the range of the stage that
raises it, with W or I in place of E: W1389 is a compile-stage
warning, W3500 a statement warning, W5024 and W5046 run warnings. The
number says where in the pipeline it was found, as an error's does.
6.2 Naming conventions
<Prefix><Number>_<CATEGORY>_<DETAIL>
- All caps, underscores.
- Numbers are stable and monotonic within a category.
7) Canonical error codes (v0.1 minimum)
7.1 Parse errors (E0xxx)
E0001_UNEXPECTED_TOKEN— the parser met a token it cannot use here.E0002_UNTERMINATED_STRING— a string literal opens and never closes.E0003_UNTERMINATED_BLOCK_COMMENT— a/*block comment opens and never closes.E0004_EXPECTED_TOKEN— something specific was required at this position and is missing. The message names what.E0005_INVALID_DATE_LITERAL— a date is not a real calendar date, or not inYYYY-MMorYYYY-MM-DDform.
7.2 Module/import (E12xx)
E1201_IMPORT_CYCLE— two files import each other, directly or through a chain.E1202_IMPORT_NOT_FOUND— an imported file does not exist at that path, or the model root has nomodel.cfdl. The root is the folder that containsmodel.cfdl; the CLI takes that folder, or the file itself, which names its folder.E1203_IMPORT_OUTSIDE_MODEL_ROOT— an import reaches outside the model's directory. A model is self-contained, so it can be moved or shared without carrying hidden dependencies.
7.3 Global structure (E11xx)
-
E1101_MISSING_VERSION— noversiondeclaration. It states which language version the model is written against. -
E1102_MISSING_MODEL— nomodeldeclaration, so the model has no name. -
E1103_MISSING_TIME— notimedeclaration. Without a timeline there is no grid to evaluate amounts on. -
E1104_MULTIPLE_VERSION—versionis declared more than once. -
E1105_MULTIPLE_MODEL—modelis declared more than once. -
E1106_MULTIPLE_TIME—timeis declared more than once. A model has one timeline. -
E1107_MULTIPLE_USE_PACK— more than oneuse pack. A model draws contracts from a single pack. -
E1108_USE_PACK_NOT_IN_MODEL_FILE—use packappears in an imported file rather than the model's own. The pack applies to the whole model, so it is declared where the model is. -
E1109_MISSING_ENTITY— no entity is declared. Every stream belongs to one. Fields that move: -
E1123_PREV_OUTSIDE_NEXT—prevnames a recurrence's own previous value and means nothing outside anext. A field's previous value is readable elsewhere asprev.<entity>.<field>, and an account's opening balance asprev.<account>. -
E1125_NO_STATE_NAMESPACE— an expression readsstate.<name>. There is no such namespace: a value that changes over time is a field of the entity it describes, declared as<name> init <expr> next <expr>inside that entity's block and read as<family>.<entity>.<name>. Without this the reference reaches the engine, which warns and substitutes zero — an entire series evaluating to nothing while the run still reportsstatus: ok. -
E1127_FIELD_RULE_READS_FIELD— a field's rule names another field by its family path. A field means this period's value at close, which does not exist yet inside a rule;prev.<entity>.<field>says the previous period. Unrejected it would resolve through the open-world entity root, return null and evaluate to zero. -
E1128_FIELD_DECLARED_TWICE— a field is declared both with=and with a rule. Both bind the same path, so one would silently win. -
E1129_PREV_IN_FIRST_PERIOD— a stream reads a field's previous period but runs from the model's first period, where there is none. Unrejected the read resolves to nothing and the stream evaluates to zero. Checked on hand-written and pack-lowered streams alike; the lowered form names the contract whose term set the schedule, since that is the term a model author can move. -
E1131_UNKNOWN_FIELD_READ— an expression reads a field the entity does not declare. Field paths resolve through the open-worldentityroot, so unrejected a misspelling reads as null: in arithmetic the run is refused, and in a guard the edge never fires. Checked in both spellings: a qualified path (asset.north.<field>) against that entity, and an entity-relative one (entity.<field>) against a stream's owner or, in a model's lifecycle guard or arrival value, against every entity bound to the machine, including what each one's type declares and a pack derives or lowers onto it (docs/01§7.3). A pack's own guard is checked when the pack loads. Apart_sumsummand is checked on every part the call reaches: a bare name must be a field each part declares, andprev.<name>an account or moving field each part carries (docs/03§4). Lifecyclestatuskeeps the open world; declared fields do not. -
E1133_UNKNOWN_TIME_READ— an expression reads atime.binding that does not exist. The vocabulary is closed —t,date,days_in_period,phase,ppy— so a miss is a typo, and unrejected it evaluates to zero every period with the run still reporting ok. There is deliberately noE1132forinputs.: an input may be supplied entirely by the run configuration, which the compiler never sees, so an unresolved input is the engine's to refuse. -
E1134_SERIES_READ_IN_LOGIC— an event's guard or action value, a field's rule, or an option's election or payoff calls a series reduction (series_sumand its five siblings). All of these are evaluated before any stream has a value, so the read binds nothing: the engine substitutesfalsein a guard and0in a rule, warns once per period, and publishes a full set of numbers understatus: ok— an event that never fires, or a recurrence whose collapseprevcarries for the rest of the run. A stream, a waterfall and the results layer do see stream values; drive logic from a field, a curve,time.*orinputs.*instead. Under the period walk this becomes an ordering rule: a guard may read a stream's settled history, at or before the previous period, and the same-period and forward forms stay refused.
7.4 Symbols and references (E13xx)
E1001_DUPLICATE_ENTITY— two entities share a name.E1002_DUPLICATE_CONTRACT— two contracts share a name. Give one a suffix to keep them separable.E1003_DUPLICATE_STREAM— two streams share a name.E1004_DUPLICATE_PHASE— two phases share a name.E1005_DUPLICATE_ASSUME— two assumptions share a name.E1006_DUPLICATE_OPTION— two options share a name.E1007_DUPLICATE_EVENT— two events share a name.E1008_DUPLICATE_METRIC— two metrics share a name. Both would publish undermetric.<name>and one would win silently.E1009_DUPLICATE_KEY— a contract's or an option'stermsblock states one term twice, or itspartiesblock binds one role twice. One value was meant, and which one is the author's to say; the related location is the first statement of the key.E1301_UNRESOLVED_ENTITY_REF— a stream, contract or event action names an entity that is not declared. A status write may also name a contract's own node,contract.<name>(docs/01§8.6); a node no contract declares is refused the same way.E1303_CONTRACT_SUBJECT_MISSING— a contract states noon entity, and the model declares no asset entity or two or more, so there is no subject to write it on (docs/01§8.1). The message names the asset entities. A contract with no subject used to lower on the entity that sorted first, so one asset's agreement could land on another without a word.E1340_WATERFALL_NO_SOURCE— a waterfall declares nofrom, so there is no pot to allocate.E1341_WATERFALL_FORWARD_REF— a step'spaid.<step>names a step declared later in the same waterfall. Steps pay in declaration order, so a later step has not paid anything when an earlier one is evaluated.E1342_WATERFALL_SERIES_NOT_VISIBLE— a series reduction names a step, or a step's shortfall (shortfall.<waterfall>.<step>), of this waterfall or of a later one. Steps publish when their waterfall finishes, so the read would aggregate to zero and say nothing. An EARLIER waterfall is the documented composition and still compiles; within one waterfall a step reads an earlier step's shortfall asowed.<step> - paid.<step>.E1395_RUNS_UNKNOWN_LINE— a lifecycle'sin <state> run …names a line role that no line the machine governs has (docs/01§7.3.3). A machine governs the lines of the contracts it is bound to and of the contracts written on the entities it is bound to; each line's role is the one its pack rule stamps, or the one aneffectsstream states withline <role>. Refused because a misspelled role would gate nothing and say nothing. The hint lists the roles the machine does govern.E1399_MALFORMED_SELECTOR— a quoted selector cannot be read: a*that is not a whole segment (cre*.rent), an empty segment (a..b) or an empty alternative (a.* |) (docs/01§16.2, selectors). Refused wherever the compiled model carries the selector — a series reduction in any expression, a slice's or statement'sstreamandcategory— and in a pack metric's expression at load, because a selector that cannot be read would match nothing in silence. The hint states the dialect.E1398_PAYMENT_UNKNOWN_LINE— a contract'spayment <line> …names a line the contract does not lower (docs/01§8.1). The line is its master role —rent,interest,revenue— as the pack's rule states it; a misspelled line would place nothing. The hint lists the lines the contract lowers.E1349_UNRESOLVED_LIFECYCLE_REF— an entity or a contract (docs/01§8.6) bindslifecycle <name>and no lifecycle block declares it, or a stateincludesa machine that no lifecycle block, pack or the language declares (docs/01§7.3.5).E1396_NESTED_STATE_CLASH— a state name appears at two levels of a machine and the machines it includes (docs/01§7.3.5). The status is the innermost state and an enclosing state stands for the states inside it, so a name has to mean one state whereverstate_enter,active in stateorin <state> runreads it.E1397_NESTED_MACHINE_CYCLE— a machine includes a machine that, directly or through another, includes the first again (docs/01§7.3.5). A machine cannot contain itself.E1356_PARTICIPANT_RETURN_NOT_A_PARTY—irr/moicnames something that is not a party, or a party that owns no account, or is written as text rather than a reference. A participant's return is folded over the party's OWN ACCOUNT — contributions are negative inflows, receipts are allocations in — so a party without one has nothing to fold.E1355_PARTICIPANT_RETURN_OUTSIDE_METRIC—irr,moic,npv,yieldorspreadappears outside ametricdeclaration. All five fold the finished projection, so reading one in a stream amount, an activation, an event guard, a waterfall step or an account inflow asks for a return on, or a price of, cash that expression has not produced yet. Left to run time it is a substituted zero and a warning nobody prints. Widened 1 October 2026 from the two participant returns to the two valuation folds (docs/03§4).E1354_METRIC_FORWARD_REF— a metric reads a metric declared below it, or reads itself. Metrics compose in DECLARATION ORDER, the same rule waterfalls follow, which makes the dependency an order rather than a graph. Reading itself is a different mistake: a metric is a fold over the finished projection, not a recurrence — carry a running quantity as a field the walk advances.E1350_LIFECYCLE_CONFLICT— an entity binds a model-declared lifecycle, but its ontology type already declares one. One machine per entity.E1351_LIFECYCLE_NO_INITIAL— a lifecycle block declares noinitial. Every machine opens somewhere.E1352_DUPLICATE_LIFECYCLE— two lifecycle blocks share a name. One machine, one declaration.E1353_UNREACHABLE_STATE_WRITE— an event, an option or a contract's term boundary (docs/01§8.5) setsstatusto a state no declared edge enters. The write can never be legal, whatever state the entity is in at run; declare the edge or drop the write. An edge-less machine stays unconstrained.E1347_UNRESOLVED_ACCOUNT_REF— a step allocatesto account <name>and no such account is declared. An account is not an entity and resolves in its own namespace, which is what theaccountkeyword in the step says. An account a contract opens under its pack,<subject>.<name>.<instance>, is a destination too.E1343_WATERFALL_DUPLICATE_STEP— two steps in one waterfall share a name, which would makepaid.<step>ambiguous.E1344_WATERFALL_NO_REMAINDER— a waterfall never says where the remainder goes, so cash could be left unallocated with nothing to say so. A waterfall drawingfrom <account>is exempt: what its steps leave stays in the account for the next scheduled date (docs/01§10.6), so nothing is lost.E1348_WATERFALL_NO_SCHEDULE— a waterfall does not say when it distributes. The schedule is half of what a distribution says: between its scheduled periods the pot accumulates, so "every quarter" and "once at exit" are different deals rather than two spellings of one. The omission used to lower to a one-shot in the first period, distributing whatever that period happened to produce; there is no default right often enough to be silent.E1346_STREAM_READS_WATERFALL_STEP— a series reduction in the causal plane — a stream's amount or guard, a field's rule, an event's guard or action value, an option's election, payoff or action value, an account's inflow — names a waterfall step or a step's shortfall (shortfall.<waterfall>.<step>). Every waterfall runs after the causal plane and a step's series is visible to a later waterfall'sfromand to nothing else, so the read could only ever aggregate to zero. A.*selector whose prefix is a waterfall's name, orshortfall.and a waterfall's name, orshortfallalone, is refused too: it names steps that exist and are unreadable, not a family that may be empty. A waterfall never writes a balance in the causal plane; what a party was paid is its account, read asprev.<account>.E1386_STREAM_FOLDS_STATE— a stream's series reduction names an entity field or an account. A stream's reduction selects streams; a field's and an account's series are state, read strictly backward, so the selector matched nothing and would aggregate to zero in silence while the same text in a metric folds the real value. Read the field directly, or the account asprev.<account>; a metric may fold either.E1388_REFERENCE_ENTITY_CARRIES_CASH— a reference entity is given something a reference cannot have (docs/01§7.1): an account in its block, apart ofin either direction, alifecycle, a stream or a contract written on it, a contract term or a waterfall step naming it, an account it owns — or no field with a rule at all, which makes it a set of stated values, an assumption's job. One code for the one sentence: a reference has fields and a recurrence and nothing else. The message names the clause; the hint names the owner the cash belongs on, or theassumethe values belong in.stateon one isE1317's, since the type declares no lifecycle.E1387_PART_AGGREGATE_UNKNOWN_ENTITY—part_sumorpart_countnames an entity the model does not declare. The compiler expands an aggregate into a sum over the declared parts, so it knows the parent is missing; refused with the IR path and the declared entities, rather than reaching the run as an unresolved name.E1302_UNRESOLVED_STREAM_REF— an event activates or deactivates a stream the model does not run. Event action targets were never resolved, so a misspelling matched nothing and the action was silently inert: the stream it was meant to stop kept paying, with no diagnostic and no warning. Checked after lowering rather than in the resolver, so a name a CONTRACT produced resolves as readily as one the model declared — the symbol table is built before the pack is chosen, and a check running there reported an unlowered name and a typo alike. The hint lists every stream in the model, both kinds.E1357_LIFECYCLE_AUGMENT_TOPOLOGY— alifecycleblock names a machine the PACK declared and also statesinitialorstate. A model may restate the machine's EDGES and add arrival actions to it; the STATE SET stays the pack's, because the finite set is what makesactive in state,state_enterand a status write checkable, and a misspelling a compile error rather than a phantom state. A model needing different states declares a separate machine under its own name. The states are refused rather than ignored — silently dropping them would leave the model saying one thing and the machine doing another. An edge naming a state the pack does not declare isE1316.E1358_ARRIVAL_ACTION_SETS_STATUS— anon enteror edge action writesstatus. An arrival action sets FIELDS on the entity that transitioned; a status write would fire a second transition inside the same period, breaking one-transition-per-entity-per-period. A transition that should cause another transition is topology — an edge out of the target state, taken next period — and status writes remain the named event's privilege.E1359_ARRIVAL_ACTION_UNKNOWN_FIELD— anon enteror edge action sets a field the entity bound to that machine does not have. The name is entity-relative, so it resolves against every entity bound to the machine and all of them need the field; the set is the union of what the model's entity block declares and what its ontology type contributes. Refused because a misspelled field is a write that lands nowhere — the silent-substitution shape of a misspelled series. The action may instead name a FIELD ROLE a master declares (set balance = 0); a role that no contract on the transitioning entity fills — a closed-form debt lowers no balance field — is refused with the same code, saying so. A contract'son startoron end(docs/01§8.5) setting a field its subject does not declare is refused with the same code: the block writes the contract's subject by a subject-relative name, as an arrival action writes the entity that transitioned. An arrival action on a machine a contract binds (docs/01§8.6) is refused with the same code: the agreement carries no fields, and its machine reads its terms rather than writing them.E1360_DUPLICATE_ENTITY_ID— two entities declare the same literal fieldid. The id is a stable identity for the layer above the model — engine-opaque, published in the results graph (docs/06) — and a consumer joining on it would merge two things into one. Uniqueness within the model is the one thing the language can check about a value it must not interpret (docs/01§7.1).E1361_DUPLICATE_SLICE— two slices share a name. Same rule as a metric: one name, one selection.E1362_SLICE_UNKNOWN_ENTITY— a slice'sentity(orexcept entity) names an entity the model does not declare. A slice selects by reference, and a reference is what the compiler can check — refused rather than silently matching nothing.E1363_SLICE_UNKNOWN_TYPE— a slice's or a statement row'stypenames an ontology type the active ontology does not define. The hint lists the known contract types; a master type (Contract.Debt) matches transitively throughrefines.E1375_UNKNOWN_LINE_ROLE— a slice's or a statement row'slinenames a line no contract type in the active ontology produces. A line is a role a master names —interest,rent,proceeds— and each pack rule names the one it emits, so the hint offers the near miss or lists the lines the vocabulary can produce.E1376_UNKNOWN_REFERENCE— a reference names something this model does not declare: a term of typecontractoraccount(a guarantee'scovered, a note'sprincipal_account), a waterfall step'sfor contract, or an option'son contract; or a pack rule's read through acontract-typed term ({{contract.<term>.line.<role>}},{{contract.<term>.account.<name>}},docs/07§6.4) where the named contract lowers no such line or opens no such account, the message naming what it has. Refused with the near miss rather than read as zero; a reference is what the compiler can check.E1377_STEP_LINE_NOT_ALLOCATED— a waterfall step paysfor contract <name> line <role>and the contract's type does not declare that line allocated. A step pays what the structure allocates; a line a rule lowers is paid by the rule, and a step paying it would count the cash twice. The hint lists the type's allocated lines.E1389_TERM_OUTSIDE_DECLARED_BOUND— a contract or an option states a term whose value lies outside the bound its type's field declares (docs/07§6.3): ashareof 1.5 where the field is above 0 and below 1, a buyout'sdelinquent_monthsof 0 where it is at least 1, anobligationthat is neitheroptionalnormandatory, aballoon_at_maturityof 2 where the field is a boolean. The message names the contract or option, the term, the value and the bound, all built from the declaration, and the hint states the bound. A value that is not a number on a numeric field is reported the same way. Only a literal is checked: a term that defers to an input or states an expression is not, because its value is not known when the model compiles. A boolean field is the exception: it is statedtrueorfalse, and an input or an expression in its place is reported, since no input supplies a flag. A refinement inherits a bound with its field.E1392_CONTRIBUTED_STEP_UNMATCHED— a waterfall'spay for contract <name|"selector"> line <role>expands to nothing (docs/01§10.2): the name or selector matches no declared contract, or every contract it matches is of a type whose pack declares no[[steps]]rule for that line. Also raised where a matched contract states neither the account its step rule pays nor a party in the line'spaid_torole, or omits a term the rule reads. The hint gives the hand-written step.E1378_NONCASH_STREAM_MOVES_NOTHING— a stream isaccrualorwriteoffand names no account. A non-cash stream is a movement of a balance and nothing else: addmoves <account>, or make it an inflow or outflow if money actually moves.E1379_NONCASH_STREAM_CATEGORY— anaccrualorwriteoffstream carries a cash flow category. The category roots classify cash; a non-cash stream is excluded from every cash fold, so a category on it would be a claim it cannot keep. The repair is to drop the category, pack or no pack:E5029asks a category of cash streams only.E1380_UNKNOWN_ACCOUNT_MOVED—moves <name>names an account declared neither on the stream's entity nor as a structure account. Declare it (account <name> owed|due [init <expr>]in the entity block, or at the model level) or correct the name; unrejected the movement would land nowhere.E1381_MOVED_ACCOUNT_HAS_NO_SIDE— a cash stream moves an account that declares no side. Whether an inflow raises or lowers a balance follows fromowed(a liability of its owner) ordue(a receivable); without one the direction of the movement is undefined.E1383_FOLDED_ACCOUNT_DECLARED— a container declares an account a member also declares. The container's account of that name IS the members' fold, readable asprev.<container>.<name>and declared nowhere; a declaration would double it or hide it. A claim of the container's own takes another name.E1384_FOLDED_ACCOUNT_MOVED— a streammovesa container's folded account. A fold is the sum of its members' balances and is moved only by moving a member's; the hint says to move the member's account or declare one of another name on the container.E1385_LINE_HAS_NO_RULE_FOR_TERMS— a contract's type declares a line, and no rule that lowers it applies to the terms as stated. Rules select on a term's value or on whether a term is stated (docs/07§6.4); the combination written is one the pack has no row for — a floating level-pay loan, say. Refused rather than lowered without the line: the cash would simply be missing.E1382_ACCOUNT_READ_WITHOUT_PREV— an expression reads an account as a current value (asset.loan.balance). A balance is readable inside a period only as its opening,prev.<account>— the prior close, settled state. This period's close is the sum of streams still being computed and does not exist yet.E1364_SLICE_CATEGORY_ROOT— a slice's category selector is not rooted in operating, investing or financing. A selector that could never match anything is a typo, not a choice.E1371_UNKNOWN_CONTRACT_TERM— a contract, or an option, states a term its type does not declare. The roster is the pack type's own terms plus its masters'; a term outside it is read by no rule, so before this check a misspelledescalationwas a lease that never escalated. The hint names the near miss, or lists the type's terms. A term the contract's owneffectsblock reads ascontract.<term>has a reader and is admitted (docs/01§8.3).E1372_MISSING_CONTRACT_TERM— a contract, or an option, omits a term its type requires, or states none of a group of alternatives (one_of: a lease's rent isrentorrent_year); or an option's election or payoff readscontract.<term>and neither the option nor the contract it is written on states it, or a contract'son startoron endreads acontract.<term>the contract does not state (docs/01§8.5), or a machine a contract binds reads one in a guard or an action (docs/01§8.6),contract.term_startandcontract.term_endincluded where the contract's term opens at a state entry and so states no date — a read with no value is a missing term, never a zero. Checked against the effective roster before any rule is expanded;E5006remains the rule-consumption backstop for a term a rule reads with no default.E1373_UNKNOWN_CONTRACT_TYPE— a type named on a declaration resolves to nothing the model may declare there: anoption ... typethe active ontology does not define, a two-tokencontract <type> <instance>whose type the pack does not declare, a fused contract name no rule lowers, an election written as acontract, or a lowered type written as anoption. The hint names the near miss or lists what may be declared. SupersedesE2002for a contract under a pack that declares contract types.E1374_ABSTRACT_TYPE_INSTANTIATED— a declaration names a master (Contract.Debt,Contract.Option). A master is refined, never declared; the hint lists its concrete refinements.E1390_OPTION_ON_WRONG_AGREEMENT— an option whose election type states what it is written on (written_on,docs/07§6.1) is writtenon contractan agreement of another type, oron entityor on nothing: a CRE renewal on a loan, a loan extension on a lease. The hint names the model's agreements it may be written on. The generic elections (Option.Call,Option.Put,Option.Renewal,Option.Refinance) state none.E1366_DUPLICATE_STATEMENT— two statements share a name. Same rule as a metric and a slice: one name, one presentation.E1367_STATEMENT_UNKNOWN_STRUCTURE— a statement presents a hierarchy the engine does not build, or asks for a category hierarchy in a model whose streams declare no category. Either would render as one residual row and nothing else — technically complete and useless — so it is refused rather than shipped empty. Known structures:entity(thepart oftree the results graph publishes) andcategory(the dotted path).E1369_STATEMENT_AUTHORED_AND_GENERATED— a statement states both astructureand its own rows, or neither. A generated statement partitions the cash by construction, because a hierarchy covers its own tree; an authored one partitions it by the author's care. Mixed, neither guarantee holds — an authored row claims streams the generated rows already claimed, so the bottom line double-counts and the reconciliation that makes a statement trustworthy becomes noise. A statement stating neither would render nothing.E1368_STATEMENT_UNKNOWN_REFERENCE— a statement filters by a slice, a row draws a slice, or a statement shows a metric, that the model does not declare; or aratiorow divides an operand that is neither a declared slice nor a subtotal the active pack publishes (docs/01§15.5). A pack's statements are checked the same way, since the compiler emits them as declarations; the message then names the pack's statements file. A presentation that silently shows nothing is the failure this check exists to end.E1370_STATEMENT_SERIES_ROW_CLAIMS— an authored row draws a publishedseriesbeside a claim clause (category,stream,slice,entity, or a ratio'sof/to). A series row presents a fold of the ledger, claims no streams and stays out of the bottom line; a claim clause beside it could only be resolved by a precedence the reader cannot see, which is a silently ignored clause. Refused instead.E1365_METRIC_UNKNOWN_SERIES— a metric folds a series name this model does not publish.series_sum/series_avg(and each sibling reduction, to its own identity) return 0.0 for a selector that matches nothing, which is right for a.*selector and wrong for a name spelled out in full; in a metric it is worse than wrong, because a fold publishes ONE number under a name the author chose, with no series beside it to show the zero (docs/01§15.3). A metric may fold any series the valuation plane publishes: a stream by its own name or asstream.<name>, a waterfall step,entity.<symbol>.net_cash_flow,account.<name>, an entity field, a subtotal, a declared slice's net asslice.<name>, ormodel.net_cash_flow. A ratio subtotal is foldable by every reduction butwal,npv,yieldandspread: its undefined periods publish as null and a fold skips them (docs/03§4), while those four measure cash paid, as a life, a present value, a yield or a spread, and a ratio is not cash, so each over a ratio is refused with its own hint.E1304_UNRESOLVED_OPTION_REF— an event exercises an option that is not declared. Checked in the compiler rather than the resolver, because options are not in the symbol tables.E1310_ENTITY_BLOCK_WITHOUT_TYPE— an entity uses a block but declares no type, so there is nothing to check the block against.E1311_UNKNOWN_ENTITY_TYPE— an entity declares a type the active ontology does not define. The known types are listed.E1319_UNKNOWN_ENTITY_FAMILY— an entity's family word is not one ofasset,party,container,reference(docs/01§7.1). Refused where it is written, typed or not: before this checkentity carpark lotcompiled, ran and published"family": "carpark", and the author learned of it at the first read of a field, as an unresolved name. The hint names the near miss (aset→asset) or lists the roster, so a misspelling can be told from a family the language lacks.E1324_PAYEE_OWNS_SEVERAL_ACCOUNTS— a waterfall step pays a party by the bare form,to party.x, and that party owns two or more accounts. The bare form means the party's one account; with several the destination is ambiguous, so the step names it,to account <name>. Before this check the engine kept the first account and dropped the rest from allocations with a run warning. The message lists the party's accounts and the hint writes the step with the first.E1323_ENTITY_FAMILY_MISMATCH— an entity's family word disagrees with its type:entity asset acme : Party. A type's family is the root of its refinement chain, derived when the pack loads (docs/07§6.1), and the declaration's word is checked against it; the message names both and the hint gives the declaration with the type's family.E1312_MISSING_REQUIRED_FIELD— an entity omits a field its type requires.E1313_UNKNOWN_ENTITY_FIELD— an entity sets a field its type does not declare. The declared fields are listed.E1314_UNKNOWN_PARENT_ENTITY—part ofnames an entity that is not declared. Hierarchy is optional; a declared parent is not.E1315_ENTITY_PART_OF_ITSELF— an entity is its own parent.E1330_CONFLICTING_ACTIVE_CLAUSES— a stream declares bothactive whenandactive in state. Use one:active in statefor a lifecycle state,active whenfor anything else.E1331_OWNER_HAS_NO_LIFECYCLE— a stream is active in a lifecycle state but its owner's type declares no lifecycle.E1333_VALUATION_FOLD_ARGUMENTS— a metric callsnpv,yieldorspreadwith the wrong shape: the wrong number of arguments, a first argument that is not a series selection text, or, forspread, an index that is not a series-shaped assumption named asinputs.<name>.npv("<selection>", rate[, from_t])prices what a selection pays at an annual rate;yield("<selection>", outlay[, from_t])solves for the annual rate at which it is worth the outlay;spread("<selection>", outlay, inputs.<index>[, from_t])solves for the spread over the index path (docs/03§4). A metric is evaluated once at the horizon, where a wrong shape would refuse the run with no span, so the compiler reports it at the declaration.E1332_UNKNOWN_ACTIVE_STATE— a stream is active in a state its owner's lifecycle does not declare. A state name is checked against the lifecycle; a string comparison such asentity.status == "leasd"is not, and stays false for every period.E1318_ENTITY_HIERARCHY_CYCLE—part offorms a cycle. Reported once, from the cycle's lexicographically first entity, rather than once per member. An entity aggregates its children, so a cycle has no bottom to sum from.E1316_UNKNOWN_LIFECYCLE_STATE— an entity starts in a state its lifecycle does not declare, or an event, an option or a contract's term boundary (docs/01§8.5) setsstatusto one, or a lifecycle says what runs in one (in <state> run …,docs/01§7.3.3). This is the misspelled status made impossible rather than merely unlikely.E1317_TYPE_HAS_NO_LIFECYCLE— an entity declares a starting state but its type has no lifecycle.E1320_UNKNOWN_PARTY_ENTITY— a contract or option binds a role to an entity that is not declared.E1321_NOT_A_PARTY— a role is bound to an asset. A contract is between parties.E1322_UNKNOWN_PARTY_ROLE— a role is bound that the contract type does not declare, or one the type leaves UNBOUND (a purchased pool's borrowers are many and unnamed). Roles are the type's effective roles, resolved through its master chain: a CRE lease bindslandlord, which is the master'slessor, and the hint lists each role a model may bind with the master's word beside it. A role belongs to the agreement, not to the entity.E1302_UNRESOLVED_STREAM_REF— something names a stream that is not declared — often an event deactivating one.E1304_UNRESOLVED_OPTION_REF— an event exercises an option that is not declared.
7.5 Contracts and streams (E20xx/E21xx)
E2001_CONTRACT_MISSING_TERM— a contract omits a term its pack requires. The message names it; see the pack's contract table.E2002_CONTRACT_MISSING_EFFECTS— a contract produces no streams, so it has no effect on the model: itseffectsblock is empty or missing, and no pack rule lowers it. Under a pack that declares contract types, a contract no rule lowers is a type the pack does not declare and is reported asE1373instead.E2101_STREAM_MISSING_SCHEDULE— a stream has noschedule, so there is no period for its cash to land in.E2102_STREAM_MISSING_AMOUNT— a stream has noamount.E2103_SCHEDULE_OUT_OF_BOUNDS— a schedule reaches outside the model timeline. The bound is the cash horizon plus anyproject <n>tail, since the engine evaluates streams over both; a schedule may reach into the tail deliberately to feed aseries_sumvaluation. Applied to hand-written streams during validation and mirrored onto pack-lowered ones during lowering, so a pack cannot express what a model may not.E2104_SCHEDULE_INVALID_RANGE— a schedule'stois before itsfrom.E2105_SCHEDULE_INVALID_DAY_OF_MONTH— a day rule names a day outside 1–31.E2106_SCHEDULE_PHASE_NOT_FOUND— a schedule is anchored to a phase that is not declared.E2107_STREAM_CURRENCY_MISMATCH— a stream's currency differs from the model's reporting currency. Cash flows are summed period by period, so the two would be added as if they were the same unit. Convert explicitly in the amount expression, or declare the model in that currency.E2108_SCHEDULE_FINER_THAN_CALENDAR— the schedule's interval is finer than the model's calendar cadence. The occurrences are not lost: a period holds many accruals and their amounts sum, which is the same machinery a settlement lag uses. What cannot be done is telling them apart — an accrual is stored as a model period index, so occurrences inside one period share an environment, and an amount that varies over time is computed once and multiplied rather than summed across the occurrences. A constant amount would be exact; anything else is silently wrong, so both are rejected. Use a coarser interval, or declare a finer calendar.E2109_SCHEDULE_CONFLICTING_PLACEMENT— a schedule combinesmidwith a day rule ornetpayment terms, or a contract'spaymentclauses give one line — or all its lines — both a placement and a lag, or time it twice (docs/01§8.1). Each states where in its period the cash sits; two placements is a contradiction, not a refinement.E2110_STREAM_WINDOW_FORWARD— a stream'samountfolds a series over a window with a bound of the formtime.t + k, which reaches past the period the stream pays in by construction. A stream has no window: the income after a date is a valuation's to fold — a pack[[valuations]]figure or ametric— and a sale pays that figure asmetric.<name>. A literal bound is not refused here; the walk's watermark refuses it at run if it reaches past what has settled.E2111_STREAM_READS_UNKNOWN_METRIC— a stream'samountreadsmetric.<name>, and no metric of that name is declared by the model or published by a pack valuation; or the figure is published every period (docs/07§6.4), a series rather than a figure at a date, which no stream pays. A read with no value is refused, never zero.E2112_STREAM_FEEDS_ITS_VALUATION— a stream pays a figure that folds the stream itself, by name or through a subtotal the stream's category is classified into: a sale struck from income that includes the sale. Classify the reversion outside what the figure folds (investing.disposal.*), or fold a subtotal that excludes it.
7.6 Events and actions (E22xx)
E2113_SUBTOTAL_READ_UNSETTLED— causal logic folds a money subtotal over a window reaching a period whose cash it cannot wait for, or reads a ratio subtotal at all (docs/01§9). A field, a guard, an event or an option settles before the period's streams, so it reads strictly backward, ending attime.t - 1. A stream may end the window attime.t, read after every stream the subtotal folds, and is refused when it is itself one of them — a management fee in operating expenses reading the period's NOI — since the subtotal cannot settle before it; a cycle through another stream isE5035. A window past the period (time.t + k) is refused for every reader. A window whose bound is not plainly the period is checked at run, where a cell not yet folded fails loudly rather than reading zero.E2114_STREAM_MISSING_DIRECTION— a stream states no direction (docs/01§9.1). It used to lower as an outflow, so a stream of income written withoutinflowbecame a cost with no diagnostic. Stateinfloworoutflow, oraccrualorwriteofffor a balance moved with no cash.E2115_STREAM_MISSING_CURRENCY— a stream states no currency and the model states none for it to take (docs/01§5.6, §9.1). A stream that omits its currency takes the one the model declares withmodel "..." currency <code>; with neither, it used to land in USD, which nothing in the model said.E2201_EVENT_WHEN_NOT_BOOL— an event'swhenis not a true/false expression.E2202_STREAM_ACTIVE_NOT_BOOL— a stream'sactive whenis not a true/false expression.- An event may set a field the entity does not declare: the
entityroot is deliberately open-world, which is how lifecyclestatusworks. A DECLARED field refuses a value it cannot hold — the engine warnsset … to non-numeric …; store unchangedand the stored value is untouched, so nothing downstream reads the bad write.
7.7 Expressions / typing (E30xx/W30xx)
E3001_EXPR_PARSE_ERROR— an expression is not valid CFDL.E3003_EXPR_TYPE_ERROR— an expression combines types that cannot combine, such as a date and a number.E3004_EXPR_ILLEGAL_OP— an operator is not defined for these operands. Warnings:EXPR_EVAL— a run-time expression failure: a division by zero, a function on an argument out of range, a non-numeric result. The evaluator's own code, carried in the message of the refusal it becomes —E5032in a field's rule,E5043in every other reader — so a consumer routes on those. Nothing is substituted for the value. A guard whose series window lies before the first period is not a failure: it is false (docs/03§5).EXPR_UNKNOWN_NAME— the run-time form of an unresolved name, emitted per read and folded by the run intoE5031, which is what a consumer should route on. Both are the expression evaluator's own codes (docs/03§5), registered here because results carry them.W1358_PACK_GUARD_REPLACED— a model restated a pack machine's edge, replacing the pack's guard. This is supported: a pack machine is an accelerator rather than a constraint, and one transition can be written in several forms where a pack ships one of them as its default. It warns because the condition deciding a transition is no longer the pack's, and a reader of the run should see that without consulting the pack. The message names the machine, the edge, the guard displaced and what now decides; the transitions journal repeats both when the edge fires. A replacement leaves the pack's arrival and edge actions in place.W1389_SECURITY_LINE_UNPAID— a contract's type marks a line ALLOCATED, paid by the priority of payments rather than lowered by a rule, and no waterfall step names that contract and line, though a waterfall pays the contract's subject or a step already binds the contract. The model is valid and runs clean: a class present in the interest waterfall and absent from the principal one is never repaid, the trust keeps the money and the ledger balances, so the omission shows only as a holder account that ends at zero. Warned once per line, naming the contract, the line and the waterfalls on its subject; the hint gives the step to add. A security in a deal with no priority of payments is not warned. An instrument that genuinely has no such line, such as an interest-only strip, is a type that does not allocate it, not a note that omits a step.W1390_STEP_CONTRACT_OFF_SUBJECT— a waterfall step paysfor contracta contract written on an entity outside the waterfall's subject and itspart offamily. A waterfall distributes its own subject's cash, so the step moves one structure's cash to another's claim and the ledger still balances. Thepart offamily counts in either direction, so a trust paying notes written on the owner trust it is part of is not warned. The message names the waterfall, the step, the contract and both entities.W1393_CONTRACT_SUBJECT_INFERRED— a contract states noon entityand the model declares exactly one asset entity, so the contract is written on it (docs/01§8.1). The message names the entity; the hint gives theon entityclause that silences the warning.W1391_TERM_ACCOUNT_OUTSIDE_AGREEMENT— an account-typed contract term (a note'sprincipal_account) names an account owned by a party the contract does not bind. The account measures the agreement's position, so a claim measured on another party's account runs off someone else's receipts: a class whose principal account is another class's is never paid. An account owned by the structure, or by a non-party entity, is not warned. The message names the term, the account, its owner and the parties the contract binds.
7.8 Pack errors (E4xxx)
E4004_MISSING_PACK— the named pack could not be loaded — not found, or found and rejected.
7.9 Lowering/emission (E5xxx)
-
E5031_UNRESOLVED_NAME— a run read a name nothing binds — a mistypedinputs.or an assumption the run configuration never supplied — and would have read it as zero. Fatal, naming every distinct unresolved name. An assumption the model DECLARES that failed to produce a number is reported as that, with the failure that explains it, rather than as "not declared". A declared metric the DETERMINISTIC run cannot evaluate — a participant's return on a party that never received anything — refuses under this code too, with the reason; a scenario or a Monte Carlo trial that cannot evaluate it omits the key from its summary and records the reason underomittedinstead. -
E5032_FIELD_EVALUATION_FAILED— a field's rule failed to evaluate in some period — a division by zero, a function argument out of range such aspmtwith no payments left — or produced something that is not a number. Named with the field, the clause and the period; a value that was never computed is not a number and is not substituted with one. -
E5033_INVALID_RUN_CONFIG— the run configuration or a run flag is malformed: an unknownvaluation_grainorarithmetic, anas_ofthat is not a date; a run that states bothannual_discount_rateandannual_discount_curve, or a curve the model does not declare; an override — in the deterministic block, a scenario or the Monte Carlo distributions — whose key matches nothing the model declares or reads (cprforinputs.cpr, a misspelled input, the retiredstream.<name>:amountoverride, which names what replaces it), or an inputs-file entry keyed by a name no assumption declares, each named with the nearest name it could have meant; an inputs file named by path where a host takes the document inline, or an entry stating no shape or more than one. -
E5034_SCHEDULE_FAILED— a schedule could not be placed on the timeline at run time. -
E5035_SERIES_CYCLE— a circular series read, or a read into a stream whose series names are computed at run time; no evaluation order satisfies it. Also a figure a deferred stream pays that moves once the amounts reading it are struck: it folds what depends on it (docs/01§9), and the message names the stream that moved and the logic that read the deferred amount. -
E5036_ASSUMPTION_CYCLE— a circular derivation amongassumevalues. -
E5038_NEEDS_THE_WALK— a forward-reaching read keeps the model on the column order, and the model needs what only the period walk computes: a stream moves or reads an account, where no balance is carried, or an asset sold before the horizon's end is valued on the income after its sale, which is its own tail (docs/01§7.3.3). -
E5043_EXPRESSION_EVALUATION_FAILED— a stream's amount oractive when, an event'swhen, an option'sexercise whenor payoff, an account's inflow orinit, a waterfall's pot or step, or an action's value failed to evaluate — a division by zero, a function argument out of range, a non-numeric result — or, in hand-written IR, failed to compile. Named with the reader and the period, every distinct failure at once; a value that was never computed is not a number and is not substituted with one, and a condition that could not be evaluated neither held nor failed. The same rule asE5032, which a field's rule keeps. A guard whose series window lies entirely before the first period is not this: nothing has happened yet, and the guard is false. -
E5044_VALUATION_TERM_ANCHORED— a contract publishes a valuation that folds the projection from its date (docs/07§7's[[valuations]], a figure over the term's start or a reach past it) and its term is anchored to a state entry (docs/01§8.1), which has no date at compile time. State the term as dates, or value it on its terms alone: a sale on a stated income or at a price may be anchored to the entry that makes it. -
E5045_INPUT_NOT_SUPPLIED— a bodiless assumption (docs/01§12.1) and the run supplied nothing for it: noinputs.<name>parameter and no entry of the inputs file by its full name. The model states no value, so the run must; refused before any period exists, naming every such assumption, never read as zero. -
E5046_INVALID_SHARD— a shard of a sharded run (docs/09, running a model as shards) that cannot be computed or merged: atrialsvalue that is notnoneor<from>..<to>withfrombelowto, a range past the run's trial count, or a range asked of a run that declares no Monte Carlo; at the merge, shards of different models, engines, trial counts or seeds, a trial covered by two shards or by none, two base shards whose ledgers differ, or a document with no shard record where a shard was expected. On an entity merge, two shards publishing one series over different timelines or in different kinds. The merge of entity shards also carries this code on a warning naming the pack subtotals it left out. -
E5042_VALUATION_UNCOVERED— a contract publishes a valuation (docs/07, valuations) whose figures foldreach_yearspast the contract's date, and the timeline — cash horizon plusprojecttail — does not reach that far, or the calendar is daily or weekly, where a valuation's whole-month window cannot be placed. A valuation is never folded short: extend theprojecttail by the periods the message names, or move the date. A figure published every period (per_period) folds its window from the last period of the cash horizon, so that is the date the reach is counted from. -
E5041_INPUT_OUT_OF_BOUNDS— a value the run supplied — an override, a scenario value, a Monte Carlo draw, an entry of the inputs file — is outside the domain of the assumption's type (docs/01§5.7), outside thewithinthe model states, or outside the bound the pack states on the contract term that reads it (carried in the IR asterm_bounds, and cited under the pack validation's own code). Refused, never adjusted: state a value inside the bound, keep a distribution'sclipinside it, or widen the bound where the deal genuinely differs. The compile-time counterpart on a literal isE2307. -
E5040_CURVE_READ_OUTSIDE_RANGE— a stream, guard, account inflow or option payoff read a series outside the effective dates it declares (docs/01§12.5):inputs.<name>at a period, orcurve_value(inputs.<name>, <date>)at a date, the series'from/todoes not cover. Outside them the series has no value; the run is refused, naming the series, the date and the reader, once per reader rather than per period. The run's own discount series (annual_discount_curve) is held to the same rule over the valuation horizon. -
E5004_INVALID_LOWERING_RULE— a pack's lowering rule is malformed, or a templated key expands to a value its slot cannot take: net days or months that are not whole numbers, aschedule_on_daythat is not 1 to 31 oreom, an unknownschedule_conventionorschedule_calendar, or aschedule_except/schedule_alsoentry that is not a date (docs/07§6.4). Also raised where a contract type's pack event acts on its subject and the contract is written on no entity. -
E5005_PHASE_NOT_FOUND— a lowering rule anchors to a phase the model does not declare. -
E5006_MISSING_CONTRACT_TERM— a lowering rule reads a contract term the contract does not supply. -
E5007_DUPLICATE_LOWERED_STREAM— two contracts lower to the same stream name. A line the pack names for its contract needs a suffix on one. A line the pack names for the contract's SUBJECT is one per subject, and a second contract lowering it is refused: a unit rolls on one lease on a market leasing set, since the lease struck at its expiry is the unit's. -
E5008_INVALID_CURVE— a series-shaped assumption (assume <name> = curve { … },docs/01§12.5) declares a malformed point, two values for one date, no point at all, an invalid effective date, effective dates that end before they start, or a point outside its effective dates. Two series of one name areE1005. -
E5028_INVALID_QUANTILE— a quantile-shaped assumption (assume <name> = quantile { … },docs/01§12.6) declares a malformed point, a share outside 0..1, shares out of order or repeated once read in the declared order, values that fall as share rises, or no point at all. Two quantiles of one name areE1005. -
E5009_LOWERED_EXPR_INVALID— a pack lowering rule expanded to an amount expression the parser rejects. Without this the engine evaluates the failed expression as zero and continues with only a warning. -
E5021_DUPLICATE_LOWERED_FIELD— two contracts lower to one field name with different recurrences, so one would silently win. Give the rule'sfield_namea per-contract discriminator ({{contract.suffix_ident}}). Identical definitions collapse instead, which is what several contracts sharing one curve should do. Statement completeness. These are warnings rather than errors: the statement still renders, and the point is that the reader can see what is wrong with it. -
E5029_STREAM_MISSING_CATEGORY— a CASH stream (infloworoutflow) declares nocategorywhile a pack is active. Its cash still reachesmodel.totaland the entity roll-up and folds into no subtotal at all, so every domain metric is computed as though the stream were not there. An error rather than a warning because with a pack loaded there is always a right answer available — a flow that does not belong in net operating income takes a different root — and a coverage ratio that quietly excluded a stream is wrong and says so nowhere. Without a pack a category stays optional, because nothing folds. Anaccrualorwriteoffis exempt: it moves a balance, reaches no total and no fold, and carries no category at all (E1379). -
E5030_AMBIGUOUS_CONTRACT_CATEGORY— a contract states onecategoryand lowers more than one stream. A contract lowers one or more streams and its pack states a category for each, so a single clause cannot say which it reclassifies: it would set all of them to the same value, and a coverage ratio computed off a principal repayment reclassified as interest is wrong with nothing to show for it. Name the stream —category <stream> = <path>— once per stream. The bare form stays legal where the contract lowers exactly one, because there is then nothing to disambiguate. -
W5022_UNKNOWN_SERIES_REFERENCE— a series reduction (series_sum,series_avg,series_min,series_max,series_prod,series_count,wal) names a series no stream, contract or waterfall step produces, so it reduces over nothing and whatever reads it is reading nothing. A warning rather than an error because a literal name matching nothing is also a pack idiom:cre.exitsums NOI components by name whether or not the property declared each one. Selectors with a*or alternatives are exempt, and are how a model states that matching nothing is intended; each exact alternative is checked. -
W5024_CURVE_READ_PAST_END— a stream or a field reads a series past its last point, and the series states noto, so it holds its last value there. Right for a rate deck, wrong for a schedule, and only the modeler knows which; the warning is once per reader and series. Declaring the effective dates answers it either way:topast the reader's horizon says the hold is meant,toat the last point says the reader ends there, and outside them the run refuses (E5040). -
W5046_ASSUMPTION_UNREAD— an assumption that no expression in the model reads and no run setting names. It sits on the review page and a scenario can override it, and moving it changes nothing. Usually the figure it describes is restated as a literal somewhere, or was computed by hand from it: read it where the model uses the figure, derive the figure from it, or remove it. A set is read when any of its fields is. Raised by the engine, once per assumption, because a run may read a series-shaped assumption the model text never names: a discount curve the run configuration states (annual_discount_curve) counts as a read. An override does not; that is the case the warning exists for. -
W5047_IRR_AT_SEARCH_FLOOR— a run's flows solve for a rate only at -100% a year, the floor of the rate search, somodel.irris not published (docs/06). It happens where the net cash never returns what it invests but the flows' timing alternates inside each period, such as an outflow at a period's start and a smaller inflow at its middle. A slice's or a party's IRR is omitted in the same case without a warning. -
W3500_STATEMENT_UNCLASSIFIED_STREAM— cash that no row of the statement claims, usually a hand-written stream carrying nocategory. It is collected into a visibleresidualrow rather than dropped, so the bottom line still reconciles and the omission is on the page instead of in the difference. The pack loader checks the same property for declared CATEGORIES statically; this is the half that needs a run, because a stream with no category at all is invisible until one happens. -
W3501_STATEMENT_STREAM_DOUBLE_COUNTED— a stream claimed by more than one row. Worse than an omission: the bottom line is then wrong in a direction that looks entirely plausible. -
W3502_STATEMENT_BOTTOM_LINE_RESIDUAL— the statement's rows do not sum to what the statement is accountable for, within half a cent. That ismodel.totalfor an unfiltered statement and the SLICE's total for one scoped to a slice: reconciling a filtered statement against the model would report the filter as a shortfall, and a warning that fires on a correct model is noise. Asserted, never corrected. -
W5023_UNRECOGNISED_PACK_CATEGORY— a stream's category is well-rooted and valid, and is not one the active pack recommends. The three roots are the only gate: a pack'scategorieslist is the domain's conventional spelling, not permission, because a pack cannot enumerate every leaf a deal needs. Reported in the statement's diagnostics rather than inresults.warnings, besideW3500, for two reasons: the consequence of an unrecommended category is a presentation one — no row of a pack statement claims it, so it lands in the residual — andresults.warningsbelongs to the engine, which has no pack. Names a near match when one is a single edit away, the bar the compiler already uses for a misspelled term. Reported once per distinct category: thirteen expense lines sharing one misspelling are one mistake. -
E5022_UNKNOWN_STREAM_CATEGORY— a stream, or an option's payoff, declarescategory <path>that the active pack does not list in its manifestcategories. A category is a dotted path into the cash flow statement (operating.deduction.abatement) and is what a fold aggregates on, so an unlisted one would leave the stream reported as a line and counted in no subtotal — visible and wrong, rather than absent and obvious. Use one the pack declares, or add it to the pack's vocabulary. With no pack in use there is no vocabulary, so any category is unknown. A pack whose own vocabulary is not rooted inoperating,investingorfinancingfails to load rather than reaching this check. -
E5010_TERM_UNKNOWN_INPUT— a contract term referencesinputs.<name>for an input that is not declared. Declare it withassume <name> = …orassume <name> ~ <Dist>(…). -
E5011_TERM_CLIP_OUT_OF_BOUNDS— a term defers to an input whoseclipcan produce values outside the range the pack allows for that term. The value itself cannot be checked until the run, but the clip states the range the driver can reach, so it can be. -
E5014_RULE_CADENCE_UNSUPPORTED— as above, but declared by one lowering rule rather than the whole pack. This exists so a pack can carry neutral and month-locked rules side by side while it is being migrated, instead of being gated wholesale. -
E5018_TERM_START_OFF_GRID— a pack contract'sterm_startdoes not fall on one of the model's period boundaries. Periods step from the model's start by whole calendar units, and elapsed-period counting measures whole steps from the term, so a term beginning mid-period counts short for the contract's whole life. Always satisfied on a monthly calendar, where everyYYYY-MMterm is a boundary. -
E5015_TERM_MONTHS_NOT_DIVISIBLE— a_monthsterm used as a count of payment periods does not divide into whole periods on this grid. A 30-month loan is not two and a half annual payments, and no closed form can express one, so this is an error rather than a rounding. Thresholds such asfree_rent_monthspro-rate instead and never reach here. -
E5016_RESERVED_TERM_PREFIX— a contract term beginsmodel.,time.,periods.orwhole_periods.. Lowering rules resolve those prefixes before contract terms, so the term would be shadowed and never read. Term keys may legitimately be dotted, so this is reachable by accident. -
E5017_PERIOD_TERM_NOT_LITERAL— a_monthsterm that a rule converts into periods is not a literal number: it defers toinputs.<name>, holds an expression, or does not parse as a number at all. The conversion happens at compile time and a non-literal is not known until the run. -
E5019_UNKNOWN_DAY_COUNT— a contract'sday_countoramortization_day_countis not one of30/360,30e/360,act/360,act/365. Not defaulted silently: the gap between act/360 and act/365 is roughly 1.4% of interest. -
E5027_ACTUAL_AMORTIZATION_BASIS— a contract'samortization_day_countisact/360oract/365. That term chooses what the CONSTANT payment is struck on, and an Actual basis expands to a period-local divisor (360 / time.days_in_period) which the annuity then applies to every remaining period — so the payment moves with month length. Measured on a single 1.2m loan at 6%: a 460.68 swing over twelve months, with no pool, no prepayment and no defaults involved. Strike the payment on30/360and accrue interest on the Actual basis withday_count, which is what an Actual/360 loan document says;day_countitself is unaffected, because a per-period divisor is exactly right for a per-period accrual. -
E2303_ASSUME_MISSING_PARAM— a distribution is missing a parameter it requires. -
E2304_ASSUME_INVALID_CLIP— aclip=[lo, hi]is malformed or inverted. -
E2305_ASSUME_UNKNOWN_TYPE—assume <name> : <type>names a type the language does not have. A type isfraction,rate,decimal,intorduration(docs/01§5). On a set the slot names a pack's set type, and one no active pack declares — or a value's type on a set — is refused with the declared set types in the hint (docs/01§12.7). -
E2306_ASSUME_INVALID_WITHIN— awithin [lo, hi]is malformed, inverted, or reaches outside the domain of the assumption's type; or a distribution'sclipcan produce a value outside the type's domain or thewithin, so the draws would break the bound; or a set states awithinof its own, which is stated per field. -
E2307_ASSUME_OUT_OF_BOUNDS— a literal assumption is outside the domain of its type (afractionabove 1, adurationthat is not whole) or outside thewithinthe model states. A value the run supplies is checked the same way at run start (E5041). -
E2308_ASSUME_FIELD_UNKNOWN— a set typed by a pack's set type states a field the type does not declare, or states a value where the type nests a subset (new = 120where the roster hasnew.term_months). The field would be read by nothing. The hint lists the roster at that level (docs/01§12.7). -
E2309_ASSUME_SET_READ— an expression, a contract's term or an entity's=field reads a set whole (inputs.retail_market), which has no single value, outside the two slots declared to hold a set (a set's field, a set-typed term), or reads a field the set does not state. A set declares no defaults: a field it leaves out has no value, and the read is refused rather than given a pack's number the model never stated. Checked in every position an expression may stand, with the reader's line where one is known and its IR path always. A read of a name no set is part of is the run's to resolve, as before, since a run may supply an input the model does not declare. -
E2316_ASSUME_SET_NAMED— a set is named where a set is expected, and what is named is not one (docs/01§12.7): a contract term the pack types as a set type states something other thaninputs.<set>, names an assumption that is not a set, or names a set of another type; or a chain of set fields naming sets returns to where it started, or sets name each other so that each contains itself, leaving no set that states the fields. An override addressed through a named set is refused at the run asE5033, naming the set that states the field. -
E2310_ASSUME_FIELD_INVALID— an assumption's value is not what it is declared as: a bare word (downtime_months = nine, a name that would read as nothing), a string where a number is declared or a number where a string is, a fraction in a whole-number field, a distribution in a string field, a number outside the bound the set's type declares for the field (a commission of 6 where the field is 0 to 1), or a stated unit other than the one the set's type declares. Checked where the value is written, whether or not anything reads it yet; units are asserted, never converted, as a term is held toE5024. -
E2311_ASSUME_SOURCE_INVALID— asource { … }states a field a source does not have, states one twice, or givesas_oforretrieveda string or a text field a date. A source statespublisher,series,as_of,retrieved,urlandnote, every one optional (docs/01§12.8). -
E2312_ASSUME_SHAPE_READ— a series or a quantile is read in a way the language does not admit (docs/01§12.5, §12.6). A series:curve_valuegiven a string (curve_value("sofr", …), the retired spelling; a series is an assumption, named asinputs.sofr),curve_valuegiven an assumption that is not a series, or an entity's=field naming a series, a literal slot with one value where a series has one per period. A quantile: a quantile function given a string or an assumption that is not a quantile, a bareinputs.<name>read of a quantile, which is a function with no single value, or a quantile in an entity's=field. A distribution:samplegiven anything but a distribution-shaped assumption (sample(inputs.rent)whererentis a value, a series, a quantile or a set;docs/01§12.2.1). Checked in every position an expression may stand, with the reader's line where one is known and its IR path always. -
E2313_ASSUME_READS_PERIOD— an assumption's expression reads the reader's period (docs/01§12.1):time.*,prevor a state, a series reduction orwal,state_enter, a metric, a subtotal, a paid or owed balance, an entity field that carries a rule, a series bare (inputs.sofr, the series at the period), orsample(…), a draw keyed by its reader's entity and period (docs/01§12.2.1). An assumption is evaluated once, at run start, before any period exists; each of these failed there as an unresolved name (E5031) and is now refused where written. A series at a stated date,curve_value(inputs.sofr, date(2026, 1, 1)), and an entity's literal fields are read as constants of the model. -
E2314_ASSUME_BOUND_UNTYPED— an assumption states no value and no type (docs/01§12.1). An assumption the run supplies states its type, a language type or a pack observable, since it is all the model says about the number; without one there is nothing to check the supplied value against. -
E2315_SAMPLE_WITHOUT_SUBJECT—sample(inputs.<name>)is read where no subject entity or no period is bound (docs/01§12.2.1): an event'swhenor action, an option, a waterfall's pot or step, an account, a metric, or an entity's=field, which is resolved once at run start. A draw is keyed by its reader's subject entity and period, so it is read in a field rule, a stream, a lifecycle guard or an arrival action. The message names the reader and why it has no subject; the fix is to draw in a field on the entity the draw belongs to and read that field here. -
E2401_OPTION_MISSING_EXERCISE— an option declares noexercise when, so nothing can ever trigger it. -
E2402_OPTION_MISSING_PAYOFF— an option declares nopayoff, so exercising it would move no cash. -
E5024_TERM_UNIT_MISMATCH— a term is supplied in units the rule does not declare for it. -
E5025_TERM_EXPR_INVALID— a term holds an expression that does not compile. Checked at the term's own span, before substitution: after the splice the error would point at a rule the modeler did not write. -
E5026_TERM_EXPR_IN_LITERAL_SLOT— a term holding an expression is used by a rule where only a literal can go: a stream name, a schedule date, a frequency, or a net-days count. Those slots are never parsed as expressions, so an expression there is not evaluated late — it is wrong. Expression terms are valid where the rule uses the term in an expression, which isamount_exprand a field'sinit/next.
Both cadences gates are a migration scaffold rather than a permanent
statement about a pack: the entries are removed rule by rule as the
expressions become cadence-neutral.
7.10 Pack domain validations (E6xxx–E9xxx)
Two term spellings that mean the same figure in different units — a per-period
amount and an annual amount_year — are checked in both directions: at
least one must be given (any_term_present), and at most one may be
(terms_mutually_exclusive). The second matters because a lowering rule sums
the pair with zero defaults, templates having no conditional, so stating both
would silently add them. E6030, E7010 and E7011 are those checks.
These diagnostics come from a pack's own validations.toml, evaluated by the
compiler against each contract. They are pack-origin diagnostics and must
include file/span (contract span when a term-level span is unavailable).
Each first-party pack owns a reserved code range; the pack loader rejects a
validations file whose codes fall outside its declared code_prefix.
| Pack | Range | File |
|---|---|---|
| CRE | E6xxx | packs/cre/validations.toml |
| OpCo | E7xxx | packs/opco/validations.toml |
| Energy | E8xxx | packs/energy/validations.toml |
| Credit | E9xxx | packs/credit/validations.toml |
Presence of terms required by a lowering template is not listed here: that
is handled generically for every pack by E5006_MISSING_CONTRACT_TERM.
CRE pack codes:
-
E6001_CRE_LEASE_MISSING_BASE_RENT— a CRE lease states no rent:rent,rent_year,rent_psforrent_per_unit_month. -
E6002_CRE_LEASE_INVALID_TERM_RANGE -
E6011_CRE_EXIT_INVALID_EXIT_CAP—cre.exit'scap_rateis not above 0. E6010 and E6012, a missing cap rate and a missing income, are deleted withcre.exit_cap: each is required only on the bases that read it, and a basis's term left out isE5006. -
E6030_CRE_LEASE_AMBIGUOUS_RENT— a CRE lease states its rent more than one way: two ofrent,rent_year,rent_psfandrent_per_unit_month. Give one. -
E6033_CRE_UNIT_INVALID_ESCALATION— a lease unit'sescalationis below -1, which would make rent negative on the first step. -
E6032_CRE_UNIT_INVALID_PRO_RATA—pro_rata_shareis a fraction between 0 and 1 -
E6040_CRE_ROLLOVER_INVALID_PROBABILITY—renewal_probabilityis a probability between 0 and 1 -
E6041_CRE_ROLLOVER_INVALID_DOWNTIME—downtime_monthsis a whole number of months, 0 or more -
E6050_CRE_DEBT_MISSING_PRINCIPAL/E6051_CRE_DEBT_INVALID_PRINCIPAL— a pair: the first, a loan that states neitherprincipalnor a sizing limit (ltv_max,dscr_min,debt_yield_min) it is sized to; the second, a stated principal that is not positive -
E6052_CRE_DEBT_MISSING_RATE/E6053_CRE_DEBT_INVALID_RATE— the same pair for the rate: neitherinterest_ratenorindexstated; a nominal annual rate below 0 -
E6054_CRE_DEBT_INVALID_AMORT—amortization_monthsstrikes the payment and is normally longer than the loan's term -
E6055_CRE_DEBT_INVALID_IO_MONTHS— whole months, 0 or more -
E6057_CRE_CONSTRUCTION_INVALID_COMMITMENT— the facility'scommitment, zero or greater. Renamed from..._INVALID_EQUITY_COMMITMENTwhen equity became its own contract. -
E6058_CRE_CONSTRUCTION_INVALID_RATE— a nominal annual rate, 0 or more (0.08 for 8%), the floor every debt contract states. -
W6001_CRE_CONSTRUCTION_RATE_ABOVE_ONE— the rate is above 1, which is almost always 8 entered where 0.08 was meant. A convention: the run proceeds, and a coupon above 100% that is meant keeps the warning. -
E6059_CRE_CONSTRUCTION_INVALID_DRAW_MONTHS— the availability period, 1 or more whole months. Reuses the number of..._INVALID_DRAW_ACCRUAL_FRACTION, deleted whenpayment proceeds start|mid|endplaced the draw. -
E6060_CRE_CONSTRUCTION_INVALID_TERM_RANGE— the build must sit inside the model timeline, or the schedule silently loses draws -
E6061_CRE_OPEX_LINE_MISSING_AMOUNT— an operating expense line statesamountoramount_year; both default to zero, so stating neither is a line that silently costs nothing -
E6062_CRE_OPEX_LINE_PCT_FIXED_RANGE— the fixed SHARE, in [0, 1]; catches 81 entered where 0.81 was meant, which would otherwise report a wrong expense rather than fail -
E6063_CRE_OPEX_LINE_OCCUPANCY_RANGE— a ratio of occupied space, in [0, 1]; zero is a fully dark building and is legitimate -
E6065_CRE_CONSTRUCTION_INVALID_INTEREST— a construction loan'sinterestis notpaid,reserve,shortfallorrolled_up. Renamed from..._INVALID_CAPITALIZE_INTERESTwhen the 0/1 flag became the three elections, and widened to the fourth,shortfall, when it was added. -
E6076_CRE_CONSTRUCTION_INVALID_FEE—fee_pctis a share of the commitment, from 0 to 1. -
E6077_CRE_LEASE_INVALID_OUTCOME— a unit lease's or a speculative lease'soutcomeis notdraw,renew,reletorvacate. Refused rather than read: any other word would fall through to the draw. -
E6078_CRE_EXIT_INVALID_BASIS—cre.exit'sbasisis notforward_noi,trailing_noi,stated_noiorprice. -
E6079_CRE_EXIT_INVALID_SHARE—share, the share of the asset sold, is not above 0 and at most 1. -
E6080_CRE_UNIT_SALES_ON_INVESTMENT_ASSET—cre.unit_salesis written on an asset whoseintentis notsale; an asset held for investment is sold bycre.exit. -
E6081_CRE_UNIT_SALES_PACE_ON_UNIT— a unit sale on an individually modeled unit states apace. A pace sells a count, on a building or a unit type; a unit closes on its own sale's term. -
E6082_CRE_UNIT_SALES_INVALID_DEPOSIT—deposit_pctis not from 0 to 1. -
E6083_CRE_UNIT_SALES_INVALID_RELEASE—release_pctis not from 0 to 1. -
E6084_CRE_PURCHASE_INVALID_CLOSING_COSTS—closing_costs_pctis below 0. -
E6085_CRE_LEASE_INVALID_ESCALATION_KIND—escalation_kindis notstep,indexorcollar. -
E6086_CRE_LEASE_INVALID_COLLAR— a collar'sescalation_flooris above itsescalation_cap. -
E6087_CRE_LEASE_INVALID_ESCALATION_INTERVAL—escalation_every_monthsis not a whole number above 0. -
E6088_CRE_LEASE_INVALID_RECOVERIES—recoveriesis notgross,modified_gross,netornnn. -
E6089_CRE_LEASE_INVALID_RECOVERABLE_CATEGORIES—recoverable_categoriesis nottaxes,taxes_insuranceorall. -
E6090_CRE_LEASE_INVALID_GROSS_UP,E6091_CRE_LEASE_INVALID_VARIABLE_SHARE,E6092_CRE_LEASE_INVALID_CAM_CAP—gross_up_occupancy,variable_shareorcam_cap_pctis not from 0 to 1. -
E6093_CRE_LEASE_INVALID_DEPOSIT—security_deposit_monthsis below 0. -
E6094_CRE_GROUND_LEASE_INVALID_SIDE— a ground lease'ssideis notfeeorleasehold. -
E6095_CRE_GROUND_LEASE_MISSING_RENT— a ground lease states neitherrentnorrent_year. -
E6096_CRE_GROUND_LEASE_INVALID_RESET,E6097_CRE_GROUND_LEASE_INVALID_PARTICIPATION—reset_pctorparticipation_pctis not from 0 to 1. -
E6098_CRE_DEBT_INVALID_ON_BREACH—on_breachis notcash_trap,sweepordefault. -
E6099_CRE_DEBT_INVALID_BASIS— a permanent loan'sbasisis notforward_noi,trailing_noiorstated_noi. -
E6100_CRE_DEBT_LOCKOUT_WITHOUT_MAKE_WHOLE— a loan stateslockout_monthsand nomake_whole_rate: a payoff inside the lockout pays the make-whole, as a defeasance costs. -
E6101_CRE_DEBT_FLOATING_WITHOUT_AMORTIZATION_RATE— a floating permanent loan states noamortization_rate, the fixed rate its schedule and its sizing constant are struck on. -
E6102_CRE_DEBT_LTV_WITHOUT_VALUE— a loan sized onltv_maxstates neithercap_ratenorvalue. -
E6103_CRE_DEBT_FLOATING_MAKE_WHOLE— a floating loan statesmake_whole_rate; it has no fixed schedule to discount, and its prepayment isstep_down_pct. -
E6104_CRE_DEBT_INVALID_CURE_PERIODS—cure_periodsis not a whole number of tests, 1 or more. -
E6105_CRE_DEBT_INVALID_TEST_INTERVAL—covenant_test_every_monthsis not a whole number of months, 1 or more. -
E6106_CRE_DEBT_COVENANT_LTV_WITHOUT_VALUE— a loan tested oncovenant_ltv_maxstates neithercap_ratenorvalue. -
E6107_CRE_EQUITY_INVALID_COMPOUNDING—compoundingis notannual,monthlyorsimple. -
E6108_CRE_EQUITY_INVALID_HURDLE_BASIS—hurdle_basisis notirrormultiple. -
E6109_CRE_SPONSOR_FEE_INVALID_BASIS— a sponsor fee'sbasisis notacquisition,development,construction_management,asset_management,dispositionorfund_management. -
E6110_CRE_EQUITY_INVALID_CATCH_UP—catch_upis not above 0 and at most 1. -
E6111_CRE_EQUITY_HURDLES_NOT_ASCENDING,E6112_CRE_EQUITY_THIRD_HURDLE_NOT_ASCENDING— a tier's hurdle is not above the one before. -
E6113_CRE_EQUITY_PROMOTE_WITHOUT_ACCOUNT— an interest earning a promote (promote_on) states nopromote_account. -
E6114_CRE_PROPERTY_TAX_INVALID_ASSESSMENT—assessmentis notstatedorincome. -
E6115_CRE_PROPERTY_TAX_INVALID_ABATEMENT—abatement_pctis not from 0 to 1. -
E6116_CRE_PROPERTY_TAX_INVALID_RATIO—assessed_pct_of_valueis not above 0 and at most 1. -
E6117_CRE_INCENTIVE_INVALID_NATURE— an incentive'snatureis notcapitalorrental_subsidy. -
E6118_CRE_INCENTIVE_SHARE_WITHOUT_BASIS,E6119_CRE_INCENTIVE_TARGET_WITHOUT_BASIS— an incentive paying ashareor topping up to atargetnames nobasisseries. -
E6120_CRE_AFFORDABLE_INVALID_INCOME_BAND—rent_limit_pct_of_amiis not above 0 and at most 1. -
E6121_CRE_AFFORDABLE_INVALID_UNITS—restricted_unitsis not a whole number, 0 or more. -
E6122_CRE_UNIT_SALES_AMBIGUOUS_CLOSINGS— unit sales state bothprice_per_unitandgross_proceeds; the closings are stated one way. -
E6123_CRE_UNIT_SALES_TERM_NEEDS_A_PRICE— unit sales statinggross_proceeds, a ledger of closings, also statepace,price_growthordeposit_pct, each of which acts on a price. -
E6067_CRE_PCT_RENT_INVALID_OVERAGE_PCT— a fraction between 0 and 1. -
E6068_CRE_BUDGET_LINE_MISSING_AMOUNT— a budget line states no spend:amountper period,amount_year, ortotalspread overprofile. -
E6069_CRE_BUDGET_LINE_AMBIGUOUS_AMOUNT— a budget line states its spend more than one way; the rules would sum them. -
E6070_CRE_BUDGET_LINE_INVALID_PROFILE—profileis not one oflevel,s_curve,front_loaded,back_loaded. A spend of any other shape is stated asamountper period, an expression. -
E6071_CRE_BUDGET_LINE_INVALID_CONTINGENCY—contingency_pctis below 0. -
E6072_CRE_BUDGET_LINE_INVALID_RETAINAGE—retainage_pctis below 0 or not below 1; holding back the whole spend would release a sum the line never paid a share of. -
E6073_CRE_EQUITY_COMMITMENT_INVALID_FUNDING—fundingis notfirst,pro_rataorat_start. -
E6074_CRE_EQUITY_COMMITMENT_INVALID_COMMITMENT—commitmentis below 0. -
E6075_CRE_EQUITY_COMMITMENT_INVALID_SHARE—shareis not from 0 to 1; preferred equity, which takes no share of the rest, is 0. -
E6124_CRE_EQUITY_COMMITMENT_TERM_NOT_READ— a commitment fundedat_startstatesfunding_shareorfunds_after. It contributes the whole commitment at the term's start and reads no budget, so neither term would be read; the term is refused rather than ignored. A funding share outside 0 to 1 and a negativefunds_afterare refused by the fields' own bounds (E1389). -
E6064_CRE_REVENUE_LINE_MISSING_AMOUNT— a revenue line statesamountoramount_year; both default to zero, so stating neither is a line that silently earns nothing
OpCo pack codes:
E7001_OPCO_LINE_MISSING_AMOUNTE7002_OPCO_LINE_INVALID_SCHEDULEE7003_OPCO_LINE_INVALID_GROWTHE7010_OPCO_LINE_AMBIGUOUS_AMOUNT— a line states bothamount(per period) andamount_year(annual); they would be summed, so stating both is refusedE7025_OPCO_PERPETUITY_RATE_NOT_ABOVE_GROWTH— a growing perpetuity needsdiscount_ratestrictly abovegrowth_rate. At or below it the denominator reaches zero and then goes negative, so the contract would return a huge value and then a negative one with nothing to say the model had stopped meaning anything.E7026_OPCO_PERPETUITY_MISSING_BASE_VALUE— the terminal-period flow the perpetuity is struck on.E7027_OPCO_PERPETUITY_MISSING_DISCOUNT_RATE— the terminal capitalization rate, stated on the contract rather than taken from the run's discount rate.E7028_OPCO_PERPETUITY_MISSING_GROWTH— state 0 for a flat perpetuity.E7029_OPCO_PERPETUITY_INVALID_SELLING_COSTS— a fraction between 0 and 1.E7011_OPCO_TAXES_AMBIGUOUS_DA— OpCo cash taxes state bothda_monthly(per period) andda_year(annual). They would be summed; give one.E7012_OPCO_TAXES_MISSING_RATE— a cash-taxes contract states notax_rate. The term may hold an expression (curve_value(inputs.tax_rates, time.date)) so a varying rate needs no second term; without this check, stating nothing would silently model a business that pays no tax.E7013_OPCO_WC_MISSING_AMOUNT_OR_RULEE7014_OPCO_WC_INVALID_SCHEDULEE7020_OPCO_EXIT_MISSING_MULTIPLEE7021_OPCO_EXIT_INVALID_MULTIPLEE7022_OPCO_EXIT_MISSING_BASE_VALUEE7023_OPCO_EXIT_INVALID_SCHEDULEE7024_OPCO_EXIT_EBITDA_INVALID_MULTIPLEE7030_OPCO_DEBT_INVALID_AMORTE7031_OPCO_DEBT_INVALID_RATE
Energy pack codes:
E8001_ENERGY_INVALID_DEGRADATIONE8002_ENERGY_INVALID_AVAILABILITYE8003_ENERGY_INVALID_ESCALATIONE8004_ENERGY_INVALID_PRICE_ESCALATIONE8010_ENERGY_INVALID_MACRS_LIFEE8011_ENERGY_INVALID_TAX_RATEE8020_ENERGY_DEBT_INVALID_RATEE8021_ENERGY_DEBT_INVALID_TERM_MONTHSE8022_ENERGY_DEBT_INVALID_PRINCIPALE8023_ENERGY_RESERVE_INVALID_SIZE— a reserve'smonths_of_debt_serviceis not greater than 0.E8024_ENERGY_RESERVE_INVALID_TARGET— a reserve'stargetis negative.E8025_ENERGY_RESERVE_INVALID_RATE— a reserve'sinterest_rateis negative.
Credit pack codes:
E9001_CREDIT_INVALID_BALANCEE9002_CREDIT_INVALID_RATEE9003_CREDIT_INVALID_TERM_MONTHSE9010_CREDIT_INVALID_CPRE9011_CREDIT_INVALID_CDRE9012_CREDIT_INVALID_SEVERITYE9013_CREDIT_INVALID_RECOVERY_LAGE9014_CREDIT_INVALID_SERVICING_FEEE9015_CREDIT_INVALID_PREPAY_PENALTYE9016_CREDIT_INVALID_PSA_SPEED—psa_speedis a MULTIPLE of the standard prepayment curve, so 1.5 means 150% PSA. Must be 0 or more; 0 selects the flatcprpath.W9001_CREDIT_PSA_SPEED_ABOVE_TEN—psa_speedis above 10 (1000% PSA), the highest speed a published table prints. A convention, not a definition: the run proceeds, and a stress case that means it keeps the warning.E9017_CREDIT_INVALID_SDA_SPEED—sda_speedis a multiple of the standard default assumption. Must be 0 or more; 0 selects the flatcdrpath.W9002_CREDIT_SDA_SPEED_ABOVE_TEN—sda_speedis above 10 (1000% SDA); the same convention asW9001.E9018_CREDIT_INVALID_ABS_SPEED—abs_speedis the Absolute Prepayment Model speed: the fraction of ORIGINAL balance prepaying each month. Already monthly, so unlikecpr/cdrit is not converted. Must be 0..1.E9019_CREDIT_INVALID_AGE_MONTHS—age_monthsis the pool's weighted average age at closing. PSA, SDA and the ABS model are all indexed from ORIGINATION, so a seasoned pool starts part-way up the ramp; leaving it at the default 0 on a seasoned pool understates prepayment. Non-negative integer.E9020_CREDIT_RATE_FLOOR_ABOVE_CAPE9021_CREDIT_INVALID_SHARE— a participation'sshareis not in (0, 1]. A share above one pays out more than the pool produced; zero is a participation in nothing.E9022_CREDIT_INVALID_COUPON— a note'scouponis negative.E9023_CREDIT_INVALID_NOTE_INTEREST— a note'sinterestis neitherpaid(a period's unpaid interest is not carried) nordeferred(it stays owed and bears interest at the coupon).E9032_CREDIT_DEFERRED_NOTE_WITHOUT_INTEREST_ACCOUNT— a note statesE9033_CREDIT_NOTE_COUPON_AND_INDEX— a note states bothcouponandindex. A class's rate is fixed or floating: state the coupon, or the index with its margin, floor and cap.interest = "deferred"and nointerest_account. Unpaid interest stays owed and bears interest at the coupon, so it accrues to an account the holder is paid from; without one the lowering has nowhere to carry it. The pack reports it before lowering, so the omission is one diagnostic on the note.E9024_CREDIT_INVALID_ADVANCING— a servicing agreement'sadvancingis notnone,interestorprincipal_and_interest.E9025_CREDIT_INVALID_STOP_ADVANCE— a servicing agreement'sstop_advance_monthsis not a whole number of months, 1 or more. A servicer that advances until the loan leaves the pool states none.E9026_CREDIT_INVALID_SERVICING_FEE— a servicing agreement'sfee_rateis negative.E9027_CREDIT_INVALID_ADVANCE_RATE— a servicing agreement'sadvance_rateis negative.E9028_CREDIT_INVALID_GUARANTEE_LIMIT— a guarantee'slimitis negative.E9029_CREDIT_INVALID_GUARANTEE_FEE— a guarantee'sfeeis negative.E9030_CREDIT_INVALID_MAKE_WHOLE_FLOOR— a loan'smake_whole_flooris negative.E9031_CREDIT_TWO_PREPAYMENT_PREMIUMS— a loan states bothprepay_penalty_rateandmake_whole_rate; a prepayment pays one premium.
7.11 Codes a model cannot reach
Emitted, registered, and never met through a model written in the language, so no fixture can exemplify them and the repair catalog counts them apart. The first four are the engine's guards for IR it did not get from the compiler — a document written or edited by hand — and for the I/O around it. The last four are a pack author's faults, which fire only on a pack that ships malformed — a field rule whose splice of an entity literal does not compile, an interval or a cadence no pack states, a subtotal over a category no rule emits — and the packs that ship do not.
E5002_IR_SCHEMA_VALIDATION_FAILED— the IR the compiler produced does not satisfy the published IR schema, or the IR being read does not. Only that: every other way a run can fail has a code of its own below, so a reader who trusts the code is not sent to the schema for a failure the schema would have passed.E5003_IR_EMIT_FAILED— the IR could not be written.E5037_SERIES_READ_IN_LOGIC— the engine's own check forE1134, for IR the compiler never saw.E5039_UNKNOWN_ACTION_KIND— an event's or option's action names a kind the engine does not execute. Only hand-written IR can carry one; the run is refused rather than reported as ok with the action journaled as ignored, which is what it did before results 0.14.E5020_LOWERED_FIELD_INVALID— a pack lowering rule expanded to a fieldinitornextthe parser rejects. Same reasoning asE5009: the engine's fallback for a failed rule is zero, which would flatten every stream reading the field rather than fail loudly.E5012_RULE_INVALID_INTERVAL— a lowering rule'sschedule_everyis not one ofday,week,month,quarter,year.E5013_PACK_CADENCE_UNSUPPORTED— the model's calendar is not one the pack declares incadences. A pack whose expressions divide annual figures by a literal 12 assumes one period is one month; on any other grid the schedule adapts correctly and only the amount does not, so the model produces plausible figures out by a factor of twelve. Refusing to lower is the only safe option. Use a calendar the pack supports, or a pack that supports the calendar.E5023_SUBTOTAL_UNKNOWN_CATEGORY— a pack subtotal folds a category no rule emits, so the row would always be zero.
8) Deprecation and evolution policy
Before 1.0, a retired code is DELETED. There is no installed base, so
there is nobody holding a saved diagnostic whose meaning a reuse could
corrupt, and a register carrying entries for conditions that can no longer
arise costs every reader — human and machine — the work of telling live codes
from dead ones. Remove the entry, remove the check, and let the number return
to the pool. A check the grammar shadows — one the parser refuses before it
can run — loses its entry and keeps its code in the crate, as a guard for an
AST built by hand, on check-diagnostic-parity's shadowed list with the
reason: a fallback arm that says nothing would be worse than one that names a
retired code. A code a model cannot reach but a host can stays registered,
under §7.11. The rules below take effect at 1.0, when saved artifacts start
to outlive the release that produced them:
- Do not reuse codes: once assigned, a code is never reused.
- Soft deprecation: a deprecated code may remain emitted for one minor version with a note.
- Hard deprecation: removal only in a major language version.
A documented code must be an emitted code, both ways. make check-diagnostic-parity compares this page against every code the crates and
the pack validations emit, and against the numbers the pack READMEs cite. A
promised diagnostic that never fires is worse than an undocumented one: the
repair catalog teaches an agent to expect a code that will not come.
9) CLI rendering (informative)
CLI tools SHOULD render diagnostics as:
- The severity and code, then the message:
error[E2103_SCHEDULE_OUT_OF_BOUNDS]: Stream 'debt.principal' schedule is outside model timeline ... - Then the location, when the diagnostic has a file:
--> <file>:<line>:<col>, the file shown under the model root the command was given. - Then the hint,
= hint: ..., and each note,= note: .... - A snippet with a caret underline is optional.
cfdl renders this form on stderr; --json prints the diagnostic objects
(§2) on stdout instead:
error[E2103_SCHEDULE_OUT_OF_BOUNDS]: Stream 'debt.principal' schedule is outside model timeline (timeline: 2026-01-01 to 2026-12-01).
--> fixtures/invalid/bad_schedule_out_of_bounds/model.cfdl:7:1210) Golden diagnostics files
For invalid fixtures, store expected diagnostics as:
gold/diag/<fixture>.diag.json
Rules:
-
Assert
code,severity,file, andspan. -
A fixture fails at compile unless it carries an
expectfile naming the stage its diagnostic fires at.expect=warn: the model compiles, and the golden is the IR'swarnings.expect=run: the model compiles and is run (with the fixture'srun.jsonandpackwhen present, from a scratch directory under fixed names so no path varies); the golden is the run's diagnostics when it fails, or its warnings when it succeeds, the results' code-prefixed strings and each statement's diagnostics in diagnostic form. A run that succeeds with no warning fails the fixture. The Python SDK's compile-error test covers only fixtures with noexpect. -
Messages are asserted in FULL. The golden runner compares canonical JSON and diffs it, so rewording a message changes a golden and must be re-blessed with
CFDL_GOLD_UPDATE=1.An earlier revision of this page said messages "may be asserted via substring match to allow minor wording changes". No runner has ever done that. The exact comparison is the better behavior and is kept deliberately: a diagnostic's wording is part of its contract with the reader, and a silent drift in what the compiler says is exactly as bad as a silent drift in what it computes. Making a reword show up in a diff is the point, not friction.