Incremental Updates and Triggers — Continuous Cost Lens Recomposition
Status & scope
- Status: Draft — ready to implement.
- Module:
lens/runtime/incremental/(new). - Definition of done: streaming replay test green — a recorded
LayerChangeEventsequence replays to byte-identicalRecompositionEvent/ThresholdCrossingEventhashes (threat domain). - Sister specs: parallax SPEC-11 (Correlation Persistence), parallax SPEC-27 (Continuous Fusion) — external repo.
- Milestone: Phase 4 (continuous operation).
- Date: 2026-04-27.
1. Problem
Cost Lens (Prism) today is request-response. An operator asks for a route, a threat surface, a coverage window, an action window — the framework composes the answer from current layer data and returns it. Evidence is captured per request.
This works for analytical workflows ("plan a route through the AO this morning") and demo scenarios. It fails for operational scenarios where:
- Layer data changes continuously. A gauge reading updates; precipitation forecast revises; a TLE epoch advances; a no-fly polygon is added. The threat surface, route, coverage window, or action window the operator was looking at thirty seconds ago is now stale.
- Re-composing the whole surface on every change is expensive. A flood threat surface over the Houston AO is a 1km grid of ~50,000 cells. A precipitation update at one gauge changes ~200 cells in a 5km radius. Recomposing all 50,000 cells on every gauge update is wasteful at best, untenable at sub-second latency.
- No event-driven trigger. Source adapters (SPEC-13) currently fetch on demand. There is no path from "the source data just changed" to "recompose the affected portion of the lens output and emit a signal if a threshold crossed."
- No streaming evidence chain. SPEC-04 (Evidence Model) captures evidence per composition. There is no event chain that says "at T, layer L changed; at T+50ms, cells C₁..C_n recomposed; at T+60ms, threshold X was crossed at cell C_k; signal emitted."
- No replay determinism for the streaming case. FED-SPEC-02 (replay determinism) covers batch composition. Streaming replay — replaying a sequence of layer changes and verifying the same recomposition events fire byte-for-byte — has no spec yet.
The result: Prism is an analytical tool when the customer needs an operational system. Streaming Mesh (Xanadu) provides the transport, but transport without incremental compute is plumbing. This spec defines the compute.
2. Decision
Define an incremental recomposition pipeline that:
- Listens for layer-change events (source-data update, scheduled refresh, composition cascade, manual)
- Computes the change footprint for each event (the spatial / temporal / topological region affected)
- Recomposes only the affected portion of the lens output (sparse update over the footprint)
- Maintains an append-only event chain in the evidence model (extends SPEC-04) so the streaming case is replayable
- Emits signals when threshold crossings occur during recomposition
- Cascades to dependent lenses via
lens://references (extends SPEC-12)
This is not a rewrite of lens.runtime.compose. Batch composition stays for analytical and demo use. Incremental recomposition is a new pipeline that reuses the same primitives (cost models, weights, thresholds) but operates on layer-change deltas instead of full layer reads.
3. Architecture
Source-data update Scheduled refresh Composition cascade
(adapter detects change) (layer TTL expires) (referenced lens recomposed)
│ │ │
└────────────────────────────┼────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ LayerChangeEvent │
│ - layer_id │
│ - change_kind │
│ - footprint │
│ - prev_data_hash │
│ - new_data_hash │
│ - source_event_ref │
└──────────────┬──────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ IncrementalRecompositionPipeline │
│ │
│ 1. RESOLVE FOOTPRINT │
│ Compute affected output region per domain │
│ (traversal: edge → routes; threat: cell footprint; │
│ observation: sat × time × grid; temporal: │
│ constraint → windows) │
│ │
│ 2. RECOMPOSE │
│ Apply weights × layer values over footprint only │
│ Cached per-layer rasters; only composition recomputes│
│ │
│ 3. THRESHOLD CHECK │
│ Per cell / route / window in the footprint, did any │
│ threshold transition? (above → below or vice versa) │
│ │
│ 4. EMIT EVENTS │
│ RecompositionEvent (always) │
│ ThresholdCrossingEvent (if any threshold crossed) │
│ CascadeEvent (if dependent lenses must recompose) │
│ │
│ 5. SIGNAL EMISSION │
│ For threshold crossings, emit typed signal │
│ (signal_class = "computed", producing_engine = │
│ "prism", producing_lens_id = lens_id) │
└──────────────────────────────────────────────────────────┘
│
▼
Promotion boundary into DES
(signal → InsightRoot if filter passes)
4. Per-domain incremental update patterns
Each Cost Lens domain has a different change-propagation shape. The pipeline dispatches to a per-domain footprint resolver.
4.1 Traversal (routing)
- Layer change: edge weight updates (weather, traffic, threat overlay, terrain wetness from threat-domain composition)
- Footprint: affected edges → routes that traverse them
- Strategy: maintain a
route → edgereverse index. On edge-weight change, invalidate routes that touched that edge. Rerun A* only on invalidated routes (or on the affected sub-graph if the operator's query was an isochrone). - Cost: O(invalidated_routes × A) — typically far less than O(all_routes × A).
4.2 Threat (risk surface)
- Layer change: one input layer updates (e.g., precipitation, temperature, threat polygon)
- Footprint: spatial extent of the layer change. A precipitation update at one gauge affects cells within an interpolation radius. A new threat polygon affects cells inside the polygon plus a buffer.
- Strategy: per-layer rasters are cached at the grid resolution. On layer change, write the new layer raster (full or sparse delta). Recompose the surface only over the changed footprint by re-applying weights × layer values.
- Cost: O(affected_cells × layer_count) — typically far less than O(total_cells × layer_count).
4.3 Observation (sensor coverage)
- Layer change: TLE update (satellite ephemeris advances), new satellite added, sky-mask change at a ground station
- Footprint: affected satellite × affected time range × visibility-affected grid
- Strategy: SGP4 propagation is fast (~µs per epoch). Re-propagate only the affected satellite over the affected time range. Recompute coverage on the visibility-affected grid cells.
- Cost: O(1 satellite × affected_time × affected_grid) — typically far less than O(all_sats × full_time × full_grid).
4.4 Temporal (action windows)
- Layer change: one constraint updates (weather forecast revision, fuel availability change, mission-window shift)
- Footprint: time interval where the constraint changed × downstream windows that intersected that interval
- Strategy: maintain windows as interval sets. On constraint change, recompute the intersection only over the affected time interval.
- Cost: O(affected_intervals) — typically far less than O(all_constraints × full_time).
5. Triggers
Five trigger sources, dispatched through one entry point.
| Trigger | Source | Latency target | Notes |
|---|---|---|---|
| Source-data change event | Adapter detects (poll diff, CDC, ES write trigger, file watcher) | Sub-second once detected | Adapter responsibility extended in SPEC-13 |
| Streaming-mesh message | Xanadu peer-to-peer event | Sub-second | Profile 3 deployment only |
| Scheduled refresh | Layer TTL expires (per SPEC-05) | Per TTL — minutes to hours | Time-decay layers (temporal forecasts, weather) |
| External webhook | Customer system pushes update | Sub-second once received | Customer-side integration; bounded by adapter validation |
| Composition cascade | A referenced lens recomposes (lens:// reference) |
Sub-second after dependency recomposes | Topological dispatch via SPEC-12 composer |
All triggers produce a LayerChangeEvent with a uniform schema (§7). The pipeline is trigger-source-agnostic past that point.
6. Evidence chain — extension to the evidence model
SPEC-04 captures evidence per composition. This spec extends it for the streaming case. Three new event types appended to the lens evidence chain:
6.1 LayerChangeEvent
event_type: layer_change
lens_id: houston_flood_threat_v1
layer_id: precipitation_gauge_data
timestamp: 2026-04-27T14:23:11.412Z
change_kind: data_update # data_update | layer_added | layer_removed | weight_change
footprint:
type: spatial
bounding_box: [...]
affected_cells: 217
prev_data_hash: sha256:...
new_data_hash: sha256:...
source_event_ref: adapter:gauge_KHOU_2026-04-27T14:23:11
trigger_source: source_change
6.2 RecompositionEvent
event_type: recomposition
lens_id: houston_flood_threat_v1
triggering_layer_change_id: lce_...
timestamp: 2026-04-27T14:23:11.487Z
footprint_summary:
affected_cells: 217
affected_routes: 0
affected_windows: 0
prev_output_hash: sha256:... # over the affected footprint only
new_output_hash: sha256:...
composition_version: 1.0.0 # weights / cost models version
6.3 ThresholdCrossingEvent
event_type: threshold_crossing
lens_id: houston_flood_threat_v1
triggering_recomposition_id: rec_...
timestamp: 2026-04-27T14:23:11.491Z
location: { cell_id: HC_2174 } # or route_id / window_id
threshold_id: high_risk
prev_value: 0.71
new_value: 0.83
direction: crossed_above
signal_emitted: sig_...
6.4 Replay determinism — streaming case
Replay walks the event chain in order. Given the same source-data update sequence, the same LayerChangeEvent → RecompositionEvent → ThresholdCrossingEvent sequence must fire with byte-identical hashes. This is the streaming extension of FED-SPEC-02.
Constraints required for byte-identical replay:
- Event ordering — events must be totally ordered (timestamps + sequence number; ties broken deterministically).
- Footprint resolution determinism — given the same layer change, the same footprint must be computed (no sets, no hash-randomization-dependent ordering).
- Composition determinism — already required by FED-SPEC-02 for the batch case; same property must hold per-cell.
- Threshold check determinism — float comparison ordering pinned (no parallel reductions that change order between runs).
7. Schema
# lens/models/incremental.py (new)
@dataclass(frozen=True)
class LayerChangeEvent:
event_id: str
lens_id: str
layer_id: str
timestamp: datetime
change_kind: ChangeKind # DATA_UPDATE | LAYER_ADDED | LAYER_REMOVED | WEIGHT_CHANGE
footprint: Footprint # spatial / temporal / topological — domain-specific
prev_data_hash: str
new_data_hash: str
source_event_ref: str
trigger_source: TriggerSource
@dataclass(frozen=True)
class RecompositionEvent:
event_id: str
lens_id: str
triggering_layer_change_id: str
timestamp: datetime
footprint_summary: dict
prev_output_hash: str
new_output_hash: str
composition_version: str
@dataclass(frozen=True)
class ThresholdCrossingEvent:
event_id: str
lens_id: str
triggering_recomposition_id: str
timestamp: datetime
location: dict # cell_id | route_id | window_id
threshold_id: str
prev_value: float
new_value: float
direction: CrossingDirection # CROSSED_ABOVE | CROSSED_BELOW
signal_emitted: str | None
8. Public API
# lens/runtime/incremental/__init__.py
class IncrementalPipeline:
def __init__(
self,
lens_spec: LensSpec,
layer_cache: LayerCache,
evidence_writer: EvidenceWriter,
signal_emitter: SignalEmitter,
): ...
def on_layer_change(self, event: LayerChangeEvent) -> list[Event]:
"""Process a layer change. Returns the cascade of events emitted."""
...
class FootprintResolver(Protocol):
"""Per-domain footprint computation. One implementation per Cost Lens domain."""
def resolve(self, change: LayerChangeEvent, lens_spec: LensSpec) -> Footprint: ...
9. File layout
lens/
runtime/
incremental/
__init__.py
pipeline.py ← IncrementalPipeline orchestration
footprint/
__init__.py
traversal.py ← TraversalFootprintResolver
threat.py ← ThreatFootprintResolver
observation.py ← ObservationFootprintResolver
temporal.py ← TemporalFootprintResolver
triggers/
__init__.py
source_change.py ← Adapter-driven trigger
scheduled.py ← TTL-driven trigger
cascade.py ← Composition-cascade trigger
webhook.py ← External webhook trigger
streaming_mesh.py ← Xanadu peer event trigger
replay.py ← Streaming replay verification
models/
incremental.py ← LayerChangeEvent, RecompositionEvent, ThresholdCrossingEvent
10. Acceptance criteria
- [ ]
LayerChangeEvent,RecompositionEvent,ThresholdCrossingEventdata classes implemented (frozen) - [ ]
IncrementalPipeline.on_layer_change()dispatches to the per-domain resolver and emits the event chain - [ ] Per-domain footprint resolvers implemented for all four Cost Lens domains (traversal, threat, observation, temporal) with at least one canonical scenario each
- [ ] Trigger sources implemented: source-change adapter callback, scheduled (TTL), composition cascade. Streaming-mesh and webhook may follow.
- [ ] Threshold-crossing detection emits typed signals through the existing signal emitter (extend SPEC-04 if needed)
- [ ] Streaming replay test: given a recorded sequence of
LayerChangeEvents, replay produces byte-identicalRecompositionEventandThresholdCrossingEventhashes - [ ] Performance test (threat domain reference scenario — Houston flood): single gauge update produces sub-second recomposition over the affected footprint (<200ms target on TRM-1 reference machine)
- [ ] Performance test (traversal domain reference scenario): single edge-weight change produces sub-second route invalidation + reroute over the affected sub-graph
- [ ] Composition-cascade verified: when a referenced lens recomposes, the dependent lens recomposes via the same pipeline (no special path)
- [ ] FED-SPEC-02 streaming-replay benchmark passes for the threat domain reference scenario
11. Non-goals
- Not a transport layer. Streaming Mesh (Xanadu) is the transport for Profile 3 deployments; this spec defines the compute that runs on top of it.
- Not a stream-processing framework. No Flink, no Beam, no Kafka Streams. Pure Python; events flow through the pipeline in-process; coordination across nodes is Xanadu's job.
- Not Entity Lens. Parallax has SPEC-11 (incremental fusion math) and SPEC-27 (continuous fusion pipeline). This spec is the Cost Lens equivalent.
- Not a UI / operator surface. Beacon's incremental rendering is downstream and out of scope; this spec defines the events Beacon will consume.
12. Open questions
- Per-cell evidence vs. footprint-summary evidence. SPEC-04 captures evidence per composition. For streaming, do we capture per-cell evidence on every threshold crossing, or footprint-summary evidence with per-cell drill-down on demand? Lean: footprint-summary by default; per-cell on threshold crossings.
- Cascade depth limits. SPEC-12 caps composition depth at 3. Same limit applies for cascade triggers? Lean: yes — same invariant.
- Backpressure under high-frequency layer changes. When a layer changes faster than the pipeline can recompose, what's the policy? Drop, batch, or queue with explicit lag metrics? Lean: batch within a configurable time window (default 100ms) with a debounce; emit one
LayerChangeEventper batch summarizing the deltas. - Multi-layer simultaneous changes. When two layers change in the same window, is recomposition done once over the union footprint, or twice sequentially? Lean: once over the union — but the event chain captures both
LayerChangeEvents with a singleRecompositionEventreferencing both.
13. Cross-references
| Spec | Relationship |
|---|---|
| parallax SPEC-11 | Sister spec for Entity Lens incremental fusion math (UnionFind absorbing new records). This spec is the Cost Lens equivalent. |
| parallax SPEC-27 | Sister spec for Entity Lens continuous fusion pipeline (event-driven trigger + signal emission). This spec mirrors its shape for Cost Lens. |
| SPEC-04 (axonis-lens) | Evidence model — extended here with three new event types. |
| SPEC-05 (axonis-lens) | Operational lifecycle — Init→Execute→Sync. The streaming case lives inside Execute. |
| SPEC-12 (axonis-lens) | Composition — extended here with composition-cascade triggers. |
| SPEC-13 (axonis-lens) | Source adapters — extended here with change-detection responsibilities. |
| FED-SPEC-02 (themis) | Replay determinism — streaming case is the V1 gap closed by §6.4 of this spec. |
| Streaming Mesh / Xanadu | Transport for Profile 3 deployments; the conduit on which LayerChangeEvents flow between Edge Nodes. |
| Themis Phase 4 | Instruments Scenario A (5-node Profile 3) for benchmarking the streaming-replay determinism property defined here. |
14. Implementation notes
This spec is DRAFT. Recommended implementation order:
- Models first —
LayerChangeEvent,RecompositionEvent,ThresholdCrossingEventas frozen dataclasses. No pipeline yet. - Threat-domain footprint resolver — first concrete domain. Houston flood is the reference scenario.
- Source-change trigger — adapter callback hook. Polls / CDC / file-watcher are the V0 detection mechanisms.
- Pipeline orchestration — wire 1+2+3 together; emit event chain to existing evidence writer.
- Streaming replay test — verify byte-identical replay on the threat domain reference. This closes the FED-SPEC-02 streaming gap for one domain.
- Remaining domains — traversal, observation, temporal footprint resolvers (in this order; traversal has the most customer pull).
- Cascade trigger — composition-cascade via SPEC-12.
- Streaming-mesh trigger — Xanadu peer event ingestion. Requires Profile 3 deployment.
- Webhook trigger — customer-side integration shape.
Themis Phase 4 instruments the resulting pipeline for the FED-SPEC-02 streaming-replay benchmark.
Owner: CPO. Aligned with parallax SPEC-11 + SPEC-27. Next review when the threat-domain reference implementation lands.
Depends on: component.prism.composition, component.prism.evidence-model, component.prism.operational-lifecycle, component.prism.source-adapters
Realizes: product.lens