Skip to content

Cascade

Partial

The 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.

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.

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.

Broad decision to specific deployment; most-specific (deepest) wins:

platform the install-wide binding
product the leaf component's product bindings
standard the owning system's standard bindings
location 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.
  • platform is 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-wide platform:<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 earth misses mars, and a top added next quarter starts uncovered. A tier above today’s tops is a new location_type and a real node, never a magic one; “everything” is platform, 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.

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:

KindWhere its default is declared
settingThe tagged struct field on Settings (its default: tag).
propertyThe 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).
variableNone.
secretNone.
tagNone.

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.

  • 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.

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.

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).

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 its 5min workaround beats the template’s 30s and the admin’s install-wide 60s, 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: 3 is not a platform binding 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).

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.