Skip to content

Instantly share code, notes, and snippets.

@suissa
Last active June 29, 2026 00:35
Show Gist options
  • Select an option

  • Save suissa/74ec34b313722f7e6abb023a9ab9a1b2 to your computer and use it in GitHub Desktop.

Select an option

Save suissa/74ec34b313722f7e6abb023a9ab9a1b2 to your computer and use it in GitHub Desktop.
Sim. A forma mais correta é não criptografar o Event Store inteiro com uma chave única. Vamos criptografar cada evento, ou cada pequeno lote de eventos, com uma chave de dados própria, e o Linus Salamander atua como Enclave Vault/KMS interno que cria, protege, versiona e autoriza o uso dessas chaves.
A arquitetura fica assim:
Agent
├── EventSourcingSidecar
│ ├── recebe evento em claro apenas em memória
│ ├── normaliza evento
│ ├── calcula hashes/cadeia de integridade
│ └── grava evento cifrado no Local Event Store
├── CryptoSidecar
│ ├── pede chave ao Linus Salamander
│ ├── cifra payload
│ ├── remove chave da memória
│ └── decifra somente quando autorizado
└── Linus Salamander Enclave Vault
├── Master Keys / KEKs
├── políticas de acesso
├── rotação de chaves
├── auditoria de decrypt
└── unwrap temporário de Data Keys
O padrão que devemos usar é envelope encryption: o dado é cifrado com uma Data Encryption Key, e essa chave de dados é cifrada por uma chave superior, geralmente chamada de Key Encryption Key. Esse modelo é recomendado/consagrado em KMSs modernos; a AWS descreve envelope encryption exatamente como cifrar dados com uma data key e depois cifrar essa data key com outra chave, enquanto o NIST SP 800-57 é a referência geral para ciclo de vida e gerenciamento de chaves criptográficas.
A estrutura do evento gravado deveria ser algo assim:
{
"event_id": "evt_01...",
"agent_id": "UserDomainAgent_...",
"stream_id": "user:123",
"event_type": "UserContactRequested",
"event_version": 1,
"metadata_plain": {
"schema_version": "1.0",
"created_at": "2026-06-28T16:56:00-03:00",
"tenant_id": "itarare",
"agent_type": "LawyerWhatsAppAgent",
"key_id": "lsm:kek:tenant:itarare:v3",
"dek_id": "lsm:dek:event:evt_01..."
},
"crypto": {
"alg": "XChaCha20-Poly1305",
"nonce": "...",
"encrypted_dek": "...",
"aad_hash": "...",
"key_version": 3
},
"integrity": {
"payload_hash": "...",
"previous_event_hash": "...",
"event_hash": "...",
"signature": "..."
},
"payload_ciphertext": "..."
}
Eu deixaria metadados mínimos em claro e o payload cifrado. O Event Store precisa conseguir ordenar, filtrar por stream, aplicar replay técnico e fazer retenção sem abrir dados sensíveis. Então campos como event_id, stream_id, agent_id, event_type, created_at, key_id, schema_version podem ficar visíveis. Já dados pessoais, conteúdo de conversa, documentos, valores, nomes, CPF, resumo do atendimento e anexos devem ir para payload_ciphertext.
A regra seria:
Metadados operacionais: claro, mínimos e não sensíveis.
Payload de domínio: sempre cifrado.
Anexos/documentos: sempre cifrados separadamente.
Chaves: nunca ficam no Event Store.
O fluxo de escrita:
1. Agent gera evento de domínio.
2. EventSourcingSidecar normaliza o evento.
3. CryptoSidecar pede uma DEK ao Linus Salamander.
4. Linus Salamander gera ou libera uma DEK temporária.
5. CryptoSidecar cifra o payload.
6. CryptoSidecar apaga a DEK em claro da memória.
7. EventSourcingSidecar calcula hash do ciphertext + metadados + hash anterior.
8. Event Store grava somente o evento cifrado.
O fluxo de leitura/replay:
1. Agent pede replay de uma stream.
2. EventSourcingSidecar lê eventos cifrados.
3. Para cada evento, valida hash chain e assinatura.
4. CryptoSidecar pede autorização ao Linus Salamander.
5. Linus Salamander verifica policy, agent_id, tenant_id, purpose e escopo.
6. Se autorizado, libera unwrap temporário da DEK.
7. CryptoSidecar decifra em memória.
8. Agent recebe o payload reconstruído.
9. Chave e payload temporário são descartados após uso.
A parte importante: o Linus Salamander não deve virar um banco de dados de eventos. Ele deve guardar ou derivar as chaves, controlar acesso, registrar auditoria e permitir rotação/revogação. A AWS KMS documenta esse modelo: a data key é usada fora do KMS para cifrar dados, removida da memória depois do uso, e a versão cifrada da data key pode ficar armazenada junto do dado cifrado.
Eu faria três níveis de chave:
Root Key
└── Tenant KEK
└── Agent KEK
└── Event DEK
Ou seja:
Linus Salamander Root Key
Cidade/Tenant Key: itarare
Agent Key: LawyerWhatsAppAgent
Event/Data Key: evento individual ou lote pequeno
Isso te dá isolamento por cidade, por profissional, por agent e por evento. Se um advogado sair da plataforma, você pode revogar ou congelar o acesso daquele escopo sem quebrar toda a base da cidade.
Para o Local EventSource de cada Agent, eu usaria:
DEK por evento sensível
ou
DEK por stream curta
ou
DEK por janela temporal
Minha recomendação:
Eventos normais: DEK por stream + rotação diária/semanal.
Eventos sensíveis: DEK por evento.
Documentos/anexos: DEK por arquivo.
Para advogado, documentos e conversas podem conter dados pessoais e informações sensíveis. Então eu não colocaria tudo na mesma chave. A divisão segura fica:
ContactRequested → DEK por stream
AppointmentRequested → DEK por stream
DocumentUploaded → DEK por arquivo
LegalDocumentExtracted → DEK por evento
ClientConversation → DEK por conversa ou por janela
O algoritmo simétrico: eu usaria XChaCha20-Poly1305 ou AES-256-GCM. Se você quer simplicidade, segurança e nonce grande, XChaCha20-Poly1305 é bom. Se quiser mais padrão corporativo e aceleração via hardware, AES-256-GCM é mais aceito. Em ambos, usar AEAD, porque você precisa de criptografia autenticada: confidencialidade + integridade do payload.
O AAD é essencial. Ele prende criptograficamente o payload aos metadados. Exemplo:
AAD =
tenant_id
agent_id
stream_id
event_id
event_type
event_version
created_at
previous_event_hash
Assim, se alguém copiar o ciphertext de um evento para outro stream, ou tentar trocar metadados, a decriptação falha.
A cadeia de integridade deve ser feita sobre o evento cifrado, não só sobre o payload aberto:
event_hash = HASH(
event_id
stream_id
event_type
metadata_plain
crypto_metadata
payload_ciphertext
previous_event_hash
)
Isso mantém o Event Store auditável sem precisar abrir o conteúdo. Event stores/event sourcing usam naturalmente um log append-only, e a Azure descreve o padrão Event Sourcing como uma sequência de eventos registrada em append-only store. Para auditoria criptográfica, a ideia de cadeia de hashes em event store também já aparece em implementações como EventSourcingDB, onde cada evento contém hash do predecessor para formar uma cadeia verificável.
A política do Linus Salamander precisa receber um pedido assim:
{
"operation": "decrypt",
"purpose": "replay_for_agent",
"tenant_id": "itarare",
"agent_id": "LawyerWhatsAppAgent",
"stream_id": "lawyer:123:contact:456",
"event_id": "evt_01...",
"key_id": "lsm:kek:tenant:itarare:agent:lawyer-whatsapp:v3",
"requested_by": "LawyerDomainAgent",
"session_proof": "DPoP/mTLS/passkey-bound-proof"
}
E o Linus Salamander só libera se a política bater:
- agent autorizado;
- tenant correto;
- chave ativa;
- sessão válida;
- purpose permitido;
- escopo permitido;
- limite de tempo válido;
- auditoria registrada;
- não houve revogação.
O ponto mais forte da sua arquitetura é: o Event Store local pode ser comprometido e mesmo assim o atacante só terá ciphertext + metadados mínimos + hashes. Sem Linus Salamander, ele não reconstrói o conteúdo.
Para LGPD e direito ao esquecimento, Event Sourcing é delicado porque eventos são append-only. A solução prática é crypto-shredding: você mantém o evento por auditoria, mas destrói/revoga a chave que permite abrir o dado pessoal. Assim o log continua íntegro, mas o conteúdo sensível fica irrecuperável. Para arquiteturas event-driven, retenção e privacidade precisam ser pensadas explicitamente, porque event stores preservam histórico e não funcionam como CRUD comum.
Então a regra seria:
Nunca apagar evento por padrão.
Apagar ou revogar a chave quando o dado pessoal precisar deixar de ser acessível.
Adicionar evento de redaction/tombstone.
Manter hash, metadados mínimos e prova de que existiu.
Exemplo:
ClientDocumentUploaded
ClientDocumentExtracted
ClientRequestedDeletion
ClientDataCryptoShredded
Depois do ClientDataCryptoShredded, o replay sabe que aquele dado foi destruído criptograficamente e não tenta reconstruir o payload.
A fronteira final:
Event Store:
- guarda eventos cifrados
- guarda hash chain
- guarda metadados mínimos
- guarda encrypted_dek
- não guarda chave aberta
Linus Salamander:
- guarda/deriva KEKs
- controla unwrap de DEKs
- audita decrypt
- aplica policies
- rotaciona chaves
- revoga acesso
CryptoSidecar:
- cifra/decifra localmente
- usa chaves temporárias
- apaga segredo da memória
EventSourcingSidecar:
- append-only
- replay
- snapshot
- hash chain
- projeções
Minha recomendação de design para o seu padrão:
Agent-local Encrypted Event Sourcing
com Linus Salamander como Enclave Vault
Ou em fórmula:
EncryptedEvent(E) :=
AEAD_Encrypt(
DEK_event,
payload(E),
AAD = agent_id ⊕ stream_id ⊕ event_id ⊕ event_type ⊕ previous_event_hash
)
StoredEvent(E) :=
metadata_plain(E)
⊕ encrypted_dek(DEK_event, KEK_agent)
⊕ ciphertext(E)
⊕ hash_chain(E)
⊕ signature(E)
E o contrato arquitetural:
Nenhum Agent grava evento em claro.
Nenhum Event Store possui chave de decriptação.
Nenhuma chave de evento vive além da operação.
Nenhum replay sensível ocorre sem autorização do Linus Salamander.
Nenhum payload é confiável sem validação AEAD + hash chain.
Isso encaixa perfeitamente com seu modelo d
e Agent + CryptoSidecar + Local
EventSourceSidecar + Linus Salamander
Enclave Vault.
Quando o agente reinicia, ele não deve guardar chave em disco para continuar. Ele deve passar por um fluxo de re-hydration autorizada: lê os últimos eventos cifrados, prova sua identidade para o Linus Salamander, recebe autorização temporária para abrir só o necessário, reconstrói o estado e apaga as chaves da memória.
O fluxo correto é este:
Agent reinicia
Supervisor sobe primeiro
Agent prova identidade
CryptoSidecar abre sessão com Linus Salamander
EventSourcingSidecar lê últimos eventos cifrados
CryptoSidecar pede unwrap das chaves necessárias
Eventos são descriptografados em memória
Estado local/projeção é reconstruído
Chaves temporárias são descartadas
Agent volta a operar
A regra central:
O agente pode recuperar estado.
Mas ele nunca “possui” a chave permanentemente.
Na prática, você precisa de três coisas: snapshot cifrado, replay incremental e sessão curta de decrypt.
O ideal é não descriptografar todos os eventos desde o começo sempre que reiniciar. O Agent mantém snapshots locais cifrados.
Exemplo:
event-001
event-002
event-003
...
event-100
snapshot-100
event-101
event-102
event-103
Quando reinicia, ele faz:
1. Abre o snapshot-100.
2. Reaplica apenas event-101, event-102, event-103.
3. Reconstrói o estado atual.
Isso evita custo alto, reduz decrypt e melhora performance.
A estrutura ficaria assim:
LocalQBEQStore
├── events/
│ ├── event-001.enc
│ ├── event-002.enc
│ └── event-103.enc
├── snapshots/
│ └── snapshot-100.enc
└── projections/
└── current_state.enc
Mas atenção: snapshot também é dado sensível, então ele também precisa estar cifrado.
A sequência de boot do Agent deveria ser formalizada:
AgentBootIntent
Supervisor validates process identity
CryptoSidecar requests session from Linus Salamander
Linus Salamander validates:
- agent_id
- tenant_id
- machine identity
- mTLS certificate
- DPoP proof
- signed boot nonce
- policy
- key version
Linus Salamander grants short-lived decrypt session
EventSourcingSidecar rehydrates state
Agent enters READY state
Eu modelaria isso como um evento também:
AgentRestarted
AgentRehydrationRequested
AgentRehydrationAuthorized
AgentStateRebuilt
AgentReady
Mas esses eventos de boot não devem expor segredo. Eles só registram auditoria.
O pedido para o Linus Salamander seria algo assim:
{
"operation": "rehydrate",
"tenant_id": "itarare",
"agent_id": "LawyerWhatsAppAgent:lawyer_123",
"stream_scope": [
"lawyer_123:appointments",
"lawyer_123:contacts",
"lawyer_123:documents"
],
"from_snapshot": "snapshot_100",
"events_after": 100,
"purpose": "agent_restart_recovery",
"session_ttl_seconds": 60,
"proof": {
"mtls": "...",
"dpop": "...",
"boot_nonce_signature": "..."
}
}
O Linus Salamander não deve devolver “a chave mestra”. Ele deve devolver uma dessas opções:
Opção A: DEKs temporariamente unwrapped para aquele escopo.
Opção B: capability token que permite unwrap por evento durante 60 segundos.
Opção C: decrypt streaming dentro do enclave, retornando plaintext só ao CryptoSidecar autorizado.
Para o seu caso, eu escolheria a Opção B:
Linus Salamander emite uma Rehydration Capability curta.
Exemplo:
{
"capability_id": "rehydrate_cap_01",
"agent_id": "LawyerWhatsAppAgent:lawyer_123",
"allowed_operation": "decrypt",
"allowed_streams": [
"lawyer_123:appointments",
"lawyer_123:contacts"
],
"max_events": 200,
"expires_in_seconds": 60,
"single_use": true
}
Aí o CryptoSidecar usa essa capability para abrir as chaves dos últimos eventos. Depois expira.
O contrato de segurança fica:
- capability curta;
- escopo mínimo;
- uso único ou limitado;
- máximo de eventos;
- purpose obrigatório;
- auditoria obrigatória;
- sem chave persistida;
- sem decrypt fora do CryptoSidecar.
Para evitar pedir chave evento por evento e ficar lento, você pode usar janelas de rehydration.
Exemplo:
snapshot key → abre snapshot atual
stream key window → abre eventos dos últimos 15 minutos ou últimos N eventos
document keys → só abre documentos se realmente precisar
Então o agente não precisa abrir tudo:
Atendente jurídico:
- precisa abrir agenda recente;
- precisa abrir contatos pendentes;
- não precisa abrir todos os documentos antigos;
- não precisa abrir conversas encerradas de meses atrás.
Isso é importante. O Agent só deve descriptografar o working set, não o histórico inteiro.
Eu dividiria o estado em três camadas:
1. Hot State
Estado necessário para continuar funcionando agora.
Ex.: contatos pendentes, agenda do dia, últimos atendimentos.
2. Warm State
Estado acessado sob demanda.
Ex.: histórico dos últimos 90 dias.
3. Cold State
Arquivo/auditoria.
Ex.: documentos antigos, eventos encerrados, atendimentos arquivados.
No boot, só abre o Hot State.
AgentRestart
Decrypt Hot Snapshot
Replay Hot Events
Agent Ready
O restante só abre quando necessário.
A fórmula:
Rehydrate(Agent) :=
Decrypt(snapshot_hot)
⊕ Replay(events_after_snapshot)
⊕ LazyDecrypt(warm/cold streams only when requested)
Snapshots também devem ter hash chain:
{
"snapshot_id": "snapshot_100",
"agent_id": "LawyerWhatsAppAgent:lawyer_123",
"covers_until_event": 100,
"state_ciphertext": "...",
"encrypted_dek": "...",
"state_hash": "...",
"last_event_hash": "...",
"created_at": "..."
}
Na hora de restaurar:
1. Verifica hash do snapshot.
2. Verifica se snapshot aponta para o último event_hash conhecido.
3. Descriptografa snapshot.
4. Reaplica eventos posteriores.
5. Confere se a projeção final bate.
Se algo falhar:
- snapshot corrompido;
- hash chain inválido;
- key version revogada;
- evento faltando;
- decrypt negado;
O Agent não deve subir em modo normal. Ele sobe em modo degradado:
AgentState = DEGRADED_RECOVERY
E dispara:
AgentRehydrationFailed
Aí o Supervisor decide:
- tentar snapshot anterior;
- pedir reautorização;
- sincronizar com backup remoto;
- chamar humano;
- isolar Agent.
Para seu padrão, eu criaria estes estados canônicos:
BOOTING
AUTHENTICATING
REHYDRATING
REPLAYING
READY
DEGRADED
SEALED
FAILED
SEALED é importante. Significa:
O Agent está vivo, mas não recebeu autorização para descriptografar seu estado.
Nesse modo ele pode responder:
O atendimento está temporariamente indisponível para recuperação segura. Tente novamente em instantes ou aguarde retorno humano.
Mas ele não inventa estado.
Para o Event Store local, o processo fica assim:
onAgentStart():
supervisor.start()
identity = supervisor.proveAgentIdentity()
cap = linusSalamander.requestRehydrationCapability(identity, scope)
snapshot = localStore.loadLatestHotSnapshot(agent_id)
plaintextState = cryptoSidecar.decrypt(snapshot, cap)
events = localStore.loadEventsAfter(snapshot.last_event_id)
for event in events:
assert verifyHashChain(event)
plaintextEvent = cryptoSidecar.decrypt(event, cap)
plaintextState = apply(plaintextState, plaintextEvent)
cryptoSidecar.zeroize()
agent.setState(plaintextState)
agent.ready()
Em pseudocódigo mais próximo da arquitetura:
async function rehydrateAgent(agentId: AgentId) {
const identityProof = await Supervisor.proveIdentity(agentId)
const capability = await LinusSalamander.requestCapability({
operation: "rehydrate",
agentId,
purpose: "agent_restart_recovery",
ttlSeconds: 60,
maxEvents: 200,
proof: identityProof
})
const snapshot = await LocalQBEQStore.loadLatestSnapshot(agentId, {
scope: "hot"
})
let state = await CryptoSidecar.decryptSnapshot(snapshot, capability)
const events = await LocalQBEQStore.loadEventsAfter({
agentId,
afterEventId: snapshot.lastEventId
})
for (const encryptedEvent of events) {
EventSourcingSidecar.verifyHashChain(encryptedEvent)
const event = await CryptoSidecar.decryptEvent(
encryptedEvent,
capability
)
state = EventSourcingSidecar.apply(state, event)
}
await CryptoSidecar.zeroizeSession(capability)
return state
}
A parte mais importante: state em claro deve existir só em memória e, mesmo assim, com cuidado. Se possível:
- memória travada;
- tempo curto;
- sem swap;
- zeroize após uso;
- processo isolado;
- permissões mínimas;
- logs sem payload;
- crash dump desativado para dados sensíveis.
O erro que você deve evitar é este:
Agent reiniciou
lê uma chave local salva
descriptografa tudo sozinho
Isso quebra o conceito do Linus Salamander como Enclave Vault.
O certo:
Agent reiniciou
prova identidade
recebe autorização temporária
abre só o necessário
continua
A decisão de design que eu adotaria:
O Agent não recupera chaves.
O Agent recupera capacidades temporárias de decriptação.
Isso deixa a arquitetura mais forte.
Nome do fluxo:
Secure Agent Rehydration
Ou no seu padrão:
Linus Salamander Rehydration Protocol
Contrato final:
AgentRestart(E) :=
Proof(agent_identity)
⊕ Capability(LinusSalamander, scope, ttl, purpose)
⊕ Decrypt(hot_snapshot)
⊕ Replay(encrypted_events_after_snapshot)
⊕ Zeroize(session_keys)
⊕ AgentReady
E a regra canônica:
Um Agent só pode continuar após reinício se conseguir reconstruir seu estado por replay autorizado, usando capability temporária emitida pelo Linus Salamander, sem persistir chaves locais e sem descriptografar mais dados do que o necessário.
Sim, você está certo. A versão correta do seu stack não é “mTLS clássico + DPoP opcional”. No seu padrão, o mTLS precisa ser híbrido/pós-quântico, e ML-KEM + DPoP entram como comportamentos LinearAutoDestroy.
A correção fica assim:
Transporte Agent↔Agent:
- TLS 1.3
- mTLS com certificado curto por Agent
- Hybrid Key Exchange: X25519 + ML-KEM
- DPoP obrigatório por request/intenção
- ML-KEM LinearAutoDestroy
- DPoP LinearAutoDestroy
O ML-KEM é o padrão NIST FIPS 203 para key encapsulation pós-quântica, com parâmetros ML-KEM-512, ML-KEM-768 e ML-KEM-1024. Para o seu runtime, eu usaria ML-KEM-768 como padrão seguro/equilibrado.
O DPoP é definido no RFC 9449 como mecanismo de proof-of-possession no nível da aplicação: o cliente prova posse de uma chave privada por request usando um JWT no header DPoP, reduzindo replay de tokens.
A nova tabela canônica fica:
Camada Algoritmo Uso
Transporte TLS 1.3 Canal seguro base
Autenticação mútua mTLS X.509 curto Agent A e Agent B se autenticam
Key exchange clássico X25519 Segredo clássico efêmero
Key exchange pós-quântico ML-KEM-768 Segredo resistente a quantum
Combiner HKDF-SHA256 Combina X25519 + ML-KEM
Canal TLS AES-256-GCM ou ChaCha20-Poly1305 Cifra do transporte
Proof-of-possession DPoP Ed25519 ou ES256 Prova por request/intenção
Assinatura Agent Ed25519 ou ML-DSA futuramente Autoria
Payload XChaCha20-Poly1305 Cifra da mensagem
Event Store Envelope Encryption DEK por evento/stream
Vault Linus Salamander KEK, policy, certificados, capabilities
A fórmula do handshake fica:
AgentMTLSHandshake(A, B) :=
TLS1.3
⊕ mTLS(A_cert, B_cert)
⊕ X25519_ECDHE(A, B)
⊕ ML-KEM-768(A, B)
⊕ HKDF-SHA256(X25519_secret ∥ MLKEM_secret)
⊕ LinearAutoDestroy(X25519_secret)
⊕ LinearAutoDestroy(MLKEM_secret)
⊕ ChannelReady
O ponto importante: X25519 continua, mas não sozinho. Ele entra em modo híbrido com ML-KEM.
Por quê?
X25519 protege contra atacantes clássicos atuais.
ML-KEM protege contra harvest-now-decrypt-later.
HKDF combina os dois segredos.
Se um dos dois continuar seguro, a sessão continua protegida.
O IETF tem trabalho específico para construção de hybrid key exchange em TLS 1.3, motivado pela transição para criptografia pós-quântica.
Agora, sobre o LinearAutoDestroy: no seu modelo, ML-KEM e DPoP não são só algoritmos. Eles viram Semantic Behavior Types obrigatórios.
ML-KEM como LinearAutoDestroy
O segredo gerado pelo ML-KEM não pode sobreviver depois da derivação da chave de sessão.
MLKEMSharedSecret : LinearAutoDestroy<Secret>
Uso:
1. Agent B possui ML-KEM public key.
2. Agent A encapsula segredo para Agent B.
3. Agent A obtém:
- ciphertext_kem
- shared_secret_mlkem
4. Agent B decapsula ciphertext_kem.
5. Ambos têm shared_secret_mlkem.
6. HKDF combina shared_secret_mlkem + X25519_secret.
7. shared_secret_mlkem é destruído imediatamente.
Contrato:
ML-KEM secret só pode ser usado uma vez:
- input para HKDF;
- nunca logar;
- nunca persistir;
- nunca serializar;
- nunca reutilizar;
- destruir após derivação.
Em pseudo-tipo:
type MLKEMSharedSecret =
LinearAutoDestroy<SecretBytes, "ml-kem-shared-secret">
type HybridSessionKey =
DerivedKey<"tls-agent-session">
Uso:
const mlkemSecret = MLKEM.decapsulate(ciphertext, privateKey)
// mlkemSecret: LinearAutoDestroy
const x25519Secret = X25519.derive(ephemeralPrivate, peerPublic)
// x25519Secret: LinearAutoDestroy
const sessionKey = HKDF_SHA256.derive({
ikm: concat(
mlkemSecret.readOnce(),
x25519Secret.readOnce()
),
salt: handshakeTranscriptHash,
info: "allascode-agent-mtls-hybrid-v1"
})
mlkemSecret.destroy()
x25519Secret.destroy()
A regra semântica:
Depois de HKDF, o segredo ML-KEM deixa de existir.
DPoP como LinearAutoDestroy
O DPoP também deve ser linear, porque cada prova deve valer para uma request/intenção, não para várias.
DPoPProof : LinearAutoDestroy<JWTProof>
Fluxo:
1. Agent A vai chamar Agent B.
2. Agent A cria DPoP proof para:
- método;
- URL/canal;
- intent;
- nonce;
- timestamp;
- body_hash;
- message_id.
3. Agent A assina a prova.
4. Agent B valida.
5. Agent B registra jti como usado.
6. A prova é destruída.
7. Reuso do mesmo jti = replay bloqueado.
Payload DPoP:
{
"typ": "dpop+jwt",
"alg": "EdDSA",
"jwk": "public-key-or-thumbprint",
"htm": "POST",
"htu": "agent://itarare/lawyer-123/LawyerAgendaAgent",
"iat": 1782677105,
"jti": "proof_01HX...",
"ath": "access-token-hash",
"intent": "AppointmentRequested",
"body_hash": "sha256..."
}
Contrato:
DPoP proof só pode ser consumido uma vez:
- TTL curto;
- jti único;
- preso ao destino;
- preso ao método;
- preso ao intent;
- preso ao hash do payload;
- destruído após validação.
Em pseudo-tipo:
type DPoPProof =
LinearAutoDestroy<SignedJWT, "dpop-proof">
function createDPoP(intent, payloadHash): DPoPProof {
return signOnce({
htm: "POST",
htu: intent.targetAgentUri,
intent: intent.name,
body_hash: payloadHash,
jti: randomId(),
iat: now()
})
}
mTLS final com ML-KEM + DPoP
O fluxo completo Agent A → Agent B:
1. Agent A pede certificado curto ao Linus Salamander.
2. Agent B também tem certificado curto.
3. Agent A abre conexão TLS 1.3 mTLS com Agent B.
4. Handshake usa X25519 + ML-KEM-768.
5. HKDF deriva chave de sessão híbrida.
6. X25519_secret é destruído.
7. MLKEM_secret é destruído.
8. Agent A monta payload.
9. Payload é cifrado com XChaCha20-Poly1305.
10. Agent A cria DPoP proof linear para aquela intenção.
11. Agent A assina envelope.
12. Agent B valida mTLS, policy, DPoP, assinatura e AEAD.
13. Agent B registra jti usado.
14. Agent B grava evento cifrado no Event Store.
15. DPoP proof é destruído.
Fórmula canônica:
SecureAgentCall(A → B) :=
HybridMTLS(
TLS1.3,
mTLS(A_cert, B_cert),
X25519,
ML-KEM-768::LinearAutoDestroy
)
⊕ PolicyCheck(A, B, Intent)
⊕ PayloadEncrypt(XChaCha20-Poly1305)
⊕ DPoP(Intent, PayloadHash)::LinearAutoDestroy
⊕ Signature(Ed25519)
⊕ EventStoreAppend(EncryptedEvent)
Agora, o papel do Linus Salamander:
Linus Salamander:
- emite certificado curto por Agent;
- publica trust bundle;
- guarda policy Agent↔Agent;
- registra chave pública DPoP do Agent;
- emite capability curta;
- valida rotação;
- revoga agentes;
- protege KEKs do Event Store;
- audita decrypt, sign, cert issue e replay.
Eu definiria três tipos de identidade por Agent:
1. mTLS Identity
Usada para canal.
2. DPoP Identity
Usada para provar posse por request/intenção.
3. Event Signing Identity
Usada para assinar eventos persistidos.
Elas podem derivar de uma raiz do Agent, mas eu prefiro separar:
agent_mtls_key
agent_dpop_key
agent_event_signing_key
Porque o comprometimento de uma não quebra tudo.
Stack corrigida
Transporte:
- TLS 1.3
- mTLS com certificados X.509 curtos por Agent
- Hybrid KEX: X25519 + ML-KEM-768
- HKDF-SHA256 como combiner
- AES-256-GCM ou ChaCha20-Poly1305 no canal TLS
LinearAutoDestroy no transporte:
- X25519 ephemeral secret
- ML-KEM shared secret
- Hybrid handshake secret
- Session resumption secret, se existir
Payload Agent↔Agent:
- XChaCha20-Poly1305 para cifrar mensagens
- Ed25519 para assinar envelope
- X25519 opcional para payload E2E quando passar por broker
- HKDF-SHA256 para derivar message keys
- BLAKE3 ou SHA-256 para hash de payload/evento
Event Store:
- XChaCha20-Poly1305 ou AES-256-GCM
- Envelope Encryption
- DEK por evento/stream
- KEK protegida pelo Linus Salamander
- Hash chain com SHA-256 ou BLAKE3
Proof-of-possession:
- DPoP obrigatório
- Assinatura Ed25519 ou ES256
- DPoPProof como LinearAutoDestroy
- jti single-use
- TTL curto
- body_hash obrigatório
- intent obrigatório
A frase correta da arquitetura:
Entre agentes, o canal é protegido por TLS 1.3 mTLS híbrido com X25519 + ML-KEM-768, ambos efêmeros, e toda chamada carrega uma prova DPoP LinearAutoDestroy vinculada ao intent, ao destino e ao hash do payload.
E o contrato mais forte:
Nenhum Agent fala sem mTLS.
Nenhum mTLS é apenas clássico.
Nenhum ML-KEM secret sobrevive ao HKDF.
Nenhum DPoP pode ser reutilizado.
Nenhum payload sensível depende só do transporte.
Nenhum evento entra no Event Store sem assinatura, hash chain e criptografia.
Essa é a versão alinhada com o que vocês já tinham definido: ML-KEM + DPoP como QuarkBehavior/LinearAutoDestroy dentro do mTLS Agent↔Agent.
Minha próxima pergunta para aceitar a arquitetura é: **como você representaria formalmente a diferença entre “cura preservou a intenção” e “cura mudou a intenção, mas continuou passando nas invariantes”?**
Porque a sua resposta resolve uma parte grande do problema, mas ainda falta esse corte semântico.
O que você descreveu está tecnicamente forte:
```md
Intent formalizado
→ invariants
→ validação Haskell
→ validação Prolog
→ simulação até persistência
→ análise de efeitos colaterais
→ Human-in-the-Healing-Loop para cura pesada
→ reversibilidade obrigatória
→ auditoria obrigatória
```
Isso já separa seu harness de um self-healing ingênuo. Trabalhos recentes sobre harness de agentes estão indo justamente na direção de tratar o harness como camada explícita de execução, ambiente, ferramentas, contexto, lifecycle, observabilidade, verificação e governança. O HarnessFix, publicado em junho de 2026, por exemplo, propõe diagnosticar falhas em trajetórias de agentes e atribuir o problema a camadas específicas do harness antes de gerar reparos. ([arXiv][1]) Outro trabalho recente, Life-Harness, defende adaptar a interface/runtime harness em vez do modelo, especialmente em ambientes determinísticos e governados por regras. ([arXiv][2])
A sua diferença possível é que você não está falando só de “melhorar o harness”. Você está propondo uma cadeia formal:
```md
Intent → Invariant → Simulation → SideEffectGraph → Human Gate → Reversible Patch → Audit Trail
```
Isso aproxima sua proposta de runtime verification e formal methods aplicados a agentes. Há trabalhos recentes defendendo que agentes precisam obedecer invariantes formais e que a verificação deve aparecer no runtime ou no post-hoc trace analysis, não só no teste final. ([Preprints][3])
Mas aqui está a crítica principal: **reversível e auditável ainda não é suficiente**.
Exemplo simples:
```md
Intent original:
"Cancelar pedido se o pagamento não foi confirmado."
Self-healing errado:
"Cancelar pedido porque a consulta ao PaymentAgent falhou."
O sistema pode:
- passar no schema
- passar no Prolog
- persistir corretamente
- ser auditável
- ser reversível
Mas ainda assim corrompeu a intenção.
```
Então falta uma peça: **Intent Preservation Proof**, ou algo equivalente.
Eu modelaria assim:
```md
IntentPreservationCheck:
before_intent_hash
after_healing_intent_hash
invariant_result
domain_policy_result
side_effect_diff
semantic_diff
persistence_projection_diff
human_required
reversible
compensable
audit_trail_id
```
E adicionaria uma regra dura:
```md
Uma cura só pode ser automática se:
1. preserva o intent original;
2. não altera política de domínio;
3. não introduz novo efeito externo;
4. é replayável;
5. é reversível ou compensável;
6. passa nas invariantes;
7. reduz reincidência esperada;
8. não aumenta risco sistêmico.
```
Outro ponto: **nem tudo que é reversível é realmente reversível**.
No banco de dados, você pode reverter. Em Event Sourcing, você pode compensar. Mas no mundo externo nem sempre dá:
```md
- Pix enviado
- mensagem enviada no WhatsApp
- pedido despachado
- e-mail enviado
- notificação jurídica enviada
- produto reservado para outro usuário
- decisão clínica sugerida
```
Nesses casos, eu não chamaria de reversibilidade. Chamaria de **compensabilidade**.
Então eu separaria:
```md
Reversible:
Pode voltar ao estado anterior sem consequência externa relevante.
Compensable:
Não pode voltar exatamente, mas pode emitir evento corretivo, ajuste, reembolso, aviso ou restauração parcial.
Irreversible:
Não pode ser curado automaticamente. Exige Human-in-the-Healing-Loop antes da ação.
```
Isso deixa sua arquitetura bem mais blindada.
O uso de Haskell e Prolog faz sentido se você dividir responsabilidades:
```md
Haskell:
- tipos
- invariantes puras
- validadores determinísticos
- estado esperado
- transições permitidas
- simulação funcional
- propriedades testáveis
Prolog:
- regras de domínio
- autorização
- compliance
- exceções
- relações semânticas
- conflito entre intents
- inferência sobre efeitos colaterais
```
Exemplo conceitual:
```haskell
data HealingDecision
= AutoHeal
| RequireHumanValidation
| BlockHealing
canAutoHeal :: Intent -> Invariants -> SideEffectGraph -> HealingPatch -> HealingDecision
```
E em Prolog:
```prolog
requires_human_validation(Intent, Patch) :-
changes_money(Patch);
changes_identity(Patch);
changes_permission(Patch);
touches_external_side_effect(Patch);
semantic_diff_high(Intent, Patch).
block_healing(Patch) :-
removes_validation(Patch);
bypasses_auth(Patch);
mutates_audit_log(Patch);
rewrites_immutable_event(Patch).
```
Agora, sobre “cura pesada”: eu definiria formalmente, não por sensação.
```md
Cura leve:
- retry
- fallback de canal
- reordenação idempotente
- correção de formato semântico inequívoco
- reconstrução de read model
- revalidação de cache
- recuperação de snapshot
Cura média:
- escolha entre duas interpretações possíveis
- alteração de workflow interno
- reprocessamento de evento
- correção de projeção
- recomposição de estado
Cura pesada:
- altera dinheiro
- altera estoque
- altera identidade
- altera permissão
- altera contrato
- altera entrega
- altera decisão jurídica, clínica, financeira ou reputacional
- gera efeito externo irreversível
```
A regra ficaria:
```md
Leve → pode ser automática se passar nas invariantes.
Média → pode ser automática somente se houver replay histórico semelhante aprovado.
Pesada → sempre Human-in-the-Healing-Loop antes da aplicação.
```
A sua ideia começa a ganhar um nome técnico mais preciso:
```md
Formally-Governed Predictive Self-Healing Harness
```
Ou no vocabulário do seu ecossistema:
```md
Anti-Fragile Intent-Preserving Healing Harness
```
Eu não usaria apenas “Self-Healing Harness”, porque isso é genérico demais. O núcleo original está em **Intent-Preserving**.
A peça que falta é esta:
```md
Todo Intent precisa ter:
1. Intent Schema
O que o usuário/domínio quer realizar.
2. Intent Invariants
O que nunca pode ser violado.
3. Intent Preconditions
O que precisa ser verdade antes da execução.
4. Intent Postconditions
O que precisa ser verdade depois.
5. Intent Side Effects
Quais efeitos podem acontecer.
6. Intent Compensation Policy
Como desfazer ou compensar efeitos.
7. Intent Human Gate Policy
Quando exige validação humana.
8. Intent Healing Boundary
O que pode e o que nunca pode ser curado automaticamente.
9. Intent Replay Contract
Como reproduzir deterministicamente o erro.
10. Intent Preservation Check
Como provar que a cura não mudou a intenção.
```
Se era isso que você ia escrever em “Todo intent tem q ss”, então eu trocaria por:
```md
Todo Intent tem que ter uma Semantic Safety Specification.
```
Ou mais forte:
```md
Todo Intent tem que ter uma Intent Safety Specification.
```
Nome curto:
```md
.intent.safety
```
Exemplo:
```yaml
intent: Order.cancelWhenPaymentExpired
preconditions:
- order.exists
- payment.status in ["pending", "expired"]
- delivery.not_dispatched
invariants:
- cannot_cancel_paid_order
- cannot_cancel_dispatched_order
- cannot_mutate_payment_history
- cannot_delete_audit_events
allowed_healing:
- retry_payment_lookup
- rebuild_payment_projection
- request_payment_status_from_gateway
- mark_as_pending_validation
human_required:
- payment_status_conflict
- delivery_already_started
- refund_required
- user_disputes_cancellation
forbidden_healing:
- bypass_payment_validation
- rewrite_payment_event
- delete_order_event
- force_cancel_without_payment_proof
side_effects:
reversible:
- update_read_model
- rebuild_projection
compensable:
- notify_customer
- reserve_stock
irreversible:
- refund_pix
- dispatch_delivery
```
Meu parecer atualizado: **agora sua proposta tem um núcleo realmente forte**, porque você amarrou self-healing com formalização de intent, invariantes, simulação, side effects, auditoria e HITL. A parte que ainda precisa ficar rigorosa é a diferença entre:
```md
cura que preserva a intenção
vs
cura que apenas satisfaz invariantes locais
```
A próxima correção que eu pediria de você é esta:
**como você desenharia o `IntentPreservationCheck`? Ele compara só invariantes, ou também compara semanticamente o antes/depois da intenção, os efeitos colaterais e a projeção final persistida?**
[1]: https://arxiv.org/abs/2606.06324?utm_source=chatgpt.com "From Failed Trajectories to Reliable LLM Agents: Diagnosing and Repairing Harness Flaws"
[2]: https://arxiv.org/abs/2605.22166?utm_source=chatgpt.com "Adapting the Interface, Not the Model: Runtime Harness Adaptation for Deterministic LLM Agents"
[3]: https://www.preprints.org/manuscript/202604.1029?utm_source=chatgpt.com "AgentVerify: Compositional Formal Verification of AI Agent ..."

