feat(ttp): E.3.9 BehavioralLifter (R0031-R0040)
Reads pre-shaped session aggregates from TaggerEvent.payload and emits techniques per Appendix A behavior tables. Per-rule predicates dispatch on match.kind (lifter:behavioral_<name>); the lifter holds its own RuleIndex watching the same RuleStore as the engine, so disable / clip / TTL state reaches lifter-bound rules through the same atomic-swap path. R0032/R0036/R0037/R0040 YAMLs had over-escaped regex strings (\\ instead of \\) — fixed in place. Factory wired so default get_tagger() returns CompositeTagger with BehavioralLifter shipped; remaining three lifters (E.3.10-E.3.12) land in subsequent commits. E.2.6 contract preserved via TolerantTagger: empty payload steady-state yields [] with zero ERROR records. Disabled / clipped / expired state verified.
This commit is contained in:
@@ -106,13 +106,18 @@ class CompositeTagger(Tagger):
|
||||
def get_tagger() -> Tagger:
|
||||
"""Return the configured tagger instance.
|
||||
|
||||
Lazy package layout: the composite is constructed with an empty
|
||||
lifter list during the contract phase. E.1.6 will replace this
|
||||
with explicit lifter wiring; callers don't change.
|
||||
Synchronous construction: each shipped lifter takes the shared
|
||||
:class:`RuleStore` reference, but the per-lifter watch loops are
|
||||
started by the worker (E.3.14), not by this factory. Tests that
|
||||
instantiate via this path get an idle composite — exercising the
|
||||
watch loop is the worker's contract.
|
||||
"""
|
||||
name = os.environ.get("DECNET_TTP_TAGGER_TYPE", _DEFAULT).strip().lower()
|
||||
if name == "composite":
|
||||
return CompositeTagger(lifters=[])
|
||||
from decnet.ttp.impl.behavioral_lifter import BehavioralLifter
|
||||
from decnet.ttp.store.factory import get_rule_store
|
||||
store = get_rule_store()
|
||||
return CompositeTagger(lifters=[BehavioralLifter(store)])
|
||||
raise ValueError(
|
||||
f"Unknown tagger: {name!r}. Known: {_KNOWN}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user