docs(plans): plan 0017 — GAR-393 slice 1 (PATCH /v1/groups/{id})#22
Merged
michelbr84 merged 1 commit intomainfrom Apr 16, 2026
Merged
docs(plans): plan 0017 — GAR-393 slice 1 (PATCH /v1/groups/{id})#22michelbr84 merged 1 commit intomainfrom
michelbr84 merged 1 commit intomainfrom
Conversation
Slice 1 of 3 of GAR-393 ("Rotas POST/GET/PATCH /v1/groups com OpenAPI").
POST and GET already shipped via plan 0016 M4. This plan covers only
PATCH /v1/groups/{group_id}; invites/members/setRole/DELETE member stay
out of scope for plan 0018+ (slice 2).
LIST /v1/groups is explicitly NOT in GAR-393's formal endpoint list and
stays out of this slice entirely — either a sub-issue or a standalone
GAR-XXX will carry it if/when it becomes necessary.
Pre-plan validation (gate 2, 2026-04-15) confirmed empirically:
- groups table has no RLS (app-layer authz only; migrations 001 + 007)
- garraia_app has GRANT UPDATE ON ALL TABLES (migration 007:70)
- Action::GroupSettings exists and Owner/Admin carry it (action.rs + can.rs)
- updated_at has no trigger — handler must set it explicitly (001:115)
- type='personal' reserved, handler must reject with 400 (001:114)
Zero migration, zero new role, zero enum change, zero ADR. Reuses 100%
of plan 0016's AppPool + harness + authz_http_matrix foundation.
Design invariants documented:
1. PATCH semantics = partial modification via COALESCE (not PUT)
2. updated_at = now() set explicitly in every UPDATE
3. type='personal' rejected with 400 at 3 layers (validate fn, unit test, integration test)
4. empty body rejected with 400 deterministic detail
7 tasks + 1 validation pass. ~330 LOC estimated (150 production + 180
test). authz_http_matrix expansion from 15 to 18/19 cases (PATCH ×
owner/admin/member/outsider) is mandatory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Docs-only PR adding plan 0017 as draft and registering it in the index. No code touched.
Plan at a glance
Linear: GAR-393 — "Rotas POST/GET/PATCH /v1/groups com OpenAPI" (Backlog, High,
epic:ws-api).Slice: 1 of 3 of GAR-393. POST + GET already shipped via plan 0016 M4. This plan delivers only
PATCH /v1/groups/{group_id}.Out of scope (explicit by operational decision 2026-04-15):
LIST /v1/groups— not in GAR-393's formal endpoint list; gets a sub-issue or standalone ticket later.Gate 2 — pre-plan empirical validation (2026-04-15)
Before writing the plan, verified each risk against the actual repo:
groupstable under RLS?migrations/001_initial_users_groups.sql:100-101,007_row_level_security.sql:19-21garraia_apphasUPDATEgrant ongroups?migrations/007_row_level_security.sql:70Action::GroupSettingsexists?crates/garraia-auth/src/action.rs:37GroupSettings?crates/garraia-auth/src/can.rs:6-7updated_athas trigger?migrations/001_initial_users_groups.sql:115type = 'personal'API-visible?migrations/001_initial_users_groups.sql:114Conclusion: zero new migration, zero new role, zero enum change, zero ADR. Pure reuse of plan 0016's foundation (
AppPool+ harness +authz_http_matrix).Design invariants (non-negotiable for this slice)
Documented in the plan header:
PATCH, semantics = partial modification. Handler accepts any subset of mutable fields (name,type) and only updates what's explicitly present viaCOALESCE($new, column). Not aPUT(full-resource replacement). Empty body{}→ 400, not no-op 200.updated_atset explicitly by the handler on every UPDATE. Schema has no trigger — forgetting this is a silent staleness bug. The UPDATE query always containsupdated_at = now()inSET, regardless of which columns the caller changed.type = "personal"rejected with HTTP 400, defense-in-depth at 3 layers: (a)UpdateGroupRequest::validate, (b) unit test ingroups.rs, (c) integration test inrest_v1_groups.rs. Any single-layer regression is caught before merge."patch body must set at least one field".Shape
rest_v1/groups.rs,rest_v1/mod.rs,rest_v1/openapi.rs,tests/rest_v1_groups.rs,tests/authz_http_matrix.rs, (conditional)garraia-auth/src/role.rssecurity-auditor: reserved for the code PR review, not this docs PR — first mutation endpoint on tenant-root resource warrants itOpen questions — all closed before approval
GroupReadResponse(reuses GET struct).family/team, rejected forpersonal.MembersManage/GroupDelete.Test plan
Execution plan (next step, after this docs PR merges)
feat/0017-gar-393-v1-groups-patchbranch, batch with checkpoints (commit per task, stop and report at each milestone).@security-auditorinvoked on the code PR before merge.🤖 Generated with Claude Code