| 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) |
god modules, ui kit families |
dead code |
low |
M |
P2 |
closed 2026-07-25 (Pass 2 W4b): 0 findings; exports/types/enumMembers at error in CI; tests entered the knip graph; duplicates off (only hits were the 6 deliberate access aliases canUpdateX = canReadX) |
Pass 2 W4b |
knip loader noise: Error loading src/payload.config.ts (server-only guard) |
knip run output |
tooling noise |
none (exit 0, static graph resolves) |
S |
P3 |
open |
|
Aikido scan unusable: aikido_full_scan answers Opengrep exit code 2 for any input, including a one-line probe file, so the per-change security scan the rules mandate cannot complete. What it reported is an artifact of the failed run, not attributable to the code under review; the Aikido feed lists nothing for this repo. The shpjs devDependency added by B8 F2 is therefore SCA-unverified |
Aikido MCP / local scanner |
tooling outage (security gate) |
medium — the gate is skipped, not passed |
S |
P2 |
open 2026-07-26 (found during B8 F2) |
|
| Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins |
tests/helpers/testDatabaseLease.ts (withMissingInviteConsentFixture) |
test-infra race |
low (ms-wide window; shared leases added 2026-07-23 narrow it) |
M |
P2 |
open |
|
users collection has no roles (all admins omnipotent) |
src/collections/Users.ts |
RBAC |
medium (small trusted admin set) |
M (schema migration) |
P1 before widening /admin |
closed 2026-07-27 (admin|editor; migration 20260727_032523_add_users_roles; roles-admin-payload.md) |
AGENTS Known Gaps #1 |
Any campaign user can enumerate staff names/roles (canReadCampaignUsers) |
src/utilities/access/campaignUsers.ts |
info disclosure |
low |
S |
P2 |
open |
|
removeSupporterData deletes via overrideAccess: true while canDeleteSupporter is admin-only |
src/app/(campaign)/campanha/actions/supporter.ts |
access asymmetry (likely intentional LGPD path) |
low |
S |
P3 |
open — document or align |
|
Legacy public consent flows hardcode document IDs (consent: 2) |
src/app/(frontend)/actions/submitWhatsapp.ts |
correctness |
medium |
S |
P1 |
closed 2026-07-25 (Pass 2 D3): resolves Consent.key = 'whatsapp-inscricao' fail-closed; migration 20260725_170000 tags the live doc (id 2, key vazio) com a chave |
AGENTS Known Gaps #2 |
| E2E suite thin (7 specs; most campaign CRUD int-only) |
tests/e2e/ |
coverage |
medium |
L |
P2 |
open |
|
| CI runs on GitHub mirror only (no Codeberg/Woodpecker pipeline) |
.github/workflows/ci.yml |
process |
low |
M |
P3 |
open |
|
E2E campaign journeys flake with 2 workers — root cause narrowed 2026-07-26 (B13), and it is not only latency: (a) campaignMunicipalities/campaign-pwa click the leader consent checkbox before hydration attaches the handler, and (b) the municipality fixture picks names by regex, so "Conde" also matches "Condeúba". Reproduced on a clean main tree, so it is not a B13 regression; both specs pass in isolation |
tests/e2e/campaignMunicipalities.e2e.spec.ts, tests/e2e/campaign-pwa.e2e.spec.ts |
test-infra |
medium (masks real failures in the delivery gate) |
S |
P2 |
open — wait for an interactive-state signal instead of the element existing, and anchor fixture lookups to exact names/slugs instead of substring regex |
|
| Post listing cache holds all published posts, filters in memory |
src/utilities/posts.ts |
scale (fine at ~tens of posts) |
low |
M |
P3 |
open — watch corpus growth |
|
Petition generateStaticParams uses uncached getDocuments without select/depth: 0 |
src/utilities/documents.ts consumers |
over-fetch (build-time) |
low |
S |
P2 |
closed 2026-07-23 (Phase 2: getPetitionIds with depth: 0 + empty select) |
|
Compare mode (?compare=) still queries per candidate uncached cross-request |
src/utilities/municipalityMapData.ts |
perf |
low |
M |
P3 |
open — React cache() dedups in-request; add unstable_cache if profiling warrants |
|
Map bundle serializes all years × scenarios (~100KB raw), +63.6 KB after B13 (competitiveRankByYear is ~57 KB of it: three years × 417 codes × {rank, candidates}, paid on every dashboard load even by the coordinator who never opens the "posição no município" scale) |
municipalityMapContract.ts consumers |
payload size |
low — gzip crushes repeating keys to ~10KB on the wire; instant year/scale switching is a core analysis flow worth the bytes |
M |
P3 |
the 2026-07-23 deferral asked for profiling before acting; B13 supplied the number, so the trim is now scheduled as B13+ F4 (roadmap fill-in) |
B13+ F4 |
Props→state useEffect mirrors in list controls/comboboxes |
MunicipalityListExpectedVotesControl, AsyncSearchCombobox, StrictCombobox |
pattern |
none — reviewed 2026-07-23: guarded-equality sync is the correct "server echo vs local draft" reconciliation (A10 pattern), not derived state |
— |
— |
closed (accepted as correct) |
|
User-visible "Praça" copy survived the M1 rename (map panel title "Mapa das Praças", admin field descriptions, provisional Onda 0 consent texts, SupportStatusBadge summary, fallback/error strings, code comments) |
rg -n 'Praça' src (~96 hits in 53 files, ~55 user-visible — 2026-07-25 audit) |
copy drift |
low (cosmetic; consent texts are provisional Onda 0 copies) |
S |
P2 |
open — scheduled Pass 2 W4f (consent texts stay for the legal batch) |
Pass 2 W4f |
Access checks re-read the actor per Local API call: getFreshCampaignUser caches in req.context, but a payload.find({ user, overrideAccess: false }) that passes no req gets a fresh context, so one /campanha/municipios render issues ~5 identical campaignUser reads on top of the layout's own pair |
src/utilities/access/shared.ts + every overrideAccess: false caller |
perf (round-trip fan-out) |
low since the gru1 move (~2 ms each; it was ~120 ms each while functions ran in iad1) |
M |
P2 |
open — measured 2026-07-26. A React cache() layer would dedupe across requests, but it changes actor freshness inside server actions that mutate roles (reloadUnrestrictedActor), so it needs its own delivery with pins |
|
authenticateCampaignToken reads the actor twice: Payload's JWT strategy already fetches the user document, then findByID(depth: 1) re-reads it — the second read's only unique contribution is populating avatar for the sidebar |
src/utilities/campaignAuth.ts |
perf (a round trip in the layout, so on every page) |
low |
S |
P2 |
open — dropping it needs the avatar resolved another way, plus pins on removePrivateAuthFields and on role-downgrade freshness |
RS+ |