docs(neo4j): propose CAD design schema addendum for Architect workflow

This commit is contained in:
2026-06-03 06:48:49 -04:00
parent 4fe34ac858
commit 947c5102f3

View File

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