Sim. A forma correta é comparar até o estado terminal, não só no ponto onde o erro apareceu.

Eu chamaria isso de:

Counterfactual Saga Simulation

Ou no seu vocabulário:

Intent-Preserving Reverse SAGA Harness

Mas a ideia precisa de uma correção técnica: não é exatamente uma SAGA inversa tradicional. A SAGA clássica executa passos e, se algo falha, roda compensações. O que você está propondo é mais forte: pegar uma falha, gerar uma hipótese de cura, executar essa hipótese em um universo paralelo determinístico e comparar se o fim preserva a intenção original.

A estrutura fica assim:

Intent original
→ execução real falhou
→ harness congela o contexto
→ cria branch de simulação
→ aplica healing candidate
→ executa até a persistência final
→ calcula diff semântico
→ calcula diff de efeitos colaterais
→ calcula diff de projeções
→ decide: aplicar, pedir humano ou bloquear

O ponto principal: a simulação não compara apenas “antes/depois do erro”. Ela compara “intenção esperada vs. realidade final produzida pela cura”.

Então todo Intent precisa ter um contrato de finalização:

IntentTerminalContract

Ou:

Final Projection Contract

Esse contrato diz: “quando esse Intent terminar corretamente, quais estados, eventos, projeções e efeitos são aceitáveis?”

