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
15 changes: 15 additions & 0 deletions .claude/plans/d-lanesock-dr-dt64-lane-socket-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,18 @@ Read in code at lance-graph `fb27c46`, OGAR main and report-rs `83093de`. Nothin
### Still closed

Value ingress and the durable workbook writer; D4/S-INGRESS and layout admission; text/codebook authority; exact decimal; production CRM_COMPANY XVERWEIS; cross-coordinate Gather, ADMISSION B, NotASide, OP-14; the `RegistryClassView` DOLCE `entity_type_id` defect (tracked separately).

## Source issuer: the store is bound to the source (2026-11-07)

`LaneSource` now has a required method, `frames()`. It returns the `FrameStore` that lives with the data the source borrows from. `BindStore::bind_lanes` and `LaneBinding::check`/`lane` refuse `Frame(ForeignFrame)` unless the caller's `frames` argument is that same store, compared by identity. The refusal happens before any `lane_of` or `borrow`.

Until now the caller chose the pairing. A binding made over one source could be checked and read against a different source whose lane numbers matched, and the substitute's values were returned. That measurement is in the prior audit; `a_substituted_source_is_refused_not_read` in `crates/lance-graph-dt64/tests/lane_socket.rs` now pins the refusal.

**Owner rule.** An implementation takes `frames()` from the same owner its lanes come from. A constructor never accepts an outside store. In-repo impls:
- dt64 test `Source` and dir-sim `Directory` own their store.
- soa64 `U64Source` returns `SoaU64::frames()`, so swapping the SoA swaps the issuer with it.
- report `AbiLanes` builds its store from the batch.

**Residual (OPEN).** The trait cannot prove that an implementer's store issued that implementer's bytes, so implementer trust remains. Closing it needs byte authentication or a sealed trait. No external implementer is known.

Validation stays per call. There is no check-once or lease; see the optional safe-lease PR.
36 changes: 18 additions & 18 deletions crates/lance-graph-dir-sim/tests/ordinal_chain_lab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,13 +1076,15 @@ mod lanesock {
}

/// The source owner: AD lanes in AD row order, an Entra lane in Entra row
/// order. It hands out its own slices.
/// order. It hands out its own slices, and owns the frame store its data
/// lives with (`LaneSource::frames`): a reorder advances that store.
pub struct Directory {
pub ad_space: CoordSpace,
pub entra_space: CoordSpace,
pub department: Vec<i32>,
pub cost_center: Vec<i32>,
pub licenses: Vec<i32>,
pub frames: FrameStore,
}

impl Directory {
Expand All @@ -1099,6 +1101,7 @@ mod lanesock {
department: (0..l.ad.len()).map(|i| (i % 7) as i32).collect(),
cost_center: (0..l.ad.len()).map(|i| ((i * 37) % 300) as i32).collect(),
licenses: (0..l.entra.len()).map(|j| (j % 50) as i32).collect(),
frames: FrameStore::new(revisions(1, 1, 1)),
}
}
}
Expand Down Expand Up @@ -1136,6 +1139,9 @@ mod lanesock {
_ => return None,
}))
}
fn frames(&self) -> &FrameStore {
&self.frames
}
}

