{
  "available": true,
  "status": "adopted",
  "note": "The bootstrap kit is the shortest path from 'a machine exists' to 'the fleet is using it'. It is declared here and executed by scripts/bootstrap-node.sh; the console renders this file so the plan an operator reads is the plan that runs. Every step states how re-running it is safe, how to prove it worked, and how to undo it — a bootstrap step you cannot verify or reverse is a one-way door onto somebody's home network.",
  "summary": {
    "targets": 2,
    "onboarded": 0,
    "fleetSlots": 4,
    "liveSlots": 0,
    "pendingSlots": 4,
    "steps": 15,
    "mutatingSteps": 10,
    "humanActions": 3,
    "openHumanActions": 2,
    "gaps": 5,
    "notes": 2
  },
  "governor": {
    "knobs": {
      "consumer": "jarvisos.supervisor.capacity() on the dispatch node",
      "note": "The supervisor today derives worker fan-out from MemAvailable alone, which is correct for VIN — a 1.9 GiB node where memory is the only thing that runs out. It is wrong for home1, where 16 GiB looks free while the CPU is stalled 38% of the time and 15 GiB is already in swap. These knobs add the two constraints memory cannot see, and the console derives every node's slots the same way so the number on the page is the number the governor should use.",
      "minFreeMb": 300,
      "workerMemoryMb": 350,
      "coresPerWorker": 2,
      "maxParallelDefault": 8,
      "pressure": {
        "cpuSomeAvg300": 25,
        "memFullAvg10": 1,
        "derateDivisor": 2,
        "note": "PSI, not load average: load counts runnable tasks, pressure counts time actually lost waiting. A node can carry load 14 across 12 cores and be fine; a node stalling 38% of every five minutes is already late."
      },
      "envKeys": {
        "minFreeMb": "JARVISOS_MIN_FREE_MB",
        "workerMemoryMb": "JARVISOS_WORKER_MEMORY_MB",
        "maxParallel": "JARVISOS_MAX_PARALLEL",
        "nodeId": "JARVISOS_NODE_ID"
      },
      "envFile": "/etc/jarvisos/config.env"
    },
    "fleetSlots": 4,
    "liveSlots": 0,
    "nodes": [
      {
        "id": "home1",
        "entityId": "home1",
        "slots": 3,
        "binding": "cpu",
        "derated": true,
        "byMemory": 46,
        "byCpu": 6,
        "ceiling": 6,
        "source": "evidence",
        "onboarded": false,
        "capabilities": [],
        "heartbeatAt": null,
        "memoryOnlySlots": 8,
        "reasons": [
          "Bound by cpu: 12 core(s) at 2 per worker.",
          "Halved for pressure — CPU stalled 38.8% of the last five minutes (threshold 25%); memory fully stalled 6.55% of the last ten seconds (threshold 1%)."
        ]
      },
      {
        "id": "vin",
        "entityId": "vin",
        "slots": 1,
        "binding": "memory",
        "derated": false,
        "byMemory": 1,
        "byCpu": 1,
        "ceiling": 2,
        "source": "evidence",
        "onboarded": false,
        "capabilities": [],
        "heartbeatAt": null,
        "memoryOnlySlots": 1,
        "reasons": [
          "Bound by memory: 773 MiB available, 300 MiB held back, 350 MiB per worker."
        ]
      }
    ]
  },
  "targets": [
    {
      "id": "home1",
      "entityId": "home1",
      "role": "primary worker and always-on compute",
      "onboarded": false,
      "live": false,
      "heartbeatAt": null,
      "heartbeatAgeSec": null,
      "endpoint": {
        "mesh": "tailscale",
        "magicDns": "uk-james-home1.tail99ba7.ts.net",
        "meshAddress": "100.96.106.91",
        "adminUser": "root",
        "adminPath": "tailscale-ssh",
        "note": "Reach it by MagicDNS name, never by the 10.10.0.10 literal in the inventory — that address answered nothing in this run."
      },
      "serviceAccount": {
        "user": "jarvis",
        "home": "/home/jarvis"
      },
      "capabilities": [],
      "advertises": [
        "always-on-compute",
        "local-durable",
        "private-mesh",
        "bulk-adjacent"
      ],
      "requiresWorkers": [
        "worker-claude",
        "worker-codex"
      ],
      "missingWorkers": [
        "worker-claude",
        "worker-codex"
      ],
      "ceiling": {
        "maxWorkers": 6,
        "rationale": "12 cores at two cores per worker. Deliberately below what memory alone would allow (46), because this node already runs 83 containers and a legacy service estate that were here first."
      },
      "measured": {
        "at": "2026-08-16",
        "method": "read-only enumeration over Tailscale SSH — see evidence E3 and E4",
        "cores": 12,
        "memTotalMb": 34972,
        "memAvailableMb": 16745,
        "swapUsedMb": 15615,
        "load1": 5.88,
        "cpuPressureSomeAvg300": 38.8,
        "memPressureFullAvg10": 6.55
      },
      "capacity": {
        "slots": 3,
        "byMemory": 46,
        "byCpu": 6,
        "ceiling": 6,
        "binding": "cpu",
        "derated": true,
        "stalls": [
          "CPU stalled 38.8% of the last five minutes (threshold 25%)",
          "memory fully stalled 6.55% of the last ten seconds (threshold 1%)"
        ],
        "reasons": [
          "Bound by cpu: 12 core(s) at 2 per worker.",
          "Halved for pressure — CPU stalled 38.8% of the last five minutes (threshold 25%); memory fully stalled 6.55% of the last ten seconds (threshold 1%)."
        ]
      },
      "capacitySource": "evidence",
      "stages": [
        "preflight",
        "identity",
        "filesystem",
        "tooling",
        "secrets",
        "agent",
        "deploy",
        "advertise"
      ]
    },
    {
      "id": "vin",
      "entityId": "vin",
      "role": "public edge and dispatch node",
      "onboarded": false,
      "live": false,
      "heartbeatAt": null,
      "heartbeatAgeSec": null,
      "endpoint": {
        "mesh": "tailscale",
        "magicDns": "vin.tail99ba7.ts.net",
        "meshAddress": "100.68.255.98",
        "adminUser": "jarvis",
        "adminPath": "local",
        "note": "Already bootstrapped by hand before this kit existed. Listed so the kit can verify the node it runs on and so the fleet capacity figure is complete."
      },
      "serviceAccount": {
        "user": "jarvis",
        "home": "/home/jarvis"
      },
      "capabilities": [],
      "advertises": [
        "public-ingress",
        "public-ipv4",
        "isolated",
        "burst"
      ],
      "requiresWorkers": [
        "worker-claude",
        "worker-codex",
        "worker-gemini"
      ],
      "missingWorkers": [
        "worker-claude",
        "worker-codex",
        "worker-gemini"
      ],
      "ceiling": {
        "maxWorkers": 2,
        "rationale": "1 vCPU and 1.9 GiB of RAM. Memory is the binding constraint here and the existing governor already models it correctly."
      },
      "measured": {
        "at": "2026-08-16",
        "method": "local /proc — see evidence E7",
        "cores": 1,
        "memTotalMb": 1909,
        "memAvailableMb": 773,
        "swapUsedMb": 525,
        "load1": 0.12,
        "cpuPressureSomeAvg300": null,
        "memPressureFullAvg10": null
      },
      "capacity": {
        "slots": 1,
        "byMemory": 1,
        "byCpu": 1,
        "ceiling": 2,
        "binding": "memory",
        "derated": false,
        "stalls": [],
        "reasons": [
          "Bound by memory: 773 MiB available, 300 MiB held back, 350 MiB per worker."
        ]
      },
      "capacitySource": "evidence",
      "stages": [
        "preflight",
        "identity",
        "filesystem",
        "tooling",
        "secrets",
        "agent",
        "deploy",
        "advertise"
      ]
    }
  ],
  "card": {
    "id": "home1-claude-signin",
    "priority": 1,
    "title": "Sign the Claude CLI in as the jarvis service account on home1",
    "why": "Claude Code is the engineering worker. Its sign-in is an interactive OAuth device flow: it prints a URL, waits for a browser, and writes the credential itself. Nothing headless can complete it, and until it is done home1 can be dispatched planning work but no code.",
    "blocks": "All engineering dispatch to home1. The node will onboard, heartbeat and advertise capacity without it — and every coding task will still land on VIN's single slot.",
    "node": "home1",
    "url": "https://claude.ai/login",
    "commands": [
      "ssh root@uk-james-home1.tail99ba7.ts.net",
      "sudo -u jarvis -H npm install -g @anthropic-ai/claude-code   # only if `sudo -u jarvis -H command -v claude` is empty",
      "sudo -u jarvis -H claude",
      "# then, at the prompt: /login  — it prints a URL, open it, approve, come back"
    ],
    "fields": [
      {
        "label": "Account",
        "value": "jkkec23@gmail.com",
        "note": "The same account the CLIs on home1 already use under the 'james' user. Using a different one is fine, but write down which."
      },
      {
        "label": "Verify",
        "value": "sudo -u jarvis -H test -f /home/jarvis/.claude.json && echo signed-in"
      },
      {
        "label": "Then",
        "value": "sudo /opt/jarvisos/bin/jarvisos-bootstrap.sh advertise",
        "note": "Re-advertises immediately; this card clears on the next heartbeat."
      }
    ],
    "clearsWhen": {
      "node": "home1",
      "capability": "worker-claude"
    }
  },
  "actions": [
    {
      "id": "home1-claude-signin",
      "priority": 1,
      "title": "Sign the Claude CLI in as the jarvis service account on home1",
      "humanOnly": true,
      "satisfied": false,
      "node": "home1",
      "capability": "worker-claude",
      "surfaced": true
    },
    {
      "id": "home1-codex-signin",
      "priority": 2,
      "title": "Sign the Codex CLI in as the jarvis service account on home1",
      "humanOnly": true,
      "satisfied": false,
      "node": "home1",
      "capability": "worker-codex",
      "surfaced": false
    },
    {
      "id": "home1-mesh-enrolment",
      "priority": 3,
      "title": "Enrol the node on the tailnet",
      "humanOnly": true,
      "satisfied": true,
      "node": "home1",
      "capability": "private-mesh",
      "surfaced": false
    }
  ],
  "stages": [
    {
      "id": "preflight",
      "name": "Preflight",
      "purpose": "Prove the machine is the kind of machine this kit can bootstrap, and that we are already on the private plane, before anything is written.",
      "steps": [
        {
          "id": "preflight-host",
          "label": "Linux with systemd, and we are root",
          "safety": "safe",
          "mutates": false,
          "idempotency": "read-only",
          "verify": "uname -s is Linux, /run/systemd/system exists, effective uid is 0 (or sudo -n works)",
          "rollback": "Reads only; there is nothing to undo.",
          "reversible": false,
          "secretRefs": []
        },
        {
          "id": "preflight-mesh",
          "label": "Tailscale is installed, logged in and carrying this node",
          "safety": "safe",
          "mutates": false,
          "idempotency": "read-only",
          "verify": "BackendState is Running and Self.DNSName is non-empty",
          "rollback": "Reads only. The kit never runs `tailscale up`, never edits the tailnet ACL and never touches a firewall: joining a private network is an act of enrolment that belongs to a human with the admin console open.",
          "reversible": false,
          "secretRefs": []
        },
        {
          "id": "preflight-console",
          "label": "The status console answers from this node",
          "safety": "safe",
          "mutates": false,
          "idempotency": "read-only",
          "verify": "HTTP 200 with console:\"ok\"",
          "rollback": "Reads only.",
          "reversible": false,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "identity",
      "name": "Service account",
      "purpose": "Give fleet work an identity of its own, so what it can reach is a decision rather than an accident of whose shell it borrowed.",
      "steps": [
        {
          "id": "service-account",
          "label": "Create the jarvis system account",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "exact — an existing jarvis account is left completely untouched, including its shell, home and groups",
          "verify": "getent passwd jarvis returns a line and the home directory exists",
          "rollback": "userdel jarvis (only with --purge, and only if this run created it; the account is otherwise left alone)",
          "reversible": true,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "filesystem",
      "name": "Roots",
      "purpose": "Create the four roots everything else assumes: code, state, logs and transcripts. Nothing here is on the NAS — a worker root that lives on the thing we may be recovering cannot be used to recover it.",
      "steps": [
        {
          "id": "roots-code",
          "label": "Code root /opt/jarvisos and its bin/",
          "safety": "safe",
          "mutates": true,
          "idempotency": "exact — mkdir -p and an explicit chmod; existing content is not touched",
          "verify": "/opt/jarvisos/bin is a directory, group jarvis, mode 0755",
          "rollback": "rmdir /opt/jarvisos/bin /opt/jarvisos if empty; never recursive",
          "reversible": true,
          "secretRefs": []
        },
        {
          "id": "roots-state",
          "label": "State, workspace, report and transcript roots under /var/lib/jarvisos",
          "safety": "safe",
          "mutates": true,
          "idempotency": "exact — mkdir -p; existing task state, workspaces and transcripts are never rewritten",
          "verify": "all five directories exist, owned by jarvis, mode 0750",
          "rollback": "Left in place. These hold task history and worktrees; removing them on rollback would delete work the rollback is trying to protect. --purge removes them only after printing what it is about to lose.",
          "reversible": true,
          "secretRefs": []
        },
        {
          "id": "roots-log",
          "label": "Log root /var/log/jarvisos",
          "safety": "safe",
          "mutates": true,
          "idempotency": "exact",
          "verify": "/var/log/jarvisos exists, owned by jarvis, mode 0750",
          "rollback": "Left in place; logs are evidence about the run that is being rolled back.",
          "reversible": true,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "tooling",
      "name": "Command line tools",
      "purpose": "Prove the tools the runner and the workers need are present. The kit reports what is missing; it installs only from the distribution's own package manager, and only when told to.",
      "steps": [
        {
          "id": "tooling-base",
          "label": "Base CLIs: git, curl, node, python3, rsync, jq",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "exact when everything is present (the common case); otherwise apt-get install of the missing set only, and only with --allow-apt",
          "verify": "every one of the six resolves on PATH",
          "rollback": "Packages installed by this step are listed in the run log and can be removed with apt-get remove. Nothing is removed automatically: another workload may have started depending on them in the meantime.",
          "reversible": true,
          "secretRefs": []
        },
        {
          "id": "tooling-workers",
          "label": "Worker CLIs and their sign-in state for the service account",
          "safety": "safe",
          "mutates": false,
          "idempotency": "read-only",
          "verify": "each binary resolves for the jarvis account and its credential file exists — presence only; the kit never reads the contents",
          "rollback": "Reads only. Credentials are never written, copied or moved by this kit: a token copied between accounts is a token whose custody nobody can describe afterwards.",
          "reversible": false,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "secrets",
      "name": "Secret references",
      "purpose": "Put the node's configuration file in place with the right ownership and the right keys, and leave the values to whoever owns them.",
      "steps": [
        {
          "id": "config-env",
          "label": "/etc/jarvisos/config.env, root:jarvis 0640",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "additive — an existing file is never rewritten; only missing keys are appended, and an existing key keeps its value even when empty",
          "verify": "the file exists, is mode 0640 root:jarvis, and contains every key in paths.configKeys",
          "rollback": "A backup is written to /etc/jarvisos/config.env.bak-bootstrap-<stamp> before any append; rollback restores it. A file this run created from nothing is removed.",
          "reversible": true,
          "secretRefs": []
        },
        {
          "id": "ingest-token",
          "label": "Install or enroll this node's scoped ingest identity",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "exact — an identical credential is a no-op; a different one is refused unless --force-token",
          "verify": "POST /api/v1/heartbeat from this node returns 202, not 401",
          "rollback": "The pre-change credential backup is restored. Revocation in the console registry is a separate operator action.",
          "reversible": true,
          "secretRefs": [
            "node credential"
          ]
        }
      ]
    },
    {
      "id": "agent",
      "name": "Node agent",
      "purpose": "Install the heartbeat and capability agent, so the node stops being something a human has to remember and starts being something the fleet can see.",
      "steps": [
        {
          "id": "agent-install",
          "label": "Copy the kit to /opt/jarvisos/bin/jarvisos-bootstrap.sh",
          "safety": "safe",
          "mutates": true,
          "idempotency": "exact — byte-identical copy is skipped; a differing copy is replaced and the old one kept as .prev",
          "verify": "/opt/jarvisos/bin/jarvisos-bootstrap.sh --version matches the kit version",
          "rollback": "Remove the file, restoring .prev if this run replaced one.",
          "reversible": true,
          "secretRefs": []
        },
        {
          "id": "agent-unit",
          "label": "systemd unit and timer jarvisos-node-agent",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "exact — the unit text is generated deterministically and written only when it differs; the timer is enabled only if not already enabled",
          "verify": "systemctl is-enabled jarvisos-node-agent.timer is 'enabled' and one manual `systemctl start jarvisos-node-agent.service` exits 0",
          "rollback": "systemctl disable --now jarvisos-node-agent.timer, then remove both unit files and daemon-reload.",
          "reversible": true,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "deploy",
      "name": "Host-substrate deployment runner",
      "purpose": "Provide a break-glass deployment path only for workloads explicitly classified as host substrate. Ordinary applications use the Coolify templates in config/deploy-templates.json.",
      "steps": [
        {
          "id": "deploy-runner",
          "label": "Exception-gated template unit jarvisos-deploy@.service",
          "safety": "disruptive",
          "mutates": true,
          "idempotency": "exact — deterministic unit text, written only when it differs",
          "verify": "systemctl cat jarvisos-deploy@.service resolves and a dry-run instance exits 0",
          "rollback": "Remove the template unit and daemon-reload. Instances are transient and hold no state.",
          "reversible": true,
          "secretRefs": []
        }
      ]
    },
    {
      "id": "advertise",
      "name": "Advertise capacity",
      "purpose": "Tell the fleet what this node can actually take, measured rather than declared, within seconds of it being connected.",
      "steps": [
        {
          "id": "advertise-capacity",
          "label": "Measure and POST the capability heartbeat",
          "safety": "safe",
          "mutates": false,
          "idempotency": "exact — a heartbeat is an observation; sending it twice reports the same node twice",
          "verify": "the console returns 202 and the node appears with a fresh heartbeat and a non-empty capability list",
          "rollback": "Nothing is written on either side beyond an in-memory observation that expires on its own. Stopping the timer stops the advertisement.",
          "reversible": false,
          "secretRefs": [
            "node credential"
          ]
        }
      ]
    }
  ],
  "paths": [
    {
      "path": "/opt/jarvisos",
      "purpose": "code root",
      "owner": "root:jarvis",
      "mode": "0755",
      "backupTier": "t0-crown-jewels"
    },
    {
      "path": "/opt/jarvisos/bin/jarvisos-bootstrap.sh",
      "purpose": "installer and node agent, one file",
      "owner": "root:jarvis",
      "mode": "0755",
      "backupTier": "t0-crown-jewels"
    },
    {
      "path": "/var/lib/jarvisos/state",
      "purpose": "task store and locks",
      "owner": "jarvis:jarvis",
      "mode": "0750",
      "backupTier": "t0-crown-jewels"
    },
    {
      "path": "/var/lib/jarvisos/workspaces",
      "purpose": "repository and worktree root, one directory per project",
      "owner": "jarvis:jarvis",
      "mode": "0750",
      "backupTier": "t1-reproducible"
    },
    {
      "path": "/var/lib/jarvisos/reports",
      "purpose": "worker run reports",
      "owner": "jarvis:jarvis",
      "mode": "0750",
      "backupTier": "t1-reproducible"
    },
    {
      "path": "/var/lib/jarvisos/transcripts",
      "purpose": "worker transcripts; may contain quoted source and must never leave the node unredacted",
      "owner": "jarvis:jarvis",
      "mode": "0750",
      "backupTier": "t1-reproducible"
    },
    {
      "path": "/var/log/jarvisos",
      "purpose": "agent and deployment logs",
      "owner": "jarvis:jarvis",
      "mode": "0750",
      "backupTier": "none"
    },
    {
      "path": "/etc/jarvisos/config.env",
      "purpose": "node configuration and non-secret settings",
      "owner": "root:jarvis",
      "mode": "0640",
      "backupTier": "t0-crown-jewels"
    },
    {
      "path": "/etc/jarvisos/node-credential",
      "purpose": "this node's scoped ingest credential",
      "owner": "root:jarvis",
      "mode": "0640",
      "backupTier": "t0-crown-jewels"
    }
  ],
  "secrets": [
    {
      "name": "node credential",
      "location": "/etc/jarvisos/node-credential",
      "custody": "node",
      "sensitivity": "operational",
      "suppliedBy": "offline issue/rotation, or short-lived bootstrap enrollment"
    },
    {
      "name": "tailscaled node state",
      "location": "/var/lib/tailscale/tailscaled.state",
      "custody": "node",
      "sensitivity": "operational",
      "suppliedBy": "tailscale up, run by a human at enrolment"
    },
    {
      "name": "worker CLI credentials",
      "location": "/home/jarvis/.claude.json, /home/jarvis/.codex/auth.json, /home/jarvis/.gemini/",
      "custody": "node",
      "sensitivity": "identity",
      "suppliedBy": "interactive OAuth sign-in by a human, as the service account"
    }
  ],
  "evidence": [
    {
      "id": "E1",
      "observedAt": "2026-08-16",
      "method": "tailscale status --json and tailscale ping, from VIN",
      "subject": "home1",
      "finding": "home1 is on the tailnet and reachable: uk-james-home1.tail99ba7.ts.net / 100.96.106.91, tag:homelab-jc, online, 33-39 ms via DERP(lhr) with a direct path also established.",
      "closes": [
        "G2"
      ]
    },
    {
      "id": "E2",
      "observedAt": "2026-08-16",
      "method": "ssh -o BatchMode=yes, from VIN",
      "subject": "home1",
      "finding": "Administrative access already exists and needs no new credential: home1 runs Tailscale SSH, and the tailnet policy permits root and james from VIN. It denies jarvis and ubuntu, so the service account will not be reachable over SSH — which is correct, and is why the agent is a timer that reports outward rather than a door that is knocked on.",
      "closes": []
    },
    {
      "id": "E3",
      "observedAt": "2026-08-16",
      "method": "read-only enumeration over Tailscale SSH",
      "subject": "home1",
      "finding": "Ubuntu 26.04 LTS, kernel 7.0.0-29, AMD Ryzen 5 PRO 5650U, 12 cores. MemTotal 34.2 GiB, MemAvailable 16.4 GiB, swap 24 GiB with 15.2 GiB already in use. Root filesystem 3.6 TB with 2.9 TB free; the NAS is mounted at /mnt/nas/homelab (63 TB, 87% used). Load average 5.88 / 14.65 / 18.94 across 12 cores, 83 running containers.",
      "closes": [
        "G1"
      ]
    },
    {
      "id": "E4",
      "observedAt": "2026-08-16",
      "method": "/proc/pressure, over Tailscale SSH",
      "subject": "home1",
      "finding": "CPU pressure some avg300 = 38.80%, memory pressure full avg10 = 6.55%. The node is not idle: it is losing a measurable share of every five minutes to contention, while MemAvailable still reads 16 GiB. This is the number that makes memory-only admission control wrong here.",
      "closes": []
    },
    {
      "id": "E5",
      "observedAt": "2026-08-16",
      "method": "read-only enumeration over Tailscale SSH",
      "subject": "home1",
      "finding": "Present: node v22.22.2, npm 10.9.7, git 2.53.0, curl, jq 1.8.1, python3 3.14.4, docker 29.4.1, rsync, tailscale 1.102.2, gemini 0.47.0. Absent: no jarvis account, no /opt/jarvisos, no /etc/jarvisos, no /var/lib/jarvisos, no /var/log/jarvisos, no JarvisOS unit. claude and codex exist and are signed in only for the human user 'james' under /home/james/.local/bin.",
      "closes": []
    },
    {
      "id": "E6",
      "observedAt": "2026-08-16",
      "method": "ping, from VIN",
      "subject": "home1",
      "finding": "10.10.0.10 — the address the inventory declares for home1 — answered nothing. Every reachable path to this node in this run was the MagicDNS name.",
      "closes": []
    },
    {
      "id": "E7",
      "observedAt": "2026-08-16",
      "method": "systemctl and /proc, on VIN",
      "subject": "vin",
      "finding": "1 vCPU, MemTotal 1.9 GiB, MemAvailable 773 MiB, 525 MiB of swap in use, load 0.12. jarvisos-dispatch.timer is active and the supervisor derives its fan-out from MemAvailable alone, which yields exactly one worker slot here today.",
      "closes": []
    },
    {
      "id": "E8",
      "observedAt": "2026-08-16",
      "method": "read-only enumeration over Tailscale SSH",
      "subject": "home1",
      "finding": "home1 carries a pre-existing estate this kit did not create and does not manage: jarvis-bridge, jarvis-canary, jarvis-fleet, jarvis-opencode-sync, jarvis-proxy-autofix and jarvis-proxy-backup units, plus 83 containers. The kit manages the names jarvisos-node-agent.service, jarvisos-node-agent.timer and jarvisos-deploy@.service, and nothing else.",
      "closes": []
    }
  ],
  "findings": [
    {
      "code": "target-not-onboarded",
      "severity": "gap",
      "title": "home1 has not reported in",
      "detail": "No live heartbeat with capabilities from \"home1\". Its 3 worker slot(s) are a measurement from 2026-08-16, not a signal. Run scripts/bootstrap-node.sh install on it, then advertise.",
      "targetId": "home1",
      "actionId": null
    },
    {
      "code": "capacity-from-evidence",
      "severity": "note",
      "title": "home1 capacity is from evidence, not a live agent",
      "detail": "Derived from read-only enumeration over Tailscale SSH — see evidence E3 and E4 on 2026-08-16. It is the best number available and it will not update on its own.",
      "targetId": "home1",
      "actionId": null
    },
    {
      "code": "governor-overadmits",
      "severity": "gap",
      "title": "Memory-only admission would over-dispatch home1",
      "detail": "The supervisor's current formula gives 8 worker slot(s) here; deriving from cores and pressure as well gives 3. Bound by cpu: 12 core(s) at 2 per worker. Halved for pressure — CPU stalled 38.8% of the last five minutes (threshold 25%); memory fully stalled 6.55% of the last ten seconds (threshold 1%). Set JARVISOS_MAX_PARALLEL=3 in /etc/jarvisos/config.env on home1 until the supervisor reads this endpoint.",
      "targetId": "home1",
      "actionId": null
    },
    {
      "code": "target-not-onboarded",
      "severity": "gap",
      "title": "vin has not reported in",
      "detail": "No live heartbeat with capabilities from \"vin\". Its 1 worker slot(s) are a measurement from 2026-08-16, not a signal. Run scripts/bootstrap-node.sh install on it, then advertise.",
      "targetId": "vin",
      "actionId": null
    },
    {
      "code": "capacity-from-evidence",
      "severity": "note",
      "title": "vin capacity is from evidence, not a live agent",
      "detail": "Derived from local /proc — see evidence E7 on 2026-08-16. It is the best number available and it will not update on its own.",
      "targetId": "vin",
      "actionId": null
    },
    {
      "code": "human-action-open",
      "severity": "gap",
      "title": "Sign the Claude CLI in as the jarvis service account on home1",
      "detail": "Claude Code is the engineering worker. Its sign-in is an interactive OAuth device flow: it prints a URL, waits for a browser, and writes the credential itself. Nothing headless can complete it, and until it is done home1 can be dispatched planning work but no code. Blocks: All engineering dispatch to home1. The node will onboard, heartbeat and advertise capacity without it — and every coding task will still land on VIN's single slot.",
      "targetId": "home1",
      "actionId": "home1-claude-signin"
    },
    {
      "code": "human-action-open",
      "severity": "gap",
      "title": "Sign the Codex CLI in as the jarvis service account on home1",
      "detail": "Codex is the reviewer in the dispatch model, so without it home1's work is written and merged by the same worker. Its sign-in is the same interactive OAuth flow and equally cannot be automated. Blocks: Independent review of work produced on home1.",
      "targetId": "home1",
      "actionId": "home1-codex-signin"
    }
  ]
}