Exemplo:

intent: Order.cancelWhenPaymentExpired

terminal_contract:
  order.status: cancelled
  payment.status: expired
  delivery.status: not_started
  stock.reservation: released
  customer.notification: scheduled_or_sent
  audit.event: required

forbidden_terminal_states:
  - order.status: cancelled AND payment.status: paid
  - order.status: cancelled AND delivery.status: dispatched
  - payment.history: mutated
  - audit.event: missing

A simulação roda contra esse contrato.

A arquitetura mínima seria:

1. Error Capsule
Captura o erro real.

2. Intent Contract
Define o que deveria acontecer.

3. Shadow Event Store
Reexecuta tudo sem tocar no mundo real.

4. Effect Sandbox
Simula side effects sem enviá-los.

5. Projection Runner
Gera as projeções finais simuladas.

6. Semantic Diff Engine
Compara intenção, estado, eventos, side effects e correlações.

7. Healing Decision
Decide auto-heal, human-in-the-loop ou block.

O Error Capsule precisa guardar:

error_capsule:
  error_id: err_123
  intent_id: Order.cancelWhenPaymentExpired
  agent_id: OrderDomainAgent
  entity_id: order_991
  input_payload_hash: abc
  state_before_hash: def
  failed_step: PaymentDataAgent.lookup
  failed_contract: payment_status_required
  observed_error: timeout
  event_stream_position: 1042
  idempotency_key: idem_789
  trace_id: trace_555

