Repository navigation
fix(desktop): the assistant sees the print check of its changes, blocks measure before they cut - #357
Merged
Merged
Conversation
…ong one diagonally Audit D1/MG-03: width and depth were taken over all bodies together, so two 20 mm cubes designed 300 mm apart blocked as 320 x 20 mm. Audit D2/MG-02: only the axis-parallel box and its quarter turn were tried, so a 300 x 10 x 5 mm bar blocked on a 256 mm plate although it needs 219.2 mm a side across the diagonal. Every body is now measured as it lies and tried in steps of 0.1 degrees about Z; the finding names its body. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…body Audit D3: the area on the bed was added up over all bodies and compared once, so the 400 mm2 of a cube hid a sphere or a cone on its tip beside it (no contact finding). Each body now gets its own finding, named even when no face of it lies on the bed. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ross their whole face Audit D4, N-druckpruefung-1: the wall check knew no direction and no layer height. A 0.3 mm plate flat on the bed blocked as "thinner than the nozzle", and the 0.8 mm floor the design rule asks for warned as a wall under two lines. What lies within 45 degrees of flat, as the body lies, now stands against four layers (at least 0.8 mm) and blocks under one layer; the profile carries the layer height. Audit D6: only the middle of each triangle was measured, so a wall tapering from 2.5 to 0.2 mm passed. Rays are now spread a nozzle width apart and set at the corners, and the block counts the area under the nozzle, not the faces that touch it. Audit MG-04: a wall of exactly two lines (0.9 mm) was reported as under them. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…s corners rest on walls Audit MG-01, N-mechanik-1: a patch counted as a small overhang when every corner of its triangles lay within reach of an edge with material below. A flat face is two triangles with all corners on its walls, so the ceiling of a box (1600 mm2), a 40 mm bridge and the 102 mm ceiling of a guide's groove (3058 mm2) dropped out and the check read ready. Points spread over each triangle are tested now, not only its corners. A flat ceiling between two such edges up to 10 mm apart is a bridge and stays out (UltiMaker; the assistant's design rule takes the same constant); a wider one is reported. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Audit N-druckpruefung-2: SolidCraft's surface bodies (closed false, volume 0) ran through the check as printable parts: four walls without a floor gave only "0 mm2 on the bed", a cube with its top face removed only "bodies touch", and a mesh of five faces read ready. The render mesh keeps the count of mesh edges with one triangle, which the edge derivation already had. A body with any is reported as open, blocks, and is left out of the other checks. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… read as open Review of the print check: a degenerate triangle (a, a, b) left an edge from a to itself that no other triangle closes, so the count of open edges was above 0 and the check blocked a closed solid as an open shell. Such an edge is skipped. Measured on SolidCraft 0.40.1: a sphere (25504 triangles), a cone with a tip (5242) and a cube with a modelled M5 thread (8918) carry no such triangle and no open edge. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… no free edge between Review of the print check, three faults in what counts as a small overhang: A line through a point of a ceiling took the nearest anchor ahead and behind without asking what lay between. The roof of a blind T-slot (9 mm between the walls, a 3 mm slot down the middle) read as a 9 mm bridge: 0 mm2, where main had 189 mm2. A direction is a bridge only when the nearest rim of the patch on both sides is an anchor; the ring under a counterbore with a through hole is reported too (65 mm2). Any edge whose neighbour goes on downwards was an anchor. A shelf 8 mm out of a wall with a drip edge at its tip read ready, the lip counting as a second wall. An anchor now needs its material to come down, never up, to the bed or to a face that looks up; the shelf is reported with 240 mm2. The assistant was told that lying holes up to about 10 mm are bridged while the check warned from 8 mm. Both take LYING_HOLE = 8 mm now (MakerVerse, the one source of the audit's scale with a size): the roof of a lying hole up to that stays out. The cases stand on closed bodies (a grid of blocks, prisms with clipped ears). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… without a word Review of the print check: A body whose model carries no count of open edges fell out of both the open and the closed bodies: 10 x 10 x 300 mm read ready. It is checked as closed. A profile without a layer height switched the floor check off (a 0.3 mm plate read ready). It is checked as 0.2 mm. "(four layers)" was wrong under a 0.2 mm layer, where the 0.8 mm minimum is more (6.7 layers at 0.12 mm). The finding carries the number of layers and the texts name it. Fifty pins made fifty contact findings, of which the report kept 40 and the note 12. The bodies with little on the bed are one finding with their count; the report says how many findings it leaves out, and the note how many findings and bodies. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ll that made it Audit R1: reading the check after the last change was only asked of the assistant, so a turn could end on a blocked part it never saw checked. The work log now hands every answer of a call that moved the design's revision to the check: it waits up to 5 s for the mirror and the check of that revision and puts the findings behind the answer, or says that the check is still running. All three routes share this tool chain. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… did not Audit R3: "ready to print, nothing found" and "the print check still judges what you build" promised more than the check covers. The note now lists what was checked and what was not (strength and layer direction, chosen fits and hole allowances, fine details, bed-edge chamfers), and the standard topics say that the check does not test their rules. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…s cuts need Audit R54: the blocks named what the surrounding plastic must be only in their answer and built without measuring. A keyhole and a magnet pocket are now measured on trial before anything is cut (the pocket with what must stay round and under it, laid in as a probe body), the seat of an insert is checked by the volume it takes, and a block that leaves the body in pieces is taken back, each with the measured value. What is still not measured (wood screw, the wall round an insert) says so in the answer. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…sees what a change saved Reported 2026-11-12: the user had to ask for print time and material to be optimised every time. A fifth design rule group (on by default, switched off in the settings or with partbuddy_design_rules) tells the assistant what the shape decides of both, with the wall and layer sizes of the printer in use, and what stays the slicer's. The print check now carries per body the solid volume, the height and layers as it prints, the area on the bed and the area that leans past the overhang limit, so the numbers come with every check. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ules # Conflicts: # apps/desktop/src/main/agents/blockTool.ts # apps/desktop/test/blockTool.test.ts
…when the design changed Review of PR 357. With two bodies or a joint the mesh check now goes ahead of the clearance and joint measures and is told as preliminary, so no step waits for SolidCraft's measuring (waits set from timings at the engine). A call counts as a change by the undo history, not by the revision that trials and saves move too. The same verdict is told in full once per turn and then in one line. The drawing tools are changing tools. A body that cannot be measured no longer holds the report back, and the measures say what solid volume overstates and carry a shell estimate. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Review of PR 357: half to three quarters of the wall is the rule of injection moulding against sink marks; on a 0.9 mm wall it gave a rib of one line, under the thinnest wall the print check accepts. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… curved faces pass Review of PR 357. The plastic a block needs is measured from under the face on, so a curved or sloping face is not read as a gap. The seat of an insert and a countersunk hole are given no direction: it is read from what the cut took out of the body, and the wall round and the plastic under them are measured after the cut, which is taken back when they are missing (a plate just as thick as the seat is deep, a pilot hole through the part). The threshold stands on shares measured at SolidCraft 0.40.1. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…part lies, in one pass Second review of the print check. What counts as a small overhang is reworked: The bridge was tried in twelve fixed directions. An 8 mm tunnel turned by 1 degree was reported whole (240 mm2), a lying hole of 8 mm was free at 0 and 90 degrees only, and a roof meshed as a grid was reported at 15 degrees. The directions now come from the anchors of the patch, and one is chosen for the patch as a slicer lays a bridge. Points just inside the corners are tested, not points on the rim. A hole in a bridge reported the whole ceiling (231 mm2 of 30 mm, 791 mm2 of 100 mm). Only what does not hold is reported, with the arm back to its anchor: the two stubs beside the hole, 15 mm2. By the same rule the roof of the blind T-slot reports its two arms (162 mm2, not the 9 mm bridge behind the slot) and the ring under a counterbore its two stubs (20 mm2). Whether material stands was searched anew from every rim: 14 s on 24000 triangles of a stepped funnel, 56 s on 48000. One flood from the bed finds it, upwards only and across overhang no further than a small one reaches; 100000 triangles take under a second. A face that looks up stands when the underside straight below it does, so a lip with a hook, a step or a bead that looks up no longer carries a shelf. A hole of 9 mm with 20 segments stayed out: the rim of its roof lies on a mesh edge. The distance now runs to where the curve passes the limit angle. A profile without an overhang angle is checked at 45 degrees. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… an angle or with many bodies Second review of the print check: the overhang finding carried the profile's angle as it came, so one that is not a number failed the report's schema; it carries the limit that was checked. The count of bodies with little on the bed was capped at 500 in the schema, where a larger design would have lost the whole report. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… hole along its own way Second review of PR 357. SolidCraft caps the depth of its undo history at 200, so a block is undone step by step until bodies, volumes and feature count are as before, never further than it sent, and says so when that fails. Each hole of a block is cut and measured by itself, so holes that take unequal amounts no longer share one skewed axis. The plastic is measured from 0.5 mm under the face on, a seat must also cut its whole cylinder (its rim at a chamfer does not), the wood screw's stock is the screw's diameter round its hole, and the answers say what was measured. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…s on a preliminary note Second review of PR 357. At the cap of SolidCraft's undo history a moved revision counts as a change, or no step past it got its check. A preliminary check says how to get the full one in its short form too, and from partbuddy_view that the rest is coming. A clearance or joint measure that failed is named as not measured. Whether a check is unchanged is read off all its findings, not the lines shown. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…rned and mirrored 178 cases, each in nine turns on the bed and mirrored. The 27 the check still gets wrong run as expected failures (ten overhangs that pass as printable: low stairs, a drip edge with a cheek; seventeen printable shapes that are reported: channels that turn, tunnels with cut or rounded corners, lying nut pockets). The check itself is unchanged. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…wth, not in seconds The thread took 3.9 s against a limit of 3 s on a busy runner. Each mesh is now timed against one a quarter of its size, the fastest of three runs each: one pass takes 4 times as long (measured 4.2 to 7.3 under load), searching anew from every rim 16 times; the limit is 12. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
ManuelStaggl
added a commit
that referenced
this pull request
Nov 12, 2026
…not be forged by a description (#356) Fixes the severity-A findings of the full audit (2026-11-12) on telling bought standard parts from printed bodies, and the gaps an independent review found in the first version of this change. Everything is measured with the audit harness (`tools/audit`, cases `normteile-policy-*`) against SolidCraft 0.40.1, headless. ## How a standard part is told now One recognition (`boughtParts` in `mirror.ts`) stands behind print check, plate, size list, export and the list of parts to buy. A body is a standard part only when 1. its component's description is one `parts.insert` writes: a family's name, one of its sizes and a length, all read from `parts.library` (no list of names in PartBuddy); 2. the component holds that one body and nothing else (all 13 families build exactly one); 3. the body has the shape SolidCraft builds for that description: `solidcraft-cli run` builds the part alone, and both are measured with `inspect.measure`: volume and area, the counts of faces, edges and vertices, and of each face its area, the length of its outline and its radius (all within 1e-6). Whatever cannot be read, measured or built counts as printed and is logged with the body and the reason: a screw in the print check shows, a printed part missing from it does not. The mirror does not wait for a part being built; export, slicer, size list and the parts to buy do. ## S-01 / G2 / N-normteile-policy-1: one of 13 library families was printed and exported | | before | after | |---|---|---| | families recognised (one part each) | 12 / 13 (`countersunk_socket_screw` missing) | 13 / 13 | | every size of every family, plus odd lengths, a third turned to [1, 2, 3] | not measured | 185 / 185 | | `partbuddy_mechanism {kind: screw, head: countersunk}`: `structure.standardParts` | `[]` | `[Body1, Body2]` | | bodies exported | Block, Plate, Body1, Body2 | Block, Plate | | STL of the exported bodies | 6084 triangles | 1364 (Block and Plate alone: 1364) | | control `head: cap` | 2 standard parts, 1456 = 1456 | unchanged | The tool's sentence "the screws are in the design as standard parts" is true for both heads. `mechanismTool.ts` and `mechanisms.ts` are untouched. ## H-01: the assistant could turn a printed body into a "standard part" `execute component.metadata.set {component, description: "Hex nut M3"}` took a 0.3 mm lid out of print check, plate and export. The policy refuses `component.metadata.set` with a description (`description`, `values.description`, as a batch step). Since review point 2 the forged description would no longer work either: the lid is not shaped like the nut. | | before | after | |---|---|---| | `decide()` | allow | deny | | `structure.standardParts` after the attempt | `[Deckel]` | `[]` | | print check | ready, "nothing found" | block, "walls down to 0.3 mm" | ## Review **1. The policy compared command ids exactly; SolidCraft runs them in any case.** Measured over 50 spellings of tool, command, argument and parameter names: SolidCraft takes a command id in any case (`Component.Metadata.Set`, `File.Export`), a batch step's command as `id`, and a batch's steps from a script file (`path`); tool names, argument names and parameter keys only as written (`Execute`, `Params`, `Path`, `Key` are unknown or ignored). Every rule now sees the lower-case, trimmed id, normalised in one place for execute, batch steps and nested steps; a step's `id` is decided like its `command`; `batch` with a `path` is refused. `decide()` for `Component.Metadata.Set`, `Document.Metadata.Set` with PartBuddy's key, `Edit.Checkpoint`, `Doc.recovery_list`, `Diagnostics.Crash_Reports`, a step `{id: ...}` and `batch {path}`: allow -> deny. Test: `agent.test.ts`, "decides a command id in any spelling SolidCraft runs". **2. Bodies in a library component counted unchecked.** A box, a 0.3 mm lid (print check `block`) and an M3 nut; the steps sent past the policy: | steps | before | after | |---|---|---| | `component.move_bodies` of the lid into the nut | ready, lid a standard part | block, lid printed (2 bodies: stage 1) | | `component.copy_bodies` into the nut | reproduced by the review | block (stage 1) | | `component.activate` the nut + `solid.box` | reproduced by the review | block (stage 1) | | `solid.combine` join of a plate onto the nut | reproduced by the review | block, nut body printed: 310.17 mm³ and 24 faces against 45.91 mm³ and 9 (stage 2) | | `solid.box {operation: join}` onto the nut | reproduced by the review | block (stage 2) | | `solid.remove` the nut, lid moved in | not measured | block (stage 2) | | description "Hex nut M3" on the lid's component | ready (H-01) | block (stage 2) | | nut described as "Hex nut M4" | not measured | nut printed (stage 2) | | "Plain washer M3x8" on the lid, beside the nut | not measured | lid printed, nut still a standard part | Stage 2 comes from a reference, not from library dimensions: a world bounding box changes with the part's direction, volume and area do not (turned, hidden, resized: at most 6e-11 off the reference). What the reference costs and where it is kept: see the second review, point 1. Tests: `structure.test.ts` ("counts every body of a library component as printed…", "takes a body for the part only when it has the shape…"), `partReference.test.ts`, `mirrorRevision.test.ts`. **3. Body names kept in the design's metadata.** The key is neither read nor written any more; parts put in from the menu are library components like any other. Measured: key set to `["SHCS M3x12"]`, nut renamed, lid renamed to `SHCS M3x12`: the lid stays printed, the renamed nut stays a standard part. The policy still refuses writes to the key by its prefix. **4. Parts to buy.** No longer `assembly.bom`: the list is the standard parts themselves, counted by their placements through subassemblies, with the part number SolidCraft gives the reference. A nut, a washer in a subassembly placed twice, a printed lid: with the lid given `part_number: "ISO 4032 M3"` (BOM: one row, components [2, 5], count 2), the nut set to `bom_type: reference` (BOM: no nut), the nut's part number emptied: the list is 1 x Hex nut M3, 2 x Plain washer M3 in every case. Test: `structure.test.ts`, "lists the parts to buy from the standard parts themselves". **5. A failed `parts.library` switched the recognition off silently.** A failed batch, a rejected request, no results and an empty library each write a line to the log; the library is read once per engine connection and again after a failure. Test: `mirrorRevision.test.ts`, "says so in the log when parts.library fails or is empty". **6. A fallback to the former twelve names is left out.** A family SolidCraft renames or a size it drops cannot be built by the installed SolidCraft, so there is nothing to hold the body against; a fallback by name alone would be the gap of point 2 again. Such a part counts as printed and is logged. ## Second review **1. The reference blocked the mirror, and a failed run was tried again with every mirror.** | | before | after | |---|---|---| | what the mirror waits for a new part | 0.37 s (1 part), 4.2 s (30), 43 s (all sizes), with every start | 4 to 14 ms: it asks and goes on | | build of the references | in the mirror | in the background: 0.7 s (1 part), 11 s (30), 98 to 112 s (all 185 sizes), once per SolidCraft program; then the design is mirrored once more | | after a restart of the app | built again | 0.1 s, read from `part-references.json` in the cache folder (kept under the program's version, size and date) | | a part SolidCraft refuses (`Socket head cap screw M3x5000`) | one failed process with every mirror | one run of 0.2 s, then remembered with its reason, over restarts too | | a run that gives no answer | up to (1 + N) x 2 min with every mirror | ends after 20 s + 2 s a part (the slowest single part builds in 0.5 s); its parts are not tried again for 5 min | Until a part's reference is there its body counts as printed, so for that moment the print check sees the screw. What leaves PartBuddy does not: export, send to slicer, the size list and the parts to buy read the standard parts anew and wait for their references. The build takes two runs now (how many faces a part has is known only once it is built), which is why it is slower than the one the review timed. **2. The same reference was built twice by the mirror and the list of parts to buy.** A description being built is not started again; whoever waits, waits for the run that is under way. **3. Five numbers did not fix the shape.** A ring of R 4.0251, r 3.3699, 1 mm high has the volume, area and counts of the M3 washer to 2.6e-11. The shape now also holds each face's area, outline and radius. `model.faces` and `model.edges` were measured and are not used: they give values of the display mesh, up to 2 % apart between the same part straight and turned; `inspect.measure` of a face is exact. Measured: the ring, described as "Plain washer M3", stays printed; all 185 sizes stay standard parts, a third of them turned. **4. `cliReferences` was untested.** `partReference.test.ts`, with the program's runs injected: a reference is kept and no process runs for it again; a refused part is remembered, also over a restart; a part that cannot be built lets the others stand; a run without an answer is tried again only after a while; the script file is removed; a background build asks for exactly one more mirror (`mirrorRevision.test.ts`: "mirrors the delivered revision again, once however often it is asked"); two askers, one build; the ring against the washer. **5. Older designs.** No saved design with standard parts lies in the repository (`apps/desktop/e2e`, `tools/fixtures`, `tools/templates`, `site`: none). Measured instead on the two designs the audit saved on 2026-11-12 (`families.sc`, one part of each family; `screw-cap.sc`, a screw joint), opened again by `solidcraft-cli run --in`: 13 of 13 and 2 of 2 parts are told after the rebuild. Which SolidCraft wrote them (0.40.0-rc.4 or 0.40.1) the files do not say. ## Third review - **a. A crash was kept as "SolidCraft does not build this part".** Only SolidCraft's own answer is kept in the file: the `parts.insert` step failing with its reason, or another part built for the words. A crash exit code, an answer that is no JSON, a script that cannot be written or removed are remembered until PartBuddy ends (one try per start, and one more by an export), with a line in the log. Test: `partReference.test.ts`, "does not take a crash, or an answer that does not read, for SolidCraft refusing the part". - **b. Nobody mirrored again after a run without an answer, and an export could go out with the screw.** A timer builds the parts once the five minutes are over; every background build ends in one mirror. Export, send to slicer, size list and the parts to buy try a failed reference once more and, when a body still cannot be checked, write nothing and fail with the reason, which the export's existing error notice shows ("standard parts could not be checked, so nothing was written (…). Try again in a moment."). Tests: "tries a program that gave no answer again after a while by itself…", `supervisor.test.ts` "exports no standard part, waits for its reference, and says so instead of exporting a body it could not check". - **c. The print check saw the screw while its reference was built.** Such bodies are in `structure.pendingParts` and `usePrintCheck.ts` leaves them out; after the build they are standard parts or printed bodies again. Marking the report as provisional for that moment is left to the merge with #357 (its `measuring` field). Test: `mirrorRevision.test.ts`, "does not wait for a part SolidCraft is still building…". - **d. The kept file was taken on trust.** Its key carries a format number; an entry is used only as a complete reference (every field there, every number finite, as many face areas as faces), else dropped and built again. Test: "builds anew what a kept file holds in another form, and takes no entry on trust". - **e. `refresh()` during a failing mirror was lost.** That mirror is tried once more. Test: "does not lose a reference built while a mirror fails, and mirrors nothing again after a reset". - **f. More than 200 descriptions.** `ready` goes on until nothing is being built (at most 20 runs). Test: "waits for every part when there are more than one run takes". - **g. `screenshot {path}` overwrote any file in the allowed folders** (own commit). Every tool of SolidCraft the policy lets through is refused a file argument (`path`, `file`, `reference`, `output`); kept are `compare_picture` with the photo PartBuddy saved for the user (the photo note tells the assistant to use it) and `skill_reference` with a document's plain name. Checked against `tools/list` of 0.40.1: `screenshot` (path, wrote 442 140 bytes over a JSON file), `compare_picture` (reference, reads a PNG or JPEG), `skill_reference` (file, refuses paths itself) and `batch` (path, refused since the first review) take a file; the other tools answer "unknown argument". Test: `agent.test.ts`, "refuses a file argument on every tool of SolidCraft it lets through, but for a photo the user sent". ## Still open - While a part's reference is built (0.7 s once per part and SolidCraft program; 0.1 s after each start of the app) its body is left out of the print check, and the report does not yet say it is provisional (see c). - An export fails with a notice when a standard part cannot be checked (SolidCraft's `solidcraft-cli` missing or not answering); it does not offer to export anyway. - A shape is as strong as these measures: a body that meets volume, area, counts and every face's area, outline and radius of a library part within 1e-6 would pass. For the washer these fix the ring; no other body was tried. - A library component with two bodies counts as printed altogether, the library's body too (marked `shortcut:` in `mirror.ts`). - A real part whose description is changed in SolidCraft itself counts as printed. - N-normteile-policy-4 and -5 (B: `screenshot {path}`, `compare_picture {reference}`, `document.metadata.set {properties}`) and S-30 (C) are not part of this change. ## Not checked - The contract tests (`e2e/solidcraft.contract.spec.ts`) and the app itself were not started. Two of them depend on the reference from `solidcraft-cli` inside the app ("standard part" on a seated `SHCS M3x12`, two standard parts after a screw joint); the same flows pass in the harness. One expectation changed: the screw joint test waits up to 30 s for the two "standard part" labels (they follow a mirror after the bodies show), as the other test already does. - Designs written by SolidCraft 0.30.1 or a 0.40.0-rc, with parts put in from the menu: none is at hand and no other SolidCraft is installed here. If such a part's description or shape differs, its body counts as printed and the log names it: `body "<name>" is described as "<description>" but is not that part (…)` or `…, which SolidCraft did not build to hold it against (<reason>)`. - SolidCraft's window as the engine (not headless), and a part placed several times by one `parts.insert`. ## Last review (commits `df8c844`, `ebef1d6`) Six points on `46e330a` and `c38f5b6`. Where this section and "Still open" above differ, this section holds. 1. **`pendingParts` stayed for good when the mirror after the build failed.** The body was left out of the print check until a mirror that never came. The scheduler keeps the last revision; when a mirror fails and that revision has bodies waiting, it goes out again without `pendingParts`, so the body is checked as a printed one. The owed mirror is tried three times, 5 s apart, and a mirror that works ends the debt. Test: `mirrorRevision.test.ts`, "hands a body that waited for its part back to the print check when the mirror after fails, and tries that mirror a few times only". 2. **Without `solidcraft-cli` export, slicer and sharing package wrote nothing, for good.** A missing program is its own state (`unavailable`), not a failed run: such bodies count as printed, go along, and the result's notice names them ("Included as printed parts, because PartBuddy could not check here whether they are bought standard parts …", German and English). Nothing is listed to buy. A run that fails for now still writes nothing. Tests: `supervisor.test.ts` (the export test, last block: `{ unverified: ['Nut'] }`, the nut exported, no hardware), `partReference.test.ts` "says that no part can be built here when solidcraft-cli is missing, without a run or a timer". 3. **A failing `parts.library` locked the export of a design without any standard part.** Without the library no body can be taken for a part, so nothing is left unchecked; a rejected request counts like a failed batch. Test: `mirrorRevision.test.ts`, "leaves nothing unchecked when parts.library fails, so an export is not held up by it". 4. **The photo exception was a pattern of the path.** The policy compares the resolved path with the files PartBuddy kept and named to the assistant in this run; UNC, device and relative paths are refused before that. Refused in the test: another project's photo folder, a photo not kept, `//server/share`, `\\server\share`, `\\.\`, `\\?\`, relative paths, an 8.3 name, `..`, another file in the folder. Test: `agent.test.ts`, "takes for the user's photo only the file PartBuddy kept, not a path that looks like one". 5. **The file lock hung on four argument names.** Each tool let through has the list of arguments `tools/list` of SolidCraft 0.40.1 gives for it (without `screenshot`'s and `batch`'s `path`); any other argument is refused, also as a batch step. For engine commands `file` (`sketch.replace`) is refused like `path`, and `inspect.compare_picture` reads only a kept photo. Test: `agent.test.ts`, "lets a tool of SolidCraft through only with the arguments it is known to take, and with no file but a photo the user sent". 6. **Timer chains without an end.** A part whose run gives no answer gets three runs, by one timer for all parts that wait instead of one per run; the timer is called off with the design or the engine (`forget`). The mirror's retries of point 1 end after three. An export still gives a part one more run. Tests: `partReference.test.ts` "gives up a program that never answers after three runs, and waits for nothing once the design is gone", and the scheduler test of point 1. Left open by this round: - With a failing `parts.library` a real screw in the design is exported as a printed body with a line in the log only; the user gets no notice (unlike point 2, where the bodies are known by name). - After the three retries of point 1 the body stays checked as a printed one until the next change of the design mirrors it again. Checked in this round: eslint on the changed files, `typecheck`, and vitest for `mirrorRevision`, `partReference`, `supervisor`, `agent`, `photo`, `structure`, `i18nKeys` (all pass). Not run: the whole unit suite, E2E, the contract tests against a real SolidCraft, the app itself; the notice of point 2 was not looked at in the window. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com> Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
… click Two contract runs on unrelated branches failed here with every click missing the text although the clicks arrived within milliseconds. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ules # Conflicts: # apps/desktop/src/main/print/printCheckNote.ts # apps/desktop/src/main/print/printProfiles.ts # apps/desktop/src/renderer/src/print/report.ts
…ssistant-check-rules
ManuelStaggl
force-pushed
the
fix/assistant-check-rules
branch
from
November 12, 2026 16:41
ae1fe37 to
89b9847
Compare
…uilding Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ole answer
Since the tool's answer waits for the print check of its change, the part
shows before the assistant's turn ends; a user action sent right then was
refused ("the assistant is working on the design"). The first-build sends
now wait for the Stop button to go. The wall plate and the clipped cover
read the answer as a whole: the check behind each block ends in a list of
measures, which splits the answer into several elements.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ssistant-check-rules
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.
A-findings of the full audit (2026-11-12) on the rules for the assistant round the print check, on top of #353, plus the report of 2026-11-12 that the assistant does not optimise print time and material on its own, and the points of the independent review of this PR.
origin/mainis merged in (undo pattern of #348, block refusals of #355).R1 – the check reaches the assistant in the turn that changed the design
Before: "Read them after your last change, before you call the part done" was a request. The check reached the model with the next user message; a turn could end on status
blockit never saw.After: the work log (
workLog.ts) reads SolidCraft's history before and after every changing call. When the design changed, it hands the tool's answer towithCheckOfChange(printCheckNote.ts), which waits until the mirror has delivered that revision and the renderer has reported its check, and puts the note behind the answer. It waits outside the work log's queue.Why this and not a step at the end of the turn: Claude Code (gate proxy), ChatGPT and local models (
loop.ts) all call tools through the one chaintrack(upstream)inindex.ts. One hook there covers all three; a turn-end step would have needed three implementations and a guard against loops. A tool answer cannot loop: no change, no note.Details, after the review:
measuring: true) and the full check when SolidCraft has measured clearance and joints. The answer waits up to 1.5 s for the full check, then takes the mesh check marked as preliminary; after 3 s without any check it says so. Timings at SolidCraft 0.40.1 without a window (median of 5): mirror and mesh check 211 ms for a box with its lid, 141 ms for a hinge; the hinge's two joint sweeps 498 ms;inspect.clearance1 ms for both; the renderer adds 250 ms of quiet time. Not measured: the renderer's own check time in the app.designChanged). The revision alone does not: trials, saves and a block that was taken back move it. At the cap of SolidCraft's undo history (200; a step there leaves the depth and moves the revision) a moved revision counts, at the price of a check too many after a trial. Measured: a probe batch that is rolled back leaves revision, undo and redo as they were;body.print_facemoves revision and undo depth, so it gets its check.CheckToldgives the full note the first time in a turn and whenever status, any finding (all of them, with faces and numbers, not only the lines shown), the preliminary mark or a failed measure change; else one line plus the measures that changed. A new user message starts over.partbuddy_place_drawingandpartbuddy_move_drawing(check, queue, lock while a question is open, a line in the work log). The other PartBuddy tools (view,standard,ask,plan,assembly,design_rules) change no geometry.Texts to the assistant:
PartBuddy checks the part for printing after every change and shows the result to the user as a light; the findings come behind the answer of each tool call that changed the design, and partbuddy_view gives the latest ones.[PartBuddy: the ready-to-print check of the latest change is still running; call partbuddy_view for it before you call the part done.]partbuddy_viewitself:[PartBuddy: the ready-to-print check of the latest change is still running; it will be here in a moment.]Preliminary: clearance and joints still being measured (bodies that overlap or come closer than a push fit, joints that stop before a limit); call partbuddy_view for the full check before you call the part done.A clean preliminary check readsnothing found so far (build volume, wall and floor thickness, overhang angle, area on the bed, open bodies, failed features, bodies that touch).partbuddy_viewitself:Preliminary: clearance and joints still being measured (…); the full check will be here in a moment.NOT measured, because SolidCraft's measure failed: bodies that overlap or come closer than a push fit. Measure it yourself (inspect.clearance) before you call the part done.The clean line then leaves that part out of what it checked.[PartBuddy: ready-to-print check after this change: unchanged, nothing found; clearance and joints still being measured: call partbuddy_view for the full check before you call the part done.][PartBuddy: ready-to-print check after this change: unchanged, still not ready to print, the same 1 finding as before.]/… unchanged, nothing found.], followed byMeasures that changed:and those lines when there are any.R3 – a clean check says what it looked at
Before:
ready to print, nothing found./The print check in partbuddy_view still judges what you build.After (from the finding kinds of
printCheck.ts,withClearance,withMotion):… nothing found in what it checks (build volume, wall and floor thickness, overhang angle, area on the bed, open bodies, failed features, bodies that touch, bodies that overlap or come closer than a push fit, joints that stop before a limit).and at the endNot checked: strength and layer direction under load, the fits and hole allowances you chose, details and text too fine to print, bed-edge chamfers. Judge those yourself.Not checked: …sentence.None of this has been printed by PartBuddy yet, and the print check does not test these rules: measure what you built against them.R54 – blocks measure the plastic they need
One measure for all four: a probe cylinder is laid in as a body in a batch that is rolled back, and
inspect.interferencesays how much of it lies in the part. The cylinder starts 0.5 mm under the point, so a face that falls away that little (a curved one) is not read as a gap, and an edge or a chamfer is.solid.holenames no direction); taken back when short0.6 mmunder a seat and under a countersink are our own choice (three layers), marked so in the code. "Round its hole" for the wood screw is the reading of the block's own sentence ("between a hole and an edge"); measured from the countersink it refused a 15 mm tab.Refusals, as the engine gave them:
Not built: at [220, 30, 0] P3 has 3 mm of plastic where 6.7 mm are needed (the mean over a width of 9 mm; the far side, an edge or another cut takes the rest); a keyhole needs the part 6.7 mm thick behind its entry and its slot. Thicken the part there, move the keyhole away from edges and other cuts, or use woodScrew.(an entry over the edge names the entry:at [30, -3, 0] … 1.18 mm)… has 3 mm of plastic where 3.3 mm are needed (the mean over a width of 8.7 mm; …); a pocket needs 1.2 mm of plastic round it and 0.6 mm under it. Thicken the part there, move the pocket away from edges and other cuts, or take a thinner magnet.… has 6.52 mm of plastic where 7.3 mm are needed (the mean over a width of 8 mm; …); the seat of an M3 insert needs 2 mm of plastic round it and 0.6 mm under it. Thicken the part there, move the seat away from edges and other cuts, or use a boss.Not built: at [4, 20, 20] the seat would cut 83.2 mm³ of plastic where its whole cylinder is 84.2 mm³: its rim breaks out of the face, into an edge, a chamfer, the far side or another cut; the seat of an M3 insert needs …… has 2.49 mm of plastic where 2.65 mm are needed (the mean over a width of 12.7 mm; …); a countersunk hole for a 4 mm wood screw needs 4 mm of plastic round its 4.7 mm hole and a plate at least 2.65 mm thick. Thicken the plate, or move the hole away from edges and other cuts.Not built: it would leave 2 bodies where Strip was one (Strip (1)): the cut goes right through the part there. Give a point with more plastic round it, or a smaller size…. What was built of it could NOT be taken back: undo it before you call again(and a line in the log)Success answers end with
PartBuddy measured that plastic before building, from 0.5 mm under the face on: it is there.(keyhole, magnet),PartBuddy measured that plastic, from 0.5 mm under the face on, and that each seat cut its whole cylinder: it is there.(insertSeat),PartBuddy measured that plastic, from 0.5 mm under the face on: it is there.(woodScrew), orPartBuddy could not measure that plastic this time: check it yourself.insertSeat addsEach seat needs 2 mm of plastic round it and 0.6 mm under it.Taking a block back. SolidCraft 0.40.1 reads the depth of its undo history as 200 at most (211 features: 200; one more: still 200, revision and feature count move). A block that must not stay is therefore undone one step at a time until the design measures as before (same bodies, same volumes, same number of features and sketches, history no deeper), with at most the steps it sent plus three; then the top component is made active again.
At the engine (SolidCraft 0.40.1, audit harness):
[-0.707107, 0, -0.707107].bloeckecases (40) pass.Limits: what is missing must be more than a thousandth of the stock's volume to be seen (a seat 6 mm from the edge with the 3 mm chamfer is built: its wall is 1 to 2 mm over the top millimetre only). The uppermost 0.5 mm are not probed. Pockets of one call are measured against the body before the cuts, not against each other. The same conditions in
mechanismTool.tsare untouched (others work there).Material and print time (reported 2026-11-12)
Rule group
economy(fifth group, on by default; settings switch "Design lean" / "Sparsam konstruieren",partbuddy_design_rules { economy }). Text for a 0.4 mm nozzle and 0.2 mm layers:The report line becomes
After each design step, name what you added on your own, what you did for material and print time and the sizes you snapped in one short line, e.g. "…, shell 1.35 mm with two ribs instead of a solid block".Measures with every check (tool answers, next message,
partbuddy_view), computed in the renderer from the mirrored mesh withmeshMetricsandoverhangOf:The shell estimate: surface × two lines, never more than the solid, plus 15 % of the rest. A block 40 × 40 × 20 mm reads 32 cm³ solid and about 9.7 cm³ as a shell; a wall of four lines reads the same in both. Six bodies are listed, the rest counted; volumes under 1 cm³ are given in mm³. No mass: the material profiles carry no density. A body that cannot be measured is left out and named in the log; a report the main process refuses is sent again without measures.
packages/geometryis unchanged;laid(print face down) andsmallOverhangReachare not exported there, sobodyMeasures.tsrepeats the pose and the reach (markedshortcut:).Yardstick
Test expectations changed
test/printCheckNote.test.ts:ready to print, nothing found→ the new clean text andNot checked: ….test/standardTool.test.ts: closing sentence of a standard topic.test/blockTool.test.ts: the keyhole sends a trial batch (solid.cylinder inspect.interference … sketch.inspect) before its cuts;MAGNEThasaroundandunder; the R54 cases run on a plate that answers probes and holes as the engine does.test/designRules.test.ts: rule list and defaults carryeconomy; the report line names material and print time; ribs.e2e/app.spec.ts: the list of rule switches carrieseconomy.e2e/solidcraft.contract.spec.ts(wall plate): the answers must carryPartBuddy measured that plastic before building, from 0.5 mm under the face on: it is there.after the keyholes and the magnets andPartBuddy measured that plastic, from 0.5 mm under the face on: it is there.after the wood screws.test/checkOfChange.test.ts(both routes, no change, bounded wait, two stages, moved revision without change, a failing call that changed the design, undo, two calls at once, unreadable history, drawing tools, told once),test/bodyMeasures.test.ts.Checked
pnpm run typecheck,eslint .andvitest runinapps/desktop. Audit harness against SolidCraft 0.40.1:bloecke(40 cases), the block cases on flat, sloping and curved faces, the probe shares and the timings above. Not run locally: the app, Playwright E2E and contract tests (CI runs them), a real chat.Contract run 43261598187: eight red tests (fixed in e0f821e, tests only)
the assistant is working on the design. Cause: the tool's answer now waits for the print check of its change (withCheckOfChange), so the part shows before the assistant's turn ends, and the tests called the user's action as soon as the mesh showed. Log of the screw test: session started 16:50:13.770,design:apply-action failed16:50:14.710. The tests now wait for the Stop button to go (turnEnded) after each first build; no fixed waits.it is there.; the print check behind each block ends in a list of measures, which the chat renders as a list, so the answer is several elements and agetByTextacross two blocks no longer matches. The tests read the whole answer (assistant-text) instead.withCheckOfChangereturns after at mostCHECK_WAIT_MS(3 s) per changing call, also when the check never comes or the design changed (test/checkOfChange.test.ts: bounded wait, check of another design). Measured locally on SolidCraft 0.40.1 with the fake assistant: the turn ended 150 to 350 ms after the part showed in seven builds, 1.7 s in the first one after a cold start. Before this PR the answer did not wait at all. No product change.contract.ymlin CI.🤖 Generated with Claude Code