nthlayer-workers 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nthlayer_workers/__init__.py +5 -0
- nthlayer_workers/cli.py +234 -0
- nthlayer_workers/correlate/__init__.py +1 -0
- nthlayer_workers/correlate/cli.py +847 -0
- nthlayer_workers/correlate/config.py +111 -0
- nthlayer_workers/correlate/correlation/__init__.py +1 -0
- nthlayer_workers/correlate/correlation/changes.py +87 -0
- nthlayer_workers/correlate/correlation/dedup.py +62 -0
- nthlayer_workers/correlate/correlation/engine.py +244 -0
- nthlayer_workers/correlate/correlation/temporal.py +79 -0
- nthlayer_workers/correlate/correlation/topology.py +104 -0
- nthlayer_workers/correlate/ingestion/__init__.py +1 -0
- nthlayer_workers/correlate/ingestion/protocol.py +10 -0
- nthlayer_workers/correlate/ingestion/severity.py +18 -0
- nthlayer_workers/correlate/ingestion/webhook.py +197 -0
- nthlayer_workers/correlate/notifications.py +85 -0
- nthlayer_workers/correlate/prometheus.py +234 -0
- nthlayer_workers/correlate/reasoning.py +375 -0
- nthlayer_workers/correlate/session.py +189 -0
- nthlayer_workers/correlate/snapshot/__init__.py +1 -0
- nthlayer_workers/correlate/snapshot/generator.py +170 -0
- nthlayer_workers/correlate/snapshot/model.py +177 -0
- nthlayer_workers/correlate/snapshot/token.py +14 -0
- nthlayer_workers/correlate/state.py +88 -0
- nthlayer_workers/correlate/store/__init__.py +5 -0
- nthlayer_workers/correlate/store/protocol.py +48 -0
- nthlayer_workers/correlate/store/sqlite.py +443 -0
- nthlayer_workers/correlate/summary.py +180 -0
- nthlayer_workers/correlate/traces/__init__.py +1 -0
- nthlayer_workers/correlate/traces/protocol.py +120 -0
- nthlayer_workers/correlate/traces/tempo.py +667 -0
- nthlayer_workers/correlate/traces/topology.py +39 -0
- nthlayer_workers/correlate/types.py +77 -0
- nthlayer_workers/correlate/worker.py +630 -0
- nthlayer_workers/learn/__init__.py +5 -0
- nthlayer_workers/learn/__main__.py +5 -0
- nthlayer_workers/learn/cli.py +164 -0
- nthlayer_workers/learn/retrospective.py +381 -0
- nthlayer_workers/learn/trends.py +102 -0
- nthlayer_workers/learn/worker.py +366 -0
- nthlayer_workers/measure/__init__.py +3 -0
- nthlayer_workers/measure/__main__.py +5 -0
- nthlayer_workers/measure/_parsing.py +15 -0
- nthlayer_workers/measure/adapters/__init__.py +0 -0
- nthlayer_workers/measure/adapters/_util.py +24 -0
- nthlayer_workers/measure/adapters/devin.py +119 -0
- nthlayer_workers/measure/adapters/gastown.py +88 -0
- nthlayer_workers/measure/adapters/prometheus.py +277 -0
- nthlayer_workers/measure/adapters/protocol.py +20 -0
- nthlayer_workers/measure/adapters/webhook.py +161 -0
- nthlayer_workers/measure/api/__init__.py +0 -0
- nthlayer_workers/measure/api/normalise.py +50 -0
- nthlayer_workers/measure/api/queue.py +243 -0
- nthlayer_workers/measure/api/response.py +51 -0
- nthlayer_workers/measure/api/server.py +504 -0
- nthlayer_workers/measure/calibration/__init__.py +0 -0
- nthlayer_workers/measure/calibration/loop.py +62 -0
- nthlayer_workers/measure/calibration/slos.py +212 -0
- nthlayer_workers/measure/calibration/verdict_calibration.py +31 -0
- nthlayer_workers/measure/cli.py +753 -0
- nthlayer_workers/measure/config.py +191 -0
- nthlayer_workers/measure/detection/__init__.py +6 -0
- nthlayer_workers/measure/detection/detector.py +82 -0
- nthlayer_workers/measure/detection/protocol.py +29 -0
- nthlayer_workers/measure/governance/__init__.py +0 -0
- nthlayer_workers/measure/governance/engine.py +163 -0
- nthlayer_workers/measure/manifest.py +77 -0
- nthlayer_workers/measure/notifications.py +53 -0
- nthlayer_workers/measure/pipeline/__init__.py +0 -0
- nthlayer_workers/measure/pipeline/evaluator.py +155 -0
- nthlayer_workers/measure/pipeline/router.py +160 -0
- nthlayer_workers/measure/store/__init__.py +0 -0
- nthlayer_workers/measure/store/protocol.py +38 -0
- nthlayer_workers/measure/store/sqlite.py +276 -0
- nthlayer_workers/measure/telemetry.py +116 -0
- nthlayer_workers/measure/tiering/__init__.py +0 -0
- nthlayer_workers/measure/tiering/classifier.py +58 -0
- nthlayer_workers/measure/tiering/promotion.py +118 -0
- nthlayer_workers/measure/trends/__init__.py +0 -0
- nthlayer_workers/measure/trends/tracker.py +72 -0
- nthlayer_workers/measure/types.py +75 -0
- nthlayer_workers/measure/worker.py +439 -0
- nthlayer_workers/observe/__init__.py +25 -0
- nthlayer_workers/observe/__main__.py +5 -0
- nthlayer_workers/observe/api/__init__.py +1 -0
- nthlayer_workers/observe/assessment.py +95 -0
- nthlayer_workers/observe/cli.py +737 -0
- nthlayer_workers/observe/config.py +11 -0
- nthlayer_workers/observe/db/__init__.py +1 -0
- nthlayer_workers/observe/decision_records.py +220 -0
- nthlayer_workers/observe/dependencies/__init__.py +18 -0
- nthlayer_workers/observe/dependencies/discovery.py +294 -0
- nthlayer_workers/observe/dependencies/providers/__init__.py +48 -0
- nthlayer_workers/observe/dependencies/providers/backstage.py +467 -0
- nthlayer_workers/observe/dependencies/providers/base.py +76 -0
- nthlayer_workers/observe/dependencies/providers/consul.py +518 -0
- nthlayer_workers/observe/dependencies/providers/etcd.py +360 -0
- nthlayer_workers/observe/dependencies/providers/kubernetes.py +682 -0
- nthlayer_workers/observe/dependencies/providers/prometheus.py +368 -0
- nthlayer_workers/observe/dependencies/providers/zookeeper.py +399 -0
- nthlayer_workers/observe/deployments/__init__.py +1 -0
- nthlayer_workers/observe/discovery/__init__.py +14 -0
- nthlayer_workers/observe/discovery/classifier.py +66 -0
- nthlayer_workers/observe/discovery/client.py +189 -0
- nthlayer_workers/observe/discovery/models.py +53 -0
- nthlayer_workers/observe/drift/__init__.py +26 -0
- nthlayer_workers/observe/drift/analyzer.py +383 -0
- nthlayer_workers/observe/drift/models.py +174 -0
- nthlayer_workers/observe/drift/patterns.py +88 -0
- nthlayer_workers/observe/explanation.py +118 -0
- nthlayer_workers/observe/gate/__init__.py +39 -0
- nthlayer_workers/observe/gate/conditions.py +92 -0
- nthlayer_workers/observe/gate/correlator.py +154 -0
- nthlayer_workers/observe/gate/evaluator.py +192 -0
- nthlayer_workers/observe/gate/policies.py +226 -0
- nthlayer_workers/observe/gate_adapter.py +40 -0
- nthlayer_workers/observe/incident.py +36 -0
- nthlayer_workers/observe/portfolio/__init__.py +17 -0
- nthlayer_workers/observe/portfolio/aggregator.py +168 -0
- nthlayer_workers/observe/portfolio/scorer.py +13 -0
- nthlayer_workers/observe/slo/__init__.py +19 -0
- nthlayer_workers/observe/slo/collector.py +235 -0
- nthlayer_workers/observe/slo/spec_loader.py +40 -0
- nthlayer_workers/observe/sqlite_store.py +152 -0
- nthlayer_workers/observe/store.py +92 -0
- nthlayer_workers/observe/verification/__init__.py +22 -0
- nthlayer_workers/observe/verification/exporter_guidance.py +146 -0
- nthlayer_workers/observe/verification/extractor.py +127 -0
- nthlayer_workers/observe/verification/models.py +101 -0
- nthlayer_workers/observe/verification/verifier.py +111 -0
- nthlayer_workers/observe/worker.py +332 -0
- nthlayer_workers/respond/__init__.py +2 -0
- nthlayer_workers/respond/__main__.py +4 -0
- nthlayer_workers/respond/agents/__init__.py +0 -0
- nthlayer_workers/respond/agents/base.py +556 -0
- nthlayer_workers/respond/agents/communication.py +115 -0
- nthlayer_workers/respond/agents/investigation.py +124 -0
- nthlayer_workers/respond/agents/remediation.py +219 -0
- nthlayer_workers/respond/agents/triage.py +132 -0
- nthlayer_workers/respond/cli.py +772 -0
- nthlayer_workers/respond/config.py +135 -0
- nthlayer_workers/respond/context_store.py +256 -0
- nthlayer_workers/respond/coordinator.py +487 -0
- nthlayer_workers/respond/metrics.py +104 -0
- nthlayer_workers/respond/notification_backends/__init__.py +1 -0
- nthlayer_workers/respond/notification_backends/ntfy_backend.py +158 -0
- nthlayer_workers/respond/notification_backends/protocol.py +59 -0
- nthlayer_workers/respond/notification_backends/slack_backend.py +203 -0
- nthlayer_workers/respond/notification_backends/stdout_backend.py +56 -0
- nthlayer_workers/respond/notifications.py +247 -0
- nthlayer_workers/respond/oncall/__init__.py +1 -0
- nthlayer_workers/respond/oncall/escalation.py +103 -0
- nthlayer_workers/respond/oncall/runner.py +193 -0
- nthlayer_workers/respond/oncall/schedule.py +243 -0
- nthlayer_workers/respond/safe_actions/__init__.py +0 -0
- nthlayer_workers/respond/safe_actions/actions.py +139 -0
- nthlayer_workers/respond/safe_actions/registry.py +171 -0
- nthlayer_workers/respond/safe_actions/webhook.py +194 -0
- nthlayer_workers/respond/server.py +357 -0
- nthlayer_workers/respond/sre/__init__.py +1 -0
- nthlayer_workers/respond/sre/brief.py +175 -0
- nthlayer_workers/respond/sre/delegation.py +101 -0
- nthlayer_workers/respond/sre/post_incident.py +146 -0
- nthlayer_workers/respond/sre/shift_report.py +129 -0
- nthlayer_workers/respond/sre/suppression.py +91 -0
- nthlayer_workers/respond/types.py +109 -0
- nthlayer_workers/respond/verdict_submission.py +56 -0
- nthlayer_workers/respond/worker.py +533 -0
- nthlayer_workers/respond/worker_helpers.py +140 -0
- nthlayer_workers/runner.py +198 -0
- nthlayer_workers-1.0.0.dist-info/METADATA +19 -0
- nthlayer_workers-1.0.0.dist-info/RECORD +175 -0
- nthlayer_workers-1.0.0.dist-info/WHEEL +5 -0
- nthlayer_workers-1.0.0.dist-info/entry_points.txt +2 -0
- nthlayer_workers-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Helpers for opening IncidentContext from triggers.
|
|
2
|
+
|
|
3
|
+
``open_from_snapshot`` and ``open_from_breach`` build IncidentContext objects
|
|
4
|
+
from polled core API payloads. ``filter_after`` and ``breach_ids_with_snapshots``
|
|
5
|
+
support the cursor / dedup logic in ``RespondModule._ingest_*``. Topology tier
|
|
6
|
+
fields are populated by the worker via ``client.get_manifest()`` and passed
|
|
7
|
+
into the open helpers as the ``tiers`` mapping.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import uuid
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
|
|
14
|
+
from nthlayer_workers.respond.config import RespondConfig
|
|
15
|
+
from nthlayer_workers.respond.types import (
|
|
16
|
+
IncidentContext,
|
|
17
|
+
IncidentState,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _make_incident_id(service: str) -> str:
|
|
22
|
+
"""Build INC-<SERVICE>-YYYYMMDD-HHMMSS-<uuid8> id from current UTC time.
|
|
23
|
+
|
|
24
|
+
Includes an 8-char uuid suffix to disambiguate incidents opened within
|
|
25
|
+
the same second for the same service (rare but possible — two snapshots
|
|
26
|
+
flushing back-to-back, or a snapshot + its orphan-breach fallback racing).
|
|
27
|
+
"""
|
|
28
|
+
now = datetime.now(timezone.utc)
|
|
29
|
+
stamp = now.strftime("%Y%m%d-%H%M%S")
|
|
30
|
+
safe_svc = service.upper().replace("/", "-")
|
|
31
|
+
suffix = uuid.uuid4().hex[:8]
|
|
32
|
+
return f"INC-{safe_svc}-{stamp}-{suffix}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def open_from_snapshot(
|
|
36
|
+
snap: dict,
|
|
37
|
+
config: RespondConfig,
|
|
38
|
+
*,
|
|
39
|
+
tiers: dict[str, str] | None = None,
|
|
40
|
+
) -> IncidentContext:
|
|
41
|
+
"""Build IncidentContext from a correlation_snapshot assessment.
|
|
42
|
+
|
|
43
|
+
Primary trigger path — rich context from correlate's snapshot output.
|
|
44
|
+
The optional ``tiers`` mapping (service -> tier) is supplied by the
|
|
45
|
+
worker after fetching manifests from core; without it the triage agent
|
|
46
|
+
would see a hardcoded ``"standard"`` tier for every service, which would
|
|
47
|
+
distort severity derivation for critical services.
|
|
48
|
+
"""
|
|
49
|
+
data = snap.get("data") or {}
|
|
50
|
+
domain = data.get("domain") or {}
|
|
51
|
+
service = domain.get("service") or "unknown"
|
|
52
|
+
|
|
53
|
+
parent_ids = list(data.get("parent_ids") or [])
|
|
54
|
+
trigger_ids = [snap["id"], *parent_ids]
|
|
55
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
56
|
+
|
|
57
|
+
metadata: dict = {
|
|
58
|
+
"blast_radius": data.get("blast_radius", []),
|
|
59
|
+
"correlation_summary": data.get("nl_summary"),
|
|
60
|
+
"peak_severity": data.get("peak_severity"),
|
|
61
|
+
"event_count": data.get("event_count"),
|
|
62
|
+
"affected_services": data.get("affected_services", []),
|
|
63
|
+
# Capture-at-write-time: store threshold so escalation gate doesn't need
|
|
64
|
+
# config back-reference to make decisions
|
|
65
|
+
"escalation_threshold": config.escalation_threshold,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
affected = data.get("affected_services") or [service]
|
|
69
|
+
tiers = tiers or {}
|
|
70
|
+
return IncidentContext(
|
|
71
|
+
id=_make_incident_id(service),
|
|
72
|
+
state=IncidentState.TRIGGERED,
|
|
73
|
+
created_at=now,
|
|
74
|
+
updated_at=now,
|
|
75
|
+
trigger_source="nthlayer-correlate",
|
|
76
|
+
trigger_verdict_ids=trigger_ids,
|
|
77
|
+
topology={"services": [{"name": s, "tier": tiers.get(s, "standard")} for s in affected]},
|
|
78
|
+
metadata=metadata,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def open_from_breach(
|
|
83
|
+
breach: dict,
|
|
84
|
+
config: RespondConfig,
|
|
85
|
+
*,
|
|
86
|
+
tiers: dict[str, str] | None = None,
|
|
87
|
+
) -> IncidentContext:
|
|
88
|
+
"""Build IncidentContext from a quality_breach verdict (fallback path).
|
|
89
|
+
|
|
90
|
+
Fallback trigger path — thinner context. The triage agent will produce
|
|
91
|
+
a thinner initial briefing because there's no correlation snapshot to
|
|
92
|
+
reference. Appropriate, since the fallback only fires when correlate is
|
|
93
|
+
degraded.
|
|
94
|
+
"""
|
|
95
|
+
service = breach.get("service") or breach.get("subject", {}).get("service") or "unknown"
|
|
96
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
97
|
+
|
|
98
|
+
metadata: dict = {
|
|
99
|
+
# Static reason — threshold is logged separately by _ingest_orphan_breaches
|
|
100
|
+
# so the metadata string stays stable if the threshold is configurable
|
|
101
|
+
"fallback_reason": "no_correlation_snapshot",
|
|
102
|
+
"severity": breach.get("severity") or breach.get("data", {}).get("severity"),
|
|
103
|
+
"escalation_threshold": config.escalation_threshold,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
tiers = tiers or {}
|
|
107
|
+
return IncidentContext(
|
|
108
|
+
id=_make_incident_id(service),
|
|
109
|
+
state=IncidentState.TRIGGERED,
|
|
110
|
+
created_at=now,
|
|
111
|
+
updated_at=now,
|
|
112
|
+
trigger_source="nthlayer-measure-fallback",
|
|
113
|
+
trigger_verdict_ids=[breach["id"]],
|
|
114
|
+
topology={"services": [{"name": service, "tier": tiers.get(service, "standard")}]},
|
|
115
|
+
metadata=metadata,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def filter_after(records: list, cursor: str | None) -> list:
|
|
120
|
+
"""Return records with created_at strictly greater than cursor."""
|
|
121
|
+
if cursor is None:
|
|
122
|
+
return list(records)
|
|
123
|
+
return [r for r in records if (r.get("created_at") or "") > cursor]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def breach_ids_with_snapshots(snapshots: list[dict]) -> set[str]:
|
|
127
|
+
"""Set of breach IDs that already have an associated correlation snapshot.
|
|
128
|
+
|
|
129
|
+
Used by the fallback path to dedup orphan breaches without an O(N) HTTP
|
|
130
|
+
fan-out. v1.5 limitation: pagination of the snapshot list means breaches
|
|
131
|
+
referenced only by snapshots beyond page 1 are not deduped — fallback
|
|
132
|
+
will over-trigger an incident in that case (safer than dropping signals).
|
|
133
|
+
"""
|
|
134
|
+
seen: set[str] = set()
|
|
135
|
+
for snap in snapshots or []:
|
|
136
|
+
data = snap.get("data") or {}
|
|
137
|
+
for pid in (data.get("parent_ids") or []):
|
|
138
|
+
if isinstance(pid, str):
|
|
139
|
+
seen.add(pid)
|
|
140
|
+
return seen
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""Worker module orchestrator.
|
|
2
|
+
|
|
3
|
+
Runs all configured worker modules on their specified intervals. Each module
|
|
4
|
+
implements the WorkerModule protocol: restore_state → process_cycle → persist_state.
|
|
5
|
+
|
|
6
|
+
One heartbeat emitted per runner cycle (not per module). State persisted to
|
|
7
|
+
core via the HTTP API after each cycle. Graceful shutdown on SIGTERM: finish
|
|
8
|
+
current cycle, persist state, then exit.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
runner = ModuleRunner(core_url="http://localhost:8000")
|
|
12
|
+
runner.register(observe_module, interval_seconds=30)
|
|
13
|
+
runner.register(measure_module, interval_seconds=60)
|
|
14
|
+
await runner.run() # blocks until SIGTERM
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import signal
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Protocol, runtime_checkable
|
|
24
|
+
|
|
25
|
+
import structlog
|
|
26
|
+
|
|
27
|
+
from nthlayer_common.api_client import CoreAPIClient
|
|
28
|
+
|
|
29
|
+
logger = structlog.get_logger()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@runtime_checkable
|
|
33
|
+
class WorkerModule(Protocol):
|
|
34
|
+
"""Protocol that all worker modules implement."""
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def name(self) -> str:
|
|
38
|
+
"""Module name (e.g. 'observe', 'measure')."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
async def restore_state(self, state: dict[str, Any] | None) -> None:
|
|
42
|
+
"""Restore processing state from a previous run."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
async def process_cycle(self) -> None:
|
|
46
|
+
"""Execute one processing cycle."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
async def get_state(self) -> dict[str, Any]:
|
|
50
|
+
"""Return current state for persistence."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class RegisteredModule:
|
|
56
|
+
"""A module registered with the runner."""
|
|
57
|
+
|
|
58
|
+
module: WorkerModule
|
|
59
|
+
interval_seconds: float
|
|
60
|
+
last_run: float = 0.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class ModuleRunner:
|
|
65
|
+
"""Orchestrates worker modules on configured intervals.
|
|
66
|
+
|
|
67
|
+
Each tick:
|
|
68
|
+
1. Check which modules are due to run
|
|
69
|
+
2. Run due modules (sequentially within a tick)
|
|
70
|
+
3. Persist state for each module that ran
|
|
71
|
+
4. Emit one heartbeat to core (only on ticks where a module ran)
|
|
72
|
+
|
|
73
|
+
Graceful shutdown: SIGTERM sets the shutdown flag. The current cycle
|
|
74
|
+
completes, state is persisted, then the runner exits.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
core_url: str = "http://localhost:8000"
|
|
78
|
+
instance_id: str = "workers-1"
|
|
79
|
+
tick_interval: float = 1.0 # seconds between scheduler ticks
|
|
80
|
+
|
|
81
|
+
client: CoreAPIClient | None = None # inject for testing; created lazily if None
|
|
82
|
+
|
|
83
|
+
_modules: list[RegisteredModule] = field(default_factory=list, init=False)
|
|
84
|
+
_shutdown: bool = field(default=False, init=False)
|
|
85
|
+
|
|
86
|
+
def register(self, module: WorkerModule, interval_seconds: float) -> None:
|
|
87
|
+
"""Register a module to run at the specified interval."""
|
|
88
|
+
self._modules.append(RegisteredModule(
|
|
89
|
+
module=module,
|
|
90
|
+
interval_seconds=interval_seconds,
|
|
91
|
+
))
|
|
92
|
+
logger.info("module_registered", module=module.name, interval=interval_seconds)
|
|
93
|
+
|
|
94
|
+
async def run(self) -> None:
|
|
95
|
+
"""Run the module loop until SIGTERM."""
|
|
96
|
+
if self.client is None:
|
|
97
|
+
self.client = CoreAPIClient(base_url=self.core_url)
|
|
98
|
+
self._install_signal_handlers()
|
|
99
|
+
|
|
100
|
+
logger.info("runner_starting", modules=[m.module.name for m in self._modules])
|
|
101
|
+
|
|
102
|
+
# Restore state for all modules
|
|
103
|
+
await self._restore_all_state()
|
|
104
|
+
|
|
105
|
+
# Main loop
|
|
106
|
+
while not self._shutdown:
|
|
107
|
+
cycle_start = time.monotonic()
|
|
108
|
+
ran_any = False
|
|
109
|
+
|
|
110
|
+
for reg in self._modules:
|
|
111
|
+
if self._shutdown:
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
now = time.monotonic()
|
|
115
|
+
if now - reg.last_run >= reg.interval_seconds:
|
|
116
|
+
await self._run_module(reg)
|
|
117
|
+
ran_any = True
|
|
118
|
+
|
|
119
|
+
# Emit heartbeat if any module ran
|
|
120
|
+
if ran_any:
|
|
121
|
+
await self._emit_heartbeat()
|
|
122
|
+
|
|
123
|
+
# Sleep until next tick
|
|
124
|
+
elapsed = time.monotonic() - cycle_start
|
|
125
|
+
sleep_time = max(0, self.tick_interval - elapsed)
|
|
126
|
+
if sleep_time > 0 and not self._shutdown:
|
|
127
|
+
await asyncio.sleep(sleep_time)
|
|
128
|
+
|
|
129
|
+
# Graceful shutdown: persist state and emit final heartbeat
|
|
130
|
+
logger.info("runner_shutting_down")
|
|
131
|
+
await self._persist_all_state()
|
|
132
|
+
await self._emit_heartbeat()
|
|
133
|
+
logger.info("runner_stopped")
|
|
134
|
+
|
|
135
|
+
async def _restore_all_state(self) -> None:
|
|
136
|
+
"""Restore state for all modules from core API."""
|
|
137
|
+
for reg in self._modules:
|
|
138
|
+
name = reg.module.name
|
|
139
|
+
result = await self.client.get_component_state(name)
|
|
140
|
+
state = result.data if result.ok else None
|
|
141
|
+
try:
|
|
142
|
+
await reg.module.restore_state(state)
|
|
143
|
+
logger.info("state_restored", module=name, had_state=state is not None)
|
|
144
|
+
except Exception:
|
|
145
|
+
logger.exception("state_restore_failed", module=name)
|
|
146
|
+
|
|
147
|
+
async def _run_module(self, reg: RegisteredModule) -> None:
|
|
148
|
+
"""Run a single module's processing cycle."""
|
|
149
|
+
name = reg.module.name
|
|
150
|
+
start = time.monotonic()
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
await reg.module.process_cycle()
|
|
154
|
+
except Exception:
|
|
155
|
+
logger.exception("cycle_failed", module=name)
|
|
156
|
+
reg.last_run = time.monotonic()
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
duration_ms = int((time.monotonic() - start) * 1000)
|
|
160
|
+
logger.info("cycle_complete", module=name, duration_ms=duration_ms)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
state = await reg.module.get_state()
|
|
164
|
+
await self.client.put_component_state(name, state)
|
|
165
|
+
except Exception:
|
|
166
|
+
logger.exception("state_persist_failed", module=name)
|
|
167
|
+
|
|
168
|
+
reg.last_run = time.monotonic()
|
|
169
|
+
|
|
170
|
+
async def _persist_all_state(self) -> None:
|
|
171
|
+
"""Persist state for all modules (shutdown path)."""
|
|
172
|
+
for reg in self._modules:
|
|
173
|
+
try:
|
|
174
|
+
state = await reg.module.get_state()
|
|
175
|
+
await self.client.put_component_state(reg.module.name, state)
|
|
176
|
+
except Exception:
|
|
177
|
+
logger.exception("shutdown_persist_failed", module=reg.module.name)
|
|
178
|
+
|
|
179
|
+
async def _emit_heartbeat(self) -> None:
|
|
180
|
+
"""Emit a single heartbeat to core."""
|
|
181
|
+
module_names = [r.module.name for r in self._modules]
|
|
182
|
+
await self.client.heartbeat(
|
|
183
|
+
component="workers",
|
|
184
|
+
instance_id=self.instance_id,
|
|
185
|
+
state={"modules": module_names},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _install_signal_handlers(self) -> None:
|
|
189
|
+
"""Install SIGTERM/SIGINT handlers for graceful shutdown."""
|
|
190
|
+
loop = asyncio.get_running_loop()
|
|
191
|
+
|
|
192
|
+
def _handle_signal(signum: int) -> None:
|
|
193
|
+
sig_name = signal.Signals(signum).name
|
|
194
|
+
logger.info("signal_received", signal=sig_name)
|
|
195
|
+
self._shutdown = True
|
|
196
|
+
|
|
197
|
+
for sig in (signal.SIGTERM, signal.SIGINT):
|
|
198
|
+
loop.add_signal_handler(sig, _handle_signal, sig)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nthlayer-workers
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NthLayer workers — Tier 2 background computation (observe, measure, correlate, respond, learn)
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: nthlayer-common>=0.1.8
|
|
8
|
+
Requires-Dist: httpx>=0.27
|
|
9
|
+
Requires-Dist: pyyaml>=6.0
|
|
10
|
+
Requires-Dist: structlog>=24.1.0
|
|
11
|
+
Requires-Dist: scipy>=1.11
|
|
12
|
+
Requires-Dist: numpy>=1.24
|
|
13
|
+
Requires-Dist: starlette>=0.40
|
|
14
|
+
Requires-Dist: uvicorn>=0.30
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
18
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
19
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
nthlayer_workers/__init__.py,sha256=eaVfM5tNHjxsZSacYUacy5-sDf7DAiX0ktEumbVDjVs,189
|
|
2
|
+
nthlayer_workers/cli.py,sha256=P4w6jms7dcrQYe0OCgAiCrh69rVyJE4S5PShpYIqgtg,10981
|
|
3
|
+
nthlayer_workers/runner.py,sha256=ND6vgfug-nk4nT3-zWXXUUX4598ahs4C3RfFkKQyLRY,6779
|
|
4
|
+
nthlayer_workers/correlate/__init__.py,sha256=xuB0EPKB6d0bluRTi5Ze86rG9zz0S1h5y6_qsOxmcnw,77
|
|
5
|
+
nthlayer_workers/correlate/cli.py,sha256=vq2HLY7IXMP3SV65jrLxD4gNka2mMeP3G2dRk6bragM,32294
|
|
6
|
+
nthlayer_workers/correlate/config.py,sha256=u3pRwz2RzdD8lh5efrpj2LTvs4dJw7tNWLv7aLexQZE,3735
|
|
7
|
+
nthlayer_workers/correlate/notifications.py,sha256=hJq86Jc0ogabc7Z3gPCtblxyGH8C812nMYqG2Zo277I,2812
|
|
8
|
+
nthlayer_workers/correlate/prometheus.py,sha256=kJEoudNFWaWnAiYczm1MBS4GucFD4JcsR6CIwMzIac4,8506
|
|
9
|
+
nthlayer_workers/correlate/reasoning.py,sha256=38X7jWaYAaUrDX-MbJYcpaIfdMwLe0meiJ4AtcPZVVo,12833
|
|
10
|
+
nthlayer_workers/correlate/session.py,sha256=v84kUrxLPoCiHRlWSbYEtgvTulO3c-hpiYIYnU-Vm6c,6873
|
|
11
|
+
nthlayer_workers/correlate/state.py,sha256=2iFoQ4Y38rxcrZk2VhJBYlOCPiUWUp0JUFXArSjMhfI,3067
|
|
12
|
+
nthlayer_workers/correlate/summary.py,sha256=5eMs7cA1Y5vtkAX4NPBJlK9XMVWG5B0GuSOlM0dmIwA,6152
|
|
13
|
+
nthlayer_workers/correlate/types.py,sha256=TTc3b9Q5YeYwi15prWWyw7uZa1-OurT8P84Emu5gfk8,1759
|
|
14
|
+
nthlayer_workers/correlate/worker.py,sha256=nwOcY-TQ8xhwgVtfu5vSryWHEqPyvDxFkELmZzQ7Rtw,23225
|
|
15
|
+
nthlayer_workers/correlate/correlation/__init__.py,sha256=vY8CBiMDxKuZnfyyQBmnRfqPfrU5nZV4FeIrm1kOij0,81
|
|
16
|
+
nthlayer_workers/correlate/correlation/changes.py,sha256=spCueq22TjiBmIlvysg1o246pAnzR4x2Do16BnriXYU,3372
|
|
17
|
+
nthlayer_workers/correlate/correlation/dedup.py,sha256=WaONBtDDeo2aPl4QO5dlLYcPSNHV7gD-etoU4wMAHmc,2211
|
|
18
|
+
nthlayer_workers/correlate/correlation/engine.py,sha256=q0pZPuPvHfJe4WR5HRl3EextP-Atm_ht5VpDcNkJuKc,9092
|
|
19
|
+
nthlayer_workers/correlate/correlation/temporal.py,sha256=S4GIYBHUFH1QMhDh3NZ9BaU-NjZhCianbMt9mvNARQA,2542
|
|
20
|
+
nthlayer_workers/correlate/correlation/topology.py,sha256=jpsHE5bFqHaVJlpgdbcHHl-927ngXXu2xw5HC5bGrzo,3717
|
|
21
|
+
nthlayer_workers/correlate/ingestion/__init__.py,sha256=vDVAhVEDSmTqwbuE-iTl608EdrIKD_TJtVXCmuINzy0,34
|
|
22
|
+
nthlayer_workers/correlate/ingestion/protocol.py,sha256=SKKgS7sk7Sbzb4HIQSA-x_ARLyYWZy5ooL2sYkfbg_0,416
|
|
23
|
+
nthlayer_workers/correlate/ingestion/severity.py,sha256=BgA9b7ak6wey-sqOGiUJ7GwLxS5AGwXAJCbXJ8YtGRM,774
|
|
24
|
+
nthlayer_workers/correlate/ingestion/webhook.py,sha256=gRiNpPA5AWEWpR09jxC7ei595IEvfLh6KKTLLZ3700I,7270
|
|
25
|
+
nthlayer_workers/correlate/snapshot/__init__.py,sha256=ktUHJp6tO2xA4bCKlfgjDyvtklfGH9r1sjlvsNQ_jNY,38
|
|
26
|
+
nthlayer_workers/correlate/snapshot/generator.py,sha256=rkzN5OwlOntJSAukrhrSxfIlXr0GtMi90IeWTra6e9A,5994
|
|
27
|
+
nthlayer_workers/correlate/snapshot/model.py,sha256=YmxWA92C5VSoIqScNSOoMh84pVz7eTISfBTjZyv_SB8,6433
|
|
28
|
+
nthlayer_workers/correlate/snapshot/token.py,sha256=cK8JbGCKnTLZlKcFtIVyhQfwrpBWaEc9BVV9pQcJw7Y,370
|
|
29
|
+
nthlayer_workers/correlate/store/__init__.py,sha256=LyB8Af1SAQ2yYTwrMEOT-jaVnRiMh7yix4A9N1P2LrU,212
|
|
30
|
+
nthlayer_workers/correlate/store/protocol.py,sha256=V1ehlh6DaAFvHPVNyJG34Q4PDdJ_MpXfTPrmJIUde2c,1295
|
|
31
|
+
nthlayer_workers/correlate/store/sqlite.py,sha256=p7bpkwnCOnpALMitdnsFDVw3KGRkWsqIe8g-BVXn6YY,15853
|
|
32
|
+
nthlayer_workers/correlate/traces/__init__.py,sha256=xLPvQyhEWNeK40BLSzQ68GWcpGRCNiB2oa007rl59gU,53
|
|
33
|
+
nthlayer_workers/correlate/traces/protocol.py,sha256=BcW5dB966tGKUBksD1eJrWIGfy6NwyhuYp0659wsHLg,2965
|
|
34
|
+
nthlayer_workers/correlate/traces/tempo.py,sha256=gkiJL2JQCRrB0OfXShzwxeyONWjO_9jh-sbQqZlk4-M,25501
|
|
35
|
+
nthlayer_workers/correlate/traces/topology.py,sha256=H-nuDHiPcWHrC_IZidVYpA1iFsrzYHnCh6cNSxoDMjM,1553
|
|
36
|
+
nthlayer_workers/learn/__init__.py,sha256=PUM5PL8m11YR4XBw-A7yOn4_A18iqdaXxkOfN4mDKM4,199
|
|
37
|
+
nthlayer_workers/learn/__main__.py,sha256=2mHVByvhKq50ShjbOahA5HB2cdyMPIHHXyxHRWQk-1E,101
|
|
38
|
+
nthlayer_workers/learn/cli.py,sha256=EhfyYx8cOyliGLRIExtp5F9FPA3Ht8E6wY7Mr-iNiO0,6468
|
|
39
|
+
nthlayer_workers/learn/retrospective.py,sha256=KdELzz9WRTNd__aPsX94Q5lMWLxZEZ0yBWstvnPQuF4,14133
|
|
40
|
+
nthlayer_workers/learn/trends.py,sha256=3dWQpOekWerpSrmEJTX-UQDWETjj9QEyzx5mdHFtqwY,2538
|
|
41
|
+
nthlayer_workers/learn/worker.py,sha256=D24qPJcYoVJU2U4yyrYKttgZy6CjCw7X9e6Sa3YYf-8,14565
|
|
42
|
+
nthlayer_workers/measure/__init__.py,sha256=F1RVG7x-tHn-zvDcRhc29vxX5cP9U8CPwFM75gpXiVc,108
|
|
43
|
+
nthlayer_workers/measure/__main__.py,sha256=RCvvsj5ut4UO-9RBSk6zmG4FftQA9ADPSkvYtnwYLD0,106
|
|
44
|
+
nthlayer_workers/measure/_parsing.py,sha256=73kKWSkW7rf9SlODaW4gGALefWS7a7wzgs3Cb4bSbH4,499
|
|
45
|
+
nthlayer_workers/measure/cli.py,sha256=houf28VX5vl4uvnKGPDpjhp6L70IrBGQ3vV8PxC96vk,27577
|
|
46
|
+
nthlayer_workers/measure/config.py,sha256=V-gQN2pGHuBAgkrHGb_taCpLXdrvc_rWZAWivBAEdlU,6525
|
|
47
|
+
nthlayer_workers/measure/manifest.py,sha256=aQvbLyy4ORY97cWks99gBysdQlgLDnwCyo6Swt6T8JA,2240
|
|
48
|
+
nthlayer_workers/measure/notifications.py,sha256=QAF2_CC2uepQV3Ng3lAzZHK7lZ_aU431yI6V8Ks6ofw,1745
|
|
49
|
+
nthlayer_workers/measure/telemetry.py,sha256=PMHYS7EsPBBAbx6U1NOgXhXN7H1u11csf_FMN4-se5I,3345
|
|
50
|
+
nthlayer_workers/measure/types.py,sha256=59mP2pgcd5TKS__RI6bx2efk6zuuQA7Qy5YZKHi0NuA,2057
|
|
51
|
+
nthlayer_workers/measure/worker.py,sha256=ihGv090nM9iK5NteFT8lxsFL2P-pCscJDJP1U8u37vs,17106
|
|
52
|
+
nthlayer_workers/measure/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
+
nthlayer_workers/measure/adapters/_util.py,sha256=Yzk0IGW1iv7L6YFwgXl3OL4GwaYuPRuDSKz4-lc2BYc,664
|
|
54
|
+
nthlayer_workers/measure/adapters/devin.py,sha256=Lk2q_iwUU7y7XldKbIBqBXFAtAuD0hULC_OrQt6hfGg,4061
|
|
55
|
+
nthlayer_workers/measure/adapters/gastown.py,sha256=az3MSv4IdWqyfOrZ-tkizRbdsmea6zdE_wREen-xTIE,2822
|
|
56
|
+
nthlayer_workers/measure/adapters/prometheus.py,sha256=gYpdi4FIabS5zEET6sHfnyPyLZstxFR_F3Gt-d4UOfc,9776
|
|
57
|
+
nthlayer_workers/measure/adapters/protocol.py,sha256=P3dMr4Ucg10_bhzm6z88C8JBAQtiW_7ETMh4OugVTB8,534
|
|
58
|
+
nthlayer_workers/measure/adapters/webhook.py,sha256=vmj-LF1iw93l4AU16aX7ukyQeIZjAmJmLEu3z6fl0oQ,6224
|
|
59
|
+
nthlayer_workers/measure/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
+
nthlayer_workers/measure/api/normalise.py,sha256=w8rFD_WIRReqLfFnw_KiCZ9nPGrtE-RZyDpGfIpmkXo,1700
|
|
61
|
+
nthlayer_workers/measure/api/queue.py,sha256=SNgxGmTPvpcHTLa6KvJX9GY0i0JbuLd6wVXw3v3b490,9346
|
|
62
|
+
nthlayer_workers/measure/api/response.py,sha256=T6IlFOwjuDtn1fMIb5Ijl6ZjWNeyEeykYUrXs96TTdY,1458
|
|
63
|
+
nthlayer_workers/measure/api/server.py,sha256=3Vgk4Ek_VcrbJs1jHYTVbIXzqHYno88g6P0daHL2RAs,18492
|
|
64
|
+
nthlayer_workers/measure/calibration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
+
nthlayer_workers/measure/calibration/loop.py,sha256=9X6xLc8r_JzxuJqKIcahGKnGyPKKN3cshc4Dr8tQUpQ,1890
|
|
66
|
+
nthlayer_workers/measure/calibration/slos.py,sha256=fI2wwz99eiTpqwSuzYxtr9coLUk86Z6SxkpmMlxpThw,7013
|
|
67
|
+
nthlayer_workers/measure/calibration/verdict_calibration.py,sha256=zDFUkXTqmMdCgy1Lq9rxrj16hniUIP4CmO5SEiw0P00,1087
|
|
68
|
+
nthlayer_workers/measure/detection/__init__.py,sha256=LXwiiaUKgibD9SEoALxXXTO0zWciOASOK3gXS9zf-zc,316
|
|
69
|
+
nthlayer_workers/measure/detection/detector.py,sha256=FDCfWVZ413oMungPxln0T6PKYqFuUVDTlDrQMmZiIiI,2890
|
|
70
|
+
nthlayer_workers/measure/detection/protocol.py,sha256=yWEzJIMtEfjL2kMVmgo4og6JgGtPMEiOruyvtFzXrFQ,733
|
|
71
|
+
nthlayer_workers/measure/governance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
+
nthlayer_workers/measure/governance/engine.py,sha256=-HrIdMhO7Awc4FybZco2mKP3PSvke6DqNEEmyoyndRI,6135
|
|
73
|
+
nthlayer_workers/measure/pipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
+
nthlayer_workers/measure/pipeline/evaluator.py,sha256=6nU-c-CLexNSVi3FueZI0oAzJK94Q5RGYPmt8QttDZg,5800
|
|
75
|
+
nthlayer_workers/measure/pipeline/router.py,sha256=VZZ3Eca3PnVUu51d6dKMCmYxap0PSmrqtYUx51ZOgmY,6193
|
|
76
|
+
nthlayer_workers/measure/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
|
+
nthlayer_workers/measure/store/protocol.py,sha256=H8pY9_GCUxuUj4L0UZUZgDjzk7xt2YdtFxg57ApvfVM,1133
|
|
78
|
+
nthlayer_workers/measure/store/sqlite.py,sha256=w_kDoBi8DK_a4u6vz993nMhlBqCN-CIQFkIa5BR63Rg,11668
|
|
79
|
+
nthlayer_workers/measure/tiering/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
+
nthlayer_workers/measure/tiering/classifier.py,sha256=A72cYbjGwV7G8BasboRjgQ6NpywDROyMEJHWTdQ-wyU,1820
|
|
81
|
+
nthlayer_workers/measure/tiering/promotion.py,sha256=ywVynaq79p4HPVXjvs5UGa1VWKnWiumXEAv9hPK8qoc,4245
|
|
82
|
+
nthlayer_workers/measure/trends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
+
nthlayer_workers/measure/trends/tracker.py,sha256=2plSDdOb3w-l9IaYjgSxBKT_BTw4K8Cl114oyW5hHec,2605
|
|
84
|
+
nthlayer_workers/observe/__init__.py,sha256=wJCJlTeMJsev9w63vy172fxH7RmTt7Km-WiAYOOJfXc,617
|
|
85
|
+
nthlayer_workers/observe/__main__.py,sha256=SBAP8d2BAU2ep1Uvt05wb5B0GogZ6fcbx8cryuoKY80,124
|
|
86
|
+
nthlayer_workers/observe/assessment.py,sha256=vWsgSnuotFqlAahMuRz0QyMn3JkFWY2HuLOHRoiN-_0,2503
|
|
87
|
+
nthlayer_workers/observe/cli.py,sha256=mPIpXGTXUeFgJag4hRZ1CZSVHHhZ-tZdbMuV9Io-IxM,28275
|
|
88
|
+
nthlayer_workers/observe/config.py,sha256=_czQgYpKZcqRXK2TQTHBW4qFVIyz1ffXQfAUySoT_z0,255
|
|
89
|
+
nthlayer_workers/observe/decision_records.py,sha256=5sC2Xdq2Pnzsi-G3ZvNDGnpu5ylFTfQ5uxcgmNNcX6s,8133
|
|
90
|
+
nthlayer_workers/observe/explanation.py,sha256=5IJ5n9CdMskrFhD0cd0W7sLhY8UiELH9ilTplNMGtuI,4226
|
|
91
|
+
nthlayer_workers/observe/gate_adapter.py,sha256=2oJ9-zEVk-g9soGT_raBf7GbXjBN3frOLE4Gq9JAL4Y,1462
|
|
92
|
+
nthlayer_workers/observe/incident.py,sha256=4nnW68NFaLC74jZSu-yDy1GGkpi-cnJlZp62nn_uB-0,1117
|
|
93
|
+
nthlayer_workers/observe/sqlite_store.py,sha256=n7U-Tqi3beGMUVBB_6c4ih_jVPwYYDBwliCF41cMcqM,5505
|
|
94
|
+
nthlayer_workers/observe/store.py,sha256=h0Gwe9T--BeT7AlUHYSkXMlDSJG1VNCQiaZkmt41kWU,3139
|
|
95
|
+
nthlayer_workers/observe/worker.py,sha256=EmtbLaNxTxtezBCc1t2OzjsGwfy01ZR5nNwsgonu7j0,12242
|
|
96
|
+
nthlayer_workers/observe/api/__init__.py,sha256=HI6olQxSLa1752Vw-I4R-DcOID80JRnGTSRC37l-yLw,31
|
|
97
|
+
nthlayer_workers/observe/db/__init__.py,sha256=1n8N9LPD5ioUIiN3jYkoyJPaUg51Y9lAUxDmimQgYnA,30
|
|
98
|
+
nthlayer_workers/observe/dependencies/__init__.py,sha256=8bUHQtpgQ6X7cbhAQ-6prn7n9T52zfvxuNFzoNbw8kE,474
|
|
99
|
+
nthlayer_workers/observe/dependencies/discovery.py,sha256=baiI9hJOgzYqBlfSZuS24hf9d8O3Fbgv0jshtZxwR9w,12420
|
|
100
|
+
nthlayer_workers/observe/dependencies/providers/__init__.py,sha256=9sPNgm4X3V_-NZhAo4tUdRp0TglTDwE6EzngZRehI48,1410
|
|
101
|
+
nthlayer_workers/observe/dependencies/providers/backstage.py,sha256=9TTS0MMVd84UCgDeSiBrsX0wnlleTi9f83JE34C0Apw,15113
|
|
102
|
+
nthlayer_workers/observe/dependencies/providers/base.py,sha256=R5blZkQeJBkbn7B7LKpufuCRk5GP2Kvhl4KrSTKg6SE,2407
|
|
103
|
+
nthlayer_workers/observe/dependencies/providers/consul.py,sha256=lYBgh33P1ghfkxtrsTHhq1v_LnUtlpG_gq5ycpaZ9cw,18265
|
|
104
|
+
nthlayer_workers/observe/dependencies/providers/etcd.py,sha256=nDz5BnqiuThdbhWXwPmG7epnD6FL8bEXkBy7Bb4Jg9c,10837
|
|
105
|
+
nthlayer_workers/observe/dependencies/providers/kubernetes.py,sha256=B227RXtw2JZ_sFzPbBBDinQHSEXadvE_6HaUMCLQMuE,27345
|
|
106
|
+
nthlayer_workers/observe/dependencies/providers/prometheus.py,sha256=Fp_fKUsA0xlfjE32v4rhUO1vfmdOjOEuV7_d_zmBkWQ,13688
|
|
107
|
+
nthlayer_workers/observe/dependencies/providers/zookeeper.py,sha256=FWMhMUYpz8OqdRPPIEhSKxjWcGfog2AKrRgw8GwSflQ,13135
|
|
108
|
+
nthlayer_workers/observe/deployments/__init__.py,sha256=LW3Te70cpTmW1EyhX4CGM9su9JaCSP3FgE3WbyH9IQI,33
|
|
109
|
+
nthlayer_workers/observe/discovery/__init__.py,sha256=knvYRf6n5JSl3dnZQLzDT4ov4XziX-nT8KHlGcrShsg,461
|
|
110
|
+
nthlayer_workers/observe/discovery/classifier.py,sha256=4FfWL5PLxjswCpvWh3QhVceR3O3kY0hNuACeSn1x4yw,2463
|
|
111
|
+
nthlayer_workers/observe/discovery/client.py,sha256=1FLJP5CQBfXfrZkGQ3hB4GxZtarp6fsaK_5Cexvo6o4,6837
|
|
112
|
+
nthlayer_workers/observe/discovery/models.py,sha256=yJRv2LRKhv18k_FyDnxiQo8mRffxsrF2JzvypNiOnUw,1313
|
|
113
|
+
nthlayer_workers/observe/drift/__init__.py,sha256=L5eDT8NepE-ZYEFO2vZ7By_mEAWfT30hIyMr5iiU3ew,616
|
|
114
|
+
nthlayer_workers/observe/drift/analyzer.py,sha256=BOcvmLUaqlSJlDyhZT-WVHbrHKyfF0jGhYQ74dPrnVM,12844
|
|
115
|
+
nthlayer_workers/observe/drift/models.py,sha256=S5H2BE87jJX8BGDNcUb-GryMXzWIKFeeVXxxykmoOiA,5039
|
|
116
|
+
nthlayer_workers/observe/drift/patterns.py,sha256=EL89Sdy_rP101XQJHI2sblhW1juIjz5suGMCR281uAI,2737
|
|
117
|
+
nthlayer_workers/observe/gate/__init__.py,sha256=WhJiEIPcAKCdXCVZtQoKEmAK7Yk3HBaK9ikJsPUUXsg,843
|
|
118
|
+
nthlayer_workers/observe/gate/conditions.py,sha256=-7ZmfPQ7QZxjy2f4-_3vRPyo9MVQTcNqzxezqGm-5oo,2577
|
|
119
|
+
nthlayer_workers/observe/gate/correlator.py,sha256=IgqF0m6gXLs-pakEA7zL5CCuyrbep36Tj7qQciUOhuw,5401
|
|
120
|
+
nthlayer_workers/observe/gate/evaluator.py,sha256=cE-eHtCMc-8dlSAIlc9Nq5dSq9a8ARPEDq4CcPX09sY,6401
|
|
121
|
+
nthlayer_workers/observe/gate/policies.py,sha256=1ApH2IuA6cnufTIS9qufgiJj8GSix9sZnEsYdDd5p3E,7118
|
|
122
|
+
nthlayer_workers/observe/portfolio/__init__.py,sha256=7l-H1eH-J9H86bC4z_JXzzdVpxqF2tRCxNPAeoohY2w,365
|
|
123
|
+
nthlayer_workers/observe/portfolio/aggregator.py,sha256=6LXLiL8zOjTc8qDTHkyGXnyLoljgKk0h7Mnjo70uQ5w,5377
|
|
124
|
+
nthlayer_workers/observe/portfolio/scorer.py,sha256=NdfMLqKNhJm6AhTjk8aOpP3Dakbm-sG2JAXB_ic5zEU,435
|
|
125
|
+
nthlayer_workers/observe/slo/__init__.py,sha256=Iff4tQlh9k63nHHGZNnWma297ODTU9PSw68RrOKMVV4,407
|
|
126
|
+
nthlayer_workers/observe/slo/collector.py,sha256=3HZ4vPfCAcf1F975TjUZYNZfoIH7gJSBS2o0sVjptG4,7719
|
|
127
|
+
nthlayer_workers/observe/slo/spec_loader.py,sha256=VxIeN9hEsbrZ7eWwPDopH7nqb60JApkFOvXWkaL-Z-U,1295
|
|
128
|
+
nthlayer_workers/observe/verification/__init__.py,sha256=WDp0pob_FyFwQhoFsMYi8PNpFsWX4NsB1Kg79mgamqY,589
|
|
129
|
+
nthlayer_workers/observe/verification/exporter_guidance.py,sha256=u-BTbrHPkayIhHohpcfHSuQ7dUZXH2dGoxmvd1cZgS4,5311
|
|
130
|
+
nthlayer_workers/observe/verification/extractor.py,sha256=epbdb9SB7U9rQxWH2UaCfYdxejTZThVDQugyPAtLN7I,4467
|
|
131
|
+
nthlayer_workers/observe/verification/models.py,sha256=wEbUqnwKXhUkQUhtAPnXex55fQIBojcZHQvpWaDnSVU,2681
|
|
132
|
+
nthlayer_workers/observe/verification/verifier.py,sha256=PyribgvKxC1bmburP60zR1ScwHL3_GpSKHmeLHsFsdI,4055
|
|
133
|
+
nthlayer_workers/respond/__init__.py,sha256=0ViOuEq15AOVcM82sk7ojbe1yIip0_y1aIiuXFADeyg,72
|
|
134
|
+
nthlayer_workers/respond/__main__.py,sha256=na_BKmfOvUV455ULu2TPQ2dEAOFTmMXWchi5BoJcHLQ,104
|
|
135
|
+
nthlayer_workers/respond/cli.py,sha256=qGDtkG9HiuO36w7botXokPtobVJfXR2BF3COLli1U4I,30914
|
|
136
|
+
nthlayer_workers/respond/config.py,sha256=73iK4C8ief-FpporEIEYyvaQ6ktD3JYpDzRizppFCJM,6042
|
|
137
|
+
nthlayer_workers/respond/context_store.py,sha256=S546cXY6AqJ_PE8n5rP-5vD06zoEdbt8YfPKTuj4Yas,9863
|
|
138
|
+
nthlayer_workers/respond/coordinator.py,sha256=wPhZsmaAveMkZiVc-iSbfBhokn-b6P-5oPEKUE4__NQ,19245
|
|
139
|
+
nthlayer_workers/respond/metrics.py,sha256=yZ8B70YeO1XiBrKHILqny8OHmR6YfjConaFn7M2GfEo,4140
|
|
140
|
+
nthlayer_workers/respond/notifications.py,sha256=bPZQywSUVWqvxF2GNwCcvjCVe7luHhlO1cM2GPkyGgI,9143
|
|
141
|
+
nthlayer_workers/respond/server.py,sha256=e0zI664RfSYMWJAPVKll2QlifL4uMzybDI96gfiXo8U,13967
|
|
142
|
+
nthlayer_workers/respond/types.py,sha256=lYa3b_OAJMkN_ZhRCJ0scibCqXRTZ5dNQ8rdn2FqdxQ,2807
|
|
143
|
+
nthlayer_workers/respond/verdict_submission.py,sha256=5PnMOFRzKOpIvr5ua1qu3oA_a-TohEmKjkjQ1Lzz6Rw,2139
|
|
144
|
+
nthlayer_workers/respond/worker.py,sha256=D0VA29pD9MR9Mfnp0xifnMIfHq3ysSa6wagtbQ4m-8s,23330
|
|
145
|
+
nthlayer_workers/respond/worker_helpers.py,sha256=uwqqSDPvwdCgL3cTGI0AAval7O0cLNzJwq7yPSa2PBA,5372
|
|
146
|
+
nthlayer_workers/respond/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
147
|
+
nthlayer_workers/respond/agents/base.py,sha256=pQdn44EATTT5lim4oyQdmZI5Mxr2DzhWS0HR37rdxQk,23878
|
|
148
|
+
nthlayer_workers/respond/agents/communication.py,sha256=4Rr8JeSNp28ySoO7w-PyxLFrQO0dSKSbcnduyg_DS1E,4460
|
|
149
|
+
nthlayer_workers/respond/agents/investigation.py,sha256=cnNQBOKl7YS7nLMlLnXp1vhROU6GacB1MTNmkV-6iiw,4899
|
|
150
|
+
nthlayer_workers/respond/agents/remediation.py,sha256=GJJp_49znw9-VrMo9V10fq7hlDj1MetMALt0gx8MZrA,9148
|
|
151
|
+
nthlayer_workers/respond/agents/triage.py,sha256=7PcYSuRzPJS5kAc5vgGXCVeQm---7XpKbEj6SsFbEQs,5316
|
|
152
|
+
nthlayer_workers/respond/notification_backends/__init__.py,sha256=wTHrhO22FCS65n17FQr6Z_tm9zc5VYvHnlpMhju9LK8,61
|
|
153
|
+
nthlayer_workers/respond/notification_backends/ntfy_backend.py,sha256=zTPi-RZfKaSuy1cSc8s5-zExLZrTWS4iOlZgkgfcsb8,5060
|
|
154
|
+
nthlayer_workers/respond/notification_backends/protocol.py,sha256=wPbY8VSq85GFF_Ylt_67NtGpHXqlVO3MMaL3gYi1Y6Q,1554
|
|
155
|
+
nthlayer_workers/respond/notification_backends/slack_backend.py,sha256=NzTRrgCVRhRyOedYXlxpubjyo9C2HwXaarTYXvSBJBU,6258
|
|
156
|
+
nthlayer_workers/respond/notification_backends/stdout_backend.py,sha256=-5v9X3BTIh3NccCpYXz4Y-NHL2r3lC7tCLYaTFJPbF8,1846
|
|
157
|
+
nthlayer_workers/respond/oncall/__init__.py,sha256=VYyqTvUNjteDIC0uIiYUT4AOvFIYcsmMRJwsLup5fo8,57
|
|
158
|
+
nthlayer_workers/respond/oncall/escalation.py,sha256=f7O4KI1YPPatjA-0ZRfHgcIX2_VcIo37yUmBAqDwd64,3412
|
|
159
|
+
nthlayer_workers/respond/oncall/runner.py,sha256=KPMnH8i5qtW-uX0W6wzW2QXxgDNtArttE3mi93RybLk,6565
|
|
160
|
+
nthlayer_workers/respond/oncall/schedule.py,sha256=nlykurmSF-dBvRiuOcPdi2G_sM3mmVcQRGnCs7qyx3Y,8100
|
|
161
|
+
nthlayer_workers/respond/safe_actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
162
|
+
nthlayer_workers/respond/safe_actions/actions.py,sha256=aSaSHAXaCMbNdY80JMOYCbl25SNxtHIODnWIKW2LOkg,5599
|
|
163
|
+
nthlayer_workers/respond/safe_actions/registry.py,sha256=T3Y6ckX245H4qISyhiZe78LPuecDENU_rMx24-_Pjo4,6415
|
|
164
|
+
nthlayer_workers/respond/safe_actions/webhook.py,sha256=K9i6KniGrz3Z4Fpftvlc-tIFT4-Q61LVfPxV2onVNbM,7038
|
|
165
|
+
nthlayer_workers/respond/sre/__init__.py,sha256=hHiGy9eoyeM-r_TXZEoGuwvNDHahyrqZrHJu4Bvgp_4,59
|
|
166
|
+
nthlayer_workers/respond/sre/brief.py,sha256=yO6TrVBmNFxcBgs0ibSSUc_rsFuBvWADJSbHboB5N-I,5563
|
|
167
|
+
nthlayer_workers/respond/sre/delegation.py,sha256=CqrGhnOCG78CN0Gt-9ArSW4mcOldQMVIPmmRRY3YNaQ,2940
|
|
168
|
+
nthlayer_workers/respond/sre/post_incident.py,sha256=FbGy4uziU6MMikFv1a4Aw5J-UaG1MUKxCdG5Iy38FFg,4458
|
|
169
|
+
nthlayer_workers/respond/sre/shift_report.py,sha256=ebkgf6YD7Ci-Qgd_SBkofzZQJEAZ9HhJU-VVovwt48o,3875
|
|
170
|
+
nthlayer_workers/respond/sre/suppression.py,sha256=cZuPZNdE_w5Qcmkap-5oFYpZtTJRTiG4zwn9B9WEwPA,2563
|
|
171
|
+
nthlayer_workers-1.0.0.dist-info/METADATA,sha256=9YhhvBW26gbBpb3rh0343w3nkFepIvnrs5T8CBVln-I,668
|
|
172
|
+
nthlayer_workers-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
173
|
+
nthlayer_workers-1.0.0.dist-info/entry_points.txt,sha256=2JkWFZ-tg8UxsKZVtw8QQHtjw1ma8Os9YumjsqjGIag,63
|
|
174
|
+
nthlayer_workers-1.0.0.dist-info/top_level.txt,sha256=858CaamV5DuS0_myaDLIr8KbG8naVnuh2VMyS3PrIUg,17
|
|
175
|
+
nthlayer_workers-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nthlayer_workers
|