Repository navigation
Verify K1 and K2 acceptance with frozen member cases and explicit verdicts - #198
Conversation
995682d to
f7305e6
Compare
axisrow
left a comment
There was a problem hiding this comment.
Ревью Apibim#198 (test: run frozen K2 cases and record K1 K2 acceptance gaps)
Вердикт: запрошены изменения — одна находка в инлайне.
Порт в целом точен: драйвер и evidence-документ совпадают с проверенной форк-версией (range-diff =), независимые проверки объёма/площади/Euler, явные девиации POS-12/NEG-52/53 с защитными assert-ами. Однако порт взял исходную версию драйвера и НЕ перенёс форк-фикс (fork commit 04dc542, «count every member or mixed fixture case in the K2 driver»): драйвер по-прежнему молча пропускает member/mixed-кейсы фикстуры, не входящие в CASES, поэтому заявленное в доке свойство не выполняется для добавлений. В форке это было исправлено счётчиком всех member/mixed-кейсов (33) с отказом при дрейфе и переформулировкой доки. Находка в инлайне — то же требование, что предъявлялось форк-версии.
| ## K2 fixture inventory | ||
|
|
||
| `f06_member_cases.rs` now drives all 30 member/mixed cases previously assigned to K2 by the K1 | ||
| inventory. Every fixture ID is asserted exactly once, so additions or removals require an explicit |
There was a problem hiding this comment.
Порт не перенёс форк-фикс 04dc542: драйвер молча пропускает новые member/mixed-кейсы фикстуры, не входящие в CASES, так что «additions require an explicit inventory change» не выполняется для добавлений — перенесите счётчик всех member/mixed-кейсов с отказом при дрейфе и скорректируйте формулировку.
The merge-base changed after approval.
… acceptance evidence (#198), C03.11 CLI rebase (#219) The upstream commits carry work the fork already holds in equal or newer versions (the Q01 publication test, the frozen K2 member cases, the C03.11 rebase from fork #83), so the merged tree is identical to the previous fork master. The four add/add conflicts are places where the upstream copies are narrower than the fork ones: the command-evidence mark group (A09), the D01-D03 drawing commands, and the rigid member placement (#117) whose upstream-side F06 deviations our merged engine already retired. All four resolve to the fork versions. The merge commit links the histories so the next sync starts from a common ancestor instead of re-fighting the same conflicts. Co-Authored-By: Claude Code <[email protected]>
* feat: render a published drawing version to PDF and print sheets (D08) (#244) D08 adds the two export surfaces that D03's publication lacked. Both bind to one exact published document version, reusing the strictly resolved stroke geometry that the SVG export already produced, so fonts and symbols stay BeamCraft's own contributor-original stroke drawing: no font is embedded, referenced or substituted anywhere (AGENTS.md sections 1 and 2). - engine `drawing::pdf`: deterministic single-page PDF 1.4 built from path-painting operators only. No /ID, clock or random field: identical version inputs give identical bytes. Literal-string metadata escapes backslash, parentheses and every non-printable byte as three-digit octal. - engine `drawing::print`: the print pipeline shape. Resolved strokes are laid out 1:1 in paper millimetres, centred on a fixed A4-A0 portrait sheet; a drawing that cannot fit its sheet refuses with LIMIT_EXCEEDED instead of silently rescaling, because a rescaled print would misstate every drawn size against its model-millimetre label. - svg refactor: the resolved stroke groups, bounds and glyph/arrow helpers are shared by all three export surfaces; SVG bytes are unchanged. - CLI `export-drawing` gains `--format svg|pdf` and `--sheet a4|a3|a2|a1|a0` (pdf = print-ready centred sheet page); the drawing contract document module map records the new submodules. Native printer spooling (OS dialogs, CUPS/GDI) stays a desktop-app follow-up; the headless pipeline now ends in the print-ready page. Checks: cargo fmt --all; cargo clippy --workspace --all-targets -- -D warnings; cargo test --workspace; cargo run -p beamcraft-cli -- --version. Engine unit tests cover PDF structure (xref offsets, no /Font), print fit refusals and translation-invariant centring; the CLI end-to-end test prints one published version onto A4 twice and asserts identical bytes, refusals for unknown sheet/format and untouched document heads. Fixes #232 Co-authored-by: axisrow <[email protected]> * feat: lay plate thickness off the contour plane (A12.1) (#251) * feat: lay plate thickness off the contour plane (A12.1) A plate gains the input thickness_side: top, middle or bottom. The contour stays in the element-local plane z = 0; the side says where the thickness t is laid off from it. Top is the default and keeps every older plate: absent input, absent patch field and absent stored token all read as top, and the top path is bit-identical to A02. The engine lays the plain kernel mesh off with the production rigid map and re-certifies it with its own face table, normals and tolerance, so holes pierce the laid-off band and plane-cut records replay on the laid-off body. The side is stored as the optional steel:plate:thicknessSide token and changes the body cache key and the shape signature. Numbering compares side-blind facts, so two plates differing only in the side share a number. Refs #192 (slice A12.1 of the complex-contour issue; A12.2 and A12.3 follow). * fix: encode the thickness side in the UpdateElement fingerprint An op-id retry that differed only in thickness_side hashed equal and silently replayed instead of refusing with OP_ID_REUSE. The side now rides behind the append-only tag 5, like translation_mm and level, so the fingerprints of older patches are unchanged. --------- Co-authored-by: axisrow <[email protected]> * feat: fastener definitions and libraries pinned by digest (A14.1) (#259) * feat: fastener definitions and libraries pinned by digest (A14.1) Slice A14.1 of the complete bolt groups card (refs #194): a fastener definition in a fastener library in the A07 format, pinned into the project by content digest, with a synthetic example library and one European DIN set. The group, its placement and the generated holes are the following slices (A14.2-A14.4); this slice adds no element and touches no engine command. - beamcraft_model::fastener: FastenerSpec with the bolt amendment's fields (kind, hex head, nut, washer, hole clearance, standard lengths, thread protrusion, declared mass, washer and hole defaults) admitted by the F04 codes and caps plus the definition's own consistency rules; DefinitionCategory::Fastener. - beamcraft_layer_engine: Definition::Fastener rendered as a kind "fastener" definition at /World/Definitions/Fasteners/FST_<sha256>, re-read and re-admitted like a profile; the mapping descriptor gains the fastener line. - beamcraft_cli: load_fastener reads one fastener library file with the profile loader's budgets, identity and refused-entry reporting, entries under /Library/Fasteners; list-library and show-library take --fastener-library (an alternative to --library/--environment, no environments yet per O-B2); define-fastener pins one entry into the store as an immutable definition, so the model never needs the library file again; add-library refuses fastener records (the shipped file is the provenance record). - assets/libraries/synthetic-fasteners.usda: three contributor-original synthetic entries (washered bolt, bare bolt, screw with hole_mode none). assets/libraries/din-fasteners.usda: M12/M16/M20 hex bolts with DIN EN ISO 4032 nuts and DIN EN ISO 7089 washers, clearances from the EN 1090-2 standard hole series, re-entered as our own data; both recorded in ATTRIBUTION.md. - docs/design/a14-1-fastener-libraries.md records the slice, its decisions and its limits. Acceptance covered here: an unknown designation is refused (NOT_FOUND, never a near match); every shipped value has a recorded source and licence; pinning is proven end to end (the digest the listing predicts is what the store pins, and a fresh process reads the definition with the library deleted). "A group never needs the library after pinning" is completed by A14.3 when groups consume the digest. Co-Authored-By: Claude Code <[email protected]> * fix: stop the public fastener docs linking a private loader item The quality job documents with RUSTDOCFLAGS=-D warnings; the intra-doc link from load_fastener to the private load_bytes_as helper broke that. The link is replaced by the public load_fastener_bytes chain it already describes. Co-Authored-By: Claude Code <[email protected]> * fix: the index checks the fastener definition category like the other two The U03 index guards still knew two definition categories: the kind/category cross-check admitted a fastener-kind definition at a profile path and a section kind at a fastener path, a malformed leaf under /World/Definitions/Fasteners passed unflagged, and a digest-less fastener link target lost its digest diagnostic. DefinitionCategory now maps each bim:definitionKind to its category, all three guards cover Fastener, and the index test carries the fastener cases. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * feat: V11 selection switches and snap panel (#255) * feat: add the V11 selection switches and the snap panel The model window's status bar now carries the eight selection switches (parts, assemblies, bolts, welds, holes, grids, construction objects, items) and a Snap switch that opens a snap-kind strip above the bar. A switch is picking state only: it is consulted when a pick or a grid hit-test runs and read by nothing else, so it can never move the model or the snapshot token. The engine's pick query carries the switches as a SelectionFilter (a class that is off is left out of the scan without a refusal or a skip entry), and the CLI's pick exposes it as --exclude with the class tokens, so the same filter is reachable headlessly. Placement point resolution is deliberately untouched: it is a tool's point resolution, not a selection, and the tools consume the snap panel's kinds in T01.6. Switches for classes that have no registered snapshot kind yet (bolts, welds, holes, construction objects, items) are accepted and exclude nothing until those kinds arrive. Fixes #197 * docs: update the status bar doc comment to the V11 switches --------- Co-authored-by: axisrow <[email protected]> * feat: typed forward path for resolution receipts across undo and redo (C04) (#256) Co-authored-by: axisrow <[email protected]> * test: prove the C05 two-offline-client acceptance end to end (#258) The C05 acceptance rows of the C03-C06 note (docs/design/c03-c06-acceptance.md) had no executable tests: the offline two-client harness, the retry path after a base mismatch, the exactly-once publication across commit crash windows, the interleaved coordinator publications, forced identity collisions and the bounded failure surface. This adds them as one real-binary suite, offline_clients.rs, without touching production behavior. The suite bounds its element count (32 + 32) because every commit re-verifies the whole model; the 1000 + 1000 sweep stays recorded as out of scope in the note. The coordinator rebase (C03 remainder) and compensation (C06) rows stay specified only: their runtime is deferred after the MVP by owner decision and needs one focused PR per C-package. Refs #160 Co-authored-by: axisrow <[email protected]> * fix: make the fork-sync PAT the only path for workflow-file pushes (#278) * bisect: fork-sync without the checkout token line * bisect: fork-sync without the workflows permission * fix: make the fork-sync PAT the only path for workflow-file pushes The previous attempt granted the default token `workflows: write` next to `contents: write`. GitHub rejects that: the whole fork-sync.yml fails startup validation on every push and the schedule cannot start at all (cron dead since 13:27Z, bisected 2026-10-27 - the file without the workflows scope validates, with it does not). The job therefore keeps `contents: write` only. The optional FORK_SYNC_TOKEN secret stays the opt-in path for upstream commits that touch .github/workflows/*: without the secret such a sync stops at the push with the script's exit 4 (a red run is the alarm, per the sync contract); with it, the push carries the account identity and every synced master tip gets a CI verdict. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * feat: own drawn paper objects in the drawing document (D07 slice 1) (#271) The drafting card needs geometry primitives that belong to a drawing and never to the model. Add bounded drawn objects (line, polyline, arc, circle, rectangle) as document-owned intent: one full UUIDv7 each, exact paper millimetres, radians, admitted through the same D01 admission and persisted in the closed canonical intent USD under /Drawing/Drafting (schema descriptor extended, canonical re-encode comparison unchanged). The new edit-drafting command replaces the drawn set from the document alone - it takes no model store, proving the card's acceptance that drawn objects never touch the model. Published versions stay immutable: an edit after publication lands as a fresh draft on top and the old version still exports its exact bytes. inspect-drawing reports the drawn objects. Refs #231. Text, hatching, symbols and layers/line types are the later slices; SVG/PDF rendering of drawn objects follows separately. Co-authored-by: axisrow <[email protected]> * feat: loft a ruled surface between two contours (G13) (#273) * feat: loft a ruled surface between two contours (G13) Two planar contours of equal vertex count form one closed certified solid: straight rulings join the index correspondence, wall quads follow the contour edges (split along the interior diagonal when a skew correspondence folds them non-convex), and the G05 cap triangulations close both faces. The sheet thickness is the distance between the contours themselves, so a bent plate authors its outer and inner bend contours and a lofted plate its two face contours directly. Arc contours carry the proven G05 sagitta bound; cylinder and cone patches are the translated-arc and two-radius lofts, checked in the fixtures against the annular-sector and cone closed forms. Fold lines report the rulings at which an authored contour does not continue straight, and the seal verifies the exact oblique-prism volume for exact translations (Cavalieri). Refs #186: slice 1 of the card; the acceptance gate stays with the issue. * test: seal a folded-wall skewed loft through the interior-diagonal split (G13) Review follow-up: every accepted fixture had convex wall quads, where both diagonals are valid, so the diagonal choice in wall_triangles was untested on the acceptance path. The new fixture (a skewed five-point loft from the seeded random scan over random contours and end frames) seals only through the interior-diagonal branch: with the default split forced, the certification refuses this exact body (checked by mutation). The loft carries independent volume bounds (positive, under its bounding box) and an area floor (the two cap areas), plus the divergence-theorem read-back of the blob. Refs #186. --------- Co-authored-by: axisrow <[email protected]> * feat: sweep a member along a polyline path with one mitred section (P12) (#257) * feat: sweep a member along a polyline path with one mitred section (P12) A polybeam is a member whose axis holds a polyline of three to 1024 element-local points instead of two; the point count alone distinguishes it from the P03 straight member, and the stored `bim:axisPointsMm` attribute and its schema are unchanged. The body is the G12 sweep of the one pinned section along that path: every interior vertex is a shared mitre ring, the end rings stay perpendicular to the end tangents, and the section is parallel-transported with no authored roll. The quantity of record is the effective section area times the polyline path length. G12 corollary 1 carries the acceptance: for a section centred on the path every mitre plane crosses the centroid fibre at the vertex, so the certified mesh volume is area x path length and the mitre angles drop out; the seal cross-checks the mesh against that analytic value before anything is stored. Cuts keep the certified replay volume. Point editing is a typed `UpdateElement` patch field: the complete path replacement, authored as `Change::Axis` on the canonical part of an instance group. Two points straighten a polybeam member, three or more make it a polybeam again; the body, mesh and quantity records follow. `create-polybeam` exposes the command in the CLI, and the BOM and numbering member length becomes the body's own path length. Fixes #187 Co-Authored-By: Claude Code <[email protected]> * style: reflow a polybeam test assertion after the mesh bound fix Co-Authored-By: Claude Code <[email protected]> * fix: clear the member roll when an update bends it into a polybeam An update that bends a rolled straight member into a polybeam kept the authored rollRad the sweep silently ignores; the opinion is now cleared and authored as removed on the canonical part. A patch that names path points on a plate target is refused with UNSUPPORTED_PROPERTY instead of dropping the field, and the module doc no longer links the private commit tail. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * feat: five viewport render modes on user-remappable shortcuts (V16) (#276) * feat: five viewport render modes on user-remappable shortcuts (V16) The engine defines the modes in one query (RenderMode: wireframe, transparent, grey, shaded, selected only); the window keeps one loaded scene and only switches the drawing pipeline, so a mode change never reloads, re-uploads or moves the camera. Selected only reads the chosen element with one engine query into a cached single-instance draw of the mesh the device already holds. Shortcuts live in shortcuts.json in the user configuration directory (BEAMCRAFT_CONFIG_DIR or --config-dir with --store): Ctrl+1 to Ctrl+5 by default, a damaged file keeps the defaults and is reported, and one key never drives two modes. --perf-run gains a phase per mode name, so one run records the frame-time baseline of every mode over the loaded scene. * test: a config directory is accepted with a store window --config-dir now names the shortcuts file's directory in the model window too, so the usage test refuses the combinations that are still invalid: an author without a model and a metrics run with a config directory. * fix: a perf mode phase resets the render mode on its boundary A mode phase left App.mode set after its last frame, so a following phase without a mode of its own drew its frames in that leaked mode while the report labelled them with its own name. The boundary frame now re-states the next phase's mode, or shaded after the last mode phase, so a mixed plan measures what its report says. --------- Co-authored-by: axisrow <[email protected]> * refactor: share the changed_layers publication tail and the CLI test fixtures (#272) The copy/edit command adapters each carried an identical changed_layers computation; publication::changed_layers replaces those 13 copies. The CLI integration suites each carried a private temp-store fixture, binary runner and JSON accessors; support::StoreFixture now backs them and the shared text/at accessors replace the local copies, keeping command-specific change construction, admission and replay decisions, evidence and id-map harnesses and domain expectations local. Behavior and the CLI output contract are unchanged; the workspace suite passes before and after the refactor. Co-authored-by: axisrow <[email protected]> * feat: place a member's section about its axis (P14) (#274) * feat: place a member's section about its axis (P14) A straight member's section could only sit centred on its axis. Real detailing needs the section off the axis: a beam flush with a grid line, a column face on a boundary, a solid that starts below a floor slab while its axis stays on the grid. The axis points stay the modelling line; the position moves the material. create-member authors an optional SectionPlacement: plane (left/centre/ right) and depth (front/centre/back) with additive offsets in the rolled local section frame, and start/end offsets along the axis that move the solid's faces, so its length follows and must stay positive. The rotation about the axis stays the existing roll. Columns share the whole path. The default placement keeps the kernel's centred body bit for bit: the P03 vertex expressions are used verbatim when nothing is displaced, so mesh bytes, quantities and signatures of members authored without the fields are unchanged. Position is stored as six optional USD attributes (all six or none, absent = centred); the schema descriptor changes, so older pre-alpha stores are refused without migration, the standing pre-alpha policy. Body-cache keys and the P-Mass gross signature include the placement. Design note: docs/design/p14-section-placement.md. * style: cargo fmt on the P14 end-to-end tests * fix: the kernel checks the P14 offset caps itself The frame-error mapping re-checked the offsets, but it only runs when the frame is refused, so a finite offset past the dimension cap built through a direct kernel call or an untrusted committed store re-evaluated without the model admission. Every builder now admits the offsets (finite, within 1e6 mm) before the frame, with kernel and engine regressions. * fix: adapt the P14 replay to the polybeam member slice --------- Co-authored-by: axisrow <[email protected]> * fix: restore CI after shared D07 fixture and G13 guard changes (#279) * fix: use the shared fixture in D07 drafting tests * fix: simplify the G13 finite sweep admission guard * feat: treat plate contour corners with a chamfer or a rounding (A12.2) (#275) * feat: treat plate contour corners with a chamfer or a rounding (A12.2) Slice A12.2 of the complex-plate-contours task (refs #192, the F04 plate amendment draft section 2). A contour vertex may carry a chamfer (one or two cut distances) or a rounding (a radius) as an authored parameter; the sharp contour stays stored. The evaluator derives the treated outline in f64 (arcs faceted to at most the 0.05 mm chord deviation of the round-hole rule), the kernel validates it with its exact predicates, and arc_deviation_mm reports the largest kept deviation. A treated origin rectangle takes the contour path, so quantities are those of the faceted body under the new kernel-a12-corner-treatment-volume label. A treatment on a corner that does not really turn, and treatments whose consumed lengths reach an edge length, are INVALID_ARGUMENT (field corner); values are bounded like other coordinates, and a derived outline beyond the F04 contour cap is LIMIT_EXCEEDED. Holes and plane cuts replay on the treated outline. The five parallel steel:plate:corner* attributes author together or not at all; copies, instances, rebases, undo and reopen carry them exactly. Numbering compares the evaluated outline, so plates whose treated outlines differ share no number; quantity signatures gain the block only for treated plates, keeping older stores' records current. No UpdateElement patch and no corner editing commands: those are the A12.3 slice. Co-Authored-By: Claude Code <[email protected]> * docs: correct the numbering outline comment and bound arc segments early (review notes) The outline_of comment still said the origin rectangle has no outline fact; a treated origin rectangle carries its treated outline (A12.2). The arc faceting now refuses, before it allocates, a rounding whose segment count alone exceeds the F04 contour cap, so a radius near the coordinate cap cannot expand to hundreds of thousands of points on its way to the same refusal. Co-Authored-By: Claude Code <[email protected]> * test: finish migrating the drafting suite to the shared fixtures The merge of #272 left tests/drawing/drafting.rs on the removed Fixture fields and the unscoped Limits import, so the pick suite - which includes it through a path module - no longer compiled and clippy refused the whole workspace. This is the tail of that refactor, included here to unblock CI; no production code changes. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * fix: drop the unused Limits import from the drafting test (#283) A12.2 (#275) removed the last Limits use from tests/drawing/drafting.rs but kept the import, so cargo clippy --workspace --all-targets -D warnings fails on master (test target pick) and the fork-sync gate refuses every sync. Remove the dead import. Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * feat: line, polygon and part cuts, fit, chamfer, split and combine as A-2 records (A13) (#282) * fix: review follow-ups for the A13 fabrication slice Rebased onto current master, so the A12.2 corner-treatment files that an earlier stale-base push showed as deleted are untouched. The split replay now requires the created half and returns both halves from the head; a combine refuses a parallel axis offset across the survivor's line; the split fingerprint hashes the label bytes; a member target of a polygon or part cut is the documented UNSUPPORTED_GEOMETRY; polygon cuts and part cuts carry the member admission; tests cover the split retry, the offset refusal and the member target. * fix: a split replay compares both halves with the probe Presence of the created element alone still replayed a retry that renamed the created half or fired after the target gained a further record. The hold now compares the head created state with the probe created state, so any changed content under the accepted operation id is a reuse refusal, and the a13 suite covers the renamed retry. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * feat: switch between a perspective and an orthographic camera in one window (V13) (#249) * feat: switch between a perspective and an orthographic camera in one window (V13) The orbit camera gains an orthographic projection next to the perspective one, switched from the ribbon's View tab (PROJECTION group). The switch keeps the target, direction and distance, and the orthographic window always follows the distance (half-height = distance * tan(half FOV)), so the model does not jump and zoom, pan, fit and the distance bounds mean the same in both projections. One shader draws both: params.x carries the field angle in perspective and the half-height in orthographic, so the lateral division is unchanged; the mode slot picks the w (view depth vs 1) and the depth mapping. The pick ray follows the image: under the orthographic projection every pixel is a parallel ray along -back from the laterally offset eye point, so what a click selects and where a placement lands keep matching what is on screen (acceptance). The camera uniform grows from 80 to 96 bytes for the mode slot. Closes #198 Co-Authored-By: Claude Code <[email protected]> * fix: pass the selection filter to pick_in in the orthographic click test The rebase onto master kept two-argument pick_in calls; the T02 helper integration added the SelectionFilter argument. ALL matches the sibling click test's semantics. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * chore: add an upstream pull request gate script A cumulative fork branch once rolled back other people's upstream commits on its way to a mirror PR. The gate makes that failure mode impossible to repeat silently: it refuses a branch whose base is behind upstream/master (printing how many upstream commits are missed), refuses deletions of files that exist in upstream/master unless each path is passed to --allow-delete, and prints a changed-files and +/- lines summary otherwise. The script is read-only bash + git; CONTRIBUTING.md gains the matching step 8. Co-Authored-By: Claude Code <[email protected]> * bug: surface moved upstream files as deletions in the PR gate git detects renames by default, so an upstream file moved to a new path showed as R and slipped past the deletion check of upstream-pr-gate.sh; the old path vanished from upstream without hitting --allow-delete. Pass --no-renames so the moved-from path lands in the deleted list. Co-Authored-By: Claude Code <[email protected]> * feat: update cut records atomically with immutable retry evidence (A03) (#264) * feat: remove cuts without reusing record or semantic cap identities (A03) (#265) * feat: update hole records with canonical immutable replay (A04) (#266) * feat: remove hole records with persistent non-reusable identities (A04) (#267) * fix: replay cut creation from immutable accepted evidence (#268) * docs: accept MVP amendments and define the non-duplicating M5 CLI delivery (#263) * docs: accept MVP amendments and map the M5 CLI delivery dependencies * docs: record published CLI slices and avoid duplicate prerequisite work * docs: consolidate the master CI repair reference * docs: record merged P14 and current-master CI repair * feat: author circular member paths with true arc length and bounded meshes (P13) (#270) * feat: persist circular member paths with true arc quantities (P13) * fix: retain sweep deviation admission before arc length capacity * docs: state the P14 placement boundary for circular members * fix: carry the curved flag through the fabrication member adapter --------- Co-authored-by: axisrow <[email protected]> * feat: atomically replace and clear plate corner treatments (A12.3 prerequisite) (#281) * fix: use the shared fixture in D07 drafting tests * feat: replace and clear plate corners in one linked-group edit * fix: return a public typed CLI capacity error for corner lists * fix: qualify the D07 JSON limit across parent fixture updates --------- Co-authored-by: axisrow <[email protected]> * chore: run the local test gate with nextest (#288) * chore: run the local test gate with nextest The local gate (CLAUDE.md, CONTRIBUTING.md, the PR template and fork-sync.sh gate 3/3) still ran `cargo test --workspace`, which executes the 151 test suites serially: 382 s warm. nextest, already the CI runner and documented in docs/development.md, runs the same 1827 tests with process-level parallelism in 132 s (measured locally, #285). Doctests stay a separate gate line because nextest never runs them, exactly like CI. Part of #285. Co-Authored-By: Claude Code <[email protected]> * chore: describe the nextest plus doctest gate in fork-sync usage Same stale-gate class as the main change: the usage block still named bare 'test --workspace' as the gate step. Co-Authored-By: Claude Code <[email protected]> --------- Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * test: speed up the CLI test suite without dropping a case (#287) * test: build the CLI integration suites into one test binary The 76 integration suites of beamcraft-cli each linked their own ~200 MB test binary, and `cargo test` runs test binaries one after another, so a run waited for the slowest case of every suite in turn while the other threads sat idle. One `cli` binary (tests/cli.rs, one module per suite file) links once and lets libtest schedule all 454 cases on one pool. Every suite file stays where it is and keeps its cases; only the shared `support` module moves to the crate root. The two suites that re-run their own binary as a crash child now name the child by its module path. The nextest `fast` profile filters the same suites by module, and documented `--test <suite>` commands become `--test cli -- <suite>::`. `macos_icon` checks the desktop app's icon and stays its own target. Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z * test: run the independent offline-client cases side by side The C05-01 suite-scale case authored 32 plates in workspace A and then 32 in workspace B one process at a time; with every commit re-verifying the draft history this was the longest CLI test by far. The two workspaces are separate stores, as two offline machines would be, so they are now authored on two threads; publication, rebase and every assertion stay sequential and unchanged. The crash-window case looped over seven commit points with a fresh fixture each; each point is now its own test so they run in parallel and a failure names its window. Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z * test: fail when a CLI suite file is not part of the cli binary With `autotests = false`, a new file under apps/beamcraft-cli/tests that is not declared in tests/cli.rs would never build or run, silently. A guard test now reads the directory and requires every suite file to be a module of the binary. docs/development.md says how to run one suite. Claude-Session: https://claude.ai/code/session_01HF6nmZMJd1rwsnpfakAu3Z --------- Co-authored-by: Claude <[email protected]> * test: avoid redundant CLI fixture preparation (#286) * feat: compensate a published operation as a forward draft operation (C06) (#296) * feat: compensate a published operation as a forward draft operation (C06) A compensation appends one ordinary evidence-bearing draft operation that restores the target's semantic write groups to the accepted state before it, materialized against the current published head with the rebase materializer. The C04 planner runs first and refuses with the bounded report naming every accepted operation after the target that intersects the target's footprint, so a later edit by another actor is never rolled back or silently overwritten. The identity replays exactly once across processes, entry points and crash windows; the compensated draft publishes through the ordinary path. The wire operation compensate and the coordinator-compensate client run the same local semantics with the token actor, who must own the workspace. Acceptance C06-01..C06-07 is executable in tests/compensation.rs. Refs #293, refs #12, refs #160. * fix: refuse a compensation on a dirty draft so unsent work is never dropped The compensation candidate replaces the whole draft content, so a draft with unsent operations would silently lose them. The precondition now requires a clean draft (draft head at the synced sequence) with a typed remedy, like the C03/C04 discipline of retaining or explicitly renouncing unsent work; the disjoint-edit acceptance now publishes its extra plate before compensating and asserts it survives. --------- Co-authored-by: axisrow <[email protected]> * feat: rebase an unsent draft through the authenticated coordinator (C03) (#294) The coordinator wire only spoke head and publish, so a client had to run rebase-workspace as a separate local process between publications. The new rebase operation runs the same guarded local rebase with the token-derived actor as the only trusted actor, answers the same typed result plus the current head, never moves the published head, and replays one request identity across both entry points and crash windows. Acceptance C03-R01..C03-R08 runs against a real serve-coordinator process in tests/coordinator_rebase.rs. Refs #290, refs #12, refs #160. Co-authored-by: axisrow <[email protected]> * refactor: build the trusted rebase and compensation actor without a dead refusal branch (#297) The coordinator's rebase (C03, #294) and compensation (C06, #296) both derived the trusted actor by re-parsing the authenticated id's canonical text, so every request carried a refusal branch that validation had already made unreachable. Uuid7::from_bytes builds the typed host id from the model id's validated big-endian bytes instead; the canonical text is derived, so the construction is infallible and the dead INVALID_ARGUMENT branch is gone from both wire operations. A host unit test pins the canonical formatting and its round trip through parse. The acceptance specification's header still listed the coordinator rebase (C03) and compensation (C06) as specified only; both run executable as of 2026-10-30 (tests/coordinator_rebase.rs, tests/compensation.rs), so the header now points at the suites and names the semantic-conflict workflow (C04) as the part that remains specified only. Refs #294, refs #296, refs #12, refs #160. Co-authored-by: axisrow <[email protected]> * chore: install cargo-nextest in the fork-sync workflow (#298) PR #288 moved gate 3/3 of tools/fork-sync.sh to cargo nextest, but the cron runner never installed it, so every pending sync died with "no such command: nextest", rolled the merge back and exited 3, leaving the fork behind upstream. Reuse the pinned taiki-e/install-action step from ci.yml with the same pinned nextest version. Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * chore: install software Vulkan for the fork-sync UI tests (#299) The first sync after the nextest fix reached the full test gate and died in beamcraft-ui: a UI test panics with "a native or software wgpu adapter is required" on the adapter-less ubuntu runner. Upstream CI installs mesa-vulkan-drivers for the same tests; give the sync runner the same package so its gate runs the same code in the same environment. Co-authored-by: axisrow <[email protected]> Co-authored-by: Claude Code <[email protected]> * CI: block PRs whose commits or description carry the Claude Code Co-Authored-By trailer (#301) * feat: fail CI when a PR commit or description carries the Claude Code attribution trailer * fix: take the PR description from the event payload so the scan cannot fail silently --------- Co-authored-by: axisrow <[email protected]> * docs: reflect the K4 delivery state and add the post-MVP C07/C08 cards (#309) The K4 row and the deferred paragraph still said C03-C06 were deferred, but master has moved past that: C03 (#294) and C06 (#296) are merged, C04 is delivered (upstream #503 closed completed) and C05 is delivered at suite scale (32 + 32) with its 1000 + 1000 acceptance open (#292, upstream #504). The row now states the per-package facts in the K6 style, the deferred paragraph lists the remaining post-MVP collaboration work instead, and the later-work paragraph names the M6 cards C07 and C08 (upstream #506, #507) that the roadmap did not mention. Refs Apibim-com/BeamCraft#738, Apibim-com/BeamCraft#739. Co-authored-by: axisrow <[email protected]> * docs: mark the C04 acceptance rows executable and fix the C04-09 cap (#319) The C04 rows have been executable since PRs #171 (mechanics) and #180 (C04-01..C04-10), but the status line still said "remains specified only". The implemented, tested conflict-report budget is MAX_REBASE_CONFLICTS = 256 (crates/engine/src/rebase.rs), not the sketched 4096. Fork issue #291. Co-authored-by: axisrow <[email protected]> * test: add the expanded Q01 alpha story and its coverage matrix (#321) The original Q01 scenario ends at numbering, publication and a drawing; the expanded acceptance (fork #318, upstream #334) asks the story to carry the model tools, views/filters, storey model data, invalid-input and cancellation regressions, and exact stored outputs, with a record of what actually executed. The new suite adds those steps as one fresh-binary story next to the delivered one, and docs/design/q01-alpha-matrix.md separates the executed cells (macOS/Apple silicon, CLI plus the existing run_ui suites) from the cells that stay open, with the open dependency issues named as reasons. The record is evidence, not a gate pass. Co-authored-by: axisrow <[email protected]> * feat: add typed user attributes on any element (I04) (#322) * docs: add the I04 user-attributes design note The storage, engine and CLI design for defined user attributes (text, number, integer, boolean, choice, with units) on any element, their attribute sets, and the unknown-attribute rule: values shown, never dropped, definitions mutable in a dedicated schema scope. * feat: add typed user attributes on any element (I04) Defined attributes (text, number, integer, boolean, choice, with units and named sets) live as mutable schema metadata under /World/Definitions/Attributes; values are bim:attr:<name> properties of any element, written through the existing edit publication path. A value may outlive its definition: such an unknown attribute is shown by attribute-get and never dropped, because every edit is a targeted property write. Commands: attribute-define, attribute-undefine, attribute-set, attribute-clear, attribute-get, attributes. A schema change conflicts as one AttrSchema target; a value change writes only the element's attributes group. The changeset dependency encoding gains a v3 magic without retagging v2, and the conservative changeset reads cover the new group. * docs: fix the broken intra-doc link of the attribute module * fix: align the attribute view, note and unit admission with review values_of no longer presents a leftover value as a defined one when the attribute was re-defined with another type (it stays in the unknown list alone); the design note names NOT_FOUND for setting an undefined attribute; a unit label refuses control characters, as its comment promised, since it is stored verbatim in USD and JSON. --------- Co-authored-by: axisrow <[email protected]> * feat: schedule the verified head through attribute templates (I05) (#323) * docs: design I05 schedules on the shared BOM and filter layers * feat: schedule the verified head through attribute templates (I05) * fix: quote schedule CSV headers and bound the template read --------- Co-authored-by: axisrow <[email protected]> * feat: edit plate contours vertex by vertex (A12.3) (#325) * feat: edit plate contours vertex by vertex (A12.3) The A12.3 prerequisite (atomic --corners replacement) could only retype the whole sparse list; editing a contour after creation meant rewriting it from scratch. This delivers the amendment's five editing commands on top of that replacement: move-plate-vertex, move-plate-edge, insert-plate-vertex, delete-plate-vertex and set-plate-corner. The engine part is the pure contour_edit module: each edit maps the authored contour and its sparse corner list to the edited pair, remapping treatment positions after an insertion or deletion and carrying the deleted vertex's treatment away. UpdateElement grows a complete contour_points_mm replacement (fingerprinted append-only, so earlier patch fingerprints are unchanged); the CLI commits each edit as one shape-changing contour + corners patch through the existing update path, so the kernel revalidates the new contour, every hole and cut replays, a linked definition group updates atomically and the revision is one undo unit. delete-plate-vertex refuses on a three-vertex contour whatever the caller asks; invalid geometry refuses before publication. keep_degenerate (P-Degenerate) remains outstanding. * fix: separate the contour-edits help segment from the next one --------- Co-authored-by: axisrow <[email protected]> * feat: build fastener geometry prototypes (A14.2) (#326) * feat: build fastener geometry prototypes (A14.2) * fix: count the shank-through-head overlap once and keep the profile codes --------- Co-authored-by: axisrow <[email protected]> * refactor: dispatch CLI commands from the registry and list access and parameters (slices 2-3 of #753) (#760) * refactor: dispatch CLI commands from the registry (second slice of #753) The binary chose an adapter with a chain of `c == "..."` comparisons in main.rs plus a `handles()` function per adapter, a second hand-kept inventory that could drift from the registry. Each registry entry now names its adapter (`Route`), and main.rs matches on `registry::route(word)`. A word that is not registered cannot reach an adapter and takes the store-level path, which refuses it as before, so a command that is dispatched but unregistered is no longer possible. The per-adapter `handles()` functions are removed. Behaviour, exit codes and output of existing commands are unchanged. * test: wait for the dropped rebase request before the retry The coordinator answers each connection on its own thread, so in lost_answers_and_a_killed_server_replay_exactly_once the retry could overtake the request whose client was dropped and do the merge itself, answering workspace_rebased instead of workspace_rebase_replayed. It failed once on the ubuntu CI runner. Wait until the draft head moves, which happens when the dropped request has merged, before sending the retry. * refactor: list access and parameters of every CLI command (third slice of #753) `beamcraft commands` now carries, for every command, whether it reads or writes (Access) and its options: value shape, whether it is required and, for alternatives, a one_of group. The five common request identity options are listed on the commands that take them (Ids::Common) instead of being repeated in 125 entries. The data was taken from each adapter's allow-list and required checks, not from the help prose; where they differ (for example update-element --corners and --axis-point, create-grid, create-plate --material) the code wins. A rule that links optional options (--width with --length) stays in the family help. Tests: the listing carries access and parameters; an unlisted option is refused as unknown (so the probe is valid); every listed option is accepted by its command, which fails if a listed name is wrong or removed. --------- Co-authored-by: Yury Isachenkov <[email protected]> * docs: generate the CLI command reference from the registry (slice 7 of #753, first part) (#761) COMMANDS.md renders every command from the registry as one usage line (required options bare, optional ones in brackets, one-of groups in parentheses) with its family, tier and access. A test fails when the committed file differs from the generated text, and BEAMCRAFT_BLESS=1 rewrites it, so the reference cannot drift from `beamcraft commands`. The README is not shortened yet: it carries rules and examples that the registry does not hold. Also lists three options the adapters accept but the registry missed: update-element --profile and --profile-digest (added by #757) and create-project --environment (added by #755). The "listed option is accepted" test cannot see an omission, so every undeclared sibling option was probed once by hand; no other command accepts an unlisted option except the shared drawing allow-list and a few options that are accepted and ignored or refused later. Co-authored-by: Yury Isachenkov <[email protected]> * docs: interactive-assist features need no CLI command (#762) An agent builds a model by giving coordinates and ids directly, so cursor picking and snapping and the start screen (settings, environments, project-name suggestion, recent projects) do not need a CLI command. Narrow the headless-first rule in AGENTS.md section 3 to features an agent or script needs, and record the decision. The CLI commands themselves are removed in a separate change; the engine APIs stay for the window. Co-authored-by: Yury Isachenkov <[email protected]> * refactor: remove the interactive-assist CLI commands and the tier (slices 4-5 of #753) (#764) An agent builds a model by giving coordinates and ids directly, so the CLI no longer needs cursor picking, snapping or the start screen. Remove pick, snap, show-settings, set-settings, list-environment, suggest-project-name, list-recent and add-recent (owner decision of 2026-10-31, see AGENTS.md section 3). The engine and host functions behind them stay. list-feature, lookup-feature, element-edges and work-plane stay: preview-drawing takes a feature id. With no ui-assist command left, the tier is empty information: drop `Tier`, the `tier` field of `commands` and `commands --tier`. The window still runs pick and the settings commands as a child process, so its start screen, theme persistence and click selection are broken until #763 moves it to the engine; the six UI tests that need them are ignored with that issue's number. The CLI tests of the removed commands are deleted (construction_snaps.rs, the pick and snap tests, the settings tests) or cut down to the commands that remain. The agent skill and the design notes no longer present them. Co-authored-by: Yury Isachenkov <[email protected]> * docs: point the CLI README at the generated command reference (slice 7 of #753) (#765) The README repeated a usage block for most commands. They had drifted (create-plate and update-element lacked many options the commands take) and the generated COMMANDS.md now states them from the registry, with a test that keeps it current. Replace each usage block with a link to the command's entry there; the rules, examples and acceptance notes of each section stay. The one worked example (define-profile then create-member with concrete values) stays too. Co-authored-by: Yury Isachenkov <[email protected]> * feat: construction objects as stored helper snap targets (T02) (#330) Helper points, lines, circles and planes join the model as owned `construction` elements under /World/Construction with identity placement and world-millimetre geometry. They are snap and placement targets only: the BOM and the numbering preview keep reading plate, member and assembly records alone, so a helper is never scheduled or numbered, and deletion is the ordinary DeleteElement lifecycle. Snapping gains a construction source family with its own gate (--construction-snaps) and six kinds (point, line ends and nearest point, circle centre and nearest circumference point, plane foot); helper lines also feed the generic midpoint kind. Continuous kinds follow the nearest switch, and source and point-kind gates run before truncation, as for every other family. The UI compiles with the new source setting and resolves body/reference/grid snaps as before; a viewport slice that draws helpers will pass them into the snap context like the CLI already does. Copy, rotate and arrays remain T03. Co-authored-by: axisrow <[email protected]> * feat: shared typed element filters with class rules (V14) (#329) * feat: shared typed element filters with class rules (V14) Port the reviewed filter evaluator of the lost pre-reset delivery onto the name-keyed attribute schema: one bounded engine Dataset admits records and rules before matching, so unknown fields, unknown attributes and incompatible operators refuse before matching, and a missing value never satisfies a comparison, including ne. Rules cover type, name, class (P15) and user attributes (I04) with all/any groups. query-elements runs the same evaluator over the verified head through the CLI, with sorted pagination and a head manifest pin. Saved sets, view bindings and the UI editor stay with the views layer (V12, W01), which is not re-delivered yet; the evaluator and rule JSON are exactly what they will evaluate. Refs #206. * fix: stale choice options stop targeting and accept the none model pin A value that outlived its undefine into a redefinition with other options passed the record's type check but refused the whole admitted dataset, so every query on such a store failed forever; records now include only values the engine's admission accepts, and a stale option simply never matches. `--expected-model none` now pins a store without a revision, as the help already documented. * fix: resolve the Record intra-doc link the docs gate compiles cargo doc --workspace -D warnings refused the CLI module's unqualified [`Record`] link; the comment now links the engine path and says "admits" where it described the type check the record read actually performs. --------- Co-authored-by: axisrow <[email protected]> * feat: fold and roll plates through owned bend records (A10) (#333) * feat: fold and roll plates through owned bend records (A10) Slice A10 of the bent-plate task (refs axisrow#190, milestone M1). A plate may carry bend operation records (A-2): folds about lines in the blank plane (inner radius, signed angle) or one roll to a cylinder or cone section about an axis. The authored contour of a bent plate is its developed mid-surface outline, so the unfolded outline is the authored contour and the analytic volume stays outline area x thickness: one bend's annular-sector section area is |angle| x t x (r + t/2), exactly the thickness times the developed length it consumes. The body is one G13 ruled loft of the derived cross-section and its translation along the fold-line or roll-axis direction, so the blank must be a rectangle with one side pair parallel to the folds (folds: regions fit and do not overlap) or the developed annular sector about the axis start (cone: one shared apex ratio, the axial span agreeing with the slant range); the wrap stays below a full turn. Bend arcs facet to at most the 0.05 mm chord rule and the kept deviation is reported; folds refuse treatments, holes, cuts and a thickness side. Quantities run under the kernel-a10-bent-plate-volume label; numbering distinguishes bent plates by their hashed local surface. Storage: seven parallel steel:plate:bend* attributes (the Change::Bends whole-list replacement), shared to instances like cuts. CLI: bend-plate with --kind fold|roll; the inspection reports the records and the unfold. The ruled cap faces now share one face id per cap (one entry per logical face), which the view snapshot's face-table rules require. Checks: cargo fmt, clippy -D warnings, cargo nextest --workspace, cargo test --workspace --doc; kernel fixtures compare the sealed folds and rolls with independent closed forms (the L and U folds, the annular-sector prism, the cone band and the development alone). * fix: address the A10 review findings Five review findings on axisrow#333: * a cylinder roll now anchors its loft origin on the authored axis line (the ring stays centred on that line, not on the element origin); * a cone roll with equal radii is refused in the model, the layer validation and the change path: a true cone cannot have them, and the stored encoding keeps one radius for a cylinder, so an equal-radius cone would read back as a cylinder and refuse its own sector blank on every replay; * the seven steel:plate:bend properties are listed in the rebase property groups, so a published Change::Bends delta rebases instead of refusing as unsupported and accepted bend writes stay inside conflict scoping; * Change::Bends joins the plate-only kind guard, so bend attributes can no longer land on a beam or column; * the bent path refuses a clockwise or degenerate blank like the flat paths do, so a negative analytic area can no longer publish. Checks: cargo fmt, clippy -D warnings, cargo nextest run --workspace (1958 tests), cargo test --workspace --doc. * fix: anchor a cylinder roll on the axis line at the blank along-min Review round 2: origin = a anchored the along position on an arbitrary point of the axis line, so two records for one axis line published bodies shifted along it and a far-away endpoint pushed the body off the blank span. The loft origin is now the line point at the blank's along minimum, so records for the same line publish the same body wherever their endpoints sit on it; a regression test compares the two blobs. Checks: cargo fmt, clippy -D warnings, cargo nextest run --workspace. --------- Co-authored-by: axisrow <[email protected]> * feat: add non-destructive view cut planes (V15) (#334) * feat: add non-destructive view cut planes (V15) A cut hides part of the model in one window's image without changing the model, its revisions or any measurement: the engine record holds up to eight user half-spaces plus an optional work-area box, expands to at most sixteen effective planes, and answers a conservative element mask that keeps every partially intersecting body. The window applies the cut as a fragment discard through a second shader uniform, so bodies are cut through in the image while meshes and uploads stay untouched; the View tab's Cut plane tool edits the per-window state. The headless clip-view command reports the effective planes and visible ids over the synthetic scene and re-builds the snapshot after masking to assert the token is unchanged, and its token equals the uncut snapshot-view token of the same pin: a cut is not revision data. * test: name the storage and revision fixture dirs with the process id The compaction and recovery fixtures already carry the process id in their temp directory names, but storage and revision still used test-{nanos}-{counter} with a process-local counter: under nextest's process-per-test model two test processes starting within the same nanosecond build the same name and one crashes with AlreadyExists, as the macOS CI shard did on this branch. Same fix as the compaction fixture got. --------- Co-authored-by: axisrow <[email protected]> * Merge upstream master into the fork: #748 and #749 (#331) * feat: deliver I04 user attributes, the expanded Q01 alpha story and C04 acceptance fixes (#748) * feat: deliver I04 user attributes, the expanded Q01 alpha story and C04 acceptance fixes Typed user attributes (I04): attribute definitions and values on any element across model, engine, layer engine and CLI, stored in USD, unknown attributes never dropped. Expanded Q01 alpha story: a real-binary end-to-end scenario with its coverage matrix. C03-C06 acceptance rows marked executable with the corrected C04-09 cap; the document arrives in its fork state, overlapping the open C03/C06 pull request that carries its new coordinator and compensation tests. * bug: reject control characters in an attribute-set name AttrDef::validate checked only the length of a set name, the one free-text field admitted without the label rule, so a raw newline or other control byte could ride into the USDA string through bim:attr:set. The check now refuses bytes up to U+001F and DEL, like unit labels, option strings and text values, and the design card and the adversarial suite say so. --------- Co-authored-by: axisrow <[email protected]> * feat: rebase the workspace draft through the coordinator (C03) (#749) The coordinator wire gains a `rebase` operation and the CLI a `coordinator-rebase` client, so a client no longer needs a separate local `rebase-workspace` process between publications: the same guarded local rebase runs server-side with the token-derived actor as the only trusted actor, never moves the published head, and replays one request identity across both entry points and crash windows. Acceptance C03-R01..C03-R08 runs against a real `serve-coordinator` process with real token files (`apps/beamcraft-cli/tests/coordinator_rebase.rs`). The transport needed a host-side conversion from the validated model id of the authenticated actor. It deliberately takes the validated `beamcraft_model::id::Uuid7` instead of raw bytes: a constructor from unchecked bytes would hand out a `Uuid7` that `Uuid7::parse` rejects and break the invariant every persisted id relies on (review finding on the combined C03/C06 delivery, Apibim-com/BeamCraft#737). A round-trip unit test pins the conversion. Compensation (C06) is not part of this change: per that review, compensating a published version is an administrator-only action that needs the post-MVP roles-and-permissions model, so it stays deferred. Co-authored-by: axisrow <[email protected]> --------- Co-authored-by: axisrow <[email protected]> * feat: loft a plate between two contours that need not be coplanar (A11) (#327) * feat: loft a plate between two contours that need not be coplanar (A11) A lofted plate stores its two face contours on the plate register (steel:plate:form=lofted plus the two contour arrays) and evaluates through the G13 ruled primitive: one ruling per vertex pair, wall quads along the contour edges, G05 cap triangulations, sealed by the G09 certification. The sheet thickness is the distance between the contours, so the command carries no thickness, holes, cuts or corner treatments. Each contour is admitted by the shared F04 contour admission (winding, planarity band, exact simplicity), which refuses self-intersecting input; a renumbered (twisted) end contour is refused by the seal instead of repaired, and a near-planar admitted contour is refused rather than repaired into an inexact frame. The element keeps the plate kind token and part path, so the instance and category registers stay untouched. Engine: CreateLoftedPlate with its own content fingerprint, the authored record, quantities with mass from the pinned material's density, and a shape signature over the two contours. Layer engine: the Shape::LoftedPlate variant with schema descriptor, encode and structural validation. CLI: create-lofted-plate and inspect-lofted-plates through the same atomic store and strict verifier as evaluated plates. Fixes #191 * feat: show lofted plates in the committed view snapshot A committed lofted plate decoded and verified, but the pinned view snapshot only carried evaluated plates and members, so the element stayed invisible to the viewport. Evaluate the committed lofted plates on inspection, in the manner of every other part, and add their scene elements: the certified mesh stays element-local under the authored rigid pose, in element-id order with the rest of the scene. An engine fixture builds the snapshot from a loft body; the end-to-end suite asserts the element, its exact world bounds and the mesh triangle count through the real binary. --------- Co-authored-by: axisrow <[email protected]> * feat: place drawing views on a document (D04 slice 1) (#335) * feat: place drawing views on a document (D04 slice 1) The D01-D03 contract admits exactly one view per document, so a drawing cannot carry the front, top and right projections of its member side by side, and no command can add one without rebuilding the whole intent from options. Place views as document objects instead: `DrawingSpec.views` admits up to MAX_VIEWS additional ViewSpecs with the same paper bounds as the primary view, nothing is placed automatically, and `projection::resolve_view` resolves each placement from the document's exact pinned snapshot, refusing a foreign view or a stale pin before geometry. View UUIDs are distinct from every other document object in both replacement directions, and the closed one-page intent now budgets its page across placed views and drawn objects together (MAX_INTENT_PAGE_ITEMS), because the reader page, not either collection cap, is the real ceiling. The wire format gains one optional `Scope "Views"` with canonical ordinal children, the schema descriptor is bumped, and the CLI mirrors `edit-drafting` with `edit-views`: no model store on the command line, idempotent replay by request fingerprint, `placed_views` in the inspect output. Part of #228 (view placement core); sections/details and titles/scales are the next slices, sheet rendering stays with D05. * docs: record placed views in the drawing help and contract (D04 review) The edit-views command and its --placed-view syntax were absent from the DOCUMENTS help line and from the drawing contract that its header binds to the code, so the changed surface and its two mirrors disagreed about what the CLI admits. The help now lists edit-views beside edit-drafting, including that create/refresh accept --placed-view without using it and that a refresh drops existing placements; the contract gains section 6b (placed views, their identity rules and the shared one-page cap), admission bullets, and the edit-views entry in the module map. Review finding on #335. --------- Co-authored-by: axisrow <[email protected]> * feat: accept member targets on AddHole/UpdateHole/RemoveHole (A04.2) (#332) A member hole is an owned MemberHoleRecord in the member frame (F06 member-hole amendment): axis point, a section direction, a through or ranged extent and the authoritative round polygon. Records replay in ordinal order after the plane cuts, through the kernel's certified through-hole operation mapped by an exact signed axis permutation, so an axis-aligned straight rectangle-profile member cuts, quantities and undoes end to end today. Section profiles, polybeams, curved paths, non-axis-aligned frames and ranged cutters refuse with UNSUPPORTED_GEOMETRY naming the kernel slice G14.1 that lifts them; a hole that no longer fits refuses with a diagnostic and changes nothing. Storage adds the steel:mhole:* attribute family with its own monotone counter and retired identities, so plate records (A04) and member records never conflate under update-hole/remove-hole while record ids stay one project-wide space. The CLI add-hole/update-hole take the member flags, and the response names the plate or member as the target decides. Refs axisrow/BeamCraft#315. Co-authored-by: axisrow <[email protected]> * feat: add copy, move, rotate, mirror and array transforms (T03) (#328) * feat: add copy, move, rotate, mirror and array transforms (T03) T03 wanted the five transform families by vector or two points, with one undo step per operation and links and quantities remapped. A move stays the placement patch (P08), so the two-point forms are relative fields on the patch (`move_mm`, `rotate`, `mirror`) that the engine composes with the current placement; their replay is decided from history (the head must still be the revision the operation committed), because a relative transform cannot recompute its would-be result from a head it already moved. A mirror is not rigid, so it is re-encoded through a symmetry of the element's local body (`R' = A·R·L`): a plate flips its thickness side and reflects its cuts, a straight member reflects its path and cuts and trades the section offsets; polybeams refuse. The profile definition is not mirrored (chiral sections keep their handedness) - documented as the task's honesty limit. Arrays create independent P05-style duplicates in one batch revision, so one undo step takes every copy back; the level link follows every copy. Gate: fmt, clippy -D warnings, nextest --workspace (1929), doctests. * fix: review fixes for the T03 transform encoding Three findings from the AO review of #328: A mirror never shares a patch with a member path replacement: the mirror re-encodes the body through the path…
What this changes
Mirror of the fork's #70 (
axisrow/BeamCraft,test/k1-k2-gate-evidence), 2 commit(s), verbatim:08f58d6test: run frozen K2 cases and record K1 K2 acceptance gaps04dc542test(f06): count every member or mixed fixture case in the K2 driverStacked on #197 — merge that first. Until #197 is merged, this PR's diff against
masterincludes the earlier promotion commits; this PR's own delta is the 2 commits above. Commits upstream already contained drop out of the chain automatically.How you verified it
cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo test --workspaceThe triple ran on the final promotion chain (which equals the fork's merged master) on macOS; upstream CI re-runs it per push.
Never-crash
unwrap/expect/panic!/todo!outside tests (clippy-D warningsdenies them; the fork enforces the same workspace lints)unsafeProvenance
ATTRIBUTION.md)Anything a reviewer should look at first
Verbatim fork material, already reviewed on the fork side (axisrow#70); the judgement call is the stacking order — merge the chain in numeric order.
🤖 Generated with Claude Code
Issue tracking
Fixes #378