Com isso, o harness consegue reconstruir o mundo no ponto exato da falha.

A simulação acontece assim:

EventStore real até posição N
        ↓
Shadow Event Store
        ↓
aplica HealingCandidate
        ↓
executa Intent até terminal state
        ↓
gera projeções simuladas
        ↓
compara com TerminalContract

Visualmente:

Real stream:
E1 → E2 → E3 → ERROR

Simulation branch:
E1 → E2 → E3 → HealingCandidate → E4' → E5' → FinalProjection'

Comparison:
FinalProjection' ≟ IntentTerminalContract

O segredo é nunca simular diretamente no banco real. Você cria uma branch temporária:

shadow://trace_555/order_991/healing_candidate_1

Essa branch tem:

- eventos copiados até o erro;
- relógio congelado ou controlado;
- seed determinística;
- gateways externos mockados;
- idempotency keys preservadas;
- side effects gravados como intenção de efeito, não executados.

Para side effects, você não envia WhatsApp, Pix, e-mail ou entrega real durante simulação. Você gera EffectIntent.

Exemplo:

effect_intent:
  type: WhatsApp.sendMessage
  to: customer_123
  template: order_cancelled
  simulated: true
  would_send: true
  irreversible: true

Depois o Prolog decide se esse efeito pode existir naquela cura.

