feat(mcp): assembly joint mutates in shared cad_submit map - #69
Conversation
jackControls
left a comment
There was a problem hiding this comment.
Thanks for stacking the joint mutates into the shared map. The map entries, disclosure tags, and host-neutral create/update round-trip look sound, but I found one PR69-specific blocker and three blockers carried by the stacked base:
-
P1 — attached joint mutations are marked saved.
assembly_create_jointandassembly_update_jointreturn a joint DTO rather than a solid update.applyInboxNow()therefore falls through toloadDocument(), which unconditionally setsdirty: false. The joint appears in the live project, but closing the tab/app can skip the unsaved-changes warning and lose it. Please use a mutation-aware assembly refresh that preserves dirty state and add an attachedcad_submitintegration test covering joint visibility anddirty: trueafter both create and update. -
P1 — inherited from PR60: cross-tab publish identity is lost between reservation and write. The reservation returns session/project identity, but the write payload only carries generation. Native write handling then targets whichever project is active at write time. A tab switch between export and write can publish one tab's model into another tab's session. Please carry and validate the reserved session/project identity through the write.
-
P1 — inherited from PR60: concurrent
cad_submitcalls can allocate the same inbox sequence. Sequence allocation scans formax + 1, then writes separately. Two submitters can choose the same sequence and one atomic rename can replace the other while both calls report success. Please allocate with an exclusive create/lock or another atomic reservation mechanism. -
P2 — copied from PR68: advertised joint schemas reject valid serialized DTOs containing null. Optional fields including
source_surface_frame, primary limits, and advanced limits serialize as null, while their schemas accept only objects. Passing a created or queried joint back toassembly_update_jointcan therefore fail protocol-level validation. The current round-trip test callsCadServer::call_tooldirectly and bypasses input-schema validation. Please allow object-or-null for these fields and add a schema-validating round-trip test.
I recommend fixing item 1 here, then rebasing onto corrected PR60 and PR68. This PR also needs the assembly-refresh behavior from PR63 (including its stale-preview correction), or an equivalent focused refresh, so attached joint changes update the UI without resetting saved state.
Focused local checks passed: nbcad-mcp-mutate 5/5, MCP server 47/47, and desktop session bridge 14/14.
|
Addressed item 1 (local only; not merging). Did not rebase onto #60/#68 — those tips are not yet carrying the identity/seq/schema fixes. P1 — attached joint mutations marked saved
Test: Checks: |
|
Item 1: attached
Test: Also on this push (not waiting for #60 identity/seq —
Please re-review item 1. Items 2–3 stay with corrected #60. |
Rebase jackControls#63 onto jackControls#60 tip f241739. Mutate map entries and ToolSpecs already landed with jackControls#60/jackControls#62; keep unique classifier/lookup coverage so cad_submit accepts the six assembly modeling mutates while assembly_document and assembly_solution stay read-safe inspect tools.
applyInboxNow no longer falls back to loadDocument() (which cleared dirty). Assembly ops get a targeted refreshAfterInboxApply with dirty:true; other non-solid results refresh live state the same way. inboxApplying still guards re-entry; native apply already owns the engine_revision bump.
af2e670 to
06cad51
Compare
|
Rebased onto #60 Force-with-lease push: Local: |
|
Rebased onto #60 Kept unique leftover: joint MUTATES, dirty-refresh (with Local checks: rustfmt on touched; |
jackControls
left a comment
There was a problem hiding this comment.
Thanks for the updates. I rechecked the full stacked head: the publisher/inbox races are fixed, joint create/update inbox results preserve dirty state, targeted assembly refresh clears all stale previews (including jointMotionPreview), and the nullable joint schema round-trip is covered. The focused joint integration test and all platform/MCP CI jobs pass. Approved.
The merge-base changed after approval.
refreshAfterInboxApply invalidated jointMotionPreviewGeneration but left the stored preview in place. Viewport prefers jointMotionPreview.solution over assemblySolution, so an attached assembly mutate could keep rendering the old motion pose. Clear it beside jointPreviewSolution.
Stack those ToolSpecs on jackControls#60's MUTATES map so cad_submit accepts them while attached. assembly_document / assembly_solution stay read-safe.
assembly_create_joint/update return a joint DTO, so applyInboxNow no longer falls through to loadDocument() (dirty:false). Port jackControls#63 refreshAfterInboxApply with dirty:true and clear stale jointMotionPreview.
Serialized JointDefinitionDto emits null for source_surface_frame and limit objects. Advertised create/update schemas now allow object-or-null so a queried joint can pass protocol-level inputSchema validation.
Drop the extra helper; applyInboxNow uses the same scene+document guard as jackControls#63 so joint DTOs take refreshAfterInboxApply (dirty:true).
cad_submit no longer leaks into cad_script. Failed/malformed joint inbox heads are dead-lettered so later seqs can apply. UpdateJointRequestDto stays replace-all (omitted limits clear); query-then-update preserves them.
06cad51 to
26785fe
Compare
|
Rebased onto new #63 tip New tip: Real bugs / contract locks
Already sound (locked with tests)
Tests
fmt on touched. Not merging until Approved. |
Confirm replace-all (not patch): id+name-only is schema-invalid, and both omitted keys and explicit JSON nulls clear optional limits. Lock create-then- update before refresh, detach-mid-inbox (no fork), and wrong/missing occurrence dead-letter so the queue stays unblocked.
|
Second adversarial pass at No host bug. Replace-all is still the contract (not a patch). Tightened the advertised New angles locked:
#68 is not APPROVED+MERGEABLE — left it alone (parent merges). Local: Re-requesting review. |
Match native apply: a same-base leftover after the first publish must dead-letter with generation_conflict so the queue cannot wedge. Lock solution-after-submit, same-pair joints, malformed inbox JSON, and occurrence-id swap behavior.
|
Third adversarial pass at One real bug, helper now matches native. New angles locked:
Local: Re-requesting review. |
Match native apply: a head not in the shared mutate map must dead-letter before host_apply so inspect/unknown names cannot archive as applied. Lock failed joint create leaving no ghost id.
Fifth-pass: no remaining loadDocument dirty:false fallthrough on joint-adjacent inbox results. Lock mechanismPreview clear, applyInboxNow early-return on !applied, and already-applied seq as a no-op.
|
Fifth adversarial pass at No host bug. Remaining New angles locked:
No TS/vitest harness; locked with existing Rust Local: Re-requesting review. |
Sixth-pass: no applyInboxAll loadDocument path; leftover refreshAfterInboxApply stays the single jackControls#63 dirty:true contract. Lock cad_refresh vs cad_load_project_model joint parity and a part-document joint inbox typed reject.
|
Sixth adversarial pass at No host bug. New angles locked:
Local checks: rustfmt on touched; |
Leftover apply_inbox_op now dead-letters generation_conflict when heartbeat generation is missing so later seqs cannot wedge. applyInboxNow publishes in a finally after native applied:true so a leftover refresh throw cannot skip cad_refresh. Age-stale heartbeat with matching generation still applies.
|
Seventh adversarial pass at Two real holes, both locked.
Also locked (no host change): leftover vs native error class + Focused local checks: |
Eighth-pass coverage: leftover generation_conflict then seq 2, reattach-then-apply (same and other session), finally publish still reserved-identity, native switch-back apply, occurrence-id after rename, whitespace-only joint name typed reject.
|
Eighth adversarial pass at No host bug. Queue unwedge after a failed head was already the leftover+native contract (dead-letter, then next New angles locked:
Jack review comments: none new on tip Local: Re-requesting review. |
|
Ninth adversarial pass at No host bug. Leftover New angles locked:
O_EXCL double submit — already locked on #60/#63/#69 ( Jack review comments: none new on tip Local: Re-requesting review. Not merging. |
Host delete already cascades joints for the deleted body only. Lock leftover applySolidUpdate after feature-delete, pending create/update against a gone body, inverted limits, unicode names, and raw wrong-tool inbox JSON.
|
Tenth adversarial pass at No host bug. Host Body-delete vs joints
Schema edges (already defined; not invented)
Jack: no new inline comments. Latest review is the earlier dismissed approval. Not merging. Local: rustfmt on touched; |
|
valid but incorrectly shaped inbox JSON such as [] gets archived without its error reason. |
Summary
assembly_create_joint/assembly_update_jointincrates/mcp-mutatesocad_submitaccepts them while attached.assembly_document/assembly_solutionstay inspect-only (not in the map).Stack
Branched from #60 tip
25bdf45. Merge after #60 and #68.Test plan
cargo test -p nbcad-mcp-mutatecargo testinmcp-server(incl.assembly_joint_create_update_query_roundtrip, ToolSpec↔map sync, classifier)