Doctrine

ORA-2026-0112: Lift accounting must distinguish span-promote from interaction-attribute

world-modellift-accountingattribution

ORA-2026-0112: Lift accounting must distinguish span-promote from interaction-attribute

Rule

A ticket that promotes spans is not the same as a ticket that attributes interactions. Every attribution-lift ticket MUST report both metrics separately in its proof packet. A span-cleanup result without an interaction lift is reported as 0 in the rate column, not silently rolled in.

The surprise

Camber attribution lives at two altitudes:

  • Span-level: span_attributions.decision IN ('review', 'assign', 'none') with span_attributions.project_id.
  • Interaction-level: interactions.project_id — the parent row read by every product surface.

Pre-1910 plan modeled CMB-3287 ("X1 strong-anchor review auto-promote") as a +305 attribution lift. The ticket shipped. Span counts moved (305 spans transitioned reviewassign). The attribution-rate metric (interactions.project_id IS NOT NULL) did not move. The 305 spans had parent interactions that already carried project_id. X1 was review-band cleanup, not interaction-attribute.

This is not a mistake the original plan author should have caught — it required live readback against the schema to surface. But it IS a structural pattern: any future ticket that operates on span_attributions without checking the parent interactions.project_id state is at risk of the same miscount.

Two metrics, both load-bearing

MetricSourceSurface impact
Span liftspan_attributions row count change in target stateReview queue depth, span dossier completeness
Interaction liftinteractions.project_id IS NOT NULL count changeBT app, Monday Brief, Redline, pre-call prep — every product surface

A lever can produce span lift with zero interaction lift (X1 case). A lever can produce interaction lift with zero span lift (cascade repair — CMB-3322 operates on already-assigned spans, so the span row is unchanged but the interaction row gets project_id populated). Most levers produce both, but the relationship is not 1:1 and must not be assumed.

Required practice

On every attribution-lift ticket

Proof packet MUST include both columns:

LIFT_REPORT:
  spans_changed:       N (state transitions in span_attributions)
  interactions_lifted: M (newly non-NULL project_id at parent)
  cascade_explanation: <if M ≠ N, why>

On every plan / megaplan

Composition tables MUST have separate "Span lift" and "Interaction lift" columns. The cumulative attribution-rate calculation reads ONLY the interaction column. Span-cleanup levers carry 0 in the interaction column even when they ship cleanly.

On every dispatch

Dispatch text MUST name which metric the lift estimate refers to. "X1 lifts 305" is ambiguous and forbidden. "X1 promotes 305 spans (review → assign), 0 interaction lift expected because parents already attributed" is correct.

Why this matters past the X1 case

The pattern repeats. Future attribution levers will operate on:

  • Review queue cleanup (span-only, often 0 interaction lift)
  • Cascade repair (interaction-only, 0 span change)
  • Re-evaluation passes (mixed; depends on parent state)
  • Voice fingerprinting / speaker identity (resolves anonymous speakers, may produce 0 interaction lift while substantially raising downstream fidelity)

Without this doctrine, any of these levers can be miscounted in dispatch language, in plan composition tables, in the rate-objective declaration. The miscount erodes the meaning of "attribution rate" as a fleet metric and fakes progress toward the Phase 5 done-declaration trigger.

Anti-pattern

A ticket reports "lifted N rows" without specifying span vs interaction. Composition tables collapse the two into one column. Plans declare attribution-rate progress based on span counts that didn't cascade. Done-declaration ritual fires on a metric that the surfaces don't reflect.

Compliance check

Before any attribution-lift ticket posts DONE, the seat MUST run a readback that proves both numbers. Example:

-- Span-side
SELECT decision, COUNT(*) FROM span_attributions WHERE <ticket-affected-rows> GROUP BY decision;
-- Interaction-side
SELECT COUNT(*) FILTER (WHERE project_id IS NOT NULL),
       COUNT(*) FILTER (WHERE project_id IS NULL)
FROM interactions WHERE id IN (<parent ids of affected spans>);

Both numbers go in the proof packet. If they disagree, the ticket explains why before posting DONE.

Filed alongside

  • ORA-2026-0062 (world-model fidelity > coverage) — span vs interaction is a fidelity-vs-coverage instance: span counts are coverage signals, interaction lifts are fidelity signals at the surface.
  • ORA-2026-0080 (work proof on every DONE) — lift report is one of the WORK_PROOF artifact types.
  • ORA-2026-0111 (constraint-boundary comments) — the span-promote/interaction-attribute distinction is a >5min surprise; future levers MUST comment the distinction at the write site.

Effective date

Active 2026-04-30. Retroactive: ORA-1910 megaplan rebuild (CLAUDE-CLI-MacBook-Air-ORA-xw62) restated CMB-3287 / X1 lift accounting per this doctrine. All in-flight Wave 1 / Wave 1.5 / Wave 2 / Wave 4 tickets MUST comply on their next DONE.