A comparação precisa ter cinco níveis.

Primeiro: comparação estrutural.

O payload continua válido?
O schema continua válido?
Os tipos semânticos continuam válidos?
Os eventos gerados têm forma correta?

Segundo: comparação de invariantes.

Nenhuma regra dura foi violada?
Nenhuma transição proibida aconteceu?
Nenhuma entidade ficou em estado impossível?

Terceiro: comparação de intenção.

A cura ainda realiza o mesmo objetivo?
A ação final corresponde ao Intent original?
A cura não trocou o significado da operação?

Quarto: comparação de efeitos colaterais.

A cura gerou novo efeito externo?
O efeito é reversível, compensável ou irreversível?
O humano precisa aprovar?

Quinto: comparação de projeção final.

O estado persistido final é aceitável?
O read model ficou coerente?
O grafo de correlação mudou de forma aceitável?
A entidade ficou semanticamente mais próxima de outra entidade errada?

Esse quinto ponto conecta diretamente com o que você falou antes sobre correlação semântica. A simulação precisa comparar o grafo antes/depois.

Exemplo:

semantic_correlation_diff:
  entity: Customer_123
  before:
    closest_entities:
      - Customer_123_Profile: 0.94
      - Order_991: 0.89
      - Payment_771: 0.86
  after:
    closest_entities:
      - Customer_888_Profile: 0.91
      - Order_991: 0.82
      - Payment_771: 0.80

