# Time-to-live (TTL) Disposable-by-default sandboxes — SAND-WP-0009. ## Semantics Each ready sandbox has: | Field | Meaning | |-------|---------| | `ttl` | Active duration string (e.g. `4h`) | | `expires_at` | UTC timestamp when the sandbox should be reaped | On `create`, TTL comes from `SandboxCreateRequest.ttl` or the profile `ttl.default`, capped at `ttl.max`. Anchor is `ready_at`. Duration format: positive integer + unit — `s`, `m`, `h`, `d` (e.g. `30m`, `4h`). ## extend_ttl Extend a live sandbox (`ready` or `active`): ```bash sandboxer extend-ttl --duration 2h ``` HTTP: `PATCH /v1/sandboxes/{id}/ttl` with `{"duration": "2h"}`. Extension adds to the current `expires_at` (or now if already past). Total lifetime from `ready_at` cannot exceed profile `ttl.max`. ## expire / reap TTL reap is distinct from host inventory `reap-stale`: | Command | Purpose | |---------|---------| | `sandboxer expire` | Sandboxes past `expires_at` or profile `ttl.idle_reap` | | `sandboxer reap-stale` | Orphan host resources vs store inventory | ```bash sandboxer expire # dry-run (default) sandboxer expire --apply # mark expired, destroy ``` HTTP: `POST /v1/sandboxes/expire?apply=true` Flow on `--apply`: 1. Transition to `expired` (State Hub milestone) 2. `destroy` (idempotent teardown) ## Profile fields ```yaml ttl: default: 4h max: 24h idle_reap: null # optional; reap when updated_at + idle_reap elapsed ``` ## activity-core Scheduled jobs should invoke `sandboxer expire --apply` (or HTTP equivalent). See `docs/integrations/activity-core.md`.