Fusion-to-ADI Integration Pattern
Status & scope
- Status: DRAFT
- Layer: integration boundary (fusion → ADI/DES)
- Date: 2026-04-30
- Author: Chris Yonclas
- Companion specs: wire-message-families, dissent (MachineDissent), axonis-lens SPEC-19 (incremental updates)
- Supersedes (in part): the fusion-integration design discussion; becomes the architectural reference that its sub-tickets implement.
1. Purpose
Define the integration pattern by which any lens engine — Parallax (Identity Lens / entity resolution), Prism (Cost Lens / Decision Lens / source-localization), or future engines — emits results into the ADI workflow (Monitor → Investigate → Decide → Attest → Act).
The architectural insight: all lens engines collapse to the same four-contract integration pattern. Engine differs. Lens YAML differs. Output content differs. Integration pattern is identical.
2. The Architectural Insight
Today, Parallax produces entity-match results and Cortex emits them as Signals. The proposed Cost Lens (Prism) family produces threat surfaces, route corridors, observation windows, and action timing. Both must flow through the same ADI pipeline so that:
- One Beacon dispatcher renders any signal regardless of which engine produced it
- One Cortex lens runtime dispatches lens YAMLs to the right engine
- One evidence-block schema captures pedigree across engines (replay determinism, FED-SPEC-02)
- One signal schema lets policy/triggers fire without engine-specific branches
This is not a refactor. It is the platform pattern that lets ADI grow without coupling to engine implementations.
3. The Four Contracts
3.1 Signal
The "header" object — what hits the Monitor queue and triggers downstream actions.
Signal:
signal_id: ULID
signal_type: str # e.g. "fusion_identity_match", "threat_threshold_crossed",
# "corridor_change", "observation_window_open"
severity: enum(low, medium, high, critical)
lens_id: str # which lens produced this
lens_version: semver
score: float # composite score / confidence / risk magnitude
summary: str # one-line human-readable
evidence_block_ref: BlockRef # pointer to pedigree
view_hint: ViewHint # how to render (see §3.3)
timestamp_utc: ISO8601
scope: { object_types: [...], geographic: str, time_window: str }
signing: { signer_id, signature, query_hash }
Key invariant: the signal does NOT contain raw data. It contains a pointer to evidence (evidence_block_ref) and a hint for rendering (view_hint). Raw data stays inside the block, behind ABAC.
3.2 Evidence Block
The "why" object — full pedigree, replay-deterministic, frozen-once-written.
Existing schema (extend, don't replace):
EvidenceBlock:
block_id: ULID
lens_id, lens_version
inputs: { node_a_query_hash, node_b_query_hash, ... } # what data was queried
derivation: { transforms, normalisations, blocking, scoring } # how
output: # what
type: enum(entity_match, surface_grid, route, window, anomaly, ...)
payload: <type-specific> # see §4 use cases
view_hint: ViewHint # NEW — see §3.3
signing: { signer_id, signature, frozen_at }
query_hash: SHA-256 # canonical-form hash for replay
view_hint is added to the block so an investigator viewing the block in isolation (without going through the signal) still gets the right rendering.
3.3 ViewHint
NEW contract — small, declarative, dispatched on by Beacon.
ViewHint:
component: str # canonical component name — see §3.4
layout_type: enum(map_layer, table, timeline, gantt, kpi, choropleth,
cluster_card, polyline, heatmap, evidence_panel,
geo_overlay_with_uncertainty, ...)
color_scale: optional<str> # named scale from a palette registry
time_axis: optional<{ start, end, bucket }>
geographic_bounds: optional<bbox>
primary_field: optional<str> # which output field drives the visual
display_options: dict # component-specific tunables
ViewHint is advisory, not authoritative — Beacon can substitute or fall back. But if Cortex emits a hint that Beacon doesn't understand, the rendering should degrade gracefully (table view as default).
3.4 Component
Beacon's UI widgets. Contract is the canonical name registered in a Component Registry. New components added over time; old components survive lens-YAML changes because the lens names the component, not the implementation.
| Component (canonical name) | Renders | Status |
|---|---|---|
cluster_card |
Entity-match clusters with per-field scores | exists |
evidence_panel |
Frozen evidence block with derivation tree | exists |
kpi_tile |
Single-number KPI with trend | exists |
geo_temporal_viewer |
Multi-layer map with time slider | exists |
signal_feed |
Time-ordered signal stream | exists |
gantt |
Time-window / scheduling visualization | new |
choropleth |
Heatmap on geographic grid | new |
polyline_with_cost |
Route line with per-segment cost coloring | new |
geo_overlay_with_uncertainty |
Source location + confidence ellipse | new |
lens_editor |
Form-based or YAML-based lens editor | new (Phase 4) |
4. Use Case Walk — Same Pattern, Different Content
| Use case | Engine | Signal signal_type |
Block output.type |
ViewHint component |
|---|---|---|---|---|
| VRS screening match | Parallax (Identity) | fusion_identity_match |
entity_match |
cluster_card |
| Multi-INT source attribution | Parallax + Prism (Decision) | source_detected |
localized_source |
geo_overlay_with_uncertainty |
| Threat surface high-cell | Prism (Cost: threat) | threat_threshold_crossed |
surface_grid |
choropleth |
| Route corridor change | Prism (Cost: traversal) | corridor_change |
route |
polyline_with_cost |
| Observation window open | Prism (Cost: observation) | observation_window_open |
time_window |
gantt |
| Anomaly via incremental fusion | Parallax (incremental) | cluster_split_dissent |
machine_dissent (SPEC-30) |
evidence_panel |
| N-party Disney retention match | Parallax | multi_brand_customer_aligned |
entity_cluster_n_party |
cluster_card |
The pattern holds for all of these. Adding a new lens type means:
1. Define what the engine emits as output.payload
2. Pick or add a view_hint.component
3. (If needed) build the new component in Beacon
4. Lens YAML names everything
Nothing in Cortex's signal/block emitter changes per lens. Nothing in Beacon's dispatcher changes per lens.
5. What Changes in Cortex
| Component | New / Existing | Description |
|---|---|---|
| Lens runtime dispatcher | NEW | Reads lens_type from YAML; routes to parallax.ops.fusion (entity_resolution) or prism.{traversal,threat,observation,temporal} or future engines. Single MCP tool: execute_lens(lens_id, inputs). |
| Signal/Block emitter | EXTEND | Existing create_block() and create_signal() paths gain view_hint field on output; populated by the engine adapter. |
| Lens registry | NEW (small) | UDS-backed store of validated lens YAMLs — versioned, immutable once frozen, replay-deterministic. CRUD via MCP tools. |
| Lens validator MCP tool | NEW | Validates a YAML against the lens schema; returns parse errors structured for the UI editor. Reusable by tier-0 lens editor. |
| Engine adapters | EXTEND / NEW | parallax_adapter (extends existing path); prism_adapter (new — even partial Prism plugs in). Each adapter knows how to map engine output → output.payload + view_hint. |
| Trigger / policy engine | EXTEND | Existing signal-policy hook fires on filter rules (signal_type, severity, score >= X). Already in place; just needs to consume the unified signal schema. |
Not changing: UDS, ABAC, Cortex MCP tool count (just adding ~5 new tools). Beacon UI patterns. Pack format.
6. What Changes in Beacon
| Component | New / Existing | Description |
|---|---|---|
| ViewHint dispatcher | NEW (~50 LOC) | Given a Signal or Block, look at view_hint.component, route to that component. Fallback to evidence_panel table view for unknown hints. |
| Component library | EXTEND | Most components exist (geo_temporal viewer, evidence panel, KPI tiles, COA cards, cluster card). NEW: gantt, choropleth, polyline_with_cost, geo_overlay_with_uncertainty. |
| Lens result panel (S-NEW per CLAUDE.md) | NEW | Already in Jocelyn's queue — fits this pattern. The panel reads view_hint to decide layout. |
| Lens editor UI | NEW (Phase 4 — see §8) | Form-based editor; uses the Cortex lens validator MCP for live validation. |
Not changing: Beacon's existing block-execution path (the execute_monitor_block call). NgRx store shape. Pack consumption. The engine work is orthogonal — Beacon just consumes the unified contracts.
7. What Changes in Prism (axonis-lens)
| Component | Status |
|---|---|
| Engine | Early — axonis-lens repo, four lens types defined (Traversal, Threat, Observation, Temporal); lens YAML schema + parser shipping |
| ADI integration | None today — but with this spec, integration is a Cortex adapter, not Prism work |
| MCP tool dispatch | Inherits from §5's Lens runtime dispatcher — Prism plugs in as another adapter |
Output → view_hint mapping |
NEW — per Prism lens type, define which view_hint.component to emit |
Prism doesn't need to know about ADI. It produces typed output (route, surface, window). The Cortex prism_adapter wraps that output in Signal+Block+ViewHint.
8. Lens Editor — Tiers and Scope
Lens editing is a real product surface. Phased approach:
| Tier | Capability | Effort |
|---|---|---|
| 0 — Upload + Validate | Drop a YAML in the lens registry; validator MCP tool returns parse errors; saved if valid. CLI + REST endpoint. | small (~3 days) |
| 1 — Form-based editor | Auto-generate form from lens_spec.schema.json; pick lens type, set fields, sliders for thresholds, dropdown for transforms. Live validate. |
medium (1-2 weeks frontend + small Cortex MCP support) |
| 2 — Visual flow editor | Drag-and-drop blocks for layers, weights, threshold curves; render preview against test data. | large (~1 month, requires real customer signal) |
| 3 — LLM-assisted | "Build me a lens that screens UK customers against VRS, prefer recall" → generates YAML + explains tradeoffs. | research-grade |
Tier 0 is the unblock. Required for any lens-as-config story. Tier 1 is the first customer-visible edit experience. Tiers 2-3 are roadmap.
9. Phased Sequencing
| Phase | Duration | Deliverables | Owner |
|---|---|---|---|
| 1 — Define contracts (this doc) | 1-2 days | SPEC-33 v1 frozen; sub-tickets named | CPO + me |
| 2 — Cortex integration plumbing | 1-2 sprints | Lens runtime dispatcher; unified signal/block emitter with view_hint; lens registry; parallax adapter (Prism adapter scaffold) | Cortex team |
| 3 — Beacon dispatcher + missing components | 1 sprint | ViewHint dispatcher; gantt, choropleth, polyline_with_cost, uncertainty-ellipse components | Beacon team (Jocelyn) |
| 4 — Lens editor Tier 0 → Tier 1 | 1 sprint each | Tier 0 (upload + validate); Tier 1 (form-based editor) | Beacon + Cortex MCP support |
| 5 — Prism adapter (when Prism is ready) | 1-2 weeks | Wire each Prism lens type's output → component name | Prism team / future hire |
Phase 1 unblocks Phase 2 and Phase 3 to run in parallel. Phase 4 can start once Phase 2's lens registry + validator MCP exist. Phase 5 is on Prism's timeline.
10. Open Questions
- Where does the lens registry physically live? UDS (cleanest — one ABAC story) or a separate UDS-adjacent store? Recommend UDS, treat lens YAMLs as a special object type.
- How do lens versions resolve at fusion time? Pin to version in the signal? Allow "latest" with audit-trail of which version ran? Recommend pin-by-default for replay-determinism (FED-SPEC-02).
- What's the ABAC story for lens editing? Who can save a new lens? Who can edit existing? Recommend: lens editing requires
lens_authoringscope; lens execution requiresfusion_operatorscope. Lens YAMLs themselves are subject to ABAC like any other UDS object. - How does this interact with SPEC-30 MachineDissent? Dissent records are themselves a signal type —
signal_type: machine_dissentwith the dissent payload as the block. Same pattern. Use case walk in §4 includes this row. - Cueing orchestrator (epic #35) interaction. When a signal triggers downstream sensor tasking, what's the Cue payload schema? SPEC-32 (Wire Message Families) Operational family is the right bucket; SPEC-33 needs a small "outbound trigger" section in v2 of this doc once #35 has a Phase 1 design.
11. Sub-tickets to file (Cortex)
These become the implementation tickets under #229:
| Title | Phase | Estimate |
|---|---|---|
| Cortex: Lens runtime dispatcher with engine adapter pattern | 2 | 5d |
| Cortex: Lens registry MCP tools (save / get / list / freeze / version) | 2 | 3d |
| Cortex: Lens validator MCP tool (returns structured errors) | 2 | 2d |
| Cortex: Extend create_block / create_signal with view_hint field | 2 | 1d |
| Cortex: Parallax adapter (entity_match → ViewHint(cluster_card)) | 2 | 2d |
| Cortex: Prism adapter scaffold (4 lens types → 4 view_hints) | 2 | 3d |
12. Sub-tickets to file (Beacon)
| Title | Phase | Estimate |
|---|---|---|
| Beacon: ViewHint dispatcher in signal-render path | 3 | 2d |
| Beacon: gantt component | 3 | 4d |
| Beacon: choropleth component | 3 | 4d |
| Beacon: polyline_with_cost component | 3 | 3d |
| Beacon: geo_overlay_with_uncertainty component | 3 | 3d |
| Beacon: Lens editor Tier 0 (upload + validate) | 4 | 5d |
| Beacon: Lens editor Tier 1 (form-based, schema-driven) | 4 | 8d |
13. Anti-goals (what this spec is NOT)
- Not a re-architecture. Reuses the existing Block/Signal/ABAC/UDS infrastructure. Adds one new contract (ViewHint), extends two (Block, Signal).
- Not a customer-facing doc. This is implementation-grade architecture. GTM language for "lens-as-config" / "lens editor" / "Decision Lens" lives in
chris-gtm/. - Not Beacon-internal architecture. Beacon's NgRx + component patterns are out of scope. ViewHint dispatcher is one new piece in an existing component.
- Not a replacement for SPEC-32. SPEC-32 defines wire-message families (Capability / Cue / Fusion / Evidence / Operational) that flow over RabbitMQ. SPEC-33 defines what ADI does WITH those messages once they arrive. Complementary specs.
14. Glossary
| Term | Meaning |
|---|---|
| Identity Lens | Parallax — "are these records the same entity?" |
| Cost Lens / Decision Lens | Prism — "what's the cost / risk / window / route?" |
| ADI | Axonis Decision Intelligence platform (Beacon + Cortex + UDS) |
| Signal | Header-level event in the Monitor queue |
| Evidence Block | Full pedigree, frozen, signed |
| ViewHint | Render directive carried by signal/block |
| Component | Beacon UI widget |
| Lens registry | UDS-backed store of validated lens YAMLs |
| MCP tool | Cortex callable tool exposed via the MCP protocol |
End of SPEC-33 Draft 1. To freeze for v1: review with Cortex lead + Beacon lead + Prism owner; resolve §10 open questions; assign Phase 2/3 tickets.
Depends on: component.parallax.dissent, component.parallax.wire-message-families
Realizes: product.fusion
Required by: component.parallax.coordination-ledger