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
50 changes: 50 additions & 0 deletions .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
## 2026-09-05 — ledger L1/L2: the semantic leaks the T2/T3 gate could not see

**Branch `claude/membrane-l1-l2-semantic-leaks`.** Follows #74 (the mechanical
fence). These are the two rows `membrane-tiers.md` marked `[OPEN]` because
reflection cannot tell `int classid` from `int facet`.

**L1 — `WideFieldMask.ofFacets(int...)` → package-private.** It built a mask
from raw facet SLOT positions: a byte position crossing the consumer wall. Zero
production callers (only `MaskNativeOpsTest`, same package, unaffected). The
name-side replacement already existed and needed no ABI: `RowStore.hop(int
edgeClassid, Mask)` passes `allFacets()` and `lgj_hop` narrows natively by
`edge_participation(classid)` — the javadoc already says "a caller cannot widen
past what the class actually permits." `allFacets()` = "let the class decide"
is now the consumer's whole participation vocabulary. Demoted, not removed
(I-LEGACY-API-FEATURE-GATED: same shape, narrower reach) — it stays as the
in-package bridge behind `ofMatchBits` and the bit-layout tests. Pinned BY NAME
in `ApiSurfaceTest` (re-publicising it fails the build).

**L2 — closes by the EXISTING gate; the ledger overstated it.** Read again:
`abi.md:312` — "Java's *public* API never sees an address." The offset+stride
carrier is `Engine.LaneWindow` in `internal.ffm`, used only inside
`RowStore`/`Mask`/`LaneProbe` (package-private) and the sanctioned lab
consumers (bench, valhalla-lab). `ApiSurfaceTest`'s `internal.*` prefix already
fences it from every public signature. And `RowStore`'s `row * strideBytes()`
reads the stride FROM the served descriptor (`abi.md:367-368`), never computes
it — that is NAMED under `kernel-membrane-warden`, not GEOMETRY-LEAK. Added a
structural pin: the class exists AND lives under a FORBIDDEN prefix, so any
public signature carrying it is already a LEAK — L2 is closed by proof, not
promise. The genuine residual is not a leak but a design ceiling: Valhalla
views that KNOW the layout should be OGAR-emitted per ClassView, not
hand-carved in the lab (membrane-tiers.md Tier 3) — a future wave, not a fence.

**Not run in-session:** the Java suite (JDK 21 here vs JDK 27 + preview FFM).
`WideFieldMask.java` compiled standalone and the L1 reflection pin proven
standalone; CI runs the rest.

**Same-PR correction (codex + coderabbit P2, both valid).** (1) Demoting one
factory was not a fence: `WideFieldMask` was a public *record*, so its canonical
ctor `new WideFieldMask(1L << slot)` was a public bits-in path, and
`ofMatchBits(int)` another. Now a `final class` with a private ctor; `ofFacets`
AND `ofMatchBits` package-private; value-based `equals`/`hashCode`; no public
`value()`. Still JEP 401 value-class-eligible. The pin is on the SHAPE, not a
name: no public ctor, not a record, every public factory zero-arg. (2) The L2
pin was erasure-blind: `leaksIn` used `getReturnType()`, so `List<Engine.
LaneWindow>` would erase to `List`. `checkType()` now walks generic signatures
(ParameterizedType / GenericArrayType / WildcardType / TypeVariable), with
can-it-fire (`List<MethodHandle>` flagged; erased-only misses it — the walk is
load-bearing; nested + array generics flagged) and can-it-stay-silent
(`List<String>` not flagged). Standalone: shape proof 5/5, walker proof 4/4.

## 2026-09-04 — lgj_hop: the conjunction is one truth-table pass, and the lane is read as a lane (5×)

