Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to this project are documented here. Format follows [Keep a
- **`Verification/Production QA.md`.** An honest engineering gap analysis between Shakedown today and a factory production-QA station (calibrated limits, golden binning, Measurement System Analysis, gated preconditions, defect coverage / escape rate, SPC), and the architecture to close it. The calibrated-baseline mechanism is the first leg.
- **`repeatability.sh` (Measurement System Analysis).** Runs the variance phase N times back to back on one unit and reports the gage's own run-to-run noise as a fraction of the fail tolerance (6-sigma of `spread_pct` over the runs, against the AIAG Gage R&R rule: under 10% capable, 10-30% marginal, over 30% the gage rivals the limit). Answers whether a metric is even capable of a pass/fail before we trust its limits. The repeatability leg of the production-QA path; reproducibility across stations and a golden-unit characterization run are still owed.
- **`--strict` precondition gating.** Production / calibration mode: the AC-power and quiet-system checks that are only warnings by default become hard preconditions, checked after inventory but before the load phases. A run not on AC or on a busy system aborts immediately (exit 3) instead of burning 45 minutes on a number that can't be compared to a baseline. Default runs are unchanged; the report records `strict_mode` in Phase 0. Ambient and chassis-soak gating still need fixturing the project lacks.
- **Preset auto-selection.** `./run` with no `--target` now matches the detected hardware (chip, memory, model) against the presets in `targets/` and uses the one that fits, so the right chassis class, inventory asserts, and golden baseline all apply with nothing to type. The match uses the same sources as the inventory asserts (`chip_type` / `cpu_type`, `hw.memsize`), so an auto-selected preset always passes its own asserts; exactly one match wins, and zero or several falls back to chassis auto-detection with asserts skipped, as before. `--target` is now an optional override for asserting an expected SKU or forcing a preset.
- **Store Day Checklist** reframed to a single in-store flow centered on the one command (`./run --store`) and the verdict banner.

### Added: in-store verification toolkit (wave 8)
Expand Down
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,10 @@ Shakedown is the procedure for catching those.

```bash
git clone https://github.com/ugglr/mac-shakedown ~/mac-shakedown && cd ~/mac-shakedown
./run --target mbp-16-m5-max-64
./run
```

Or without a preset, which auto-detects chassis class from `system_profiler` (fine for Macs that don't have a target preset yet):

```bash
./run
```
`./run` auto-selects the matching preset from your hardware (chip, memory, model), so it bins against the right thresholds and baseline with nothing to type. Pass `--target mbp-16-m5-max-64` (see `targets/`) to assert an expected SKU, or to override the match; if no preset matches, it auto-detects chassis class from `system_profiler` and skips the inventory asserts.

The orchestrator runs the automated phases (preflight → inventory → battery → race benchmark → SSD test → memory bandwidth → CPU variance → thermal load) end-to-end, asks for sudo once upfront (Phase 5 and the SSD page-cache drop need it), and writes a SCHEMA-compliant report to `Reports/local/` plus a sanitized PR-able copy to `Reports/submissions/`. Opt-in flags add heavier passes: `--noaccel` (a non-accelerated BLAKE2b variance pass), `--gpu` (a Metal GPU compute pass), and `--llama` (clones and builds llama.cpp for a combined CPU+GPU+memory AI load). `--store` bundles the thorough profile for verifying a new unit. Runtime ~20 min on Intel, ~27 min on Air, ~47 min on MacBook Pro.

Expand Down Expand Up @@ -92,13 +88,13 @@ Two ways:

Hard-fails if the chip / RAM don't match the preset. Useful when verifying you got the SKU you paid for.

2. **No target.** Auto-detects chassis class, skips the SKU asserts, still runs all the variance / thermal / battery checks:
2. **No target (the usual way).** Auto-selects the matching preset from the detected hardware, so the right thresholds, asserts, and baseline apply with nothing to type:

```bash
./run
```

Use this for Macs that don't have a preset yet, or existing units you're self-testing rather than verifying as new.
If exactly one preset matches your chip / memory / model it is used (and printed); if none match, it falls back to auto-detecting chassis class and skipping the SKU asserts, fine for Macs that don't have a preset yet.

(Don't see your SKU? `targets/README.md` has the schema. Open a PR adding a preset.)

Expand Down
2 changes: 1 addition & 1 deletion Verification/Benchmark Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Cheapest and most decisive reads first, then one quick baseline, then a fast cos

The 15-20 min store window cannot run a sustained loop. Do it that night, on AC, on a hard flat surface in a cool room.

1. **Harness, thorough profile:** `SHAKEDOWN_YES=1 ./run --target mbp-16-m5-max-64 --store`. Both CPU workloads (including the non-accelerated BLAKE2b that matches where the defect was reported), the GPU pass, a long warmup, and 8 iterations. Rerun any single WARN.
1. **Harness, thorough profile:** `SHAKEDOWN_YES=1 ./run --store` (it auto-selects the preset for your hardware; add `--target <name>` to pin one). Both CPU workloads (including the non-accelerated BLAKE2b that matches where the defect was reported), the GPU pass, a long warmup, and 8 iterations. Rerun any single WARN.
2. **Sustained Cinebench loop:** Cinebench 2024 multi-core back to back for ~30-45 min (or its minimum-test-duration mode). Record EACH run's score. A healthy unit settles to a stable sustained score; a defective one keeps sagging run-over-run. Infant-mortality and batch-variance defects often pass a cold first run and only show by the 3rd-4th run under heat.
3. **Second workload:** re-run Geekbench 6 (CPU and Metal) a few times against the live Geekbench Browser average for your exact model.
4. **Compare against a sibling** if you captured one: `./Verification/scripts/compare-reports.sh sibling.json yours.json`.
Expand Down
2 changes: 1 addition & 1 deletion Verification/Runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The procedure for each verification phase. The automated phases (0–5, the cold

**Total time:** ~45 min on a MacBook Pro, ~25 min on a fanless Air, ~35 min on a desktop or Intel Mac, +30 min if you opt into the idle-drain test.

> **Target preset.** Pass `--target <name>` to assert the unit matches the preset in `targets/<name>.json`. Without `--target`, the inventory asserts are skipped. Useful for Macs that don't yet have a preset, or for self-testing an existing unit.
> **Target preset.** Without `--target`, the matching preset is auto-selected from the detected hardware (chip, memory, model), so the right thresholds, asserts, and baseline apply automatically; if none matches, the inventory asserts are skipped. Pass `--target <name>` to assert the unit matches the preset in `targets/<name>.json`, or to override the match.

## Phase 0: Pre-flight (1 min)

Expand Down
4 changes: 2 additions & 2 deletions Verification/Store Day Checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ Apple's QA catches the obvious build stuff (speakers, keys, screen). What it has
xcode-select --install
git clone https://github.com/ugglr/mac-shakedown ~/mac-shakedown
cd ~/mac-shakedown
./run --target mbp-16-m5-max-64 --store
./run --store
```

- `xcode-select --install` is a one-time GUI dialog (~10 min); the rest is one paste.
- Swap `mbp-16-m5-max-64` for your SKU (see `targets/`), or drop `--target` to auto-detect.
- No SKU to type: `./run` auto-selects the matching preset from the machine's own hardware (it prints which one). Add `--target <name>` (see `targets/`) only if you want to assert an expected SKU or force a specific preset.
- Enter your login password when it asks for sudo (the thermal phase needs it).
- `--store` is the thorough profile: ~45 min of CPU variance (accelerated **and** non-accelerated), a 10-minute sustained thermal load, the memory triad, and the GPU pass. It takes over; walk around while it runs.
- Want the exact workload the M5 defect was reported on? Add `--llama` (it builds llama.cpp and runs an LLM load; extra time).
Expand Down
71 changes: 67 additions & 4 deletions Verification/scripts/run-shakedown.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ Usage: $(basename "$0") [--target <preset>] [--store] [--strict] [--no-sudo] [--

--target <preset> optional. Target preset name (file under targets/ without .json)
e.g. mbp-16-m5-max-64, macbook-air-m5-16, mbp-16-intel-2019.
Without a target, inventory asserts are skipped (useful for
Macs that don't yet have a preset).
If omitted, the matching preset is auto-selected from the
detected hardware (chip, memory, model), so you usually do
not need it. Pass it to assert an expected SKU, or to
override the match. If nothing matches, asserts are skipped.
--no-sudo skip Phase 5 (sustained thermal load), the only phase that
needs sudo. Phase 4 (variance) still runs and is the headline
test. Alias: --skip-thermal.
Expand Down Expand Up @@ -96,8 +98,8 @@ Usage: $(basename "$0") [--target <preset>] [--store] [--strict] [--no-sudo] [--
--notes "..." optional free-form note to embed in the report. Setting any
note flips submission_safe to false (notes may contain PII).

Chassis class: read from the preset when --target is given; otherwise
auto-detected from system_profiler (override with CHASSIS_CLASS env var).
Chassis class: read from the preset when one is given or auto-selected;
otherwise auto-detected from system_profiler (override with CHASSIS_CLASS env var).
HELP
exit 0
;;
Expand Down Expand Up @@ -127,6 +129,67 @@ if [[ "$STORE" -eq 1 ]]; then
fi

TARGET_FILE=""

# Auto-select a preset when the user gave neither --target nor a CHASSIS_CLASS
# override: match the detected hardware (chip, memory, model) against the presets
# in targets/. The preset's match fields are exactly what the machine reports
# about itself, using the same sources as the inventory asserts (chip_type /
# cpu_type, hw.memsize), so an auto-selected preset always passes its own asserts.
# One unambiguous match wins; zero or several leaves TARGET empty and we fall back
# to chassis auto-detection with asserts skipped, exactly as before. Pass --target
# to assert an expected SKU or to override the match.
if [[ -z "$TARGET" && -z "${CHASSIS_CLASS:-}" ]]; then
TARGET=$(python3 - "$REPO_ROOT/targets" <<'PYEOF'
import glob
import json
import os
import subprocess
import sys

def sysctl(key):
try:
return subprocess.check_output(["sysctl", "-n", key],
stderr=subprocess.DEVNULL).decode().strip()
except Exception:
return None

try:
sp = json.loads(subprocess.check_output(
["system_profiler", "-json", "SPHardwareDataType"],
stderr=subprocess.DEVNULL))
hw = sp["SPHardwareDataType"][0]
except Exception:
sys.exit(0) # no detection -> no auto-target, fall back

chip = hw.get("chip_type") or hw.get("cpu_type") or ""
model = " ".join(filter(None, [hw.get("machine_model"), hw.get("machine_name")]))
memraw = sysctl("hw.memsize")
mem_gb = round(int(memraw) / (1024 ** 3)) if memraw and memraw.isdigit() else None

matches = []
for path in sorted(glob.glob(os.path.join(sys.argv[1], "*.json"))):
try:
with open(path) as f:
t = json.load(f)
except (OSError, ValueError):
continue
cp = t.get("chip_pattern")
if not cp or cp not in chip:
continue
if t.get("memory_gb") is not None and t["memory_gb"] != mem_gb:
continue
mmi = t.get("model_must_include")
if mmi and mmi not in model:
continue
matches.append(os.path.splitext(os.path.basename(path))[0])

if len(matches) == 1:
print(matches[0])
PYEOF
)
[[ -n "$TARGET" ]] && echo "shakedown: auto-selected target '$TARGET' from detected hardware (pass --target to override)" >&2
fi

if [[ -n "$TARGET" ]]; then
TARGET_FILE="$REPO_ROOT/targets/$TARGET.json"
if [[ ! -f "$TARGET_FILE" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion baselines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Calibrated golden baselines, one `<preset>.json` per `targets/<preset>.json`. Ea
./Verification/scripts/make-baseline.sh good1.json good2.json ... > baselines/mbp-16-m5-max-64.json
```

When `baselines/<preset>.json` exists, `./run --target <preset>` bins each measured metric against its golden control limit and the verdict becomes a **calibrated** pass/fail: a metric outside its limit fails the run, and the verdict banner says so. Absent a baseline, the verdict is within-unit only (advisory).
When `baselines/<preset>.json` exists for the run's preset (`./run` auto-selects the preset from the hardware, or pass `--target <preset>`), each measured metric is binned against its golden control limit and the verdict becomes a **calibrated** pass/fail: a metric outside its limit fails the run, and the verdict banner says so. Absent a baseline, the verdict is within-unit only (advisory).

This directory ships mostly empty on purpose. A baseline is only as good as the known-good units it was built from, and that corpus is still being gathered. See [`Verification/Production QA.md`](../Verification/Production%20QA.md) for the calibration discipline (golden reference, 3-sigma control limits, the path to production-grade). Build your own from a unit you trust to get a calibrated verdict today.