{
  "available": true,
  "status": "adopted",
  "note": "Everything JarvisOS cannot finish on its own, in one place, with the condition that closes it. Nothing here is a task: if automation could do it, it belongs in the remediation or deployment register. Every entry was already stated as a gap somewhere else in this repo — this file is what makes those gaps addressable to a person instead of merely recorded.",
  "generatedAt": "2026-08-17T04:10:00Z",
  "reviewOn": "2026-09-14",
  "policy": {
    "principle": "A requirement is a thing only a human can supply. Node access, a provider credential, an approval, a euro, or somebody standing next to hardware. If JarvisOS can do it, it is not a requirement, it is work.",
    "closure": "Closure is derived on every render, never written down. A requirement carries the condition — a file becoming readable, a node advertising a capability, an entity appearing in the inventory — and stops being asked for the moment that condition holds. Where nothing can check, the register says why, and closure is a named person's attestation on the record rather than a tick.",
    "custody": "No credential value appears in this file, in the API payload, in a page, in a log line or in a task report. A credential requirement carries a name, an absolute path outside this repository, and a rotation interval. Presence is established with accessSync — the file is asked whether it is readable, never what it contains — and a declared path inside this working tree is refused at load.",
    "surfacing": "Only asks with nothing human in front of them are surfaced. A requirement waiting on another requirement is listed as waiting, not asked for: a list that mixes the two teaches its reader to skim both.",
    "maxOpenAsks": 6,
    "secretsRoot": "/var/lib/jarvisos/secrets"
  },
  "summary": {
    "total": 14,
    "imported": 3,
    "open": 12,
    "shortlisted": 6,
    "demoted": 6,
    "blocking": 2,
    "waiting": 2,
    "satisfied": 0,
    "lapsed": 1,
    "unverifiable": 1,
    "overdue": 0,
    "autoClosable": 7,
    "attestOnly": 6,
    "byCategory": {
      "credential": 2,
      "node-access": 4,
      "physical": 1,
      "approval": 2,
      "billing": 1,
      "information": 2
    },
    "credentialsPresent": 0,
    "credentialsTotal": 4,
    "oldestAskDays": 120,
    "fatal": 0,
    "crit": 2,
    "warn": 8
  },
  "next": {
    "id": "coolify-read-token",
    "source": "register",
    "title": "Mint the read-only Coolify API token",
    "category": "credential",
    "urgency": "blocking",
    "owner": "owner",
    "state": "open",
    "actionable": true,
    "why": "Coolify exposes no route for minting or revoking API tokens (gap coolify-no-token-api), so every token in this estate is created by hand in the UI. Without the read token the adapter cannot list servers, resources or deployment status, which means `jarvisos-estate.mjs reconcile` cannot run and the 26 live Coolify resources stay unclaimed by the inventory.",
    "blocks": "Estate reconciliation, the catalogue's Coolify source, deployment status for every application, and gap estate-unclaimed, which is currently the largest crit in the repository.",
    "ask": [
      "Open http://uk-james-home1.tail99ba7.ts.net:8000 from the tailnet and sign in.",
      "Keys & Tokens → API tokens → Create, name it jarvisos-read, and grant read only. Do not grant read:sensitive: the adapter is built to receive redacted env values and a token that can read them is a token that can leak them.",
      "Write it straight to the node, without pasting it into a chat, a commit or a task: install -m 0600 -o jarvis -g jarvis /dev/stdin /var/lib/jarvisos/secrets/coolify-read.token, then paste and Ctrl-D.",
      "Confirm with: sudo -u jarvis test -r /var/lib/jarvisos/secrets/coolify-read.token && echo present"
    ],
    "commands": [],
    "fields": [],
    "url": "http://uk-james-home1.tail99ba7.ts.net:8000",
    "nodeId": "home1",
    "site": null,
    "approver": null,
    "question": null,
    "cost": null,
    "blocksEntities": [
      {
        "id": "home1",
        "name": "Home1",
        "tier": "critical"
      }
    ],
    "needs": [],
    "unmetNeeds": [],
    "unblocks": 1,
    "requestedOn": "2026-08-16T18:00:00Z",
    "waitingDays": 1,
    "dueBy": null,
    "overdue": false,
    "closesWhen": {
      "kind": "secret-present",
      "on": "console",
      "location": "/var/lib/jarvisos/secrets/coolify-read.token",
      "envName": "COOLIFY_TOKEN_READ",
      "node": null,
      "capability": null,
      "entityId": null,
      "withinSec": null,
      "statement": null,
      "whyNotAutomatic": null,
      "provenBy": null
    },
    "closedBy": null,
    "reason": "/var/lib/jarvisos/secrets/coolify-read.token is not readable and COOLIFY_TOKEN_READ is not set.",
    "verifiable": true,
    "byAttestation": false,
    "provenBy": null,
    "revalidates": null,
    "attested": null,
    "secretRef": {
      "name": "COOLIFY_TOKEN_READ",
      "location": "/var/lib/jarvisos/secrets/coolify-read.token",
      "custody": "Owned by the jarvis service account on the node that runs the adapter, mode 0600, never copied into this repository and never printed by any runner. config/coolify.json declares the same location; this register is what asks for it.",
      "envName": "COOLIFY_TOKEN_READ",
      "mode": "0600",
      "rotationDays": 365,
      "issuedBy": "the Coolify UI on home1 — there is no API for it",
      "scope": "read, without read:sensitive"
    }
  },
  "asks": [
    {
      "id": "coolify-read-token",
      "source": "register",
      "title": "Mint the read-only Coolify API token",
      "category": "credential",
      "urgency": "blocking",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "Coolify exposes no route for minting or revoking API tokens (gap coolify-no-token-api), so every token in this estate is created by hand in the UI. Without the read token the adapter cannot list servers, resources or deployment status, which means `jarvisos-estate.mjs reconcile` cannot run and the 26 live Coolify resources stay unclaimed by the inventory.",
      "blocks": "Estate reconciliation, the catalogue's Coolify source, deployment status for every application, and gap estate-unclaimed, which is currently the largest crit in the repository.",
      "ask": [
        "Open http://uk-james-home1.tail99ba7.ts.net:8000 from the tailnet and sign in.",
        "Keys & Tokens → API tokens → Create, name it jarvisos-read, and grant read only. Do not grant read:sensitive: the adapter is built to receive redacted env values and a token that can read them is a token that can leak them.",
        "Write it straight to the node, without pasting it into a chat, a commit or a task: install -m 0600 -o jarvis -g jarvis /dev/stdin /var/lib/jarvisos/secrets/coolify-read.token, then paste and Ctrl-D.",
        "Confirm with: sudo -u jarvis test -r /var/lib/jarvisos/secrets/coolify-read.token && echo present"
      ],
      "commands": [],
      "fields": [],
      "url": "http://uk-james-home1.tail99ba7.ts.net:8000",
      "nodeId": "home1",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "home1",
          "name": "Home1",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 1,
      "requestedOn": "2026-08-16T18:00:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "secret-present",
        "on": "console",
        "location": "/var/lib/jarvisos/secrets/coolify-read.token",
        "envName": "COOLIFY_TOKEN_READ",
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "/var/lib/jarvisos/secrets/coolify-read.token is not readable and COOLIFY_TOKEN_READ is not set.",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": {
        "name": "COOLIFY_TOKEN_READ",
        "location": "/var/lib/jarvisos/secrets/coolify-read.token",
        "custody": "Owned by the jarvis service account on the node that runs the adapter, mode 0600, never copied into this repository and never printed by any runner. config/coolify.json declares the same location; this register is what asks for it.",
        "envName": "COOLIFY_TOKEN_READ",
        "mode": "0600",
        "rotationDays": 365,
        "issuedBy": "the Coolify UI on home1 — there is no API for it",
        "scope": "read, without read:sensitive"
      }
    },
    {
      "id": "home1-claude-signin",
      "source": "bootstrap",
      "title": "Sign the Claude CLI in as the jarvis service account on home1",
      "category": "node-access",
      "urgency": "blocking",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "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.",
      "ask": [
        "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"
      ],
      "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."
        }
      ],
      "url": "https://claude.ai/login",
      "nodeId": "home1",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "home1",
          "name": "Home1",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": null,
      "waitingDays": null,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "node-capability",
        "on": "console",
        "location": null,
        "envName": null,
        "node": "home1",
        "capability": "worker-claude",
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "home1 has never reported, so it cannot yet advertise \"worker-claude\".",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "offsite-restore-drill",
      "source": "register",
      "title": "Run the quarterly off-site restore drill, in person",
      "category": "physical",
      "urgency": "soon",
      "owner": "owner",
      "state": "lapsed",
      "actionable": true,
      "why": "gwscli-s3-offsite is the only copy that survives loss of the home rack, and its declared verification is a quarterly restore drill. The drill needs the passphrase, which by policy exists on no node (gap restore-drill-passphrase), so no automation can ever perform it. An untested off-site copy is a belief, not a backup.",
      "blocks": "Any claim that the estate survives losing the home rack. The data-resilience register's restore-drill evidence for gwscli-s3-offsite goes stale without it.",
      "ask": [
        "Fetch the passphrase from wherever it is held offline. Do not put it on a node, including this one.",
        "Restore a known object from gwscli-s3-offsite to scratch space and verify its contents against the on-site copy.",
        "Record the date and what was restored in the attestation below, and destroy the scratch copy.",
        "If the restore fails, this stops being a requirement and becomes an incident."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": null,
      "site": "wherever the off-site backup passphrase is held — by policy it is on no node in this estate",
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "gwscli-s3-offsite",
          "name": "Offsite backup replica",
          "tier": "critical"
        },
        {
          "id": "nas-backups",
          "name": "NAS backup dataset",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": "2026-04-20T00:00:00Z",
      "waitingDays": 120,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "An object was restored from the off-site copy end to end and verified against the on-site original.",
        "whyNotAutomatic": "The passphrase is deliberately on no machine in this estate. That is the control that makes the off-site copy a disaster-recovery copy rather than a second thing an attacker on this network already has — and it is exactly what makes the drill unautomatable.",
        "provenBy": null
      },
      "closedBy": "attested by owner on 2026-04-20",
      "reason": "Closed on a person's word: owner attested \"Restored the 2026-04-19 nas-backups object from the off-site copy and matched its checksum against the on-site original.\" on 2026-04-20.",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": {
        "dueAt": "2026-07-19T00:00:00.000Z",
        "daysLeft": -31,
        "why": "The inventory declares gwscli-s3-offsite's verification as a quarterly restore drill. A drill that happened once is a drill that proved the backup worked in April."
      },
      "attested": {
        "at": "2026-04-20T00:00:00Z",
        "by": "owner",
        "statement": "Restored the 2026-04-19 nas-backups object from the off-site copy and matched its checksum against the on-site original."
      },
      "secretRef": null
    },
    {
      "id": "edge-single-point-decision",
      "source": "register",
      "title": "Decide whether the estate gets a second public edge",
      "category": "approval",
      "urgency": "soon",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "Gaps deployment-single-edge and estate-lifecycle-single-point say the same thing from two directions: one edge, one control plane, and every HA property in three registers is a plan rather than a fact until that changes. The auth register makes it sharper — one forward-auth upstream means an auth container failure reads as the whole estate being down.",
      "blocks": "The multi-node deployment register's edge layer, the second forward-auth upstream in the identity register, and any honest claim that this estate survives losing VIN.",
      "ask": [
        "Read docs/MULTI-NODE-DEPLOYMENT.md 'who owns the request path' and docs/AUTH-HA.md 'the capability × scenario matrix'.",
        "Choose one: accept the single edge and record it as an accepted risk with a review date; or fund a second edge and say which node terminates it.",
        "Record the decision in the attestation below. The Caddyfile is managed configuration and is not edited by JarvisOS in either case — the second upstream is an owner action by design, so that it is a decision rather than an omission."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": null,
      "site": null,
      "approver": "owner",
      "question": "Does the estate accept that Caddy on VIN is the single public edge — every published hostname lost with one node — or is a second edge worth its recurring cost and its configuration duplication?",
      "cost": null,
      "blocksEntities": [
        {
          "id": "caddy",
          "name": "Caddy (reverse proxy)",
          "tier": "critical"
        },
        {
          "id": "jarvisos-auth",
          "name": "JarvisOS Auth (SSO)",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": "2026-08-16T22:39:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "The single-edge risk has been either accepted with a review date or funded with a named second edge.",
        "whyNotAutomatic": "This is a question about what the owner is willing to pay for and lose. Nothing on this node can observe a preference, and inferring one from the current topology would just be reading the status quo back as consent.",
        "provenBy": null
      },
      "closedBy": null,
      "reason": "No machine can check this — This is a question about what the owner is willing to pay for and lose. Nothing on this node can observe a preference, and inferring one from the current topology would just be reading the status quo back as consent. It closes when somebody attests: \"The single-edge risk has been either accepted with a review date or funded with a named second edge.\"",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "nas-service-account",
      "source": "register",
      "title": "Create a jarvis service account on the NAS and authorise the control-plane key",
      "category": "node-access",
      "urgency": "soon",
      "owner": "owner",
      "state": "unverifiable",
      "actionable": true,
      "why": "The storage register places every byte on a tier and says when that tier runs out, but the numbers behind it are derived from what the register places, not from the pool. The measured answers are `df` and `du` on a machine this process has no account on. A NAS with no service account is a NAS whose occupancy is an estimate that will be believed.",
      "blocks": "Measured storage occupancy for nas-backups and nas-media, and therefore any honest 'days until full' figure. Also every future remediation that would touch the NAS, which currently cannot be attempted at all.",
      "ask": [
        "On the NAS admin UI, create a local user `jarvis` with no shell login beyond SSH and no access to the media dataset.",
        "Append the control-plane public key at /var/lib/jarvisos/secrets/id_jarvis_nas.pub to /home/jarvis/.ssh/authorized_keys on the NAS, mode 0600.",
        "Restrict it: prefix the key line with command=\"/usr/bin/df -P; /usr/bin/du -sx\" only if the NAS OS supports it; otherwise record here that the account is unrestricted so the risk is on the page rather than in somebody's memory.",
        "Then run the proving command below from VIN."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": "nas",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "nas",
          "name": "NAS",
          "tier": "critical"
        },
        {
          "id": "nas-backups",
          "name": "NAS backup dataset",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": "2026-08-17T00:00:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "file-present",
        "on": "nas",
        "location": "/home/jarvis/.ssh/authorized_keys",
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": "ssh -o BatchMode=yes jarvis@10.10.0.20 'df -P /' && echo reachable"
      },
      "closedBy": null,
      "reason": "Runs on nas, which this console cannot see. Proven by: ssh -o BatchMode=yes jarvis@10.10.0.20 'df -P /' && echo reachable",
      "verifiable": false,
      "byAttestation": false,
      "provenBy": "ssh -o BatchMode=yes jarvis@10.10.0.20 'df -P /' && echo reachable",
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "home1-codex-signin",
      "source": "bootstrap",
      "title": "Sign the Codex CLI in as the jarvis service account on home1",
      "category": "node-access",
      "urgency": "soon",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "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.",
      "ask": [
        "ssh root@uk-james-home1.tail99ba7.ts.net",
        "sudo -u jarvis -H npm install -g @openai/codex   # only if `sudo -u jarvis -H command -v codex` is empty",
        "sudo -u jarvis -H codex login"
      ],
      "commands": [
        "ssh root@uk-james-home1.tail99ba7.ts.net",
        "sudo -u jarvis -H npm install -g @openai/codex   # only if `sudo -u jarvis -H command -v codex` is empty",
        "sudo -u jarvis -H codex login"
      ],
      "fields": [
        {
          "label": "Account",
          "value": "the ChatGPT account already used by codex on home1 under 'james'"
        },
        {
          "label": "Verify",
          "value": "sudo -u jarvis -H test -f /home/jarvis/.codex/auth.json && echo signed-in"
        },
        {
          "label": "Then",
          "value": "sudo /opt/jarvisos/bin/jarvisos-bootstrap.sh advertise"
        }
      ],
      "url": "https://auth.openai.com/",
      "nodeId": "home1",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "home1",
          "name": "Home1",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": null,
      "waitingDays": null,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "node-capability",
        "on": "console",
        "location": null,
        "envName": null,
        "node": "home1",
        "capability": "worker-codex",
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "home1 has never reported, so it cannot yet advertise \"worker-codex\".",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "home1-mesh-enrolment",
      "source": "bootstrap",
      "title": "Enrol the node on the tailnet",
      "category": "node-access",
      "urgency": "soon",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "Joining a private network is an act of enrolment. It needs an auth key or an interactive login from the admin console, and this kit deliberately has neither: an installer that can add machines to your private network is an installer that can add anyone's.",
      "blocks": "Everything. Nothing else in the kit runs without the private plane.",
      "ask": [
        "# on the new node, as root:",
        "tailscale up --ssh --hostname <node-id>",
        "# then approve the machine in the admin console if the tailnet requires it"
      ],
      "commands": [
        "# on the new node, as root:",
        "tailscale up --ssh --hostname <node-id>",
        "# then approve the machine in the admin console if the tailnet requires it"
      ],
      "fields": [
        {
          "label": "Tailnet",
          "value": "tail99ba7.ts.net"
        },
        {
          "label": "Tag",
          "value": "tag:homelab-jc",
          "note": "The tag home1 already carries. Tags decide what the ACL lets the node reach."
        },
        {
          "label": "Verify",
          "value": "tailscale status --json | grep BackendState"
        }
      ],
      "url": "https://login.tailscale.com/admin/machines",
      "nodeId": "home1",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "home1",
          "name": "Home1",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": null,
      "waitingDays": null,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "node-capability",
        "on": "console",
        "location": null,
        "envName": null,
        "node": "home1",
        "capability": "private-mesh",
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "home1 has never reported, so it cannot yet advertise \"private-mesh\".",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "burst-spend-approval",
      "source": "register",
      "title": "Approve the elastic-compute spending ceiling",
      "category": "billing",
      "urgency": "soon",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "The burst register is adopted with zero recurring cost as the default and the rollback plan, and no billable operation is executable: the runner prints the provider request and refuses it. What it does not have is a person's agreement to the ceiling, and every grant in that register is issued against a budget this approval sets.",
      "blocks": "Every billable burst operation, and therefore the write-scoped Hetzner token that would carry one out.",
      "ask": [
        "Read the comparison at /burst — including the rejected options, which are kept precisely so the decision is visible.",
        "Confirm or change the two numbers: a 10 EUR monthly ceiling and a 2 EUR per-run ceiling, against roughly 34 hours of transient work a month.",
        "Confirm the break-even reading: above it, the honest recommendation is the permanent machine and this approval should be refused rather than raised."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": null,
      "site": null,
      "approver": null,
      "question": null,
      "cost": {
        "oneOffEur": 0,
        "monthlyEur": 0,
        "ceilingEur": 10,
        "basis": "config/burst.json policy.monthlyCeilingEur and runCeilingEur. Recurring is zero because the adopted targets are the existing VPS and free home1 capacity; the ceiling bounds the elastic options held on standby. Every price in that register is unverified — see requirement vin-invoice-figure."
      },
      "blocksEntities": [],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 1,
      "requestedOn": "2026-08-17T03:31:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "The owner has agreed the monthly and per-run burst ceilings, or set different ones.",
        "whyNotAutomatic": "No machine may consent to spending somebody else's money. This node is deliberately built so that it cannot: it holds no billing credential and the runner refuses every billable verb without a signed grant.",
        "provenBy": null
      },
      "closedBy": null,
      "reason": "No machine can check this — No machine may consent to spending somebody else's money. This node is deliberately built so that it cannot: it holds no billing credential and the runner refuses every billable verb without a signed grant. It closes when somebody attests: \"The owner has agreed the monthly and per-run burst ceilings, or set different ones.\"",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "tailnet-acl-burst",
      "source": "register",
      "title": "Write the tailnet ACL for tag:burst",
      "category": "approval",
      "urgency": "soon",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "Gap tailnet-acl-for-burst is a crit: the burst plan attaches rented nodes to the private mesh and no ACL for tag:burst exists. A burst node with the default policy is a machine somebody else's hypervisor is hosting, sitting on the same flat network as the Postgres primary.",
      "blocks": "Attaching any burst node to the mesh. It is a prerequisite of the write-scoped provider token, because the first thing that token would do is create a node that joins the tailnet.",
      "ask": [
        "In the tailscale admin console, add a tag:burst owned by the account that runs the tailnet.",
        "Write the ACL narrow: tag:burst may reach the control plane's ingest port and nothing else; nothing on the tailnet may initiate to tag:burst.",
        "Set an auth key for tag:burst that is ephemeral and pre-authorised, so a destroyed node leaves no stale machine behind.",
        "Record here that it exists. The key itself goes to /var/lib/jarvisos/secrets, never into this file."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": null,
      "site": null,
      "approver": "owner (tailnet admin)",
      "question": "What may a rented, short-lived burst node reach on the tailnet, and what may reach it?",
      "cost": null,
      "blocksEntities": [],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 1,
      "requestedOn": "2026-08-17T03:31:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "A tag:burst exists with a narrow ACL and an ephemeral, pre-authorised auth key.",
        "whyNotAutomatic": "This node holds no tailnet admin credential and by policy will not be given one: the ability to rewrite the ACL of the private plane is the ability to grant itself access to everything on it.",
        "provenBy": null
      },
      "closedBy": null,
      "reason": "No machine can check this — This node holds no tailnet admin credential and by policy will not be given one: the ability to rewrite the ACL of the private plane is the ability to grant itself access to everything on it. It closes when somebody attests: \"A tag:burst exists with a narrow ACL and an ephemeral, pre-authorised auth key.\"",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "nas-stable-address",
      "source": "register",
      "title": "Give the NAS a stable name instead of a LAN literal",
      "category": "information",
      "urgency": "whenever",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "The NAS is registered at 10.10.0.20. Home1 was registered at 10.10.0.10 until 2026-08-16, and that literal answered nothing from VIN — bootstrap evidence E6. The same failure is sitting in the register unexercised, and it will be discovered during an incident rather than before one.",
      "blocks": "Nothing today, because nothing on VIN currently talks to the NAS. It blocks trusting the NAS address the first time something does — which is the moment when finding out is most expensive.",
      "ask": [
        "Decide which it is: enrol the NAS on the tailnet and use its MagicDNS name, or give it a DHCP reservation and an internal-DNS record.",
        "Record the answer here, then update the `address` and `addressNote` of the `nas` entity in config/inventory.json the way home1's was updated.",
        "Re-run `npm run check`: the inventory validator is what proves the change is coherent."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": "nas",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "nas",
          "name": "NAS",
          "tier": "critical"
        }
      ],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": "2026-08-16T22:39:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "The NAS has a stable name, the inventory records it, and the note says what it was before.",
        "whyNotAutomatic": "The question is which addressing scheme the estate should use, and no reading of the current one answers it — a literal that resolves today is exactly the state being complained about.",
        "provenBy": null
      },
      "closedBy": null,
      "reason": "No machine can check this — The question is which addressing scheme the estate should use, and no reading of the current one answers it — a literal that resolves today is exactly the state being complained about. It closes when somebody attests: \"The NAS has a stable name, the inventory records it, and the note says what it was before.\"",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    },
    {
      "id": "hetzner-read-token",
      "source": "register",
      "title": "Mint the read-only Hetzner token for the burst-only project",
      "category": "credential",
      "urgency": "whenever",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "The read credential is what turns the burst register's arithmetic from committed numbers into verified ones: it reads the provider's own price list, the server types and this month's usage. It is free, it cannot change anything, and the provider itself refuses it on every mutating route — which is why it is the credential every automatic operation uses.",
      "blocks": "Price verification (gap prices-unverified) and usage-against-ceiling reporting. Nothing spends money as a result of this token existing.",
      "ask": [
        "Create a separate Hetzner Cloud project for burst. Do not use the project VIN lives in: the API token scope is a whole project, so a burst token in VIN's project is a token over VIN.",
        "Security → API tokens → Generate, permission Read.",
        "install -m 0600 -o jarvis -g jarvis /dev/stdin /var/lib/jarvisos/secrets/hetzner-read.token",
        "Confirm with: sudo -u jarvis test -r /var/lib/jarvisos/secrets/hetzner-read.token && echo present"
      ],
      "commands": [],
      "fields": [],
      "url": "https://console.hetzner.cloud/",
      "nodeId": null,
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 1,
      "requestedOn": "2026-08-17T03:31:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "secret-present",
        "on": "console",
        "location": "/var/lib/jarvisos/secrets/hetzner-read.token",
        "envName": "HCLOUD_TOKEN_READ",
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "/var/lib/jarvisos/secrets/hetzner-read.token is not readable and HCLOUD_TOKEN_READ is not set.",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": {
        "name": "HCLOUD_TOKEN_READ",
        "location": "/var/lib/jarvisos/secrets/hetzner-read.token",
        "custody": "Minted by the owner, written straight to the location above with mode 0600, never pasted into a task, a commit or a chat. config/burst.json declares the same location and the same env name.",
        "envName": "HCLOUD_TOKEN_READ",
        "mode": "0600",
        "rotationDays": 180,
        "issuedBy": "the Hetzner Cloud console, against the burst-only project",
        "scope": "read, one project"
      }
    },
    {
      "id": "vin-invoice-figure",
      "source": "register",
      "title": "Supply the actual VIN invoice figure",
      "category": "information",
      "urgency": "whenever",
      "owner": "owner",
      "state": "open",
      "actionable": true,
      "why": "Gap prices-unverified: every euro in the burst comparison is a number committed to a register, not one taken from an invoice. The comparison's whole claim is that renting is cheaper than upgrading, and that claim rests on what VIN actually costs today.",
      "blocks": "Nothing operational. It blocks the burst decision being evidence rather than an estimate — and the break-even line, which is the number at which the entire policy inverts.",
      "ask": [
        "Find the most recent VIN invoice and read off the monthly total including VAT and any traffic overage.",
        "Compare it with the recurringMonthly figure the /burst page prints. If they differ, the register is wrong and the break-even hours move with it.",
        "Record the figure here; correcting config/burst.json is then JarvisOS's job, not yours."
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": null,
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [],
      "needs": [],
      "unmetNeeds": [],
      "unblocks": 0,
      "requestedOn": "2026-08-17T03:31:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "attest",
        "on": "console",
        "location": null,
        "envName": null,
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": "The real monthly VIN cost has been read off an invoice and checked against the burst register.",
        "whyNotAutomatic": "The invoice is in the owner's mailbox and behind a billing login. This node holds no billing credential and by policy will not be given one — a console that can read invoices can be made to read everything else in that account.",
        "provenBy": null
      },
      "closedBy": null,
      "reason": "No machine can check this — The invoice is in the owner's mailbox and behind a billing login. This node holds no billing credential and by policy will not be given one — a console that can read invoices can be made to read everything else in that account. It closes when somebody attests: \"The real monthly VIN cost has been read off an invoice and checked against the burst register.\"",
      "verifiable": true,
      "byAttestation": true,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": null
    }
  ],
  "shortlist": [
    "coolify-read-token",
    "home1-claude-signin",
    "offsite-restore-drill",
    "edge-single-point-decision",
    "nas-service-account",
    "home1-codex-signin"
  ],
  "alsoOpen": [
    "home1-mesh-enrolment",
    "burst-spend-approval",
    "tailnet-acl-burst",
    "nas-stable-address",
    "hetzner-read-token",
    "vin-invoice-figure"
  ],
  "waiting": [
    {
      "id": "coolify-deploy-token",
      "source": "register",
      "title": "Mint the deploy-scoped Coolify API token",
      "category": "credential",
      "urgency": "soon",
      "owner": "owner",
      "state": "waiting",
      "actionable": false,
      "why": "The adapter deliberately holds separate read and deploy credentials, because Coolify refuses a deploy token on read routes and a read token on deploy routes — the separation is enforced by the vendor, not by us. This is the credential that lets JarvisOS restart, redeploy and roll back without also being able to change configuration or delete anything.",
      "blocks": "Every automated recovery action that ends in a redeploy or a rollback. Until it exists, remediation for a wedged application stops at 'fetch a human'.",
      "ask": [
        "In the same Keys & Tokens screen, create a second token named jarvisos-deploy with the deploy ability and nothing else.",
        "install -m 0600 -o jarvis -g jarvis /dev/stdin /var/lib/jarvisos/secrets/coolify-deploy.token",
        "Confirm with: sudo -u jarvis test -r /var/lib/jarvisos/secrets/coolify-deploy.token && echo present"
      ],
      "commands": [],
      "fields": [],
      "url": null,
      "nodeId": "home1",
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [
        {
          "id": "home1",
          "name": "Home1",
          "tier": "critical"
        }
      ],
      "needs": [
        "coolify-read-token"
      ],
      "unmetNeeds": [
        "coolify-read-token"
      ],
      "unblocks": 0,
      "requestedOn": "2026-08-16T18:00:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "secret-present",
        "on": "console",
        "location": "/var/lib/jarvisos/secrets/coolify-deploy.token",
        "envName": "COOLIFY_TOKEN_DEPLOY",
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "/var/lib/jarvisos/secrets/coolify-deploy.token is not readable and COOLIFY_TOKEN_DEPLOY is not set.",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": {
        "name": "COOLIFY_TOKEN_DEPLOY",
        "location": "/var/lib/jarvisos/secrets/coolify-deploy.token",
        "custody": "Same custody as the read token, and deliberately a different file: one credential per ability means a leak costs one ability.",
        "envName": "COOLIFY_TOKEN_DEPLOY",
        "mode": "0600",
        "rotationDays": 365,
        "issuedBy": "the Coolify UI on home1",
        "scope": "deploy only — start, restart, stop, redeploy, roll back, cancel"
      }
    },
    {
      "id": "hetzner-write-token",
      "source": "register",
      "title": "Mint the read-write Hetzner token for the burst-only project",
      "category": "credential",
      "urgency": "whenever",
      "owner": "owner",
      "state": "waiting",
      "actionable": false,
      "why": "This is the only credential in the estate that can create something billable. It exists last on purpose: after the ceiling is agreed, after the mesh policy that governs what it creates, and after the free read token has proved the project and the prices. Hetzner tokens are read or read-write for a whole project — there is no 'may create but not delete' scope (gap hetzner-token-scope-is-coarse), so this token's blast radius is bounded by the project it lives in and by nothing else.",
      "blocks": "Provisioning any elastic capacity. Deliberately: with no write token, the burst plan is a plan and the estate's recurring cost stays at zero.",
      "ask": [
        "Only after the three prerequisites above are closed.",
        "Same burst-only project. Security → API tokens → Generate, permission Read & Write.",
        "install -m 0600 -o jarvis -g jarvis /dev/stdin /var/lib/jarvisos/secrets/hetzner-write.token",
        "Then verify the refusal path still holds: `node scripts/jarvisos-burst.mjs provision --dry-run` must still print the request and refuse it without a grant. A credential appearing must not change what runs unattended."
      ],
      "commands": [],
      "fields": [],
      "url": "https://console.hetzner.cloud/",
      "nodeId": null,
      "site": null,
      "approver": null,
      "question": null,
      "cost": null,
      "blocksEntities": [],
      "needs": [
        "burst-spend-approval",
        "tailnet-acl-burst",
        "hetzner-read-token"
      ],
      "unmetNeeds": [
        "burst-spend-approval",
        "tailnet-acl-burst",
        "hetzner-read-token"
      ],
      "unblocks": 0,
      "requestedOn": "2026-08-17T03:31:00Z",
      "waitingDays": 1,
      "dueBy": null,
      "overdue": false,
      "closesWhen": {
        "kind": "secret-present",
        "on": "console",
        "location": "/var/lib/jarvisos/secrets/hetzner-write.token",
        "envName": "HCLOUD_TOKEN_WRITE",
        "node": null,
        "capability": null,
        "entityId": null,
        "withinSec": null,
        "statement": null,
        "whyNotAutomatic": null,
        "provenBy": null
      },
      "closedBy": null,
      "reason": "/var/lib/jarvisos/secrets/hetzner-write.token is not readable and HCLOUD_TOKEN_WRITE is not set.",
      "verifiable": true,
      "byAttestation": false,
      "provenBy": null,
      "revalidates": null,
      "attested": null,
      "secretRef": {
        "name": "HCLOUD_TOKEN_WRITE",
        "location": "/var/lib/jarvisos/secrets/hetzner-write.token",
        "custody": "Minted last, held by the jarvis service account, and useless on its own: every billable operation additionally requires a scoped, budget-capped, expiring, single-use grant from the owner.",
        "envName": "HCLOUD_TOKEN_WRITE",
        "mode": "0600",
        "rotationDays": 180,
        "issuedBy": "the Hetzner Cloud console, against the burst-only project",
        "scope": "read-write, one project — the narrowest the provider offers"
      }
    }
  ],
  "satisfied": [],
  "closed": [
    {
      "id": "home1-tailnet-enrolment",
      "title": "Enrol home1 on the tailnet",
      "category": "node-access",
      "closedAt": "2026-08-16T00:00:00Z",
      "closedBy": "bootstrap evidence E6 — the MagicDNS name answered from VIN when the 10.10.0.10 literal did not",
      "note": "Closed by evidence rather than by a tick. It is kept here because it is the case that proves the closure model: the requirement stopped being asked for when something observable became true, and the inventory entry was corrected in the same movement."
    },
    {
      "id": "home1-node-credential",
      "title": "Issue home1 a node credential so it can heartbeat",
      "category": "credential",
      "closedAt": "2026-08-16T19:42:00Z",
      "closedBy": "the identity registry at /etc/jarvisos/node-identities.json carries a home1 entry; the console stores only its SHA-256 digest",
      "note": "Replaced the shared ingest token. The console reports presence and lifecycle metadata and has never held the value."
    }
  ],
  "findings": [
    {
      "code": "ask-open-blocking",
      "severity": "crit",
      "fatal": false,
      "statement": "Mint the read-only Coolify API token",
      "detail": "Coolify exposes no route for minting or revoking API tokens (gap coolify-no-token-api), so every token in this estate is created by hand in the UI. Without the read token the adapter cannot list servers, resources or deployment status, which means `jarvisos-estate.mjs reconcile` cannot run and the 26 live Coolify resources stay unclaimed by the inventory. Blocks: Estate reconciliation, the catalogue's Coolify source, deployment status for every application, and gap estate-unclaimed, which is currently the largest crit in the repository.",
      "requirementId": "coolify-read-token",
      "category": "credential",
      "surfaced": true,
      "overdue": false
    },
    {
      "code": "ask-open-blocking",
      "severity": "crit",
      "fatal": false,
      "statement": "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.",
      "requirementId": "home1-claude-signin",
      "category": "node-access",
      "surfaced": true,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "Decide whether the estate gets a second public edge",
      "detail": "Gaps deployment-single-edge and estate-lifecycle-single-point say the same thing from two directions: one edge, one control plane, and every HA property in three registers is a plan rather than a fact until that changes. The auth register makes it sharper — one forward-auth upstream means an auth container failure reads as the whole estate being down. Blocks: The multi-node deployment register's edge layer, the second forward-auth upstream in the identity register, and any honest claim that this estate survives losing VIN.",
      "requirementId": "edge-single-point-decision",
      "category": "approval",
      "surfaced": true,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "Create a jarvis service account on the NAS and authorise the control-plane key",
      "detail": "The storage register places every byte on a tier and says when that tier runs out, but the numbers behind it are derived from what the register places, not from the pool. The measured answers are `df` and `du` on a machine this process has no account on. A NAS with no service account is a NAS whose occupancy is an estimate that will be believed. Blocks: Measured storage occupancy for nas-backups and nas-media, and therefore any honest 'days until full' figure. Also every future remediation that would touch the NAS, which currently cannot be attempted at all.",
      "requirementId": "nas-service-account",
      "category": "node-access",
      "surfaced": true,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "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.",
      "requirementId": "home1-codex-signin",
      "category": "node-access",
      "surfaced": true,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "Enrol the node on the tailnet",
      "detail": "Joining a private network is an act of enrolment. It needs an auth key or an interactive login from the admin console, and this kit deliberately has neither: an installer that can add machines to your private network is an installer that can add anyone's. Blocks: Everything. Nothing else in the kit runs without the private plane.",
      "requirementId": "home1-mesh-enrolment",
      "category": "node-access",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "Approve the elastic-compute spending ceiling",
      "detail": "The burst register is adopted with zero recurring cost as the default and the rollback plan, and no billable operation is executable: the runner prints the provider request and refuses it. What it does not have is a person's agreement to the ceiling, and every grant in that register is issued against a budget this approval sets. Blocks: Every billable burst operation, and therefore the write-scoped Hetzner token that would carry one out.",
      "requirementId": "burst-spend-approval",
      "category": "billing",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "warn",
      "fatal": false,
      "statement": "Write the tailnet ACL for tag:burst",
      "detail": "Gap tailnet-acl-for-burst is a crit: the burst plan attaches rented nodes to the private mesh and no ACL for tag:burst exists. A burst node with the default policy is a machine somebody else's hypervisor is hosting, sitting on the same flat network as the Postgres primary. Blocks: Attaching any burst node to the mesh. It is a prerequisite of the write-scoped provider token, because the first thing that token would do is create a node that joins the tailnet.",
      "requirementId": "tailnet-acl-burst",
      "category": "approval",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-lapsed",
      "severity": "warn",
      "fatal": false,
      "statement": "\"Run the quarterly off-site restore drill, in person\" has lapsed and is being asked for again",
      "detail": "The inventory declares gwscli-s3-offsite's verification as a quarterly restore drill. A drill that happened once is a drill that proved the backup worked in April. It fell due on 2026-07-19, 31 day(s) ago. It closed itself once and it will close itself again.",
      "requirementId": "offsite-restore-drill"
    },
    {
      "code": "ask-storm",
      "severity": "warn",
      "fatal": false,
      "statement": "12 asks are open against a declared ceiling of 6",
      "detail": "Either the estate genuinely needs that many decisions, or this register has stopped prioritising. Deliberately not fatal: a build that fails because the world has work in it is a build nobody trusts."
    },
    {
      "code": "ask-open",
      "severity": "note",
      "fatal": false,
      "statement": "Give the NAS a stable name instead of a LAN literal",
      "detail": "The NAS is registered at 10.10.0.20. Home1 was registered at 10.10.0.10 until 2026-08-16, and that literal answered nothing from VIN — bootstrap evidence E6. The same failure is sitting in the register unexercised, and it will be discovered during an incident rather than before one. Blocks: Nothing today, because nothing on VIN currently talks to the NAS. It blocks trusting the NAS address the first time something does — which is the moment when finding out is most expensive.",
      "requirementId": "nas-stable-address",
      "category": "information",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "note",
      "fatal": false,
      "statement": "Mint the read-only Hetzner token for the burst-only project",
      "detail": "The read credential is what turns the burst register's arithmetic from committed numbers into verified ones: it reads the provider's own price list, the server types and this month's usage. It is free, it cannot change anything, and the provider itself refuses it on every mutating route — which is why it is the credential every automatic operation uses. Blocks: Price verification (gap prices-unverified) and usage-against-ceiling reporting. Nothing spends money as a result of this token existing.",
      "requirementId": "hetzner-read-token",
      "category": "credential",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-open",
      "severity": "note",
      "fatal": false,
      "statement": "Supply the actual VIN invoice figure",
      "detail": "Gap prices-unverified: every euro in the burst comparison is a number committed to a register, not one taken from an invoice. The comparison's whole claim is that renting is cheaper than upgrading, and that claim rests on what VIN actually costs today. Blocks: Nothing operational. It blocks the burst decision being evidence rather than an estimate — and the break-even line, which is the number at which the entire policy inverts.",
      "requirementId": "vin-invoice-figure",
      "category": "information",
      "surfaced": false,
      "overdue": false
    },
    {
      "code": "ask-waiting",
      "severity": "note",
      "fatal": false,
      "statement": "\"Mint the deploy-scoped Coolify API token\" is not an ask yet",
      "detail": "Waiting on coolify-read-token. Deliberately not surfaced: nobody can start it, and printing it would train its reader to skim the ones they can.",
      "requirementId": "coolify-deploy-token"
    },
    {
      "code": "ask-waiting",
      "severity": "note",
      "fatal": false,
      "statement": "\"Mint the read-write Hetzner token for the burst-only project\" is not an ask yet",
      "detail": "Waiting on burst-spend-approval, tailnet-acl-burst, hetzner-read-token. Deliberately not surfaced: nobody can start it, and printing it would train its reader to skim the ones they can.",
      "requirementId": "hetzner-write-token"
    }
  ],
  "source": "/app/config/requirements.json"
}