# 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).