Aurora SIGER — Fase 5: NCAS, a Colônia Ganha Memória
A colônia que operou no tempo e se mapeou no espaço agora se lembra. Persistência dupla (JSON e log append-only), cinco regras de álgebra booleana provadas por tabela-verdade exaustiva e um alerta que deixa de ser digitado para ser derivado da simulação da Fase 3. Projeto acadêmico FIAP 2026.
Python · Pytest · Álgebra booleana · Persistência em arquivo · Engenharia de prompts
O cenário
As quatro primeiras fases do Aurora SIGER viveram inteiramente em RAM. A telemetria da decolagem, a fila de pouso, os sete sóis de operação energética, o grafo de dependências: tudo era calculado, exibido e descartado. Cada execução começava do zero e não deixava rastro. Um sistema assim consegue reagir, consegue prever e consegue mapear, mas não consegue responder à pergunta mais banal de qualquer operação real: o que aconteceu ontem, e como eu confiro?
A Fase 5 responde a essa pergunta dando à colônia a primeira coisa que ela nunca teve: memória em disco. O sistema que materializa isso é o NCAS, Núcleo Cognitivo da Aurora Siger, uma aplicação de menu com 13 opções, construída só com a biblioteca padrão do Python, que registra, organiza, consulta e interpreta as informações operacionais da colônia.
É o quinto ato do arco. Decolagem, pouso, operação, topologia, e agora memória. E o salto conceitual da fase não está no que o NCAS grava, e sim no que ele decidiu não gravar.
Como funciona
Dois formatos, um critério
O sistema escreve em dois formatos, e o critério é a natureza do dado.
| Dado | Formato | Por quê |
|---|---|---|
| Módulos, alertas, interações | JSON | vários campos chave/valor, lidos, filtrados e atualizados individualmente |
| Registro cronológico da colônia | TXT append-only | linhas sequenciais, só acrescentadas ao final e lidas em ordem |
A justificativa fica clara quando se imagina a troca. Um log em JSON exigiria, a cada linha registrada, ler o arquivo inteiro, inserir um item e reescrever tudo: custo linear na história acumulada para anotar um único evento. Um cadastro em texto puro exigiria varrer e reparsear linhas toda vez que alguém perguntasse "quais alertas são críticos?". Cada formato é ótimo para um padrão de acesso e péssimo para o outro.
As três aberturas de arquivo aparecem com papéis distintos: 'r' para leitura, 'w' para escrita truncando (o cadastro é reescrito por inteiro), 'a' para acréscimo ao final (o log nunca reescreve o passado). Todas passam por with open(...), de modo que o descritor fecha mesmo quando a escrita levanta exceção. Sem o gerenciador de contexto, um erro no meio de um write deixaria o buffer sem descarregar, e o dado pareceria gravado sem estar.
O diretório de dados é parâmetro do construtor, nunca constante de módulo. Isso tem três consequências práticas: duas colônias podem ser registradas em paralelo, os testes escrevem em diretório temporário e jamais na raiz do repositório, e com um relógio injetado o log fica comparável byte a byte, o que estende à camada de persistência o determinismo que a Fase 3 já tinha na simulação.
As cinco regras, e a diferença entre provar e ilustrar
O coração lógico da fase são cinco regras de álgebra booleana. Cada uma é uma função pura que devolve as duas formas da expressão, a original e a simplificada, já avaliadas, junto com o teorema que autoriza a simplificação:
| # | Regra | Forma original | Forma simplificada | Teorema |
|---|---|---|---|---|
| 1 | Alerta crítico | (FALHA ∧ CRÍTICO) ∨ (FALHA ∧ ¬CRÍTICO) | FALHA | Absorção: A·B + A·B' = A |
| 2 | Alerta geral | (FALHA ∧ CRÍTICO) ∨ CONSUMO_ELEVADO | já mínima | sem literal comum |
| 3 | Liberar consulta | AUTORIZADO ∧ MÓDULO_ATIVO | já mínima | complemento da regra 4 |
| 4 | Bloquear operação | ¬(AUTORIZADO ∧ MÓDULO_ATIVO) | ¬AUTORIZADO ∨ ¬MÓDULO_ATIVO | De Morgan: (A·B)' = A' + B' |
| 5 | Priorizar atendimento | (URGENTE ∧ ESSENCIAL) ∨ (URGENTE ∧ DISPONÍVEL) | URGENTE ∧ (ESSENCIAL ∨ DISPONÍVEL) | Fatoração: A·B + A·C = A·(B+C) |
A redundância da forma original é o ponto didático. Ela existe para ser comparada, não para ser executada. E manter duas regras irredutíveis é deliberado: simplificar é um resultado possível, não uma obrigação, e reconhecer que uma expressão já está na forma mínima faz parte do exercício.
A regra 1 é o exemplo mais limpo da absorção. Com FALHA falso, os dois termos morrem. Com FALHA verdadeiro, sobra CRÍTICO ∨ ¬CRÍTICO, que vale sempre. O valor de CRÍTICO não altera a saída em nenhuma linha, logo ele não pertence à expressão mínima:
| FALHA | CRÍTICO | (F∧C) ∨ (F∧¬C) | F | equivalente |
|---|---|---|---|---|
| F | F | F | F | ✓ |
| F | V | F | F | ✓ |
| V | F | V | V | ✓ |
| V | V | V | V | ✓ |
O detalhe que muda a natureza dessa tabela é onde ela é verificada. A verificação acontece em teste automatizado, sobre todo o domínio de entrada, via itertools.product:
@pytest.mark.parametrize("falha,critico", itertools.product([False, True], repeat=2))
def test_alerta_critico_preserva_tabela_verdade(falha, critico):
check = rules.alerta_critico(falha, critico)
assert check.original == check.simplificada
Quatro combinações para as regras de duas variáveis, oito para as de três, 28 linhas ao todo, e nenhuma delas escapa. Conferir a equivalência sobre um único par de valores mostra que os dois lados coincidem naquele ponto. O teorema afirma que coincidem em todos, e uma amostra de tamanho um não distingue identidade algébrica de coincidência numérica.
Vale registrar que a purificação da regra e a prova do teorema foram o mesmo movimento. Enquanto a função imprimia por dentro, testá-la exigiria capturar stdout. Devolvendo um objeto com as duas formas avaliadas, a tabela-verdade exaustiva saiu de graça.
O alerta que se tornou verdadeiro
Aqui está a decisão que define a fase.
Na versão original, as regras booleanas recebiam três valores digitados pelo operador (falha, crítico, consumo_elevado), e esses mesmos três ficavam gravados dentro de cada alerta no arquivo JSON. Só que as fases 3 e 4 já calculam essas grandezas a partir da física da colônia. O estado de falha de um módulo é uma propriedade da simulação. A criticidade sai da árvore de tiers. O consumo instantâneo é calculado hora a hora, com termo térmico e fator de estrangulamento de potência.
Persistir um booleano derivável cria uma segunda fonte de verdade que envelhece em silêncio. Se o módulo for reparado na hora 60, o "falha": true gravado no arquivo vira mentira e nada no sistema reclama. O dado não fica corrompido, fica desatualizado, o que é pior, porque continua parecendo válido.
Então o JSON passou a guardar apenas o que o sistema não sabe calcular: que ocorrência houve, quando, e com que prioridade a tripulação a classificou. O resto é derivado no momento da análise. Nome do módulo e criticidade saíram pelo mesmo raciocínio. É o mesmo movimento que a Fase 4 fez ao derivar a prioridade dos nós da árvore de criticidade em vez de manter uma tabela paralela.

