Skip to content

Commit 5bd02d9

Browse files
committed
feat(contract): D-H2H-1 head2head superposition winner-select
head2head::{Head2Head, WinnerCriterion, CompetitionOutcome} — picks the winning competing-expert bid over the existing a2a_blackboard. Pure read + arg-extremum: no new identity, copies nothing (select-not-duplicate, I-VSA-IDENTITIES). Go metaphor: DissonanceMin=infight, SupportSpread=Raumgewinn, Tempered=default; margin = the dark-horse signal. The SELECTION half of item-4 superposition; the parallel-mailbox executor is the CI-gated consumer side. 516 contract lib tests (+7); head2head.rs clippy pedantic+nursery clean. Board: LATEST_STATE inventory + STATUS_BOARD D-H2H-1. https://claude.ai/code/session_012SorR8UbtEvYmbX8cXftj7
1 parent c6357e2 commit 5bd02d9

4 files changed

Lines changed: 243 additions & 0 deletions

File tree

.claude/board/LATEST_STATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
## Current Contract Inventory (lance-graph-contract)
4444

45+
> **2026-05-31 — ADDED (D-H2H-1, head2head superposition winner-select)**: `lance_graph_contract::head2head::{Head2Head (judge), WinnerCriterion (DissonanceMin≈infight / SupportSpread≈Raumgewinn / ConfidenceMax / Tempered=default), CompetitionOutcome}`. `Head2Head::select(&Blackboard) -> Option<CompetitionOutcome>` picks the winning competing-expert bid over the existing `a2a_blackboard` (confidence/dissonance/support) — pure read + arg-extremum, **no new identity, copies nothing** (select-don't-duplicate, `I-VSA-IDENTITIES`); `margin` = the dark-horse signal. The *selection* half of head2head superposition; parallel-mailbox *execution* is the CI-gated consumer side. Zero-dep; 516 contract lib tests (+7); clippy pedantic+nursery clean.
46+
4547
> **2026-05-31 — ADDED (D-MBX-9-IN, VersionScheduler contract slice, on `b6e3cc6`/lance7)**: `lance_graph_contract::scheduler::{DatasetVersion(u64), VersionScheduler (trait), NextPhaseScheduler (reference impl)}`. The IN-direction dual of `MailboxSoaOwner` (`E-SUBSTRATE-IS-THE-SCHEDULER`): `on_version<V: MailboxSoaView>(&V, DatasetVersion, ExecTarget) -> Option<KanbanMove>` lowers a Lance `versions()` tick to the next legal Rubicon `KanbanMove`; `NextPhaseScheduler` is the forward-arc reference (Libet `-550ms` anchor on Planning→CognitiveWork, `None` on absorbing). Read-only over the view (**propose-not-dispose**, R1); composes only existing contract types; zero-dep. 509 contract lib tests (+6); clippy pedantic-clean. CI-gated twin = `LanceVersionScheduler` over `VersionedGraph::versions()` via callcenter `LanceVersionWatcher`. Closes D-MBX-9 IN-direction at the type level (OUT twin + core impl remain CI-gated).
4648
4749
> **2026-05-31 — MERGED (#441, D-CLS arc, merge `a77e119`)**: `lance_graph_contract::class_view::{FieldMask (u64 presence bitmask), ClassView (resolver trait), ClassProjection, RenderRow}` + `ClassView::render_rows` (off-bits-skipped). `ClassId = u16` (reuses `soa_view::class_id`). The class meta-DTO **flies ABOVE the agnostic SoA** — labels/shape/DOLCE resolve LATE from the OGIT cache, nothing semantic in the row (C2 presence≠semantics; N3 stable positions; out-of-range mask bits IGNORED not folded — Codex P2). Ontology side: `class_resolver::RegistryClassView` (impls `ClassView` over the live `OntologyRegistry`, DOLCE via `classify_odoo`) + `odoo_blueprint::class_signature::{StructuralSignature, OdooEntity::signature()/object_view() carrier methods, audit, shape_families, curated_entities, corpus_summary}` (deterministic FNV-1a structural-hash group-by, NOT Aerial-cluster). Zero-dep preserved; extends `ontology::{ObjectView,FieldRef,DisplayTemplate}`, reuses `class_id` (no new newtype). 497 contract + 240 ontology lib tests. D-CLS-{FM,RES,SIG,AUDIT,RENDER} all Shipped.

.claude/board/STATUS_BOARD.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ Plan path: `.claude/plans/unified-soa-convergence-v1.md`. Handover `.claude/hand
562562
| D-MBX-8 | Σ10 commit stamps **t = −550 ms** wall-clock (Libet anchor) in `SigmaTierRouter`; downstream ractor START fires | sigma-tier-router + shader-driver | 120 | MED | **Queued** | gates on D-MBX-A4 + D-MBX-A6 Phase 1 |
563563
| D-MBX-9 | Rubicon kanban view in `surrealkv`-on-lance (4 columns: Planning · Cognitive work · Evaluation · Commit·Plan·Prune); ractor lifecycle hooks = kanban moves | surreal_container + ractor | 250 | HIGH | **Queued** | gates on D-MBX-7 + D-MBX-8 + surreal_container BLOCKED(B/C/D) resolved (OQ-11.6) + D-PERSONA-5 |
564564
| D-MBX-9-IN | contract slice of D-MBX-9 IN-direction (`E-SUBSTRATE-IS-THE-SCHEDULER`): `scheduler::{DatasetVersion, VersionScheduler, NextPhaseScheduler}` — Lance `versions()` tick → next legal `KanbanMove`, zero-dep, read-only-over-view (propose-not-dispose) | lance-graph-contract | 130 | LOW | **Shipped (contract)** | 509 lib tests (+6); clippy pedantic-clean; CI-gated twin `D-MBX-9-IN-impl` (LanceVersionScheduler over `VersionedGraph::versions()`) named not written |
565+
| D-H2H-1 | head2head superposition winner-select (item 4, Go infight-vs-Raumgewinn): `head2head::{Head2Head, WinnerCriterion, CompetitionOutcome}``select(&Blackboard)` arg-extremum over existing bids, select-not-duplicate | lance-graph-contract | 130 | LOW | **Shipped (contract)** | 516 lib tests (+7); clippy pedantic+nursery clean; parallel-mailbox executor = CI-gated consumer side |
565566
| D-MBX-10 | SoA version byte at layout root (`MailboxSoAHeader`); refuse v(N>M) bytes on v(M) reader; field-isolation matrix tests on every column op (`I-LEGACY-API-FEATURE-GATED` discipline) | lance-graph-contract | 100 | HIGH | **Queued** | foundation — should land early in P2; gates on OQ-11.5 |
566567
| D-MBX-11 | Lance `=6.0.0 → =6.0.1` patch bump (5 Cargo.toml files identified) | workspace Cargo.toml | 10 | LOW | **Queued (mechanical)** | none — can land in parallel with par-tile prereq |
567568
| D-MBX-12 | 8-PR workspace-wide consumer alignment: 12.1 AriGraph · 12.2 Vsa16k audit · 12.4 lance-graph · 12.5 planner · 12.6 shader-driver · 12.7 callcenter · 12.8 ontology audit · 12.9 thinking-styles | per-crate | 800 | per-PR | **Queued (multi-PR)** | sequencing per OQ-11.8: 12.4 → 12.5 → 12.6 → 12.7 → 12.1 → 12.9 → 12.2 → 12.8 |
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
//! # `head2head` — competing-expert superposition + winner selection.
2+
//!
3+
//! Two (or N) mailbox-experts reason over the same input *in parallel*, each
4+
//! posting a [`BlackboardEntry`]; head2head selects **one** winner whose
5+
//! emissions become the authoritative spiral. This is the *selection* half of
6+
//! the superposition (item: "head2head mailbox thinking as superposition") — the
7+
//! parallel mailbox *execution* is the CI-gated consumer side; the
8+
//! winner-pick over the blackboard is what lives, zero-dep and verifiable, here.
9+
//!
10+
//! ## The Go metaphor (the user's framing)
11+
//! Two strategies compete and the board scores them:
12+
//! - **infight** (close tactical combat) ≈ [`WinnerCriterion::DissonanceMin`]:
13+
//! the expert with the least internal contradiction — tightest local resolution.
14+
//! - **Raumgewinn** (territory / influence) ≈ [`WinnerCriterion::SupportSpread`]:
15+
//! the expert whose top-K support atoms cover the most distinct ground.
16+
//!
17+
//! ## Separation of concerns — select, never duplicate
18+
//! [`Head2Head::select`] is a pure read + arg-extremum over the *existing*
19+
//! [`Blackboard`] entries (`confidence` / `dissonance` / `support` are already
20+
//! there). It stores no new identity and copies nothing — an identity is
21+
//! *pointed at* ([`ExpertId`]), never re-materialized (`I-VSA-IDENTITIES`). The
22+
//! winner is a *decision over state*, not a new copy of it.
23+
//!
24+
//! Zero dependencies. Pure data + one method on the selector carrier.
25+
26+
// Pedantic carve-outs (the repo does not deny pedantic; these are in-context false
27+
// positives): cast_precision_loss — the support count n ∈ 0..=4 is exact as f32;
28+
// float_cmp — tests compare exact integer-valued scores; missing_const_for_fn —
29+
// `score` transitively calls the non-const `slice::contains`.
30+
#![allow(clippy::cast_precision_loss, clippy::float_cmp, clippy::missing_const_for_fn)]
31+
32+
use crate::a2a_blackboard::{Blackboard, BlackboardEntry, ExpertId};
33+
34+
/// How to pick the winner among competing experts. Each maps a
35+
/// [`BlackboardEntry`] to a scalar score; the highest score wins.
36+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
37+
pub enum WinnerCriterion {
38+
/// Lowest dissonance wins — the **infight** reading: least internal
39+
/// contradiction (score = `1 - dissonance`).
40+
DissonanceMin,
41+
/// Highest self-reported confidence wins (score = `confidence`).
42+
ConfidenceMax,
43+
/// Widest distinct support wins — the **Raumgewinn**/territory reading
44+
/// (score = count of distinct non-zero atoms in `support[4]`).
45+
SupportSpread,
46+
/// Confidence tempered by dissonance: `confidence * (1 - dissonance)`.
47+
/// The default — rewards certainty that isn't bought with contradiction.
48+
#[default]
49+
Tempered,
50+
}
51+
52+
/// The outcome of a head2head: who won, by how much, under which criterion.
53+
///
54+
/// `margin` is the winner's lead over the best *other* expert
55+
/// (`winner_score - runner_up_score`); with no distinct runner-up it is the
56+
/// winner's uncontested score (lead over the `0.0` floor). A small `margin` is
57+
/// the dark-horse signal — the wave was nearly a coin-flip, so the deterministic
58+
/// particle chain should confirm before the winner's spiral is trusted.
59+
#[derive(Clone, Copy, Debug, PartialEq)]
60+
pub struct CompetitionOutcome {
61+
/// The expert whose top-scoring bid won.
62+
pub winner: ExpertId,
63+
/// The winner's score under `criterion`.
64+
pub winner_score: f32,
65+
/// The best *other* expert, if any competed.
66+
pub runner_up: Option<ExpertId>,
67+
/// `winner_score - runner_up_score` (lead over the `0.0` floor if uncontested).
68+
pub margin: f32,
69+
/// The criterion the board judged by.
70+
pub criterion: WinnerCriterion,
71+
}
72+
73+
/// The board judge: holds the [`WinnerCriterion`] and scores competitors.
74+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
75+
pub struct Head2Head {
76+
/// How this judge scores each bid.
77+
pub criterion: WinnerCriterion,
78+
}
79+
80+
impl Head2Head {
81+
/// A judge with an explicit criterion.
82+
#[must_use]
83+
pub fn new(criterion: WinnerCriterion) -> Self {
84+
Self { criterion }
85+
}
86+
87+
/// Score one bid under this judge's criterion (higher = better).
88+
#[must_use]
89+
pub fn score(&self, e: &BlackboardEntry) -> f32 {
90+
match self.criterion {
91+
WinnerCriterion::DissonanceMin => 1.0 - e.dissonance,
92+
WinnerCriterion::ConfidenceMax => e.confidence,
93+
WinnerCriterion::SupportSpread => distinct_support(e.support) as f32,
94+
WinnerCriterion::Tempered => e.confidence * (1.0 - e.dissonance),
95+
}
96+
}
97+
98+
/// Select the winning expert over the blackboard's bids, or `None` if the
99+
/// board is empty. Each entry is a *bid*; the expert of the top-scoring bid
100+
/// wins, and the runner-up is the top bid from any *other* expert.
101+
#[must_use]
102+
pub fn select(&self, bb: &Blackboard) -> Option<CompetitionOutcome> {
103+
let best = bb
104+
.entries
105+
.iter()
106+
.max_by(|a, b| self.score(a).total_cmp(&self.score(b)))?;
107+
let winner = best.expert_id;
108+
let winner_score = self.score(best);
109+
110+
let runner = bb
111+
.entries
112+
.iter()
113+
.filter(|e| e.expert_id != winner)
114+
.max_by(|a, b| self.score(a).total_cmp(&self.score(b)));
115+
let (runner_up, runner_score) =
116+
runner.map_or((None, 0.0), |r| (Some(r.expert_id), self.score(r)));
117+
118+
Some(CompetitionOutcome {
119+
winner,
120+
winner_score,
121+
runner_up,
122+
margin: winner_score - runner_score,
123+
criterion: self.criterion,
124+
})
125+
}
126+
}
127+
128+
/// Count of distinct non-zero atoms in a 4-slot support vector (the territory
129+
/// measure for [`WinnerCriterion::SupportSpread`]). `0` is the no-atom sentinel.
130+
fn distinct_support(support: [u16; 4]) -> usize {
131+
let mut seen = [0u16; 4];
132+
let mut n = 0;
133+
for a in support {
134+
if a != 0 && !seen[..n].contains(&a) {
135+
seen[n] = a;
136+
n += 1;
137+
}
138+
}
139+
n
140+
}
141+
142+
#[cfg(test)]
143+
mod tests {
144+
use super::*;
145+
use crate::a2a_blackboard::ExpertCapability;
146+
147+
fn entry(id: ExpertId, confidence: f32, dissonance: f32, support: [u16; 4]) -> BlackboardEntry {
148+
BlackboardEntry {
149+
expert_id: id,
150+
capability: ExpertCapability::ReasoningTopology,
151+
result: 0,
152+
confidence,
153+
support,
154+
dissonance,
155+
cost_us: 0,
156+
}
157+
}
158+
159+
fn board(es: Vec<BlackboardEntry>) -> Blackboard {
160+
Blackboard { entries: es, round: 0 }
161+
}
162+
163+
#[test]
164+
fn tempered_default_rewards_certainty_without_contradiction() {
165+
// A: high confidence but high dissonance (0.9 * 0.4 = 0.36).
166+
// B: lower confidence but clean (0.7 * 0.9 = 0.63 wins).
167+
let bb = board(vec![
168+
entry(1, 0.9, 0.6, [0; 4]),
169+
entry(2, 0.7, 0.1, [0; 4]),
170+
]);
171+
let out = Head2Head::default().select(&bb).unwrap();
172+
assert_eq!(out.criterion, WinnerCriterion::Tempered);
173+
assert_eq!(out.winner, 2);
174+
assert_eq!(out.runner_up, Some(1));
175+
assert!(out.margin > 0.0);
176+
}
177+
178+
#[test]
179+
fn dissonance_min_is_the_infight_pick() {
180+
// Same confidence; the tighter (lower-dissonance) expert wins the infight.
181+
let bb = board(vec![
182+
entry(1, 0.8, 0.5, [0; 4]),
183+
entry(2, 0.8, 0.2, [0; 4]),
184+
]);
185+
let out = Head2Head::new(WinnerCriterion::DissonanceMin).select(&bb).unwrap();
186+
assert_eq!(out.winner, 2);
187+
}
188+
189+
#[test]
190+
fn support_spread_is_the_raumgewinn_pick() {
191+
// Same confidence/dissonance; the expert covering more distinct ground wins.
192+
let bb = board(vec![
193+
entry(1, 0.8, 0.1, [42, 42, 0, 0]), // 1 distinct atom
194+
entry(2, 0.8, 0.1, [7, 9, 13, 21]), // 4 distinct atoms (territory)
195+
]);
196+
let out = Head2Head::new(WinnerCriterion::SupportSpread).select(&bb).unwrap();
197+
assert_eq!(out.winner, 2);
198+
assert_eq!(out.winner_score, 4.0);
199+
assert_eq!(out.margin, 3.0); // 4 distinct vs 1 distinct
200+
}
201+
202+
#[test]
203+
fn confidence_max_ignores_dissonance() {
204+
let bb = board(vec![
205+
entry(1, 0.95, 0.9, [0; 4]), // noisy but loud → wins on raw confidence
206+
entry(2, 0.6, 0.0, [0; 4]),
207+
]);
208+
let out = Head2Head::new(WinnerCriterion::ConfidenceMax).select(&bb).unwrap();
209+
assert_eq!(out.winner, 1);
210+
}
211+
212+
#[test]
213+
fn uncontested_single_expert_has_no_runner_up() {
214+
let bb = board(vec![entry(7, 0.8, 0.2, [0; 4])]);
215+
let out = Head2Head::default().select(&bb).unwrap();
216+
assert_eq!(out.winner, 7);
217+
assert_eq!(out.runner_up, None);
218+
assert_eq!(out.margin, out.winner_score); // lead over the 0.0 floor
219+
}
220+
221+
#[test]
222+
fn multiple_bids_per_expert_use_the_experts_best() {
223+
// Expert 1 posts twice; its best bid represents it; runner-up is expert 2.
224+
let bb = board(vec![
225+
entry(1, 0.3, 0.0, [0; 4]),
226+
entry(1, 0.9, 0.0, [0; 4]), // expert 1's best
227+
entry(2, 0.5, 0.0, [0; 4]),
228+
]);
229+
let out = Head2Head::new(WinnerCriterion::ConfidenceMax).select(&bb).unwrap();
230+
assert_eq!(out.winner, 1);
231+
assert_eq!(out.runner_up, Some(2));
232+
}
233+
234+
#[test]
235+
fn empty_board_has_no_winner() {
236+
assert!(Head2Head::default().select(&board(vec![])).is_none());
237+
}
238+
}

crates/lance-graph-contract/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub mod external_membrane;
5656
pub mod faculty;
5757
pub mod grammar;
5858
pub mod graph_render;
59+
pub mod head2head;
5960
pub mod hash;
6061
pub mod hhtl;
6162
pub mod high_heel;
@@ -101,6 +102,7 @@ pub mod world_model;
101102
// Re-exports for the most commonly used collapse_gate types.
102103
pub use class_view::{ClassId, ClassProjection, ClassView, FieldMask, RenderRow};
103104
pub use collapse_gate::{CollapseGateEmission, GateDecision, MailboxId, MergeMode};
105+
pub use head2head::{CompetitionOutcome, Head2Head, WinnerCriterion};
104106
pub use kanban::{ExecTarget, KanbanColumn, KanbanMove, RubiconTransitionError};
105107
pub use scheduler::{DatasetVersion, NextPhaseScheduler, VersionScheduler};
106108
pub use soa_view::{MailboxSoaOwner, MailboxSoaView};

0 commit comments

Comments
 (0)