silkscreen-audit --record: let a live 'silkscreen proof' deviation answer the label-count rule (a face meant to carry no references), patch attached

noah · ·closed by Colby Knox

Version: adom-aiflow 0.2.56+noah.1 (local build of 0.2.56); patched and tested as 0.2.56+noah.2.

What happened: a board whose owner-facing faces must carry no reference designators (a desk nameplate: Noah asked for the back to look finished, so the 158 LED and 22 capacitor references moved to F.Fab only) can never record the silkscreen step proof. silkscreen-audit --record passes the audit itself (0 coverage errors; 0 missing obstacles; 0 collisions) and then refuses: silkscreen proof not recorded: 43 visible silkscreen label(s) for 219 footprints (at least 0.5 per footprint). deviate "silkscreen proof" --why ... is recorded and finish lists it, but finish-guard keeps answering continue with invalidated evidence: silkscreen (adopt-board changed the board file; silkscreen-audit --record again), so the run cannot be completed honestly.

Change (attached diff, silkproof.rs + main.rs): silkproof::proof_with(audit, input, board, density_waiver). silkscreen-audit --record looks up a live deviation silkscreen proof bound to the run's current board and spec, and only then lets it answer the label count. Everything else still has to pass: the audit itself, inventory coverage of every footprint and via, and the geometry check against the board. The proof records labelDensityWaived with the counts, the deviation and its reason, and the command prints a Note line naming it. proof() keeps its signature (no waiver).

Tested: a new unit test a_live_deviation_waives_only_the_label_count: a board with no visible labels is refused without the deviation, recorded with it, and a failed audit stays refused even with it. The existing silkproof tests pass (7). The Adam Telle nameplate run recorded its proof with it: silkscreen step proof recorded: 219 footprints and 0 vias covered, 43 labels plus the Note, after which finish-guard cleared the silkscreen evidence.