pub fn method(id: u32, reads: u8) -> MethodBinding {
Expand All @@ -1156,8 +1162,6 @@ mod lanesock {
pub struct Bound {
pub store: BindStore,
pub view: View,
pub frames: FrameStore,
pub frame: Frame,
pub route: RouteAt,
}

Expand All @@ -1177,13 +1181,9 @@ mod lanesock {
)
.unwrap();
let version = store.route(POSITION, HYBRID_USER).unwrap();
let frames = FrameStore::new(revisions(1, 1, 1));
let frame = frames.seal();
Bound {
store,
view,
frames,
frame,
route: RouteAt {
position: POSITION,
version,
Expand All @@ -1198,15 +1198,16 @@ mod lanesock {
selection: CoordSpace,
) -> Result<LaneBinding, LaneRefusal> {
let mask = WideFieldMask::from_positions(fields);
let frame = dir.frames.seal();
b.store.bind_lanes(
&b.view,
&b.frames,
&dir.frames,
dir,
LaneRequest {
route: b.route,
rev: SchemaRev(1),
fields: &mask,
frame: &b.frame,
frame: &frame,
scope: READER,
selection,
},
Expand Down Expand Up @@ -1235,7 +1236,7 @@ fn bound_property_lanes_fold_under_the_ruler_selection() {
assert!(admitted.reads.has(DEPARTMENT) && binding.fields().has(DEPARTMENT));
let sum = lowered(confirmed_hybrid(), Agg::SumI32(Col(0)));
let dept = binding
.lane(&b.store, &b.view, &b.frames, &dir, DEPARTMENT)
.lane(&b.store, &b.view, &dir.frames, &dir, DEPARTMENT)
.unwrap();
let masks = plane_refs(&f.planes);
let lanes = [dept];
Expand All @@ -1255,7 +1256,7 @@ fn bound_property_lanes_fold_under_the_ruler_selection() {
Agg::Count,
);
let cost = binding
.lane(&b.store, &b.view, &b.frames, &dir, COST_CENTER)
.lane(&b.store, &b.view, &dir.frames, &dir, COST_CENTER)
.unwrap();
let lanes = [cost];
let inputs = QueryPlanes {
Expand Down Expand Up @@ -1313,17 +1314,16 @@ fn an_entra_lane_is_refused_under_an_ad_selection() {
fn a_reordered_source_refuses_the_old_binding() {
use lanesock::*;
let l = faulted(130, 6);
let mut b = bound();
let dir = Directory::over(&l, 1);
let b = bound();
let mut dir = Directory::over(&l, 1);
let binding = bind(&b, &dir, &[DEPARTMENT], dir.ad_space).unwrap();
b.frames.advance(revisions(1, 1, 2));
dir.frames.advance(revisions(1, 1, 2));
assert_eq!(
binding.check(&b.store, &b.view, &b.frames, &dir),
binding.check(&b.store, &b.view, &dir.frames, &dir),
Err(lance_graph_dt64::LaneRefusal::Frame(
FrameRefusal::CoordinatesChanged
))
);
b.frame = b.frames.seal();
let dir2 = Directory::over(&l, 2);
assert!(bind(&b, &dir2, &[DEPARTMENT], dir2.ad_space).is_ok());
// The old directory's row-order revision no longer matches the selection.
Expand Down Expand Up @@ -1355,7 +1355,7 @@ fn bound_lane_fold_at_64k_allocates_nothing() {
let mut got = Value::SumI64(0);
for _ in 0..PASSES {
let dept = binding
.lane(&b.store, &b.view, &b.frames, &dir, DEPARTMENT)
.lane(&b.store, &b.view, &dir.frames, &dir, DEPARTMENT)
.unwrap();
let lanes = [dept];
let inputs = QueryPlanes {
Expand All @@ -1369,7 +1369,7 @@ fn bound_lane_fold_at_64k_allocates_nothing() {
let t = Instant::now();
for _ in 0..PASSES {
let _ = binding
.lane(&b.store, &b.view, &b.frames, &dir, DEPARTMENT)
.lane(&b.store, &b.view, &dir.frames, &dir, DEPARTMENT)
.unwrap();
}
let socket = t.elapsed();
Expand Down
7 changes: 7 additions & 0 deletions crates/lance-graph-dt64/src/dr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,13 @@ impl FrameStore {
*self.current
}

/// Whether `other` is this same store (identity, never value): the
/// lane-socket gate (D-LANESOCK) uses it to refuse a caller-chosen store
/// that is not the one a source's data lives with.
pub(crate) fn same_store(&self, other: &FrameStore) -> bool {
Arc::ptr_eq(&self.identity, &other.identity)
}

/// `Ok` iff `frame` is this store's current seal. A frame of another store
/// is `ForeignFrame` even at equal numbers; a stale frame of this store
/// names the first revision that moved, or `FrameResealed` if none did.
Expand Down
24 changes: 24 additions & 0 deletions crates/lance-graph-dt64/src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,19 @@ pub trait LaneSource {
) -> Option<LaneAddr>;

fn borrow(&self, addr: &LaneAddr) -> Option<Self::Lane<'_>>;

/// The [`FrameStore`] that lives with the data this source borrows from.
///
/// The source owns this pairing, not the caller: an implementation derives
/// the store from the same owner its lanes come from and never takes one
/// from outside. [`BindStore::bind_lanes`] and [`LaneBinding::check`]
/// refuse `Frame(ForeignFrame)` when the caller's `frames` is any other
/// store, so a binding made over one source cannot be checked or read
/// against another source whose lane numbers happen to match.
///
/// The trait cannot prove that the returned store issued this source's
/// bytes; that remains the implementer's obligation.
fn frames(&self) -> &FrameStore;
}

/// A DR position and the route version it was resolved at.
Expand Down Expand Up @@ -162,6 +175,7 @@ impl BindStore {
source: &S,
req: LaneRequest<'_>,
) -> Result<LaneBinding, LaneRefusal> {
source_owns(frames, source)?;
frames.check(req.frame).map_err(LaneRefusal::Frame)?;
let class = self
.routed_at(req.route.version, req.route.position)?
Expand Down Expand Up @@ -202,6 +216,15 @@ impl BindStore {
}
}

/// The caller's `frames` must be the store the source's data lives with.
fn source_owns<S: LaneSource + ?Sized>(frames: &FrameStore, source: &S) -> Result<(), LaneRefusal> {
if frames.same_store(source.frames()) {
Ok(())
} else {
Err(LaneRefusal::Frame(FrameRefusal::ForeignFrame))
}
}

/// The position must still route to `class` at the store's current version.
fn current_route(store: &BindStore, position: u16, class: u32) -> Result<(), LaneRefusal> {
let now = store.routed_at(store.current_version(), position)?;
Expand Down Expand Up @@ -288,6 +311,7 @@ impl LaneBinding {
frames: &FrameStore,
source: &S,
) -> Result<(), LaneRefusal> {
source_owns(frames, source)?;
frames.check(&self.frame).map_err(LaneRefusal::Frame)?;
current_route(store, self.route.position, self.class)?;
// Revision and count only: the ordered predicate witness was checked
Expand Down
Loading
Loading