feat(ttp): E.3.3 repository — insert_tags + listing rollups (dual backend)

Dialect-split: portable rollup queries on TTPMixin; bulk insert with
ON CONFLICT DO NOTHING / INSERT IGNORE in the per-dialect repos.
Confidence-floor (< 0.3) drop applied at mixin layer before the
dialect hook. BaseRepository now declares the six TTP methods abstract.

Tests in tests/web/db/test_ttp_repo.py flipped from pytest.fail stubs
to real dual-backend behavioral tests; tests/ttp/test_confidence.py
drop-below-floor xfail removed.
This commit is contained in:
2026-05-01 08:04:46 -04:00
parent 226b3adfa2
commit fee697694d
7 changed files with 452 additions and 98 deletions

View File

@@ -2,6 +2,12 @@ from abc import ABC, abstractmethod
from typing import Any, Optional
from decnet.web.db.models.topology import DeckyRow, EdgeRow, LANRow, TopologySummary
from decnet.web.db.models import (
CampaignTechniqueRow,
IdentityTechniqueRow,
TechniqueRollupRow,
TTPTag,
)
class BaseRepository(ABC):
@@ -1300,3 +1306,49 @@ class BaseRepository(ABC):
async def count_probe_relays(self, attacker_ip: str, decky: str) -> int:
raise NotImplementedError
# -------------------- TTP tagging (E.3.3) --------------------
@abstractmethod
async def insert_tags(self, rows: list[TTPTag]) -> int:
"""Bulk-upsert ``ttp_tag`` rows with ``INSERT OR IGNORE`` semantics.
Drops rows with ``confidence < 0.3`` (the floor pinned in
``tests/ttp/test_confidence.py``). Returns the count of rows
actually written. Idempotent — replaying the same source events
converges to the same tag set without duplicates.
"""
raise NotImplementedError
@abstractmethod
async def list_techniques_by_identity(
self, uuid: str,
) -> list[IdentityTechniqueRow]:
"""Per-Identity TTP rollup (joins through ``Attacker.identity_id``)."""
raise NotImplementedError
@abstractmethod
async def list_techniques_by_attacker(
self, uuid: str,
) -> list[IdentityTechniqueRow]:
"""Per-Attacker (per-IP) TTP rollup; excludes identity-rollup tags."""
raise NotImplementedError
@abstractmethod
async def list_techniques_by_campaign(
self, uuid: str,
) -> list[CampaignTechniqueRow]:
"""Campaign-wide TTP rollup across member identities."""
raise NotImplementedError
@abstractmethod
async def list_techniques_by_session(
self, sid: str,
) -> list[IdentityTechniqueRow]:
"""Session-scoped TTP timeline."""
raise NotImplementedError
@abstractmethod
async def list_distinct_techniques(self) -> list[TechniqueRollupRow]:
"""Fleet-wide distinct-technique rollup."""
raise NotImplementedError