A arquitetura do Alembic repousa sobre exatamente quatro propriedades — não seis, não "boas práticas", mas quatro invariantes nomeados, cada um afirmado no código e a maioria governada por uma ADR. São as regras que todo pacote mantém, a razão de o sistema ser testável, reproduzível e confiável. Conhecê-los é conhecer a espinha do motor. Fonte: docs/alembic-complete-map.md §2, verificado contra os arquivos citados.
Uma nota sobre "seis". Uma versão mais antiga deste curso dizia "seis invariantes". Isso estava desatualizado. O mapa como-construído lista exatamente quatro — a contagem abaixo é a autoritativa. Confundir "princípios" extras com os invariantes que sustentam carga é precisamente o tipo de divergência que o método de engenharia reversa (lição 20) existe para pegar.
Os quatro, em resumo
① run() nunca lança; o resultado é uma união discriminada uniforme
Você viu isto na lição 14. É imposto estruturalmente por runWithGuards (adapter-core.ts:118) e verificado em produção — o handoff registra um 429 real surgindo como falha tipada. O núcleo de orquestração restabelece a mesma fronteira uma camada acima com runDebateSafe/runSwarmSafe (harness/src/core.ts:313-334), então um throw dentro de um passo de council ou swarm também vira um valor. Governado pela ADR-0009.
② Os motores são agnósticos a adapter E a store
Os kernels puros recebem apenas vistas readonly e efeitos colaterais injetados. O DebateEngine, o scoring e o verifier recebem um AdapterRegistry injetado (council/src/debate.ts:71-83). A camada ETL roteia todo IO por um FsPort injetável — "toda função aqui é testável em memória". E o funil recebe um registry de adapters injetado, então trocar por um registry offline torna a run inteira $0 e hermética (funnel.ts:79-81).
// o padrão, em todo lugar: dependências chegam como argumentos, nunca imports de concretos
runDebate({ board, pack, adapters, requestId }); // adapters injetados → teste com fakes
runT0Pipeline(corpusDir, { fs }); // FsPort injetado → roda em memória
runFunnel(corpusDir, { adapters: offlineRegistry }); // registry offline → $0, hermético
Esta é a lição 5 (portas & injeção) elevada a lei arquitetural: nenhum motor alcança um adapter, filesystem ou store concreto. É exatamente o que faz a suíte de 400+ testes rodar rápido, em memória, sem rede — e o que permitiu testar os subsistemas do @alembic/hermes (lições 7–13) sem abrir um único socket.
③ IDs content-addressed + layout determinístico de run-dir
O id de uma run é o hash de conteúdo (estilo SHA-256) do seu spec: runIdFor(spec) (swarm/src/orchestrator.ts:168). Mude qualquer campo do spec e você obtém um novo diretório de run — runs são reproduzíveis porque inputs idênticos convergem para o mesmo id e o mesmo lugar no disco:
// run dirs: <baseDir>/runs/<runId> · events.jsonl append-only + checkpoint.json// stores são content-addressed por SHA-256 sobre JSON canônico (chaves ordenadas) →// re-anexar conteúdo idêntico é no-op, então re-runs convergem em vez de duplicar
É por isso que módulos de plano (alembic.plan.ts) precisam ser determinísticos — Date.now(), new Date() e Math.random() são rejeitados pela VM. Não-determinismo mudaria o hash do spec a cada run e quebraria o replay, então o motor recusa carregar um plano que o contenha (a lição 17 cobre o portão que impõe isso em planos).
④ Dissenso é preservado/forçado pelo Verifier, não meramente por um prompt
Este é o mais sutil, e o que mais times erram. Muitos "councils" adicionam um prompt que diz "faça advogado do diabo" e chamam isso de adversarial. O Alembic não confia num prompt para produzir dissenso — ele torna o dissenso estrutural:
O Verifier maker-checker é read-only por arquitetura: 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 (council/src/verifier.ts:19-99).
"Contrarian-last" é imposto no board-load — contrarian_not_last é um erro duro de carregamento (council/src/board.ts:142-149) — e realizado como sequenciamento real (fases seriais, membros paralelos).
Um bug que foi corrigido, registrado com honestidade. O handoff já listou "contrarian-last é ficção (o prompt diz, o código roda paralelo)" entre bugs a não levar adiante. No código atual já não é ficção: é imposto no board-load e realizado por execução serial de fases. O mapa nota a divergência e diz "o bug foi corrigido" — proveniência acima de polimento. Governado pela ADR-0003: não há Role "contrarian" privilegiado; a pressão adversarial é trabalho do Verifier.
1. Quantos invariantes arquiteturais o mapa como-construído nomeia?
Correto: c. O §2 do mapa lista quatro. Um curso antigo dizia "seis" — estava desatualizado. Os quatro são as propriedades que sustentam carga; tudo o mais é consequência ou convenção.
2. Por que um módulo de plano deve evitar Date.now() e Math.random()?
Correto: b. O id da run é um hash do spec; não-determinismo geraria um hash diferente (e run-dir diferente) toda vez, derrotando replay e cache. Determinismo é pré-condição do invariante ③, então o motor recusa planos não-determinísticos.
3. O que torna o invariante de "dissenso" do Alembic mais forte que um prompt "seja um contrarian"?
Correto: d. Um prompt pode ser ignorado ou racionalizado. O Alembic impõe o dissenso na arquitetura: um Verifier independente, sem mutação, com oráculos determinísticos, mais a ordenação contrarian checada no load. A ADR-0003 faz da pressão adversarial uma propriedade do sistema, não um papel.
Confusões comuns
"Agnóstico a adapter é só uma interface." Significa mais: motores nunca importam um adapter, filesystem ou store concreto — esses chegam como argumentos injetados. O teste do invariante é que você consegue rodar qualquer motor totalmente em memória com fakes, o que a suíte faz por toda parte.
"Content-addressing é para dedupe." Dedupe é um benefício colateral. O propósito mais profundo é replay e convergência: inputs idênticos produzem o mesmo id de run e o mesmo local no disco, então um re-run retoma ou re-deriva o mesmo resultado em vez de bifurcar um novo.