feat(ttp): E.3.15 UKC bridge — production phase-handoff edge fires
Add BaseRepository.list_ttp_decky_phases(identity_uuid) returning per-decky tag observations as (decky_id, tactic, created_at_ts) rows ordered by creation time. Rewrite from_identity_row() to project tactic → UKCPhase via tactic_to_ukc_phase and populate the four phase-handoff maps (first/last_phase_per_decky, first/last_seen_per_decky) so combined_campaign_weight finally lights up on real DB rows — not just synthetic fixtures. ConnectedComponentsCampaignClusterer.tick() pulls each active identity's per-decky phase observations before projecting features. Repo failures are non-fatal: a partial repo falls back to the empty phase-handoff signal (legacy behavior) so the worker stays up. tests/clustering/test_ttp_phase_handoff.py pins the production-row pair clearing CAMPAIGN_EDGE_THRESHOLD on a C2 → DISCOVERY hand-off — the trip-wire that says the whole project paid off. commands_by_phase_on_decky itself stays empty on the production path: it is consumed only by the synthetic-fixture similarity surface, and the phase-handoff edge does not use it. Synthetic fixtures still populate it directly via from_synthetic_identity.
This commit is contained in:
@@ -1352,3 +1352,25 @@ class BaseRepository(ABC):
|
||||
async def list_distinct_techniques(self) -> list[TechniqueRollupRow]:
|
||||
"""Fleet-wide distinct-technique rollup."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def list_ttp_decky_phases(
|
||||
self, identity_uuid: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Per-decky tag observations for the campaign-clusterer's UKC
|
||||
bridge (E.3.15).
|
||||
|
||||
Returns every ``ttp_tag`` row for *identity_uuid* (and the IPs
|
||||
rolling up to it) carrying a non-NULL ``decky_id`` and
|
||||
``tactic``, projected to ``{decky_id, tactic, created_at_ts}``.
|
||||
Callers project ``tactic`` → :class:`UKCPhase` via
|
||||
:func:`decnet.clustering.ukc.tactic_to_ukc_phase` to populate
|
||||
:class:`IdentityFeatures.first_phase_per_decky` /
|
||||
``last_phase_per_decky`` / ``first_seen_per_decky`` /
|
||||
``last_seen_per_decky`` so the production phase-handoff edge
|
||||
can finally fire.
|
||||
|
||||
Default body returns ``[]`` so legacy mocks / non-SQLModel
|
||||
repos remain valid; the real implementation lives on the
|
||||
SQLModel TTP mixin.
|
||||
"""
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user