diff --git a/crates/adom-aiflow/src/main.rs b/crates/adom-aiflow/src/main.rs
index caf048f..97a3bad 100644
--- a/crates/adom-aiflow/src/main.rs
+++ b/crates/adom-aiflow/src/main.rs
@@ -3425,7 +3425,12 @@ fn main() {
                 for k in ["powerInputs", "testPoints"] { audit[k] = labels[k].clone(); }
                 std::fs::write(out, serde_json::to_string_pretty(&audit).unwrap()).unwrap_or_else(|e| err(&format!("{}: {e}", out.display()), &[]));
                 let data = read_json(&input.display().to_string());
-                let mut proof = silkproof::proof(&audit, &data, &b).unwrap_or_else(|why| {
+                // a live `silkscreen proof` deviation on the run's current board and spec answers the label count only
+                let waiver = if board_path == r.current_board() {
+                    let (bs, ss) = run_hashes(&r);
+                    deviations::live(&r.data, &bs, &ss).into_iter().find(|d| d["item"] == "silkscreen proof")
+                } else { None };
+                let mut proof = silkproof::proof_with(&audit, &data, &b, waiver.as_ref()).unwrap_or_else(|why| {
                     r.log("silkscreen-proof-refused", json!({"board": board_path, "audit": out, "why": why}));
                     err(&format!("silkscreen proof not recorded: {}", why.join("; ")), &["Build the inventory from the whole board (every footprint by its reference, every via and hole on both faces) and fix the labels, then `silkscreen-audit --record` again.".into()])
                 });
@@ -3444,6 +3449,7 @@ fn main() {
                 // 0.2.18: what the labels say, not only where they sit: power inputs without a voltage, test points without their net
                 for w in silk_polarity::warnings(&b).iter().filter(|w| w.contains("power input") || w.contains("test point")) { eprintln!("Warning: {w}"); }
                 for f in fab["findings"].as_array().into_iter().flatten().filter_map(Value::as_str) { eprintln!("Warning (Fab layer, not blocking): {f}. {}", fab_layer::TIDY_HINT); }
+                if !proof["labelDensityWaived"].is_null() { eprintln!("Note: the label count ({} for {} footprints) is answered by the deviation \"silkscreen proof\": {}", proof["labelDensityWaived"]["labels"], proof["labelDensityWaived"]["footprints"], proof["labelDensityWaived"]["why"]); }
                 ok(&format!("{}silkscreen step proof recorded: {} footprints and {} vias covered, {} labels\n{}", String::from_utf8_lossy(&result.stdout), proof["footprints"], proof["vias"], proof["labels"], silk_polarity::label_check_lines(&labels).join("\n")), &["Geometry audit only; native DRC, label association and both native 2D/3D views remain required. Then `step 3d`.".into()]);
                 return;
             }
diff --git a/crates/adom-aiflow/src/silkproof.rs b/crates/adom-aiflow/src/silkproof.rs
index 6b66884..6a37e30 100644
--- a/crates/adom-aiflow/src/silkproof.rs
+++ b/crates/adom-aiflow/src/silkproof.rs
@@ -14,6 +14,14 @@ fn covers(id: &str, reference: &str) -> bool {
 
 /// The proof's evidence, or why it cannot be recorded.
 pub fn proof(audit: &Value, input: &Value, board: &Board) -> Result<Value, Vec<String>> {
+    proof_with(audit, input, board, None)
+}
+
+/// `proof`, with the label-density criterion answered by a live `silkscreen proof` deviation (bound to this
+/// board and spec): a face that is meant to carry no references (a desk object's display face or back, a
+/// product's cosmetic side) still records the geometry proof. Only the count is waived: the audit must pass,
+/// cover every footprint and via, and match the board's geometry; the waiver and its reason go into the proof.
+pub fn proof_with(audit: &Value, input: &Value, board: &Board, density_waiver: Option<&Value>) -> Result<Value, Vec<String>> {
     let mut why = Vec::new();
     if audit["passed"] != true { why.push("the audit did not pass".to_string()); }
     let inventory = input["inventory"].as_array().cloned().unwrap_or_default();
@@ -28,16 +36,23 @@ pub fn proof(audit: &Value, input: &Value, board: &Board) -> Result<Value, Vec<S
     if via_items < vias { why.push(format!("the audit inventory has {via_items} via/hole item(s), the board {vias} vias")); }
     why.extend(geometry_gaps(input, board));
     let (labels, fps) = crate::provenance::silk_labels(board);
+    let mut waived = Value::Null;
     if fps > 0 && (labels as f64) < crate::provenance::MIN_SILK_PER_FOOTPRINT * fps as f64 {
-        why.push(format!("{labels} visible silkscreen label(s) for {fps} footprints (at least {:.1} per footprint)", crate::provenance::MIN_SILK_PER_FOOTPRINT));
+        match density_waiver {
+            Some(d) => waived = json!({"labels": labels, "footprints": fps, "minimumPerFootprint": crate::provenance::MIN_SILK_PER_FOOTPRINT,
+                                       "deviation": d["item"], "why": d["why"], "at": d["at"], "aiThread": d["aiThread"]}),
+            None => why.push(format!("{labels} visible silkscreen label(s) for {fps} footprints (at least {:.1} per footprint)", crate::provenance::MIN_SILK_PER_FOOTPRINT)),
+        }
     }
     if !why.is_empty() { return Err(why); }
-    Ok(json!({
+    let mut p = json!({
         "pass": true, "footprints": refs.len(), "footprintsCovered": refs.len(), "vias": vias, "viaItems": via_items,
         "labels": labels, "inventoryItems": audit["inventoryItems"], "obstacles": audit["obstacles"], "marks": audit["marks"],
         "collisions": 0, "nativeVerified": false,
         "note": "geometry audit of the supplied whole-board inventory; native DRC, label association and both native 2D/3D views remain required",
-    }))
+    });
+    if !waived.is_null() { p["labelDensityWaived"] = waived; }
+    Ok(p)
 }
 
 /// How far a supplied box may fall short of the board's own geometry (mm).
@@ -223,6 +238,28 @@ mod tests {
   (via (at 5 5) (size 0.6) (drill 0.3) (layers "F.Cu" "B.Cu"))
 )"#;
 
+    #[test]
+    fn a_live_deviation_waives_only_the_label_count() {
+        let b = Board::from_text("t.kicad_pcb", BOARD.to_string()).unwrap();
+        let input = json!({"inventory": [{"id": "R1", "kind": "body", "layers": ["F.SilkS"]}, {"id": "C1:body", "kind": "body", "layers": ["F.SilkS"]}, {"id": "v1", "kind": "via", "layers": ["F.SilkS", "B.SilkS"]}],
+            "obstacles": [{"id": "R1", "kind": "body", "box": [1.4, 1.4, 2.6, 2.6]}, {"id": "C1:body", "kind": "body", "box": [5.4, 1.4, 6.6, 2.6]}, {"id": "v1", "kind": "via", "box": [4.7, 4.7, 5.3, 5.3]}]});
+        let passed = json!({"passed": true, "inventoryItems": 3, "obstacles": 4, "marks": 4});
+        // the same board with every silkscreen text hidden: no labels at all
+        let bare = Board::from_text("t.kicad_pcb", BOARD.replace("(layer \"F.SilkS\")", "(layer \"F.SilkS\") (hide yes)")).unwrap();
+        assert_eq!(crate::provenance::silk_labels(&bare).0, 0);
+        {
+            assert!(proof(&passed, &input, &bare).unwrap_err().join("; ").contains("per footprint"));
+            let dev = json!({"item": "silkscreen proof", "why": "a desk object's display face: no references on purpose", "at": "2026-11-10T00:00:00Z", "aiThread": "t"});
+            let p = proof_with(&passed, &input, &bare, Some(&dev)).unwrap();
+            assert_eq!(p["labelDensityWaived"]["labels"], 0);
+            assert_eq!(p["labelDensityWaived"]["deviation"], "silkscreen proof");
+            // the waiver never covers anything else: a failed audit stays refused
+            assert!(proof_with(&json!({"passed": false}), &input, &bare, Some(&dev)).unwrap_err()[0].contains("did not pass"));
+        }
+        // a board that meets the count records no waiver
+        assert!(proof_with(&passed, &input, &b, None).unwrap().get("labelDensityWaived").is_none());
+    }
+
     #[test]
     fn records_only_a_passing_audit_that_covers_the_board() {
         let b = Board::from_text("t.kicad_pcb", BOARD.to_string()).unwrap();

1 Reply

Colby Knox marked this in progress ·
Colby Knox linked pull request #80, which closes this when it merges ·
Colby Knox closed this in pull request #80 ·
Colby Knox ·

Thanks for the patch and the test. It's in AI Flow 0.2.63, as you wrote it, with one addition: silkscreen-audit --record also accepts the deviation under the name finish gives the label-count line (deviate "silkscreen"), as well as deviate "silkscreen proof". Only the label count is waived: the audit, coverage of every footprint and via, and the geometry check still have to pass. The proof records labelDensityWaived. The refusal's hint and the skill now explain how.

Upgrade with adom-wiki pkg install adom/adom-aiflow (then adom-aiflow --version shows 0.2.63). Your local +noah.2 build can go.

Log in to reply.