NodeVault: way out for an org whose key
Epic 9747 ("Implement NodeVault on Windows and Mac") shipped the Windows code (#6853), and win-t8703 became capable after an update with no operator step. The live Windows acceptance checks (AC-2/3/5/8/9/10 from Task 9864) were never run. This task removes what blocks them and then runs them.
## Reproduction (Finish 9866, 2026-10-25 ~15:45Z)
Signed in to dev (app.dev.jaah.ai) as the Org B admin (`org/console/dev-orgb-login`) and opened Settings → NodeVault. Current key `1a3dc2db8297`, since 10/9/2026. The only node, win-t8703 (Online, fingerprint e5a95a4e5d23), reads "Waiting for a node that holds the key". No node holds the current key and there is no backup. The operator answered Task 9864's question with "Reset Org B NodeVault", but the console has no way to do that.
## Cause
- Rotation needs a holder. `apps/api/app/routes/nodevault.py:1010-1012` and `:1059-1061` wrap the new key under the current one (a chain blob), and `:1068-1073` refuse the start with `NO_ONLINE_HOLDER`.
- Abandon (`routes/nodevault.py:907-958`) keeps the current key.
- Retire (`routes/nodevault.py:1137-1153`) refuses the current key (`ONLY_OLD_KEYS`).
- So an org whose only holder is gone has no way to start over in the console. That breaks operator decision D4: "Console should not explain why something can't. It must make it work so that everybody can". Draft #9887, filed outside this epic, describes the same gap. Build it here, then close or link #9887.
## Fix (no new surface beyond one action)
1. In Settings → NodeVault, add a "Start over" action for the org Owner. It is offered only when the current key has no holder: no node has a delivered grant for it, and none is online and holding it. The confirmation names what is lost: every credential sealed under NodeVault becomes unreadable and must be re-entered. Confirming retires the current key and its grants, and leaves the org ready for a fresh Setup. It writes an audit event under Area = NodeVault.
2. The server enforces the same no-holder precondition. Use the existing `_waiting_for_holder` logic at `routes/nodevault.py:249-261`, and refuse with a clear error when a holder exists.
3. Fix the win-t8703 runner slot. A runner checkout on win-t8703 (project win-t4744-probe, task #5270) was refused with "slots declared by admin@jaah.ai, who is not a member of" Org B. Re-declare the slot through the console as an Org B member, so an Org B runner can open a session there.
## Acceptance
- Unit and API tests: Start over is refused while a holder exists and succeeds when none does. Afterwards, Setup → Distribute delivers to a capable node. An audit event is written.
- Live on dev, Org B: Start over, then Setup (generate, backup, distribute). win-t8703 reads **Delivered**. Screenshot.
- Then run Task 9864's Windows live ACs on win-t8703 and cite the output:
- AC-2: `jaah nodevault fingerprint` equals the node page and the NodeVault row; `jaah nodevault keys` lists the current hash.
- AC-3: a task pinned to win-t8703 fetches a NodeVault credential and reports a match, without printing it.
- AC-5: `icacls` on the keystore, and runner `type` → "Access is denied". The widen-and-repair step needs an Administrator shell; record it as deferred if none exists.
- AC-8: rotate, then retire. The node shows the new key Delivered and the old hash is gone from `jaah nodevault keys`.
- AC-9: Reset identity re-registers the node and re-grants it.
- AC-10: with the plan term off, the fetch still works.
- `./dc check full` passes in the merge lane, and Jaah Deploy is green.
Out of scope: NodeVault crypto, envelope format and the browser key generation (N3). No copy that explains why a node can't hold keys (N4). Rotation's chaining rule stays as it is. Mac live checks stay deferred (OD3: no macOS node on dev). Operator-plane node capability display is Task #9897.
Done means:
- The Start over action and its server guard are merged, with tests.
- win-t8703 reads Delivered in Org B's Settings → NodeVault (screenshot), and AC-2/3/5/8/9/10 output is cited in the report.
- Required checks: Jaah Deploy
Outcome
Artifacts
Activity
- Ready
- Runner started
- Awaiting response