From 5e91adb5b73486e768903f70b3df160c315737c1 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Tue, 7 Jul 2026 12:57:29 +0200 Subject: [PATCH] =?UTF-8?q?docs(rfc-0001):=20Rev=209=20=E2=80=94=20offboar?= =?UTF-8?q?ding=20via=20top-level=20`tracebloc=20delete`=20+=20single-mach?= =?UTF-8?q?ine=20CLI=20scope?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decided by Lukas 2026-07-07. Two coupled decisions: - The CLI is scoped to THIS machine (one client per machine — the §7.2 anchor); the account's fleet lives in the web-UI. So `client use` is removed and `client list` is internal-only (hidden; still callable for the installer pre-flight). Amends the §6.2 surface; folds in the cli#136 client-surface prototype. - Offboarding is a top-level `tracebloc delete` (not `client delete --uninstall`): a SOFT offboard that revokes the machine credential, tears down the local install, wipes local data, and RETAINS all backend artifacts (client record, dataset catalog, use cases, trained models) as history. A hard row-destroy is REJECTED — it would cascade the client's training telemetry (weight refs, confusion matrices, carbon). New §7.10 (design of record) + Rev 9 callouts on §6.2/§7.4; R12 flipped to addressed; C.6 corrected to name the built machine-revoke endpoint (POST /edge-device//revoke, backend#886) and note DELETE is unrouted. Supersedes the cli#136 proto's client delete/DELETE offboarding draft. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../0001-cli-auth-and-client-provisioning.md | 132 ++++++++++++++++-- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/docs/rfcs/0001-cli-auth-and-client-provisioning.md b/docs/rfcs/0001-cli-auth-and-client-provisioning.md index 745f646e..ff49b697 100644 --- a/docs/rfcs/0001-cli-auth-and-client-provisioning.md +++ b/docs/rfcs/0001-cli-auth-and-client-provisioning.md @@ -60,6 +60,23 @@ > > The §6.6/§6.7/§7.7 bodies below are retained as the original design-of-record; the > inline **Rev 8** callouts mark where the shipped behavior now differs. +> +> **Rev 9 (2026-07-07)** offboarding + single-machine CLI scope (R12 — decided by +> Lukas 2026-07-07): +> - **The CLI is scoped to THIS machine; the fleet lives in the web-UI.** One machine +> hosts one client (the §7.2 anchor), so the CLI never needs to *select among* +> clients: `client use` is **removed** and `client list` is **internal-only** (hidden, +> still callable for the installer's one-client pre-flight). Managing many clients +> across an account is a web-UI concern. (Amends the §6.2 surface; folds in the +> cli#136 client-surface prototype.) +> - **Offboarding is top-level `tracebloc delete`** (not `client delete --uninstall`): +> on a one-client machine, "delete the client" *is* "remove tracebloc," and the verb +> no longer collides with `data delete`. It is a **soft offboard** — revoke the +> machine credential, tear down the local install, wipe local data, and **retain all +> backend artifacts** (the client record, dataset catalog, use cases, trained models) +> as history. A hard row-`destroy` is **rejected**: it would cascade-delete the shared +> training history. New §7.10; R12 moves from future-proof to addressed. Supersedes the +> cli#136 prototype's `client delete` / `DELETE` offboarding draft. ## 0. Decisions settled in this revision @@ -322,6 +339,13 @@ Global: `--verbose` / `TRACEBLOC_LOG_LEVEL` for diagnosability (§8.5), and a (R12). `tracebloc cluster doctor` extends to also check auth/config/token state, so it can diagnose a failed *provision*, not just cluster health. +> **Amended — Rev 9 / cli#136:** the CLI is single-machine (one client per machine — +> the §7.2 anchor), so `client use` is **removed** and `client list` is **internal-only** +> (hidden; still callable for the installer's one-client pre-flight). Offboarding is the +> top-level **`tracebloc delete`** (§7.10) — not `client delete --uninstall`; on a +> one-client machine it *is* the delete, and it owns the full local teardown. Fleet +> management across an account moves to the web-UI. + ### 6.3 Backend additions (in `tracebloc/backend`) - `POST /device/code` → `{ device_code, user_code, verification_uri, @@ -598,6 +622,11 @@ identity vanished). - **Checks RBAC** (write `403` → "ask an admin", §6.3). - **Clears the stale active pointer** afterward (§7.5). +> **Amended — Rev 9 / cli#136:** on the single-machine CLI, the per-client `client delete` +> picker is superseded by top-level **`tracebloc delete`** (§7.10), which owns the full +> local teardown + soft offboard. These guards (refuse-if-a-job-is-running, `--force`, +> RBAC 403 → ask-an-admin, clear-pointer) carry over to it unchanged. + ### 7.5 Stale active client — cross-account *and* cross-env (confirmed bug today) **Risk.** The active client is cached locally. **`logout` today clears only the @@ -689,6 +718,74 @@ CLI **resumes into it** instead of minting a second. - Concurrent attaches to one cluster are serialized by the `unique` `cluster_id` constraint (§6.3); the loser fetches and adopts the winner. +### 7.10 Offboarding — `tracebloc delete` (R12) — **[decision, Rev 9]** + +**Goal.** A clean inverse of install for a retired box — stop the heartbeat, kill the +credential, reclaim local disk — *without* stranding an R3 orphan and *without* +destroying the shared history built on this client. + +**`tracebloc delete` is a top-level command** (not `client delete --uninstall`): on the +single-machine CLI (Rev 9) the machine hosts exactly one client, so "delete the client" +is "remove tracebloc," and the verb no longer collides with `data delete`. It is a +**soft offboard** with three explicit scopes, surfaced to the user as one screen before +a typed confirm: + +1. **Removed from this machine.** Revoke the machine credential (below); `helm + uninstall`; `k3d cluster delete` (this also prunes the dangling kubeconfig entry); + reclaim the tracebloc container images (scoped to `ghcr.io/tracebloc/*` — **never** a + blanket `docker system prune`); `rm -rf ~/.tracebloc`. On the single-host path the + on-prem datasets live *there* (host bind-mount, not node-ephemeral — neither + `helm uninstall` nor `k3d cluster delete` erases them), so this is the one + irreversible step. Finally remove the CLI binary + `tb` alias. +2. **Retained on the backend, as history.** The client record, the dataset catalog + entries (`UserDataSet`), the use cases (`PrivateCompetition`), and the trained-model + records + leaderboards **stay**. A colleague's model must not vanish because the data + owner reclaimed their laptop. This is not new behavior: no backend path hard-deletes + these, and `revoke` preserves the client row by construction (§9, C.6). +3. **Left in place.** Shared system software the installer also laid down — Docker, + Homebrew, `kubectl`/`k3d`/`helm`, and (on a Linux GPU box) **NVIDIA drivers + + container toolkit** — is **listed, never removed**: other software depends on it, and + an uninstaller must never pull it out or trigger a reboot. + +**Credential kill = the machine-credential revoke action, not `DELETE`.** Offboard calls +`POST /edge-device//revoke` (C.6 — the action that unsets the password, deletes the +DRF token, and revokes the `ClientAccessToken`s in one transaction while preserving the +row). `DELETE /edge-device//` is **not routed** (`405`) and revokes nothing — an +offboard built on it would leave a live credential for up to the token TTL (R2). This +satisfies R12's "leave no live credential" with **no** row deletion. + +**Hard `destroy` is rejected.** Deleting the `EdgeDevice`/`User` row would CASCADE the +per-client training telemetry — epoch metrics, cycle records (incl. their +`modelWeightsFile` references and confusion matrices), carbon history — the very shared +history retained in scope 2, while leaving datasets/experiments alive-but-orphaned +(`SET_NULL`) and dangling the JSON/text edge-id references (`edges_involved`, +`dataset_summary`). There is therefore no CLI or backend path that row-deletes a client; +offboard is soft-only. *(Full account/history erasure — GDPR-style — is a separate, +account-level web-UI/support flow, deliberately out of the per-machine CLI's reach.)* + +**Tombstone, don't mislead — backend work.** Retained artifacts must not read as live +once the data is gone: +- **Decommissioned state.** The client needs an explicit *decommissioned* marker, + distinct from the transient heartbeat `OFFLINE` (offboard ≠ a sleeping laptop). Set by + the revoke/offboard call. *(None exists today — new state.)* +- **Mark its datasets unavailable.** A dataset flips to `edge_dataset_status=deleted` + today only via the edge reporting which ingestor-ids it still holds — but an + offboarded edge never reports again, so the offboard call must flip its datasets + itself. +- **Gate new training at launch.** `ExperimentView.post` reads the *frozen* dataset + summary, so a run against a tombstoned dataset is currently *queued* and fails late at + a gone edge. Add the availability gate the un-archive path already carries + (`CompetitionSerializer.validate_is_archived`) so it fails fast — "data no longer + available on edge." + +**Verification.** Because the local data wipe is irreversible, the confirm is a **typed +client-name** confirmation (not `[y/N]`), preceded by the removed / retained / left +three-way summary. `--yes` for automation; `--keep-data` uninstalls the software while +sparing `~/.tracebloc`. Inherits §7.4's live-work guards (refuse if a training job is +running unless `--force`). Teardown lives in `internal/nodeboot` (shell-out to +k3d/helm), extended past the cli#136 prototype's k3d/Linux-only scope. This is the +connect/install family's inverse — the **#880 uninstall/offboard** flow. + ## 8. UX — drafted flows > **Tracked as four acceptance families** on the epic (#830) — **#877** connect/install · @@ -1035,13 +1132,17 @@ cadences + a new contract = undefined failures on skew. *Mitigation:* CLI sends version header; backend advertises a min-supported version with a clear "upgrade required" message; `chart_version` on the heartbeat. *(cli + backend + chart.)* -### R12 — No clean uninstall / offboarding (ease-of-use, future-proof) +### R12 — Clean uninstall / offboarding — **addressed (§7.10, Rev 9)** -There's no inverse of install: nothing stops the heartbeat, de-registers the box, or -clears local state, so every retired/rebuilt box becomes an R3 orphan. *Mitigation:* -`client delete --uninstall` / installer `--uninstall` that tears down the release, -de-registers the `EdgeDevice`, and clears `~/.tracebloc` — the intended cure for R3 -rebuild-orphans. *(cli + installer.)* +There was no inverse of install: nothing stopped the heartbeat, killed the credential, or +cleared local state, so every retired/rebuilt box became an R3 orphan. **Resolved by +top-level `tracebloc delete` (§7.10):** a soft offboard that revokes the machine +credential (`POST /edge-device//revoke`, C.6 — not the `405` `DELETE`), tears down +the local release + cluster, reclaims local disk, and **retains backend history as +tombstones** — never a hard row-delete (which would cascade shared training telemetry). +Remaining build: the *decommissioned* client state + the dataset-unavailable flip on the +revoke action, and the launch-time availability gate (all §7.10). *(cli `tracebloc +delete` + `internal/nodeboot`; backend revoke-extend + launch gate.)* ### Watch-items (not blockers) @@ -1254,9 +1355,22 @@ POST /auth/revoke # Bearer → 204 # `logout` MUST call this (not built # server-side (a local clear leaves it valid — R2) ``` -Machine-credential revoke ("kill a compromised node *now*", §9) is a **separate** -EdgeDevice action, distinct from `DELETE`: invalidate the password hash + force -re-enroll, without tearing down the cluster install. +Machine-credential revoke ("kill a compromised node *now*", §9; and the credential +half of offboard, §7.10) is a **separate** EdgeDevice action, distinct from `DELETE`: + +```http +POST /edge-device//revoke # Bearer, CanManageClient → 200 [backend#886, BUILT] + # one transaction: unset password + delete DRF Token + # + revoke the ClientAccessTokens; PRESERVES the row + # + cluster_id (re-enroll / history). Idempotent; + # account-scoped (cross-account → 404). +``` + +It invalidates every credential + forces re-enroll **without** tearing down the cluster +install and **without** deleting the row. `DELETE /edge-device//` is deliberately +**not routed** (`405`) — there is no hard client destroy (§7.10: a row delete would +cascade the client's training history). **§7.10 offboard extends this action** to also +set the *decommissioned* state + mark the client's datasets unavailable `[NEW]`. ### C.7 Data model — backend `[NEW]`