How Decisions Become Network State
CU-IP, the Intelligence Plane, emits decisions: a PCI to assign, a neighbor relation to add
or remove. Nothing acts on a decision but the controller, and this page is what it does with
one. The Intelligence Functions themselves are on the
CU-IP page; their tuning is chart values under racora-cu.cuIp.
The Loop
Every NRCell has its own timer, firing every racora-controller.decisions.pollIntervalS
(default 1 s). Each tick fetches CU-IP's active decisions over its Flight endpoint
(/decisions/active), keeps the ones aimed at this cell, and dispatches each by its function
(pci, anr) and the spec field it targets. The routing is function-agnostic; what differs
per function is data, described below.
The Gates
A decision is applied only when every gate passes on this tick. Each has a name that appears
in the controller's log, and as the dispatch.outcome attribute of the dispatch.consider
span once the decision reaches the live re-check (a decision the first quick check skips
emits no span):
| Gate | Skips when | Outcome |
|---|---|---|
| target | the decision is not for this cell or its field | skipped_target_mismatch |
| stabilization | the cell's last applied change for this function is younger than decisions.minStabilizationS (default 30 s); PCI falls back to status.pciAssignedAt when no watermark exists | skipped_not_stable |
| freshness | the decision is older than that last applied change, so it describes the state before it | skipped_stale |
| confidence | the decision's confidence is below decisions.minConfidence (default 0.95; deterministic functions emit 1.0) | skipped_low_confidence |
verify-current (pci, anr) | the live spec value (spec.pci; spec.neighbors, order-insensitive) differs from the current_value the decision was computed from, so a concurrent operator edit is not overwritten | skipped_stale |
| precondition | the decision carries a precondition no handler is registered for, or its handler could not satisfy it on this tick | skipped_precondition_unsupported, skipped_precondition_failed |
decisions.perFunction overrides the stabilization and confidence knobs per function.
The stabilization gate also absorbs the re-emission window after an apply: CU-IP keeps
emitting the same recommendation until it observes the new spec.
Before and After the Change
- Preconditions act. A PCI decision carries
cell_locked: the handler sendscell_lockto the CU-CP, which bars the cell, releases its UEs and deactivates it, before the spec is patched. ANR decisions carry no precondition. - The patch is an ordinary write to the cell's spec (
spec.pci,spec.neighbors), the same field an operator would edit. - After the patch, per function:
pcirebuilds the DU configuration, so the controller waits for the DU rollout to finish and then sends the explicitcell_unlock. If the cell is declaredadminState: Locked, the unlock is withheld and the cell stays down, as declared.anrneeds neither: the neighbor change flows to the running CU-CP through the same mobility sync as an operator edit. A function without a registered policy gets nothing extra and a warning in the log.
The Audit Trail
Every apply stamps status.lastApplied[<function>] with the time and the decision id. That
is what the stabilization and freshness gates read back, which is what makes a one-second
poll loop single-shot per decision. Functions add their own fields:
- PCI writes
status.lastRetune, setsstatus.pciSourcetocontroller-retunedand refreshesstatus.pciAssignedAt. - ANR rewrites
status.neighbors[]with provenance: every relation carriessource: manual|anrandappliedAt.
Both are on kubectl get nrcell <name> -o yaml; the fields are in the
NRCell API.
Who Owns a Neighbor Relation
Provenance decides what ANR may touch:
- Relations you declare are
source: manual. In the default ownership mode,merge-preserve-manual, ANR never removes them, whatever the measurement graph says.ownership: anr-owns-allin the function's configuration (racora-cu.cuIp.engineConfig) opts every relation into ANR's removal logic;ownership: report-onlyevaluates and logs without acting. - Relations ANR adds are
source: anr, and only those are subject to its removal logic. Deleting one from the spec yourself removes it, and ANR re-adds it only if the radio evidence still supports it. Re-declaring an ANR-added relation does not adopt it as manual; provenance is one-way. - When you edit
spec.neighborsdirectly, the controller refreshes the provenance map: new entries default tomanual, existing entries keep their source, entries removed from the spec drop out. - Each ANR change lands as one audited decision in
status.lastApplied.anr, and reaches the running CU-CP live, without a DU restart.
Turning a function off is dropping it from racora-cu.cuIp.engines (default pci,anr).