SIGN IN SIGN UP

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