**Branch `claude/pr-294-ragged-path-validation-170zcy`**, lgj-abi only — no
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public long rowCount() {
*
* <p><strong>Low-level inspection / diagnostics.</strong> High-level query or traversal
* implementations MUST NOT use this as their execution engine — see the root CLAUDE.md
* mask-native policy. {@link WideFieldMask#ofMatchBits(int)} is the typed bridge for a caller
* mask-native policy. {@code WideFieldMask.ofMatchBits(int)} (package-private bridge) is the typed bridge for a caller
* that does need this bitset as a {@link WideFieldMask}.
*
* @throws IndexOutOfBoundsException if {@code row} is not in {@code [0, rowCount())}
Expand Down
71 changes: 61 additions & 10 deletions java/src/main/java/com/adaworldapi/lancegraph/WideFieldMask.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* positions, at which point it promotes once to {@code Wide(Box<[u64]>)}.
* <strong>This Java type carries the Small tier only</strong> — one {@code long}, no promotion
* path. A {@link RowStore} has exactly 32 facets today, so the low 32 bits are the only ones this
* library ever populates or reads through {@link #allFacets()}/{@link #ofFacets}: positions
* library ever populates or reads through {@link #allFacets()} (or, in-package, {@code ofFacets}): positions
* 32..63 are addressable (a {@code long} has room for them, matching the Rust {@code Small}
* representation bit-for-bit) but no {@link RowStore} shape in this codebase uses them. A future
* surface past 64 fields needs the Wide-tier promotion on the Rust side, mirrored here as a
Expand All @@ -24,17 +24,40 @@
* facet-adjacent vocabularies ({@link FacetId}, the raw match bitset, this type) with no declared
* relationship between any of them.
*
* <h2>Why this is a final class and not a record (T2/T3 membrane, ledger L1)</h2>
*
* <p>A Java record's canonical constructor is unavoidably public. For this type that constructor
* takes the raw bits — {@code new WideFieldMask(1L << slot)} would be a public path from a facet
* SLOT position to a participation mask, and a slot index is a byte position, which never crosses
* the consumer wall (lance-graph {@code .claude/knowledge/membrane-tiers.md}). So the constructor
* is private and the ONLY public ways to obtain a mask are {@link #EMPTY} and {@link #allFacets()}
* — "let the class decide": a consumer names the edge CLASS ({@link RowStore#hop(int, Mask)}
* takes a classid) and the native side narrows participation to that class's
* {@code ClassView}-resolved facets. The bit-level factories ({@code ofFacets},
* {@code ofMatchBits}) are package-private bridges for the inspection surface and the bit-layout
* tests. {@code ApiSurfaceTest} pins the shape: no public constructor, and every public factory
* takes zero arguments.
*
* <h2>Valhalla A/B candidate</h2>
*
* <p>Same rules as {@link FacetId} and {@link MaskId}: final, immutable, identity-free, so the
* same source compiles as a {@code value record} under JEP 401. Two masks are "the same mask"
* when their bits are equal, never because they are the same object — nothing here relies on
* same source compiles as a {@code value class} under JEP 401 (a value class may keep a private
* constructor and public factories — the fence above costs nothing under flattening). Two masks
* are "the same mask" when their bits are equal, never because they are the same object —
* {@link #equals(Object)} and {@link #hashCode()} are value-based, and nothing here relies on
* reference equality, so flattening changes no observable behaviour.
*/
public record WideFieldMask(long value) {
public final class WideFieldMask {

private static final int FACET_COUNT = 32;

/** The low 32 bits are this store's facets; the record-era {@code value} component, now private. */
private final long value;

private WideFieldMask(long value) {
this.value = value;
}

/** No facet participates. */
public static final WideFieldMask EMPTY = new WideFieldMask(0L);

Expand All @@ -44,7 +67,18 @@ public static WideFieldMask allFacets() {
}

/**
* Build a mask from the populated facet positions.
* Build a mask from raw facet SLOT positions — <strong>package-private by the T2/T3
* membrane</strong> (lance-graph {@code .claude/knowledge/membrane-tiers.md}, ledger L1).
*
* <p>A slot index is a byte position, and byte positions never cross the consumer wall: a
* consumer names <em>which edge class</em> it is hopping ({@link RowStore#hop(int, Mask)}
* takes a classid) and the native side narrows participation to that class's
* {@code ClassView}-resolved facets ({@code edge_participation}). {@link #allFacets()} is
* therefore the consumer's whole vocabulary — "let the class decide" — never a hand-picked
* set of slots. This factory remains for the in-package bridge from the inspection surface
* ({@link #ofMatchBits(int)}) and for tests that pin the bit layout; it was public through
* ABI minor 10 and is demoted here, not removed (I-LEGACY-API-FEATURE-GATED: the shape is
* preserved, only its reach changes).
*
* @param positions each must be in {@code 0..31} — the 32-facet domain of a {@link RowStore}
* row. Validated eagerly rather than silently folded to a no-op: the
Expand All @@ -53,7 +87,7 @@ public static WideFieldMask allFacets() {
* the call site, matching {@link FacetId}'s own convention in this codebase.
* @throws IllegalArgumentException if any position is outside {@code 0..31}
*/
public static WideFieldMask ofFacets(int... positions) {
static WideFieldMask ofFacets(int... positions) {
Comment thread
AdaWorldAPI marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
java.util.Objects.requireNonNull(positions, "positions");
long bits = 0L;
for (int p : positions) {
Expand All @@ -70,11 +104,17 @@ public static WideFieldMask ofFacets(int... positions) {
* The bridge from {@link FacetMatchView#matchesOf(long)}'s raw per-row bitset: bit {@code f}
* of {@code matchBits} becomes facet position {@code f} of this mask.
*
* <p><strong>Package-private by the T2/T3 membrane</strong> (ledger L1): a raw facet bitset is
* a set of slot positions, and {@code matchesOf} is an inspection/diagnostics read — feeding
* its bits back into {@link RowStore#hop} would be Java deciding membership from per-row
* reads, the exact execution state the mask-native policy forbids. The bridge stays for the
* in-package tests that pin the bit layout; it is not consumer vocabulary.
*
* <p>Zero-extended, never sign-extended — a negative {@code int} still becomes a mask whose
* only meaningfully-populated positions are its low 32 bits, matching a {@link RowStore}'s
* own facet count exactly.
*/
public static WideFieldMask ofMatchBits(int matchBits) {
static WideFieldMask ofMatchBits(int matchBits) {
return new WideFieldMask(Integer.toUnsignedLong(matchBits));
}

Expand All @@ -100,14 +140,25 @@ public int count() {
*
* <p>Package-private: {@link RowStore} is the only caller that needs the raw bits directly;
* every other consumer works through {@link #has}, {@link #count}, and the factories above.
* (The record's own canonical accessor, {@link #value()}, is unavoidably public — a Java
* record's canonical component accessor cannot be declared non-public — but this method is
* the one the facade's internals actually call, named the way the D-LGJ-W8 spec names it.)
* There is deliberately NO public accessor for the raw bits — the record-era public
* {@code value()} went with the record (see the class javadoc): reading the bits is a slot
* read, and slots do not cross the consumer wall in either direction.
*/
long bits() {
return value;
}

/** Value-based: two masks are equal iff their bits are equal (identity is irrelevant). */
@Override
public boolean equals(Object o) {
return o instanceof WideFieldMask other && other.value == value;
}

@Override
public int hashCode() {
return Long.hashCode(value);
}

@Override
public String toString() {
return "WideFieldMask[" + count() + " facets, 0x" + Long.toHexString(value) + "]";
Expand Down
Loading