DMN decision model DSL (.dmn)
Overview
Section titled “Overview”A standalone decision-modeling surface implementing the DMN decision
architecture in the Phero Lang spirit: a .dmn file declares one decision
model whose central semantic structure is the Decision Requirements
Graph — the input data (the inputs block), the decisions, the
business knowledge models (reusable functions, the model declarations),
the knowledge sources (the authorities behind decisions), the decision
services (invocation contracts), and the three requirement kinds
(information, knowledge, authority) that wire the graph. Logic — a decision
table, a FEEL expression, an invocation of a business knowledge model — is
an implementation detail attached to a node, never the node itself: a
decision with no logic clause at all is a valid, first-class declaration
(the architecture-first reading of DMN, which minimal.dmn pins). Parsed by
phero-syntax (parse_dmn, the chumsky parser) at compile time, lowered
and validated by phero-driver (compile_dmn_str) against the imported
.mox domains, and emitted as the versioned phero_ir::dmn::DmnModel
artifact (the phero-ir crate).
No in-tree backend exists yet: downstream consumers ingest the artifact
exactly like the DddModel, EventModel, and UseCaseModel artifacts.
The normative grammar lives exactly once — the .dmn decision-model
sources section of the parser module docs
(crates/phero-syntax/src/parser/mod.rs); the normative compile contract —
entry points, resolution semantics, and validation rules — is documented on
phero_driver::compile_dmn_str (crates/phero-driver/src/lib.rs). This page is
the language reference; when prose and either authority disagree, they win.
File layout and imports
Section titled “File layout and imports”A .dmn file is import declarations followed by exactly one
decision_model. The fence below compiles from this document (the docs
fences are compiled by CI); it needs no imports to be valid:
decision_model Approval { namespace nz.example.approval;
inputs { amount: int; applicant: string; }
decisions { /// The desk-level auto-approval gate. decision auto_approve { output boolean; logic expression "amount <= 5000" } }}- One model per file: the
decision_modelis the file’s single top-level declaration; the file name is the artifact’s identity. - Imports come first, one per line, with the canonical
;. An import after thedecision_modelis a reported, recovered syntax error, the.evtrule..dmnfiles import.moxdomains and.evtevent contracts — never.dddor other.dmnfiles (the architectural rule, see Cross-surface integration). - Resolution: each import names a provided
.moxdomain, matched by exact path or lexical resolution relative to the.dmnfile’s directory (the.deployrule), or a provided.evtcontract, matched by exact path — the import string as written, the table-path rule. An import no provided entry names is the errorimported file "<path>" was not provided. With no imports, the model compiles against the empty domain union — every type must then be a primitive (minimal.dmnpins that shape). - Imported domains lower through the ordinary domain pipeline against the
union namespace; input, output, and parameter types resolve there.
Imported contracts join the compile’s events bucket — the declared
events the decisions’
emitsentries resolve against (rule 14 below).
Grammar
Section titled “Grammar”The normative EBNF for the .dmn surface lives exactly once, in the parser
module docs under .dmn decision-model
sources
(crates/phero-syntax/src/parser/mod.rs) — the single authority for
dmn_import, decision_model_decl, model_member, and every nested
production; this page keeps only the language-level rules.
Language-level rules:
- Members in canonical order: the
decision_modelbody reads metadata (namespace,id,version,effective,jurisdiction— each at most once, each a singleton regardless of source order) theninputs, thenknowledge(sources before models), thendecisions, thenrequirements(information, knowledge, authority groups in that order), thenservices, thenviews. Members may be written in any source order; the AST groups them per kind and the formatter canonicalizes. A decision body readsoutput/outputs, thenlogic, thenemits— the same any-source-order, canonical-emission discipline. - Statement lines end in
;— metadata entries, typed entries (name: Type;), requirement lines,descriptionanddirectionclauses, and invoke bindings.logicclauses carry no;: a table or expression clause ends at its closing string, an invocation at its closing brace, and the formatter emits exactly that whether or not the source had one. - Names are unique across all sections: inputs, knowledge sources, knowledge models, decisions, services, and views share one namespace per model (rule 2 below), so every reference stays unambiguous.
- Types are the
.dmnprimitive set —string,int,boolean,date, spelled lowercase — or an imported domain type by its bare class/enum name (rule 4 below). - Doc comments: a
///line directly above adecision,model,source,service, orviewheader becomes that element’s description, carried in the artifact. Plain//comments are preserved verbatim bypherolang fmt. - Name lists (
inputs/outputs/encapsulatesof a service,showof a view) are brace lists of bare names — inline when they fit comfortably, one name per line past the column budget.
Vocabulary
Section titled “Vocabulary”Every .dmn word is a contextual identifier (the .deploy
discipline): special only in its grammar position, an ordinary name
everywhere else, escapable with ^ (crates/phero-syntax/src/dmn.rs).
version is the surface’s one real lexer keyword (the .mox vocabulary
precedent); import is the shared one every surface has.
One trap: agent is a real .actor lexer keyword, so it can never be a
.dmn input, decision, or service name unescaped — support.dmn names its
input helper for that reason. Element names share one namespace per model
(rule 2 below), so the restriction reaches every section.
Inputs
Section titled “Inputs”inputs { age: int; other_super_fund: SuperFundType;}The graph’s input data: named, typed values decisions consume. Names
are snake_case elements of the model’s single namespace; types resolve per
rule 4 and lower to the domain IR’s TypeRef (qualified) in the artifact.
Inputs are the only nodes nothing is required of: a declared input no
decision requires is legal (a DRG may carry data beyond one evaluation’s
reach), while the reverse — a requirement naming an undeclared input — is
rule 5’s error.
Knowledge: sources and business knowledge models
Section titled “Knowledge: sources and business knowledge models”The knowledge block holds the two knowledge kinds, sources first.
Knowledge sources are the authorities decisions answer to:
source KiwiSaverAct { name "KiwiSaver Act 2006"; reference "https://www.legislation.govt.nz/act/kiwisaver2006";}name and reference are verbatim labels carried into the artifact, never
interpreted — no fetch, no format check (the .evt governance-label
discipline). Sources enter the graph only through authority
requirements (governed_by); an undeclared source is rule 7’s error.
Business knowledge models are the reusable functions decisions invoke:
/// Employer superannuation contribution tax: the tax on the/// employer's contribution at the given rate.model calculate_esct { parameters { employer_contribution: int; rate: int; } output int; logic expression "employer_contribution * rate / 100"}A BKM is parameters (typed, like inputs), one output type, and one
logic clause — the same three logic kinds a decision takes. It has no
requirements of its own and never appears in a decision’s outputs: it is
applied through a decision’s logic invoke (below), which is the only
edge into it (rule 11; an invokes naming no declared model is rule 6’s
error).
Decisions and output contracts
Section titled “Decisions and output contracts”/// The pay-run contribution picture.decision calculate_contributions { outputs { employee_deduction_required: EmployeeDeductionRequired; employer_cec_amount: int; esct_amount: int; compliance_status: ComplianceStatus; } logic table "./tables/contributions.md"}A decision declares an output contract and (optionally) logic and outcome events:
output <Type>;— the single-output form. The output has no name of its own; consumers read the decision’s value under the decision’s name, and an external decision table labels its one output column freely.outputs { name: Type; ... }— the multi-output form. Names are FEEL names: an external decision table’s output columns must match them character-for-character, and aninvokebinding may name them (below).emits { <entry>; ... }— the outcome events the decision publishes (the DMN → EVT edge, below).- No output at all is a parse error — a decision that produces nothing
is not a decision; no
logicis deliberate: a logic-less decision declares a node whose implementation is deferred, the architecture-first contractminimal.dmnpins (decision draft { output boolean; }).
The logic clause is one of the three kinds below; a decision may carry
it at most once.
Logic kinds
Section titled “Logic kinds”logic table "<path>"
Section titled “logic table "<path>"”The decision’s rules live in an external markdown decision table; the
path resolves relative to the .dmn file’s directory. The .dmn declares
the contract (which inputs the table may test, which outputs it must
produce), the markdown file implements the rules — the split keeps logic
reviewable in isolation and lets the same table serve documentation and
execution. Compile-time checks are rule 13 (provided, parseable, signature
exact); the format itself is the next section.
logic expression "<FEEL>"
Section titled “logic expression "<FEEL>"”The decision’s value is the FEEL expression text, stored verbatim
(interior spacing is never normalized, so pherolang fmt round-trips it
byte-identically). The driver syntax-checks the text (rule 12) and stops
there — no deep typing at compile time (see Deviations). Conditional
(if) expressions gate like every other expression: the rev-pinned
dsntk fork parser accepts the full DMN FEEL conditional grammar (the
fork fix for dsntk-rs#14). The intended discipline is that an
expression reads the decision’s own information requirements — its
required inputs, or required decisions by name (whose named outputs may be
read individually) — but name-level checking of expression text is a
documented deferral.
logic invoke <model> { <param>: "<FEEL>"; ... }
Section titled “logic invoke <model> { <param>: "<FEEL>"; ... }”The decision applies a business knowledge model; each binding maps one of
the model’s parameters to a FEEL expression string. Bindings must cover
every parameter and bind no unknown names (rule 11) — a binding name
resolves to a declared input, a required decision, or a named output of a
required decision, exactly the information-requirement universe. Invoking
an undeclared model is rule 6’s error.
decision esct_calculation { output int; logic invoke calculate_esct { employer_contribution: "employer_cec_amount"; rate: "esct_rate"; }}DMN → EVT: the emits block
Section titled “DMN → EVT: the emits block”A decision may map its outcome to declared events — the .evt
contracts (docs/EVENTS.md). Consumers name events, never their
payloads:
decision calculate_contributions { outputs { ... } logic table "./tables/contributions.md" emits { ContributionRecorded when "compliance_status = \"compliant\""; VoluntaryContribution; }}An entry is EventName; or EventName when "<FEEL>"; — the when
condition is optional, its FEEL text a quoted string, and every entry
carries the canonical ;. Names are bare (single-segment) and
resolve union-wide against the declared events of every .evt
contract the file imports (rule 14): the model names what its outcome
publishes, the contract owns the payload — no field wiring crosses the
edge, exactly the .ddd service-operation emits shape.
The FEEL gate applies to a when text like any logic expression (rule
12, the same conditional deferral). The loosely-coupled fallback: a
.dmn whose host provides no event contracts — an empty events bucket
and no .evt imports — lowers its emits entries as authored,
unchecked; the names become the record a consumer fills in by providing
the contracts. A .dmn that declares an import "<path>.evt" line is
held to it: the import must resolve (rule 1) and the names must resolve
against the provided contracts (rule 14).
Requirements: the three groups
Section titled “Requirements: the three groups”The requirements block wires the graph. Each group holds Name <connector> Name; lines; the three connectors are the three requirement kinds of the
DMN specification:
information—decision requires X;: the decision needs X’s data or X’s result before it can evaluate. The left side must be a decision; the right side resolves to a declared input or decision (rule 5). Information requirements are what make the graph a graph — and what the acyclicity check (rule 8) runs over. They also fix a table logic’s input signature (rule 13): alogic tabledecision’s information-required inputs are exactly the table’s input columns.knowledge—decision invokes model;: the decision applies a business knowledge model. The right side must be a declaredmodel(rule 6), and the decision’slogic invokemust exist and bind (rule 11) — the requirement states the dependency, the logic states the call.authority—decision governed_by source;: the knowledge source governs the decision. The right side must be a declaredsource(rule 7).
Groups may repeat or interleave in source order; the formatter canonicalizes to information, knowledge, authority.
Decision services
Section titled “Decision services”/// The payroll-facing contract: assess one employee's contributions/// for a pay run.service AssessContributions { inputs { ... } outputs { calculate_contributions esct_calculation } encapsulates { ... }}A decision service is the invocation contract a caller binds to — the
DDD-facing seam (below): inputs names the model’s inputs the caller
supplies, outputs names the decisions whose results the caller receives,
and encapsulates names everything inside the service boundary (decisions,
models, sources). Every name in all three lists must resolve (rule 9);
services are PascalCase by convention, elements snake_case. A service is a
naming and invocation boundary only — no logic, no requirements of its own.
Versioning
Section titled “Versioning”The model’s version metadata line is the compatibility contract: a
decision model at version X.Y.Z (strict semver — three numeric
dot-separated parts) states its service contracts’ generation, and the
authoring rule is same major = compatible: a breaking change to a
service’s inputs or outputs MUST bump the major. The compiler does not
diff signatures across versions — the contract is the inputs/outputs
the service declares, and the convention is enforced at authoring time.
Consumers pin what they invoke: a .ddd invokes entry, an
inject decision line, or a .cmmn decision task’s invokes clause
may carry version compatible (the current generation: the highest
semver in the highest major present in the decision bucket) or
version "X.Y.Z" (exactly that generation). Resolution rules:
- No pin — the reference resolves by name alone (the rule-17 behavior, unchanged).
version "X.Y.Z"— the same-name service whose model declares that exact version resolves; zero matches is E0252 (naming the versions available in the bucket), two or more candidates declaring it is E0253 (ambiguous, naming the models).version compatible— the highest declared version in the highest major present resolves; models whose version is absent or not strictX.Y.Zcannot satisfy a pin (E0254 when none can).
The conformance fixtures carry the regulated lending pair:
lending-1.dmn (version "1.5.0") and lending-2.dmn
(version "2.0.0" — the major bump), the two versioning consumers the
resolution rules resolve against.
view overview { description "The KiwiSaver decision requirements graph: eligibility, contributions, and ESCT."; show { ... } direction left_to_right;}A view declares presentation intent for the graph: description is
prose, show names the elements the view displays (decisions, models,
sources — any declared element, rule 10), direction is an opaque
layout label (left_to_right / top_to_bottom are the conventions;
the word is carried verbatim). Views are not a DRD/DI diagram
format — no coordinates, no edges beyond the requirements the graph
already declares (see Deviations).
Artifact wire format
Section titled “Artifact wire format”DmnModel is a standalone, versioned wire artifact following the phero-ir
wire format contract (see also the phero_ir::dmn module
docs):
- Versioned via
DMN_MODEL_FORMAT_VERSION(= 1), theActorModelprecedent.DmnModel::from_jsonrejects absent/wrong versions withIrError::UnsupportedFormatVersion(an absent version reports as 0). - Struct fields serialize as
camelCase; type references reuse the domain IR’sTypeRefunder adjacent tagging ({"type": "<variant>", "value": ...}), no parallel wire-level type shape — theEventModelprecedent. - Names and reference strings are kept as authored: requirement lines,
invoke bindings,
emitsentries, service/view lists, table paths, and source labels are plain strings; resolving them is a driver concern, never a wire concern. - A logic-less decision serializes with no
logickey at all, and every collection field is#[serde(default)]and omitted when empty — a model reduced to metadata serializes as exactly{"formatVersion":2}, byte-identical no matter what is added later. Unknown fields are ignored on deserialize.
Excerpt of the committed golden artifact
(tests/conformance/dmn/minimal.dmn.json) — the metadata, one input, and
the logic-less decision (key order follows the emitter’s canonical order):
{ "formatVersion": 2, "name": "Minimal", "namespace": "nz.example.minimal", "inputs": [ { "name": "flag", "type": { "type": "primitive", "value": "boolean" } } ], "decisions": [ { "name": "draft", "outputs": [ { "type": { "type": "primitive", "value": "boolean" } } ] } ]}External decision tables
Section titled “External decision tables”A logic table "<path>" points at a markdown file in the dsntk decision
table shape: optional > header lines, then exactly one table. The
first header cell is the hit policy (U, A, P, F, C, C+,
C#, C<, C> — carried verbatim; semantics are the table engine’s
concern, not the driver’s); the second row marks each column’s clause —
`i` input, `o` output, `ann` annotation. Cell values are
FEEL unary tests: strings quoted ("Compliant"), ranges (<10, >=10,
[1..5]), - the wildcard. Annotation columns are supporting prose,
never outputs — the classic ingest defect this contract exists to
prevent is an annotation column typed as a result.
> # Contribution eligibility> Whether the employee's deduction is required (KiwiSaver Act 2006, s 5A).
| U | age | member | Eligibility ||:-:|:----:|:------:|:-----------:|| | `i` | `i` | `o` || 1 | <18 | - | "No" || 2 | >=18 | false | "No" || 3 | >=18 | true | "Yes" |The division of labor is the contract’s point: the .dmn declares the
signature, the markdown implements the rules, and the compile-time check
(rule 13) holds the two together — the table’s input columns must be
exactly the decision’s information-required inputs and its output columns
exactly the declared outputs, each name matching character-for-character
(column names are FEEL names). A single-output decision names no output,
so its one output column’s header is a free label. Tables are plain data
files: pherolang fmt formats the .dmn, never the markdown.
compile_dmn_str’s tables parameter carries the contents keyed by the
path as written — hosts resolve the path relative to the .dmn file, the
driver never touches the filesystem (the sigil/DomainImports
discipline).
Resolution & validation
Section titled “Resolution & validation”Any error-severity diagnostic anywhere (the .dmn file, an imported
domain, a referenced table) drops the artifact; warnings do not. The
authoritative, numbered contract is the Validation rules section of
compile_dmn_str (crates/phero-driver/src/lib.rs); the same fourteen in
brief:
- Imports — every import names a provided
.moxdomain (matched by exact path or relative resolution) or a provided.evtcontract (matched by exact path); missing names are the errorimported file "<path>" was not provided. - Uniqueness — element names are unique across all sections (inputs, knowledge sources, knowledge models, decisions, services, views): one namespace per model, so every reference kind stays unambiguous (E0204).
- Metadata singletons —
namespace,id,version,effective, andjurisdictioneach appear at most once (E0214); all five are optional, and none is interpreted beyond the string carried into the artifact. - Typing — every input, output, and parameter type resolves to a
.dmnprimitive or an imported-domain type by bare name; unknown or ambiguous names error (E0200). - Information requirements — the requiring side is a decision, and every required name resolves to a declared input or decision (E0201).
- Knowledge requirements — every
invokestarget is a declared knowledge model (E0202). - Authority requirements — every
governed_bytarget is a declared knowledge source (E0203). - Acyclicity — the decision information graph is acyclic: a decision never requires itself, directly or through the chain (E0205).
- Decision services — every
inputs,outputs, andencapsulatesname resolves against the declared elements (E0206). - Views — every
showelement resolves against the declared elements (E0207). - Invocations — a
logic invokenames a declared model (else E0202), covers all of its parameters, and binds no unknown names (E0213): a binding name resolves to a declared input, a required decision, or a named output of a required decision. - Expressions —
logic expressiontexts, invoke binding expressions, andemitswhentexts are syntactically valid FEEL (E0208); stored verbatim even when checking fails, the.cmmnwhenprecedent. Conditional (if) forms gate like every other expression: the rev-pinned dsntk fork parser accepts the full DMN FEEL conditional grammar (the fork fix for dsntk-rs#14). - Decision tables — every referenced markdown table is provided (E0209), parses in the dsntk shape (E0210), and its clause signature matches the decision’s declared contract exactly: input columns equal the decision’s information-required inputs (E0211), output columns equal its declared outputs (E0212), names character-for-character.
- Emitted events — every
emitsentry of every decision names a declared event of the provided.evtcontracts, pooled union-wide across all of them (E0215, with a did-you-mean help line naming the declared events). An empty events bucket with no.evtimports is the blessed loosely-coupled behavior: the entries lower as authored, unchecked.
Diagnostic codes
Section titled “Diagnostic codes”Every .dmn diagnostic carries a stable code in the dmn.check band:
E0200-E0215 are the surface codes (typing through table signature and
emitted events, rule by rule as above), and E0250/E0251 are the
.ddd decision-link codes fired on the design side of the
integration below. Run pherolang explain E0205 for one entry,
pherolang explain --list for the ledger; the registry lives in
docs/DIAGNOSTICS.md.
Cross-surface integration
Section titled “Cross-surface integration”.mox — the type universe
Section titled “.mox — the type universe”DMN depends on .mox domains and .evt event contracts (never a
design or policy artifact): inputs, outputs, and parameters resolve
against the union namespace of the imported domains (rule 4), lowered
through the ordinary domain pipeline. This is the architectural rule of
the surface: a decision model consumes domain types and declared events,
never a design or policy artifact.
.evt — the emits edge
Section titled “.evt — the emits edge”The arrow points from decisions to contracts. A decision’s emits block
names the declared events its outcome publishes (the DMN → EVT
section above); the .evt contracts own the
payloads. The compile joins the two when the host provides the compiled
contracts (rule 14, E0215) and stays loosely coupled otherwise — an empty
events bucket lowers the names as authored. The edge is deliberately
one-way at the contract level too: an .evt file never references a
.dmn model, so decisions can start emitting without touching the
contracts, and contracts can evolve without touching the decisions.
.ddd — the decision link
Section titled “.ddd — the decision link”The arrow points one way. A .ddd service operation may link to a
decision service: the operation declares which decision service it
applies (invokes on the operation, inject decision in the module
design — see docs/DDD.md for the design-side contract), and the two
decision-link codes E0250/E0251 diagnose a link that names no declared
decision service or disagrees with the operation’s signature. The .dmn
side of the link is inert by construction: a decision model cannot import
a design, so a design can always be added, replaced, or removed without
touching the decisions it calls.
CLI usage
Section titled “CLI usage”pherolang check kiwisaver.dmn # validate + imported domains + tablespherolang ir kiwisaver.dmn -o kiwisaver.dmn.json # emit the DmnModel artifactpherolang fmt --check kiwisaver.dmn # canonical formatting gateTry it on the canonical fixtures from the repository root:
cargo run -p phero-cli -- check tests/conformance/dmn/kiwisaver.dmncargo run -p phero-cli -- ir tests/conformance/dmn/minimal.dmn -o minimal.dmn.json- Import paths and table paths resolve relative to the
.dmnfile’s directory; the CLI reads each imported.moxfrom disk, compiles each imported.evtthrough the ordinary event pipeline (the contract’s own imports resolve next to it), and the referenced markdown tables with them. A missing import file or table is a clean error naming the resolved path. - Diagnostics come from every file involved (the
.dmnfile, its imported domains, its tables) and render grouped per file, the way.evtcompiles render them. - Directory batch mode (
pherolang check <dir>,pherolang build <dir>) picks.dmnfiles up like every other surface: each model compiles independently, imports resolved relative to it, andbuildemits one<input path>.jsonartifact per file under the out directory. pherolang fmtformats.dmnfiles with the canonical layout: imports hoisted into a tight first section, then thedecision_modelwith members in the canonical order above, canonical;on every statement line,logicclauses without one, name lists inline when they fit comfortably and one name per line past the column budget, and exactly one blank line between sibling blocks and declarations.kiwisaver.dmnis the layout reference. Stdin is treated as.mox.
Deviations from OMG DMN 1.6
Section titled “Deviations from OMG DMN 1.6”The surface follows the DMN decision architecture (DRG, the three requirement kinds, hit policies) and deviates deliberately where Phero Lang’s model demands it:
- A text surface, not XML — one
decision_modelper file, no DMN XML schema, no model interchange between files. - FEEL is stored, not executed — expression texts are strings,
syntax-checked at compile time and never deeply typed; the type
discipline Phero Lang applies to its own expression surfaces (
whenguards, policies) is not imposed on FEEL. - Decision tables are external markdown — the dsntk shape, one table
per file, not boxed expressions inside the model; the
.dmncarries the signature, the markdown the rules. - Types come from
.mox, not item definitions — no FEELitemDefinitionmachinery; the domain model is the type universe. - No DRD/DI — no diagram interchange;
viewsdeclare presentation intent (what to show, which direction), not graphics. - No evaluation engine in-tree — the artifact carries the architecture; execution is a backend/consumer concern (the table engine, the FEEL evaluator, are downstream choices — dsntk compatibility is the table format’s north star, not a dependency).
The dsntk crates the surface does use (dsntk-recognizer for table
ingest, dsntk-feel-parser/dsntk-feel for syntax) are rev-pinned in
[workspace.dependencies] to one fork revision — the sigil/restingstate
discipline — so a compiler build never floats on an upstream release; the
pin carries the dsntk-rs#14 conditional-expression fix.
7. emits names events, never payloads — a decision maps its outcome
to declared .evt events by bare name (the DMN → EVT edge); no
payload wiring, no event-versioning or delivery contract crosses the
edge, and a host that provides no contracts gets the loosely-coupled
record (the names lower as authored, unchecked). The .ddd operation
emits clause carries the same discipline.
Conformance
Section titled “Conformance”tests/conformance/dmn/ holds the canonical fixture set: kiwisaver.mox
(the NZ-payroll domain), kiwisaver.dmn (the flagship model — all five
metadata entries, nine inputs mixing primitives and domain types, two
sources, one invoked BKM, three decisions covering all three logic kinds,
an emits block on the table decision, all three requirement groups, a
decision service, and a view; it is the formatter’s layout reference),
kiwisaver.evt (the contract declaring the emitted ContributionRecorded
and VoluntaryContribution events over the same domain), the two
referenced tables tables/eligibility.md and tables/contributions.md
(the latter exercising the four-output signature and the annotation
column that is not an output), and minimal.dmn (the architecture-first
minimum: no imports, one input, one logic-less decision).
The examples/ side ships support.dmn (the support-desk refund
decisions over support.mox: a domain-typed Ticket/User input pair,
an expression decision, a table decision with its
examples/support/tables/refund-authority.md, an invoked BKM composing the two,
the AssessRefund service the design links to, and an emits block on
RefundAssessment naming the RefundApproved event declared in
examples/support/support.evt) and lending.dmn
(the architecture-first lending model with no imports: five decisions,
two BKMs, three sources, two views, and examples/lending/tables/lvr.md).
Regenerate the goldens with
PHERO_UPDATE_FIXTURES=1 cargo test -p phero-driver --test dmn_golden (the
fixture artifacts kiwisaver.dmn.json and minimal.dmn.json) and
PHERO_UPDATE_FIXTURES=1 cargo test -p phero-driver --test examples_artifacts
(the example artifacts examples/artifacts/support.dmn.json and
examples/artifacts/lending.dmn.json).