← Commit history

Honor reviewed explicit molecule positions while preserving interface locks

John Lauer ·d33b2fbe55 · ·parent 3493a07
2 files changed +65−5
crates/adom-aiflow/src/checks.rs+59−5
@@ -279,14 +279,18 @@ pub fn molecule_check(b: &Board, spec: &Value, before: Option<&Board>) -> Findin             }         }     }-    // fixed references: where the spec says, and where the run's first board had them+    // Explicit MP1-relative positions define the reviewed interface; other positions and all+    // rotations retain the starting-board baseline. MP1 itself remains the absolute anchor.     if let Some(fixed) = m["fixed"].as_object() {         if let Some(o) = origin {             for (r, pos) in fixed {                 let (x, y) = (pos.get(0).and_then(|v| v.as_f64()).or(pos["x"].as_f64()), pos.get(1).and_then(|v| v.as_f64()).or(pos["y"].as_f64()));-                let (Some(x), Some(y)) = (x, y) else { continue };+                let (Some(x), Some(y)) = (x, y) else {+                    f.error(format!("{r}: spec.molecule.fixed needs numeric x and y"), "Repair the interface contract; malformed positions cannot waive the starting-board lock.");+                    continue;+                };                 match b.footprints.get(r) {-                    Some(fp) if (fp.x - o.0 - x).abs() < 0.01 && (o.1 - fp.y - y).abs() < 0.01 => {}+                    Some(fp) if (fp.x - o.0 - x).abs() <= 0.005 && (o.1 - fp.y - y).abs() <= 0.005 => {}                     Some(fp) => f.error(format!("{r} is at ({:.2}, {:.2}) mm from MP1; spec.molecule.fixed puts it at ({x}, {y})", fp.x - o.0, o.1 - fp.y), "The interface is fixed: move it back (the scaffold and the Control Panel wiring depend on it), or change the spec on purpose."),                     None => f.error(format!("{r} (spec.molecule.fixed) is not on the board"), ""),                 }@@ -296,15 +300,25 @@ pub fn molecule_check(b: &Board, spec: &Value, before: Option<&Board>) -> Findin     if let Some(b0) = before {         let mut refs: Vec<String> = spec["fixedRefs"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default();         for r in pins.keys() { if !refs.contains(r) { refs.push(r.clone()); } }+        if let Some(fixed) = m["fixed"].as_object() {+            for r in fixed.keys() { if !refs.contains(r) { refs.push(r.clone()); } }+        }         let mut moved = Vec::new();         for r in &refs {             if let (Some(a), Some(c)) = (b0.footprints.get(r), b.footprints.get(r)) {-                if (a.x - c.x).abs() > 0.005 || (a.y - c.y).abs() > 0.005 || (a.rot - c.rot).abs() > 0.01 {+                // The explicit position was checked above. It may intentionally revise a+                // defective starting interface, but cannot move MP1 or change rotation.+                let explicit_position = r != "MP1" && m["fixed"].get(r).is_some();+                let moved_position = !explicit_position+                    && ((a.x - c.x).abs() > 0.005 || (a.y - c.y).abs() > 0.005);+                if moved_position || (a.rot - c.rot).abs() > 0.01 {                     moved.push(format!("{r} ({:.2}, {:.2}, {:.0} deg) -> ({:.2}, {:.2}, {:.0} deg)", a.x, a.y, a.rot, c.x, c.y, c.rot));                 }+            } else {+                f.error(format!("fixed reference {r} missing from current or starting board"), "Restore the reference and its reviewed interface binding; absence is not an unchanged position.");             }         }-        if moved.is_empty() { f.ok(format!("{} fixed references where the run started", refs.len())); }+        if moved.is_empty() { f.ok(format!("{} fixed references match explicit positions or starting-board locks; anchor and rotations retained", refs.len())); }         else { f.error(format!("fixed references moved since the run started: {}", moved.join("; ")), "Put them back where the start board had them (spec.fixedRefs); placement must not move the molecule interface."); }     }     f@@ -582,6 +596,46 @@ mod tests {         assert!(f.errors.iter().any(|e| e.contains("moved since the run started")), "{:?}", f.lines);     } +    #[test]+    fn explicit_interface_revision_retains_all_other_locks() {+        let spec = json!({"molecule": {"fixed": {"J1": [0, 10]}}, "fixedRefs": ["J1"]});+        let moved = BOARD.replace("(at 100 88) (property \"Reference\" \"J1\")", "(at 100 90) (property \"Reference\" \"J1\")");+        let f = molecule_check(&board(&moved), &spec, Some(&board(BOARD)));+        assert!(f.errors.is_empty(), "{:?}", f.lines);+        // The revised contract is enforced, not merely a movement waiver.+        let f = molecule_check(&board(BOARD), &spec, Some(&board(BOARD)));+        assert!(f.errors.iter().any(|e| e.contains("spec.molecule.fixed")));+        let rotated = moved.replace("(at 100 90)", "(at 100 90 90)");+        let f = molecule_check(&board(&rotated), &spec, Some(&board(BOARD)));+        assert!(f.errors.iter().any(|e| e.contains("moved since the run started")));+        let explicit_only = json!({"molecule": {"fixed": {"J1": [0, 10]}}});+        assert!(molecule_check(&board(&rotated), &explicit_only, Some(&board(BOARD))).errors.iter().any(|e| e.contains("moved since the run started")));+        let bad = json!({"molecule": {"fixed": {"J1": [0]}}, "fixedRefs": ["J1"]});+        assert!(!molecule_check(&board(&moved), &bad, Some(&board(BOARD))).errors.is_empty());+        let barely_wrong = moved.replace("(at 100 90)", "(at 100.007 90)");+        assert!(molecule_check(&board(&barely_wrong), &spec, Some(&board(BOARD))).errors.iter().any(|e| e.contains("spec.molecule.fixed")));+        let missing_lock = json!({"molecule": {}, "fixedRefs": ["J404"]});+        assert!(molecule_check(&board(BOARD), &missing_lock, Some(&board(BOARD))).errors.iter().any(|e| e.contains("fixed reference J404 missing")));+        let missing = json!({"molecule": {"fixed": {"J404": [0, 10]}}});+        assert!(!molecule_check(&board(BOARD), &missing, Some(&board(BOARD))).errors.is_empty());+    }++    #[test]+    fn explicit_positions_do_not_waive_anchor_or_scaffold_pitch() {+        let bad = BOARD.replace("124", "160").replace("84", "32")+            .replace("126 102", "162 102").replace("98 82", "98 30");+        let fixed = bad.replace("160", "164").replace("32", "28")+            .replace("162 102", "166 102").replace("98 30", "98 26");+        let spec = json!({"molecule": {"fixed": {"MP1": [0,0], "MP2": [64,0], "MP3": [0,72], "MP4": [64,72]}}});+        let f = molecule_check(&board(&fixed), &spec, Some(&board(&bad)));+        assert!(f.errors.is_empty(), "{:?}", f.lines);+        let invalid = json!({"molecule": {"fixed": {"MP2": [60,0], "MP3": [0,68], "MP4": [60,68]}}});+        assert!(molecule_check(&board(&bad), &invalid, Some(&board(&bad))).errors.iter().any(|e| e.contains("LrgMed 8 mm")));+        let anchor = fixed.replace("(at 100 100)", "(at 102 100)");+        let f = molecule_check(&board(&anchor), &spec, Some(&board(&bad)));+        assert!(f.errors.iter().any(|e| e.contains("moved since the run started")));+    }+     #[test]     fn rules_profiles() {         let dir = std::env::temp_dir().join(format!("aiflow-rules-{}", std::process::id()));
skills/aiflow-molecule/SKILL.md+6
@@ -121,3 +121,9 @@ Whenever a component page is created or its model changes (here, or through mole The LrgMed medium-contact centers follow FL + 4 + 8 k mm on each axis, not a 4 mm lattice. Read the selected scaffold page circuit.json (medium plated-hole drill 0.78 mm) and match every molecule corner pin to an actual contact. A 60 x 68 mm pin rectangle fails; 64 x 72 mm is grid-compatible but still needs contact-range and assembly checks. The molecule signal-contact/wiring grid remains 2 mm and is independent of the supporting scaffold pin grid.  Read base-scaffold-system and run adom-aiflow-probing stack for the real fixture. The medium-short pin shoulder seats on the contact lip: molecule top is scaffold top + 0.083 + 2.8 = 2.883 mm. Read GLB board surfaces and preserve explicit coordinate transforms when combining assets; never seat boards flush or align by pin tips. XY conformance alone is not Z seating or manufacturing qualification. If fit requires moving pins or changing the outline, isolate the candidate, revalidate copper and native DRC, and recheck attached carriers and probe access before adoption.++## Intentional interface corrections during a run++An explicit `spec.molecule.fixed` entry is the required position in millimetres from MP1, Y up. It takes precedence over that reference's original XY position, so a reviewed correction to an initially invalid scaffold span can be represented without rewriting history. MP1 remains at its original absolute position; every fixed reference retains its original rotation. References without explicit entries remain locked to their original positions. Malformed entries and missing references fail. Scaffold pitch, rectangle, pad/drill and edge-margin checks still apply.++Do not populate this map merely to silence a moved-pin error. First retain a before/after interface review, explain why the old layout fails the intended scaffold, verify the proposed coordinates against the actual scaffold and control-panel wiring, and record the evidence path and hashes with the spec change. Keep the original board history. Rerun native DRC, connectivity, model/assembly alignment, copper/current/thermal checks and affected probing tests; coordinate conformance is not physical assembly qualification.