O motor de decisão L2 é um kernel em camadas: um DebateEngine que preserva o dissenso, um sistema quantitativo de pontuação 0–10, um Verifier maker-checker independente, e um painel N-lentes que é o portão de emissão T3+. O truque que o torna confiável: contrarian-last e verificação read-only são impostos estruturalmente, não por prompts. Fonte: packages/council/src/{debate,verifier,board}.ts, governado pela ADR-0003.
As fases rodam na ordem declarada do board (serial), e os membros dentro de uma fase rodam em paralelo. Como as fases são seriais, uma fase posterior recebe as contribuições acumuladas das anteriores. É isso que faz "o contrarian vê tudo e fala por último" ser sequenciamento real, não um claim de prompt — o próprio comentário de cabeçalho do motor diz isso:
// packages/council/src/debate.ts:30-44 — o contrato do motor, intenção literal // As fases rodam na ordem declarada do board (SERIAL entre fases) e os // membros dentro de uma fase são despachados EM PARALELO. … A fase contrarian é // ordenada por último pelo board loader, então "o contrarian vê tudo e fala // por último" é sequenciamento REAL — fases anteriores já produziram suas // declarações antes de os inputs de modelo da fase contrarian sequer serem montados.
As chamadas de adapter obedecem ao invariante nunca-lança (lição 14): cada run() resolve para um ModelRunResult discriminado por ok, então o motor ramifica no resultado em vez de envolver chamadas em try/catch. Um membro que falha ao fazer bind, dá erro ou retorna um voto não-parseável é registrado como um MemberFailureReason — nunca uma exceção lançada.
Cada membro emite scores de eixo sobre SCORING_AXES (feasibility / revenue / cx / ttm / risk) ponderados .25/.25/.20/.15/.15. GO_THRESHOLD = 7, PIVOT_THRESHOLD = 5. O detalhe de correção não-óbvio: o eixo risk é pontuado de modo que 10 = baixo risco, somando na mesma direção de todo outro eixo — não há normalização 1 - risk para errar de cabeça pra baixo:
// packages/council/src/scoring.ts — direção uniforme; nomes de eixo são fonte única SCORING_AXES = ['feasibility', 'revenue', 'cx', 'ttm', 'risk']; // typo não zera um eixo em silêncio // score alto → GO; risk 10 = baixo risco; SEM inversão — todos os eixos somam igual
Quórum fail-closed. aggregateConsensus retorna NO_GO antes de consultar qualquer score quando há menos que MIN_VALID_AGENTS = 3 votos válidos (consensus.ts:101-125). "Um board esparso ou degradado nunca deve dar sinal verde." Um board que perdeu dois membros por falha de adapter não pode passar por acidente com um voto otimista.
O Verifier maker-checker é a encarnação do invariante ④ (lição 16). Aceita apenas vistas readonly, não expõe superfície de adapter ou mutação, e prova claims atômicos com oráculos determinísticos sobre a evidência estruturada — nunca sobre a prosa do maker:
rejected.MAX_LOOPS = 3 → estaciona para T4 (escalate-after-N), não um retry infinito.Para T3 e acima, a emissão tem portão por verifyPanel / isPanelEmissionApproved — três lentes de perspectiva diversa agregadas por quórum com um veto de preservação-de-dissenso:
| Lente | Checa |
|---|---|
| COHERENCE | = verifyDecision reusado — quórum, verdict↔score, auto-consistência |
| FAITHFULNESS | evidência presente e ultrapassa pisos de confiança/força |
| DOMAIN | um GO precisa de um sinal validation; evidência não é monocultura |
O painel aprova por quórum (padrão 2 de 3) mas uma rejeição dura em qualquer lente única veta o painel inteiro (preservação-de-dissenso), e escalate-after-N propaga. Então uma maioria confiante de duas lentes ainda pode ser detida por uma lente que achou uma falha fatal. Esta conjunção — maioria para passar, qualquer-veto para bloquear — é da qual o GO-verificado do funil (lição 15) depende. É deliberadamente mais difícil de passar que um voto simples.
MemberFailureReason, não um throw.risk = 10 significa baixo risco, então soma na mesma direção de feasibility/revenue. Não há deliberadamente um passo 1 - risk; errar isso inverteria toda decisão, então os eixos são mantidos uniformes e os nomes de eixo são fonte única de verdade.