A figura acima não é ilustrativa: é a saída de cognition.predicates rodando hora a hora sobre a simulação canônica. O NCAS abre avançando o relógio até o primeiro instante em que um módulo com alerta registrado está em falha. Com seed=42, isso acontece na hora 239, sol 9: a Produção de Alimentos quebra, e o alerta 14, que existia como linha de dados inerte desde a semeadura, passa a satisfazer ALERTA = FALHA. A expressão deixou de ser exercício de digitar s no terminal.
Duas observações que a figura entrega de graça. A primeira é que os dois módulos com mais alertas registrados, Suporte de Vida (7) e Habitação (4), atravessam os onze sóis sem quebrar nem consumir demais: as faixas deles estão vazias. O registro da tripulação e a física da colônia são camadas distintas, e o sistema mostra exatamente onde as duas se encontram. A segunda é que o consumo elevado não é evento raro. Produção de Alimentos, ISRU e Laboratório passam a maior parte do tempo acima do próprio modo adequado, porque entram em modo surplus quando há energia sobrando. Um predicado que fosse digitado uma vez e gravado nunca capturaria isso.
O limiar que separa as duas populações foi medido, não escolhido. Na simulação canônica, um módulo em modo adequate fica exatamente em 1,00× do seu nominal, enquanto os que entram em surplus saltam para algo entre 1,8× e 2,5×. Qualquer fator entre 1,05 e 1,7 separaria os dois grupos, e CONSUMO_ELEVADO_FATOR = 1.2 fica no meio da folga, longe das duas bordas. O limiar é por módulo, comparando o consumo instantâneo contra o próprio modo adequado, e não contra a bateria da colônia inteira, o que trocaria a escala do predicado no meio do caminho.
Um detalhe de engenharia que vale mencionar: a abertura do sistema procura o evento em vez de avançar um número fixo de horas. Fixar 239 funcionaria hoje e apodreceria em silêncio se qualquer constante da Fase 3 mudasse, porque o número está calibrado para o gerador de números aleatórios atual. Procurar a primeira falha relevante sobrevive à mudança.
O assistente que não chama modelo nenhum
A fase inclui quatro técnicas de engenharia de prompts, cada uma escolhida por adequação à tarefa e não por variedade: zero-shot para resumo de alerta (tarefa autoexplicativa), few-shot para classificação em conjunto fechado (três exemplos rotulados ensinam o formato), saída estruturada em JSON para resposta consumida por outro programa, e tradução para linguagem simples para ampliar quem consegue auditar o sistema.
Os quatro templates são código, não configuração. Um template é lógica de aplicação: versiona junto com quem o consome, e uma mudança nele aparece no diff. A renderização levanta erro se faltar um campo, em vez de produzir um prompt com um buraco {...} no meio, porque prompt truncado é bug silencioso: o modelo responde alguma coisa, e ninguém percebe que a pergunta estava incompleta.
O assistente que responde a esses prompts não chama modelo de linguagem algum. As respostas vêm de casamento de palavra-chave sobre tabelas declaradas no topo do módulo, e cada resposta carrega um campo gatilho: a palavra exata que decidiu a classificação, ou vazio quando caiu no caso padrão. Para saber por que um alerta foi classificado como risco alto, lê-se o campo, não o código-fonte.
A escolha custa capacidade. Um modelo real resumiria alertas melhor que qualquer tabela de palavras-chave, e com folga. O que se ganha é que toda resposta permanece explicável em princípio, e é isso que sustenta a seção de ética da fase. Uma página que alega auditabilidade ao lado de uma chamada a modelo opaco estaria descrevendo um sistema que não existe.
Por que importa
Os limites da fase são específicos e vale nomeá-los.
O assistente simulado é uma simulação de comportamento, não inteligência artificial, e a distância entre as duas coisas é exatamente o que ele não consegue fazer: lidar com uma frase que não contenha nenhuma das palavras-chave previstas. O log cresce sem rotação, o que é adequado para uma colônia de treze módulos e insuficiente para qualquer coisa maior. E derivar predicados tem um custo real de acoplamento: para analisar um alerta, o NCAS precisa da simulação da Fase 3 carregada, enquanto ler três booleanos de um arquivo não precisaria de nada. A troca foi consciente. Correção contra independência, e correção ganhou.
Uma colônia que não se lembra pode reagir e até prever. Só uma que se lembra pode ser auditada.
Há uma continuidade de tema com a Fase 1, que abriu o arco perguntando quem decide quando a máquina decide. O NCAS calcula, apresenta e registra. Isolar um módulo habitado ou bloquear uma operação continua sendo assinatura da tripulação, e automação de recomendação e automação de decisão têm perfis de risco diferentes. O que a persistência acrescenta a essa posição é a possibilidade de conferir depois: sem registro, a auditabilidade é uma alegação sobre o presente. A síntese narrativa completa está no ensaio Da topologia à memória.
Qualidade e rigor
A Fase 5 leva o pacote aurora_siger à versão 0.5.0, mantendo o monolito incremental: a camada cognitiva vive em aurora_siger.cognition (journal, store, rules, predicates, prompts, assistant, analysis, seeds, essays, cli) e o entrypoint ncas é um wrapper fino sobre ela. Zero dependências externas, como nas fases anteriores. Os dois ensaios avaliados (ética e memória) viajam como Markdown dentro do pacote e são lidos por importlib.resources, de modo que existe uma cópia só e ela funciona também a partir de um wheel instalado.
| Métrica | Valor |
|---|---|
Módulos do subpacote cognition | 10 |
| Coleções JSON persistidas | 3 (módulos, alertas, interações) |
| Alertas semeados sobre os 13 módulos canônicos | 15 |
| Regras booleanas | 5 (3 simplificáveis, 2 já mínimas) |
| Linhas de tabela-verdade verificadas em teste | 28 |
| Técnicas de engenharia de prompts | 4 |
| Chamadas a API de modelo de linguagem | 0 |
| Opções do menu | 13 |
| Testes da Fase 5 | 164 |
| Testes automatizados (acumulado) | 478 |
Os testes cobrem as tabelas-verdade exaustivas das cinco regras, o CRUD em JSON incluindo arquivo ausente e arquivo corrompido, o comportamento do log append-only (readlines, fatiamento de lista, writelines), a derivação dos predicados a partir do estado da simulação, o encadeamento completo de análise de alerta e o menu. Nenhum teste escreve fora do diretório temporário, e a raiz do repositório permanece limpa. O pacote foi verificado também a partir de um wheel instalado em ambiente virtual vazio, o que prova que o caminho inteiro da fase depende só da biblioteca padrão.
Projeto desenvolvido coletivamente pela equipe de Ciência da Computação (online) da FIAP em 2026, com a consolidação ao monorepo curada por Iúri Leão de Almeida. Explore o código-fonte no repositório GitHub, leia o relatório técnico ou rode o sistema localmente com pip install -e . && ncas.