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
33 changes: 33 additions & 0 deletions .claude/board/LATEST_STATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
## 2026-09-04 — the T2/T3 membrane gets its gate: ApiSurfaceTest fences the raw register + names every breach

**Branch `claude/membrane-tiers-bbb-fence`**, test-only. The paired doctrine +
wardens land in lance-graph (`.claude/knowledge/membrane-tiers.md`,
`kernel-membrane-warden`, `bbb-warden`) so every consumer inherits them; this
is the enforcement half in the repo that owns the Java surface.

`ApiSurfaceTest` already forbade FFM / membrane types in any public signature.
Two additive rules extend it to the T2/T3 membrane (names cross, byte positions
never do):

1. **Raw-register fence** — a `byte[]` in any public signature (return, param,
field) is a raw content register (a `[u8;12]` rail array / payload bytes)
crossing the consumer wall. Caught in `check()` on the array component type
(the package-prefix match never sees a primitive component). Ledger L6.
2. **Named-breach rule** — a public method returning ANY array must be named
`materialize*` or `import*`; folds the GraphHopTest allowlist into the
compiled-surface scan so a bridge/inherited array return can't slip a source
grep. Ledger L7.

Green on the current surface by construction: only `Mask.materializeRows()`
returns an array (named), and no public `byte[]` exists (`RowLayout.sets` is
private, unscanned). The two predicates' behaviour was proven standalone (a
byte[] return and param are flagged; an unnamed `int[]` return is flagged;
`materialize*`/`import*` pass) — 5/5. The full Java suite was NOT run in-session
(env has JDK 21; the repo targets JDK 27 + preview FFM + Valhalla) — CI runs it.

**What this gate does NOT prove (honest, per membrane-tiers.md):** reflection
cannot tell `int classid` (a name, clean) from `int facet` (a slot index, a
leak) — same type. `WideFieldMask.ofFacets(int... positions)` (L1) and the
served `LgjLaneDesc` strides (L2) are the semantic leaks the `bbb-warden`
reviews; the gate catches the mechanical subset only.

## 2026-09-03 — mask-risc-lowering ratified and then AMENDED: the vertical axis is enumerated, not cached

**PR #68** (`dfb4ab1`), plan-only. Four commits: SPEC v1 completed to the
Expand Down
44 changes: 44 additions & 0 deletions java/src/test/java/com/adaworldapi/lancegraph/ApiSurfaceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,36 @@ public static void run(Checks c) {
}
}

c.section("the T2/T3 membrane: no raw register crosses, and every array is a named breach");
// Doctrine: .claude/knowledge/membrane-tiers.md (T2/T3). A public consumer
// surface may carry NAMES (handle, classid, field name, version), counts and
// statuses — never a raw content register (byte[] = a [u8;12] rail/payload) and
// never an un-named array population. `byte[]` is folded into FORBIDDEN above.
// Array returns are the row-id/slot-index leak: allowed ONLY from a method whose
// name announces the crossing (materialize* out, import* in) — the GraphHopTest
// allowlist, enforced here on the COMPILED surface so a bridge/inherited return
// cannot slip it past a source grep.
List<String> unnamedArrays = new ArrayList<>();
for (Class<?> type : types) {
for (Method m : type.getMethods()) {
if (!isPublicApi(m) || m.getDeclaringClass() == Object.class) {
continue;
}
if (m.getReturnType().isArray() && !isNamedBreach(m.getName())) {
unnamedArrays.add(type.getSimpleName() + "." + m.getName()
+ " returns " + m.getReturnType().getSimpleName()
+ " but its name does not start with materialize/import");
}
}
}
if (unnamedArrays.isEmpty()) {
c.that("every array-returning public method names its crossing (materialize*/import*)", true);
} else {
for (String u : unnamedArrays) {
c.that("UNNAMED-BREACH: " + u, false);
}
}

c.section("the escape hatch is named, not incidental");
// Raw native access must require deliberately reaching into an internal package. It must
// never be something ordinary composition hands you.
Expand Down Expand Up @@ -135,6 +165,7 @@ private static boolean isPublicApi(Executable e) {

private static void check(List<String> leaks, Class<?> owner, String where, Class<?> t) {
Class<?> component = t;
boolean isArray = t.isArray();
while (component.isArray()) {
component = component.getComponentType();
}
Expand All @@ -144,6 +175,19 @@ private static void check(List<String> leaks, Class<?> owner, String where, Clas
leaks.add(owner.getSimpleName() + "." + where + " is " + name);
}
}
// T2/T3 membrane: a byte[] in any public signature is a raw content register
// (a [u8;12] rail array / payload bytes) crossing the wall — the substrate
// wearing a collection. Names cross; registers never do.
// (.claude/knowledge/membrane-tiers.md ledger L6.)
if (isArray && component == byte.class) {
leaks.add(owner.getSimpleName() + "." + where
+ " is byte[] (a raw content register may not cross the consumer membrane)");
}
}

/** A crossing whose method name announces it — the only sanctioned materialiser/importer. */
private static boolean isNamedBreach(String methodName) {
return methodName.startsWith("materialize") || methodName.startsWith("import");
}

/** Enumerate public types by listing the compiled package directory on the classpath. */
Expand Down