Skip to content

feat(squads): pivot squad_members + SquadRole + recorder de eventos (#355)#407

Closed
sirelves wants to merge 1 commit into
feat/squadsfrom
story/355-squad-members-events
Closed

feat(squads): pivot squad_members + SquadRole + recorder de eventos (#355)#407
sirelves wants to merge 1 commit into
feat/squadsfrom
story/355-squad-members-events

Conversation

@sirelves

@sirelves sirelves commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Parent

#342 · Closes #355 · Desbloqueada por #354 (merged)

O que construir

Pivot de membros, papéis in-squad e o recorder da trilha append-only de eventos de membership — módulo profundo reusado por todas as mutações (S3/S6/S8/S9/S10).

O que este PR entrega

Migrations (via artisan make:migration --module=squads, datas Tz, jsonb)

  • squad_membersid uuid, FKs tenant_id/squad_id/user_id, role, joined_at, left_at?, UNIQUE (squad_id, user_id)
  • squad_membership_events (append-only) — actor_id? (nullOnDelete = sistema), action, from_role?/to_role?, reason?, metadata jsonb?, occurred_at; índices (squad_id, occurred_at) e (user_id, occurred_at)

EnumsSquadRole (captain/sub_captain/member/ex_member), MembershipAction (join/leave/promote/demote)

ModelsSquadMember (Pivot + HasUuids) e SquadMembershipEvent, com PHPDoc @property sincronizado, #[Table], #[UseFactory] e casts de enum/data. O evento é imutável no próprio model (booted() barra update/delete via MembershipEventIsImmutable), garantindo o append-only.

RecorderRecordMembershipEvent (Action): grava um evento por mutação, actor nulo = sistema, occurredAt default now(), tenant_id derivado do squad.

Factories + TestsSquadMemberFactory, SquadMembershipEventFactory, e testes do recorder (transição completa, evento de sistema, metadata jsonb) + imutabilidade (update e delete).

Critérios de aceite

  • Migrations + PHPDoc sincronizado (datas Tz, jsonb)
  • Enums SquadRole e MembershipAction
  • Recorder grava evento com action, from_role/to_role, actor_id, occurred_at
  • Eventos são append-only (nunca update/delete)
  • Teste do recorder

Decisões de escopo (worth a look)

  • Índices parciais deferidos. A ADR-0002 prevê UNIQUE (squad_id) WHERE role='captain' e a exclusividade UNIQUE (tenant_id, user_id) WHERE role IN (...) em squad_members. O feat(squads): pivot squad_members + SquadRole + recorder de eventos #355 pede só UNIQUE (squad_id, user_id), então não incluí os parciais aqui — eles pertencem às issues de join/exclusividade/liderança que introduzem as Actions que mantêm essas invariantes. Fácil de adicionar agora se preferirem cravar na criação da tabela.
  • metadata cast 'array' seguindo o precedente do repo (activity/MembershipEvent e outros); não há gate NoLooseArrayCasts nesta linha.

Testes

BDD

# language: pt
Funcionalidade: Pivot de membros e trilha de eventos
  Cenário: Recorder registra uma transição
    Dado um squad e um usuário
    Quando ocorre uma mutação de membership (ex.: join)
    Então um squad_membership_event é gravado com action, papéis from/to, ator e occurred_at

  Cenário: A trilha é imutável
    Dado um evento de membership já gravado
    Quando qualquer fluxo do módulo é executado
    Então o evento existente nunca é atualizado nem removido

…ts (#355)

- squad_members pivot (SquadMember) + SquadRole enum
- squad_membership_events append-only trail (SquadMembershipEvent, immutable) + MembershipAction enum
- RecordMembershipEvent recorder, reused by every membership/role mutation
- factories + recorder and immutability tests
@sirelves sirelves requested a review from a team July 9, 2026 19:17
@sirelves sirelves closed this Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant