# concur

**Cross-source consensus check for chip libraries.**

A small Rust CLI that runs downstream of `ds2sf` and `chip-fetcher`: it loads every available source-of-truth for a chip's symbol and footprint (datasheet-extracted, KiCad UL bundle, Fusion 360 EAGLE `.lbr`, Altium binary), compares them across seven axes, and emits a result.json with `status: golden | divergent | unrecoverable` plus tagged `hints[]` so the upstream caller can either re-fetch a suspect library or escalate to a human.

The pipeline:

```
chip-fetcher  →  ds2sf (status: ok)  →  concur (status: golden)  →  downstream is happy
                                            ↓ divergent
                                       chip-fetcher refetches a lib
                                       OR human reviews
```

## Why this matters

Concur is **opinionated** about whether a divergence requires upstream work or is safe to ship. Many "disagreements" between sources are known-benign patterns (exposed-pad counted as pin N+1 in one source but not the other, JEDEC-outline-name vs marketing-name, alt-function pin labels, pad-array span misread as body dim). Concur classifies these as **variants** and ships the dataset with `status: golden_with_variants` (exit 0) so the upstream caller can move forward and just remember the variants downstream. Real disagreements stay `divergent` (exit 2).

Real example from this release's smoke tests:

```
$ concur check ~/project/chip-fetcher/library/ADS1115IDGSR
  ds2sf       — 10 pins, 10 pads, package "VSSOP-10"
  kicad       — 10 pins, 10 pads, footprint "SOT_ADS1115IDYNR_TEX"
  eagle (.lbr) — 10 pins, 10 pads, package "DGS0010A_N"

OK: ADS1115IDGSR — GOLDEN with variants [variants: label_only, stub_pin_names, body_dim_artifact]
```

What concur figured out:

- `label_only_mismatch` — KiCad's footprint name starts with `SOT_` because that's TI's UL library prefix convention, but the geometry (pad count, pad layout, pitch) all agrees with EAGLE's `DGS0010A_N` and ds2sf's `VSSOP-10`. The package family really is VSSOP. Cosmetic only.
- `stub_symbol_pin_names` — the KiCad UL symbol has pin "names" of "1", "2", "3"… instead of real labels. Downstream can use ds2sf's or EAGLE's pin labels.
- `body_dim_measurement_artifact` — ds2sf and EAGLE report different body dimensions (5.05×3.10 vs 4.45×2.00). Body dim is courtyard-only, doesn't affect placement; the diff is a measurement-method artifact (lead-tip span vs pad-array span vs body D dim).

All three are flagged but explained, exit 0, chip-fetcher moves forward.

## Outputs

`<chip-dir>/<MPN>-concur.result.json` — the audit + agent contract:

```json
{
  "schema_version": "1",
  "status": "golden_with_variants",
  "exit_code": 0,
  "mpn": "ADS1115IDGSR",
  "summary": "ADS1115IDGSR — GOLDEN with variants [variants: label_only,stub_pin_names,body_dim_artifact]",
  "sources_compared": ["ds2sf","kicad","eagle"],
  "sources_missing":  ["altium"],
  "variants": [
    { "kind": "label_only_mismatch",
      "notes": "ds2sf: VSSOP-10 → family=VSSOP | eagle: DGS0010A_N → family=VSSOP | kicad: SOT_ADS1115IDYNR_TEX → family=SOT" },
    { "kind": "stub_symbol_pin_names",
      "stub_sources": ["kicad"],
      "canonical_sources": ["ds2sf","eagle"] },
    { "kind": "body_dim_measurement_artifact",
      "notes": "ds2sf: 5.05×3.10 mm | eagle: 4.45×2.00 mm" }
  ],
  "axes": [
    { "axis": "pad_count",     "verdict": "agree" },
    { "axis": "pin_names",     "verdict": "disagree",
      "conflicts": ["kicad has stub-only pin names …"] },
    { "axis": "package_family","verdict": "disagree",
      "conflicts": ["Package families disagree across sources: ds2sf=VSSOP, kicad=SOT, eagle=VSSOP"] }
  ],
  "hints": [
    { "action": "ul_symbol_has_stub_names", "source": "kicad", "pin_count": 10,
      "suggestion": "Use ds2sf-extracted symbol JSON or refetch the .kicad_sym from a manufacturer-direct source" }
  ]
}
```

### Status ladder

