Arquitectura
Nota de despliegue de V1. La nomenclatura de la lista de entidades a continuación usa prefijos heredados "case_*" para varias tablas; los nombres reales del esquema de V1 son:
cases,investigation_runs,investigation_events,investigation_iocs,investigation_assets,investigation_links,investigation_outbox,proposals. El nombre de la tablacasesno cambia por compatibilidad hacia atrás, pero todas las tablas hijas por investigación usan el prefijoinvestigation_*. De estas, las tablas cases / investigation_runs / investigation_events son ejercitadas por el orquestador actual;proposalseinvestigation_outboxestán presentes en el esquema, pero el lado ejecutor que las consume está en la hoja de ruta. Lee esta página como la intención arquitectónica; consultasrc/soctalk/core/ir/models.pypara el esquema exacto.
1. Entidades centrales
Forma mínima. Las listas completas de columnas viven en la migración; aquí solo se nombran los campos que soportan carga.
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 exportsCada fila que porta contenido lleva tenant_id, visibility y created_at. RLS aplica por tenancy.
2. Modelo de visibilidad
Clases (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 timeReglas:
visibilityes una columna en cada fila visible para el usuario (mensajes, notas, propuestas, registros de tool_output, entradas de línea de tiempo, campos del panel de hechos).- El valor por defecto al insertar es
mssp_only. La promoción acustomer_safees una operación explícita. - Las consultas del portal del cliente filtran en la capa de la política RLS, no en el renderizado. Una sesión de visor de cliente no puede leer filas
mssp_onlyni siquiera mediante SQL directo. - Las propuestas tienen visibilidad a nivel de campo:
{action, outcome}puede sercustomer_safemientras{rationale, blast_radius}permanecemssp_only. Se renderizan como dos proyecciones. - Cada promoción de visibilidad emite una entrada en
execution_logcon el actor y la justificación.
Denegación-de-promoción-por-defecto: las políticas pueden degradar la visibilidad pero no pueden elevarla sin una acción explícita de un principal autorizado.
3. Ciclo de vida de la ejecución (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 restartTransiciones:
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:
- Como máximo una ejecución (run) por caso en estado
active | waiting_on_gate | halted_budget | paused. Se impone mediante un índice único parcial sobrecase_runs(case_id) WHERE status IN (...). - Contadores de presupuesto en la ejecución:
tokens_used,dollars_used,tool_calls_used,wall_clock_ms. Se imponen del lado del servidor; advertencia suave al 75%, detención dura al 100%. - Una ejecución en
waiting_on_gateno procesa eventos del inbox excepto eventos de resolución de gate (proposal.approved / .rejected).
4. Inbox de eventos, ordenamiento, coalescencia, idempotencia
Todo el trabajo entrante para un caso aterriza en 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 timestamptzReglas:
seqse emite mediante una secuencia con alcance de caso al insertar. Los consumidores leen estrictamente en orden deseq.idempotency_keyes único porcase_id. La inserción duplicada se descarta silenciosamente (devuelve la fila existente).- Coalescencia: antes de insertar, los eventos que coinciden con
(case_id, kind, payload.signature, window)se fusionan en una sola fila. La firma es específica del kind (alerta: huella de IOC + regla + activo; tool_result: tool_id + hash de params). causation_event_idenlaza causa → efecto para el replay.correlation_idagrupa eventos de un único disparador externo o acción del analista.- Los eventos son inmutables. Las actualizaciones se expresan como eventos de seguimiento.
Ejemplo de ráfaga: 100 alertas de host similares en 5 minutos se fusionan en un solo evento alert_ingested que porta una lista asset_ids: [...]. La ejecución lo procesa una vez.
5. Ciclo de vida de la propuesta y contrato de ejecución
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 errorIdempotencia:
proposal.idempotency_key = sha256(case_id || action_type ||
canonical_json(params))Las propuestas duplicadas dentro de una ventana activa (por defecto 15 minutos) se rechazan al insertar. Garantiza que la AI no pueda dispararse dos veces ni siquiera bajo una re-ejecución.
Comportamiento del gate:
- En
proposed: la ejecución transiciona awaiting_on_gate. - En
approved: inserta una fila encase_outboxconkind = 'execute_proposal',idempotency_key = proposal.idempotency_key. Emiteproposal_approvedencase_events. La ejecución se reanuda. - En
rejected: emiteproposal_rejectedcon la razón encase_events. La ejecución se reanuda. Sin fila en el outbox.
Ejecución:
- Un worker ejecutor separado consume
case_outboxy realiza la acción. - En caso de éxito: registra
execute_proposal_resultencase_events, actualiza la propuesta →executed, escribe una entrada enexecution_log. - En caso de fallo: registra el error, actualiza la propuesta →
failed, escribe una entrada enexecution_log. La ejecución puede proponer un reintento. - Exactamente-una-vez mediante
idempotency_key: las filas del outbox con claves duplicadas se rechazan. Los workers ejecutores reclaman filas con un lease (p. ej.,FOR UPDATE SKIP LOCKED).
La ejecución de la AI no ejecuta efectos secundarios en línea. Todo pasa por el outbox.
6. Esquema e invariantes del registro de ejecución
Solo-anexar (append-only), separado de la conversación:
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:
- No se permite ningún UPDATE ni DELETE desde los roles de la aplicación. Solo INSERT + SELECT. Se impone en la capa de otorgamiento de roles de Postgres.
- Cada cambio de estado de propuesta, cada llamada a herramienta, cada aprobación, cada anulación por parte del analista de una decisión de la AI, cada cambio de visibilidad, cada corrección, cada despacho al outbox escribe una fila.
versionscaptura el stack que produjo la acción. Requerido para la reproducibilidad y la calibración a posteriori.- La conversación es una vista renderizada de un subconjunto de eventos; no es auditoría. Destruir o compactar la conversación no destruye la auditoría.
7. Autoridad del panel de hechos y flujo de corrección
El estado estructurado del caso (hipótesis, IOCs, activos, resumen de la línea de tiempo, confianza, directivas activas) es la salida de un reductor sobre case_events. Nunca es mutado directamente por la conversación.
Reglas:
- Los mensajes de la conversación no escriben estado estructurado.
- Las actualizaciones de la AI al estado estructurado ocurren mediante eventos emitidos por la AI (
hypothesis_updated,ioc_added,asset_linked). - Las ediciones del analista en el panel de hechos emiten eventos
analyst_correction. El reductor las aplica. La AI consume la corrección como el siguiente evento del inbox y vuelve a razonar a partir del estado corregido. - El panel de hechos es eventualmente consistente con
case_events. Se mantiene una proyección materializada (tabla o vista); las lecturas pueden acceder a ella directamente. - Las correcciones directas al registro de ejecución están prohibidas; las correcciones se expresan como eventos nuevos más un puntero al evento corregido.
8. Taxonomía de capacidades de herramientas
Cada herramienta se registra con una clase de capacidad, una política de aprobación por defecto y un modelo de costo.
Clases de capacidad:
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 aprobación por defecto por clase:
read_local → autonomous
read_external_silent → autonomous
read_external_attributed → analyst_approve
write_sandbox → analyst_approve
write_external → typed_reasonModelo de costo por herramienta: {tokens_est, dollars_est, wall_ms_est, footprint}. El presupuesto de la ejecución rastrea la suma.
9. Precedencia de políticas
Las políticas se fusionan en este orden, las inferiores anulan a las superiores:
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 clave de política (aprobación de herramienta, auto-cierre, promoción de visibilidad, plantillas de respuesta, presupuesto), el valor efectivo es el alcance más profundo que la define.
Invariantes:
- La promoción de visibilidad nunca se establece en
permissivepor defecto en el alcance de instalación. El valor por defecto es "se requiere promoción explícita". - Una política de tenant no puede anular un tope duro a nivel de instalación (p. ej.,
max_tokens_per_case). - Las anulaciones locales del caso tienen alcance del caso y no persisten a casos futuros.
10. Semántica de auto-cierre / reapertura
Auto-cierre para falsos positivos de alta confianza:
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 writtenReapertura:
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 reopenInterruptor de emergencia (kill switch):
IntegrationConfig.auto_close_enabledpor tenant (por defecto: activado).CaseTemplate.auto_close_disabledpor tipo de caso.
11. Contrato de exportación a TheHive (basado en outbox, unidireccional)
Refleja casos, IOCs y notas seleccionadas hacia afuera a TheHive cuando el tenant tiene thehive_export_enabled. Nunca acepta cambios entrantes.
Fila del outbox (en 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_atReglas:
- Un cambio de estado en un objeto reflejado encola una fila de exportación con una
idempotency_keyfresca (incorpora el hash del estado). - El worker reclama con
FOR UPDATE SKIP LOCKED. En caso de éxito, registraexternal_ref(creando o actualizando del lado de TheHive según sea necesario) y escribe en execution_log. - Los webhooks entrantes de TheHive se aceptan solo para casos de dashboard de solo lectura (no en v1). Cualquier intento de aceptar estado entrante se rechaza y registra explícitamente.
- Sin bucle de reconciliación: TheHive es un espejo aguas abajo, la fuente de verdad es SocTalk.
- Las exportaciones fallidas reintentan con retroceso exponencial hasta un tope; el fallo permanente aparece en el panel de salud de integraciones.
12. Pruebas obligatorias e invariantes
La suite de pruebas (unitarias + integración) debe cubrir:
- Inmutabilidad del registro de ejecución. UPDATE y DELETE contra
execution_logdesde el rol de la aplicación fallan en la capa de Postgres. - Una única ejecución activa por caso. Los intentos concurrentes de crear una segunda ejecución activa fallan con una violación de restricción única.
- Idempotencia de propuestas. Enviar dos propuestas con la misma clave de idempotencia dentro de la ventana: la segunda se rechaza.
- Comportamiento de pausa por gate. Una ejecución con una propuesta
proposedno consume eventos que no sean de gate de su inbox. - Exactamente-una-vez del outbox. Dos workers reclamando la misma fila del outbox resultan en uno que tiene éxito y uno que no hace nada.
- Aplicación de visibilidad. Una sesión de visor de cliente no puede seleccionar filas
mssp_onlyde ninguna tabla, ni siquiera con SQL directo. - Promoción de visibilidad registrada. Cada promoción de
mssp_onlyacustomer_safeproduce una fila enexecution_log. - Flujo de corrección. El evento de corrección del analista produce un nuevo evento que el reductor aplica; la proyección del panel de hechos refleja la corrección.
- Reapertura por auto-cierre. Un evento que coincide con una reopen_signature dentro de la ventana reabre el caso e inicia una nueva ejecución.
- Idempotencia de la exportación a TheHive. Reejecutar una exportación para un objeto cuyo estado no ha cambiado es una operación nula (misma idempotency_key).
- Política de aprobación de herramientas. Una llamada a una herramienta
write_externalsin una aprobación typed_reason no puede llegar al ejecutor. - Precedencia de políticas. La anulación local del caso gana sobre la del tenant, que gana sobre la de instalación para la misma clave de política.
13. Fuera de esta especificación
- Modelos de componentes, comportamiento visual, análisis de la barra de comandos → el workstream de la UI de conversación.
- Correlación de campañas, puntuación, mecánicas cross-tenant → el workstream de campañas.
- Biblioteca de prompts, contenidos del registro de herramientas de LLM, política de versión de modelo → separar el workstream del runtime de LLM (LLM runtime) cuando lleguemos ahí.
