Repository navigation
Evaluate each distinct body once, parse the stage once and index used_by per inspection - #184
Merged
Merged
Conversation
Every read command re-evaluated every plate and member body with the kernel, and every write command did so twice: once for the head and once in its commit verifier. A body depends on its shape inputs alone, so copies and repeated parts paid the kernel again and again (issue 26, finding 2). BodyCache keeps kernel bodies by the exact bits of their complete shape inputs: contour, thickness, holes and cut of a plate; axis, roll, cut and resolved profile content of a member. Identity, label, kind, placement, material and the profile digest are not inputs, and tests check that bodies differing only in them are equal before they may share an entry. No digest stands in for an input and -0.0 and 0.0 are different keys (the P04 signatures fold them). The cache is bounded, clears itself when full and never keeps a failed evaluation. The CLI keeps one cache per thread behind evaluate_decoded, so a read of the head, the commit verifier of the same command and the copies of one model share bodies. Every stored mesh is still compared with the body it returns: a tampered derived layer is refused with a warm cache too. Measured in the variant experiment (release build, Linux, 4 vCPU): on a 295-element frame with shared sections, holes and cuts, plate-inspect went from 12.7 s to 1.7 s and copy-element from 27.7 s to 3.9 s; on 200 distinct I-section beams copy-element went from 6.4 s to 4.1 s; reads with nothing shared did not change. Claude-Session: https://claude.ai/code/session_01EEyRacqmnZp4d6Ahazivae
definitions scanned every plate for every listed definition to build used_by, and repeated the scan on every fit_page halving (issue 26, finding 2). Build the material-to-plates map once from the plates in element-id order and look each definition up in it. The lists, their order and the plates-only rule are unchanged; a new test pins them with several materials, an unused material, a member that pins a material and a profile, and a paged listing. Claude-Session: https://claude.ai/code/session_01EEyRacqmnZp4d6Ahazivae
inspect_revision and the commit verifier ran validate_layers, which parses and composes the whole stage, and then Reader::open, which parses and composes it again (issue 26, finding 2). On simple models that second pass was the largest single cost of an inspection. Reader::open_validated runs the same structural rules on the very candidate it then installs: the same checks in the same order, with the same codes and messages, and nothing is installed unless every rule passed. A test compares it with the two calls on a valid stage and on broken definition, mesh, schema-marker and parse inputs. Measured in the variant experiment (release build, Linux, 4 vCPU), with the body cache on: plate-inspect 522 -> 313 ms on 300 copied plates, 609 -> 380 ms on 300 distinct plates and 1.71 -> 1.30 s on a 295-element frame. A third variant, reading each layer blob once instead of twice, showed no measurable effect in any pair and is not part of this change. Claude-Session: https://claude.ai/code/session_01EEyRacqmnZp4d6Ahazivae
The design note gives the fixtures, the eight-way experiment with its decision rule, the before/after numbers and the phase breakdown, the trust argument, why no persistent cache is built, and what is left. Claude-Session: https://claude.ai/code/session_01EEyRacqmnZp4d6Ahazivae
The port initially reused the fork's strict cached evaluation inside evaluate_decoded. On upstream that regressed the K1 draft flow: a near-planar rectangular plate saved as an authored-only draft was then refused by the commit verifier, because the strict path rejects every plate the kernel cannot evaluate instead of listing it as unsupported (upstream #174 made such revisions possible; the fork never stored them, so its CI could not see this). evaluate_decoded keeps the lenient upstream semantics and gains the cache through a new engine::plate_draft::evaluate_committed_drafts_cached, which mirrors evaluate_committed_drafts per plate and classifies refusals with the same draft rules; the uncached entry point delegates to it with a disabled cache. The second pass of an inspection reuses the same thread's cache instead of paying for every body twice.
axisrow
commented
Oct 23, 2026
axisrow
left a comment
Collaborator
Author
There was a problem hiding this comment.
Review of PR #184 — changes requested\n\nReviewed the diff against master (236ce9b): the BodyCache and its key encodings, the cached draft evaluation, the used_by index, Reader::open_validated, the CLI wiring, the design note and the new/extended tests. CI is green (test both OSes, wasm32, deny, cold builds).\n\nThe cache itself is sound: keys are injective byte encodings of the complete shape inputs (raw f64 bits, length prefixes, variant tags), no digest stands in for an input, -0.0 and 0.0 are distinct, refusals are never kept, the byte budget and entry cap clear before overflow, and the disabled cache bypasses everything. The thread-local use in evaluate_decoded is re-entrancy-safe (try_with + try_borrow_mut with a per-call fallback). open_validated runs the same rules on the candidate it installs and keeps the live view on refusal; the new test asserts refusal parity and that the earlier view survives. plate_users preserves the previous used_by order and paging, with a test. The tamper test proves a warm cache still fails verify_derived closed (INTEGRITY_ERROR, zero new misses).\n\n### Requires change\n\n1. The plate read path lost the identity-rotation refusal (inline). On master, evaluate_committed_drafts refused a committed plate with a non-identity placement (UNSUPPORTED_GEOMETRY, placement.orientation_xyzw, "rotated placement is not evaluated in this slice") via create_plate_draft → create_plate, so every consumer of evaluate_decoded refused such a base. The cached fast path checks the placement only with Placement::validate (quaternion validity — the engine test in query/committed.rs asserts evaluate_committed accepts a rotated plate), so a supported-contour rotated plate now evaluates. inspect_revision still refuses it later through scene_elements_with_members, but copy-element, edit, delete, create-plate/create-member on such a base and the commit verifier now proceed where master refused the whole command — and a copy would propagate the rotation into a new revision. It is only reachable via a store the app itself cannot produce (draft admission refuses rotations), but it is an undocumented weakening of an untrusted-input guard and contradicts the port note "same checks, same order, same unsupported classification". Either mirror the guard in the fast path with the same error, or state the relaxation as deliberate in the port notes and the design note.\n\n### Notes (no change required)\n\n- The duplicate-id refusal of evaluate_committed_drafts changed text and position: master returned create_plate-s DuplicateId/new_id/"element id is already owned" after the full validate_arguments; now it is DuplicateId/id/"element id appears twice" before the finer argument checks. That matches the convention of evaluate_committed and is arguably better, but it is a reply-text change on malformed stores — worth a line in the PR description next to the byte-for-byte claim.\n- For the same reason, which refusal surfaces first can change when several malformations coexist (for example members now evaluate before the snapshot build refuses the rotated plate). Both outcomes are refusals; only the reported error differs.\n- solid_bytes counts the blob and separately the vertices and triangles; if the blob is their serialization this double-counts and merely clears the cache earlier — harmless and bounded.\n
The cached fast path evaluated a committed plate whose placement carries a rotation, where create_plate refuses it: every read built on such a base and the commit verifier stopped failing closed. Mirror the guard after the cached evaluation with the same error, delegate the uncached entry point through the same loop, and pin both behaviours with a test.
axisrow
commented
Oct 23, 2026
axisrow
left a comment
Collaborator
Author
There was a problem hiding this comment.
Review of PR #184 (head 6fedbe7) — approving\n\nRe-reviewed the delta since the previous review (3b57f98 → 6fedbe7): one commit, crates/engine/src/plate_draft.rs, +61 lines. The rest of the diff is unchanged and stands as reviewed.\n\nThe required change is resolved correctly:\n\n- The cached fast path now refuses a committed plate whose placement carries a rotation, with the exact error create_plate raises (UNSUPPORTED_GEOMETRY / placement.orientation_xyzw / "rotated placement is not evaluated in this slice"), so every consumer of evaluate_decoded fails closed again, as on master.\n- The placement of the guard preserves the order of effects of create_plate: the body is evaluated (and may be cached — harmless, the refusal happens before it is used), then the rotation refusal, then quantities. The fallback branch keeps going through create_plate_draft, whose classification still requires an identity rotation.\n- The uncached entry point delegates to the same loop, so both paths refuse identically; the new test pins the exact code, field and message, plus the identity neighbour evaluating cached and uncached.\n\nThis also makes the port note "same checks, same order" accurate again for the rotation case.\n\nNotes (no change required):\n\n- The PR description predates the fix commit: the port notes and the test list do not mention the new guard and its test in plate_draft.rs. A one-line addition to the description would keep the advertised test inventory complete.\n- The duplicate-id refusal text change from the previous review remains as agreed (no change required): DuplicateId/id/"element id appears twice", aligned with evaluate_committed, differing from master only on malformed stores.\n- CI at the time of this review: cold builds, wasm32 and deny green; the ubuntu and windows test jobs of this head were still running. The change is one guard plus one test and the port was verified green locally per the description.\n\nVerdict: approved.\n
Blogbotana
approved these changes
Oct 23, 2026
axisrow
referenced
this pull request
in axisrow/BeamCraft
Oct 23, 2026
…-section mesh picking (#187), inspection cache used_by index (#184), craft coordination docs (#186), assembly op_id replay (#183), K1 plate contour cleanup (#174) The upstream squashes carry the same work the fork already has under its own SHAs, plus the promotion adaptations: the doc fixes from the #187/#188 review round, the used_by index widening #184, and the #184 design doc rewritten for the upstream provenance. All six conflicts are the double-landing of promoted work, so the upstream versions are taken as canonical per the 1:1 contract: apps/beamcraft-cli/src/plate.rs, apps/beamcraft-cli/src/view_export.rs, apps/beamcraft-cli/tests/inspection_cache.rs, crates/engine/src/query/pick.rs, docs/design/issue-26-inspection-cache.md, docs/design/v04-picking-snaps.md. Nothing fork-exclusive was dropped (diff master..upstream/master checked per file before the resolution). Co-Authored-By: Claude Code <[email protected]>
This was referenced Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ported from the fork, where it is merged and running: axisrow/BeamCraft#53 (branch
bug/26-inspection-cache, merged there on 2026-10-23). Refs the fork's issue 26, finding 2 — the remaining cost inside the one inspection that every read command runs. Every reply is byte-for-byte unchanged by design.What this changes
beamcraft_engine::query::committed::BodyCache, used per thread bybeamcraft_cli::plate::evaluate_decoded). Kernel bodies are kept by the exact bits of their complete shape inputs: contour, thickness, holes and cut of a plate; axis, roll, cut and resolved profile content of a member.-0.0and0.0are different keys; the P04 signatures fold them, so they are not reused as keys.MAX_ELEMENTSentries, cleared when full, and never keeps a failure. A command's read of the head, its commit verifier and the copies of one model share bodies.used_byindex:definitionsbuilds the material-to-plates map once instead of scanning every plate for every listed definition, on everyfit_pageattempt.beamcraft_layer_engine::Reader::open_validated).inspect_revisionand the commit verifier parsed and composed the stage twice (validate_layers, thenReader::open). Now the same rules run on the candidate that is then installed, with the same order, codes and messages.Trust is unchanged. Received layer blobs are still verified against the manifest, structurally validated, indexed and decoded, and definition digests are recomputed.
verify_derivedstill compares every stored mesh with the body, cached or not. A new test warms the cache, commits a structurally valid but wrong stored mesh for a copy, and requiresINTEGRITY_ERRORwith the body served from the cache.Port notes
master(236ce9b, including Fix K1 plate contours, body frames and unsupported USD drafts #174). The four topic commits were cherry-picked from the fork; the conflicts were adapted to the current master state:plate_userssits directly above the documentedpub(crate) fn definitions_json, and the layer-engine files (Reader::open_validatedsharing one implementation of the rules withvalidate_layers) and the test follow the fork's merged form (beamcraft_json,ElementId::prim_pathmarkers).evaluate_decodedmade the commit verifier refuse exactly the revisions the draft flow is meant to store (reproduced with a near-planar rectangular plate fromcontour_plates.rs). The cache now enters throughevaluate_committed_drafts_cached(engine), which mirrorsevaluate_committed_draftsper plate — same checks, same order, same unsupported classification — and the uncached entry point delegates to it with a disabled cache. The second pass of an inspection reuses the same thread's cache instead of evaluating every body twice.plate_draftevolution that master gained since the fork's measurement base is untouched.Measured, not assumed
Whether to remove the duplicate passes was decided in runs (owner decision): body cache (C), one parse (P) and one verified read of each layer blob (R) were measured in all eight combinations plus the base, against a decision rule fixed beforehand. C and P were kept; R had no measurable effect. The design note has the full tables:
docs/design/issue-26-inspection-cache.md.Before/after on the fork (Linux container, 4 vCPU Xeon 2.8 GHz, rustc 1.97.0, the
distrelease profile, fixtures of contributor-original synthetic stores, alternating runs; every output byte-identical between the two binaries; medians in ms):plate-inspectdefinitionsview-export --out -copy-element(write path)Max RSS moves by at most +0.3 MiB and falls by up to 2.8 MiB on the frame (one parse holds one stage, not two). The figures were taken in the fork's environment and were not re-measured for this port; the code paths are the same.
How you verified it
On the fork (original PR, Linux): clean rebuild,
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace(1,247 passed), fixtures verify,beamcraft-boundaries check,cargo doc -D warnings, wasm32 check,cargo deny check; plus byte-for-byte comparison of every output ofplate-inspect,definitions,view-export --out -andcopy-elementon the four fixtures.This port (macOS, aarch64):
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warningsand the fullcargo test --workspace— green.New tests:
crates/engine/src/query/committed.rs: six unit tests — cached bodies equal fresh ones for boxes, contours, holes and cuts, and for rectangle and I-section members; every shape input misses; excluded fields provably do not change the body; signed zeros are different keys; refusals are not kept; the budget and the disabled cache behave as documented.apps/beamcraft-cli/tests/inspection_cache.rs: cold and warm inspections are equal with hit and miss counts asserted; a warm cache still refuses a structurally valid but wrong stored mesh withINTEGRITY_ERROR, as do a cold thread and the CLI.apps/beamcraft-cli/tests/definitions.rs:used_bylists, their element-id order, the plates-only rule and paging.crates/layer-engine/tests/authored.rs:open_validatedagainstvalidate_layers+Reader::open— same result on a valid stage, same refusal on broken definition, mesh, schema-marker and parse inputs and an unknown schema pin, live view kept after a refusal.Never-crash
unwrap/expect/panic!/todo!outside tests. The thread-local cache usestry_withandtry_borrow_mutand falls back to a cache of the call.MAX_ELEMENTS.unsafeProvenance
ATTRIBUTION.mdIssue tracking
Fixes #372