fix(cowshed): require landed ancestry before rm destroys a workspace
Removing a workspace deletes the image its commits live in. `rm` only proved those commits were "preserved" — contained by any host branch or `refs/cowshed` preservation ref — and `--force` skipped even that, so a scripted `cowshed rm <ws> --force` destroyed committed work that had never reached main. The gate is now ancestry, not preservation: `rm` refuses unless the project's `main` contains the workspace's HEAD, read out of main's own repository (the object store that survives the removal). Main detached is not an excuse — it is mounted for the proof and put back as found. `--force` and `--abandon` now authorize different losses and neither substitutes for the other. `--force` keeps only its transient-state meaning (dirty tree, in-progress merge, busy mount) and no longer reaches the ancestry gate; `--abandon`, long-form only, is the sole authorization for destroying unlanded commits. Before deleting, it bundles `main..HEAD` into `sessions/.trash/<ws>-<tip>.bundle` beside the retired sidecars and reports the tip, the branch tip it never reached, and the commit count, so even a deliberate abandonment is recoverable by fetching the bundle into main. No removal refusal names the flag that overrides it. The old hints literally prescribed `retry with: cowshed rm <ws> --force`, which is how coordinator scripts learned the destructive bypass; refusals now name safe remedies only and a test sweeps every one of them. The flags are documented in `cowshed rm`'s usage text, where a human reads options deliberately. Also here, because the gate depends on them: - `git bundle create` names a bundle after the refs in its rev range, so the tip must be a ref spelling; an oid tip silently yields a ref-less bundle git rejects as empty. Found by the live check, now asserted. - `git_optional_ref_oid` used `show-ref --verify`, which is fatal (exit 128) on an absent ref rather than exiting 1, so "the target branch does not exist yet" surfaced as an internal error. Switched to `rev-parse --verify --quiet`. - `rename` retires its source through the new `finish_retirement` seam instead of laundering a `force: true` removal: the fork is the preservation, so the ancestry gate would have refused a rename that loses nothing. - `land`'s default target and `rm`'s required branch share one constant, so landing cannot satisfy a check removal does not make. `remove` now answers a `RemoveReport`, threaded through the coordinator RPC, the CLI, and the Node-API addon.
D
Danny Wilson committed
a1417ab5d5ed684dff0e4a24e91c6051110988de
Parent: d68b4af