Arquitetura
Nota sobre a implantação V1. A nomenclatura da lista de entidades abaixo usa prefixos legados "case_*" para várias tabelas; os nomes reais do schema da V1 são:
cases,investigation_runs,investigation_events,investigation_iocs,investigation_assets,investigation_links,investigation_outbox,proposals. O nome da tabelacasespermanece inalterado por compatibilidade retroativa, mas todas as tabelas-filhas por investigação usam o prefixoinvestigation_*. Dentre elas, as tabelas cases / investigation_runs / investigation_events são exercitadas pelo orquestrador atual;proposalseinvestigation_outboxestão presentes no schema, mas o lado do executor que as consome está no roadmap. Leia esta página como a intenção arquitetural; consultesrc/soctalk/core/ir/models.pypara o schema exato.
1. Entidades centrais
Forma mínima. As listas completas de colunas ficam na migration; apenas os campos estruturantes são nomeados aqui.
alerts raw ingest from adapter; AI-triaged
cases investigation unit; one run at a time
case_runs a single AI execution span against a case
case_events ordered event inbox per case (immutable)
proposals AI-proposed actions awaiting human gate
execution_log append-only audit of all meaningful actions
notes markdown / evidence blocks
iocs typed artifacts; carry external_context
case_iocs, case_assets bridge tables
case_links related-case edges (shared IOC / asset / rule)
case_outbox outbound work for executors and exportsToda linha portadora de conteúdo carrega tenant_id, visibility e created_at. O RLS se aplica por tenancy.
2. Modelo de visibilidade
Classes (enum):
mssp_only default; internal reasoning, raw tool output, hypotheses
customer_safe approved for customer view
system lifecycle and state-change events, always visible
tool_output classified per-tool at registration timeRegras:
visibilityé uma coluna em toda linha visível ao usuário (mensagens, notas, proposals, registros tool_output, entradas de timeline, campos do painel de fatos).- O padrão na inserção é
mssp_only. A promoção paracustomer_safeé uma operação explícita. - As consultas do portal do cliente filtram na camada de política RLS, não na renderização. Uma sessão de visualizador-cliente não consegue ler linhas
mssp_only, nem mesmo via SQL bruto. - As proposals têm visibilidade em nível de campo:
{action, outcome}pode sercustomer_safeenquanto{rationale, blast_radius}permanecemssp_only. Renderizadas como duas projeções. - Toda promoção de visibilidade emite uma entrada em
execution_logcom o ator e a justificativa.
Negação-por-padrão da promoção: as políticas podem rebaixar a visibilidade, mas não podem elevá-la sem uma ação explícita de um principal autorizado.
3. Ciclo de vida da execução (run)
Estados:
active run consuming events and taking steps
waiting_on_gate a proposal is pending; run does not mutate state
halted_budget budget exceeded; requires analyst resume
paused analyst-paused
completed case closed
failed unrecoverable error; requires analyst resume or restartTransições:
active → waiting_on_gate on proposal created (status = proposed)
waiting_on_gate → active on proposal approved/rejected (new event)
active → halted_budget on budget exceeded
halted_budget → active on analyst resume (grants new budget)
active → paused on analyst pause
paused → active on analyst resume
active → completed on case close
* → failed on uncaught error, preserved for diagnosisInvariantes:
- No máximo uma run por case nos estados
active | waiting_on_gate | halted_budget | paused. Imposto via um índice único parcial emcase_runs(case_id) WHERE status IN (...). - Contadores de orçamento na run:
tokens_used,dollars_used,tool_calls_used,wall_clock_ms. Impostos no lado do servidor; aviso leve em 75%, parada rígida em 100%. - Uma run em
waiting_on_gatenão processa eventos do inbox exceto eventos de resolução de gate (proposal.approved / .rejected).
4. Inbox de eventos, ordenação, coalescência, idempotência
Todo o trabalho de entrada para um case chega em case_events:
event_id uuid PK
case_id FK
run_id FK nullable
seq bigint, case-scoped monotonic (sequence)
kind enum (alert_ingested, tool_result,
proposal_approved, proposal_rejected,
analyst_message, analyst_correction,
budget_warning, external_signal, ...)
payload jsonb
causation_event_id uuid nullable (which event caused this one)
correlation_id uuid (spans a causally-related fan-out)
idempotency_key text unique per case
created_at timestamptzRegras:
seqé emitido por uma sequência com escopo de case na inserção. Os consumidores leem estritamente na ordem deseq.idempotency_keyé único porcase_id. Inserção duplicada é silenciosamente descartada (retorna a linha existente).- Coalescência: antes da inserção, eventos que correspondem a
(case_id, kind, payload.signature, window)fundem-se em uma única linha. A assinatura é específica por kind (alert: fingerprint de IOC + rule + asset; tool_result: tool_id + hash de params). causation_event_idliga causa → efeito para replay.correlation_idagrupa eventos de um único gatilho externo ou ação de analista.- Os eventos são imutáveis. Atualizações se expressam como eventos subsequentes.
Exemplo de rajada: 100 alertas de host similares em 5 minutos coalescem em um evento alert_ingested carregando uma lista asset_ids: [...]. A run o processa uma única vez.
5. Ciclo de vida da proposal e contrato de execução
Estados:
draft being composed by the AI
proposed submitted to human gate
approved human approved (with typed reason if required)
rejected human rejected (reason required)
executing outbox picked up; executor running
executed action complete, result recorded
rolled_back post-execution reversal (rare, analyst-initiated)
failed executor errorIdempotência:
proposal.idempotency_key = sha256(case_id || action_type ||
canonical_json(params))Proposals duplicadas dentro de uma janela ativa (padrão 15 minutos) são rejeitadas na inserção. Garante que a AI não possa disparar em duplicidade nem mesmo sob re-execução.
Comportamento do gate:
- Em
proposed: a run transita parawaiting_on_gate. - Em
approved: insere uma linha emcase_outboxcomkind = 'execute_proposal',idempotency_key = proposal.idempotency_key. Emiteproposal_approvedemcase_events. A run retoma. - Em
rejected: emiteproposal_rejectedcom a justificativa emcase_events. A run retoma. Sem linha no outbox.
Execução:
- Um worker executor separado consome
case_outboxe realiza a ação. - Em caso de sucesso: registra
execute_proposal_resultemcase_events, atualiza a proposal →executed, escreve uma entrada emexecution_log. - Em caso de falha: registra o erro, atualiza a proposal →
failed, escreve uma entrada emexecution_log. A run pode propor uma nova tentativa. - Exatamente-uma-vez via
idempotency_key: linhas do outbox com chaves duplicadas são rejeitadas. Os workers executores reivindicam linhas com um lease (por exemplo,FOR UPDATE SKIP LOCKED).
A run da AI não executa efeitos colaterais inline. Tudo passa pelo outbox.
6. Schema e invariantes do log de execução
Somente-anexação (append-only), separado da conversa:
log_id uuid PK
case_id FK
run_id FK nullable
actor_kind enum (ai, human, system, executor)
actor_id text
kind enum (tool_call, proposal_state_change,
approval, override, visibility_promotion,
correction_applied, policy_bound,
export_emitted, ...)
subject_type enum (case, proposal, ioc, asset, note, ...)
subject_id text
before jsonb nullable
after jsonb nullable
versions jsonb (model_id, prompt_version, template_version,
policy_version at time of action)
ts timestamptz default now()Invariantes:
- Nenhum UPDATE ou DELETE é permitido a partir dos papéis da aplicação. Apenas INSERT + SELECT. Imposto na camada de concessão de papéis (role-grant) do Postgres.
- Toda mudança de estado de proposal, toda chamada de ferramenta, toda aprovação, toda substituição por analista de uma decisão da AI, toda mudança de visibilidade, toda correção, todo despacho para o outbox escreve uma linha.
versionscaptura a pilha que produziu a ação. Necessário para reprodutibilidade e calibração posterior (post-hoc).- A conversa é uma visão renderizada de um subconjunto de eventos; ela não é auditoria. Destruir ou compactar a conversa não destrói a auditoria.
7. Autoridade do painel de fatos e fluxo de correção
O estado estruturado do case (hipóteses, IOCs, assets, resumo da timeline, confiança, diretivas ativas) é a saída de um reducer sobre case_events. Nunca é mutado diretamente pela conversa.
Regras:
- Mensagens de conversa não escrevem estado estruturado.
- Atualizações da AI ao estado estruturado ocorrem via eventos emitidos pela AI (
hypothesis_updated,ioc_added,asset_linked). - Edições do analista no painel de fatos emitem eventos
analyst_correction. O reducer as aplica. A AI consome a correção como o próximo evento do inbox e raciocina novamente a partir do estado corrigido. - O painel de fatos é eventualmente consistente com
case_events. Uma projeção materializada (tabela ou view) é mantida; as leituras podem atingi-la diretamente. - Correções diretas ao log de execução são proibidas; as correções se expressam como novos eventos mais um ponteiro para o que foi corrigido.
8. Taxonomia de capacidades de ferramentas
Toda ferramenta é registrada com uma classe de capacidade, uma política de aprovação padrão e um modelo de custo.
Classes de capacidade:
read_local inspect SocTalk state only
read_external_silent no target footprint (feeds, cached intel, vector)
read_external_attributed trace at target (SIEM query, EDR read)
write_sandbox footprint without target mutation (detonation)
write_external target state change (block, isolate, notify)Política de aprovação padrão por classe:
read_local → autonomous
read_external_silent → autonomous
read_external_attributed → analyst_approve
write_sandbox → analyst_approve
write_external → typed_reasonModelo de custo por ferramenta: {tokens_est, dollars_est, wall_ms_est, footprint}. O orçamento da run rastreia a soma.
9. Precedência de políticas
As políticas são mescladas nesta ordem, a inferior sobrepõe a superior:
1. install default (shipped in chart, read-only in v1)
2. tenant override (MSSP sets per customer)
3. case template (phishing, ransomware, etc.)
4. case-local override (set for this one case by analyst)Para cada chave de política (aprovação de ferramenta, auto-close, promoção de visibilidade, templates de resposta, orçamento), o valor efetivo é o escopo mais profundo que a define.
Invariantes:
- A promoção de visibilidade nunca é definida como
permissivepor padrão no escopo de instalação. O padrão é "promoção explícita obrigatória." - Uma política de tenant não pode sobrepor um limite rígido em nível de instalação (por exemplo,
max_tokens_per_case). - Substituições em nível de case (case-local) têm escopo no case e não persistem para cases futuros.
10. Semântica de auto-close / reabertura
Auto-close para FPs de alta confiança:
Trigger:
AI assessment = fp, confidence ≥ policy.auto_close_threshold
AND policy.auto_close_enabled is true for the tenant
AND no active directive prevents auto-close
Action:
case.status = 'auto_closed_fp'
case.reopen_window_until = now() + policy.reopen_window
case.reopen_signature = {
ioc_fingerprints: [...],
asset_ids: [...],
time_window: {start, end}
}
run transitions to completed
execution_log row writtenReabertura:
Trigger:
new case_events row with kind ∈ {alert_ingested, external_signal}
whose signature intersects a case's reopen_signature
where case.status = 'auto_closed_fp'
AND now() < case.reopen_window_until
Action:
case.status = 'active'
emit reopened event into case_events
new run created
execution_log row written
conversation receives a system message noting the reopenKill switch:
IntegrationConfig.auto_close_enabledpor tenant (padrão: ligado).CaseTemplate.auto_close_disabledpor tipo de case.
11. Contrato de exportação para o TheHive (baseado em outbox, unidirecional)
Espelha cases, IOCs e notas selecionadas para fora, em direção ao TheHive, quando o tenant tem thehive_export_enabled. Nunca aceita mudanças de entrada.
Linha do outbox (em case_outbox):
id uuid PK
kind 'export.thehive.case' | 'export.thehive.ioc' | ...
external_system 'thehive'
external_ref TheHive object id (filled on first successful mirror)
object_type case | ioc | note
object_id internal subject id
idempotency_key sha256(object_type || object_id || state_hash)
payload jsonb
export_status pending | in_flight | succeeded | failed | skipped
attempts int
last_error text nullable
next_attempt_at timestamptz
created_at, updated_atRegras:
- A mudança de estado em um objeto espelhado enfileira uma linha de exportação com uma
idempotency_keynova (que incorpora o hash de estado). - O worker reivindica com
FOR UPDATE SKIP LOCKED. Em caso de sucesso, registraexternal_ref(criando ou atualizando no lado do TheHive conforme necessário) e escreve execution_log. - Webhooks de entrada do TheHive são aceitos apenas para cases de dashboard somente-leitura (não na v1). Qualquer tentativa de aceitar estado de entrada é explicitamente rejeitada e registrada.
- Sem loop de reconciliação — o TheHive é um espelho a jusante (downstream), a fonte da verdade é o SocTalk.
- Exportações com falha tentam novamente com backoff exponencial até um teto; a falha permanente aparece no painel de saúde das integrações.
12. Testes e invariantes obrigatórios
A suíte de testes (unitários + integração) deve cobrir:
- Imutabilidade do log de execução. UPDATE e DELETE contra
execution_loga partir do papel da aplicação falham na camada do Postgres. - Uma única run ativa por case. Tentativas concorrentes de criar uma segunda run ativa falham com uma violação de restrição única (unique-constraint).
- Idempotência de proposals. Submeter duas proposals com a mesma idempotency key dentro da janela: a segunda é rejeitada.
- Comportamento de pausa no gate. Uma run com uma proposal
proposednão consome eventos que não sejam de gate do seu inbox. - Outbox exatamente-uma-vez. Dois workers reivindicando a mesma linha do outbox resultam em um tendo sucesso e um não fazendo nada (no-op).
- Imposição de visibilidade. Uma sessão de visualizador-cliente não consegue selecionar linhas
mssp_onlyde nenhuma tabela, nem mesmo com SQL bruto. - Promoção de visibilidade registrada. Toda promoção de
mssp_onlyparacustomer_safeproduz uma linha emexecution_log. - Fluxo de correção. Um evento de correção do analista produz um novo evento que o reducer aplica; a projeção do painel de fatos reflete a correção.
- Reabertura de auto-close. Um evento que corresponde a uma reopen_signature dentro da janela reabre o case e inicia uma nova run.
- Idempotência da exportação para o TheHive. Reexecutar uma exportação para um objeto cujo estado não mudou é um no-op (mesma idempotency_key).
- Política de aprovação de ferramentas. Uma chamada de ferramenta
write_externalsem uma aprovação typed_reason não consegue alcançar o executor. - Precedência de políticas. A substituição case-local vence sobre a de tenant, que vence sobre a de instalação para a mesma chave de política.
13. Fora deste spec
- Modelos de componentes, comportamento visual, parsing da barra de comandos → o workstream da UI de conversa.
- Correlação de campanhas, scoring, mecânica cross-tenant → o workstream de campanhas.
- Biblioteca de prompts, conteúdo do registro de ferramentas do LLM, política de versão de modelo → o workstream separado do runtime de LLM (LLM runtime) quando chegarmos lá.