decision: block_or_human_review
reason: identity_correlation_drift

Isso é importante porque uma cura pode passar no banco, mas deslocar a identidade semântica da entidade.

A decisão final pode ser uma função assim:

data HealingDecision
  = AutoHeal
  | RequireHumanReview
  | BlockHealing
  deriving (Show, Eq)

decideHealing
  :: Intent
  -> TerminalContract
  -> SimulationResult
  -> SemanticDiff
  -> SideEffectDiff
  -> HealingDecision

E as regras em Prolog:

auto_heal(Intent, Patch) :-
  preserves_intent(Intent, Patch),
  all_invariants_hold(Intent, Patch),
  no_irreversible_side_effect(Patch),
  terminal_contract_satisfied(Intent, Patch),
  semantic_drift_low(Intent, Patch),
  replay_verified(Patch),
  audit_enabled(Patch).

requires_human(Intent, Patch) :-
  has_compensable_side_effect(Patch);
  semantic_drift_medium(Intent, Patch);
  changes_money(Patch);
  changes_stock(Patch);
  changes_delivery(Patch);
  changes_identity(Patch);
  changes_legal_or_medical_state(Patch).

block_healing(Intent, Patch) :-
  violates_invariant(Intent, Patch);
  mutates_audit_log(Patch);
  rewrites_immutable_event(Patch);
  bypasses_auth(Patch);
  semantic_drift_high(Intent, Patch);
  terminal_contract_failed(Intent, Patch).

