Address Gadfly review on #16: RemoveShare choke point + dedup
Build image / build-and-push (push) Successful in 5s

- RemoveShare now routes through requireGardenRole(roleViewer) — the standard
  authorization choke point (masks existence for non-participants) — then applies
  the owner-or-self rule on top (a participant removing someone else's share is
  now ErrForbidden, not ErrNotFound). No more bespoke GetGarden+manual check.
- Add domain.RoleOwner constant; gardenRole.String() and the tests use it instead
  of the bare "owner" literal.
- UpdatePlanting fetches the plant once and only re-checks visibility when the
  plant id actually CHANGES (new ≠ old), so a no-op plantId resend can't break a
  shared editor editing a plop that uses the owner's private plant.
- Bound ListSharesForGarden with a LIMIT backstop.

Skipped: AddShare email-existence disclosure (the issue explicitly accepts it as
inherent to email-based sharing), 404-on-missing-share (correct DELETE
semantics), and the join-scan/test-helper dedup nits.

GOWORK=off go build/vet/test ./internal/... green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
2026-07-18 23:48:20 -04:00
co-authored by Claude Opus 4.8
parent a615de633f
commit 873c591635
6 changed files with 30 additions and 18 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ func TestListGardensIncludesSharedWithRole(t *testing.T) {
// Owner sees it as "owner".
own, _ := s.ListGardens(ctx, owner)
if len(own) != 1 || own[0].MyRole != "owner" {
if len(own) != 1 || own[0].MyRole != domain.RoleOwner {
t.Fatalf("owner list = %+v, want one garden with myRole owner", own)
}
// other sees nothing yet.