- player mobile 226px (CTA do detalhe acima da dobra), 330px a partir de lg
- painel de downloads no mobile: CTA primário primeiro, 'Outros arquivos' depois
- cards mobile só com título/funcionalidade; data e 'Abrir reel' a partir de lg
- despublicado: aviso + publicação antes da mídia bloqueada; 'Sobre o reel' depois
- feature sem '(#cards)' na UI e data de publicação só com o dia
- 'Abrir reel' aria-hidden (o card inteiro é o link)
Design tier: openai/gpt-5.6-sol — crítica final certificada (v3, 10 capturas).
Lista publicados-only com capas 9:16, detalhe com player 9:16 e painel de
downloads (vídeo sem áudio primário; com áudio, mp3 e .srt quando existem;
transcrição em texto) e kill switch reversível: despublicar tira da lista e
bloqueia a mídia (C193 decision B), republicar reativa e re-carimba publishedAt.
Fiação: gate communicationCatalog (rename do speechCatalog), 3º sub-item da
vertical no nav, rule da lista no chrome, paths e loader próprios; sem
migration, sem Consent, sem tocar Media.read ou a rota de mídia do C193.
Design tier: DEGRADED — crítica final do designer indisponível no tier
primário (limite de uso); requer sign-off humano antes de push/PR.
- skill + command finos: shot list é a fonte única; ajuste regenera, nada publica
- scripts/build-reel.mjs + scripts/lib/reel*.mjs: captura CDP com timestamps de
parede (sem drift), cursor/halo DOM-injected, zoompan do log de cliques,
legendas queimadas, capa 1080x1350-safe, concat H.264 1080x1920/30fps
- ffmpeg: FFMPEG_PATH -> PATH -> @ffmpeg-installer (registry-only) com probe fail-closed
- kit oficial da campanha: public/campaign-kit (marcas positiva/negativa, numero,
estrela) + manual em docs/campaign-kit + README de regras; skill e agente de
design referenciam o material
- 26 testes unit novos + pin do command; changelog 2026-09-18-c196
Closes#1155
O run real (Educação) entregava o dossiê mas o boletim media 3502px vs 1133px úteis: os 15 fatos do acervo (sphere area) dominavam os destaques com o summary integral e não havia contador nem fit no one-pager.
- dossieBlocks marca as falas do acervo com sourcePanel: true; buildBulletin as mantém contadas no "e mais N" e fora das vagas impressas (destaques/E mais ficam com findings com fonte)
- buildBulletin ganha printLimit; emitHtmlPairPdf ganha onBulletinOverflow (espelho do onDossierOverflow do C188) e o builder do tema re-renderiza com 2 fatos a menos até caber, fail-closed
- boletim subject (tema/instituição) ganha o contador C188 que só o município tinha, com copy.remaining por descritor
- contrato do tema documenta brief/summary (C188) na skill e no subagente researcher
O GITHUB_TOKEN exportado no shell desde o OPS71 (PAT fine-grained) expirou e,
inválido, sombreava a credencial válida do gh (keyring) — quebrando
pnpm issue/agent:*/github-pr.mjs e o próprio gh (o bash dos agentes herda o env
do opencode serve, não do .bashrc). github-api.mjs passa a anexar dica acionável
a todo 401 (REST + GraphQL), com 2 testes de regressão; AGENT-OPS documenta a
expiração e a recuperação (derivar de gh auth token).
Mesmo lote do OPS71: o secret AUTOMERGE_PAT também expirou (safety net falha
fail-closed desde 21:39Z) — rotação é ação humana pendente.
C201/#1169: o typecheck da main (RED desde bdaf3c7c) foi corrigido pelo PR #1171;
este PR fecha o tracker item.
Post-mortem: docs/postmortems/2026-09-18-github-token-401.md
Modo "Por tema" no acervo de falas: expansão determinística do tema via
DeepSeek (generateObject, timeout, fallback null) alimenta o motor lexical
existente (searchText/keywords OR), mantendo facetas AND, paginação por fala,
o OR de cortes do C174 e o access control intacto. Resultado de tema mostra
selo e "Por que apareceu" com trecho real (frase contínua), sem score;
degradação honesta para termo exato com aviso e vazio honesto com saídas.
- URL: mode=tema (default omitido; canonicaliza sem q), seletor otimista
- ai/expandSpeechSearchTheme + lib/speechThemeTerms (sanitiza saída do modelo)
- view model com themeMatchTerm + espelho speechMatchesSearchTerm
- UI do card de tema (selos, proveniência, ações) e aviso degradado
- sem migration/Consent/exposição pública; gate de leitura inalterado
- testes unit/int/e2e; design hi-fi estendido e certificado pelo designer
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.
- restaura o embed do YouTube como superfície opt-in no SpeechDetailPlayer
(a Câmara segue a entrada padrão; o embed nunca é a porta automática, guardrail C178)
- controle simétrico "Assistir no YouTube"/"Assistir na Câmara" no bloco de saída,
com o ponto da fala preservado na troca (pendingSeekRef/youtubeStart)
- SPEECH_VOD_PLAYER_POLICY passa a pollAttempts: 2 / pollDelayMs: 3000 — espera
bounded dentro do clique em vez de tratar o primeiro GERANDO como falha
- unit do resolver (pin + GERANDO→PRONTO) e do player (ida-e-volta + ponto);
pin SSR do e2e mantendo o embed fora do HTML inicial
Closes#1103
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.
Nenhum modelo openai/* (luna/sol-fast/terra inclusive) fora dos agentes de design, por /models, --model, small_model ou config. Cobre o uso de gpt-5.6-luna em sessoes build. Regra always-on no AGENTS.md + doutrina/AGENT-OPS; guard estatico em tests/unit/openaiProviderScope.unit.spec.ts.
O frontier designer (openai/gpt-5.6-sol, OAuth ChatGPT Plus) só roda Criar/Criticar o artefato de item que muda UI. A regra vive na doutrina ui-design-html.md §Escopo de dispatch (gate de 3 perguntas + lista fechada); plan-issue e execution-pipeline apontam para o dono; o agente recusa fora do escopo e o smoke do guard vai para o designer-degraded.
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.