Node org move: remove the old org's

Task 5774 · Done · Coder · created · updated

## Defect PRD R3.4 (epic #5680) requires: "After the move the node holds no org-A secret … and it runs only org-B tasks." The node move (#5697, PR #4982, `apps/api/app/tenancy/transfer_node.py`) erases slots and account dirs and retires NodeVault grants. Its own report lists what it leaves behind on a node that is now org B's: 1. **Rescue bundles.** A forced `erase_tree` leaves erase-rescue bundles of org A's code on the node. The project move does the same on old-org nodes. 2. **Compose stacks.** Org A's compose stacks and volumes stay up, because erase sends no `stack_down_cmd`. A stack's `.env` can hold org-A secrets. 3. **Console access.** Org A's console relay tickets and direct-console grants on the node are not revoked. 4. **Clones.** Org A's project clones that have no slot-0 row are not erased. 5. **Tailnet tags.** The node is not retagged onto org B's tailnet tags. 6. **Fence lift.** A node that never confirms its cleanup stays fenced, and no operator control lifts the fence. #5695 also deferred: (7) dynamic environments of non-live tasks follow their task into org B but still name the old-org node, and no teardown is ordered. (8) A refused or failed move is not audited. ## Fix Extend the existing node rule and its erase intents. Do not add a new mechanism: - Items 1, 2 and 4: send `stack_down_cmd` (volumes included) and erase the clones, all in the same durable intent the slot erase uses. Ask jaahd to erase without a rescue bundle (add `rescue: false` to the erase payload and honour it in jaahd). - Item 3: revoke old-org console tickets and grants for the node in the move transaction. - Item 5: request a tailnet retag through the existing node tag path. - Item 6: add an operator "Lift fence" action on the node page, with an audit row. - Item 7: order a teardown for the moved environment rows that name an old-org node. - Item 8: write an `org_transfer_refused` activity row in the source org. ## Done means - An API test (`apps/api/tests/test_org_transfer_node.py`) shows that an idle-node move writes a stack-down intent, a clone erase and an erase with `rescue: false`; revokes console tickets and grants; and requests the retag. Each is asserted on the DB/intent rows. - A jaahd test shows that `rescue: false` leaves no rescue bundle. - An API test shows that a project move with a non-live dynamic environment on an old-org node orders that environment's teardown. - An API test shows that a refused move (409 blocked) writes exactly one refused-audit row and moves nothing else. - On the node page, an operator can lift a fence, and an audit row is written (component test plus a dev check). - `./dc check full` is green, the PR is merged, and its Jaah Deploy succeeded. Out of scope: do NOT change which entities move or the manifest contract. Do NOT touch `offboard.py` logic beyond calling it. Do NOT allow any other path to change `nodes.org_id`.

Outcome

Node move now purges stacks+volumes, erases with no rescue bundle and erases clones, revokes console access, and retags the tailnet. Added operator Lift fence, idle-env teardown and refused-move audit. PR #5014 merged, deploy OK. Full gate passes except the develop NodeLink selftest. Deferred UI nits are in the report.

Pull request

Artifacts

Activity

  • Ready
  • Runner started
  • Runner ended (done)