A simulação em si pode ser implementada como um runner determinístico:

type SimulationInput = {
  intentId: string
  errorCapsuleId: string
  eventStreamPosition: number
  healingCandidate: HealingCandidate
}

type SimulationResult = {
  generatedEvents: DomainEvent[]
  generatedEffectIntents: EffectIntent[]
  finalWriteStateHash: string
  finalReadProjectionHash: string
  terminalContractResult: "pass" | "fail"
  invariantResults: InvariantResult[]
  semanticDiff: SemanticDiff
  sideEffectDiff: SideEffectDiff
  decision: "auto_heal" | "human_review" | "block"
}

O fluxo:

async function simulateHealing(input: SimulationInput): Promise<SimulationResult> {
  const capsule = await loadErrorCapsule(input.errorCapsuleId)

  const shadowStore = await createShadowEventStore({
    fromStream: capsule.entityStream,
    untilPosition: input.eventStreamPosition,
  })

  const sandbox = createEffectSandbox({
    mode: "record_only",
    allowExternalEffects: false,
  })

  await shadowStore.append(input.healingCandidate.asEvent())

  const execution = await runIntentToTerminal({
    intentId: input.intentId,
    store: shadowStore,
    effects: sandbox,
    deterministicClock: capsule.clock,
    seed: capsule.seed,
  })

  const finalProjection = await buildProjection(shadowStore)

  const terminalResult = checkTerminalContract(
    input.intentId,
    finalProjection,
    execution.generatedEvents,
    sandbox.recordedEffects,
  )

  const semanticDiff = compareSemanticState({
    before: capsule.semanticSnapshot,
    after: finalProjection.semanticSnapshot,
  })

  const sideEffectDiff = compareSideEffects({
    allowed: capsule.intent.allowedSideEffects,
    produced: sandbox.recordedEffects,
  })

  const decision = decideHealing({
    terminalResult,
    semanticDiff,
    sideEffectDiff,
    generatedEvents: execution.generatedEvents,
  })

  return {
    generatedEvents: execution.generatedEvents,
    generatedEffectIntents: sandbox.recordedEffects,
    finalWriteStateHash: finalProjection.writeStateHash,
    finalReadProjectionHash: finalProjection.readProjectionHash,
    terminalContractResult: terminalResult.ok ? "pass" : "fail",
    invariantResults: terminalResult.invariants,
    semanticDiff,
    sideEffectDiff,
    decision,
  }
}

Na sua arquitetura, isso viraria uma cadeia de agents:

HealingHarnessAgent
→ ErrorCapsuleAgent
→ IntentContractAgent
→ HaskellInvariantAgent
→ PrologPolicyAgent
→ ShadowEventStoreAgent
→ EffectSandboxAgent
→ ProjectionDiffAgent
→ SemanticCorrelationAgent
→ HumanHealingReviewAgent
→ HealingCommitAgent

O .2flow poderia ficar assim:

IntentHealingSimulation.2flow

on ErrorCaptured(error_capsule)
  -> FreezeContext(error_capsule)
  -> LoadIntentContract(intent_id)
  -> CreateShadowEventStore(entity_stream, position)
  -> GenerateHealingCandidate(error_capsule)
  -> ApplyCandidateToShadowStore(candidate)
  -> RunIntentUntilTerminal(intent_id)
  -> BuildFinalProjection()
  -> CheckHaskellInvariants()
  -> CheckPrologPolicies()
  -> CompareSemanticCorrelation()
  -> CompareSideEffects()
  -> DecideHealing()

DecideHealing()
  -> [AutoHeal, HumanReview, BlockHealing]

AutoHeal
  -> CommitHealingEvent()
  -> MarkPatternAsReusable()
  -> UpdatePredictiveHealingIndex()

HumanReview
  -> RenderSimulationDiff()
  -> AskHumanValidation()
  -> CommitOrReject()

BlockHealing
  -> PersistBlockedHealingCase()
  -> RaiseSafetyAlert()

Agora vem a parte do “predictive self-healing”.

Depois que várias simulações são feitas, você cria uma base de padrões:

healing_pattern:
  pattern_id: hp_payment_timeout_001
  symptom: PaymentDataAgent.timeout
  intent_id: Order.cancelWhenPaymentExpired
  successful_candidates:
    - retry_payment_lookup
    - rebuild_payment_projection
  failed_candidates:
    - force_cancel_order
  auto_heal_allowed: true
  confidence: 0.97
  recurrence_count: 143
  false_positive_count: 2

A partir daí, quando o sistema perceber um estado parecido antes do erro, ele previne:

PaymentDataAgent latency subindo
+ OrderIntent aguardando confirmação
+ PaymentProjection atrasada
+ histórico mostra 91% de chance de timeout

→ Predictive Healing:
   rebuild projection antes da falha
   ou trocar canal de consulta
   ou segurar Intent em pending_validation

Isso é o ponto sintópico/anti-frágil: o erro não apenas é corrigido; ele vira um padrão preventivo.

A definição técnica fica:

Um Predictive Self-Healing Harness é um harness que transforma falhas auditáveis em padrões de prevenção, usando replay determinístico, simulação contrafactual, validação formal e comparação semântica da projeção final.

Relatório em MD:

# Relatório — Counterfactual Saga Simulation para Self-Healing Determinístico

## Ideia central

O self-healing não deve ser aceito apenas porque resolveu o erro local. Ele precisa ser simulado até o estado terminal da intenção original.

A proposta funciona como uma SAGA inversa/contrafactual: o sistema captura a falha, cria uma branch determinística, aplica uma hipótese de cura e executa o fluxo até a persistência final, sem tocar no mundo real.

## Correção conceitual

Não é apenas uma SAGA inversa tradicional.

A SAGA tradicional executa passos e compensa quando há falha.

Aqui, o harness executa uma simulação contrafactual para verificar se a cura preserva a intenção original até o final.

## Componentes necessários

- ErrorCapsule
- IntentTerminalContract
- ShadowEventStore
- EffectSandbox
- HaskellInvariantAgent
- PrologPolicyAgent
- ProjectionDiffAgent
- SemanticCorrelationAgent
- HumanHealingReviewAgent
- HealingCommitAgent

