Org repos: transfer between owners; fix DeleteOrg 409 dangling reference; delete-org UI #358
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#358
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Survey: crueber/walhub#349 candidate 3 (+ extra: no delete-org UI).
Evidence
Design
Acceptance criteria
PR #365 (fix/issue-358) implements this: direct owner-initiated transfer (POST /{owner}/{repo}/api/transfer), DeleteOrg 409 now true without a wording change, owner-only org Danger Zone + repo transfer entry. #365
PR #365 review (fix/issue-358) — adversarial data-move pass
Verified in scratch worktree at
0b49b5f(incl. my 3-line wording fix, pushed). No browser per instructions; tests + reasoning. Note: vite build run viavite+esbuilddirectly (pnpmabsent in this env).1. Move atomicity — SOUND, no loss in any crash interleaving
transfer.go:71copy-then-delete, every dst writePutCreate. Cleanup (transfer.go:113) only deletes keys this attemptPutCreated, and only runs on the copy-failure path before any delete — so a cleaned-up dst key always still has its src copy. Provably no object loss.transfer.go:169). Union of both sides is always whole. Recovery is manual (delete one side) — acceptable for an admin path.transfer.go:178) is sound for new keys (the content-addressed pack case): 409 names the residue count with dst complete. Mid-movePutCreateloss → 409 + cleanup, src untouched — tested (TestTransferRepoMidMoveCollisionCleansUp).2. access.json rewrite — CORRECT all four directions
transfer.go:255: org→org untouched (no owner-subject either side); org→user keeps + addsuser:<dst>/admin; user→org drops seller; user→user drops + adds. Self-transfer converges (handler test asserts).normPrincipallowercases so theToLowercompare is sound;nonNilBindings(access.go:186) prevents null bindings; corrupt → opaque bytes, missing → synthesize user-dst default only. All covered by tests.3. Gates — VERBATIM, honest statuses, both lanes
http_transfer.go:28,48: sourceCheckRoleadmin thenCheckCreateOwneron dst, in that order (fail-closed: strangers learn nothing). 401 anon / 403 non-admin + foreign dst / 404 ghost source / 409 occupied / 400 bad body / 405 — each pinned byTestTransferHandlersubtests incl. browser lane (201) and host-admin bypass (201).4. DeleteOrg 409 — TRUE + PINNED
orgs.go:635message names transfer, which now exists;TestDeleteOrgNamesTransferasserts the 409 contains "transfer". No wording change needed — correct call.5. UI — owner-only, typed-confirm, SDK idiom matches
Org.jsxDanger tab derived from server-authoritativecan_edit, double-guarded byShow; server still gates. Repo transfer reuses exportedDangerConfirm, navigates to the new address with cache invalidation.sdk/transfer.jsmatches theaccess.js_path/_callidiom;transferBodytrims + omits empty repo (server defaults).6/7. Cost + streaming — OK for a cold admin path
2 manifest HEADs + 1 LIST + 2 ops/key,
copyObjectstreams Get→Put (transfer.go:199), access.json viaGetBytesonly. LIST is prefix-bounded, off every hot path. Law 6/4 hold.8. Verification results
go test -race ./internal/identity/...clean; coverage 96.8% (≥95 gate holds; TransferRepo 90%, copyObject 88.9%).-race -count=10 -run 'TestTransfer|TestDeleteOrg'clean.gofmt/vetclean.transfer.test.js3/3. The 3 failures are allsmoke.test.js, which fetches a live server at:8080— that port is held by a foreign listener returning 503 in this env, so they fail identically on main (environmental, not a regression).vite build+esbuildSDK bundle OK;go build ./...OK.9. Docs — accurate; fixed 3 small inaccuracies (pushed as
0b49b5f)http_transfer.go:16: unknown dst org is 403 via CheckCreateOwner, not 404.transfer.go:22+01_identity_permissions.md§5.4: a raced transfer's src residue must be deleted (re-transfer would 409 on the occupied manifest); only a mistyped transfer recovers via transfer-back.Known limitation (NON-BLOCKING follow-up, not a merge gate)
A push that fully lands — manifest CAS included — between the copy of
manifest.pband its unconditionalDelete(..., "")is silently regressed (dst holds the stale copy). Window is tiny and admin-path-only; new-key races are already caught honestly. Suggested hardening: capture source versions during the copy LIST and conditional-delete, or re-read + compare the source manifest before the delete pass (abort-with-cleanup still possible there). Separately, the stale WAL handle on the source id degrades fail-closed (freshen → store error; fresh open → 404; auto-create semantics identical to repo delete), and orphaned disk cache is already a handled concept — no action needed.MERGE RECOMMENDATION: ready to merge (pending CI)
Fixed by PR #365 (review clean + two hardening fixes by reviewer: unknown-org 403, src-residue note; atomicity + gates + rewrite verified), merged. Closing.