Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Plan: `.claude/plans/2026-10-06-global-sudoku-replayable-orchestration-v1.md`. O
| **D-GSO-5** | P5: first recipe quartet over existing primitives | Shipped (#1355) | `cognitive-shader-driver/examples/recipe_quartet_probe.rs`: four `match`-arm recipes over `Quorum::observe`, `Quad8::fold_product`, `Morton8x8` + `PalettePerturbation::hop`, `GadamerRevision::revise` (verdict left `NotRun`: removing the new roots is tautological, so the counterfactual moves to P7); a thread-local counting allocator shows zero allocations per recipe (no instruction vector); `ProbeRecipe` is its own type, not a shipped `recipes` ID; ordinal meanings not canonized; no Pearl projection (P7) and no selector (P6); 7 tests, 6 disable runs red |
| **D-GSO-6** | P6: deterministic, versioned recipe selector | Shipped (#1358, #1362); `local_disagreement` wired + rootless-revision fix: In PR | `cognitive-shader-driver/examples/recipe_selector_probe.rs`: `SelectorPolicy::select(state)` is a pure `const fn` over a declared 4-fact `EpistemicState`; V1 = §11 order (fold, bound, local, revise), rests only on the settled state; V2 swaps two steps (differs on 2 of 16 states); a recorded `Selection` (policy + state) replays under its own policy; every cycle rests in one step per open condition and replays identically; the version covers selection only, not recipe implementations (§13 digest open); 5 tests, 5 disable runs red. Follow-up: `new_encounter` is derived from the world (revising would still change a horizon mask; equals `revise` changing it on a 2-bit universe; rootless revisions included) and the `Revision` recipe writes `GadamerRevision`'s `delta.resulting`; dropping that write never rests; `unresolved_tension` cannot drive `local_disagreement` (never cleared); the other three facts are still stand-ins; 14 tests, 10 disable runs red. Follow-up 2: `local_disagreement` = `unresolved_tension \ interrogated`, where `interrogated` is the coverage `MooreInterrogation` writes (tension itself never cleared); a revision adding a new contradiction reopens it; dropping the interrogation write never rests; `observations_pending` and `frontier_bounded` remain stand-ins; 17 tests, 15 disable runs red |
| **D-GSO-7** | P7: reasoning-band earning and downgrade | Shipped (#1360) | `cognitive-shader-driver/examples/reasoning_band_probe.rs`: `ReasoningBand` (bits 61..63) read only via `band_reading::project_band`; earned one rung per executed passing proof under the shipped Pearl `CausalMask` (SO majority → Association, PO intervention trial passed → Causal, SPO removal attack passed → Counterfactual), outcomes executed from trial data and never supplied, no skipping, `NotRun` never raises; failed test, contradicting `Quorum` (minority caps at Association, majority drops to Surface) and `simpsons_paradox_risk` lower it; replay gives the same edge bits; unreadable provenance / undeclared / band-absent refuse; ladder and thresholds are policy pins; 7 tests, 10 disable runs red |
| **D-GSO-8** | P8: seal boundary | In PR | `lance-graph-planner/examples/seal_boundary_probe.rs`: internal events change only working rows; a declared `Boundary` event freezes the changed rows with the shipped `DetachedCycleBatch::freeze` into a local ledger (no `WalSink`); an unchanged boundary writes nothing; replaying the persisted events from each prior seal reproduces the next seal exactly (frame, landings, image, hash); wrong base, dropped event and swapped order each change the seal; tie limit handled by plan option 1: the key is the event's own durable unique `seq`, a tied batch is refused, and a test shows on the real `freeze` that tied keys make the seal arrival-dependent; 6 tests, 5 disable runs red |
| **D-GSO-8** | P8: seal boundary | Shipped (#1363) | `lance-graph-planner/examples/seal_boundary_probe.rs`: internal events change only working rows; a declared `Boundary` event freezes the changed rows with the shipped `DetachedCycleBatch::freeze` into a local ledger (no `WalSink`); an unchanged boundary writes nothing; replaying the persisted events from each prior seal reproduces the next seal exactly (frame, landings, image, hash); wrong base, dropped event and swapped order each change the seal; tie limit handled by plan option 1: the key is the event's own durable unique `seq`, a tied batch is refused, and a test shows on the real `freeze` that tied keys make the seal arrival-dependent; 6 tests, 5 disable runs red. Follow-up: `apply` refuses a `seq` not greater than the last (a reuse on one row or across seals escaped `first_tie`), `resume_after` restores the top persisted key; 7 tests, 2 more disable runs red |

## D-PLX — Population-law cross-check (2026-10-03)

Expand Down
97 changes: 92 additions & 5 deletions crates/lance-graph-planner/examples/seal_boundary_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
//! (`.claude/knowledge/seal-vs-temporal-ordering-information.md` §2). The plan
//! offers three ways out; this probe takes the first: **the key is the
//! event's own sequence number, globally unique and persisted with the
//! event.** `seal` refuses a batch with a tied key instead of sealing it, and
//! event.** `apply` enforces that by refusing any event whose `seq` is not
//! greater than the last one (`a_reused_seq_is_refused`). `seal` also refuses
//! a batch with a tied key instead of sealing it, and
//! `tied_keys_make_the_seal_depend_on_arrival` shows on the real `freeze`
//! why that refusal is needed.
//!
Expand Down Expand Up @@ -70,12 +72,16 @@ struct Seal {
batch: DetachedCycleBatch,
}

/// Why a boundary did not seal.
/// Why an event was refused or a boundary did not seal.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SealError {
/// Two casts share a `stream_position`; their order would come from
/// arrival, which is not durable.
TiedKey(u64),
/// An event's `seq` is not greater than the last accepted one. Row
/// coalescing would hide a reused `seq` from `first_tie`, so the key's
/// uniqueness is checked here, per event, before anything folds.
StaleSeq(u64),
}

/// The in-memory working state plus the durable ledger.
Expand All @@ -91,6 +97,8 @@ struct Engine {
ledger: Vec<Seal>,
/// Internal operations applied since start (for the report).
internal_ops: usize,
/// The last accepted `seq`. Sequence numbers must strictly increase.
last_seq: Option<u64>,
}

impl Engine {
Expand All @@ -101,6 +109,7 @@ impl Engine {
last_change: [None; ROWS],
ledger: Vec::new(),
internal_ops: 0,
last_seq: None,
}
}

Expand All @@ -112,6 +121,13 @@ impl Engine {
/// Apply one event. Internal events touch only working state; a boundary
/// seals.
fn apply(&mut self, event: Event) -> Result<(), SealError> {
let seq = match event {
Event::Internal { seq, .. } | Event::Boundary { seq } => seq,
};
if self.last_seq.is_some_and(|last| seq <= last) {
return Err(SealError::StaleSeq(seq));
}
self.last_seq = Some(seq);
match event {
Event::Internal { seq, row, value } => {
let r = usize::from(row) % ROWS;
Expand Down Expand Up @@ -167,13 +183,16 @@ impl Engine {
}

/// Rebuild the state a seal left behind: the previous sealed rows with the
/// seal's coalesced image written over them.
/// seal's coalesced image written over them. The highest persisted key
/// becomes `last_seq`, so a resumed engine still refuses a reused one.
fn resume_after(prior: &[Seal]) -> Self {
let mut e = Self::new();
for s in prior {
for (&row, payload) in &s.batch.image {
e.sealed_rows[row as usize] = payload[0];
}
let top = s.batch.landings.iter().map(|l| l.stream_position).max();
e.last_seq = e.last_seq.max(top);
Comment on lines +194 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist the actual event high-water mark

When resuming after a seal, the largest landing position is not necessarily the last accepted event sequence: boundary events never become landings, and internal events whose folds leave a row unchanged are omitted from the batch. For example, after accepting internal seq=5 and boundary seq=6, this restores last_seq as 5, so a resumed engine accepts a reused seq=6, defeating the recovery guarantee this change claims to add. Persist and restore the accepted event high-water mark itself, including the boundary sequence, rather than deriving it from coalesced landings.

Useful? React with 👍 / 👎.

e.ledger.push(s.clone());
}
e.rows = e.sealed_rows;
Expand Down Expand Up @@ -362,17 +381,29 @@ mod tests {
reference.batch.batch_hash
);

// Two events on the same row, swapped (the fold does not commute).
// Two operations on the same row, applied in the other order (the
// fold does not commute). The values swap and the `seq`s stay put, so
// the stream is still strictly increasing and is not refused.
let mut swapped: Vec<Event> = runs[2].to_vec();
let row_of = |e: &Event| match e {
Event::Internal { row, .. } => Some(*row),
Event::Boundary { .. } => None,
};
let value_of = |e: &Event| match e {
Event::Internal { value, .. } => *value,
Event::Boundary { .. } => unreachable!("only internal events swap"),
};
let i = 0;
let j = (1..swapped.len())
.find(|&j| row_of(&swapped[j]) == row_of(&swapped[i]))
.unwrap();
swapped.swap(i, j);
let (vi, vj) = (value_of(&swapped[i]), value_of(&swapped[j]));
assert_ne!(vi, vj, "fixture: the two values must differ");
for (k, v) in [(i, vj), (j, vi)] {
if let Event::Internal { value, .. } = &mut swapped[k] {
*value = v;
}
}
let mut reordered = Engine::resume_after(&live.ledger[..2]);
for ev in swapped {
reordered.apply(ev).unwrap();
Expand All @@ -398,6 +429,62 @@ mod tests {
);
}

/// FAILS IF: a reused `seq` is accepted. Both cases from review: a reuse
/// on the same row (row coalescing hides it from `first_tie`) and a reuse
/// across two seals (each batch alone has no tie). Also after a resume.
#[test]
fn a_reused_seq_is_refused() {
// Same row, same seq: the second event folds nothing.
let mut e = Engine::new();
e.apply(Event::Internal {
seq: 5,
row: 2,
value: 1,
})
.unwrap();
assert_eq!(
e.apply(Event::Internal {
seq: 5,
row: 2,
value: 9
}),
Err(SealError::StaleSeq(5))
);
assert_eq!(e.rows[2], fold(0, 1), "the refused event did not fold");
assert_eq!(e.internal_ops, 1);

// Across seals: seq 5 is sealed, then reused after the boundary.
e.apply(Event::Boundary { seq: 6 }).unwrap();
assert_eq!(e.ledger.len(), 1);
assert_eq!(
e.apply(Event::Internal {
seq: 5,
row: 3,
value: 4
}),
Err(SealError::StaleSeq(5))
);

// After a resume from that seal, the persisted key is still known.
let mut resumed = Engine::resume_after(&e.ledger);
assert_eq!(
resumed.apply(Event::Internal {
seq: 5,
row: 3,
value: 4
}),
Err(SealError::StaleSeq(5))
);
// Silence twin: a fresh, larger seq is accepted.
resumed
.apply(Event::Internal {
seq: 7,
row: 3,
value: 4,
})
.unwrap();
}

/// The known limit, measured on the shipped `freeze`. FAILS IF: tied keys
/// stop making the seal depend on arrival order (then the refusal would be
/// unnecessary), or the probe seals a tied batch instead of refusing it.
Expand Down
Loading