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
O `pnpm knip` imprimia `ERROR: Error loading src/payload.config.ts
(...server-only...)` e saía 0 — gate verde com o ERROR silencioso, no CI
(`ci-pr.yml`/`deploy.yml`) e local. O plugin embutido do Payload carregava
`src/payload.config.ts` via jiti sob as condições Node padrão e o import
transitivo de `server-only` lançava (só o `react-server` traz o `empty.js`);
o `paths.server-only` do `knip.json` alimenta apenas o resolver estático, não
o carregamento jiti (e o `tsconfig` alias, que funcionaria no load, foi
rejeitado por re-apontar a guarda server/client — classe OPS69).
Fix: `"payload": false` e as contribuições do plugin explicitadas no
`knip.json` (`!src/payload-types.ts` load-bearing; `!src/migrations/**`
paridade), removendo o bloco `paths` inerte. Provado: `pnpm knip` sai 0 sem o
ERROR, um export morto plantado é detectado, e a corrida de controle com
`server-only` neutralizado dá findings idênticos.
Plano: docs/plans/ops-knip-importmap-artifact-impl.md
Corrige o fundo tintado (gradiente do body herdado de :root) com um
layout de segmento `data-theme="editorial"` + scroll interno, espelhando
`[type]/layout.tsx`, e porta o gate aprovado: vídeo herói, título à
esquerda, meta em relógio, prosa medida, kit WhatsApp-first responsivo,
secundárias em grade no mobile e estado indisponível redesenhado.
Extrai os botões do share kit no próprio dono (acervo/biblioteca
inalterados) e adiciona o guard e2e `data-theme="editorial"`.
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.
`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.