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
13 changes: 10 additions & 3 deletions .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
## 2026-11-06 — branch `ccr-fd2682ed-sai06j-abi-lanes` (D-LANESOCK-2, in PR)
## 2026-11-06 — branch `ccr-fd2682ed-sai06j-lanesock-harden` (D-LANESOCK-3, in PR)

- `lance-graph-report::AbiLanes` (`lane_source` module): `LaneSource` over a resident `AbiBatch`; `PublishRefusal`. Field positions via ClassView basis → `Catalog` → `FieldId`; I32/U32 lanes only; one `FrameStore` per source over `(SourceId, generation)`. No production constructor (no DR reader-scope witness); test-only feature `unwitnessed-scope`.
- `rust-test.yml` now runs the `lance-graph-report` tests (plain and the lane-socket suite).
- `LaneSource::lane_of` takes the `FieldRef` DT64 validated (`basis`); `AbiLanes` holds no ClassView. A check through a reordered ClassView of equal class, revision and count refuses `MappingChanged`.
- `AbiLanes::unwitnessed` is `#[cfg(test)]`; the `unwitnessed-scope` feature is gone, so no build (any features) constructs an `AbiLanes`.
- Spec §8: downstream consumer contract (Cockpit / Hub / Matrix over one scoped projection) and the reader-scope witness inventory.

## 2026-11-06 — main after #1969 (merged `53e849a`)

- `lance-graph-report::AbiLanes` (`lane_source` module): `LaneSource` over a resident `AbiBatch`; `PublishRefusal`. Field positions via ClassView basis → `Catalog` → `FieldId`; I32/U32 lanes only; one `FrameStore` per source over `(SourceId, generation)`. No production constructor (no DR reader-scope witness).
- `AbiBatch` holds at most `MAX_COLUMNS` (65,536) columns; `column()` returns a checked `u16` lane index.
- `rust-test.yml` now runs the `lance-graph-report` tests.
- Open: value ingress, durable writer, codebook, exact decimal, D4/S-INGRESS; `RegistryClassView` `entity_type_id` vs concept-key DOLCE defect.

## 2026-11-06 — main after #1964–#1967 (merged `9ca5daa`), OGAR #371
Expand Down
7 changes: 7 additions & 0 deletions .claude/board/PR_ARC_INVENTORY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 2026-11-06 — lance-graph PR #1969 (merged `53e849a`) — D-LANESOCK-2: AbiBatch lanes through the DT64 lane socket

- **Added:** `lance-graph-report::AbiLanes` (`LaneSource` over a resident `AbiBatch`, `publish`, `PublishRefusal`); `AbiBatch` column cap `MAX_COLUMNS` with a checked `u16` lane index (Codex P2: `i as u16` wrapped column 65,536 onto column 0 for every caller); `RegistryClassView` concept-half key test; `rust-test.yml` runs the report tests.
- **Locked (measured):** pointer-equal borrowing; two Quack folds over one binding match an independent scalar oracle; zero hot allocations on reuse; refusals for a published replacement, old row order, foreign frame, foreign source store, re-route, other coordinate space, unknown / absent / `U64` field, out-of-basis position and foreign scope; seven guards disable-verified red-then-green.
- **Not done:** no production constructor (no DR reader-scope witness); no writer or value ingress; exact decimal, codebook and D4/S-INGRESS open; `RegistryClassView` DOLCE `entity_type_id` defect pinned, not fixed. Found afterwards (D-LANESOCK-3): `AbiLanes` read a ClassView of its own and could disagree with DT64's basis; the `unwitnessed-scope` feature exposed the constructor to any build that enabled it.
- **Confidence:** CI green on the merged head.

## 2026-11-06 — lance-graph PRs #1964–#1967 (merged `9ca5daa`), with OGAR #371 — hybrid ordinal chain, Ruler-plane selection, DR × DT64 lane socket

