---
name: lifecycle-deprecate-node
risk_class: config_mutation
inputs: [node_name, replacement_node_or_reason]
verification: "homelab node <name> relations — 'affected by' must be empty before completing"
docs_update_checklist: [doc_page_deprecation_note]
transition: "active -> deprecated"
---

# Lifecycle: deprecate a node

Per [seeds/ontology.yaml](../../../seeds/ontology.yaml): a node keeps running
but takes no new dependents. **Completion condition: zero remaining
inbound `depends-on`/`routes-to` edges** — this is a hard gate, not a
suggestion; `seeds/policy.yaml` `lifecycle_overrides.deprecated.refuse`
lists `new-inbound-edges` as refused going forward.

1. Set `state: deprecated` on the node.
2. `homelab node <name> relations` — read `affected_by`. Every entry
   there is something still relying on this node.
3. Migrate or retire each dependent one at a time (point its `backend`/
   `config_repo`/ingress route elsewhere, or deprecate it too if it's
   being retired alongside).
4. Re-run `homelab node <name> relations` after each dependent is moved.
   The transition to `destroyed` is only safe once `affected_by` is
   empty — check this every time, don't assume from memory.
5. Note the deprecation on the doc page: reason, replacement (if any),
   date.

If step 2 shows dependents you didn't expect, stop and investigate
before proceeding — that's exactly the kind of drift the Week-3 detector
will catch automatically, but until then this manual check is the gate.

Next (once `affected_by` is empty):
[lifecycle-destroy-node.md](../lifecycle-destroy-node/SKILL.md).
