Build a Rule-Driven Tax Program
This tutorial builds a small, synthetic tax policy from types and rules, then uses it in a scenario, traces it to typed metadata, and audits an actual same-rule contradiction. It is a language example, not Danish tax law.
You will use three files already included in the repository:
examples/tutorial_tax.runacontains the reusable model.examples/tutorial_tax.scenario.runacontains one concrete case.examples/tutorial_tax.audit.runacontains an intentional same-rule contradiction.
Core language syntax is Stable. The metadata index is Preview, and the exploratory audit command is Experimental.
1. Run One Rule
A rule starts with |. A named exception with a satisfied under condition
takes precedence over the default rule.
# Child(age: Int)
# Person(annual_income: Int, children: List(Child))
| tax_due(person: Person) -> person.annual_income / 4
| exception two_children tax_due(person: Person) -> person.annual_income / 5 under length(person.children) >= 2
= parent = Person(
annual_income = 500000,
children = [Child(age = 5), Child(age = 8)]
)
@ print(show(tax_due(parent)))
Save that as tax.runa, then run it:
runa run tax.runa
The output is 100000: the exception selects the 20% rate for this family.
2. Give Rules a Shared Case
Real models quickly gain several rules that need the same facts. A product RuleScope keeps those facts together and lets its rule members call each other:
# TaxCase(person: Person) {
| rate_percent() -> 25
| exception two_children rate_percent() -> 20 under length(person.children) >= 2
| tax_due() -> person.annual_income * rate_percent() / 100
}
Constructing TaxCase makes the rules active for one concrete person. Calling
family_tax.tax_due() follows the rule cascade through rate_percent():
= family_tax = TaxCase(person = parent)
= due = family_tax.tax_due()
The constructor uses named arguments, and each member has direct access to
person. This avoids threading the same parameter through every rule.
3. Attach Traceable Metadata
Metadata stays ordinary, typed Futuruna data. The meta comment attaches one
value to a label, while the matching begin and end markers identify the
code covered by that label.
# SourceInfo(title: String, url: String)
# TutorialRuleMetadata(source: SourceInfo, target: ProgramReference)
# impl Meta for TutorialRuleMetadata {}
= source = SourceInfo(
title = "Synthetic family tax policy",
url = "https://futuruna.com/docs/tutorial"
)
= metadata = TutorialRuleMetadata(
source = source,
target = refof(TaxCase::tax_due)
)
--@label:family_tax_rule::meta:metadata--
----
Synthetic tutorial policy: income is taxed at 25%, except that a person with
at least two children pays 20%.
----
--@begin:family_tax_rule--
# TaxCase(person: Person) {
| rate_percent() -> 25
| exception two_children rate_percent() -> 20 under length(person.children) >= 2
| tax_due() -> person.annual_income * rate_percent() / 100
}
--@end:family_tax_rule--
ProgramReference and refof(TaxCase::tax_due) make the target structural and
checked against the program. Index the canonical example with:
runa meta examples/tutorial_tax.runa
The metadata does not change the calculation. It gives tools a typed route from an explanation or source to the code that implements it.
4. Prove a Concrete Scenario
A .scenario.runa file imports the reusable model, supplies facts, and proves
the expected result:
@ import ./tutorial_tax
= parent = Person(
annual_income = 500000,
children = [Child(age = 5), Child(age = 8)]
)
= family_tax = TaxCase(person = parent)
= due = family_tax.tax_due()
| two_child_tax_is_100000: due -> due == 100000
? two_child_tax_is_100000
@ print(show(due))
Run the checked-in scenario:
runa run examples/tutorial_tax.scenario.runa
The proof rune ? fails the run if the expected amount stops being true.
5. Audit an Actual Contradiction
The reusable tax model is coherent. To exercise the experimental auditor, this separate fixture adds two exception branches that answer the same question for the same family:
@ import ./tutorial_tax
= parent = Person(
annual_income = 500000,
children = [Child(age = 5), Child(age = 8)]
)
= family_tax = TaxCase(person = parent)
| exception family_policy reduced_rate_applies() -> family_tax.rate_percent() == 20 under length(parent.children) >= 2
| exception audit_only_income_exclusion reduced_rate_applies() -> False under parent.annual_income >= 500000
Run the exploratory audit:
runa audit examples/tutorial_tax.audit.runa
Both conditions hold: the parent has two children and earns 500000. The
family_policy branch returns True, while the audit-only exclusion returns
False. Because both are active exceptions for the same zero-argument rule,
the report identifies one contradiction.
Source order still gives execution a deterministic result, but the audit exposes the incompatible equal-priority outcomes. Separate rules are never paired by their names: a semantic contradiction must occur within one rule identity and one concrete evaluation environment.
Keep Going
Use the language reference for exact syntax, open the playground for quick experiments, or continue through the longer tutorial sequence with Hello, Futuruna.