# REPORT — slice 10B (agent half): operator-signed destructive completion (v0.16.0) (2026-06-10) > Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). ## What was implemented The security centerpiece: a destructive op runs ONLY on a verified, operator-signed authorization. Decision (a): **offline operator key + signing CLI**, hardware-key-ready. The signing key is NOT in the hub and NOT in the agent. Concrete consumer: this **closes the 8C data-bearing-wipe `pending_signature` gap**. Pairs with hub v0.10.0. ### The flow (end-to-end) 8C format of a data-bearing device → agent refuses `pending_signature` + **surfaces the bound op** (durable id + host) → operator **signs offline** (`felhom-opsign`) → uploads to the hub jobs queue → agent's signed-jobs runner **verifies** + **executes** the wipe (re-resolve durable id + re-inspect 8C → `mkfs`) → clears the job. ### `cmd/felhom-opsign` (new) — the offline signing CLI - Builds the canonical `OpBlob` by **reusing `authz.CanonicalBlob`** (the exact bytes the verifier authenticates over — signer/verifier can't drift) and signs with **`ssh-keygen -Y sign -n felhom-op-v1`** (hardware-ready: `sk-`/YubiKey work unchanged). Output: `{op_blob_b64, sig_armored}`; optional `--upload`. Touches only the operator's key. ### The verify-and-execute machinery - **`internal/signedjobs.Runner`** — fetches each opaque job → runs the **gate** (the LOCKED authz pipeline: pinned-key SSHSIG → namespace → allow-list by key MATERIAL → crypto over raw bytes → host target → time window → **durable nonce-burn LAST**) → on all-pass hands the verified op to an `Executor`. Order: **verify → burn nonce (durable) → execute → clear**. Rejects (forged/replayed/ expired/retargeted/non-pinned) never reach the executor. - **`WipeExecutor`** (the 8C consumer) — resolve the signed **durable** id → re-derive + **match** (anti-retarget) → **re-inspect (8C classifier)** still-data-bearing → `mkfs`. A path-only binding, a vanished/changed device, or a non-data-bearing target is refused **even with a valid signature**. - **`internal/storage` durable identity** — `DeviceDurableID` / `ResolveDurableDevice` over the world-readable udev symlinks (`byid:` wwn/serial, `byuuid:` fallback) — no privilege, no subprocess. - **`authz.CanonicalBlob`** promoted to production. `hub.Client.Jobs`/`CompleteJob` + `MultiObserver`. The 8C 403 now carries a `pending_op` (op + durable id + host) + a `felhom-opsign` hint. ### Pinning / rotation Operator pubkeys pinned via `authz.signers` (config, trusted path — NEVER hub-alone), **multi-key** (KeyID selects, role-scoped) for backup/rotation without a flag-day. Unchanged verifier wiring; 10B activates the execute path (the runner is the second `EnvelopeObserver`, runs on `HasSignedOps`). ## Tests (real crypto, non-hollow — assert the op did/did NOT run) - `signedjobs` over the **real** gate+verifier (in-Go minted SSHSIGs): valid → executor runs once + job cleared; **replay / non-pinned / expired / retarget / forged / no-signer** → rejected, executor never called; malformed cleared. - `WipeExecutor`: valid → `mkfs`; path-only / durable-mismatch / device-gone / re-inspect-non-data- bearing / not-probed → refused, `Format` not called. - `storage` durable: wwn-preference, uuid-fallback, path-only+traversal refusal, round-trip, missing (symlink tests gated to Linux). `go test ./...` green. ## Versioning / docs - Version `0.15.0 → 0.16.0`; `CHANGELOG.md`. Doc 03 §4 (signed path live) + §6 (8C wipe completes) + §9 (10B done) updated. ## Out of scope (per the task) - Other destructive executors (guest_destroy, decommission, **restore-overwrite → 10D**) reuse the same gate+runner; their executors plug in per-slice. 10B ships the machinery + the storage-wipe. ## Pending - **Live validation** on the demo: data-bearing wipe → `pending_signature` → sign offline with a real operator key → hub queue → agent verifies + wipes; confirm replay + a non-pinned-key signature are rejected. (Also validates the Linux-only durable-device tests + ssh-keygen interop.)