mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
docs(audit-pass6): artefatos — seção Pass 6, ledger truth-up, plano de entrega
This commit is contained in:
parent
149cc3943a
commit
8a1aa20ce9
4 changed files with 176 additions and 22 deletions
|
|
@ -254,3 +254,50 @@ Statuses: pending · in-progress · awaiting-evidence · done · deferred: \<rea
|
|||
- [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`
|
||||
|
||||
---
|
||||
|
||||
# Pass 6 — Engineering Audit (2026-08-25)
|
||||
|
||||
## Context
|
||||
|
||||
- **Started:** 2026-08-25 — first execution of the **v2 skill** (autonomous, sub-agents, single-branch delivery, single PR **sem auto-merge** — OPS98): varredores por área em paralelo → caçador de consolidação → escritores `-impl` → implementadores em série na MESMA branch `audit/pass-6`.
|
||||
- **Delta anchor:** Pass 5 (2026-08-03). **681 non-merge commits** (~910 total) in 22 days, **2.041 unique files** touched. New domains since the anchor: `people/` (9 modules), `ai/` (6), `contacts/` (5), calendar trio (`googleCalendarSync` 949 LOC + client + hooks + `calendarFeed`) + **2 new collections** (`CalendarFeed`, `GoogleCalendarSync`) + `tour/`, `suggestion/`; the **wizard domain was DELETED** (OPS95) — several Pass 4/5 rows die with it. E2E suite grew **17 → 43 specs**; OPS71→OPS98 landed (GitHub CI/tracker, homeserver deploy, single-branch audit paradigm).
|
||||
- **Method:** canon loaded (rules, ARCHITECTURE, AGENT-OPS, TECH-DEBT, TESTING, GUARDRAILS, precedents, rejected-with-reason); hotspot map (delta + churn + static gates); **six parallel sweeps** (lib, utilities+access, components, (campaign), collections+globals+(frontend)+(payload), scripts+tests) with measured findings; consolidation hunt under the anti-DRY rule; **2 open `kind:agent-miss`** harvested (#909 disarm race, #895 literal-decision data); every open ledger row re-verified (5 stale closed with evidence); P1s + miss guardrails implemented in-session on the pass branch.
|
||||
- **Baseline:** green — `tsc` 0, `lint` 0 warnings, `knip` 0 findings (known `payload.config.ts` loader noise, ledger P3 — verified still true), madge 0 cycles (**1.526** TS files src+tests, +247 since Pass 5's 859… measured as 1.104 src + 422 tests).
|
||||
|
||||
## Audit headlines (all measured)
|
||||
|
||||
- **0 P0.** Security invariants held across all six sweeps: new collections declare `access` (`CalendarFeed.ts:31`, `GoogleCalendarSync.ts:39`); actor fields use the factory (`systemStampedActorField`, `CalendarFeed.ts:101`); consent fail-closed held; transactions held on campaign writes. The one `overrideAccess`-heavy new module (`people/personDelete.ts`, 28 bypasses) is scope-guarded per site (verified) — recorded as guard-decay P1, not a hole.
|
||||
- **2 P1:** (a) the P3-E bypass ratchet is dodged by blanket module comments — 1 header covers 28 bypasses in `personDelete.ts`, 22 in `notification/*`, 6 in `googleCalendarSync.ts`, and new files (`peopleData`, `personDetail`, `calendarFeed`, `loadNamesByIds`) are absent from `pinnedUndocumented` so a fresh file ships N undocumented bypasses invisibly; (b) the P3-I page gate is dodged by name variant — `perfil/page.tsx:25` calls `getCampaignUserWithAvatar()` (regex matches only `getCampaignUser\(`) and hand-rolls the redirect, bypassing the B43 leader gate. Both remediated in-session.
|
||||
- **2 misses → 2 guardrails:** #909 (testing-audit automerge-disarm race) — skill fix was judgment-only; deterministic residual shipped as `scripts/testing-audit-disarm.mjs` (create→disarm→verify-null atomic) wired into the skill; #895 (intention plan without literal decision data) — no deterministic mechanism can detect missing data in prose → class-6 convention registered in the `plan-issue` skill (declared judgment-only).
|
||||
- **Guard dodges measured (the dominant class this pass):** CLI skeleton dodged twice (4 local `function die()` + ~20 raw `process.exit(1)`, `scriptCliConventions` sees neither); knip's `entry: ["scripts/*.mjs"]` + test-imports-as-usage keep 2 fully dead modules (`projectionSheetParse.ts` 173 LOC, `searchOnlyListOmnibox.ts` 98 LOC — 0 production importers) and 3 dead scripts alive; actor-field guard regex covers only 3 field names (`createdBy|decidedBy|declaredBy`); `server-only` sweep is blind to `next/navigation`; DAY_MS re-spelled as bare `86_400_000` ×11 in tests + 1 in lib (`campaignTime.ts:134`); `as unknown as` ×21 files in tests serves as the unguarded twin of the `as never` ban; form-action filename shapes (`taskActions.ts` etc.) dodge the ladder guard and one (`taskActions.ts:20-27`) already passes raw `error.message` to the UI.
|
||||
- **Consolidation measured:** filter-bar shells ×13 (~1.700 LOC, ~60-LOC shared machine, 9/13 clean); saved-filters machinery ×6 re-spelled despite the committed comment "must not be written twice" (`useMunicipalitySavedFilters.ts:26`); advisor-relation cell twins ×3 (89/96/99 LOC); `chipLabel` re-spelled in 12/13 omnibox files; `searchHome*` skeletons ×6 (847 LOC, cap under two names); `fixtures.own()` 49 → **75**; homeSearch int triplet 346 LOC.
|
||||
- **Ledger hygiene:** 5 stale rows closed with evidence (P4-E wizard drawers, P4-F mobile-cards import, P4-G provider wrapping, assessores/novo actor helper, P5-A wizardActionChain — the wizard domain died in OPS95 and the redirect-only page needs no actor); counts updated (P4-A…P4-D, P4-H, god modules, P5-B…P5-G, e2e specs, homeSearch suites); 19 new rows (P2/P3).
|
||||
|
||||
## Remediation deliveries (in-session, on `audit/pass-6`)
|
||||
|
||||
| WS | Content | Guard (class) | Status |
|
||||
| ----- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------ |
|
||||
| P6-1 | Bypass-ratchet hardening: header-covered bypasses count; pin new files at current counts; per-site comment = 0 | 3 — `codebaseConventions` bypass ratchet (P3-E hardened) | done |
|
||||
| P6-2 | `perfil` uses `requireCampaignPageActor`; P3-I regex widened to `getCampaignUser[A-Za-z0-9]*\(` | 3 — `codebaseConventions` page-actor guard (P3-I hardened) | done |
|
||||
| P6-M909 | Miss #909: `scripts/testing-audit-disarm.mjs` (create→disarm→verify-null, atomic) + skill wiring | 3 — script invoked by the skill's PR step (order-proof) | done |
|
||||
| P6-M895 | Miss #895: `plan-issue` skill convention — intention plans record literal decision data (tables/IDs), not narrative | 6 — declared judgment-only (no deterministic detector for missing prose data) | done |
|
||||
|
||||
## Pass 6 Decisions
|
||||
|
||||
| ID | Decision | Rationale |
|
||||
| --- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| D1 | One branch, one PR, no auto-merge (OPS98 contract) | Artifacts + implementations commit separately on `audit/pass-6`; the safety-net vetoes `audit/*` by construction |
|
||||
| D2 | Implementations = P0/P1 S/M only (cap 6): 2 P1 guard-hardenings + 2 miss guardrails | Everything else goes to `entrega-engenharia-p6.md` + ledger; P2 duplicates the last three passes' deferred pile |
|
||||
| D3 | Guard-hardening IS the fix for the two P1s — product code untouched where scope-guarded | Both P1s are guard decay, not live holes (verified per site); hardening the spec is cheaper and safer than re-writes |
|
||||
| D4 | Stale rows closed only with re-measured evidence; wizard rows closed as superseded (OPS95) | The wizard domain's deletion makes P4-E/P5-A moot — closing on code absence, not on fix |
|
||||
| D5 | #895 guardrail is class 6, declared judgment-only | Prose can't be deterministically checked for missing data; pretending otherwise would fake a guard (4b rule) |
|
||||
| D6 | knip `scripts/*.mjs` entry-list dodge → ledger, not config change in-pass | Changing knip's entry model mid-pass risks unrelated CI churn; deletion of the 3 dead scripts rides P6-G |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- [x] Three artifacts + 4 implementations committed on `audit/pass-6`
|
||||
- [x] Single Ready PR, base `main`, **sem auto-merge**, report-as-description
|
||||
- [ ] P6-A → P6-V as independent deliveries ([entrega-engenharia-p6.md](plans/entrega-engenharia-p6.md)); oversized → `plan-issue`
|
||||
- [ ] Leftovers → ledger via `capture-review-debts` (new rows already in TECH-DEBT.md)
|
||||
|
|
|
|||
|
|
@ -47,31 +47,55 @@ Ledger of known engineering debt. Items large enough to be scheduled work become
|
|||
| Constants/types single-sourcing cluster: territorial-class union declared twice across the lib/utilities boundary (adding a class compiles green and mis-sorts); `DAY_MS` ×3 (one exported from `suggestionCatalog`'s public interface); `1_000_000` vote-count ceiling ×6 across two validation layers; password `8/128` + token `20/256` policies ×2; TSE baseline years mirrored (documented excuse dies with a leaf module); `formatBrazilianPhoneDisplay` near-twin (1 call site); `advisorMunicipalitiesBatchSchema` missing the dedup transform its 3 siblings have; `voteEstimate` formatters re-spell the pt-BR format without the E18 zero-fraction pin; `hasAnyEstimate` weaker twin of `hasAnyVoteEstimate` | `lib/territorialClassSortWeight.ts:10-13`, `lib/suggestionCatalog.ts:15-19,175`, `lib/engagementLevel.ts:97`, `utilities/municipalitySignal.ts:32`, `lib/formData.ts:123-133`, `lib/schemas/votePledge.ts:9,32`, `lib/schemas/invite.ts:11,39`, `lib/voteEstimate.ts:27,102-126`, `lib/schemas/advisor.ts:69-72` | shotgun surgery | low-medium | S each | P3 | closed 2026-07-29 (P3-K): territorial-class union → `lib/territorialClassAnchors.ts` (type error on new class now); `DAY_MS` → `lib/text.ts` (out of `suggestionCatalog`'s public interface); `MAX_VOTE_COUNT` in `lib/schemas/primitives.ts` ×6 folded; `campaignPasswordSchema` exported (invite delegates); `lib/electionYears.ts` leaf (mirror + documented excuse died); `formatBrazilianPhoneDisplay` delegates; batch-schema dedup transform (action's `[...new Set]` died); `formatElectionNumber` in the 3 `voteEstimate` formatters; `hasAnyEstimate` deleted | P3-K |
|
||||
| Form-action feedback JSX at 15+ call sites with `aria-live` on only 5 — **10 error alerts silent to AT**; success→toast effect ×7; borderless inline-input class ×4 verbatim; deprecated `showLabels` prop with zero call sites (knip can't see props) | 15 forms incl. `LeadershipForm.tsx:162`, `DemandForm.tsx:152`, `MunicipalityUpdateForm.tsx:168`, `PledgeEstimateForm.tsx:74`; `VoteEstimateScenarioStrip.tsx:18,27,32` | a11y + duplication | medium (a11y) | S–M | P3 | closed 2026-07-29 (P3-G): `CampaignFormActionMessage` (aria-live on both variants + `formFieldErrors`) adopted by all 15+ sites — every alert now announces; `useCampaignFormSuccessToast` adopted ×7; `campaignInlineInputClassName` ×4; `showLabels` deleted; JSX ban guard + a11y pin (`campaignFormActionMessage.unit.spec.tsx`, unit include extended to `.tsx`) | P3-G |
|
||||
|
||||
| homeSearch text-search pipeline re-spelled per domain (gate staff → normalize → `contains` + `limit: 0` → word-start → relevance → cap), plus **unbounded `limit: 0` reads** ×5 before the in-memory refine | `src/utilities/homeSearch/searchHomeActivities.ts` (88) ↔ `searchHomeDemands.ts` (100), sim 0.72 (~63 shared lines); leaderships 0.53; `limit: 0` at `searchHomeActivities.ts:43`, `Demands:49`, `Leaderships:61`, `Advisors:37`, `StateDeputies:45` | duplication + scale | medium — every keystroke can pull a full collection slice | M | P2 | open — Pass 4 | P4-A |
|
||||
| HomeSearch result groups ×6 near-verbatim (Advisor↔Leadership, Activity↔Demand almost line-for-line; differ only in id/title/href/secondary) | `src/components/campaign/dashboard/HomeSearch*Group.tsx` (40–63 lines each; Municipality is the real variant) | duplication | low | M | P2 | open — Pass 4 | P4-B |
|
||||
| `notificationEvents` re-spells names-by-id ×7 instead of the P3-E wrappers | `src/utilities/notification/notificationEvents.ts` (`:41-57`, `:143-174`, `:212-236` — `findByID` + `select:{name}`/`{name,slug}`) | duplication | low | S | P2 | open — Pass 4 | P4-C |
|
||||
| Notification write path: `markAllNotificationsRead` updates up to 200 rows with no transaction (relocated from the removed panel button action by C108 — it now fires on every bell open, so a mid-batch failure leaves a partial mark that the next open completes, idempotently); `subscribeCampaignPush` validates with `.trim()` only and always creates (re-subscribe → unique violation → generic error); `unsubscribeCampaignPush` deletes with `overrideAccess: true` while `canDeleteOwnPushSubscriptions` already scopes the owner | `src/utilities/notification/notificationList.ts:104-117`, `src/app/(campaign)/campanha/actions/notifications.ts:88-131,137-145` | correctness | medium — partial mark-read on mid-batch failure (self-healing); hostile re-subscribe UX; bypass surface | S | P2 | open — Pass 4 | P4-D |
|
||||
| Wizard info Drawer chrome spelled ×3 (button → bottom sheet with title+description+body); skip trailing spelled ×2 (13 lines each, both `{label, href}`) | `WizardSignalTypeStep.tsx:95-112`, `WizardTrendChoiceStep.tsx:115-132`, `WizardLeadershipStep.tsx:167-179`; `WizardSignalSkipTrailing.tsx` ↔ `WizardTrendSkipTrailing.tsx` | duplication | low | S | P2 | open — Pass 4 | P4-E |
|
||||
| `MunicipalityListMobileCards` (client) value-imports `municipalityColumnLabels` from `municipalityListUrl` — a ~19 KB URL module (pulls `isMunicipalitySlug` → `municipalityCatalog`) for a string map whose type already lives in `municipalityLabels` | `src/components/campaign/municipality/MunicipalityListMobileCards.tsx:26` | boundary (B14 class) | low | S | P2 | open — Pass 4 | P4-F |
|
||||
| `HomeSearchProvider` wraps the whole chrome incl. `actions` + `summarySlot` non-consumers, and the provider value changes on every keystroke | `src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx:41-56`, `useHomeSearchQuery.ts:63-74` | state placement | low — actions/summary re-render per keypress | S | P2 | open — Pass 4 | P4-G |
|
||||
| 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 |
|
||||
| homeSearch text-search pipeline re-spelled per domain (gate staff → normalize → `contains` + `limit: 0` → word-start → relevance → cap), plus **unbounded `limit: 0` reads** ×5 before the in-memory refine | `src/utilities/homeSearch/searchHomeActivities.ts` (88) ↔ `searchHomeDemands.ts` (100), sim 0.72 (~63 shared lines); leaderships 0.53; `limit: 0` at `searchHomeActivities.ts:43`, `Demands:49`, `Leaderships:61`, `Advisors:37`, `StateDeputies:45` | duplication + scale | medium — every keystroke can pull a full collection slice | M | P2 | open — Pass 6 (re-measured: `limit: 0` ×5 holds at `searchHomeActivities:43` etc.; hit cap now duplicated under two names — `HOME_SEARCH_RESULT_HIT_CAP` vs local `HOME_SEARCH_ACTIVITY/DEMAND_HIT_CAP`) | P4-A |
|
||||
| HomeSearch result groups ×6 near-verbatim (Advisor↔Leadership, Activity↔Demand almost line-for-line; differ only in id/title/href/secondary) | `src/components/campaign/dashboard/HomeSearch*Group.tsx` (40–63 lines each; Municipality is the real variant) | duplication | low | M | P2 | open — Pass 6 (verified: 6 `HomeSearch*Group.tsx` still near-verbatim) | P4-B |
|
||||
| `notificationEvents` re-spells names-by-id ×7 instead of the P3-E wrappers | `src/utilities/notification/notificationEvents.ts` (`:41-57`, `:143-174`, `:212-236` — `findByID` + `select:{name}`/`{name,slug}`) | duplication | low | S | P2 | open — Pass 6 (verified: `findByID` ×7 in `notificationEvents.ts`) | P4-C |
|
||||
| Notification write path: `markAllNotificationsRead` updates up to 200 rows with no transaction (relocated from the removed panel button action by C108 — it now fires on every bell open, so a mid-batch failure leaves a partial mark that the next open completes, idempotently); `subscribeCampaignPush` validates with `.trim()` only and always creates (re-subscribe → unique violation → generic error); `unsubscribeCampaignPush` deletes with `overrideAccess: true` while `canDeleteOwnPushSubscriptions` already scopes the owner | `src/utilities/notification/notificationList.ts:104-117`, `src/app/(campaign)/campanha/actions/notifications.ts:88-131,137-145` | correctness | medium — partial mark-read on mid-batch failure (self-healing); hostile re-subscribe UX; bypass surface | S | P2 | open — Pass 6 (verified: `notifications.ts:141` still `overrideAccess: true`) | P4-D |
|
||||
| Wizard info Drawer chrome spelled ×3 (button → bottom sheet with title+description+body); skip trailing spelled ×2 (13 lines each, both `{label, href}`) | `WizardSignalTypeStep.tsx:95-112`, `WizardTrendChoiceStep.tsx:115-132`, `WizardLeadershipStep.tsx:167-179`; `WizardSignalSkipTrailing.tsx` ↔ `WizardTrendSkipTrailing.tsx` | duplication | low | S | P2 | **closed 2026-08-25 (Pass 6 verify):** superseded — the wizard domain was deleted (OPS95); no `Wizard*Step.tsx` files remain in `components/campaign/` | P4-E |
|
||||
| `MunicipalityListMobileCards` (client) value-imports `municipalityColumnLabels` from `municipalityListUrl` — a ~19 KB URL module (pulls `isMunicipalitySlug` → `municipalityCatalog`) for a string map whose type already lives in `municipalityLabels` | `src/components/campaign/municipality/MunicipalityListMobileCards.tsx:26` | boundary (B14 class) | low | S | P2 | **closed 2026-08-25 (Pass 6 verify):** `MunicipalityListMobileCards.tsx` imports only types now (`:3-10`) — the `municipalityColumnLabels` value import is gone | P4-F |
|
||||
| `HomeSearchProvider` wraps the whole chrome incl. `actions` + `summarySlot` non-consumers, and the provider value changes on every keystroke | `src/components/campaign/dashboard/CampaignHomeStaffChrome.tsx:41-56`, `useHomeSearchQuery.ts:63-74` | state placement | low — actions/summary re-render per keypress | S | P2 | **closed 2026-08-25 (Pass 6 verify):** `CampaignGlobalSearchProvider` (`CampaignGlobalSearchMount.tsx:9-14`) now wraps only the search body; `actions`/`summarySlot` arrive as server slots from above (`CampaignHomeStaffChrome.tsx:75-78`) — RSC-slot composition, provider value change no longer re-renders them | P4-G |
|
||||
| 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 6 (region moved to `RelationChipCell.tsx:1230`, still one `sr-only role=status aria-live=polite` per cell, mounted unconditionally) | 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 | **hardened 2026-08-25 (Pass 6, P6-1):** header-covered bypasses now count as undocumented; new files pinned at current counts; per-site comments are the only zero. Re-measured worst offenders at hardening time: `people/personDelete.ts` 28, `notification/*` 22, `googleCalendarSync.ts` 6 | 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 | **closed 2026-08-10 (P4-L, OPS30):** the spec was rewritten by B34 since the ledger entry; with the OPS30 hardening (prewarm completo + login retry) it ran **×3 green in dev a 2 workers (16:07–16:09, load 4–10, B32+B34)** + **×1 green com `E2E_PROD=1` (16:12, 1.6s/1.9s)**. Ledger closed with evidence; reopen if an agent reproduces with the new spec | P4-L |
|
||||
| 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 (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) | |
|
||||
| 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 6 (re-measured: `RelationChipCell` **1.235**, `municipalityListUrl` 740/29 exports, `municipalityPageData` 747, `peopleData` 627/19, `personDelete` 521/28 bypasses, `googleCalendarSync` 949, `campaignNavigationUrls` 636 — 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 6 (verified) | |
|
||||
| `(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 | **closed 2026-08-25 (Pass 6 verify):** the page is now a redirect-only stub (`redirect(advisorQuickCreateHref)`, `:5-8`) — no actor load, nothing to guard | |
|
||||
| `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 6 (verified: `AllocationDecision.ts:62` still `data.decidedBy = req.user.id`) | |
|
||||
| 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 6 (now 7 suites; the Activities/Demands/StateDeputies triplet = 346 LOC, ~132 shared) | |
|
||||
| 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 6 (C113/C115 calendar-family flake under load tracked as issue #906 — 4 distinct failures in 7 full runs; retry-once applied 2026-08-25) | |
|
||||
|
||||
| **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 | closed 2026-08-03 (Pass 5 P1) — after-commit registry + existence-safe send | 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 |
|
||||
| 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 | **closed 2026-08-25 (Pass 6 verify):** superseded — wizard domain deleted (OPS95); `wizardActionChain` has 0 importers | 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 6 (verified: `activityQuickActions.ts`, `campaignQuickActionLeadership.ts`, `campaignQuickActionDemands.ts` still re-spell the id list) | 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 6 (re-measured: **208 lines**, was 296 — shrunk since 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 6 (WORSE on the flagship route: `municipio/[slug]/v2/page.tsx:34-48` runs `requireCampaignPageActor` + all 4 V2 loaders in `generateMetadata` and repeats them in the body, zero `cache()`) | 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 6 (verified: 4 client components import `@/utilities/campaignFormFields` — `CampaignPasswordFields.tsx:5`, `CampaignProfileSettings.tsx:19`, `ContactCreateRow.tsx:15`, `ContactFormFields.tsx:10`) | 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 6 (new dodge measured: `perfil/page.tsx:25` calls `getCampaignUserWithAvatar()` — P3-I regex matches only `getCampaignUser\(` — **fixed by P6-2**; `globalAccessConventions` and the others verified still standing) | 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 6 (cutover tracked as issue #335) | B147 |
|
||||
| Saved-filters machinery re-spelled ×6 despite the committed "must not be written twice" comment (`useMunicipalitySavedFilters.ts:26`): hooks 43/48 LOC, controls 144/160 LOC, navs 154/166 LOC | `shared/usePeopleSavedFilters.ts` ↔ `useMunicipalitySavedFilters.ts`, `SavePeopleFilterControl` ↔ `SaveMunicipalityFilterControl`, `PeopleNavSavedFilters` ↔ `MunicipalityNavSavedFilters` | duplication (B18 machine, store layer already shared) | medium — every saved-filter fix lands twice; the people twin cloned the spec | M | P2 | open — Pass 6 (P6-A: one hook/control/nav trio over the shared store; guard 3 bans new twins) | P6-A |
|
||||
| Advisor-relation cell twins ×3 (89/96/99 LOC) — `PeopleAssessoradoCell` / `LeadershipAdvisorRelationCell` / `StateDeputyAdvisorRelationCell` differ only in FormData field, drawer title, trigger label | `components/campaign/{people,leadership,stateDeputy}/*AdvisorRelationCell.tsx` | duplication (RelationChipCell engine already owns the machine; DIRECTION_COPY-as-data precedent) | medium — a11y/copy drift across 3 surfaces (B37 class regrown) | M | P2 | open — Pass 6 (P6-B: one cell, owner-kind record as data; guard 3 bans new advisor-cell files not delegating) | P6-B |
|
||||
| Filter-bar shells ×13 re-spell the ~60-LOC list-filter machine (hook + query state + chips/seeds/suggestions + omnibox wiring); 9/13 near-clean, `StateDeputyFilters` 96L ≈ `AdvisorFilters` 103L | `components/campaign/{municipality,leadership,activity,supporter,demand,people,contacts,...}/*Filters.tsx` (~1.700 LOC total) | duplication + divergent change | high — every filter UX change lands 13× | L | P2 | open — Pass 6 (P6-C: shared shell with slots/typed adapter record; omnibox adapters already export the identical 6-fn surface) | P6-C |
|
||||
| `searchHome*` skeletons ×6 share the ~55-LOC machine (staff-gate → contains+`limit: 0` → word-start → relevance → cap); cap under two names (`HOME_SEARCH_RESULT_HIT_CAP` vs local `_ACTIVITY/_DEMAND_HIT_CAP` = 25) | `src/utilities/homeSearch/searchHome*.ts` (847 LOC / 10 files) | duplication | medium | M | P2 | open — Pass 6 (P6-D: typed core + named wrappers, runStaffEntityMutation pattern) | P6-D |
|
||||
| `chipLabel` re-spelled in 12/13 omnibox adapters; `municipalityOmnibox.ts:401-445` re-implements `filterOmniboxSuggestionSeeds` incl. local `SUGGESTION_CAP_PER_GROUP`/`seedOf` | `src/utilities/*Omnibox.ts` (~3.211 LOC family) | duplication | low | S-M | P2 | open — Pass 6 (P6-E: single `chipLabel` export + delete the holdout re-spell; guard 3) | P6-E |
|
||||
| DAY_MS re-spelled as bare `86_400_000` ×11 in tests + 1 in lib (`campaignTime.ts:134` local `msPerDay`) — the P3-K consolidation claim ("×3") never caught literals | `calendarFeed.int.spec.ts:41,532`, `homeSearchActivities.int.spec.ts:24`, `prioritiesTool.unit.spec.ts:13,263`, `suggestionCatalog.unit.spec.ts:365,404`, … | shotgun surgery (P3-K dodge) | low | S | P2 | open — Pass 6 (P6-F: import `DAY_MS` from `@/lib/text`; guard 3 flags the literal with allowlist) | P6-F |
|
||||
| Dead modules survive knip because test imports count as usage: `projectionSheetParse.ts` (173 LOC, 7 exports, 0 production importers) and `searchOnlyListOmnibox.ts` (98 LOC, 7 exports); dead scripts survive via `entry: ["scripts/*.mjs"]`: `github-pr.mjs`, `measure-e2e-family.mjs`, `regenerate-municipality-catalog-snapshot.mjs` (0 referencers) | `src/lib/projectionSheetParse.ts`, `src/lib/searchOnlyListOmnibox.ts`, `scripts/*.mjs` | dead code + tooling dodge (knip entry list) | low | S | P2 | open — Pass 6 (P6-G: delete the 5; knip config so spec-only modules surface — class 4) | P6-G |
|
||||
| CLI skeleton guard dodged twice: 4 local `function die()` (`check-plans-only-pr-closes.mjs:12`, `check-changelog-append-only.mjs:29`, `build-changelog.mjs:35`, `check-docs-conflict-markers.mjs:19`) + ~20 raw `process.exit(1)` — `scriptCliConventions` catches neither | `scripts/*.mjs` | guard dodge + duplication (P3-H class) | low | S | P2 | open — Pass 6 (P6-H: extend guard to `function die(`/`process.exit(`; migrate 4 files to `dieWithLabel`) | P6-H |
|
||||
| `taskActions.ts:20-27` hand-rolls try/catch and passes raw `error.message` to the UI (no safelist); `resultFormActions.ts:17` / `updateFormActions.ts:16-20` safelist bare literals duplicating `ACTIVITY_RESULT_STAFF_MESSAGE`/`ACTIVITY_UPDATE_*` constants — the B32+ class, both directions | `atividades/[slug]/taskActions.ts`, `resultFormActions.ts`, `updateFormActions.ts` | correctness (message-only) + B32+ contract | medium — raw Payload/DB errors can reach the toast; literal drift silently collapses refusals | S | P2 | open — Pass 6 (P6-I: wrap in `runCampaignFormAction` + import constants; guard 2/3 sweeps `error.message` passthrough and literal safelists) | P6-I |
|
||||
| Public form actions create a new `contact` row per submission with no phone/email dedup (`submitWhatsapp.ts:30`, `submitPetitionSignature.ts:40`, `submitCampaignNewsletter.ts:26`) — campaign actions dedup via `findOrCreateContactByPhone`; repeat submissions multiply PII rows and inflate the public signature counter | `src/app/(frontend)/actions/submit*.ts` | LGPD hygiene + counter integrity | medium | M | P2 | open — Pass 6 (P6-J: dedupe before create; guard 5 int pin — same input twice → 1 contact) | P6-J |
|
||||
| `findPublishedPosts` has no `select` — every listing/article page ships full published docs incl. richText bodies; article pages read 3 full-doc sets per render | `src/utilities/posts.ts:102-114`; `[type]/[category]/[slug]/page.tsx:35,72,142` | over-fetch (engineering-standards: view models with selected fields) | medium — per-render DB payload + cache size | M | P2 | open — Pass 6 (P6-K: listing projection + related-posts from the same select'd query; guard 3) | P6-K |
|
||||
| `server-only` sweep is blind to `next/navigation` value imports (`campaignFormActionError.ts:1` — isomorphic today, invisible either way) and to Payload-as-param modules (`personDelete.ts:9`) | `tests/unit/codebaseConventions.unit.spec.ts` server-only sweep; `src/utilities/campaignFormActionError.ts:1` | guard dodge (P3-K class) | low — a future server-bound `next/navigation` module passes green | S | P2 | open — Pass 6 (P6-M: widen regex to `next/navigation` + allowlist current clean importers) | P6-M |
|
||||
| D1 trigger has no teeth: 12 top-level `activity*` modules + `googleCalendar*` trio + 3 top-level omniboxes coexist because the pin is a manual allowlist; `person/` (1 file) vs `people/` (9 files) split-brain | `src/utilities/` top-level | organization + guard dodge (P3-J D1) | low (path churn) | M | P2 | open — Pass 6 (P6-N/P6-O: prefix-count spec — N top-level files sharing a domain prefix fail; fold `personContactWriteScope.ts` into `people/`) | P6-N |
|
||||
| `as unknown as` double-cast ×21 spec files — the unguarded twin of the `as never` ban (electionToolsLockdown 5×, instagramSync 7×, calendarFeed 5×, …); `stub<T>` (148 uses) exists | `tests/unit/*.unit.spec.ts` | type honesty + guard gap (Pass 1 class) | low | M | P2 | open — Pass 6 (P6-P: swap to `stub<T>`; ESLint ban in tests — class 2) | P6-P |
|
||||
| Actor-field convention guard regex covers only `createdBy|decidedBy|declaredBy` — a hand-spelled actor under any other name passes; `CampaignDemand` is whole-file allowlisted | `tests/unit/codebaseConventions.unit.spec.ts:571` | guard dodge (P3-I class) | low — new collection can twin the factory silently | S | P2 | open — Pass 6 (P6-R: invert — flag ANY `relationTo: 'campaignUser'` actor field not built by the factory) | P6-R |
|
||||
| `overrideAccess: true` without per-site comment at ~30 of 66 sites across 15 action files (all spot-checked scope-guarded — review-quality debt, not a hole) | `actions/{advisor,leadership,municipality,notifications,person,...}.ts` | convention erosion | medium — reviewers can't distinguish guarded from unguarded | M | P2 | **counted by P6-1** (ratchet pins at current counts; only per-site comments lower them) | P6-1 |
|
||||
| `fixtures.own()` manual calls grew 49 → **75**; fixture hub god modules (`campaignFixtures.ts` 1.168 LOC, `testDatabaseLease.ts` 799) absorb every suite's builders | `tests/helpers/*`, `tests/int/*` | test-infra drift + bloater | low | M | P3 | open — Pass 6 | P6-Q |
|
||||
| Gender labels re-spelled ×4 with a deliberate display-policy fork ('Não binário' vs 'Outro') — merge rejected (look-alike), drift risk noted | `contacts/contactListFilters.ts:33`, `contactListUrl.ts`, `contactOmnibox.ts`, `leadership/leadershipUi.ts:3` | shotgun surgery (rejected-merge) | low | S | P3 | open — Pass 6 (defer + trigger: 3rd distinct policy fork re-opens) | P6-S |
|
||||
| `getSignatureCount` runs an uncached `payload.count` per render | `src/app/(frontend)/actions/getSignatureCount.ts:9` | caching ladder step 1 | low | S | P3 | open — Pass 6 | P6-T |
|
||||
| AI shell has zero live regions across 8 files — assistant turns append with no announcement (B32+ contract not inherited by new domains) | `components/campaign/shell/ai/*.tsx` (incl. `CampaignAIChat.tsx:339`) | a11y (B32+ class) | low | S | P2 | open — Pass 6 (P6-V: sr-only polite region announcing "Resposta pronta") | P6-V |
|
||||
| `peopleData.ts` (627 LOC/19 exports) mixes view models + merge + scope + facets + loader; `personDelete.ts` (521 LOC) mixes enumeration + guard + 28-bypass cascade | `src/utilities/people/*` | bloater | low | M | P2 | open — Pass 6 (P6-U: split facets/sort from loader; enumeration read-only split) | P6-U |
|
||||
| `campaignNavigationUrls.ts` (636 LOC, 3 exports) imports only `@/lib/*` — pure client-safe code stranded in the server layer | `src/utilities/ai/campaignNavigationUrls.ts` | boundary (misfiled pure module) | low | M | P2 | open — Pass 6 (move to `lib/`) | P6-V2 |
|
||||
| Miss #909 residual: the disarm sequence was fixed in the skill (judgment) — deterministic guard shipped as `scripts/testing-audit-disarm.mjs` (create→disarm→verify-null, atomic), wired into the skill's PR step | `scripts/testing-audit-disarm.mjs` (new), `.agents/skills/testing-audit/SKILL.md` | guardrail (class 3) | — | S | P1 | **closed 2026-08-25 (Pass 6, P6-M909)** — Closes #909 | P6-M909 |
|
||||
| Miss #895: intention plans registering a data decision must record the literal values (tables/IDs), not narrative — no deterministic detector possible for missing prose data | `.agents/skills/plan-issue/SKILL.md` | guardrail (class 6, judgment-only declared) | — | S | P2 | **closed 2026-08-25 (Pass 6, P6-M895)** — convention registered; declared judgment-only | P6-M895 |
|
||||
|
||||
## Smell Inventory
|
||||
|
||||
|
|
|
|||
3
docs/changelog/2026-08-25-audit-pass6.md
Normal file
3
docs/changelog/2026-08-25-audit-pass6.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# 2026-08-25 — audit pass 6
|
||||
|
||||
Engineering audit Pass 6 (skill v2: autônomo, sub-agentes, branch única `audit/pass-6`, PR único sem auto-merge — OPS98). Delta 2026-08-03→25: 681 commits non-merge, 2.041 arquivos tocados; novos domínios people/ai/contacts/calendar (2 collections novas), wizard deletado (OPS95). Baseline verde (tsc/lint/knip/ciclos; 1.526 arquivos TS). 0 P0 (access/transações/consent verificados nos 6 sweeps). 2 P1 (ratchet de bypass dodged por comentário de módulo — `personDelete.ts` 28 bypasses; P3-I dodged por `getCampaignUserWithAvatar` no `perfil`) remediados com guarda na mesma entrega. 2 misses colhidas: #909 → `scripts/testing-audit-disarm.mjs` (desarme atômico create→disarm→verify-null); #895 → convenção no plan-issue (dados literais em planos de intenção, classe 6 declarada). 5 linhas de ledger fechadas com evidência, 19 novas. Artefatos: seção Pass 6 em `docs/IMPROVE-CODE-QUALITY-PLAN.md`, ledger atualizado, plano `docs/plans/entrega-engenharia-p6.md`.
|
||||
80
docs/plans/entrega-engenharia-p6.md
Normal file
80
docs/plans/entrega-engenharia-p6.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Entrega de engenharia — Pass 6 (2026-08-25)
|
||||
|
||||
Retrato consolidado do audit (Pass 6) e o plano de execução dos workstreams remanescentes. As melhorias elegíveis (P0/P1 × S/M) foram implementadas **na mesma branch** (`audit/pass-6`, PR único sem auto-merge — OPS98); todo o resto é planejado aqui por severidade, com a guarda determinística classificada na escada 1–6 (1 tipo · 2 ESLint · 3 spec de convenção · 4 análise estática CI · 5 pin comportamental · 6 doc/judgment-only declarado).
|
||||
|
||||
## Executado no Pass (commitados na branch)
|
||||
|
||||
| WS | Entrega | Guarda (classe) |
|
||||
| --- | --- | --- |
|
||||
| P6-1 | Ratchet de bypass endurecido: comentário de módulo não zera mais o arquivo (conta como indocumentado); arquivos novos entram pinados na contagem atual; só comentário per-site abaixa | 3 — `codebaseConventions` (P3-E endurecida) |
|
||||
| P6-2 | `perfil` usa `requireCampaignPageActor`; regex do P3-I ampliada para `getCampaignUser[A-Za-z0-9]*\(` | 3 — `codebaseConventions` (P3-I endurecida) |
|
||||
| P6-M909 | Guardrail da miss #909: `scripts/testing-audit-disarm.mjs` (cria PR → desarma → verifica `autoMergeRequest: null`, atômico) + fiação na skill /testing-audit | 3 — script no passo de criação do PR (a ordem deixa de depender do agente) |
|
||||
| P6-M895 | Guardrail da miss #895: convenção no skill `plan-issue` — plano de intenção que registra decisão de DADOS registra os valores literais (tabela/IDs), não só narrativa | 6 — declarada judgment-only (prosa não tem detector determinístico) |
|
||||
|
||||
## Workstreams planejados (fora do teto do Pass)
|
||||
|
||||
Ordenados por severidade; dentro dela, por blast radius crescente. Teto do Pass (P0/P1 S/M) já consumido — estes entram como entregas independentes via `plan-issue`/agent-paradigm.
|
||||
|
||||
### P2
|
||||
|
||||
| WS | Conteúdo | Guarda (classe) | Evidência |
|
||||
| --- | --- | --- | --- |
|
||||
| P6-A | Saved-filters ×6 → 1 trio (hook/control/nav) sobre o store compartilhado; copy como data | 3 — spec bane novo twin de saved-filter fora do dono | `usePeopleSavedFilters` 43L ↔ `useMunicipalitySavedFilters` 48L; comentário "must not be written twice" violado em `:26` |
|
||||
| P6-B | Advisor-relation cells ×3 → 1 célula com record de owner-kind (precedente `DIRECTION_COPY`) | 3 — spec: arquivo `*AdvisorRelationCell`/`*AssessoradoCell` novo precisa delegar ao dono | 89/96/99 LOC, só diferem FormData field/título/label |
|
||||
| P6-C | Filter-bar shells ×13 → shell com slots + adapter record (adapters já exportam a superfície de 6 fns idêntica) | 6 — judgment-only (abstração grande); gatilho: próximo shell novo | ~1.700 LOC, `StateDeputyFilters` 96L ≈ `AdvisorFilters` 103L |
|
||||
| P6-D | `searchHome*` ×6 → core tipado + wrappers nomeados (padrão `runStaffEntityMutation`); cap single-source | 3 — spec: cap com nome único; skeleton ban | 847 LOC; cap ×2 nomes (25) |
|
||||
| P6-E | `chipLabel` ×12 → 1 export; deletar re-spell de `filterOmniboxSuggestionSeeds` em `municipalityOmnibox.ts:401-445` | 3 — spec: `const chipLabel` permitido 1× | 12/13 arquivos |
|
||||
| P6-F | Bare `86_400_000` ×12 → `DAY_MS` | 3 — `scriptCliConventions`/spec flag do literal com allowlist | 11 tests + `campaignTime.ts:134` |
|
||||
| P6-G | Deletar módulos mortos (`projectionSheetParse` 173L, `searchOnlyListOmnibox` 98L) + scripts mortos (`github-pr.mjs`, `measure-e2e-family.mjs`, `regenerate-municipality-catalog-snapshot.mjs`) | 4 — knip config: spec não conta como usage (entry de testes excluído) | 0 importadores de produção |
|
||||
| P6-H | Guard de CLI estendido: ban `function die(` + `process.exit(`; migrar 4 scripts para `dieWithLabel` | 3 — `scriptCliConventions` endurecida | 4 `function die()` + ~20 `process.exit(1)` |
|
||||
| P6-I | `taskActions.ts` no wrapper `runCampaignFormAction`; safelists importam as constantes (`ACTIVITY_RESULT_STAFF_MESSAGE` etc.) | 2/3 — spec varre `error.message` em `(campaign)` + literal em safelist | `taskActions.ts:20-27`; `resultFormActions.ts:17` |
|
||||
| P6-J | Dedup de contact nos 3 forms públicos (`findOrCreateContactByPhone` pattern) | 5 — pin int: mesma submissão 2× → 1 contact | `submitWhatsapp.ts:30`, `submitPetitionSignature.ts:40`, `submitCampaignNewsletter.ts:26` |
|
||||
| P6-K | `findPublishedPosts` com `select` (projeção de listagem); related-posts derivam da mesma query | 3 — spec: `find` de post sem `select` falha | `posts.ts:102-114` sem `select` nenhum |
|
||||
| P6-M | Sweep `server-only` ampliado para `next/navigation` (+ allowlist dos limpos) | 3 — `codebaseConventions` | `campaignFormActionError.ts:1` |
|
||||
| P6-N | D1 trigger com dentes: spec conta N módulos top-level com prefixo de domínio; 12 `activity*` + trio `googleCalendar*` + 3 omniboxes top-level → subpastas | 3 — spec de contagem de prefixo | `src/utilities/` top-level |
|
||||
| P6-O | Fold `personContactWriteScope.ts` → `people/` | 3 — spec de split-brain por prefixo | `person/` com 1 arquivo |
|
||||
| P6-P | `as unknown as` → `stub<T>` + ban ESLint em tests | 2 — ESLint `no-restricted-syntax` em tests | 21 arquivos de spec |
|
||||
| P6-R | Guard de actor-field invertido: qualquer `relationTo: 'campaignUser'` não vindo da factory falha (não só os 3 nomes) | 2/3 — spec | `codebaseConventions.unit.spec.ts:571` |
|
||||
| P6-U | `peopleData`/`personDelete` split (facets/sort vs loader; enumeração read-only vs mutação) | 6 — judgment | 627/521 LOC |
|
||||
| P6-V | AI shell: região polite anunciando resposta; usar `CampaignFormActionMessage` nos contatos | 6 — judgment (a11y) | 0 aria-live em 8 arquivos `shell/ai/` |
|
||||
| P6-V2 | `campaignNavigationUrls.ts` → `lib/` (puro, client-safe) | 3 — spec de pureza top-level | 636 LOC, imports só `@/lib/*` |
|
||||
|
||||
### P3
|
||||
|
||||
| WS | Conteúdo | Guarda | Evidência |
|
||||
| --- | --- | --- | --- |
|
||||
| P6-Q | `fixtures.own()` 75 → auto-ownership; split do hub de fixtures | 3 | `campaignFixtures.ts` 1.168L |
|
||||
| P6-S | Gender labels: manter fork deliberado (rejeitado como look-alike), registrar gatilho | 6 | 'Não binário' vs 'Outro' |
|
||||
| P6-T | `getSignatureCount` com `cache()` | 2 | `getSignatureCount.ts:9` |
|
||||
| P6-X | Migração de schema (ex.: users-roles) e URLs públicas: **fora de escopo** por construção | — | ledger |
|
||||
|
||||
## Defer + trigger registrados (sem ID próprio)
|
||||
|
||||
| Candidato | Motivo do defer | Gatilho de reabertura |
|
||||
| --- | --- | --- |
|
||||
| homeSearch int triplet → `describe.each` | 2 specs gêmeos de fixture-heavy int; restruturar carrega cada assertion verbatim | 4º irmão do triplet |
|
||||
| `agent-pool.mjs` + `lib/agent-pool-*` (516L) | pool dormente (OPS65); `agent:pool` ainda tem entrada em package.json | limpeza pós-pool |
|
||||
| Parsers quick-action V1/V2 | B147 paralelo deliberado (ledger, issue #335) | cutover do v2 |
|
||||
| `municipio/[slug]/v2` prologue `cache()` | mesma classe P5-D — a rota v2 é a pior instância; corrigir junto do P5-D | — |
|
||||
| `measure-e2e-family.mjs` | ferramenta OPS87 com docs próprias | deletar junto das docs do workflow |
|
||||
|
||||
## Rejeitados como look-alike (não re-propor)
|
||||
|
||||
- `loadNamesByIds` vs `resolveAssessorados`/`peopleNameSubline` em `peopleData.ts` — algoritmos distintos (entity-union; display policy), não name-loaders.
|
||||
- Cap+message template ×4 — valores de política diferem (10 vs 435) e copy é por domínio; `nextIdsAfterMembership` já recebe o cap como dado.
|
||||
- `hasHits` ×5 — aliases nomeados de 1 linha são declarações deliberadas de interface (canon: `canUpdateX = canReadX`).
|
||||
- `homeSearchShare.ts` pass-through — sem conhecimento duplicado; só re-export.
|
||||
|
||||
## Guard map do Pass
|
||||
|
||||
| Classe de achado | Guarda | Mecanismo | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Bypass admin indocumentado | P3-E ratchet | `codebaseConventions` por-arquivo, só diminui | **endurecida P6-1** (comentário de módulo não zera; novos arquivos pinados) |
|
||||
| Ator de página hand-rolled | P3-I | regex em page.tsx de `(app)` | **endurecida P6-2** (variantes `getCampaignUser*`) |
|
||||
| Automerge armado em PR de audit | OPS98 veto + #909 | safety-net pula `audit/*`; script de desarme atômico | **nova P6-M909** |
|
||||
| Plano de intenção sem dados literais | #895 convenção | skill `plan-issue` | **nova P6-M895** (classe 6 declarada) |
|
||||
| `as never` twin (`as unknown as`) | P6-P | ESLint em tests | planejada |
|
||||
| Literal `86_400_000` | P6-F | spec com allowlist | planejada |
|
||||
| `function die(`/`process.exit(` em scripts | P6-H | `scriptCliConventions` estendida | planejada |
|
||||
| Módulo morto sustentado por spec | P6-G | knip config | planejada |
|
||||
| Actor-field fora da factory | P6-R | spec invertida (nome-independente) | planejada |
|
||||
Loading…
Reference in a new issue