ADR-005: Per-Target Parameter Binding & Fallback Resolution
Decision record for per-target UI parameter binding, fallback hierarchy, and observability.
Metadata
- ADR ID: ADR-005
- Title: Per-Target Parameter Binding Fallback Scope & UI Resolution Strategy
- Status: Proposed
- Date: 2026-03-15 (proposed)
- Owner: Frontend/Mission Planning lead
- Target Decision Date: 2026-04-12
- Relates to: System-Gaps-Deferred#per-target-binding-fallback-and-resolution
Problem / Context
The architecture supports per-target UI binding (System-APIs-Contracts#per-target-ui-binding-model) and System-ControlDesign mention "frame configuration" and "fallback scopes." However, several binding resolution questions remain:
- Fallback hierarchy: If target-specific binding doesn't exist, what's the cascade? (frame-type → antenna → global?)
- Missing bindings: If no binding exists at any scope level, does UI show blank field, or use a sensible default?
- Operator override: Can operator manually override binding at runtime? (e.g., force a target to use a different color?)
- Partial bindings: If only some UI properties are bound (e.g., color but not unit?), how are gaps filled?
- Binding version: If binding schema changes, do old targets use old or new binding?
- Multi-target conflicts: If two targets both want to control the same antenna, who wins?
Current State
- System-ControlDesign defines binding concept but not resolution algorithm
- System-APIs-Contracts#per-target-ui-binding-model shows structure but no fallback rules
- Prototypes use static UI (bindings hardcoded in React components)
Why This Matters
- Operator experience: Intuitive fallback prevents operator confusion ("why is target 2 showing wrong color?")
- Flexibility: Fallback allows operator to add targets mid-mission without UI reconfiguration
- Consistency: Clear rules ensure all operators see same UI across targets
- Backward compatibility: Old missions can use new software (binding evolution)
Deferred Decision Options
Option A: Strict Hierarchy with Smart Defaults (Flexible)
Approach: Define a strict fallback hierarchy. At each level, provide sensible defaults for missing properties.
Hierarchy (top = highest priority):
- Target-specific binding (e.g., "target-1-telemetry")
- Frame-type binding (e.g., "ccsds-20-byte-header")
- Antenna binding (e.g., "phased-array-1")
- Global binding (e.g., "default-ui-binding")
Smart defaults (if all levels missing):
- Color: Assign from pastel palette (target 1 → light blue, target 2 → light green, etc.)
- Unit: Auto-detect from field name + value magnitude (e.g., "altitude" → meters)
- Range: Auto-scale from min/max observed in recent data
- Label: Use field name from schema (e.g., "altitude_m")
Pros:
- Predictable: operator knows exactly which rule applies
- Backward compatible: old targets can use global binding
- Reduces manual config: smart defaults cover 80% of use cases
- Easy to debug: log which binding level was used for each field
Cons:
- Operator must understand hierarchy to troubleshoot
- Smart defaults can be wrong (e.g., "target1_id" wrongly auto-colored as continuous field)
- Hierarchy adds code complexity (4 database lookups per field)
Implementation:
def resolve_binding(target_id, frame_type, antenna_id, field_name):
"""Resolve UI binding using strict hierarchy."""
# Level 1: Target-specific binding
binding = db.query_binding(scope="target", id=target_id, field=field_name)
if binding:
return binding
# Level 2: Frame-type binding
binding = db.query_binding(scope="frame_type", id=frame_type, field=field_name)
if binding:
return binding
# Level 3: Antenna binding
binding = db.query_binding(scope="antenna", id=antenna_id, field=field_name)
if binding:
return binding