createCalendarFeedLinkRecord failed with `overrideAccess:false` + `user: campaignUser` because Payload stripped the required `secretSlug`/`createdBy` fields (create access was admin-only) -> 400 behind the generic message; revokeCalendarFeedRecord silently no-opped (revokedAt update access also admin-only). - canCreateCalendarFeed now enforces advisor filterMunicipality scope on data (canCreateSupporter precedent) + canSetCalendarFeedSecret / canSetCalendarFeedRevocation permit staff to mint the secret on create and mark a feed revoked; secret read stays `() => false` and update admin-only. - CalendarFeed stamps createdBy via stampCampaignCreatedBy; secret create + revokedAt update use the new staff field accesses. - Action refactored to `*Record` (payload, actor) cores + thin wrappers; assertFilterMunicipalityInScope (tour precedent); console.error keeps the real error on the server while the friendly message stays. - 10 int tests for create/scope/revoke/leader/secret; fixture tracks calendarFeed cleanup. Closes #436
2.2 KiB
C96 — Feed iCal: interseptar escopo do criador no read (defesa em profundidade)
Status: rascunho Atualizado em: 2026-08-08 Issue: #455 Priority: P3 Model: composer-2.5 Model-local: deepseek-v4-flash-high Impeccable: A — N/A sem UI Appetite: ~0,25–0,5 dia eng Responsável: —
Intenção
Débito do /simplify do C92 (#436), aprovado no gate humano. O write-side do feed (canCreateCalendarFeed + assertFilterMunicipalityInScope) já impede um assessor de criar feed pinado a município fora do escopo. Mas o read-side (buildFeedWhere, src/utilities/calendarFeed.ts:104-112) confia no feed.filterMunicipality sem interseptar os ids acessíveis do criador quando accessibleMunicipalityIds é não-nulo. Consequência: se um assessor é removido de um município depois de criar o feed, o feed continua servindo atividades daquele município até o feed ser revogado.
Aceite
- No GET
/campanha/agenda/ical/[secret], quando o criador do feed é advisor efeed.filterMunicipalityestá setado, a consulta intersepta o município do filtro com os municípios atualmente administrados pelo criador — município fora do escopo atual → feed não inclui (404/feed vazio, alinhado ao fail-closed do C16). - Coordinator/candidate (unrestricted) e admin: comportamento inalterado.
- Sem mudança no write; sem migration (config/consulta only).
Direção no codebase
src/utilities/calendarFeed.ts→buildFeedWhere: quandofeed.filterMunicipalitysetado EaccessibleMunicipalityIdsnão-nulo (advisor), exigir{ id: { equals: filterMunicipality } } AND { id: { in: accessible } }(ou validar antes e 404).resolveFeedCreatorAccessjá calculaaccessibleMunicipalityIdsno read.- Precedente:
advisorMunicipalityScopeWhere(src/utilities/access/shared.ts). - Teste int:
tests/int/calendarFeed.int.spec.ts— feed criado por advisor em município X, depois advisor removido de X (assignMunicipalityAdvisors(mun, [])), GET do feed não lista atividades de X.
Fora de escopo
- Remodelar o modelo de acesso do feed (maior refactor).
- UI (C94) / sem-filtros (C93), que já são Issues separadas.
Questões em aberto
- Nenhuma — fail-closed orientado pelo C16 e pelo C92.