{
  "schema": "fleet-bench-nodes/1",
  "_doc": "Registry for tools/bench_node.sh — the repeatable bench-node runner. THIS file is the public-safe TEMPLATE (placeholders only). Copy it to tools/bench_nodes.json (GITIGNORED) and fill in REAL tailnet IPs / ssh users / repo paths there. Only 'sanitized_name' may ever appear in a committed artifact. Mirrors the fleet_endpoints.example.json convention.",
  "_resolve_cmd_doc": "EPHEMERAL hosts (#12): a node may carry an OPTIONAL 'resolve_cmd' instead of relying on a static tailnet_ip. When present, the runner runs it just-in-time (read-only; must print exactly one host/IP on stdout) to learn the CURRENT address of a host whose IP rotates per launch (e.g. a TTL-guarded GCP bench VM, #571), then takes the SAME ssh path. `info` never calls the resolver, so it stays offline-safe. Keep the REAL project/zone/instance inside resolve_cmd or its env in the GITIGNORED registry copy.",
  "_safety": [
    "tools/bench_nodes.json is gitignored — real identity (tailnet_ip or resolve_cmd inputs, ssh_user, magicdns, repo_path, host_key) lives ONLY there.",
    "Reachability is an SSH handshake, not ICMP/tailscale ping (a node can answer ping with no sshd).",
    "host_key is PINNED here after bootstrap — the driver verifies against it instead of blind accept-new. An ephemeral host whose key rotates per provision leaves host_key empty and falls back to accept-new.",
    "resolve_cmd must be a CHEAP read-only lookup (e.g. tools/gcp_bench.py --resolve-ip for a kept GCP VM, or a tailscale magicdns resolve) — it runs on every connecting subcommand; it must never provision or bill.",
    "Every run writes lineage.json (utc datetime + fak version/commit + node + go) for traceability.",
    "Results go to tools/_registry/bench-runs/ (gitignored). Promoting a result to a committed path needs a REAL redaction pass (CPU brand + go/git + hostname) — the shape-only scrub audit does NOT do that."
  ],
  "_onboarding": {
    "_doc": "Public-safe roster of the bench nodes this runner targets and where each stands (#922). The HARDWARE-CATALOG node roster is private; this block is the committed, sanitized view. 'pending' nodes have a placeholder entry below — onboarding = copy it into the gitignored tools/bench_nodes.json, fill in REAL identity, get the driver pubkey into the node's authorized_keys, pin host_key (ssh-keyscan -t ed25519), then `bench_node.sh <node> tests`. Every 'pending' node carries an 'issue' that tracks its onboarding — a dedicated child issue where one is filed (#10 laptop-from-desktop, #12 GCP GPU VMs), else the umbrella #922 itself (desktop, dgx-a100, which have no dedicated child yet) — so this roster is a navigable decomposition index for the umbrella.",
    "roster": [
      { "node": "macbook", "sanitized_name": "node-macos-a",         "status": "onboarded", "verified": "2026-06-21", "note": "Apple M3 Pro arm64 — tests green, BenchmarkDecide ~317 ns idle." },
      { "node": "desktop", "sanitized_name": "node-linux-desktop-a", "status": "pending",   "issue": 922, "blocker": "host-reachability — desktop answers ICMP but ships with no sshd; enable sshd + add driver pubkey, then pin host_key." },
      { "node": "laptop",  "sanitized_name": "node-linux-laptop-a",  "status": "onboarded", "verified": "2026-06-30", "issue": 10, "note": "RTX 4070 Laptop via Windows SSH -> WSL; `bench_node.sh laptop bench` passed from the desktop driver (bench rc=0, BenchmarkDecide ~1.1 us/op)." },
      { "node": "gcp-gpu", "sanitized_name": "node-gcp-gpu-a",       "status": "pending",   "issue": 12,  "blocker": "private-registry-fill — dynamic-IP bridge is wired; copy the resolver entry into gitignored bench_nodes.json, set the real GCP project/zone/instance there or in env, then run bench_node.sh gcp-gpu ping after provisioning." },
      { "node": "dgx-a100","sanitized_name": "node-dgx-a100",        "status": "pending",   "issue": 922, "blocker": "no-direct-ssh — DGX is reached via a relay bridge, not a direct tailnet SSH handshake; needs a reachable sshd path before the driver can poll it." }
    ]
  },
  "nodes": [
    {
      "name": "macbook",
      "sanitized_name": "node-macos-a",
      "tailnet_ip": "198.51.100.10",
      "magicdns": "node-macos-a.example.ts.net",
      "ssh_user": "user",
      "ssh_key": "~/.ssh/id_ed25519",
      "ssh_port": 22,
      "host_key": "",
      "remote_shell": "zsh",
      "repo_path": "~/work/fleet",
      "repo_module": "private",
      "toolchain_env": "GOTOOLCHAIN=auto",
      "go_bin": "/usr/local/go/bin",
      "capabilities": ["arm64", "darwin", "metal"],
      "agent_host": false,
      "facts": { "go_version": null, "git_commit": null, "bootstrapped_at": null }
    },
    {
      "name": "desktop",
      "sanitized_name": "node-linux-desktop-a",
      "tailnet_ip": "198.51.100.20",
      "magicdns": "node-linux-desktop-a.example.ts.net",
      "ssh_user": "user",
      "ssh_key": "~/.ssh/id_ed25519",
      "ssh_port": 22,
      "host_key": "",
      "remote_shell": "bash",
      "repo_path": "~/work/fleet",
      "repo_module": "private",
      "toolchain_env": "GOTOOLCHAIN=auto",
      "go_bin": "/usr/local/go/bin",
      "capabilities": ["amd64", "linux"],
      "agent_host": false,
      "facts": { "go_version": null, "git_commit": null, "bootstrapped_at": null }
    },
    {
      "name": "laptop",
      "sanitized_name": "node-linux-laptop-a",
      "tailnet_ip": "198.51.100.50",
      "magicdns": "node-linux-laptop-a.example.ts.net",
      "ssh_user": "user",
      "ssh_key": "~/.ssh/id_ed25519",
      "ssh_port": 22,
      "host_key": "",
      "remote_shell": "windows-wsl",
      "wsl_distro": "Ubuntu",
      "repo_path": "~/work/fleet",
      "repo_module": "private",
      "toolchain_env": "GOTOOLCHAIN=auto",
      "go_bin": "/usr/local/go/bin",
      "capabilities": ["amd64", "linux", "cuda", "rtx-4070", "sm_89"],
      "agent_host": false,
      "facts": { "go_version": null, "git_commit": null, "bootstrapped_at": null }
    },
    {
      "name": "gcp-gpu",
      "sanitized_name": "node-gcp-gpu-a",
      "_ephemeral_doc": "EPHEMERAL host (#12): tailnet_ip is empty because a TTL-guarded bench VM (#571) gets a fresh IP per launch. At runtime resolve_cmd asks the GCP provisioner for the CURRENT external IP. In the gitignored copy, set BENCH_NODE_GCP_INSTANCE/BENCH_NODE_GCP_ZONE and GCP_PROJECT/GCP_ACCOUNT as needed.",
      "tailnet_ip": "",
      "resolve_cmd": "python tools/gcp_bench.py --resolve-ip \"${BENCH_NODE_GCP_INSTANCE:?set BENCH_NODE_GCP_INSTANCE}\" --zone \"${BENCH_NODE_GCP_ZONE:?set BENCH_NODE_GCP_ZONE}\"",
      "magicdns": "node-gcp-gpu-a.example.ts.net",
      "ssh_user": "user",
      "ssh_key": "~/.ssh/id_ed25519",
      "ssh_port": 22,
      "host_key": "",
      "remote_shell": "bash",
      "repo_path": "~/work/fleet",
      "repo_module": "private",
      "toolchain_env": "GOTOOLCHAIN=auto",
      "go_bin": "/usr/local/go/bin",
      "capabilities": ["amd64", "linux", "cuda"],
      "agent_host": false,
      "facts": { "go_version": null, "git_commit": null, "bootstrapped_at": null }
    },
    {
      "name": "dgx-a100",
      "sanitized_name": "node-dgx-a100",
      "tailnet_ip": "203.0.113.40",
      "magicdns": "node-dgx-a100.example.ts.net",
      "ssh_user": "user",
      "ssh_key": "~/.ssh/id_ed25519",
      "ssh_port": 22,
      "host_key": "",
      "remote_shell": "bash",
      "repo_path": "~/work/fleet",
      "repo_module": "private",
      "toolchain_env": "GOTOOLCHAIN=auto",
      "go_bin": "/usr/local/go/bin",
      "capabilities": ["amd64", "linux", "cuda", "a100", "sm_80", "multi-gpu"],
      "agent_host": false,
      "facts": { "go_version": null, "git_commit": null, "bootstrapped_at": null }
    }
  ]
}
