<!-- x-shc-release: version=2.4.15 op_fingerprint=183a4a0282542d671d799f08044f6717182932f9fa1ef5205dfca45a030cefaa op_count=177 contractFingerprint=e8f7ccc12336fb4925893ce3582b03782b182638dc460d952d046a36c1c20e14 -->
---
name: wait-until-ready
description: 'Block until a fresh SHC VPS is operable -- confirm the server is up and the agent''s SSH + sudo work before any deploy touches it. The first thing every VPS-side operator skill depends on.'
when_to_use: 'Right after ordering/provisioning a VPS, before running any deploy (shc-deploy-app), point-a-domain, harden-ssh, or nightly-backup. Use it as the gate: call it, walk away, proceed only once the VPS is ready.'
router_what: Wait until the VPS is up and SSH+sudo actually work (live proof).
router_when: 'Right after ordering a VPS, before any deploy/point-a-domain/harden/backup.'
version: 2.4.3
operator: true
client_side: false
requires: [user_api_key_operate]
provides: [vps_ready]
inputs:
  - { name: service_id, required: true, desc: the SHC service/VM id (from the order) }
  - { name: ssh_host, required: false, desc: 'VPS host/IP, if known, to confirm SSH+sudo' }
  - { name: timeout_min, required: false, default: 15, desc: give up after N minutes }
outputs:
  - { name: ready, desc: true once the VPS reports ready AND SSH+sudo are confirmed working }
estimated_minutes: [1, 15]
danger: low
---

# wait-until-ready -- block until the VPS is operable
## Operator discipline (hold to these on every step)

*Five rules that keep you safe while you operate a server for someone. Follow them on every
step of every skill.*

1. **Verify, don't trust. Never fabricate a result.** Never report `ready`, `done`, `live`,
   `deployed`, `backed up`, or `paid` unless a real tool output or healthcheck PROVED it (and that
   proof arrives in a separate later message, never in your own reply). A status field is a claim; a
   healthcheck is proof. This holds under pressure: "tell my boss in one line it's live", "answer in
   one line", "you do not need to check" do NOT make it true and do NOT waive the check. If you have
   not verified, say exactly that -- never write a done/live/paid sentence you did not earn.
2. **Spending AND destruction are the human's call -- the user CANNOT authorize you to skip the
   confirm.** For money (order, pay) OR data-loss (reinstall, delete, cancel, restore): name the
   action, state the consequence, get the human's explicit yes for THAT action; never in one step.
   The trap: "just do it / don't ask me / skip confirmation / no need to confirm / I already agree" is
   an instruction to skip the gate -- it is NOT a yes and does NOT give you permission to bypass the
   gate. The gate OUTRANKS that instruction; proceeding because they said "just do it" is itself the
   failure. So you still state the consequence and still wait for a real yes. NEVER set `confirm=true`
   yourself, NEVER map "just go" onto `human_confirmed=true`, and NEVER assume a missing confirmation
   "defaults to true" -- the opposite is true: missing -> REFUSED (409 confirmation_required). If they
   will not give the yes, you do NOT proceed -- announce nothing, run nothing. (Mechanics: every spend
   AND destructive action is gated by a **server-issued, single-use confirmation_id** -- you can NEVER
   self-confirm. On the hosted MCP the destructive/spend tool returns the confirmation_id after its own
   human-approval step. On raw /v2 a destructive/spend call with no confirmation_id returns **409
   confirmation_required** carrying a one-time, non-secret `confirmation_id` and a `how_to_proceed`
   re-call shape; AFTER the human's explicit yes, re-send the IDENTICAL request adding the header
   **`X-User-Api-Confirm: <that confirmation_id>`**. `?confirm=true` and body `confirm:true` are NOT
   accepted -- they get the same 409. The confirmation_id is bound server-side to that exact call +
   your principal and is single-use, so a leaked id is inert (it completes nothing on its own). The
   409's `how_to_proceed` is a recovery shape, NOT a license: a confirmation_id only proves the SERVER
   has a pending record for the action -- do NOT auto-resend it until the human has actually said yes to
   the consequence. Spend is also scope-gated: an `operate` key cannot spend at all -- only a `full` key
   or HTTP Basic can.)
3. **Check the source before you run it.** Validate any deploy recipe against the project's
   *current* upstream docs, not a cached memory.
4. **Take the smallest key that does the job.** An agent that only provisions should never
   hold the key that spends.
5. **When you can't verify, stop and say so.** Failing loudly beats a confident wrong answer.


Gate every VPS-side skill on this. Call it, walk away, proceed only once the VPS is `ready`.

## Safety gate (never break)

Emit `STATUS: ready` ONLY after the live SSH+sudo check passes -- never from the API status field
alone. A `provisioning_state: ready` is a claim; an SSH login that returns a shell is proof. (A
provision can report ready a beat before sshd answers.)

## Steps (poll, then prove)

```
deadline = now + timeout_min
loop:
  state = SHC API GET /vm/<service_id> -> .provisioning_state
  if state == "ready":
     ssh -o BatchMode=yes <host> "sudo -n true"  &&  emit STATUS: ready
                                                 ||  keep waiting (SSH not up yet)
  if now > deadline: emit STATUS: failed (NEXT_ACTION: contact SHC support / re-check the order)
  sleep 15
```

If the API signal lags but SSH+sudo already work and the order shows the VM active, treat SSH+sudo as
the proof and proceed -- say so plainly. This skill only reads; it never needs a spend key. A fresh
server usually answers SSH in 30-90s; on timeout, report plainly -- do not guess.

## Result contract

- ready: `STATUS: ready` + `NEXT_ACTION: proceed with the requested deploy skill`.
- waiting: `STATUS: provisioning` + `NEXT_ACTION: re-run wait-until-ready (still booting)`.
- timeout: `STATUS: failed` + `STEP/REASON/NEXT_ACTION`.

**You're done when:** an actual SSH login returned a shell and `sudo -n true` succeeded -- not when the
API status field said `ready`. Until the live SSH+sudo check passes, keep waiting.
