From 947c5102f3e5abbe09eb8f54c9b92e130f30a6bf Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Wed, 3 Jun 2026 06:48:49 -0400 Subject: [PATCH] docs(neo4j): propose CAD design schema addendum for Architect workflow --- .../neo4j/proposals/cad-design-addendum.md | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 docs/tools/neo4j/proposals/cad-design-addendum.md diff --git a/docs/tools/neo4j/proposals/cad-design-addendum.md b/docs/tools/neo4j/proposals/cad-design-addendum.md new file mode 100644 index 0000000..f471d26 --- /dev/null +++ b/docs/tools/neo4j/proposals/cad-design-addendum.md @@ -0,0 +1,281 @@ +# PROPOSAL: CAD Design Schema Addendum + +> **Status: PROPOSED / EXPERIMENTAL — not yet part of the canonical schema.** +> This addendum proposes new Engineering-team node types for the FreeCAD MCP +> CAD-design workflow (Architect / Part Builder / Validator sub-team under +> Harper). It is a working draft meant to be exercised against the live graph +> before promotion. **Do not treat these shapes as final** — fields are expected +> to change once we build against them. + +--- +proposed_version: 2.4.0-draft +targets: docs/tools/neo4j/unified-schema.md, docs/tools/neo4j/engineering.md +owner_assistant: Architect (new, Engineering team) +depends_on: 2.3.0 +author: design exploration with Robert +date: 2026-05-28 +--- + +## Why this exists + +Harper frames a CAD concept (a `Prototype`), then hands it to a new **Architect** +agent that orchestrates a small CAD team (Part Builder, Validator) driving the +FreeCAD Robust MCP server on caliban. That workflow needs to persist three +things the current schema (v2.3.0, 79 nodes) has no home for: + +1. the **overall design job** and its refined spec, +2. a **per-part manifest** (intent, target dimensions, build order, status), and +3. **validation results** (issues found, never auto-fixed). + +This addendum adds `Design`, `Part`, and `ValidationResult` under the +Engineering team, owned by the Architect. Harper's existing `Prototype` node is +the upstream concept; the new `Design` realizes it. + +### Layering principle (the core design constraint) + +There are **two authoritative stores**, each owning a different layer: + +| Layer | Source of truth | Node / artifact | +|-------|-----------------|-----------------| +| **Intent** — what we mean to build, dims, status | the graph | `Design`, `Part` | +| **Geometry** — the actual solid model | the `.FCStd` file in FreeCAD | `Design.fcstd_path` | + +The graph does **not** duplicate geometry. `Part.freecad_object` is the join key +between the intent record and the real object in the FreeCAD document. The +Validator's job is precisely to **reconcile** the two and record discrepancies as +a `ValidationResult`. This is why there is no "geometry" or "shape data" field +anywhere below — that lives in FreeCAD, queried live via the MCP tools. + +--- + +## Proposed Node Types + +### Design + +The overall CAD job. One `Design` ↔ one `.FCStd` document ↔ (usually) one +upstream `Prototype`. Owned/written by the Architect. + +```cypher +(:Design { + id: String!, // "design_wall_bracket" + name: String!, + status: String!, // specifying, building, validating, complete, abandoned + prototype_id: String, // FK → Prototype.id (the Harper handoff link) + spec: String, // refined requirements: dims, tolerances, material, loads, mounting + fcstd_path: String, // ground-truth model file on caliban (e.g. /home/robert/cad/wall_bracket.FCStd) + export_paths: [String], // STEP/STL/3MF produced at completion + screenshot_refs: [String],// paths/URIs of GUI screenshots captured by the Architect + units: String, // "mm" (default), "in" — explicit to avoid silent scale bugs + notes: String, + created_at: DateTime, + updated_at: DateTime +}) +``` + +**Status lifecycle:** `specifying` → `building` → `validating` → `complete` +(or `abandoned`). The Architect advances it; Harper reads it to know the job is +done (`complete`) without parsing geometry. + +### Part + +One modelled part = an **intent record**, not geometry. Gives the Architect a +queryable manifest and a build order. Written by the Architect; `status` may be +advanced by the Part Builder as it works. + +```cypher +(:Part { + id: String!, // "part_wall_bracket_baseplate" + name: String!, + design_id: String!, // FK → Design.id + status: String!, // planned, building, built, validated, rework + freecad_object: String, // name of the object in the .FCStd — the RECONCILIATION KEY + target_dims: Map, // {length: 80, width: 40, thickness: 4} (in Design.units) + feature_of: String, // optional Part.id — for sub-features (a pocket of a baseplate) + depends_on: [String], // Part.ids that must be built first → build order + notes: String, + created_at: DateTime, + updated_at: DateTime +}) +``` + +**On `target_dims: Map`** — Neo4j map properties are flat (no nested maps, +primitive values only). That is sufficient for dimensions but is one of the +things to validate in experimentation (see Open Questions). If it proves +awkward, the fallback is parallel `dim_*` scalar fields or a JSON string. + +### ValidationResult + +The Validator writes these and **never repairs** anything. One result per +validation pass of a part (or the whole design if `part_id` is null). + +```cypher +(:ValidationResult { + id: String!, // "valresult_2026-05-28_baseplate" (date-stamped, allows history) + design_id: String!, // FK → Design.id + part_id: String, // FK → Part.id, or null for a whole-document check + valid: Boolean!, + issues: [String], // structured strings: "Baseplate: invalid shape after pocket" + freecad_state: String, // recompute/error snapshot from validate_document + checked_by: String, // "Validator" — provenance, since multiple agents write the graph + created_at: DateTime, + updated_at: DateTime +}) +``` + +Keeping a **dated id** per pass means validation history is preserved (a part can +fail, get reworked, then pass) rather than overwritten — useful for the rework +loop and for Harper's final report. + +--- + +## Proposed Relationships + +```cypher +(Prototype)-[:REALIZED_BY]->(Design) // Harper's concept → the CAD job +(Design)-[:HAS_PART]->(Part) +(Part)-[:DEPENDS_ON]->(Part) // build ordering +(Part)-[:FEATURE_OF]->(Part) // sub-feature composition (optional) +(ValidationResult)-[:VALIDATES]->(Part) +(ValidationResult)-[:VALIDATES]->(Design) // whole-document pass +(Design)-[:PRODUCED]->(Note) // completion message back to Harper's inbox +``` + +All follow the existing edge-naming style (`REALIZED_BY`, `HAS_PART` mirror the +work team's `WON_FROM`, `HAS_*` conventions). + +--- + +## Coordination: the existing Note-based inbox + +No new mechanism. Handoffs are `Note` nodes (already the schema's generic message +carrier) with addressing tags and `action_required`. The wake trigger is +out-of-band — an agent checks its inbox when invoked or resumed. + +```cypher +// Harper → Architect (hand off a job) +MERGE (n:Note {id: "note_2026-05-28_freecad_bracket_req"}) +ON CREATE SET n.created_at = datetime() +SET n.type = "idea", + n.action_required = true, + n.tags = ["for_architect", "cad_job"], + n.related_to = ["proto_wall_bracket"], + n.content = "Wall-mount bracket: holds 5kg, fits 35mm DIN rail, 2x M4 mounts...", + n.updated_at = datetime(); + +// Architect inbox query (run first thing on invoke/resume) +MATCH (n:Note) +WHERE "for_architect" IN n.tags AND n.action_required = true +RETURN n ORDER BY n.created_at; + +// Architect → Harper (job complete) — mirror, tags:["for_harper"]; link to Design +MATCH (d:Design {id: "design_wall_bracket"}) +MERGE (n:Note {id: "note_2026-05-28_bracket_done"}) +ON CREATE SET n.created_at = datetime() +SET n.type = "insight", n.action_required = true, + n.tags = ["for_harper", "cad_result"], + n.related_to = ["design_wall_bracket", "proto_wall_bracket"], + n.content = "Bracket complete. 3 parts validated. STEP+STL exported. See Design.export_paths.", + n.updated_at = datetime() +MERGE (d)-[:PRODUCED]->(n); +``` + +A note is "consumed" by setting `action_required = false` once read/handled +(keeps the inbox query clean without deleting history). + +--- + +## End-to-end example (one bracket, sequential) + +```cypher +// 1. Harper frames the concept +MERGE (p:Prototype {id: "proto_wall_bracket"}) +ON CREATE SET p.created_at = datetime() +SET p.name = "Wall bracket", p.status = "building", + p.tech_stack = ["freecad", "freecad-mcp"], p.updated_at = datetime(); + +// 2. Architect creates the Design + part manifest (after spec refinement) +MERGE (d:Design {id: "design_wall_bracket"}) +ON CREATE SET d.created_at = datetime() +SET d.name = "Wall bracket", d.status = "building", + d.prototype_id = "proto_wall_bracket", d.units = "mm", + d.fcstd_path = "/home/robert/cad/wall_bracket.FCStd", + d.spec = "Holds 5kg; 35mm rail; 2x M4 c'bore mounts; ABS", + d.updated_at = datetime(); +MATCH (p:Prototype {id: "proto_wall_bracket"}), (d:Design {id: "design_wall_bracket"}) +MERGE (p)-[:REALIZED_BY]->(d); + +MERGE (pt:Part {id: "part_wall_bracket_baseplate"}) +ON CREATE SET pt.created_at = datetime() +SET pt.name = "Baseplate", pt.design_id = "design_wall_bracket", + pt.status = "planned", pt.freecad_object = "Baseplate", + pt.target_dims = {length: 80, width: 40, thickness: 4}, + pt.updated_at = datetime(); +MATCH (d:Design {id: "design_wall_bracket"}), (pt:Part {id: "part_wall_bracket_baseplate"}) +MERGE (d)-[:HAS_PART]->(pt); + +// 3. Part Builder builds in FreeCAD (transactioned), then marks built +MATCH (pt:Part {id: "part_wall_bracket_baseplate"}) +SET pt.status = "built", pt.updated_at = datetime(); + +// 4. Validator validates, writes result, NEVER fixes +MERGE (v:ValidationResult {id: "valresult_2026-05-28_baseplate"}) +ON CREATE SET v.created_at = datetime() +SET v.design_id = "design_wall_bracket", v.part_id = "part_wall_bracket_baseplate", + v.valid = true, v.issues = [], v.checked_by = "Validator", + v.freecad_state = "recomputed, no errors", v.updated_at = datetime(); +MATCH (v:ValidationResult {id: "valresult_2026-05-28_baseplate"}), + (pt:Part {id: "part_wall_bracket_baseplate"}) +MERGE (v)-[:VALIDATES]->(pt); + +MATCH (pt:Part {id: "part_wall_bracket_baseplate"}) +SET pt.status = "validated", pt.updated_at = datetime(); + +// 5. all parts validated → Architect completes + notifies Harper (see inbox section) +MATCH (d:Design {id: "design_wall_bracket"}) +SET d.status = "complete", + d.export_paths = ["/home/robert/cad/wall_bracket.step", "/home/robert/cad/wall_bracket.stl"], + d.screenshot_refs = ["/home/robert/cad/wall_bracket_iso.png"], + d.updated_at = datetime(); +``` + +--- + +## Open questions to resolve during experimentation + +These are deliberately unsettled — the reason this is an addendum, not a schema +edit: + +1. **`target_dims` as Map vs scalars vs JSON string.** Neo4j maps are flat and + can't be indexed; if we never query *by* a dimension, a Map is fine. If we do, + reconsider. Exercise both before promoting. +2. **History granularity.** Dated `ValidationResult` ids preserve every pass. + Do we also want dated `Part` revisions, or is `status` churn + `updated_at` + enough? (Leaning: status churn is enough; FreeCAD's own undo/redo + `.FCStd` + saves are the real revision history.) +3. **Part vs sub-feature.** `FEATURE_OF` lets a pocket/fillet be its own `Part`. + Is that useful manifest granularity, or noise? May collapse to "a Part is a + top-level solid only." +4. **`.FCStd` location & ownership.** `fcstd_path` assumes a stable CAD working + dir on caliban. Decide owner/permissions (the GUI bridge runs as `robert`). +5. **Abandoned/rework cleanup.** When a Design is `abandoned`, do dependent + `Part`/`ValidationResult` nodes stay (history) or get pruned? Leaning: stay. +6. **Multiple Designs per Prototype.** `prototype_id` is scalar (1 Design per + concept). If a prototype spawns variants, this becomes a `[:REALIZED_BY]` + fan-out — already supported by the relationship; just don't over-rely on the + scalar FK. + +--- + +## Promotion checklist (when experimentation settles) + +Only after building against the live graph and resolving the open questions: + +- [ ] Fold the three node definitions into `unified-schema.md` under a new + "Architect's Domain (CAD Design)" subsection (Engineering team). +- [ ] Add the relationships to the Engineering cross-team relationships block. +- [ ] Add 3 rows to the Node Type Summary table (→ 82 nodes) and add the + Architect to the Teams & Assistants table. +- [ ] Add an "Architect's Nodes" table + handoff note to `engineering.md`. +- [ ] Bump version to 2.4.0 with a Version History line; update the assistant + count. +- [ ] Delete this proposal file (it has served its purpose).