Terceira variante da família de dossiês: recorte pela área canônica do
acervo (SPEECH_TOPICS), com o pipeline C186/C187 generalizado para a forma
subject via THEME_UNIT (generalização aditiva; C186/C187 byte-idênticos).
- resolveSpeechTopic no dono speechFacets (fail-closed, sem slug inventado)
- themeSnapshot + extract-theme-snapshot (THEME_REPORT_CONFIRM=1, read-only)
- build-dossie-solla-tema (pack medido, PDF A4 + .md + boletim 1 página)
- skill/comando/2 subagentes (researcher por era + redator) + specs
- cenas theme-only no descritor (acervo universo/amostra, abrangência,
lacunas, hierarquia de fontes, pills do boletim) certificadas pelo designer
Closes#1143
Revisão de produto a partir do uso real (UFBA): as seções deixam de ser
capadas e fluem por quantas folhas precisarem, com packing medido
(probe → grow/shrink por folha, scripts/lib/dossiePack.mjs) e guarda de A4
no emit; o dossiê ganha redação de abertura (narrative.json escrito por
sub-agente e auditado pelo orquestrador), parágrafo de consolidação por
era, páginas de síntese e gráficos (recursos com fase, abrangência,
trajetória) e acervo como amostra declarada. O dono (dossieBlocks/
dossieRender/dossieUnit/buildPdf) foi generalizado e o ramo cidade adotou
as mesmas decisões; skills, comandos, agentes redatores, specs e designs
hi-fi re-sincronizados.
Dono único de orçamento de texto em reportText.mjs (capList,
excerptDeepDive, joinWithRemainder, summaryText/summarySurfaceText,
summaryListText), reusado por C163 e C186. Contrato summary por item nos
dois arquivos de pesquisa; superfície de resumo nunca corta com "…" e
aponta o aprofundamento; toda lista capada mostra contador; builders com
2 passadas guard-gated (integral → ponteiro → die fail-closed).
Closes#1136
Generaliza aditivamente o pipeline C186 (default município; specs verdes) via o
descritor scripts/lib/dossieUnit.mjs (municipality|institution), extrai o emit
A4/fit-guard para scripts/lib/buildPdf.mjs e a receita read-only para
scripts/lib/readOnlyExtract.mjs.
- Catálogo fail-closed src/lib/institutionCatalog.ts + institutionNameAliases.ts
(seed UFBA/Correios/Enfermagem; escape one-off --slug/--name; dedup).
- Skill/command/subagente /dossie-solla-instituicao; eras A/B/C reusadas.
- Abrangência instituição × setor × rede nunca somada; era sem fonte vira página
de lacuna explícita; lista capada declara "e mais N" (regra C188 no dono).
- Boletim modelo 1×A4 com fase junto ao valor, sem fontes, sem CTA; herda apenas
fatos com fonte do dossiê (incl. falas do acervo read-only).
- Sem schema/migration/DB write; artefatos gitignored.
O builder cortava o texto no meio com "…" quando não cabia no A4, perdendo
informação. Agora:
- contrato de pesquisa ganha `brief` (`title` ≤80 / `note` ≤120) reescrito pelo
researcher: o PDF imprime o brief reformulado e o registro/.md mantém
`answer`/`details` integrais (sem fonte nova, sem trocar a fase);
- sem `brief` o renderer cai no texto completo e a guarda de fit A4 falha
fechado — nunca mais corta em silêncio;
- tabelas longas (lacunas, notícias) e os cards de "Atuação registrada" das eras
são paginados em folhas de continuação: nada é truncado nem descartado;
- corrige honrarias da Era C vazando para a página da Era B;
- remove o `clipCopy`/`MAX_NEWS_ROWS`; SKILL.md e agent doc documentam o `brief`.
Duas causas independentes mantinham o agente de desbloqueio (OPS106) sem
entregar:
1. `fetch failed` no launcher: o homeserver só alcança o GitHub pelo proxy
CONNECT local (github-tunnel + http-proxy-socks) e o `fetch` global do Node
ignora HTTP(S)_PROXY sem `NODE_USE_ENV_PROXY` (Node >= 24). O launcher
morria lendo os jobs do run (run 35171293357) e nenhum token era criado.
`scripts/auto-unblock.sh` passa a exportar `NODE_USE_ENV_PROXY=1` quando há
proxy no env; o agente destacado herda.
2. exit 127 do agente: o serviço do runner não carrega o profile interativo,
então `~/.opencode/bin` fica fora do PATH e o `timeout ... opencode` morria
com *No such file or directory* (log unblock-35101121310.log), deixando o
token `blocked` sem PR. Novo `resolveOpenCodeBinary` (OPENCODE_BIN → argv0
com path → `$HOME/.opencode/bin` → PATH) usado pelo wrapper, pelo `--check`
e por um gate fail-closed no launcher (token blocked, sem provisionar
worktree).
Runbook + changelog OPS126; unit tests do resolver. Token órfão #1089 fechado
no replay. Flakiness de e2e full segue na #1124.
Contrato novo emendasIndicators[] (author+sphere obrigatórios, fonte obrigatória, fail-closed
para lacuna) alimenta um bloco indicatorList na página 1: uma linha por indício com
valor, finalidade, badge de esfera (município/região/polo) e link de fonte. O painel
fixo "O que NÃO anunciar" sai da página 1 e o par vira panel único de anúncio (3 bullets
invariantes viram 1), preservando a advertência de defeso. Atualiza a skill canônica e
os testes de validador/blocks/render.
Fecha o resíduo da classe S6 da OPS119+ no resto de scripts/. F1: SCRIPTS_SPEC_PINNED (42 módulos scripts/** alcançáveis de specs unit+int) espalhado em HIGH_RISK_EXACT, sincronizado por invariante de igualdade que anda o grafo de imports dos specs (missing = lib nova pinada; stale = rename). F2: isCodePath cobre scripts/ (classifyStaticScope = code). Sem migration/access/Consent/UI. gate:fast verde.
O painel `pnpm issues:tui` volta a enxergar o design UI depois do rename do
OPS114: `siblingPlanPaths` deriva `-ui-design.html` (vigente) e
`-ui-draft.html` (legado) e o novo `resolveUiDraft(planPath, exists)` — puro,
com a existência injetada pelo CLI — é dono da precedência (design vence,
draft é fallback, nenhum → design com `exists:false`). O `readPlans` sonda
design→draft no disco e o caminho REAL flui por `plansByNumber`/`buildIssueRow`
até o view model; o contrato `--json` segue `version: 1` com o shape
`{ path, exists }` e a chave histórica `uiDraft`. As superfícies do painel
trocam "rascunho UI" por "design UI".
Retrocompat é só de leitura — o acervo legado (30 `*-ui-draft.html`) não é
tocado. Unit dos dois specs cobre design-only, draft-only, ambos, nenhum, o
caminho real, body linkando o impl e o shape do JSON. Sem migration.
`worktree plan` sem argumento cria a sessão persistente sem driver (contrato do
`new`); com bag auto-submete uma única `/plan-issue <bag>` e usa o mesmo texto
como rótulo do branch, espelhando o `fix`.
- purposeInvocation (lib/agent-session): ramo plan reusa a sanitização do fix e
devolve null quando o bag fica vazio (driverless por desenho).
- opencodeLaunchDirective emite --argument para fix||plan; cmdPlan repassa
argument: bag; cmdStart isenta plan do aviso "sem comando".
- next/new/fix, --stay e o comando /worktree intactos.
- Specs repinados; textos vivos e changelog atualizados. Débito de risk map das
libs + pin do wiring registrado em #1071.
- Frente de oposição: lista de pesquisa 'opposition' (fato publicado com URL+data, sem inferir culpa) renderizada na seção 2, com nota de guardrail e URLs na seção de fontes.
- Dobradinhas: lista 'alliances' (com quem Solla soma na cidade) como bloco na seção de rede, insumo direto da abordagem.
- Investimentos: lista 'investments' (Solla, estado e federal, município e região; empenho ≠ pagamento) como bloco na conjuntura.
- Seções dinâmicas: numeração por posição; Sinais recentes e Demandas e visitas omitidas quando vazias (lacuna explícita só onde é produto).
Skill, changelog e testes unit cobrindo validador e builder.
A tabela "Notícias internas e imprensa local" não tinha larguras: o layout automático dava ~2.000px à coluna Link (URL crua, sem espaços) e estourava a página (tabela de 2.261px), espremendo o Título a 64px. Larguras explícitas (Data 9 · Veículo 14 · Título 49 · Link 28) tornam a tabela table-fixed, quebram a URL dentro da célula e dão ao Título — o texto mais longo — a maior fatia. Decisão persistida no script e na skill relatorio-cidade, com entrada em docs/changelog.
Seleção do C166 ganha "Cortar vídeo": título/descrição por IA (editáveis, com fallback determinístico), job assíncrono com ffmpeg por re-encode exato e polling dos quatro passos, e página pública unlisted /corte/<id> (noindex, OG, crédito CC BY) servindo o mesmo MP4. Collection speechCut com access canReadSpeechCut (advisor/leader negados) e kill switch por status; ffmpeg entra só no runner da imagem com FFMPEG_PATH para teste.
Workflow separado auto-unblock.yml (workflow_run no Deploy, push/main + job verify vermelho pela jobs API), launcher com flock próprio + Issue token auto-unblock como single-flight, e wrapper destacado que provisiona worktree fix/* via worktree.mjs fix --headless, aplica guard de banco local teqo_wt* fail-closed e roda opencode run --command bug-fix (cap 4h). deploy.yml/verify intocados; sem auto-retry e produção segue com approval humano.
A varredura de produção (2026-09-14) achou 33 falas em selCodColecaoCsv=J e 1
com o parâmetro vazio: o endpoint devolve 'Documento não encontrado' e a mesma
data/página resolve na coleção D que a busca atual da Câmara gera. Aliases
J/vazio → D, puros e unit-testados; DCN e demais códigos ficam como estão.
- puros: classify/parse da URL legada (dc_20b/dc_20, montaPdf), build do PDF
direto com #page, cache key por publicação, plano de reparo e fallback
- camaraFetch: resolveOfficialPdfUrl segue o redirect via .leg.br (fail-closed)
e probeLink generalizado (Range + contentType)
- import: normaliza officialTextUrl no write path (cache <out>/diario por
publicação; falha preserva PDF direto anterior, senão null)
- novo modo --repair-links [--dry-run] com guard CAMARA_IMPORT_CONFIRM fora de
alvo local, relatório de cobertura e idempotência
A camada de bairro desce à seção eleitoral: votos por seção (votacao_secao_2022_BA),
cadastro seção → local → bairro (eleitorado_local_votacao_2022), aferição por seção
(detalhe_votacao_secao_2022_BA) e candidaturas válidas (votacao_candidato_munzona_2022);
133 coordenadas ausentes preenchidas pelo cadastro atual pelo número do local.
- JSON commitado: 138 bairros, 474 locais, 5.023 seções; Solla 27.264 e Ceuci 19.776
conferidos zona a zona contra o recorte C157.
- Extrator scripts/build-solla-ceuci-salvador-bairros.mjs (--download cacheia em
data/tse-2022/, gitignored; sem banco).
- Gerador revisado: mapas por bairro com dados reais, quadrantes de LQ, tabela de
138 bairros e filas de ação por bairro.
- Personas refeitas com a base granular: sobreposição 62,9% nos bairros (vs 67,8%
nas zonas); Ceuci supera Solla em 25 bairros com 61,6% do voto dela; redutos
compartilhados Pituba/Brotas/Itaigara/Ondina/Graça/Imbuí/STIEP/Pituaçu; pontes
Rio Vermelho/Cabula/Federação/Jardim Armação.
- Métricas puras de bairro + 9 unit tests; README e changelog atualizados.
Closes#992