From 36ec79f2c2f078be57681a0a29d47d2e4f049381 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 3 Aug 2026 04:35:45 +0000 Subject: [PATCH] docs: Pass 5 engineering audit artifacts Ledger, quality-plan section, entrega-engenharia-p5, GUARDRAILS harvest (empty miss queue), TESTING e2e count 17/51, and CHANGELOG entry for the 2026-08-03 autonomous audit (delta since Pass 4). Co-authored-by: Francisco Solla --- docs/CHANGELOG-AGENTS.md | 2 + docs/GUARDRAILS.md | 8 +- docs/IMPROVE-CODE-QUALITY-PLAN.md | 46 +++++++- docs/TECH-DEBT.md | 15 ++- docs/TESTING.md | 2 +- docs/plans/entrega-engenharia-p5.md | 158 ++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 docs/plans/entrega-engenharia-p5.md diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 020e4852..6ef0a906 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**Recently resolved (2026-08-03):** **Pass 5 — auditoria de engenharia autônoma** — precheck solitário (pool desligado); delta desde Pass 4 = 627 arquivos / 520 commits; baseline verde (madge **859** arquivos). **0 P0**; **1 P1** — push de notificação via `queueMicrotask` sob `req.transactionID` corria antes do commit (`createCampaignNotification`) → after-commit registry + soft-fail. **0** `kind:agent-miss` abertas. Carry-forward P4-A…P4-L re-medido; ondas novas P5-A…P5-L em [`entrega-engenharia-p5.md`](plans/entrega-engenharia-p5.md). + **Recently resolved (2026-08-03):** **OPS18** (Issue #296) — promote dual plan-issue: Action `plan-issue-ready-on-main-merge.yml` no merge lê `Related #N` e promove Issues ainda aguardando plano (`canPromotePlanIssue`); idempotente com `pnpm agent:ready`. Planos: [`plan-issue-promote-dual.md`](docs/plans/plan-issue-promote-dual.md), impl [`plan-issue-promote-dual-impl.md`](docs/plans/plan-issue-promote-dual-impl.md). **Recently resolved (2026-08-02):** **OPS17** (Issue #292) — ciclo plan-issue: gate pós-overview antes de Issue/PR; `agent:register --plan` nasce `blocked`; após plano em `main`, `pnpm agent:ready` promove a `ready`. Planos: [`plan-issue-lifecycle-ready.md`](docs/plans/plan-issue-lifecycle-ready.md), impl [`plan-issue-lifecycle-ready-impl.md`](docs/plans/plan-issue-lifecycle-ready-impl.md). Action dual no merge = OPS18 (#296). diff --git a/docs/GUARDRAILS.md b/docs/GUARDRAILS.md index 5a13d902..1f7db963 100644 --- a/docs/GUARDRAILS.md +++ b/docs/GUARDRAILS.md @@ -41,4 +41,10 @@ Ledger dos guardrails determinísticos colhidos de `kind:agent-miss` (fluxo `pnp ## Misses colhidas aguardando guarda (fila do harvest) -(nenhuma — todas as 5 abertas em 2026-07-31 foram classificadas: 4 com guarda shippada + 1 com guarda já viva. Novas `kind:agent-miss` entram aqui até o próximo audit.) +(nenhuma — Pass 4 fechou as 5 abertas em 2026-07-31; Pass 5 (2026-08-03) colheu **0** `kind:agent-miss` abertas. Novas entram aqui até o próximo audit.) + +## Pass 5 — guardas adicionadas (não-miss) + +| Guarda | Origem | Classe | Mecanismo | Status | +| ----------------------------------------------------------------------------------------------- | -------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| Push de notificação de campanha só após commit da transação; send soft-fail se a row sumiu | P5-P1 | 3 + 5 | `onPayloadTransactionCommit` em `payloadTransaction.ts`; schedule em `createCampaignNotification`; pin em `notificationPushSchedule` unit | remediação Pass 5 (PR P5-P1) | diff --git a/docs/IMPROVE-CODE-QUALITY-PLAN.md b/docs/IMPROVE-CODE-QUALITY-PLAN.md index 671673c3..9c2bd5f9 100644 --- a/docs/IMPROVE-CODE-QUALITY-PLAN.md +++ b/docs/IMPROVE-CODE-QUALITY-PLAN.md @@ -1,6 +1,6 @@ # Improve Code Quality Plan -Four passes so far: **Pass 1** (2026-07-23/24, phases 0–6 below, all done), **Pass 2** (2026-07-25, see [Pass 2](#pass-2--engineering-consolidation-2026-07-25)), **Pass 3** (2026-07-28, audit + plan, see [Pass 3](#pass-3--engineering-audit--consolidation-2026-07-28)) and **Pass 4** (2026-07-31, audit + P0/P1 remediation + miss guardrails in-session, see [Pass 4](#pass-4--engineering-audit--remediation--guardrails-2026-07-31)). +Five passes so far: **Pass 1** (2026-07-23/24, phases 0–6 below, all done), **Pass 2** (2026-07-25, see [Pass 2](#pass-2--engineering-consolidation-2026-07-25)), **Pass 3** (2026-07-28, audit + plan, see [Pass 3](#pass-3--engineering-audit--consolidation-2026-07-28)), **Pass 4** (2026-07-31, audit + P0/P1 remediation + miss guardrails in-session, see [Pass 4](#pass-4--engineering-audit--remediation--guardrails-2026-07-31)) and **Pass 5** (2026-08-03, audit + P1 remediation in-session, see [Pass 5](#pass-5--engineering-audit--remediation-2026-08-03)). ## Context @@ -211,3 +211,47 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \ Boolean(user)`; local `function die()` vs CLI guard; allocator/e2e regex edges). +- **Rejected / look-alikes:** QuickActions host/overlay/FAB split (not twin of deleted Drawer); `searchOnlyListOmnibox` as degenerate adapter (only `q:` chip knowledge shared); municipality v2 route (product-owned parallel); HomeSearch `*GroupHasHits` too thin; profile/password `overrideAccess: true` on session-bound self-id (established trusted-server pattern — not P0/P1). + +## Remediation deliveries (in-session) + +| WS | Content | Status | +| ----- | ------------------------------------------------------------------------------------------------ | ------------------------------------------- | +| P5-P1 | Push-after-commit for campaign notifications + soft-fail missing row + convention/unit pin | own PR (Ready + auto-merge), same session | + +## Pass 5 Decisions + +| ID | Decision | Rationale | +| --- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| D1 | No P0 this pass — do not invent security theatre on session-bound self-update bypasses | IDs from `getCampaignUser()`; pattern matches WebAuthn/notification modules | +| D2 | P1 = transaction/push ordering only; P4-D mark-all/subscribe stays P2 | Partial mark-read is correctness debt already planned; push-before-commit can notify on rollback | +| D3 | municipality v2 parallel route is defer-until-cutover, not a consolidation defect | B147 shipped it deliberately; "edit the owner" applies at cutover | +| D4 | Miss fila empty → zero new miss guardrails; harden dodges only when shipping related fixes | Skill scope: miss guardrails + P0/P1; dodge map → plan P5-H | +| D5 | Aikido MCP still unavailable in this Cloud environment | Same D6 as Pass 4; ledger row unchanged | + +## Next Actions + +- [x] Artifacts PR = Ready + auto-merge +- [x] P5-P1 remediation PR = Ready + auto-merge +- [ ] P5-A → P5-L as independent deliveries ([entrega-engenharia-p5.md](plans/entrega-engenharia-p5.md)); oversized → `plan-issue` +- [ ] Leftovers → ledger via `capture-review-debts` diff --git a/docs/TECH-DEBT.md b/docs/TECH-DEBT.md index 918d6923..0bca0c46 100644 --- a/docs/TECH-DEBT.md +++ b/docs/TECH-DEBT.md @@ -14,7 +14,7 @@ Ledger of known engineering debt. Items large enough to be scheduled work become | 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 (intentional LGPD path) | low | S | P3 | closed 2026-07-29 (P3-D): documented in-code — the delete is the staff-driven titular-removal flow, scope already proven by `assertCanManageSupporter` on the exact row; day-to-day deletes stay admin-only | | | 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 (**17 specs / 48 cases** measured 2026-07-31, Pass 4 — grew from 15/31 at Pass 3; most campaign CRUD still int-only, so the substance holds) | `tests/e2e/` | coverage | medium | L | P2 | open | | +| E2E suite thin (**17 specs / 51 cases** measured 2026-08-03, Pass 5 — grew from 17/48 at Pass 4; most campaign CRUD still int-only, so the substance holds) | `tests/e2e/` | coverage | medium | L | P2 | open | | | CI runs on GitHub Actions only (no secondary 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 — mitigated 2026-07-31 (Pass 4): hydration helpers adopted (`checkRadixWhenHydrated`); the `/Cairu/i` substring literals are GONE and a catalog-keyed guard bans unanchored municipality-name regexes (miss #73, PR #93); prod full suite 48/48 green three times this session. What remains is the dev-mode `campaignLeaderships` chip failure — different class, own Pass 4 row below | | | Two e2e tests fail **deterministically at one worker on a clean `main` tree** (measured 2026-07-27 by stashing the whole B34 delivery and re-running): `campaignNearestMunicipality` "coordinator in Salvador is sent to the zone list" cannot find the `Salvador — ZE 1` link on `?q=Salvador`, and `campaignMunicipalities` "advisor opens a demand and decides it" cannot find its demand heading. These are **not** the 2-worker flake in the row above — retrying and lowering concurrency do not help — so the gate currently ships with two red specs whose failure nobody has diagnosed. **Widened 2026-07-28 (B17), same stash-and-compare method: it is not two tests but most of `campaignMunicipalities`** — five failed at one worker on the clean tree (`:56`, `:125`, `:292`, `:394`, `:446`) against four with the delivery applied, and the failing set is not stable between runs (`page.waitForURL` timeouts, a `getByLabel('Média')` strict-mode violation matching both the popover trigger and its input, a click whose target the bottom nav intercepts). Practical consequence for the next delivery: comparing the count of red tests against a stashed tree is the only way to read this spec — naming the two tests above is no longer enough. **2026-07-28 (Pass 3): root cause measured** — unanchored municipality-name regex at `campaignMunicipalities.e2e.spec.ts:263` (**23/435 prefix collisions** in the catalog: Conde/Condeúba, Catu/Caturama, Laje ×3, Barra ×4); the `getByLabel('Média')` strict-mode violation and the hydration-race consent checkbox are unchanged; fixture drift measured — the e2e cleanup lacks E14's `engagementLevel`/`levelNote`/catalog-name reset and owns+deletes the leased `apoiador-cadastro` row. Fix scheduled as **P3-C** ([entrega-engenharia-p3.md](plans/entrega-engenharia-p3.md)) | `tests/e2e/campaignNearestMunicipality.e2e.spec.ts:156`, `tests/e2e/campaignMunicipalities.e2e.spec.ts:56,125,292,394,446` | broken test (or real regression) | high — a permanently red gate trains everyone to ignore it, which is how a real failure ships | S | **P1** | closed 2026-07-29 (P3-C): full suite **31/31 green** at 2 workers. Root causes fixed — stale sort-summary copy (`déficit da meta`→`Cobertura`), `exact` trigger name vs appended status suffix (now anchored regex), option click not selecting (role=option + `toPass` hydration-retry loop, same class as `checkRadixWhenHydrated`), stretched-overlay click needing `force`, leader login using the advisor's password (each `createCampaignUser` mints its own), and 30 s test timeout → 60 s under cold-compile load. Guards: unanchored-template-regex ban in `codebaseConventions`, global `expect` 10 s | | @@ -57,13 +57,22 @@ Ledger of known engineering debt. Items large enough to be scheduled work become | Unconditional `aria-live="polite"` per chip cell (~50 always-registered regions on lideranças); the overlay family already lazy-mounts its region from first open | `src/components/campaign/shared/RelationChipCell.tsx:821-823` vs `CampaignCellEditOverlay.tsx:114-120` | a11y scale | low | S | P2 | open — Pass 4 | P4-G | | Bypass ratchet dodge: one "bypass" comment in the first 40 lines documents **every** `overrideAccess: true` in the file — `notification/*` has 20, `campaignWebAuthnCeremony` 4, zero per-call justifications | `codebaseConventions.unit.spec.ts:541-623`; `src/utilities/notification/*`; `src/utilities/webauthn/campaignWebAuthnCeremony.ts` | guard dodge | medium — the P3-E "comment justifying" intent is satisfied in letter, not spirit | M | P2 | open — Pass 4 | P4-H | | `campaignLeaderships` chip-cell spec fails **deterministically in dev mode** on clean stage (4/4 runs; stash-and-compare 2026-07-31): instrumented with console/response listeners — **no POST fires in 60 s**; signatures vary per run (option visible + click no-ops; option count 1 → 0 after first fill). Prod build green (48/48 full suite ×3 the same day). NOT the P3-C hydration class (input onChange works, listbox renders); a `toPass` retry was attempted and reverted — retrying cannot fix what is not a race | `tests/e2e/campaignLeaderships.e2e.spec.ts:91-101`; suspect surface `RelationChipCell` suggestion state vs dev-mode RSC streaming of `municipalityIndex` | test-infra / possible dev-mode cell bug | medium — a red local signal teaches people to skip the spec | M | P2 | open — Pass 4 (debug workflow, not retry; `E2E_PROD=1` is the honest local mode meanwhile) | P4-L | -| God modules on the delta: `municipalityPageData` 548 lines / `loadMunicipalityListPageBundle` 168; `municipalityListUrl` 500 lines / 23 exports (widest interface in utilities); `RelationChipCell` 826 lines / ~11 state hooks (measurement 80, commit 140, render 190) | `src/utilities/municipality/municipalityPageData.ts:307`, `src/utilities/municipality/municipalityListUrl.ts`, `src/components/campaign/shared/RelationChipCell.tsx` | bloater | low | L | P2 | open — Pass 4 (defer + trigger: next facet/sort; next chip relation or AT-scale complaint) | | +| God modules on the delta: `municipalityPageData` 503 lines / `loadMunicipalityListPageBundle` 168; `municipalityListUrl` 500 lines / 23 exports (widest interface in utilities); `RelationChipCell` 841 lines / ~11 state hooks (measurement 80, commit 140, render 190) | `src/utilities/municipality/municipalityPageData.ts:307`, `src/utilities/municipality/municipalityListUrl.ts`, `src/components/campaign/shared/RelationChipCell.tsx` | bloater | low | L | P2 | open — Pass 4 (defer + trigger: next facet/sort; next chip relation or AT-scale complaint) | | | `createSupporter` does not mark `touchedMunicipalities` — a supporter-only fixture path can leave strategy fields dirty on the row | `tests/helpers/campaignFixtures.ts:769-799` | test-infra honesty | low | S | P3 | open — Pass 4 | | | `(app)/assessores/novo/page.tsx` redirects without `requireCampaignPageActor` — the P3-I guard bans `getCampaignUser()` in pages but not a missing actor helper (1 of 34 pages) | `src/app/(campaign)/campanha/(app)/assessores/novo/page.tsx:1-6` | convention gap | low (layout still session-gates) | S | P3 | open — Pass 4 | | | `deriveDecisionAuthor` re-spells the actor stamp (`data.decidedBy = req.user.id`) while `campaignAuditFields.ts` owns the pattern (`stampCampaignCreatedBy` hardcodes `createdBy`) | `src/collections/AllocationDecision.ts:53-57` | duplication | low | S | P3 | open — Pass 4 | | -| homeSearch int suites cloned ×7 (656 LOC of same-shaped `it('returns empty…'/'scopes…'/'rejects leaders…')` — `describe.each` over domains would own the shared contract | `tests/int/homeSearch*.int.spec.ts` | duplication | low | M | P3 | open — Pass 4 | | +| homeSearch int suites cloned ×7 (662 LOC of same-shaped `it('returns empty…'/'scopes…'/'rejects leaders…')` — `describe.each` over domains would own the shared contract | `tests/int/homeSearch*.int.spec.ts` | duplication | low | M | P3 | open — Pass 4 | | | Flake inventory: fixed timeouts (`CEREMONY_TIMEOUT=60s`, HomeActions `timeout: 15000` ×many, ColumnPicker `REFRESH=20s`), `Date.now()` markers in fixture names, e2e deep links now claimed (Pass 4 PR #93) but shared leased consent rows remain load-bearing | `tests/e2e/*`, `tests/int/municipalityPageData.int.spec.ts` | flake risk | low | M | P3 | open — Pass 4 (prefer event waits; allocate municipalities — both guarded since Pass 4) | | +| **P5-P1 (remediated in-session):** `createCampaignNotification` scheduled push with `queueMicrotask` under `req.transactionID` — microtasks run before `withPayloadTransaction` commit, so push could fire for an uncommitted / rolled-back notification | `src/utilities/notification/createCampaignNotification.ts:38-40`; `src/utilities/payloadTransaction.ts:40-65` | correctness (txn ordering) | high — push for phantom notification; noisy failure on rollback | S | P1 | open — remediating Pass 5 (P5-P1 in-session) | P5-P1 | +| Wizard/action href knowledge dual-switched (`wizardActionChain` chain + previous) and wide URL surface (`campaignActionRoutes` 252 LOC / 31 exports) | `src/lib/wizardActionChain.ts`; `src/lib/campaignActionRoutes.ts` | duplication + wide interface | medium — every new wizard step touches both switches | M | P2 | open — Pass 5 | P5-A | +| Staff-detail wizard action id list re-spelled ×3 (activity / leadership / demand adapters) | `src/lib/activityQuickActions.ts:18-24`; `campaignQuickActionLeadership.ts:19-25`; `campaignQuickActionDemands.ts:14-20` | duplication | low | S | P2 | open — Pass 5 | P5-B | +| `/campanha/acoes/[slug]` page is a 296-line auth+load+branch dispatcher | `src/app/(campaign)/campanha/(app)/acoes/[slug]/page.tsx` | bloater / divergent change | medium — every quick-action lands here | M | P2 | open — Pass 5 | P5-C | +| Detail pages double-run actor+load in `generateMetadata` and page body (org / dobradinha / demanda) | `organizacoes/[slug]/page.tsx`, `dobradinhas/[slug]/page.tsx`, `demandas/[slug]/page.tsx` | duplication | low — extra DB round-trips per detail view | S | P2 | open — Pass 5 | P5-D | +| Client auth forms value-import pure helpers from `utilities/campaignFormFields` | `LoginForm.tsx`, `ForgotPasswordForm.tsx` ← `src/utilities/campaignFormFields.ts` | boundary | low | S | P2 | open — Pass 5 | P5-E | +| Guard dodges measured: `globalAccessConventions` only `=> Boolean(user)`; Local API shorthand `user,`; scripts local `function die(`; allocator find-by-slug+mutate / encoded `municipio`; e2e settle tokens in comments | `tests/unit/*Conventions.unit.spec.ts`; `scripts/check-plans-only-pr-closes.mjs:12-15` | guard dodge | medium on access guards | M | P2 | open — Pass 5 | P5-G | +| `/campanha/municipio/[slug]/v2` parallel detail shell (B147) alongside `/municipios/[slug]` | `src/app/(campaign)/campanha/(app)/municipio/[slug]/v2/` | intentional parallel — cutover debt | low until cutover; then divergent change | M | P3 | open — Pass 5 (defer + trigger: v2 replaces v1) | B147 | + ## Smell Inventory | Smell | Location | Refactoring | Status | diff --git a/docs/TESTING.md b/docs/TESTING.md index 81ec234b..ae700f5b 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -6,7 +6,7 @@ Three layers, all local-database-only (see `local-database` skill; tests refuse - **Unit** (`tests/unit/**/*.unit.spec.ts`, 117 specs as of 2026-07-31, Pass 4 — +6 convention guards land with the Pass 4 remediation PRs): pure logic; runs with a deliberately invalid `DATABASE_URL` so nothing can touch a database. - **Integration** (`tests/int/**/*.int.spec.ts`, 71 specs): real Payload + Postgres (`teqo_test`); campaign fixtures allocate seeded municipalities via a Postgres sequence so parallel spec files never share one, and purge residue on claim. Since the miss #73 guardrail (Pass 4), specs that mutate municipalities must use the allocator (no pinned slugs) — enforced by `testMunicipalityAllocatorConventions`. Shared consent rows with stable keys are leased (`tests/helpers/testDatabaseLease.ts`), never owned. -- **E2E** (`tests/e2e`, Playwright, 17 specs / 48 cases): smoke-level; most campaign behavior is pinned at the int layer. +- **E2E** (`tests/e2e`, Playwright, 17 specs / 51 cases (Pass 5, 2026-08-03; was 48 at Pass 4)): smoke-level; most campaign behavior is pinned at the int layer. CI (`.github/workflows/ci-pr.yml`) runs parallel jobs — lint (zero warnings), format, typecheck, knip, cycles, unit/int (**affected by the PR diff**: `vitest --changed` against the PR base via `scripts/test-affected.mjs`, full-suite fallback on high-risk paths), e2e (manifest path→spec via `scripts/e2e-affected.mjs` + `scripts/lib/e2e-affected-manifest.mjs`, blocking; production build + `list`/`github` reporters in CI) and build — against a Postgres 17 service with **`db:seed:minimal`**. Push to `main` (`ci.yml`) always runs the full net including e2e. diff --git a/docs/plans/entrega-engenharia-p5.md b/docs/plans/entrega-engenharia-p5.md new file mode 100644 index 00000000..d856e359 --- /dev/null +++ b/docs/plans/entrega-engenharia-p5.md @@ -0,0 +1,158 @@ +# Pass 5 — Auditoria de engenharia e remediação P1 + +## Contexto e manchetes + +- **Executado em 2026-08-03, modo autônomo (Cursor Cloud)** da skill `engineering-audit`: precheck solitário verde (pool **desligado**), varredura completa com foco no delta desde o Pass 4 (2026-07-31), **0** `kind:agent-miss` abertas, **1 P1** remediado na mesma sessão em PR próprio. Artefatos = Ready + auto-merge (contrato `agent-pr-workflow`). +- **Âncora de delta:** Pass 4. **627 arquivos** / **520 commits** — quick-actions, wizard/omnibox, e2e municipalities, home actions, shell v2 de município (B147), OPS17/18. +- **Baseline verde:** `tsc` 0, `lint` 0 warnings, `knip` 0 findings (ruído conhecido `payload.config.ts`), madge 0 ciclos (**859** arquivos, +85 desde Pass 4). +- **Manchetes (medidas):** + - **0 P0** — lockdown de globals do Pass 4 segurou; collections com `access`; consent fail-closed. + - **1 P1** — `createCampaignNotification` agenda push com `queueMicrotask` sob `req.transactionID`; microtask corre **antes** do `commitTransaction` de `withPayloadTransaction` → push pode sair para linha ainda não commitada / depois revertida. + - **Fila de misses vazia** — nenhuma guarda nova de miss; GUARDRAILS.md verificado vivo. + - **Ondas P4-A…P4-L ainda abertas** (re-medidas) — o Pass 5 **não** as reabre com IDs novos; cita e atualiza contagens no ledger. + - **Delta novo:** dispatcher `/acoes/[slug]` (296 LOC), dual-switch de href do wizard, lista de action ids de staff ×3, prólogos metadata+page, `campaignFormFields` em `utilities` no client, dodges de guarda. + +## Protocolo de delta de comportamento + +Inalterado do Pass 3/4: consolidação pode mudar saídas pequenas listadas por item; pins atualizados deliberadamente; URL B18 / schema / Consent fora de alcance sem item nomeado. + +## Onda já executada na sessão (P1) + +| PR | Conteúdo | Guarda (classe) | +| ------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| P5-P1 | After-commit registry em `withPayloadTransaction` + schedule de push; `sendCampaignPushForNotification` soft-fail se a row sumiu | pin unit do registry (5); convenção: push de notificação nunca via `queueMicrotask` sob txn (3) | + +## Ondas do plano (P2→P3, ordenadas por risco × churn do delta) + +### Onda 1 — Carry-forward Pass 4 ainda aberto (não re-planejar do zero) + +Executar na ordem do [`entrega-engenharia-p4.md`](entrega-engenharia-p4.md). Contagens Pass 5: + +| Item | Medição 2026-08-03 | +| ---- | ------------------ | +| P4-A homeSearch | `utilities/homeSearch` **837** LOC; `limit: 0` ×5 | +| P4-B groups | 6 × `HomeSearch*Group` | +| P4-C notificationEvents | 254 LOC; `findByID`×7 | +| P4-D write path | mark-all `limit: 200` + `Promise.all`; subscribe sempre `create`; unsubscribe bypass delete | +| P4-E wizard drawers | Signal/Trend/Leadership info chrome; Signal/Trend skip trailing ×2 | +| P4-F labels | `MunicipalityListMobileCards` value-import de `municipalityListUrl` | +| P4-G | provider no chrome staff; `RelationChipCell` **841** LOC + `aria-live` incondicional | +| P4-H…L | ratchet bypass, `canReadCampaignUsers`, auth double-read, fixtures, chips-dev flake | + +### Onda 2 — Consolidação do delta (quick-actions / wizard / rotas) + +#### P5-A Dispatcher tipado de href de wizard/ação + +- **Meta:** um `wizardHrefForAction(action, ctx)` (options object); `wizardActionChain` deixa de ter dois `switch` espelhados; enxugar exports de `campaignActionRoutes` (252 LOC / **31** exports). +- **Conhecimento duplicado:** mapa ação→href do wizard/chain. +- **Guarda:** pins `campaignActionRoutes.unit` + `wizardActionChain.unit` (5); tipo esgota o union de ações (1). +- **Esforço:** M · **Sev:** P2 + +#### P5-B `STAFF_DETAIL_WIZARD_ACTION_IDS` single-source + +- **Meta:** a lista idêntica em `activityQuickActions` / `campaignQuickActionLeadership` / `campaignQuickActionDemands` vira constante + helper de prefill de município. +- **Guarda:** unit do registry (5). +- **Esforço:** S · **Sev:** P2 + +#### P5-C `/acoes/[slug]` → loader + branches finas + +- **Meta:** `page.tsx` (296 LOC) perde auth/payload/URL assembly para `utilities`/view-model; a page só compõe. +- **Guarda:** convenção de prólogo de página (3) — mesmo espírito P3-I. +- **Esforço:** M · **Sev:** P2 + +#### P5-D Prólogo metadata+page sem double-load + +- **Meta:** helper tipado `requireCampaignDetailPage(slug)` (ou cache React já existente) para org/dobradinha/demanda detail — hoje `generateMetadata` e page repetem o ator+load. +- **Guarda:** pin de convenção ou `cache()` obrigatório no loader (3/1). +- **Esforço:** S · **Sev:** P2 + +### Onda 3 — Fronteiras e guardas + +#### P5-E `campaignFormFields` → `lib/` (ou contrato client-safe) + +- **Meta:** Login/ForgotPassword importam runtime de `utilities/campaignFormFields` — helper puro deve viver em `lib/`. +- **Guarda:** ESLint ou convention: client em `(campaign)` não value-importa `@/utilities/**` fora allowlist (2/3). +- **Esforço:** S · **Sev:** P2 + +#### P5-F Labels de coluna fora de `municipalityListUrl` (= P4-F, priorizar) + +- Já no plano P4; Pass 5 só reafirma: churn mobile+omnibox sobe a prioridade. + +#### P5-G Endurecimento de dodges de guarda (mapa) + +| Dodge | Guarda atual | Endurecimento | +| ----- | ------------ | ------------- | +| `!!user` / bloco `return Boolean(user)` / helper em subdir de globals | `globalAccessConventions` | banir predicados "truthy user" genéricos + sweep recursivo | +| `user,` shorthand / alias de método Local API | `localApiOverrideAccessConventions` | cobrir shorthand e `payload.db` paths críticos | +| `function die(` local em scripts | `scriptCliConventions` | banir die local rotulado; exigir `dieWithLabel` | +| `payload.find`+slug fixo+mutate; `URLSearchParams` municipio | allocator conventions | cobrir find-by-slug+mutate; encoding | +| wrapper `goto` / settle em comentário | e2e navigation | tokens de settle só em statements | + +- **Esforço:** M · **Sev:** P2 (classe segurança nos dois primeiros) + +### Onda 4 — Escala / a11y / debug (carry + delta) + +#### P5-H homeSearch bounded query (= P4-A elevado em prioridade) + +- Mesma meta P4-A; Pass 5 confirma custo: route dispara 6 loaders por keystroke com `limit: 0`. +- **Sev permanece P2** (não P1): sem gate vermelho nem vazamento de ACL; é harm de escala medido. + +#### P5-I `RelationChipCell` live-region + tamanho (= P4-G) + +- Trigger Pass 4 ainda válido; **841** LOC. + +#### P5-J Flake chips-dev `campaignLeaderships` (= P4-L) + +- Continua: debug com evidência, não retry. + +#### P5-K Fixtures `createSupporter` → `touchedMunicipalities` + +- Uma linha no helper; pin de cleanup (P3). + +#### P5-L `assessores/novo` + `deriveDecisionAuthor` + +- P3: redirect usa `requireCampaignPageActor` (ou exceção documentada na guarda P3-I); stamp via `campaignAuditFields`. + +## Defer + gatilho (sem ID novo de entrega) + +| Item | Gatilho | +| ---- | ------- | +| `/campanha/municipio/[slug]/v2` vs `/municipios/[slug]` | cutover B147 filhos / remoção do v1 | +| `suggestionCatalog` 646 LOC | 9º pattern ou 2º consumer do evaluator | +| `MunicipalityMapPanel` / `BahiaMap` | próxima feature que toque orquestração+camada | +| `municipalityListUrl`/`Omnibox` puros em `utilities` | 3º adapter omnibox novo no mês **ou** P4-F labels move | +| Omnibox `q:` chip helpers | 3º search-only adapter além dos atuais | + +## Look-alikes rejeitados + +- Profile/password `overrideAccess: true` com `id: user.id` da sessão — padrão trusted-server, não IDOR. +- QuickActions host/overlay/FAB — evolução do Drawer antigo, não gêmeo. +- `searchOnlyListOmnibox` — adapter degenerado legítimo; só o mecânico `q:` é conhecimento compartilhado. +- HomeSearch `*GroupHasHits` — predicados finos demais para abstrair sozinhos. + +## Mapa de guardas + +### Novas nesta sessão + +| Guarda | Classe | Origem | +| ------ | ------ | ------ | +| Push de notificação só após commit (registry) + soft-fail row ausente | 3 + 5 | P5-P1 | + +### Existentes reafirmadas / a endurecer (P5-G) + +Ver tabela da onda 3. Miss fila: vazia. + +### Judgment-only + +| Item | Convenção | +| ---- | --------- | +| Parallel municipality v2 shell | PRODUCT/B147; cutover apaga o desvio | +| Map god components | trigger já no ledger Pass 4 | +| Navegação e2e in-shell vs goto | TESTING.md (já declarado judgment-only no Pass 4) | + +## Regras de execução + +- Uma consolidação por entrega; gate completo bare (`tsc`, lint, format, knip, cycles, test, build) + Aikido onde MCP existir. +- Commit de estrutura ≠ commit de comportamento; teste vermelho no meio = revert. +- Migrations congeladas; guarda na **mesma** entrega do fix. +- Nada vive só no chat — leftovers → `capture-review-debts`.