ov-dev/skills/disposable/SKILL.md
`disposable: true` is the ONE and ONLY authorization for autonomous destroy + rebuild via `ov update`. MUST be invoked before any task involving `ov update`, live verification on rebuildable targets, or marking a VM / container deploy as safe-to-nuke. Explains why disposability is a DEPLOY property (not an image property), the separation between load-bearing `disposable:` and informational `lifecycle:`, why derivation is deliberately absent, and how the flag makes live verification fearless on shared hosts.
npx skillsauth add overthinkos/overthink-plugins disposableInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Schema v4 makes DeploymentNode.Disposable the sole source of truth for disposability. VmSpec.Disposable is retained during the Phase 1-5 transition for backward compatibility with the legacy ov update arch / ov vm destroy --disk paths, but the canonical field on any deployment entry (e.g. deployments.images.arch-vm.disposable: true) is what the unified dispatcher reads.
A latent bug was fixed alongside the refactor: MergeDeployConfigs previously dropped the Disposable and Lifecycle fields during project ↔ per-machine overlay merge — meaning a disposable flag on the project deploy.yml would vanish after merge with a user's ~/.config/ov/deploy.yml. That's now an explicit merge (project-set OR overlay-set → true), with the same "later wins" rule when both set it.
Live-deploy verification (CLAUDE.md R1, R10) is mandatory — and it's
much easier to carry out aggressively when you can freely destroy → rebuild → retest a target without asking the user for permission
every time. But autonomous destroy is only safe on resources whose
owner explicitly authorized it. The disposable: true flag is that
authorization. Nothing else is.
On a shared host, unrelated production services with live users may
run alongside ov-managed resources. The disposable: true flag must
therefore be:
disposable: true on a specific deploy or nothing
happens.deploys:) carries its own flag — two
instances of the same image can sit at different tiers with
different disposability.# DEPLOY-shaped YAML (deploy.yml entry):
disposable: <bool> # LOAD-BEARING authorization. Default false.
# `true` authorizes `ov update <name>` to
# destroy + rebuild + restart unattended.
lifecycle: <tier> # INFORMATIONAL ONLY. Free-form human tag.
# scratch|dev|test|qa|staging|prod|custom.
# Has ZERO effect on disposability.
ephemeral: <block> # LOAD-BEARING operational mandate. Default absent.
# Presence means "MUST be destroyed as soon as
# it isn't needed anymore". Implies
# `disposable: true` automatically (the one
# documented exception to anti-derivation —
# see "The ephemeral exception" below).
Anti-derivation invariant — with one named exception:
lifecycle: dev does NOT make a deploy disposable. A human reader
might assume it would, so the anti-derivation invariant is enforced
by ov/classification.go and a unit test
TestVmSpec_LifecycleAloneDoesNotAuthorize. If you find yourself
tempted to add "if lifecycle in {scratch,dev,test} then
disposable=true": don't. That hidden logic is the entire failure
mode this design avoids.
ephemeral: ... DOES imply disposable: true. This is the only
field allowed to derive disposability — because it strengthens the
contract rather than weakening it. "Must be destroyed when not
needed" can only be honored if "may be destroyed" is also true.
Specifically:
LoadDeployConfig auto-promotes Disposable=true when an entry
carries ephemeral: ....DeploymentNode.IsDisposable() returns Disposable || IsEphemeral()
so every consumer (including ov update) treats ephemerals as
authorized.disposable: false together with ephemeral: ... is
contradictory; the loader rejects (or auto-promotes; bool fields
cannot distinguish "explicit false" from "default false", so the
load-time auto-promote is the canonical behavior).ephemeral: itself is the operational counterpart to disposable::
disposable: true says "this resource MAY be destroyed
autonomously by ov update" — a permission.ephemeral: true (or block form) says "this resource MUST be
destroyed autonomously when no longer needed" — a requirement,
enforced by the eval-runner / BDD step keywords / TTL transient
timer registered in ov deploy add.The implication arrow is one-way. Disposable resources are not necessarily ephemeral; ephemeral resources are always disposable.
kind: vm entries in vms.yml — the VM template. Applies to
every instance unless overridden.deploys: entries in deploy.yml — the container per-deploy
counterpart. Each instance of a container image has its own
entry, so per-instance classifications are natural.~/.local/share/ov/vm/<domain-name>/instance.yml. Until then,
VMs inherit the vms.yml template classification for every
instance.For VMs with either disposable: true or a lifecycle: tag, the
libvirt domain XML carries:
<metadata>
<ov:disposable xmlns:ov="https://overthinkos.org/ns/ov/1.0">true</ov:disposable>
<ov:lifecycle xmlns:ov="https://overthinkos.org/ns/ov/1.0">dev</ov:lifecycle>
</metadata>
virsh dumpxml <domain> | grep ov: tells you the classification
without opening vms.yml.
For container deploys, the authoritative source is deploy.yml —
ov status <name> reflects it at runtime.
ov update <name> [-i <instance>]The autonomous path. Resolves <name> as either a kind:vm entity
(vms.yml) or a deploys entry (deploy.yml). Refuses if effective
disposable is false or unset, with a pointed remediation message.
Otherwise: destroy → rebuild → restart in a single orchestrated
sequence.
# Allowed (disposable: true):
ov update arch
# Refused (no disposable flag, or disposable: false):
$ ov update production-api
ov update: "production-api" is not marked `disposable: true` in
deploy.yml (current lifecycle: (unset)).
`ov update` only acts on explicitly disposable deploys —
lifecycle tags alone do NOT authorize autonomous destroy.
To opt in: edit deploy.yml and add `disposable: true` to the
entry, or run: ov deploy add production-api <ref> --disposable
Flags:
--dry-run — print the sequence without executing.--rebuild-image — also rebuild the underlying image (default:
reuse).-i <instance> — target a named instance.For containers, pass --disposable to ov deploy add:
ov deploy add my-test fedora-test --disposable --lifecycle test
This writes both fields to the deploy.yml entry (flags can also be
passed independently). Omitting --disposable means the entry
stays non-disposable — safe default.
For VMs, edit vms.yml directly on the kind: vm entity. The
disposable: true field lives alongside ram:, cpus:, etc. Any
synced host picks up the change on next ov vm create / ov rebuild.
# deploy.yml — each entry has an independent classification.
deploys:
fedora-coder: # main instance (prod): NO disposable field → NOT disposable
lifecycle: prod
fedora-coder-dev: # dev instance: lifecycle tag does NOT authorize rebuild
lifecycle: dev # ← human tag only; this is NOT disposable.
fedora-coder-qa: # explicit opt-in
lifecycle: qa
disposable: true
fedora-coder-scratch: # minimal: no tag, explicit disposable
disposable: true
Running ov update fedora-coder-dev fails with the refusal
message — the lifecycle: dev tag is informational; the deploy
still needs an explicit disposable: true to be rebuildable.
ov update fedora-coder-qa succeeds.
Today, classification applies to all instances of a kind:vm entity.
If you need per-instance overrides (e.g., ov vm create arch -i test
with different classification than -i prod), that requires the
per-instance override file at ~/.local/share/ov/vm/ov-<name>-<instance>/instance.yml
— planned follow-up.
lifecycle: is free-form string — scratch, dev, test, qa,
staging, prod are the canonical values but any tag works
(demo, perf, nightly …). Tags are informational.
If a future feature wants to link a tier to behavior (e.g.
auto-expire deploys tagged scratch after 7 days), add a NEW
explicit field (disposable.ttl, auto_cleanup: true, etc.) —
never make lifecycle: behaviorally load-bearing. The
anti-derivation invariant is what keeps this classification safe
on shared hosts.
CLAUDE.md — R10 "Verify on a disposable: true target; prove
it on a fresh rebuild", plus the "Disposable-Only Autonomy"
section./ov-build:eval — the 10 testing standards; disposable-only deployment
is Standard 4, fresh-rebuild re-verification is Standard 10./ov-vms:vms — kind:vm schema, including disposable: and
lifecycle: fields./ov-vms:arch — canonical worked example./ov-core:deploy — --disposable / --lifecycle flags on
ov deploy add./ov:rebuild — the rebuild verb command reference (not yet
authored — currently living in this skill).MUST be invoked for any task that involves:
ov update <name> (determining which targets can be rebuilt
unattended).disposable: or lifecycle: fields in
vms.yml or deploy.yml.IsDisposable() / IsDisposableFields(), never derive from
lifecycle).Invoke this skill BEFORE reading ov/classification.go or the
related YAML files.
tools
Use when authoring or modifying a charly PLUGIN — a candy with a `plugin:` block that contributes Providers (verbs/kinds/deploy-targets/steps/builders/commands), its own CUE schema, builtin (compiled-in) or external (out-of-tree git repo). Covers the unified Provider model, the per-plugin CUE-schema contract (single source → Go params for dev + schema-over-Describe RPC for runtime), the SDK, and the loader.
tools
The CUE data-validation / configuration CLI (cue), pinned to v0.16.1. Use when working with the cue candy, installing the cue binary into a box or onto a target:local dev host, or running the offline schema-vendoring pipeline that feeds charly's egress validation.
tools
CUE EGRESS validation — validating (and, where it adds value, generating) the config files charly WRITES to a system BEFORE the bytes hit disk. MUST be invoked before working on charly/egress.go, the vendored schemas under candy/plugin-egress/egress-schemas/vendor/, the ValidateEgress / registerVendoredEgressKind path, the offline `task cue:vendor` pipeline, or adding an egress schema for any written artifact (cloud-init, k8s manifests, traefik routes, runtime config, install ledger, systemd/quadlet units, ssh_config, libvirt XML).
tools
Kubernetes cluster-probe declarative check verb — the `kube:` check verb (nodes, pods, ingress, storage class, addon health, apply/delete, and arbitrary resource GETs) served out-of-process by the candy/plugin-kube plugin (vendored client-go; no external kubectl required).