- Consent.beforeDelete bloqueia delete com referências (APIError 409 composto
por coleção), fim do 23502 cru no admin (contagens com overrideAccess).
- submitWhatsapp/submitPetitionSignature delegam o ciclo de transação ao dono
withPayloadTransaction (rollback no commit-falho, AggregateError, after-commit),
preservando os contratos { ok: true } / { ok: true, signatureNumber }.
- contactStateSchema exportado + contactCitySchema/contactCityFieldSchema
consolidados; as 4 fichas usam os exports únicos (fim das cópias inline do
predicate value in CitiesByState). withMutableConsentFixture purga consent
via raw SQL para bypassar o hook no harness de teste.
Co-Authored-By: opencode <noreply@opencode.ai>
O validate de itemId em social-feed-settings lia data.platform (doc inteiro,
sempre undefined) e nunca validava ID de vídeo em produção. Agora lê
siblingData.platform via helper validateExcludedItemId. Stub YouTube ganha
ids de 11 chars e o spec acompanha, destravando o e2e do describe
'Campaign home content section'. Sem migration.
Os afterChange/afterDelete de activity e googleCalendarSync rodavam a reconciliacao completa dentro da transacao do save, segurando row lock + pool por todo o I/O do Google (15s por hop, N RTTs -> 45-75s). Corrige com deadline proprio do hook (GOOGLE_CALENDAR_SYNC_HOOK_TIMEOUT_MS=5s) via AbortSignal.timeout combinado com per-hop 15s no cliente (AbortSignal.any) — manual/webhook mantem 15s sem transacao como fallback confiavel. Fail-closed preservado (abort vira paused). Pin unit hook < per-hop + int hook <=7s com fetch lento.
F2: POST /campanha/liderancas/state-deputies substitui
setLeadershipStateDeputyMembershipFormAction (chips de /campanha/liderancas B31
e /campanha/dobradinhas B36 — mesma action record).
F3: POST /campanha/liderancas/municipalities substitui
setLeadershipMunicipalitiesFormAction (B34).
Wrappers 'use client' LeadershipStateDeputiesColumnCell /
LeadershipMunicipalitiesColumnCell mantêm o contrato (state, formData) das
shared cells (intocadas); rotas reusam os schemas exportados + allowlists
floor/cap/scope; revalidate herdado das actions record (contrato B34/B31).
F1 (sinal de municípios) não existe mais: C87 o substituiu por form
multi-campo, que por regra B155 permanece em server action — registrado
como já resolvido no plano de intenção.
Guards: whitelist sharedSheetHostConventions (+2 wrappers), prewarm e2e
(+2 rotas), B34 aponta para a rota nova. Sem migration/UI (Impeccable A).
Closes#368
Fila encadeada por instância no MunicipalityRelationEditor: delta otimista
imediato + transporte serializado (próximo elo só dispara quando o anterior
assenta), falha reverte só o delta do próprio elo e a fila segue, busy
derivado da fila (nunca preso), drain aplica o último selectedIDs confirmado
só quando a resposta final o traz (JSON) — sem ele (dobradinhas) confia no
RSC. Substitui requestSeqRef/latestConfirmedRef-seq por fila; tempID de
create em contador próprio. Contratos dos wrappers e call sites intactos.
- buildClaimQueue exclui Issues ready sem frontmatter id (não viram worktree
por construção) e withoutIdReadyIssues() expõe as puladas
- agent:claim / worktree next / pnpm issue next avisam com os números em vez
de morrer — uma Issue quebrada não trava mais a fila
- pin novo em agentPoolEligibility.unit.spec.ts
Closes#864
Mapa fixo commitado em scripts/lib/worktree.mjs (cheap=cheapestinference/deepseek-v4-flash, pro=opencode-go/qwen3.7-max, zen=opencode-go/ox-alpha-free, go=opencode-go/mimo-v2.5, alibaba=alibaba-token-plan/qwen3.7-max) com WORKTREE_VARIANT=max sempre; resolveWorktreeModel fail-high em múltiplas flags; opencodeLaunchDirective passa a emitir --model <map|preset> --variant max --auto; scripts/worktree.mjs valida at-most-one antes do claim e repassa modelo nos 3 propósitos (next/plan/new) com help/docblock atualizados; .agents/shell/worktree.sh, .opencode/commands/worktree.md, .agents/skills/worktree-next-issue/SKILL.md e local-database skill com contrato remapeado (--go deixa de ser no-op); tests/unit/worktree.unit.spec.ts pina as 5 flags, variant e guardrail.
Closes#859
Hoist collectUnmappedSrc helper in selectE2eSpecs so unmapped risk-area
files are computed before the high-risk early-return. When high-risk wins
and risk files lack a manifest entry, the curated result now includes
them in unmapped and appends '; curated + risk files without mapping: ...'
to reason — mode stays curated.
Pin: testAffected gains combined case (migration + unmapped risk file →
curated with unmapped in reason). Closes#846.
Fix manual dispatch em 18 formulários de /campanha que usavam <form action={submitAction}>: campos não controlados voltavam ao defaultValue após qualquer action assentada (sucesso ou erro) do React 19.
- WizardStepFormChrome alterado uma vez (cobre 3 wizard steps)
- 15 forms recebem dispatch manual arquivo a arquivo
- AdvisorPasswordResetButton (2 micro-forms sem campo digitável) ficou como está
- Unit spec novo campaignWizardStepFormChrome.unit.spec.tsx
- CHANGELOG-AGENTS.md cleaned (98 duplicates removed)
Also fixes:
- campaignPageActor writable gate only blocks advisors (was blocking coordinators too)
- campaignLeaderships e2e stability (waitForLoadState before attribute assertion)
Dois bugs do C142 que o e2e full do verify do deploy pegou (o PR rodou só o blast radius):
- requireCampaignPageActor gate 'writable' checava advisorEditingAccess(user)==='none', que retorna 'none' para QUALQUER papel não-advisor — barrava coordinator/candidate de páginas de escrita (municipios/[slug]/editar redirecionava o coordinator pro detalhe).
- CampaignQuickActionsFab não renderizava data-slot='campaign-quick-actions-fab', o locator do e2e campaignPermissionProfile.
Regressões pino: campaignPageActorGate.unit.spec.ts (5 casos) + assert de data-slot no spec do FAB.
Verificado: tsc/lint/format/cycles, unit 2435, int 819, e2e local dos specs afetados (os 3 casos que falhavam no deploy passam).
Fase 1: playwright bare (--list, extensão VS Code, runs ad-hoc) quebrava na
coleta — next@15 sem exports não resolve 'next/cache' em ESM nativo e
server-only exige a condição react-server; o script test:e2e carregava ambas
via NODE_OPTIONS (por isso o CI passava). Hook de resolução ESM vitest-free
(e2eEsmResolve.mjs) registrada no main via module.register + propagada aos
workers via NODE_OPTIONS quando invocada bare; path do script intocado.
Fase 2: a sequência persistente campaign_fixture_municipality_alloc nunca é
resetada e um ciclo completo claima ~505 do catálogo de 435 — o wrap em todo
ciclo em DB de worktree fazia dois runs concorrentes receberem o mesmo índice
e purgarem as linhas vivas um do outro. Registry de claims compartilhado
vitest-free (campaignMunicipalityAllocator.ts): INSERT ON CONFLICT DO NOTHING
com skip de slot vivo, steal de claim stale (TTL 2h), release no cleanup dos
dois fixtures, erro claro de exaustão. Guard estático no
testMunicipalityAllocatorConventions + spec int de stress.
Review estrutural + qualidade (sem P1): spec ganha 3 pins do contrato mais
sutil (loading em voo, retry pós-falha, quirk gate→not-ready com resposta
stale — comportamento herdado dos 3 originais, preservado); UseAsyncSearchOptionsResult
vira tipo local (export especulativo, knip types:error); docstring documenta
o quirk e o trim; plano registra o ajuste de execução.
DemandResponsibleMultiSelect (C143), ResponsibleMultiSelect (C90) e
AsyncSearchCombobox trocam o efeito duplicado (requestId/setTimeout 250ms/
loading/failed) pelo hook compartilhado src/hooks/useAsyncSearchOptions.ts,
com isQueryReady opcional preservando a semântica de query vazia de cada
consumidor. Comportamento preservado; unit novo do hook (renderHook +
fake timers); sem schema/server/migration.
Decisão (2026-08-21): o GitHub passa a ser a fonte única — main, branches,
PRs, CI via Actions E tracker de Issues. O Forgejo deixa de ser canal ativo
(git.e issue tracker congelados pós-convergência).
Data: 17 issues migradas do Forgejo para o GitHub (frontmatter id/depends/
serializes/priority/model preservados, labels mapeados, comment de origem
em cada uma; tabela Forgejo→GitHub registrada).
Tooling: github-api.mjs estendido com a camada de issues/labels/comments/
close/workflow-dispatch/contents (mesmo shape normalizado do forgejo-api);
18 scripts trocam o transporte forgejo-api → github-api; forgejo-api.mjs e
seu teste (961 linhas) removidos; teste githubApi.unit.spec.ts novo (7 casos);
issue-transition-on-merge.mjs renomeado e reapontado.
Flips pós-merge: issue-done e plan-issue-ready agora usam o GITHUB_TOKEN com
issues:write no repo (sem FORGEJO_API_TOKEN) e fecham/flipam Issues no GitHub.
Docs: AGENTS.md, AGENT-OPS.md e skills do fluxo vivo (work-issue, plan-issue,
project-status, capture-review-debts, worktree-next-issue, agent-work-issue)
apontam o tracker para github.com/fsolla/teqo/issues.
Gates: tsc/lint/format/knip/cycles/unit 2416 verdes.
* deploy: limpeza pós-deploy do build cache e tags locais antigas (INF3/F2)
O deploy compila no homeserver e acumulava build cache + tags locais
antigas no disco raiz (incidente 19/08: 45G de cache). Após o smoke:
docker builder prune -f + remoção das tags locais teqo-1313 exceto o
SHA em uso (best-effort, registry preserva o rollback).
* feat(S9): captura de novidades da campanha na home pública (form + CTA do hero)
Seção 'Receba as novidades da campanha' acima do rodapé: nome + WhatsApp
obrigatórios, e-mail/estado/cidade/comentário opcionais, toggle pré-selecionado
'Quero fazer parte do time' gravado como subscription.campaignLevel
(time|esporadico). CTA secundário do hero vira o atalho (#novidades, scroll
suave). Contact + Subscription em withPayloadTransaction com consentimento
novo fail-closed por chave estável 'campanha-novidades' — nada é gravado sem o
texto aprovado no admin. Migration 20260819_192605 (state de contact nullable
+ coluna campaign_level). Impl plan, changelog, int + e2e.
* plans(C141,C143,C142): permissões granulares por assessor + responsáveis de demanda + UI por perfil
* plans(C141,C143,C142): permissões granulares por assessor + responsáveis de demanda + UI por perfil
* feat(S8): seção 'Nossa história' na home — vídeo da trajetória embutido do YouTube (Closes#91)
* docs(S8): resolução do conflito com S9 (história → newsletter) + nota do guard e2e dev-only
* feat(S10): pixel do Meta na página pública de campanha (campo admin em SiteSettings + PageView na home + Lead no sucesso da captura)
Campo tracking.facebookPixelId no global SiteSettings (mesma validação/normalização do precedente Petition, regra centralizada em facebookPixel.ts); home (grupo (home)) injeta o base code + PageView quando o ID está configurado (fail-closed) e o CampaignNewsletterForm dispara exatamente um Lead no sucesso do S9. Migration 20260819_213947_add_site_settings_facebook_pixel_id. Closes#94
* refactor(S10): simplify pass — Lead wiring unit-tested e raças de estado compartilhado do e2e resolvidas
e2e: stub hermético de connect.facebook.net no guard compartilhado (specs paralelos renderizam a home com o pixel); advisory lock no Postgres serializando o login do usuário de teste (Payload perde sessão em login concorrente read-modify-write — classe documentada no webauthn); seedTestUser vira create-if-missing (sem janela de usuário ausente nem reset de senha); Lead do form protegido do catch de erro (tracking nunca falha uma captura commitada); descrição do campo compartilhada; unidade nova campaignNewsletterLead cobre exatamente um Lead no sucesso.
* feat(C141): perfis de permissão por assessor — Visão × Edição configuráveis, enforcement fail-closed (Closes#105)
* chore(C141): regenera changelog agregado após rebase com S10
* plans(C144): options write-scoped para formulários de escrita pós-C141
---------
Co-authored-by: Francisco Solla <admin@solla.dev>
O fail-closed silencioso da S3 deixava a assessoria cega: token errado não deixava
rastro além do picker vazio. Fim da caixa-preta:
- `instagramSyncStatus` (jsonb hidden) na global `social-feed-settings`, escrito pelo
render, pelo afterChange (sync awaited quando a credencial IG muda) e pela rota
`POST /api/social-feed/sync` (botão, auth admin + same-origin).
- `InstagramApiError` + `describeInstagramError`: o fetch parseia o body de erro da
Graph API e o mapeia para copy pt-BR com a correção (OAuthException/expired →
"token inválido/expirado ou emitido via Facebook Login — gere pelo Instagram Login";
"Invalid user id" → confira o ID; 5xx → indisponível; TypeError → rede; AbortError →
demorou demais). O ID só casa com "user" E "id" juntos (token rejections real falam
"the user must be logged in" — não podem virar a branch de ID).
- `InstagramSyncStatusPanel` (campo ui irmão do picker): 5 estados fiéis ao rascunho;
gate respeita o master `enabled`; `outcome` transiente limpo ao editar credencial;
aria-live nos boxes interativos. Home inalterada (fail-closed intacto).
- Bug real: `afterChange` roda dentro da transação do save (row lock até os hooks
retornarem); persistir o status via pool numa 2ª conexão deadlockava no lock da
própria linha (~80-230s/save). Fix: persists do sync-hook na MESMA transação via
`getPostgresTransactionDatabase` (C6); render/rota sem transação seguem no pool.
Diagrama: UPDATE transactionid Lock vs idle in transaction; fix verificado 45ms vs 232s.
- Stub IG ganha estado `invalid-token`; runbook docs/ops/instagram-feed-token-runbook.md;
migration add_instagram_sync_status (jsonb nullable).
- Débitos registrados: #124 (janela de lock) e #125 (polish P3).
- Gates: unit 2463 + int 791; lint/format/tsc/cycles; build; e2e admin 5/5 + frontend serial 19/19.
Seção 'Receba as novidades da campanha' acima do rodapé: nome + WhatsApp
obrigatórios, e-mail/estado/cidade/comentário opcionais, toggle pré-selecionado
'Quero fazer parte do time' gravado como subscription.campaignLevel
(time|esporadico). CTA secundário do hero vira o atalho (#novidades, scroll
suave). Contact + Subscription em withPayloadTransaction com consentimento
novo fail-closed por chave estável 'campanha-novidades' — nada é gravado sem o
texto aprovado no admin. Migration 20260819_192605 (state de contact nullable
+ coluna campaign_level). Impl plan, changelog, int + e2e.
Root cause (Issue #119): auto-merge armado com GITHUB_TOKEN mergea como
github-actions[bot] e o anti-recursion do GitHub nunca cria runs para o
evento closed — os flips pós-merge (issue-done / plan-issue-ready) não
rodavam em silêncio (PR #746). Arm agora usa AUTOMERGE_PAT (fail-closed);
workflow_dispatch de recuperação nos dois flips; forgejo-issue-transition
fecha a Issue (PATCH state=closed); forgejo-api retenta 410 (proxy respondeu
antes da origem) e loga corpo nginx de forma compacta.
Causa raiz (reproduzida, refuta a hipótese da Issue): daysAgo() chamava
Date.now() por chamada; as duas daysAgo(40) de Beta/Alpha podiam cair em ms
diferentes sob carga, deixando Alpha estritamente mais novo — sortStale
ordenava por timestamp e o tie-break por nome nunca disparava (0.08% das
construções sem pausa; 100% com pausa de 1 ms; fixture ancorado: 0 em 2000).
Fix test-only: âncora fixtureNowMs no load do módulo (padrão prioritiesTool)
+ hoist do timestamp empatado (igualdade estrutural) + comentário.
Nada muda em produção — o sort real é determinístico sobre timestamps fixos.
Verificação: spec 30/30, unit 2405, int 791, tsc/lint/format/cycles verdes.
O sintoma primário da Issue (deadlock do waitForChecks no próprio pending)
já tinha sido entregue pelo OPS61. O secundário — o rollup `checks` reportava
SUCCESS com um shard de e2e vermelho porque o Forgejo 9.0.3 reporta o
resultado do ÚLTIMO shard como o parent de matrix no `needs` (runs 604/607/610;
o `needs.<job>.results` do GitHub não existe neste runner) — foi eliminado
estruturalmente pelo OPS62 (mergeado durante a execução): ci-pr.yml e ci.yml
viraram jobs únicos sequenciais, sem matrix/shard, e o status do job `checks`
já é o veredito honesto da cascata.
O que este item entrega: (a) teste-pino no waitForChecks (cenário run 607:
rollup verde + shard vermelho → o CLI LANÇA "Checks falharam" — nunca confia
no rollup sozinho; invariante de defesa válido e que pegaria qualquer matrix
que volte); (b) docs honestas (agent-pr-workflow.mdc, AGENT-OPS.md) com a
narrativa do parent de matrix e da eliminação estrutural pelo OPS62. O script
ci-rollup-check.mjs e os edits de workflow planejados foram descartados no
gate (redundantes pós-OPS62). Unit 2392 verdes; gates locais ok.