aontu

Provide defaults that callers can override

Write a default in a disjunction with the type an override must satisfy, and layer defaults by rank.

Rendered from docs/how-to/provide-defaults.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

A default that nothing can override is a constant, and a default that anything can override is a bug with a delay on it. The disjunction holds the ground between: write the default beside the type an override must satisfy:

timeout: *30|integer # 30 unless overridden
{ "timeout": 30 }

The * marks the preferred branch, so with no other statement the document generates 30. A later timeout: 60 (in the same file or merged from another) takes its place:

timeout: *30|integer
timeout: 60
{ "timeout": 60 }

An override is admitted only if an alternative of the disjunction accepts it or it equals the preferred value, so timeout: 1.5 is refused. Write both lines as timeout.aon:

timeout: *30|integer
timeout: 1.5
$ aontu timeout.aon
[aontu/empty]: Cannot unify values at path $.timeout
...
 Cannot unify value: 1.5 with value: *30|integer
...
$ echo $?
1

The branch is doing the admitting, which means it is also the dial: *30 | number is how you ask for a default that any number may override:

timeout: *30|number
timeout: 1.5
{ "timeout": 1.5 }

A bound goes inside the branch the same way:

replicas: *2|(integer & min(1) & max(24))
replicas: 12
{ "replicas": 12 }

Unset, this field generates 2; 12 is admitted; 40 must be refused, because a fleet that silently accepts replicas: 40 has no policy at all. Check it with replicas.aon:

replicas: *2|(integer & min(1) & max(24))
replicas: 40
$ aontu replicas.aon
[aontu/empty]: Cannot unify values at path $.replicas
...
 Cannot unify value: 40 with value: *2|integer&min(1)&max(24)
...
$ echo $?
1

A lone *5 with no | is just a default 5 and needs none of this, and the spelling integer & (*30 | integer) means the same as *30 | integer.

Layer defaults by rank

When several layers of authority each want to supply the default, stars decide. The fewer the stars, the stronger the default (* beats ** beats ***) and a concrete value beats them all, so an organisation writes ***, a team **, an environment *:

logLevel: ***info # org
logLevel: **debug # team
logLevel: *warn # environment
{ "logLevel": "warn" }

No priority table, no merge order: the three statements can arrive from three files in any order and warn still wins. Two defaults of the same rank that disagree refuse as [aontu/pref_rank_clash], whose hint names the fix, so two teams cannot both claim the same rung silently.

Ranks also order the arms of one disjunction, and there the ladder survives elimination: *1 | **2 generates 1, and generates 2 once something rules 1 out. Adding a rung is therefore safe: it cannot take the rungs above it with it.

The marker is specified in Preference / default *. Two live versions run this at scale: the feature-flag catalog arbitrates org, then environment, then tenant over the same rank ladder, and the deployment fleet layers org, team, and environment files the same way. Offering a default is also how a referenced base lets its referrers differ (see reference and reshape) and when the value is already pinned concrete, overriding means changing a pinned value instead.