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.
Files changed (175) hide show
  1. nthlayer_workers/__init__.py +5 -0
  2. nthlayer_workers/cli.py +234 -0
  3. nthlayer_workers/correlate/__init__.py +1 -0
  4. nthlayer_workers/correlate/cli.py +847 -0
  5. nthlayer_workers/correlate/config.py +111 -0
  6. nthlayer_workers/correlate/correlation/__init__.py +1 -0
  7. nthlayer_workers/correlate/correlation/changes.py +87 -0
  8. nthlayer_workers/correlate/correlation/dedup.py +62 -0
  9. nthlayer_workers/correlate/correlation/engine.py +244 -0
  10. nthlayer_workers/correlate/correlation/temporal.py +79 -0
  11. nthlayer_workers/correlate/correlation/topology.py +104 -0
  12. nthlayer_workers/correlate/ingestion/__init__.py +1 -0
  13. nthlayer_workers/correlate/ingestion/protocol.py +10 -0
  14. nthlayer_workers/correlate/ingestion/severity.py +18 -0
  15. nthlayer_workers/correlate/ingestion/webhook.py +197 -0
  16. nthlayer_workers/correlate/notifications.py +85 -0
  17. nthlayer_workers/correlate/prometheus.py +234 -0
  18. nthlayer_workers/correlate/reasoning.py +375 -0
  19. nthlayer_workers/correlate/session.py +189 -0
  20. nthlayer_workers/correlate/snapshot/__init__.py +1 -0
  21. nthlayer_workers/correlate/snapshot/generator.py +170 -0
  22. nthlayer_workers/correlate/snapshot/model.py +177 -0
  23. nthlayer_workers/correlate/snapshot/token.py +14 -0
  24. nthlayer_workers/correlate/state.py +88 -0
  25. nthlayer_workers/correlate/store/__init__.py +5 -0
  26. nthlayer_workers/correlate/store/protocol.py +48 -0
  27. nthlayer_workers/correlate/store/sqlite.py +443 -0
  28. nthlayer_workers/correlate/summary.py +180 -0
  29. nthlayer_workers/correlate/traces/__init__.py +1 -0
  30. nthlayer_workers/correlate/traces/protocol.py +120 -0
  31. nthlayer_workers/correlate/traces/tempo.py +667 -0
  32. nthlayer_workers/correlate/traces/topology.py +39 -0
  33. nthlayer_workers/correlate/types.py +77 -0
  34. nthlayer_workers/correlate/worker.py +630 -0
  35. nthlayer_workers/learn/__init__.py +5 -0
  36. nthlayer_workers/learn/__main__.py +5 -0
  37. nthlayer_workers/learn/cli.py +164 -0
  38. nthlayer_workers/learn/retrospective.py +381 -0
  39. nthlayer_workers/learn/trends.py +102 -0
  40. nthlayer_workers/learn/worker.py +366 -0
  41. nthlayer_workers/measure/__init__.py +3 -0
  42. nthlayer_workers/measure/__main__.py +5 -0
  43. nthlayer_workers/measure/_parsing.py +15 -0
  44. nthlayer_workers/measure/adapters/__init__.py +0 -0
  45. nthlayer_workers/measure/adapters/_util.py +24 -0
  46. nthlayer_workers/measure/adapters/devin.py +119 -0
  47. nthlayer_workers/measure/adapters/gastown.py +88 -0
  48. nthlayer_workers/measure/adapters/prometheus.py +277 -0
  49. nthlayer_workers/measure/adapters/protocol.py +20 -0
  50. nthlayer_workers/measure/adapters/webhook.py +161 -0
  51. nthlayer_workers/measure/api/__init__.py +0 -0
  52. nthlayer_workers/measure/api/normalise.py +50 -0
  53. nthlayer_workers/measure/api/queue.py +243 -0
  54. nthlayer_workers/measure/api/response.py +51 -0
  55. nthlayer_workers/measure/api/server.py +504 -0
  56. nthlayer_workers/measure/calibration/__init__.py +0 -0
  57. nthlayer_workers/measure/calibration/loop.py +62 -0
  58. nthlayer_workers/measure/calibration/slos.py +212 -0
  59. nthlayer_workers/measure/calibration/verdict_calibration.py +31 -0
  60. nthlayer_workers/measure/cli.py +753 -0
  61. nthlayer_workers/measure/config.py +191 -0
  62. nthlayer_workers/measure/detection/__init__.py +6 -0
  63. nthlayer_workers/measure/detection/detector.py +82 -0
  64. nthlayer_workers/measure/detection/protocol.py +29 -0
  65. nthlayer_workers/measure/governance/__init__.py +0 -0
  66. nthlayer_workers/measure/governance/engine.py +163 -0
  67. nthlayer_workers/measure/manifest.py +77 -0
  68. nthlayer_workers/measure/notifications.py +53 -0
  69. nthlayer_workers/measure/pipeline/__init__.py +0 -0
  70. nthlayer_workers/measure/pipeline/evaluator.py +155 -0
  71. nthlayer_workers/measure/pipeline/router.py +160 -0
  72. nthlayer_workers/measure/store/__init__.py +0 -0
  73. nthlayer_workers/measure/store/protocol.py +38 -0
  74. nthlayer_workers/measure/store/sqlite.py +276 -0
  75. nthlayer_workers/measure/telemetry.py +116 -0
  76. nthlayer_workers/measure/tiering/__init__.py +0 -0
  77. nthlayer_workers/measure/tiering/classifier.py +58 -0
  78. nthlayer_workers/measure/tiering/promotion.py +118 -0
  79. nthlayer_workers/measure/trends/__init__.py +0 -0
  80. nthlayer_workers/measure/trends/tracker.py +72 -0
  81. nthlayer_workers/measure/types.py +75 -0
  82. nthlayer_workers/measure/worker.py +439 -0
  83. nthlayer_workers/observe/__init__.py +25 -0
  84. nthlayer_workers/observe/__main__.py +5 -0
  85. nthlayer_workers/observe/api/__init__.py +1 -0
  86. nthlayer_workers/observe/assessment.py +95 -0
  87. nthlayer_workers/observe/cli.py +737 -0
  88. nthlayer_workers/observe/config.py +11 -0
  89. nthlayer_workers/observe/db/__init__.py +1 -0
  90. nthlayer_workers/observe/decision_records.py +220 -0
  91. nthlayer_workers/observe/dependencies/__init__.py +18 -0
  92. nthlayer_workers/observe/dependencies/discovery.py +294 -0
  93. nthlayer_workers/observe/dependencies/providers/__init__.py +48 -0
  94. nthlayer_workers/observe/dependencies/providers/backstage.py +467 -0
  95. nthlayer_workers/observe/dependencies/providers/base.py +76 -0
  96. nthlayer_workers/observe/dependencies/providers/consul.py +518 -0
  97. nthlayer_workers/observe/dependencies/providers/etcd.py +360 -0
  98. nthlayer_workers/observe/dependencies/providers/kubernetes.py +682 -0
  99. nthlayer_workers/observe/dependencies/providers/prometheus.py +368 -0
  100. nthlayer_workers/observe/dependencies/providers/zookeeper.py +399 -0
  101. nthlayer_workers/observe/deployments/__init__.py +1 -0
  102. nthlayer_workers/observe/discovery/__init__.py +14 -0
  103. nthlayer_workers/observe/discovery/classifier.py +66 -0
  104. nthlayer_workers/observe/discovery/client.py +189 -0
  105. nthlayer_workers/observe/discovery/models.py +53 -0
  106. nthlayer_workers/observe/drift/__init__.py +26 -0
  107. nthlayer_workers/observe/drift/analyzer.py +383 -0
  108. nthlayer_workers/observe/drift/models.py +174 -0
  109. nthlayer_workers/observe/drift/patterns.py +88 -0
  110. nthlayer_workers/observe/explanation.py +118 -0
  111. nthlayer_workers/observe/gate/__init__.py +39 -0
  112. nthlayer_workers/observe/gate/conditions.py +92 -0
  113. nthlayer_workers/observe/gate/correlator.py +154 -0
  114. nthlayer_workers/observe/gate/evaluator.py +192 -0
  115. nthlayer_workers/observe/gate/policies.py +226 -0
  116. nthlayer_workers/observe/gate_adapter.py +40 -0
  117. nthlayer_workers/observe/incident.py +36 -0
  118. nthlayer_workers/observe/portfolio/__init__.py +17 -0
  119. nthlayer_workers/observe/portfolio/aggregator.py +168 -0
  120. nthlayer_workers/observe/portfolio/scorer.py +13 -0
  121. nthlayer_workers/observe/slo/__init__.py +19 -0
  122. nthlayer_workers/observe/slo/collector.py +235 -0
  123. nthlayer_workers/observe/slo/spec_loader.py +40 -0
  124. nthlayer_workers/observe/sqlite_store.py +152 -0
  125. nthlayer_workers/observe/store.py +92 -0
  126. nthlayer_workers/observe/verification/__init__.py +22 -0
  127. nthlayer_workers/observe/verification/exporter_guidance.py +146 -0
  128. nthlayer_workers/observe/verification/extractor.py +127 -0
  129. nthlayer_workers/observe/verification/models.py +101 -0
  130. nthlayer_workers/observe/verification/verifier.py +111 -0
  131. nthlayer_workers/observe/worker.py +332 -0
  132. nthlayer_workers/respond/__init__.py +2 -0
  133. nthlayer_workers/respond/__main__.py +4 -0
  134. nthlayer_workers/respond/agents/__init__.py +0 -0
  135. nthlayer_workers/respond/agents/base.py +556 -0
  136. nthlayer_workers/respond/agents/communication.py +115 -0
  137. nthlayer_workers/respond/agents/investigation.py +124 -0
  138. nthlayer_workers/respond/agents/remediation.py +219 -0
  139. nthlayer_workers/respond/agents/triage.py +132 -0
  140. nthlayer_workers/respond/cli.py +772 -0
  141. nthlayer_workers/respond/config.py +135 -0
  142. nthlayer_workers/respond/context_store.py +256 -0
  143. nthlayer_workers/respond/coordinator.py +487 -0
  144. nthlayer_workers/respond/metrics.py +104 -0
  145. nthlayer_workers/respond/notification_backends/__init__.py +1 -0
  146. nthlayer_workers/respond/notification_backends/ntfy_backend.py +158 -0
  147. nthlayer_workers/respond/notification_backends/protocol.py +59 -0
  148. nthlayer_workers/respond/notification_backends/slack_backend.py +203 -0
  149. nthlayer_workers/respond/notification_backends/stdout_backend.py +56 -0
  150. nthlayer_workers/respond/notifications.py +247 -0
  151. nthlayer_workers/respond/oncall/__init__.py +1 -0
  152. nthlayer_workers/respond/oncall/escalation.py +103 -0
  153. nthlayer_workers/respond/oncall/runner.py +193 -0
  154. nthlayer_workers/respond/oncall/schedule.py +243 -0
  155. nthlayer_workers/respond/safe_actions/__init__.py +0 -0
  156. nthlayer_workers/respond/safe_actions/actions.py +139 -0
  157. nthlayer_workers/respond/safe_actions/registry.py +171 -0
  158. nthlayer_workers/respond/safe_actions/webhook.py +194 -0
  159. nthlayer_workers/respond/server.py +357 -0
  160. nthlayer_workers/respond/sre/__init__.py +1 -0
  161. nthlayer_workers/respond/sre/brief.py +175 -0
  162. nthlayer_workers/respond/sre/delegation.py +101 -0
  163. nthlayer_workers/respond/sre/post_incident.py +146 -0
  164. nthlayer_workers/respond/sre/shift_report.py +129 -0
  165. nthlayer_workers/respond/sre/suppression.py +91 -0
  166. nthlayer_workers/respond/types.py +109 -0
  167. nthlayer_workers/respond/verdict_submission.py +56 -0
  168. nthlayer_workers/respond/worker.py +533 -0
  169. nthlayer_workers/respond/worker_helpers.py +140 -0
  170. nthlayer_workers/runner.py +198 -0
  171. nthlayer_workers-1.0.0.dist-info/METADATA +19 -0
  172. nthlayer_workers-1.0.0.dist-info/RECORD +175 -0
  173. nthlayer_workers-1.0.0.dist-info/WHEEL +5 -0
  174. nthlayer_workers-1.0.0.dist-info/entry_points.txt +2 -0
  175. 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ nthlayer-workers = nthlayer_workers.cli:main
@@ -0,0 +1 @@
1
+ nthlayer_workers