bricks sumBy() is one fused grouped fold (D-LGJ-FOLD-5), and a red materializeRows() pin on main root-caused - #85
Conversation
…-FOLD-5) `BricksQuery.sumBy()` issued one `sumOf` over `where(REGION.eq(v))` per region -- sixteen queries, each paying plan evaluation into a selection mask plus the reduction over it. Measured here at 32 crossings, and that number was itself a correction landed with the consumer: it replaced a "1 per group" claim that was wrong about a sum terminal's cost. ABI minor 12 (`lgj_plan_group_sum_i32`) made the whole question one fused native program, so the consumer now asks it once. No selection mask is built at any point and nothing is asked per group. Measured through the consumer, not inferred from the core suite: path n = 1,000 n = 64,000 before 32 32 after 1 1 and, new, across group counts at both row counts -- 1 group, 16, 64 -- all 1. The old path was already invariant in the number of rows, which is why its literal was asserted at both row counts; this one is invariant in the number of groups as well. That is a strictly stronger claim, so it gets its own assertion rather than riding on the row-count arm: the public `sumBy()` always asks for exactly `Orders.REGIONS` groups, so nothing the test could previously reach varied the group count, and "one crossing whatever the group count" would have been a doc comment no input could falsify. A package-private `sumByGroupCount` exists for that arm alone; `getMethods()` does not see it, so the aggregate-only-egress guard still audits exactly the public surface the guarantee is about. `Orders.REGIONS` replaces three literal 16s with one named source. It is a MIRROR of the native generator's classid cardinality, which the ABI manifest does not report, so it is pinned against the fixture's own behaviour and not against a copied number -- a second Java literal would drift in lockstep with whatever edited it and prove nothing. Two-sided: every id below REGIONS must carry rows (the constant is not too large) and id REGIONS itself must carry none (not too small, which would mean `sumBy()` silently drops a group). Same shape as the native side's own cardinality test. Return type stays `Map<Integer, Long>`: the map is sized by the question, one entry per group, never by the data. Handing back `GroupTotals` would leak a facade type into the consumer's public surface for nothing. Gates: BricksAuthTest 70/70 (was 62 -- the six group-count arms and the two cardinality checks are the +8), core 612/612, trades 12/12 + 3/3, native artifact rebuilt (abi 0.12, ndarray::simd avx512, release), JDK 28 `--release 28 --enable-preview`. GraphHopTest reports 1 FAILED / 65 passed, and it does so identically on a clean `origin/main` worktree at bb81d80 -- pre-existing, unrelated to this diff, root-caused and handled in the next commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
…dation's own number `GraphHopTest` was RED on `origin/main` before this branch existed -- 1 FAILED / 65 passed, verified by running it from a clean worktree at bb81d80, not inferred. Consumers have no CI line (the only workflow gates `lgj-abi`: fmt, clippy, test), so nothing would have reported it. The assertion said a second `materializeRows()` on the same Mask costs zero further crossings, citing the plan's §3.5 "describe cached per Mask; zero crossings steady-state". It measured 1. Root cause, and it is deliberate: `Mask.words()` no longer returns the cached window directly. It re-describes through `lgj_mask_describe` and compares the returned epoch against the stamp it holds, so a cached address is never read after the substrate has moved under it. Root CLAUDE.md names that as the shipped property -- `Mask` "re-validates the cached window against the generation-checked registry at each top-level facade call". The stale pin was written against the pure-cache behaviour that preceded it. Reverting the number would mean deleting the re-validation, so the test moves, not the code. The describe is still cached in the sense that matters: the words are not re-fetched and the population is never re-scanned -- `lgj_mask_describe` fills a descriptor and does no work over the rows. It is simply no longer free. Steady-state is "constant per call", not "zero". So the re-pin asserts that, rather than just flipping 0 to 1: a THIRD call is measured and required to equal the second. One extra describe happening once would satisfy a bare `== 1` on the second call; only a per-call cost satisfies second == third, which is what a per-call re-validation predicts. The content of the third call is checked against the first as well -- the re-validation returns the same selection, it does not re-answer the question. `PREDICTED_MATERIALIZE_STEADY_CROSSINGS` replaces a bare literal, so the number now carries its provenance next to its sibling first-call constant. Gates: GraphHopTest 68/68 (was 65 + 1 failed; the third-call cost and its content check are the +2), core 612/612, bricks 70/70, trades 12/12 + 3/3, JDK 28 `--release 28 --enable-preview` against abi 0.12. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
…hat hid a red main Hygiene for the two commits before it. LATEST_STATE gains the dated section (the measured table, the group-count arm's reason, the four disable arms); STATUS_BOARD moves D-LGJ-FOLD-5 off Queued with its numbers. The minor-12 section's "Still owed" clause said the bricks consumer still runs the 32-crossing path. It no longer does, so it is struck IN PLACE with the discharge date rather than deleted -- it was true when written, and this board is append-only. Its second clause (`lgj_hop` holding mask-sized Vecs) stands and is still owed. ISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE is the real finding. `main` was red on a consumer check and nothing reported it: the one workflow gates `lgj-abi` only, so no Java compiles in CI at all, and the 612-check core suite plus the three consumer suites are local gates run by whoever remembers. Same shape as the r2il probe step that sat absent from lance-graph CI -- a gate that is never dispatched is indistinguishable from no gate; there the absence was deliberate and documented, here it is neither. The entry names why it is not merely "add a job" (JDK 28 is preview-gated and an EA build apt does not carry, obtained here from a GitHub release download path because the distribution hosts are gateway-blocked -- a runner's network posture is its own question), and carries a falsifier: re-introduce the stale pin and the job must go red, since a job that builds the consumers without running their mains would pass and be no-gate-with-extra-steps. Per the board's own termination clause this commit is hygiene-only and generates no further obligations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches📝 Generate docstrings
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_45698556-6495-4b5b-98f4-54ed44f76d74) |
|
@coderabbitai review Requesting a review explicitly since this is a draft. Two things worth aiming at, because CI cannot see either of them:
Generated by Claude Code |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== changed files ==='
git diff --name-status origin/main...HEAD
echo '=== diff stat ==='
git diff --stat origin/main...HEAD
echo '=== workflows ==='
fd -t f .github 2>/dev/null | sort
for f in $(fd -t f .github/workflows 2>/dev/null | sort); do
echo "--- $f"
cat -n "$f"
done
echo '=== candidate files ==='
fd -t f '^(CLAUDE\.md|GraphHopTest\.java|Orders\.java|Bricks.*\.java|.*Bricks.*\.java|Mask\.java)$' . | sort
echo '=== changed diff ==='
git diff --find-renames --find-copies --unified=80 origin/main...HEAD -- . ':(exclude)*.lock' | sed -n '1,2400p'Length of output: 105227 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== outlines ==='
ast-grep outline java/src/main/java/com/adaworldapi/lancegraph/View.java --items all 2>/dev/null || true
ast-grep outline java/src/main/java/com/adaworldapi/lancegraph/Mask.java --items all 2>/dev/null || true
ast-grep outline java/src/main/java/com/adaworldapi/lancegraph/GroupTotals.java --items all 2>/dev/null || true
ast-grep outline consumers/bricks/src/main/java/com/adaworldapi/bricks/BricksQuery.java --items all 2>/dev/null || true
ast-grep outline consumers/bricks/src/test/java/com/adaworldapi/bricks/BricksAuthTest.java --items all 2>/dev/null || true
echo '=== grouped-fold definitions and use ==='
rg -n -C 12 --glob '*.java' '\bsumByGroup\s*\(' java consumers
echo '=== Mask words and materialization ==='
rg -n -C 20 --glob '*.java' '\b(words|materializeRows)\s*\(' java/src/main/java consumers/graph/src/main/java
echo '=== group validation and GroupTotals API ==='
rg -n -C 14 --glob '*.java' '(groups|GroupTotals|sumByGroup)' java/src/main/java/com/adaworldapi/lancegraph/View.java java/src/main/java/com/adaworldapi/lancegraph/GroupTotals.java
echo '=== exact changed Java files ==='
git diff --check origin/main...HEAD
git diff --unified=12 origin/main...HEAD -- \
consumers/bricks/src/main/java/com/adaworldapi/bricks/BricksQuery.java \
consumers/bricks/src/main/java/com/adaworldapi/bricks/Orders.java \
consumers/bricks/src/test/java/com/adaworldapi/bricks/BricksAuthTest.java \
consumers/graph/src/test/java/com/adaworldapi/graph/GraphHopTest.java | tail -n +1 | sed -n '1,2600p'Length of output: 106767 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '=== stale materialization-cost claims ==='
rg -n -i -C 4 --glob '*.java' '(zero further crossings|zero.*crossing.*repeated|zero crossings.*repeated|no further crossing|after the first call.*no.*crossing|steady-state)' java consumers
echo '=== changed group-count wording ==='
rg -n -C 3 --glob '*.java' '(two group counts|group counts|new int\[\].*\{1, Orders\.REGIONS)' \
consumers/bricks/src/main/java/com/adaworldapi/bricks/BricksQuery.java \
consumers/bricks/src/test/java/com/adaworldapi/bricks/BricksAuthTest.java
echo '=== Graph and Mask Javadoc ranges ==='
sed -n '35,55p;170,220p;255,340p' java/src/main/java/com/adaworldapi/lancegraph/Mask.java
sed -n '208,225p' consumers/graph/src/main/java/com/adaworldapi/graph/Graph.java
echo '=== all direct Java literals tied to region cardinality in bricks ==='
rg -n -C 2 --glob '*.java' '\b16\b|0\.\.15|0\.\.16' consumers/bricksLength of output: 33662
The fused ✏️ Learnings added
|
…miscount of my own CodeRabbit review on #85, both findings real and both mine. 1. I re-pinned the TEST and left two JAVADOC CONTRACTS asserting the behaviour the pin had just contradicted. `Mask.java` said every materializeRows() after the first is "an in-process segment access with no further crossing at all"; `Graph.java` said "a one-time describe, then zero further crossings for repeated calls". Both describe the pure cache that preceded the epoch re-validation. Corrected in place with the struck text kept, and stating what the cache DOES still buy, which is the part worth keeping: the words are never re-fetched and the population is never re-scanned, because lgj_mask_describe fills a descriptor and does no work over the rows. The cost is constant per call, not zero after the first. Worth naming plainly: the previous commit's message argued that a doc-comment claim is not a behaviour, and then left two doc comments claiming the opposite of the behaviour it had just measured. No test could catch it, because a javadoc contract has none -- review did. 2. `BricksQuery.sumBy()` said BricksAuthTest pins the cost across "two group counts". It uses three: 1, Orders.REGIONS, and 4 * Orders.REGIONS. My own miscount, in the same paragraph that argues the group-count arm is what makes the claim falsifiable. Gates unchanged and re-run: core 612/612, bricks 70/70, graph 68/68, trades 12/12 + 3/3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG
|
Both findings were real and both were mine. Fixed in 1. The two contracts. You are right, and the way I got it wrong is the instructive part: I re-pinned the test and left two javadoc contracts asserting the behaviour the pin had just contradicted. Corrected in place, with the struck text retained, and stating what the cache does still buy rather than only what it no longer does: the words are never re-fetched and the population is never re-scanned, because The previous commit message argued that a doc-comment claim is not a behaviour, then left two doc comments claiming the opposite of the behaviour it had just measured. No test could have caught that, because a javadoc contract has none. 2. The miscount. Also right. The javadoc said "two group counts"; the test uses three, Re-ran after the fix: core 612/612, bricks 70/70, graph 68/68, trades 12/12 and 3/3. One note on your learning entry, which is accurate and worth keeping: the per-call crossing applies to Generated by Claude Code |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_4cf811a0-5659-4993-95c1-edd03add9d1d) |
What
The consumer half of ABI minor 12, plus a red pin on
mainroot-caused on the way.1.
brickssumBy()is one fused grouped fold (D-LGJ-FOLD-5). It was sixteen queries — onesumOfoverwhere(REGION.eq(v))per region, each paying plan evaluation into a selection mask plus the reduction over it. Now onesumByGroup. No selection mask is built at any point and nothing is asked per group.2.
GraphHopTest'smaterializeRows()steady-state pin re-pinned 0 → 1. Pre-existing failure, not introduced here.Measured through the consumer
And, new, across group counts at both row counts:
The old path was already invariant in rows, which is why its literal was asserted at both row counts. This one is invariant in groups as well — strictly stronger, so it gets its own assertion instead of riding on the row-count arm.
That arm needed a seam. The public
sumBy()always asks for exactlyOrders.REGIONSgroups, so nothing previously reachable varied the group count and "one crossing whatever the group count" was a doc comment no input could falsify. A package-privatesumByGroupCountserves that arm alone;getMethods()does not see it, so the aggregate-only-egress guard still audits exactly the public surface the guarantee is about.Orders.REGIONSreplaces three literal16s with one named source for the native generator's classid cardinality, which the ABI manifest does not report. Because it is a mirror it is pinned against the fixture's own behaviour, not against a copied number — a second Java literal would drift in lockstep with whatever edited it and prove nothing. Two-sided: every id below REGIONS must carry rows, and id REGIONS itself must carry none.Return type stays
Map<Integer, Long>— sized by the question, one entry per group, never by the data. Handing backGroupTotalswould leak a facade type into a consumer's public surface for nothing.The pre-existing red, and why the test moved rather than the code
GraphHopTestreported 1 FAILED / 65 passed on a cleanorigin/mainworktree atbb81d80— established by running it there, not inferred from this diff's shape.Mask.words()no longer returns its cached window directly: it re-describes throughlgj_mask_describeand compares the returned epoch against its stamp, so a cached address is never read after the substrate moved under it. RootCLAUDE.mdnames that as the shipped property. The pin asserting a secondmaterializeRows()costs zero was written against the pure-cache behaviour that preceded it. Reverting the number would mean deleting the re-validation.The plan's §3.5 "describe cached per Mask; zero crossings steady-state" is superseded for
Mask: the words are still not re-fetched and the population is still never re-scanned —lgj_mask_describefills a descriptor and does no work over the rows — it is simply no longer free. Steady-state is constant per call, and the re-pin asserts that rather than flipping a literal: a third call is measured and required to equal the second. A one-off extra describe satisfies a bare== 1on the second call; only a per-call cost satisfies second == third.Disable arms — four, each red-then-green
sumByto the per-group loopOrders.REGIONS16 → 20Orders.REGIONS16 → 8Mask.words()to a pure cacheArm A is the one that makes this a migration rather than a behaviour change: the answer is identical, only the cost differs. Note even the degenerate case discriminates — 1 group costs 2 on the old path.
B and C together are why the cardinality check is two-sided: each direction is caught by a different half, so neither half alone would catch both.
D is why the second- and third-call assertions are independent rather than redundant — it kills one and leaves the other green, because they test different properties.
Gates
765 checks: core 612/612, bricks 70/70 (was 62 — the six group-count arms and two cardinality checks are the +8), graph 68/68 (was 65 + 1 failed — the third-call cost and its content check are the +2), trades 12/12 + 3/3. JDK 28
--release 28 --enable-previewagainst a freshly builtabi 0.12, ndarray::simd avx512, release.The finding worth acting on separately
mainwas red and nothing reported it. The one workflow gateslgj-abionly — fmt, clippy, test — so no Java compiles in CI at all, and the core suite plus all three consumer suites are local gates run by whoever remembers. Filed asISS-LGJ-CONSUMERS-HAVE-NO-CI-LINE, with the reason it is not merely "add a job" (JDK 28 is preview-gated and an EA build apt does not carry) and a falsifier: re-introduce the stale pin and the job must go red, since a job that builds the consumers without running their mains would pass and be no-gate-with-extra-steps.Board hygiene lands in the third commit; the minor-12 section's now-false "Still owed" clause is struck in place with its discharge date rather than deleted.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GXUahz73MZxtxWcfpHp9dG