- **Added:**
Expand Down
3 changes: 2 additions & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
| --- | --- | --- |
| D-LANESOCK-0 | normative spec: socket, joint version witness, refusals | Shipped (#1966) |
| D-LANESOCK-1 | `LaneSource` + `BindStore::bind_lanes` + falsifiers in lance-graph-dt64 and the dir-sim lab | Shipped (#1967) |
| D-LANESOCK-2 | `AbiLanes`: resident `AbiBatch` I32/U32 lanes through `LaneSource` (read adapter; no production constructor until a DR reader-scope witness exists) | In PR |
| D-LANESOCK-2 | `AbiLanes`: resident `AbiBatch` I32/U32 lanes through `LaneSource` (read adapter; no production constructor until a DR reader-scope witness exists) | Shipped (#1969) |
| D-LANESOCK-3 | one field basis (`lane_of` receives DT64's validated `FieldRef`); `AbiLanes` constructor `#[cfg(test)]`-only; downstream consumer contract + reader-scope inventory (spec §8) | In PR |

## D-CGMM — Lazy Codegraph fact/demand + MeTTa class-method scratch (2026-11-04)

Expand Down
20 changes: 20 additions & 0 deletions .claude/board/entries/2026-11-06-lanesock-one-field-basis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# One field basis for the lane socket; test-only scope admission (D-LANESOCK-3)

**Date:** 2026-11-06. **Status:** in PR. Follows D-LANESOCK-2 (#1969, merged `53e849a`).

## Defect (RED, recorded at `49d11c2`)

DT64 validated ClassView A; `AbiLanes` resolved its own ClassView B with the same class, revision and field count but the first two fields swapped. The bind succeeded and A's `revenue` field borrowed B's `region` lane: a wrong, nonzero value with every check passing. DT64's basis check compares the field count, so it cannot see a reorder.

## Fix

- `LaneSource::lane_of(.., field, basis: &FieldRef, frame)`: DT64 passes the basis entry it resolved, at bind and on every check. A source maps that field, never a position.
- `AbiLanes` drops its ClassView. A check through a reordered view refuses `MappingChanged`; a bind under that view borrows the lane it names.
- The three implementers were updated (dt64 test source, dir-sim lab, `AbiLanes`).
- `AbiLanes::unwitnessed` and its suite moved into a `#[cfg(test)]` unit module; the `unwitnessed-scope` feature was removed. No build, `--all-features` included, constructs an `AbiLanes`.

Disable run: accepting a changed mapping in `LaneBinding::check` turns the basis test red.

## Handoff

Spec §8: Cockpit / Hub / Matrix consume one scoped projection through DT64 → borrowed lanes → Quack; reader-scope witness inventory (OGAR `RbacAuthority` → `RbacBinding` → `authorize_scoped` / `authorize_memberships`); the missing seam is a witness sealing a decision into `dr::Scope`. Not wired.
3 changes: 2 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately
opposite directions; the stranding this convention prevents shows up in
exactly one of them, never both.

581 entries, 2026-08-06 .. 2026-11-06.
582 entries, 2026-08-06 .. 2026-11-06.

| date | entry id | finding | file |
|---|---|---|---|
| 2026-11-06 | `ruler-plane-batch-selection-lab` | | [2026-11-06-ruler-plane-batch-selection-lab.md](2026-11-06-ruler-plane-batch-selection-lab.md) |
| 2026-11-06 | `D-LANESOCK-3` | | [2026-11-06-lanesock-one-field-basis.md](2026-11-06-lanesock-one-field-basis.md) |
| 2026-11-06 | `D-LANESOCK-2` | | [2026-11-06-abibatch-lane-socket.md](2026-11-06-abibatch-lane-socket.md) |
| 2026-11-05 | `hybrid-ordinal-chain-lab-v2` | | [2026-11-05-hybrid-ordinal-chain-lab-v2.md](2026-11-05-hybrid-ordinal-chain-lab-v2.md) |
| 2026-11-05 | `coresearch-workbook-hypercube-soa-projection` | | [2026-11-05-coresearch-workbook-hypercube-soa-projection.md](2026-11-05-coresearch-workbook-hypercube-soa-projection.md) |
Expand Down
44 changes: 42 additions & 2 deletions .claude/plans/d-lanesock-dr-dt64-lane-socket-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ pub struct LaneAddr { pub space: CoordSpace, pub lane: u16, pub scope: Scope }

pub trait LaneSource {
type Lane<'a> where Self: 'a; // the borrowed lane (e.g. mask-RISC LaneRef)
fn lane_of(&self, class: u32, rev: SchemaRev, field: u8, frame: &Frame) -> Option<LaneAddr>;
fn lane_of(&self, class: u32, rev: SchemaRev, field: u8, basis: &FieldRef, frame: &Frame)
-> Option<LaneAddr>;
fn borrow(&self, addr: &LaneAddr) -> Option<Self::Lane<'_>>;
}
```

`None` from `lane_of` is UNBOUND and is refused, never read as empty. `borrow` hands out the source's own lane; it never copies, reorders or allocates a lane.
`basis` is the field DT64 resolved at `field` through the `ClassView` it validated (amended by D-LANESOCK-3: a source that read a ClassView of its own could disagree with DT64 on field order or identity at equal class, revision and count, and returned another lane). A source maps `basis`, never a position. `None` from `lane_of` is UNBOUND and is refused, never read as empty. `borrow` hands out the source's own lane; it never copies, reorders or allocates a lane.

## 3. The joint witness (DT64)

Expand Down Expand Up @@ -93,3 +94,42 @@ Gather ban, ADMISSION B, NotASide, OP-14, DRISA M1/M2, D4/S-INGRESS. The socket
3. A DT64-admitted method drives one Quack fold over a borrowed lane; the same binding drives a second operation on the other lane.
4. Independent scalar oracle parity; zero hot allocations.
5. Each refusal in §4 fires on a realistic input, and a disabled guard turns its test red.

## 8. Downstream consumer contract (handoff, not wiring)

The report-rs Cockpit (`/view/mobile`), Hub (`/view`) and Matrix (`/`) are three lenses over one scoped projection (report-rs #43). When they move onto source-backed lanes, they consume one answer through this chain and add nothing upstream:

```text
authoritative role/customer scope
→ admitted source population and frame (source owner, DR FrameStore)
→ DT64 property binding (BindStore::bind_lanes, one field basis)
→ borrowed source-owned lanes (LaneSource::borrow, e.g. AbiLanes)
→ Quack (admitted MethodRef, one evaluator)
→ authorized projection (field mask ∩ row scope)
→ Cockpit / Hub / Matrix (presentation only)
```

No lens gets its own lane binding, KPI evaluator, SoA or storage in lance-graph. A KPI is a Quack fold over the same binding; the three surfaces differ only in what they render.

### Reader-scope witness: inventory (2026-11-06)

`dr::Scope` is a bare `{ id: u64 }`; nothing issues it with authority, so no production `LaneSource` may be constructed yet (`AbiLanes` has only a `#[cfg(test)]` constructor). Existing authority pieces, read in code:

| Piece | Where | What it yields |
|---|---|---|
| `RbacAuthority::bind(&RbacPlug) -> RbacBinding` | `lance-graph-contract/src/rbac_plug.rs` | grants for exactly the plugged classids and roles; private fields, no `Default` (fails closed) |
| `authorize_scoped` → `ScopedDecision { decision, scope: Option<ScopeSpec>, field_mask }` | `lance-graph-rbac/src/authorize.rs` | stage-1 verdict, restrictive-AND row scope, union field mask |
| `authorize_memberships` → `MembershipDecision { decision, scope: Option<ScopeSet>, field_mask }` | `lance-graph-rbac/src/authorize.rs` | the same over per-membership scopes |
| `ogar_rbac::authorize_identity(&RbacBinding, &AuthenticatedUser, ..)` | OGAR `crates/ogar-rbac/src/lib.rs` | the membership decision for an `ogar-auth` user |
| `ActorContext { actor_id, tenant_id, roles }` | `lance-graph-contract/src/auth.rs` | JWT-derived actor; not a decision |

The missing seam is a witness that seals an authorized decision (decision + row scope + field mask, for one actor, one class, one frame) into what `LaneRequest.scope` / `LaneAddr.scope` compare. Rules it must keep:

- A numeric `Scope` alone authorizes nothing; equality of two ids is not a grant.
- `scope: None` ("unrestricted") from a decision is never widened automatically into customer-wide access; it must be an explicit grant.
- report-rs's ZITADEL login and its `RoleScopeAuthority` are a downstream integration point that would supply the actor to this chain, not a second upstream identity authority.
- Source status (available, stale, unknown) is reported only from observed evidence.

### 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).
9 changes: 3 additions & 6 deletions .github/workflows/rust-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,11 @@ jobs:
run: cargo test --manifest-path crates/lance-graph-dt64/Cargo.toml
- name: Run xlog tests
run: cargo test --manifest-path crates/lance-graph-xlog/Cargo.toml
# lance-graph-report: no workflow ran its tests. The second line runs the
# D-LANESOCK-2 socket suite, which needs the test-only `unwitnessed-scope`
# feature (the reader scope there is the test's assertion, not a DR
# witness) and is skipped by the plain run.
# lance-graph-report: no workflow ran its tests before D-LANESOCK-2. The
# lane-socket suite is a `#[cfg(test)]` unit module (its constructor
# takes an unwitnessed reader scope), so the plain run covers it.
- name: Run report tests
run: cargo test --manifest-path crates/lance-graph-report/Cargo.toml
- name: Run report lane-socket tests (test-only scope)
run: cargo test --manifest-path crates/lance-graph-report/Cargo.toml --features unwitnessed-scope --test lane_socket
# lance-graph-codegraph is workspace-EXCLUDED (own [workspace], as
# sag-ra-probe), so no line above reaches it. Its suites pin the atomic
# refresh (a refused refresh changes nothing; a retry equals the refresh
Expand Down
1 change: 1 addition & 0 deletions crates/lance-graph-dir-sim/tests/ordinal_chain_lab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ mod lanesock {
class: u32,
rev: SchemaRev,
field: u8,
_basis: &FieldRef,
_frame: &Frame,
) -> Option<LaneAddr> {
if class != HYBRID_USER || rev != SchemaRev(1) {
Expand Down
34 changes: 27 additions & 7 deletions crates/lance-graph-dt64/src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//! is admitted here, so reading one through the other would be a gather.

use lance_graph_contract::class_view::{ClassView, WideFieldMask};
use lance_graph_contract::ontology::FieldRef;

use crate::dr::{Frame, FrameRefusal, FrameStore, RouteVersion, Scope};
use crate::{BindStore, Refusal, SchemaRev};
Expand All @@ -42,13 +43,25 @@ pub struct LaneAddr {
/// the field is unbound, which is refused, never read as empty. `borrow`
/// hands out the source's own lane; it must not copy, reorder or allocate a
/// lane.
///
/// `basis` is the field DT64 resolved at `field` through the [`ClassView`] it
/// validated. A source maps that field, never a position it read from a
/// ClassView of its own: two views can agree on class, revision and field
/// count and still order or name their fields differently.
pub trait LaneSource {
/// The borrowed lane (for example a mask-RISC `LaneRef`).
type Lane<'a>
where
Self: 'a;

fn lane_of(&self, class: u32, rev: SchemaRev, field: u8, frame: &Frame) -> Option<LaneAddr>;
fn lane_of(
&self,
class: u32,
rev: SchemaRev,
field: u8,
basis: &FieldRef,
frame: &Frame,
) -> Option<LaneAddr>;

fn borrow(&self, addr: &LaneAddr) -> Option<Self::Lane<'_>>;
}
Expand Down Expand Up @@ -150,17 +163,16 @@ impl BindStore {
})?;
current_route(self, req.route.position, class)?;
let resolved = self.resolve(view, class, req.rev)?;
let basis = resolved.fields.len();
let mut lanes = Vec::with_capacity(req.fields.count() as usize);
for field in 0..=u8::MAX {
if !req.fields.has(field) {
continue;
}
if usize::from(field) >= basis {
let Some(field_ref) = resolved.fields.get(usize::from(field)) else {
return Err(LaneRefusal::FieldNotInBasis { class, field });
}
};
let addr = source
.lane_of(class, req.rev, field, req.frame)
.lane_of(class, req.rev, field, field_ref, req.frame)
.ok_or(LaneRefusal::Unbound { class, field })?;
admit_lane(field, &addr, req.scope, req.selection)?;
lanes.push((field, addr));
Expand Down Expand Up @@ -259,10 +271,18 @@ impl LaneBinding {
) -> Result<(), LaneRefusal> {
frames.check(&self.frame).map_err(LaneRefusal::Frame)?;
current_route(store, self.route.position, self.class)?;
store.resolve(view, self.class, self.rev)?;
let resolved = store.resolve(view, self.class, self.rev)?;
for (field, addr) in &self.lanes {
let field = *field;
match source.lane_of(self.class, self.rev, field, &self.frame) {
let field_ref =
resolved
.fields
.get(usize::from(field))
.ok_or(LaneRefusal::FieldNotInBasis {
class: self.class,
field,
})?;
match source.lane_of(self.class, self.rev, field, field_ref, &self.frame) {
Some(current) if current == *addr => {}
Some(_) => return Err(LaneRefusal::MappingChanged { field }),
None => {
Expand Down
9 changes: 8 additions & 1 deletion crates/lance-graph-dt64/tests/lane_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ impl Source {

impl LaneSource for Source {
type Lane<'a> = &'a [i32];
fn lane_of(&self, class: u32, rev: SchemaRev, field: u8, _frame: &Frame) -> Option<LaneAddr> {
fn lane_of(
&self,
class: u32,
rev: SchemaRev,
field: u8,
_basis: &FieldRef,
_frame: &Frame,
) -> Option<LaneAddr> {
if class != HYBRID || rev != SchemaRev(1) {
return None;
}
Expand Down
10 changes: 0 additions & 10 deletions crates/lance-graph-report/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,4 @@ lance-graph-contract = { path = "../lance-graph-contract" }
# report -> dt64 adds no cycle.
lance-graph-dt64 = { path = "../lance-graph-dt64" }

[features]
# TEST ONLY: exposes `AbiLanes::unwitnessed`, which takes a reader scope from
# the caller. DR has no authoritative scope issuer, so no production build
# may enable this.
unwitnessed-scope = []

[[test]]
name = "lane_socket"
required-features = ["unwitnessed-scope"]

[dev-dependencies]
Loading
Loading