| Status | Exit | Caller action |
|---|---|---|
| `golden` | 0 | Ship it, no caveats. |
| `golden_with_variants` | 0 | **Ship it.** Every disagreement is explained by `variants[]`. Downstream tools can use the variant list to decide which source to prefer (ds2sf for pin labels when the KiCad symbol has stubs, etc). |
| `divergent` | 2 | Real disagreement. Refetch a library or escalate to a human. |
| `unrecoverable` | 3 | Couldn't parse ≥2 sources. |

### Variant kinds (v0.3)

| Kind | When | What it means |
|---|---|---|
| `exposed_pad_variant` | Pad count diff is exactly 1 | One source counts the EP/thermal pad as pin N+1; the other rolls it into the body. EE practice varies; both are correct. |
| `module_extra_pads` | Pad count diff > 1 (one source has more) | KiCad/EAGLE footprint includes mounting/shield/test pads beyond the module datasheet's user-facing pinout. |
| `label_only_mismatch` | Package family names differ but geometry agrees | Naming-convention artifact (e.g. TI's `SOT_` prefix on a VSSOP footprint, JEDEC outline `DGS0010A` → VSSOP, IPC-7351 generic name `SOP65P640X120-20N` → TSSOP). Cosmetic only. |
| `stub_symbol_pin_names` | One source's pin names are pin numbers ("1","2"…) | UL stub case. Downstream should use a richer source (ds2sf or EAGLE) for labels. |
| `body_dim_measurement_artifact` | Body dim disagrees but pad geometry agrees | Different sources measured "body" differently (lead-tip span vs body D-dim vs pad-array span). Body dim is courtyard-only; doesn't affect placement. |
| `naming_convention_mismatch` | Pin names differ but every disagreement is a benign convention swap | Channel-digit position (`OUT1` ↔ `1OUT` for op-amps and multi-channel parts) or power-pin alias (`V+` ↔ `VCC`, `V-` ↔ `GND`). |

## Comparison axes

| Axis | What it checks |
|---|---|
| `pad_count` | Number of pads in the footprint |
| `pad_numbers` | Set of pad numbers/labels (1..N or A1, A4, B7…) |
| `symbol_pin_count` | Number of pins in the symbol |
| `pin_names` | Per-pin name across sources (slash → underscore, case-insensitive). Sources that emit stub names ("1","2"…) are flagged as `ul_symbol_has_stub_names` hint. |
| `pin_to_pad_map` | Does pin VDD map to the same pad number across sources? |
| `package_family` | SOIC / VSSOP / QFN / SOT / etc., with JEDEC outline aliases (DGS0010A → VSSOP, RGE → QFN, RUG → VQFN, …) |
| `body_dims` | Body x/y in mm, ±0.5 mm tolerance |

## Agent-to-agent contract

`hints[]` is a tagged-union list (separate from `variants[]` — variants explain *why* a disagreement is benign; hints tell the caller *what action to take*). v0.3 enriches refetch hints with concrete `suggested_sources` (priority-ordered) and `expected_resolution` so chip-fetcher knows where to refetch from AND how to verify the refetch worked.

| Action | Carries | When |
|---|---|---|
| `refetch_library_source` | `{format, suspect, notes}` | A specific lib looks wrong; chip-fetcher should try a different upstream. |
| `ul_symbol_has_stub_names` | `{source, pin_count, suggestion}` | KiCad UL stub case (pin "names" are pin numbers). |
| `accept_with_caveats` | `{caveats}` | Soft diffs within tolerance. |
| `human_review` | `{reason, notes}` | No automated repair safe. |
| `source_parse_failed` | `{format, path, error}` | A file couldn't be parsed at all. |

## Calling recipe

```rust
let r = run_concur(chip_dir);
match r.status.as_str() {
    "golden" => mark_chip_dir_clean(),
    "divergent" => {
        for h in &r.hints {
            match h {
                Hint::RefetchLibrarySource { format, .. } => {
                    refetch_lib_from_alternate_source(format);
                    return run_concur(chip_dir); // re-call after refetch
                }
                Hint::UlSymbolHasStubNames { .. } => {
                    // Replace .kicad_sym from ds2sf JSON via downstream sym_create.
                }
                _ => {}
            }
        }
    }
    "unrecoverable" => log_for_human(&r),
    _ => unreachable!(),
}
```

## CLI

```
concur check <CHIP-DIR>     # run cross-source check
concur inspect <CHIP-DIR>   # print summary of an existing concur.result.json
concur health               # parser readiness check
concur install              # drop SKILL.md
```

`check` flags: `--ds2sf-symbol`, `--ds2sf-footprint`, `--kicad-sym`, `--kicad-mod`, `--eagle`, `--mpn`, `--out-dir`, `--force`, `--json-only`.

## Validated chips (v0.3 release — 9-chip suite)

| MPN | Pkg | Status | Variants caught | Notes |
|---|---|---|---|---|
| ADS1115IDGSR | VSSOP-10 | golden_with_variants | label_only, stub_pin_names, body_dim_artifact | TI's `SOT_` prefix on VSSOP footprint; UL symbol stubs; ds2sf body-dim picked up the lead-tip span |
| DRV8316RRGFR | VQFN-40 | golden_with_variants | ep | EP counted as pin 41 by KiCad/EAGLE, omitted by ds2sf |
| VL53L8CX | OLGA-16 | golden_with_variants | ep, label_only, body_dim_artifact | Custom optical LGA; thermal pad EP variant |
| ESP32-S3-WROOM-1-N4 | SMD module | golden_with_variants | module_extras, body_dim_artifact | KiCad has 8 extra shield/test pads beyond the module's 41 castellations |
| LM358D | SOIC-8 | golden_with_variants | naming_convention | `OUT1`↔`1OUT` channel-digit + `V+`↔`VCC`, `V-`↔`GND` power-pin aliases |
| TPS62840YBGR | DSBGA-8 | golden_with_variants | body_dim_artifact | Cleanest of the suite; only courtyard body-dim diff |
| BMI270 | LGA-14 | golden_with_variants | label_only, body_dim_artifact | Bosch IMU; EAGLE 7.x .lbr now parses (DTD support) |
| **MSP430G2553IPW20** | TSSOP-20 | **divergent** | — | **Real bug.** KiCad symbol shifts pin functions: ds2sf says pin 14=NC, KiCad says pin 14=P1.6 with 8 alt-functions. Likely wrong sub-variant in UL bundle. |
| **R7FA4M1AB3CFP** | LQFP-100 | **divergent** | — | **Real bug.** Pin 1: ds2sf=`P108_TMS_SWDIO`, KiCad=`P400`. AVCC0 lands on pad 90 vs pad 88. **Wrong sub-variant of the Renesas RA4M1 family.** |

7/9 pass cleanly (`golden_with_variants` exit 0). 2/9 are real wrong-variant findings the upstream caller should refetch. **Zero false positives** in v0.3.

## Out of scope (current)

- **Altium .SchLib (symbol-side) parsing.** v0.6 added .PcbLib (pads) via the sister `altium-pcblib` crate; .SchLib pin labels are still pending.
- **Body-thickness comparison** (z-dim). Sources don't reliably report this.
- **Tolerance-aware lead/pad-pitch checks** beyond raw body dims.

## Altium multi-variant selection (v0.7)

Many vendor `.PcbLib`s ship multiple footprint variants in the same file — for example DRV8316RRGFR carries `RGF0040E-IPC_A` (41-pad IPC standard) AND `RGF0040E-MFG` (57-pad manufacturing layout with extra fiducials and test points). The upstream `altium-pcblib` crate picks one as "primary" by stream order.

If concur naively trusts the primary, it can disagree with kicad+eagle 100% of the time. v0.7 computes the pad-count consensus from already-parsed sources (ds2sf / kicad / eagle) and asks `altium-pcblib` for the variant whose pad count is closest to the median. Names containing `-MFG` / `_MFG` / `DEBUG` / `FIDUCIAL` / `TEST` get a tiebreak penalty so the IPC-standard variant wins when in doubt.

Effect on the benchmark: DRV8316RRGFR was reporting `divergent` with altium engaged (57 vs 41) → now reports `golden_with_variants` (41 across all four sources, only EP-counting differs).

## Build

```bash
cargo build --release
sudo cp target/release/concur /usr/local/bin/
concur install
```

Or just `./build.sh`.

## Related

- [`ds2sf`](https://github.com/adom-inc/ds2sf) — datasheet → symbol + footprint + provenance JSON. Upstream of concur.
- chip-fetcher — pulls the libraries that concur cross-checks.