## Regra principal

Uma cura só pode ser automática se:

1. preserva a intenção original;
2. passa nas invariantes;
3. respeita as políticas Prolog;
4. não produz efeito externo irreversível;
5. gera projeção final compatível;
6. mantém correlação semântica aceitável;
7. é auditável;
8. é reversível ou compensável;
9. foi validada em replay determinístico.

## Decisão

- Cura leve: pode ser automática.
- Cura média: automática apenas se houver histórico aprovado.
- Cura pesada: exige Human-in-the-Healing-Loop.
- Cura que viola invariantes, identidade, auditoria ou autorização: bloqueada.

## Nome sugerido

Anti-Fragile Intent-Preserving Healing Harness

ou

Counterfactual Saga Healing Harness

## Próximo ponto a especificar

Definir formalmente o `IntentTerminalContract`, porque ele é o que permite comparar a simulação até o final e decidir se o self-healing preservou ou corrompeu a intenção.

A pergunta que eu faria antes de aceitar “mais uma VPS” é: você quer simular estado inteiro ou só a linha mínima de eventos necessária para validar a cura?

Porque se você clonar banco/read-model inteiro para cada simulação, realmente vai comer armazenamento rápido. Mas talvez você não precise disso. O desenho melhor é uma VPS pequena só para simulação, mas com snapshot mínimo + replay parcial + copy-on-write descartável.

A VPS de simulação não deveria guardar “servidores clonados”. Ela deveria guardar:

1. ErrorCapsule
2. eventos necessários até a falha
3. snapshot mínimo da entidade afetada
4. contratos do Intent
5. resultado da simulação
6. diff final
7. HealingProposal

Ela não precisa guardar:

- banco inteiro clonado
- read model completo
- cache completo
- anexos
- logs brutos longos
- projeções permanentes
- múltiplas cópias completas do mesmo estado

Então eu faria assim:

Production VPS
  → roda sistema oficial
  → grava EventStore real
  → mantém bancos reais

Simulation VPS pequena
  → recebe ErrorCapsule
  → busca eventos mínimos
  → cria branch copy-on-write
  → roda simulação
  → gera diff
  → apaga branch

A regra principal:

Simulação é descartável.
Prova é persistente.

Ou seja, você não guarda a simulação inteira para sempre. Você guarda o resultado auditável dela.

Exemplo:

simulation_retention:
  branch_data: 1h
  shadow_projection: 1h
  generated_events: 24h
  diff_report: permanent
  healing_proposal: permanent
  proof_hash: permanent
  full_trace_if_human_review: 30d

Isso resolve o armazenamento.

O que fica permanente é pequeno:

- hash do snapshot base
- posição do EventStore
- versão do código
- versão do Intent
- versão das invariants
- versão das policies Prolog
- healing candidate aplicado
- decisão
- diff semântico
- diff de efeitos colaterais
- proof_id

A branch em si pode morrer.

Arquitetura ajustada:

Production
  |
  | ErrorCaptured
  v
SimulationQueue
  |
  v
Simulation VPS
  |
  ├── cria EphemeralSimulationBranch
  ├── roda replay mínimo
  ├── aplica HealingCandidate
  ├── executa até terminal state
  ├── compara diff
  ├── emite HealingProposal
  └── destrói branch

O storage da VPS de simulação deveria ser dividido assim:

/sim/base-cache
  cache de snapshots pequenos e reutilizáveis

/sim/branches
  branches temporárias copy-on-write

/sim/results
  resultados compactos das simulações

/sim/proofs
  hashes, diffs e relatórios auditáveis

E cada branch teria limite duro:

simulation_branch_limits:
  max_disk_mb: 100
  max_runtime_seconds: 30
  max_events_replayed: 5000
  max_generated_events: 500
  max_effect_intents: 100
  ttl_minutes: 60

Se passar disso, não é cura automática. Vai para análise humana ou simulação pesada.

Eu separaria dois tipos de simulação:

Fast Healing Simulation
- roda na VPS pequena
- replay curto
- pouca entidade correlata
- decisão automática possível

Heavy Healing Simulation
- roda sob demanda
- mais entidades
- mais correlações
- exige Human-in-the-Healing-Loop
- pode usar máquina maior temporária

Isso evita dimensionar a VPS pequena para o pior caso.

A sua VPS pequena pode ser um Simulation Worker, não um servidor completo. Ela não precisa rodar todos os serviços reais. Ela precisa rodar só os runners:

- HaskellInvariantRunner
- PrologPolicyRunner
- ShadowEventStore local
- ProjectionRunner mínimo
- SemanticDiffRunner
- SideEffectSandbox

Não precisa subir:

- WhatsApp real
- gateway Pix real
- banco inteiro
- frontend
- workers de produção
- todos os read models

O maior ganho vem de uma regra: simular por escopo semântico.

Exemplo: erro em Order.cancelWhenPaymentExpired.

Você não precisa simular a cidade inteira, nem todos os usuários. Precisa do subgrafo:

Order
Payment
StockReservation
Delivery
CustomerNotification
AuditTrail

Então o ErrorCapsule carrega um SimulationScope:

simulation_scope:
  root_entity: Order_991
  include:
    - Payment_771
    - StockReservation_551
    - Delivery_330
    - Customer_123
  exclude:
    - unrelated_orders
    - marketing_events
    - analytics_events
    - old_notifications

Isso reduz armazenamento e processamento.

A parte que você precisa tomar cuidado: se a simulação depende de correlação semântica, talvez precise puxar entidades fortemente correlatas. Mas também com limite:

semantic_scope:
  max_neighbors: 20
  min_correlation: 0.82
  max_depth: 2

Assim não explode.

O design mais correto ficaria:

Production VPS
  ├── EventStore real
  ├── bancos reais
  ├── agents reais
  └── envia ErrorCapsule

Simulation VPS
  ├── SimulationQueue
  ├── SnapshotFetcher
  ├── MinimalReplayBuilder
  ├── CopyOnWriteBranchStore
  ├── HaskellInvariantRunner
  ├── PrologPolicyRunner
  ├── SemanticDiffRunner
  ├── SideEffectFirewall
  ├── HealingProposalEmitter
  └── GarbageCollector

O GarbageCollector é obrigatório. Sem ele, sua previsão está certa: o armazenamento vai embora rápido.

Regra de limpeza:

A cada simulação finalizada:
  - manter diff
  - manter proof
  - manter hashes
  - apagar branch
  - apagar projeção temporária
  - compactar logs

O evento final poderia ser:

healing_simulation_completed:
  simulation_id: sim_123
  base_snapshot_hash: sha_abc
  event_position: 1042
  branch_deleted: true
  proof_persisted: true
  diff_persisted: true
  decision: auto_heal

A frase arquitetural forte:

A VPS de simulação não é um clone persistente da produção; é um forno descartável de hipóteses de cura.

Eu usaria esse nome:

Ephemeral Healing Simulation Worker

Ou no seu padrão:

HealingSimulationWorkerAgent

Com behaviors:

Ephemeral
CopyOnWrite
NoExternalSideEffect
DeterministicReplay
TTLBounded
ProofPersisted
BranchAutoDestroy

Esse último combina bem com seu conceito:

LinearAutoDestroySimulationBranch

A branch de simulação deveria ser obrigatoriamente LinearAutoDestroy: nasceu, rodou, produziu prova, morreu.

Resumo da decisão: sim, faz sentido ter uma VPS pequena separada, mas não para manter clone. Ela deve rodar simulações efêmeras. O armazenamento fica controlado se você persistir apenas provas, diffs e propostas, não o estado inteiro simulado.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment