import { execSync } from 'node:child_process' import { existsSync, readdirSync, readFileSync } from 'node:fs' import { basename, relative, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { legacyCamelCaseFilenameIgnores, legacyComponentFilenameIgnores, legacyComponentSyntaxIgnores, legacyFrameworkExportIgnores, } from '../../eslint-legacy-ignores.mjs' import { CONFLICT_MARKER_RE } from '../../scripts/lib/conflictMarkers.mjs' // Programmatic guards for conventions that used to be enforced only by prose // (codebase-map.mdc / docs/ARCHITECTURE.md) and drifted during Pass 2. const repoRoot = process.cwd() const walkSourceFiles = (root: string, extensions: readonly string[]): string[] => readdirSync(root, { recursive: true, withFileTypes: true }) .filter((entry) => entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) .map((entry) => resolve(entry.parentPath, entry.name)) const repoPath = (absolute: string) => relative(repoRoot, absolute) describe('server-only boundary in src/utilities', () => { // Files that value-import Payload run server code; if they ever land in a // client bundle the failure must be a build error, not a runtime surprise. // W2 marked 21 loaders by hand — this pins the rule for every new module. // Deliberate exceptions go here, each with a justification: const allowlist = new Set([]) const isTypeOnlyClause = (clause: string): boolean => { if (/^type\b/.test(clause.trim())) return true const inner = clause.trim().match(/^\{([\s\S]*)\}$/)?.[1] if (inner === undefined) return false return inner .split(',') .map((specifier) => specifier.trim()) .filter(Boolean) .every((specifier) => specifier.startsWith('type ')) } it('marks every Payload/Next-coupled utilities module with import server-only', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src/utilities'), ['.ts', '.tsx'])) { const source = readFileSync(file, 'utf8') // Side-effect imports (`import 'x'`) never match: the clause group // cannot contain quotes, so it cannot swallow a bare specifier. // P3-K widened the module set: value-imports of next/cache, next/server // and next/headers are equally server-bound (municipalityRevalidation and // campaignJsonMutationRoute were invisible to the Payload-only sweep). // B177 widened it again: @payloadcms/db-postgres is the raw-SQL adapter // used by request-path aggregates (getLeadingMunicipalities) and must be // server-bound too. const importsServerValues = [ ...source.matchAll( /^import\s+([^'"]+?)\s+from\s+['"](payload|@payload-config|next\/cache|next\/server|next\/headers|@payloadcms\/db-postgres(?:\/drizzle)?)['"]/gm, ), ].some(([, clause]) => !isTypeOnlyClause(clause!)) if (!importsServerValues) continue if (/^import 'server-only'/m.test(source)) continue if (allowlist.has(repoPath(file))) continue offenders.push(repoPath(file)) } expect(offenders, "add `import 'server-only'` (or a justified allowlist entry)").toEqual([]) }) }) describe('banned campaign terminology', () => { // Every rename of an operational concept leaves prose behind: the 2026-07-23 // remodel ("Praça"/"Núcleo" → "Município") needed a manual W4f sweep that // still missed 4 consent drafts, and C13 ("Plano de Ação" → "Atividade") // touched 62 files. Each retired term gets a row here so it cannot come back. // Migrations are frozen history and stay out of scope; allowlisted files // carry legitimate data or frozen SQL, not live copy. const bannedTerms = [ { id: 'Praça/Núcleo', // Accent-tolerant (P3-K): "praca"/"nucleo" unaccented used to slip past. pattern: /pra[çc]|n[úu]cleo/i, remedy: 'use "Município" (or allowlist genuine data)', allowlist: [ 'src/lib/cities.ts', // real locality names (e.g. "Núcleo Bandeirante" — DF) // Curated projection-sheet column aliases are genuine input data. 'src/lib/projectionSheetParse.ts', // Fixture entry deliberately exercises legacy visits written pre-rename. 'tests/unit/recentVisits.unit.spec.ts', // English prose ("one row in practice") trips the accent-tolerant // /pra[çc]/ — genuine code comment, not the retired term. 'src/collections/GoogleCalendarSync.ts', ], }, { id: 'Plano de Ação', // Accent-tolerant (agents routinely type "plano de acao") and space-tolerant, // so English prose ("action plan") cannot slip past the identifier forms. pattern: /action[\s_-]?plan|planos? de a[çc][aã]o|\/campanha\/planos/i, remedy: 'use "Atividade" / activity (or allowlist frozen history)', allowlist: [], }, ] as const const searchRoots = ['src', 'tests', 'scripts'] as const it.each(bannedTerms)('keeps $id out of src, tests and scripts', (term) => { // This spec quotes the banned literals itself. const allowlist = new Set([...term.allowlist, repoPath(import.meta.filename)]) const offenders: string[] = [] for (const root of searchRoots) { for (const file of walkSourceFiles(resolve(repoRoot, root), ['.ts', '.tsx', '.mjs'])) { const path = repoPath(file) if (path.startsWith('src/migrations/')) continue if (allowlist.has(path)) continue const lines = readFileSync(file, 'utf8').split('\n') for (const [index, line] of lines.entries()) { if (term.pattern.test(line)) offenders.push(`${path}:${index + 1}`) } } } expect(offenders, term.remedy).toEqual([]) }) }) describe('eslint legacy ignore lists', () => { // ESLint never validates ignore paths, so a file move silently orphans its // exemption (campaign-logo.tsx broke this way during Pass 2 W2). const unescapeGlob = (pattern: string) => pattern.replaceAll('\\[', '[').replaceAll('\\]', ']') it.each([ ['legacyComponentFilenameIgnores', legacyComponentFilenameIgnores], ['legacyFrameworkExportIgnores', legacyFrameworkExportIgnores], ['legacyComponentSyntaxIgnores', legacyComponentSyntaxIgnores], ['legacyCamelCaseFilenameIgnores', legacyCamelCaseFilenameIgnores], ])('every %s entry still exists on disk', (_name, entries) => { const missing = entries.filter((entry) => !existsSync(resolve(repoRoot, unescapeGlob(entry)))) expect(missing, 'file moved or deleted — update eslint-legacy-ignores.mjs').toEqual([]) }) }) describe('campaign formActions convention', () => { // Any file under `(campaign)` that calls `mapCampaignFormActionError` implements // the hand-rolled form-action ladder and must go through the shared wrappers // (`runCampaignFormAction` / `runCampaignRedirectFormAction`) so error mapping // cannot drift per route (Pass 2 W4d / C8 F4b / OPS14). Filename is not the // signal — presence of the mapper is. A file that truly cannot use the wrappers // gets an allowlist entry documenting why: const allowlist = new Map([ // C123 — the activities form pages died with the overlay; the mapper moved // to `src/utilities/activityOverlayErrors.ts` (module, not a route ladder). // Flattens field errors into message-only states for inline detail controls. [ 'src/app/(campaign)/campanha/(app)/apoiadores/[id]/formActions.ts', 'message-only flatten for inline detail controls', ], // Login: LockedAuth branch, redirect outside try, NEXT_REDIRECT rethrow. [ 'src/app/(campaign)/campanha/actions/auth.ts', 'bespoke login flow — redirect and LockedAuth outside the shared ladder', ], // Password reset/change: anti-enumeration swallow, bespoke field errors, // NEXT_REDIRECT rethrow — policies the wrappers don't absorb. [ 'src/app/(campaign)/campanha/actions/password.ts', 'anti-enumeration + bespoke password field errors outside the shared ladder', ], ]) it('routes every hand-rolled mapCampaignFormActionError ladder through a shared wrapper', () => { const offenders = walkSourceFiles(resolve(repoRoot, 'src/app/(campaign)'), ['.ts']) .filter((file) => !/\.spec\.ts$/.test(file)) .map(repoPath) .filter((path) => !allowlist.has(path)) .filter((path) => { const source = readFileSync(resolve(repoRoot, path), 'utf8') if (!/\bmapCampaignFormActionError\s*\(/.test(source)) return false // Require a real call site — a comment naming the wrappers must not pass. return !/\brunCampaign(Redirect)?FormAction\s*\(/.test(source) }) expect(offenders, 'wrap with runCampaignFormAction/runCampaignRedirectFormAction').toEqual([]) }) }) describe('campaign JSON mutation route convention', () => { // The cells' quick edits POST to cookie-authenticated route handlers, so each // one needs the same-origin check. Written by hand it is a line a new route // forgets, and forgetting it fails OPEN — hence the wrapper (B32+ F2), and // hence this sweep, which is what stops the sixth route from opting out. // // It sweeps all of `src/app` and not just `(campaign)`: a cookie-authenticated // endpoint written under `api/` would be exactly as exposed and exactly as // invisible, so the exceptions are declared here with their reason instead of // being implied by a folder. const allowlist = new Map([ ['src/app/(payload)/api/[...slug]/route.ts', "re-export of Payload's own handler and auth"], ['src/app/(payload)/api/graphql/route.ts', "re-export of Payload's own handler and auth"], [ 'src/app/(frontend)/api/revalidate/route.ts', 'server-to-server: authenticated by a secret header, deliberately callable cross-origin', ], [ 'src/app/(frontend)/api/social-feed/sync/route.ts', 'admin-session authenticated (payload.auth, users collection) + explicit isSameOriginRequest; bodyless action returning the sync status — the campaignJsonMutationRoute envelope is campaign-scoped ({status:error})', ], [ 'src/app/(campaign)/campanha/api/ai-chat/route.ts', 'streaming AI endpoint (ReadableStream, not JSON) — cookie-authenticated via campaign-token, origin-checked by cookie path', ], [ 'src/app/(campaign)/campanha/api/ai-transcribe/route.ts', 'multipart STT endpoint (audio FormData, not JSON) — sibling of ai-chat, cookie-authenticated via campaign-token + explicit same-origin check', ], [ 'src/app/(campaign)/campanha/agenda/google-webhook/[secret]/route.ts', 'Google Calendar push delivery (empty body, not JSON) — public by design, authenticated by URL secret + X-Goog channel headers (never a campaign cookie)', ], ]) it('builds every POST route under src/app with campaignJsonMutationRoute', () => { const offenders = walkSourceFiles(resolve(repoRoot, 'src/app'), ['.ts']) .filter((file) => basename(file) === 'route.ts') .map(repoPath) .filter((path) => !allowlist.has(path)) .filter((path) => { const source = readFileSync(resolve(repoRoot, path), 'utf8') // GET-only handlers (manifest, service worker) have no cookie-authed // mutation to guard — matched on the export and not the bare word, so a // GET route whose comment says "POST" is not a false offender. On the // positive side the binding is what is matched, not a mention of the // wrapper, which stops a file from pairing one wrapped handler with a // second hand-rolled one. if (!/export (const|async function) POST\b/.test(source)) return false return !/export const POST = campaignJsonMutationRoute\(/.test(source) }) expect(offenders, 'build the handler with campaignJsonMutationRoute').toEqual([]) }) }) describe('campaign refusal messages come from shared constants', () => { // `mapCampaignFormActionError` matches a thrown message against the route's // `safeMessages` by EXACT string, so a message spelled as a literal at both // ends is one reword away from silently collapsing a real refusal into the // generic error (B32+/B37; Pass 3 P3-A — four such collapses were live, // one user-reachable). Both ends must reference the shared `*_MESSAGE` / // `*_SAFE_MESSAGES` constants in `src/lib/schemas/*` (or the owning // utilities module). Internal English ops invariants that no route // safelists are allowlisted here: const allowlistedThrowLiterals = new Set(['Engagement level write was dropped.']) const campaignAppFiles = () => walkSourceFiles(resolve(repoRoot, 'src/app/(campaign)'), ['.ts']).filter( (file) => !/\.spec\.ts$/.test(file), ) it('keeps string literals out of safeMessages arrays', () => { const offenders: string[] = [] for (const file of campaignAppFiles()) { const source = readFileSync(file, 'utf8') for (const match of source.matchAll(/safeMessages\s*:\s*\[/g)) { const start = (match.index ?? 0) + match[0].length const body = source.slice(start, source.indexOf(']', start)) if (/['"`]/.test(body)) offenders.push(repoPath(file)) } } expect(offenders, 'reference shared *_MESSAGE / *_SAFE_MESSAGES constants').toEqual([]) }) it('keeps string literals out of throw new Error in campaign app files', () => { const offenders: string[] = [] for (const file of campaignAppFiles()) { const source = readFileSync(file, 'utf8') for (const match of source.matchAll(/throw new Error\(\s*(['"`])([^]*?)\1/g)) { if (!allowlistedThrowLiterals.has(match[2]!)) { offenders.push(`${repoPath(file)}: "${match[2]}"`) } } } expect( offenders, 'throw via a shared *_MESSAGE constant (or allowlist an internal English invariant)', ).toEqual([]) }) }) describe('form-action feedback comes from the primitive', () => { // P3-G: 10 of 15 hand-spelled `{state.message && state.status !== 'success'}` // blocks were mute for assistive tech. `CampaignFormActionMessage` owns the // aria-live region, so the raw spelling is banned — adoption IS the // prevention. Declared exceptions, each with its reason: const allowlist = new Set([ // The primitive itself. 'src/components/campaign/shared/CampaignFormActionMessage.tsx', // Toast-only channel (no inline JSX): success AND error both go through // sonner — a legitimate different feedback idiom, not a mute Alert. 'src/components/campaign/advisor/AdvisorPasswordResetButton.tsx', ]) it('keeps the raw feedback condition out of campaign components', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src/components/campaign'), ['.tsx'])) { const path = repoPath(file) if (allowlist.has(path)) continue const source = readFileSync(file, 'utf8') if (/state\.message\s*(&&|\|\|)\s*state\.(status|fieldErrors)/.test(source)) { offenders.push(path) } } expect(offenders, 'render feedback via CampaignFormActionMessage').toEqual([]) }) }) describe('CLI skeleton comes from scripts/lib/cli.mjs', () => { // P3-H: `die`, `sha256`, the download→cache prologue and the dotenv preamble // were re-spelled in 5–7 scripts each — the newest copied the OLD shape. // These belong to `scripts/lib/cli.mjs` alone; a new script re-spelling any // of them breaks the build here. const bannedPatterns: ReadonlyArray<[id: string, pattern: RegExp, remedy: string]> = [ ['die spelling', /const die = \(message\) =>/, 'use dieWithLabel from scripts/lib/cli.mjs'], ['sha256 spelling', /const sha256 = \(buffer\) =>/, 'use sha256Hex from scripts/lib/cli.mjs'], [ 'dotenv preamble', /loadEnv\(\{ path: '\.env\.local' \}\)/, 'use loadCliEnv from scripts/lib/cli.mjs', ], [ 'local host list', /const LOCAL_HOSTS = new Set/, 'import LOCAL_HOSTS from scripts/lib/cli.mjs', ], ] const allowlist = new Set(['scripts/lib/cli.mjs']) it.each(bannedPatterns)('keeps the %s out of scripts/', (_id, pattern, remedy) => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'scripts'), ['.mjs'])) { const path = repoPath(file) if (allowlist.has(path)) continue if (pattern.test(readFileSync(file, 'utf8'))) offenders.push(path) } expect(offenders, remedy).toEqual([]) }) }) describe('form-action feedback goes through CampaignFormActionMessage', () => { // P3-G: the raw `{state.message && state.status …}` JSX was spelled in 15 // places and 10 were mute for assistive tech (no aria-live). The primitive // owns the live region, so adoption = prevention; a new raw spelling breaks // the build here. Distinct forms are allowlisted with their reason: const allowlist = new Set([ // The primitive itself. 'src/components/campaign/shared/CampaignFormActionMessage.tsx', // toast.error channel — the control has no inline alert at all (the // destructive feedback rides the toast, mirroring the success one). 'src/components/campaign/advisor/AdvisorPasswordResetButton.tsx', ]) it('keeps the raw feedback JSX out of campaign components', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src/components/campaign'), ['.tsx'])) { const path = repoPath(file) if (allowlist.has(path)) continue const source = readFileSync(file, 'utf8') if (/state\.message\s*&&\s*state\.status/.test(source)) offenders.push(path) } expect(offenders, 'render via CampaignFormActionMessage').toEqual([]) }) }) describe('src/utilities top-level is pinned', () => { // P3-J: domain subfolders (municipality/supporter/territory/leadership/ // visit/webauthn) executed the Pass 2 D1 trigger. This allowlist IS the // trigger encoded: a NEW top-level module fails the build until registered // here with a reason — at 3 new modules of one domain in a month, the // domain earns a subfolder instead. const pinnedTopLevel = new Set([ 'activityDetailPageData.ts', 'activityDetailTabUi.ts', 'activityAgendaOmnibox.ts', 'activityFormData.ts', 'activityOverlayErrors.ts', 'activityOmnibox.ts', 'activityLeadershipOptions.ts', 'activityPageData.ts', 'activityRelationOptions.ts', 'activityResponsibleSearch.ts', 'activityUi.ts', 'activityViewModels.ts', 'advisorData.ts', 'calendarFeed.ts', 'campaignAccess.ts', 'campaignActionContext.ts', 'campaignAuditFields.ts', // C114 — Google Calendar mirror: thin Calendar API v3 transport (jose+fetch). 'googleCalendarClient.ts', // C114 — Google Calendar mirror: reconciliation engine + hook guards. 'googleCalendarSync.ts', // C114 — Google Calendar mirror: activity/config hooks (separate module so // the engine is mockable at the import boundary in tests). 'googleCalendarSyncHooks.ts', // C149 — Google Calendar OAuth: env client pair, signed single-use state // cookie and the authorization-code exchange (server-only). 'googleCalendarOAuth.ts', 'campaignAuth.ts', 'campaignBiometricsPrompt.ts', 'campaignColumnVisibilityCookie.ts', 'campaignConsent.ts', // C128 — stub `@criado.invalid` e-mail probe for staff accounts // created without usable credentials; shared by the B154 município create // and the person lifecycle Assessora create. 'campaignStubEmail.ts', 'campaignDashboardData.ts', 'campaignVoteSummarySnapshot.ts', 'campaignDemandData.ts', 'campaignEntityActions.ts', 'campaignFormActionError.ts', 'campaignFormFields.ts', 'campaignGeolocation.ts', 'campaignGoals.ts', // S10 — campaign-home Meta pixel read (site-settings global, fail-closed); // shared by the home layout (PageView) and the page (Lead prop). 'campaignHomeTracking.ts', 'campaignInvite.ts', 'campaignInviteCreation.ts', 'campaignInviteOrigin.ts', 'campaignInvitePageData.ts', 'campaignInviteRedemption.ts', 'campaignInviteRepository.ts', 'campaignJsonMutationRoute.ts', 'campaignListUrl.ts', // C100 — shared storage primitive behind the B18 saved-filters stores // (municipality + people): pure client-side factory, no Payload/Next. 'campaignSavedFilterStore.ts', 'campaignPageActor.ts', 'campaignPasswordReset.ts', 'campaignPwa.ts', 'campaignPwaClient.ts', // Push ceremony shared by the profile card and the opt-in toast (D6): // client-only (localStorage + SW + server action), no Payload/Next. 'campaignPushClient.ts', 'campaignRelationOptions.ts', 'campaignUserProfile.ts', 'consentContentHash.ts', 'contactIdentity.ts', 'contactPhoneLocks.ts', 'dashboardPriorityMunicipalities.ts', 'detailTabUi.ts', 'documentReads.ts', 'documents.ts', 'drizzleBulk.ts', 'electionCache.ts', 'electionCandidateOptions.ts', 'electionResultsImport.ts', 'entityNotFound.ts', 'extractFirstImageFromLexical.ts', 'formatRelativeAge.ts', 'globalReads.ts', 'globals.ts', // C128 — leadership identity-conflict matcher; shared by the B34 create // path and the person lifecycle create (a plain const cannot leave the // 'use server' action module). 'leadershipConflict.ts', 'hookFilledData.ts', 'leaderContactsPageData.ts', 'loadNamesByIds.ts', // OPS52 — media storage env resolution; shared by payload.config.ts // (fail-closed S3/Garage switch) and its unit spec. 'mediaStorage.ts', 'onda0Provision.ts', 'organizationData.ts', 'payloadTransaction.ts', 'postgresTransactionLocks.ts', 'posts.ts', 'recentVisits.ts', 'revalidateRequest.ts', 'sameOriginRequest.ts', 'seo.ts', 'signatureExport.ts', // C128 — dobradinha identity-conflict policy; shared by the B157 create // path and the person lifecycle create (same 'use server' export rule as // `leadershipConflict.ts`). 'stateDeputyConflict.ts', 'stateDeputyData.ts', 'stateDeputyListFilters.ts', 'stateDeputyListUrl.ts', 'stateDeputyOmnibox.ts', 'voteEstimateScenarioFields.ts', 'votePledgeData.ts', 'votePledgeViews.ts', // The campaign home content board's external feeds (YouTube Data API v3 // and Instagram Graph API) live in the `socialFeed/` subfolder — the S2 // pin predicted it: one external source stayed top-level, the second // earned the domain folder. Fetch + cache + snapshot + formatters, per // platform, siblings of `posts.ts`. ]) it('fails the build on an unregistered top-level module', () => { const actual = readdirSync(resolve(repoRoot, 'src/utilities'), { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith('.ts')) .map((entry) => entry.name) const unregistered = actual.filter((name) => !pinnedTopLevel.has(name)) const stale = [...pinnedTopLevel].filter((name) => !actual.includes(name)) expect(unregistered, 'register the module here with a reason, or subfolder the domain').toEqual( [], ) expect(stale, 'pin is stale — file moved or deleted, update it').toEqual([]) }) }) describe('campaign route-page actor comes from requireCampaignPageActor', () => { // P3-I: the staff page prologue was ~110 hand-spelled lines across 30 pages // with a real divergence (`return null` ×5 — a blank screen for a missing // session). A page that re-spells any `getCampaignUser*()` getter variant // (e.g. `getCampaignUserWithAvatar`) breaks the build here; genuinely // bespoke prologues are allowlisted with their reason: const allowlist = new Set([]) it('keeps getCampaignUser*() getters out of (app) route pages', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src/app/(campaign)/campanha/(app)'), [ '.tsx', ])) { const path = repoPath(file) if (basename(file) !== 'page.tsx' || allowlist.has(path)) continue if (/getCampaignUser[A-Za-z0-9]*\(/.test(readFileSync(file, 'utf8'))) offenders.push(path) } expect(offenders, 'use requireCampaignPageActor from @/utilities/campaignPageActor').toEqual([]) }) }) describe('system-stamped actor field comes from campaignAuditFields', () => { // P3-I: the `createdBy` relationship + readOnly + canSet* shape drifted // organically across 7 collections. The factory is data-driven; a // hand-spelled actor field breaks the build here. Genuinely distinct actor // fields are allowlisted with their reason: const allowlist = new Set([ // `decidedBy`/`decidedAt` are decision-audit fields with semantics the // stamping factory does not cover (set by the workflow hook, not create). 'src/collections/CampaignDemand.ts', ]) it('keeps hand-spelled actor fields out of collections', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src/collections'), ['.ts'])) { const path = repoPath(file) if (allowlist.has(path)) continue const source = readFileSync(file, 'utf8') // The hand-spelled shape pairs the actor name with its relationship; // factory options name the field but never spell `relationTo` here. if ( /name: '(createdBy|decidedBy|declaredBy)',[\s\S]{0,200}relationTo: 'campaignUser'/.test( source, ) ) { offenders.push(path) } } expect(offenders, 'use systemStampedActorField from @/utilities/campaignAuditFields').toEqual( [], ) }) }) describe('advisor scope fragment comes from access/shared.ts', () => { // P3-D: `{ municipality(s): { in: ids ?? [] } }` was re-spelled in 6 access // domain modules. The named policy is `advisorMunicipalityScopeWhere`; a new // collection that rewrites the fragment breaks the build here. Query builders // outside access/ that filter their own domain by municipality ids are // genuinely distinct forms — allowlisted file by file: const allowlist = new Set([ 'src/app/(campaign)/campanha/actions/leadership.ts', 'src/collections/Supporter.ts', 'src/utilities/campaignDashboardData.ts', 'src/utilities/leadership/leadershipData.ts', 'src/utilities/leadership/leadershipListUrl.ts', 'src/utilities/municipality/municipalityTriggers.ts', // C100 — the people list filters the leadership SOURCE by municipality ids // at query level (the same distinct form as `leadershipListUrl.ts`); the // actor scope is enforced by the merge, never by this where. 'src/utilities/people/peopleListUrl.ts', // B155 — the municipality list surface reads `leadership.municipalities` // by reverse batch; same distinct form as `leadershipData.ts` above. 'src/utilities/municipality/municipalityViewModels.ts', // B193 — the mobile card footer batches the LAST update per listed // municipality (a domain filter over the visible page ids); the actor // scope still comes from `overrideAccess: false` + `user`. 'src/utilities/municipality/municipalityUpdatePageData.ts', 'src/utilities/visit/visitPlannerData.ts', 'src/utilities/votePledgeData.ts', // B178 — the city overview batches activities across the 19 Salvador zone // ids (a domain filter over a known set, not an actor access scope); the // single-id form the dossier uses goes through `buildActivityListWhere`. 'src/app/(campaign)/campanha/(app)/municipios/[slug]/CityDetailSections.tsx', // B185 — the chat tool filters leaderships/pledges by a scope resolved to // a set of municipality ids (territory/city/municipality), a domain filter // over a known set — the actor scope still comes from access control // (overrideAccess: false + user). 'src/utilities/ai/tools/getPendingLeaderships.ts', // B186 — same distinct form as B185: the priority tool reads the in-window // updates of a scope already resolved (or access-narrowed) by the actor. 'src/utilities/ai/tools/getMunicipalityPriorities.ts', ]) it('keeps the scope fragment out of re-spellings', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src'), ['.ts', '.tsx'])) { const path = repoPath(file) if (path === 'src/utilities/access/shared.ts' || allowlist.has(path)) continue const source = readFileSync(file, 'utf8') if (/municipalit(?:y|ies):\s*\{\s*in:/.test(source)) offenders.push(path) } expect(offenders, 'use advisorMunicipalityScopeWhere (or allowlist a distinct form)').toEqual( [], ) }) }) describe('admin bypasses carry a justification comment', () => { // P3-E: "Local API com `user` sempre com `overrideAccess: false`; bypass // admin só com comentário justificando" was social — 89 of 127 bypasses had // none. Documented means a comment containing "bypass" within 10 lines above // the occurrence (P6-1: a module header comment alone does NOT document a // site — only per-site comments count). This is a RATCHET: counts may only // shrink. A new undocumented bypass fails the build; documenting an old one // means lowering its pin. const pinnedUndocumented = new Map([ ['src/app/(campaign)/campanha/actions/advisor.ts', 2], ['src/app/(campaign)/campanha/actions/leaderSupporter.ts', 1], ['src/app/(campaign)/campanha/actions/leadership.ts', 1], ['src/app/(campaign)/campanha/actions/municipality.ts', 1], ['src/app/(campaign)/campanha/actions/password.ts', 2], ['src/app/(campaign)/campanha/actions/profile.ts', 4], ['src/app/(campaign)/campanha/actions/stateDeputy.ts', 0], ['src/app/(campaign)/campanha/actions/supporter.ts', 7], ['src/app/(campaign)/campanha/actions/supporterImport.ts', 2], ['src/app/(campaign)/campanha/actions/votePledge.ts', 1], ['src/utilities/activityDetailPageData.ts', 1], ['src/utilities/advisorData.ts', 2], ['src/utilities/campaignInviteCreation.ts', 1], ['src/utilities/campaignInvitePageData.ts', 1], ['src/utilities/campaignInviteRedemption.ts', 13], ['src/utilities/campaignInviteRepository.ts', 2], ['src/utilities/electionCandidateOptions.ts', 1], ['src/utilities/territory/loadTerritoryOverview.ts', 1], ['src/utilities/municipality/municipalityCandidateComparison.ts', 2], ['src/utilities/municipality/municipalityElectoralBaseline.ts', 8], ['src/utilities/onda0Provision.ts', 4], ['src/utilities/stateDeputyData.ts', 0], ['src/utilities/visit/visitPlannerData.ts', 1], ['src/utilities/access/campaignUsers.ts', 2], ['src/utilities/access/contacts.ts', 2], ['src/utilities/access/leaderships.ts', 1], ['src/utilities/access/municipalities.ts', 3], ['src/utilities/access/shared.ts', 2], ['src/utilities/access/webauthnCredentials.ts', 1], ['src/collections/Activity.ts', 0], ['src/collections/CampaignDemand.ts', 1], ['src/collections/CampaignUser.ts', 3], ['src/collections/CampaignWebAuthnCredential.ts', 1], ['src/collections/Media.ts', 1], ['src/collections/Municipality.ts', 0], ['src/collections/MunicipalityUpdate.ts', 3], ['src/collections/Supporter.ts', 1], ['src/collections/SupporterImportBatch.ts', 1], ['src/collections/Users.ts', 1], ['src/collections/VotePledge.ts', 1], // P6-1 baseline: 2026-08-25 — header comments no longer zero a file; // these counts are the per-site measurement the RED of fase 1 reported. ['src/collections/Consent.ts', 7], ['src/utilities/campaignStubEmail.ts', 1], ['src/utilities/campaignVoteSummarySnapshot.ts', 6], ['src/utilities/contactIdentity.ts', 2], ['src/utilities/loadNamesByIds.ts', 5], ['src/utilities/access/notifications.ts', 2], ['src/utilities/municipality/municipalityPortfolioIndex.ts', 1], ['src/utilities/municipality/municipalityTicketPartnerData.ts', 4], ['src/utilities/notification/createCampaignNotification.ts', 3], ['src/utilities/notification/notificationEvents.ts', 8], ['src/utilities/notification/notificationList.ts', 2], ['src/utilities/notification/notificationRecipients.ts', 3], ['src/utilities/notification/sendCampaignPush.ts', 4], ['src/utilities/people/personDelete.ts', 26], ['src/utilities/supporter/supporterImportToken.ts', 4], ['src/utilities/webauthn/campaignWebAuthnCeremony.ts', 4], ['src/app/(campaign)/campanha/actions/contact.ts', 2], ['src/app/(campaign)/campanha/actions/notifications.ts', 3], ]) const countUndocumented = (path: string): number => { const lines = readFileSync(resolve(repoRoot, path), 'utf8').split('\n') let count = 0 for (const [index, line] of lines.entries()) { if (!line.includes('overrideAccess: true')) continue const context = lines .slice(Math.max(0, index - 10), index) .join('\n') .toLowerCase() if (!context.includes('bypass')) count += 1 } return count } it('never grows the undocumented-bypass count of any file', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src'), ['.ts', '.tsx'])) { const path = repoPath(file) if (path.startsWith('src/migrations/') || path.endsWith('.spec.ts')) continue const pinned = pinnedUndocumented.get(path) ?? 0 const actual = countUndocumented(path) if (actual > pinned) { offenders.push(`${path}: ${actual} undocumented (pin: ${pinned})`) } if (actual < pinned) { offenders.push(`${path}: improved to ${actual} — lower the pin from ${pinned}`) } } expect(offenders, 'justify with a "bypass" comment (or lower the pin)').toEqual([]) }) }) describe('e2e name locators are anchored', () => { // An unanchored template regex on a catalog name collides with prefix-shared // names (Conde/Condeúba — 23/435 measured in the catalog, Pass 3 P3-C). // Anchored regexes (`^…` / `…$`) and plain strings with `exact: true` are fine. it('keeps unanchored template regexes out of tests/e2e', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'tests/e2e'), ['.ts'])) { const source = readFileSync(file, 'utf8') for (const match of source.matchAll(/new RegExp\(`([^`]*)`\)/g)) { const pattern = match[1]! if (!pattern.startsWith('^') && !pattern.endsWith('$')) { offenders.push(`${repoPath(file)}: \`${match[1]}\``) } } } expect(offenders, 'anchor with ^ or use the plain string with exact: true').toEqual([]) }) }) describe('public site metadata global access', () => { // An empty `metadata` global (fresh DB / poisoned unstable_cache) used to // crash `next build` via raw field access. Every `getCachedGlobal('metadata')` // consumer must also call `resolveSiteMetadata` in the same file. it('routes every metadata global consumer through resolveSiteMetadata', () => { const offenders: string[] = [] for (const file of walkSourceFiles(resolve(repoRoot, 'src'), ['.ts', '.tsx'])) { const path = repoPath(file) if (path.startsWith('src/migrations/')) continue const source = readFileSync(file, 'utf8') if (!/getCachedGlobal\(\s*['"]metadata['"]\s*\)/.test(source)) continue if (!/\bresolveSiteMetadata\b/.test(source)) { offenders.push(path) } } expect(offenders, 'import and call resolveSiteMetadata from @/utilities/seo').toEqual([]) }) }) describe('no conflict markers in committed files', () => { // A real git conflict was committed to AGENTS.md twice: the OPS33 commit // carried `<<<<<<< HEAD` plus a mangled `> > > > > > >` closer (the // `=======` was deleted in the same bad resolution), and OPS37 inherited // the state. The scan reads tracked paths from the index and greps the // working tree, so a bad resolution fails here before it is ever pushed. // The regex lives in scripts/lib/conflictMarkers.mjs (OPS44 extraction) — // the CI docs guard shares it. The `(?:> ){7}` form is the exact corrupted // closer that shipped; a genuine 7-deep nested blockquote would need a // deliberate allowlist. const conflictMarker = CONFLICT_MARKER_RE it('keeps conflict markers out of tracked files', () => { const offenders: string[] = [] for (const file of execSync('git ls-files', { encoding: 'utf8' }).split('\n')) { if (!file) continue const lines = readFileSync(resolve(repoRoot, file), 'utf8').split('\n') for (const [index, line] of lines.entries()) { if (conflictMarker.test(line)) offenders.push(`${file}:${index + 1}`) } } expect(offenders, 'resolve the conflict before committing; never commit marker lines').toEqual( [], ) }) })