Cascade
PartialThe cascade lets an operator set a value once, high up, have it apply everywhere below while staying overridable on any one entity, and then explain exactly why a given value won.
What it resolves
Section titled “What it resolves”The effective config and variables (config and credentials), tags, and rule-sets for any entity, with a first-class resolve view explaining every effective value: the winning source and what it shadowed. The order is deliberately hand-tuned (not derivable from a single rule), so the resolve view is the safety net, not an afterthought.
User stories
Section titled “User stories”In operator terms:
- Set once, override where it matters. HQ’s devices need a different credential, so it is set at the HQ location and overrides the template for everything there, no template edit; a room three levels down inherits the nearest setting. (deployment beats template bindings; deepest location wins)
- Composition tightens the part. “Standard Huddle Room” polls codecs every 30s, tighter than the codec’s own template’s 60s, and every codec placed in a huddle room picks that up. (the system’s standard beats the component’s product)
- “Why did it get this?” The resolve view shows the “Old-firmware Room Kits” group (weight 450) shadowing the template’s 30s (worked example). (the effective-config resolve view)
The cross-cutting cases (a fleet-wide fix that auto-clears, a policy as a floor, a hand-picked set) are the group stories, placed by weight on this same scale.
The structural chain
Section titled “The structural chain”Broad decision to specific deployment; most-specific (deepest) wins:
platform the install-wide bindingproduct the leaf component's product bindingsstandard the owning system's standard bindingslocation tree earth -> luna -> port-lovell (deepest wins)system tree parent system -> subsystem -> ... (deepest wins)component tree chassis -> card -> ... (deepest wins, = the leaf)- Resolution runs for the leaf over its containment path; a non-leaf entity (a chassis, a floor) resolves over its own shorter subset.
platformis the least-specific rung, not a floor under the chain: an admin’s decision for the whole install, one rung like any other, written only with the install-wideplatform:<action>permission on top of the resource’s own (identity and access).- There is no root location. The location tree is a forest with N unparented tops, and a top is
an ordinary location: binding at
earthmissesmars, and a top added next quarter starts uncovered. A tier above today’s tops is a newlocation_typeand a real node, never a magic one; “everything” isplatform, not a location. - Philosophy: deployment beats the template. HQ’s location credential overrides what the Room Kit template binds; “Standard Huddle Room” overrides what the bare Room Kit binds.
- Templates are the leaf’s base. Ancestor nodes contribute their instance bindings, not their templates (a chassis hands a card its chassis-wide credential; the card keeps its own template).
- The three structural segments are variable-depth trees; the deepest node wins. Weight-free, pure depth.
Bindings cascade; declarations do not
Section titled “Bindings cascade; declarations do not”There are two structures here, and only one of them is the cascade.
The binding side is the cascade: every rung above is something somebody decided (a template author’s value, an operator’s at a location, an admin’s for the whole install), ordered by deployment specificity and folded most-specific-wins.
The declaration side is what a thing is absent any decision. A default lives there, a column on a definition row beside the unit, kind, and validation rule. It is not a rung: it shadows nothing and nothing shadows it. The cascade falls through to it when no rung bound anything, reported by the resolve view as a declaration, never a winning source: “an admin chose this for everything” and “nobody chose, so the type’s declaration stands” are different facts an operator needs to tell apart.
A default is a column on a definition row, so a kind with no definition table has no default. Absent means absent:
| Kind | Where its default is declared |
|---|---|
| setting | The tagged struct field on Settings (its default: tag). |
| property | The classifier contract’s default_value column (product_property, standard_property, location_type_property), the shipped instance of the pattern: EffectiveProperties reads coalesce(the instance's set value, the contract default) (ADR-0047). |
| variable | None. |
| secret | None. |
| tag | None. |
Note where a property’s default is not: the property catalog entry carries the data_type,
unit, and validation rule, but no value; a default is what a thing is under a given contract, and
the contract is the classifier’s, so the column lives on product_property and its two siblings.
The declaration side allows its own second ordering, a default narrowing along type specificity
(one product or standard declaring a different default for the same catalog property), never mixing
with the binding cascade. A catalog-wide default beneath the contract ones does not exist today; if
it lands, it lands as another declaration-side column, not a rung.
Combinators (by what is resolved)
Section titled “Combinators (by what is resolved)”- config / variables -> scalar override: the deepest/highest source wins; one value.
- tags -> union on name, override on value: names accumulate; for a given name, the winning source’s value wins.
- rules (
calc_rule/event_rule) -> additive accumulation + explicit suppression: a leaf is governed by the union of rules from every layer; a layer removes one by name with a suppression.
Groups overlay the cascade, placed by weight
Section titled “Groups overlay the cascade, placed by weight”The structural tree handles config by position and kind; groups handle it by attribute or hand-picked set, cutting across the tree. The cascade does not define groups; it consumes them by weight on the one specificity scale.
The comparison key is segmented, not a single number. Precedence is a lexicographic key
(segment_rank, depth, group_weight, creation_order), compared field by field: segment_rank
orders the structural bands (platform, templates, location / system / component trees, the instance
ceiling), depth orders within a variable-depth tree (deepest wins), group_weight places a group
relative to the structural bands, creation_order breaks an exact tie. Because the segment is the
first field, a structural segment never overruns into another however deep a tree runs or however
many group weights stack. The single specificity numbers elsewhere on this page (0, 100, the
300s, the 400s) are a presentation-only flattening of that key for the resolve view.
The registry is outside the cascade
Section titled “The registry is outside the cascade”property_type defines identity (kind, unit, validation, fusion_policy) for every property,
which the cascade never overrides (policy, not ontology). A type’s default lives on that
declaration too, off the cascade: what the value is when no layer bound anything, not a rung the
layers compete with, reported by the resolve view as a declaration.
Structural multi-membership (a component in N systems)
Section titled “Structural multi-membership (a component in N systems)”Distinct from group membership. A component belongs to zero or more systems through
system_member, and the
resolution takes the system to resolve against: given one, it resolves against that system, and
only if the component is a member of it (naming a system it has no binding to must not lend it
configuration). That is the “config differs per system” case, a per-system effective view
computed on demand rather than merged chains.
Asked with no system in hand, the chain is seeded from the component’s primary membership:
what is_primary is for, and the whole of what it is for, a default for context-free callers, never
a rule that overrides a caller who named a system. A component in exactly one system, which is
nearly all of them, never meets the distinction.
The seed stays single-valued whichever way it is chosen, a correctness requirement: the rank orders by band and then depth with no tiebreaker after that, so two seeds in the same band would resolve nondeterministically. Membership is many-valued; the chain it feeds is not.
Secrets carry no system band at all. A credential authenticates a session with the device itself, a shared component has one password, and the room it happens to serve is the wrong owner for it: an ownership decision, not a tiebreak, removing the case where an ambiguous inheritance would have been actively dangerous.
The resolve view
Section titled “The resolve view”For a target entity and a key, return:
- the effective value;
- the winning source (a tree node, or a group + its weight);
- the ordered shadowed bindings it beat (source + value).
When no rung bound the key, there is no winning source and nothing was shadowed: the view reports the declaration (“no binding; the type declares 60s”) as its own provenance, never as a bottom entry in the shadowed list.
For rule-sets: the accumulated set, each rule tagged with its source and any suppressions. One view explains both override (variables / tags) and accumulation (rules).
Worked example
Section titled “Worked example”RM204 codec (Room Kit Pro, fw 11.2) at Room RM204 -> Floor 3 -> HQ Building -> HQ Campus, in the
Huddle Room AV system. The fleet has four unparented tops (HQ Campus, East Campus, Airport
Office, Service Depot), so HQ Campus is a top, not a root. Member of two groups: Old-firmware Room Kits
(weight 450) and PCI-scope (weight 250). Specificity bands are illustrative (0 platform,
100/200 templates, 300s location by depth, 400s system by depth, 500 the instance), a
presentation flattening of the segmented key (segment_rank, depth, group_weight, creation_order),
not the comparison itself.
RM204 - cascade precedence most-specific (highest) wins============================================================================== spec source poll-interval credential retry_limit ---- -------------------------------- ------------- ---------- ----------- 500 component RM204 (explicit) - - - <- ceiling 450 group: Old-firmware Room Kits 5min * - - 440 system: Huddle Room AV system - - - 340 location: Room RM204 - - - 330 location: Floor 3 - vault-B * - 320 location: HQ Building - - - 310 location: HQ Campus (a top) - vault-A - 250 group: PCI-scope - vault-C - 200 standard: Std Huddle Room - - - 100 product: Room Kit Pro 30s - - 0 platform (admin, install-wide) 60s - -============================================================================== effective: poll-interval = 5min (group 450; shadowed template 30s, platform 60s) credential = vault-B (location Floor 3 @330; shadowed PCI-scope @250, HQ Campus @310) retry_limit = 3 (no binding at any rung: the declaration stands)The three columns are the point:
poll-interval: the Old-firmware group (450) sits above deployment, so its5minworkaround beats the template’s30sand the admin’s install-wide60s, what a fleet-wide bug fix needs.credential: the PCI-scope group (250) sits below deployment, so the specific Floor 3 (330) setting beats it, the case a fixed band would get wrong.retry_limit: nothing bound it anywhere, so the fold ends empty and the value falls through to the declaration:3is not aplatformbinding and shadowed nothing, it is what the type says a retry limit is.
Binding credential at HQ Campus covers HQ and nothing else: East Campus, Airport Office and
Service Depot are separate tops, and a fifth site added next year starts uncovered; only the
install-wide 60s at platform reaches all of them, which is why the tier exists rather than a
synthetic root location.
Service Depot is that argument made concrete rather than hypothetical. It was added to the seed
later than the other three, it is a building at the top with rooms straight off it (no campus
above, no floor between, which the type registry allows and the model has never required), and it
came up covered by exactly one thing: the platform binding. Every location-tier binding written
before it existed still misses it.
make dev seeds this fleet, so the rule is inspectable rather than illustrative: four unparented
tops with a device under two of them, one carrying the staging tag its subtree’s binding sets and
the other the prod the platform binding sets.
component RM204 (500) would top everything if it set a value directly. Additive rules accumulate
down this same ladder, and a group can suppress one by name (the Old-firmware group suppresses
the false-firing high_memory alarm).
Resolution, in one line
Section titled “Resolution, in one line”Build the entity’s ordered layer path, place matching groups on it by weight, fold variables (override) / tags (union + override) / rules (additive + suppress) down the combined specificity order, fall through to the type’s declaration where the fold is empty, and emit effective values with provenance.