nthlayer-workers 1.6.0__tar.gz → 2.0.0__tar.gz
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-1.6.0 → nthlayer_workers-2.0.0}/PKG-INFO +4 -2
- nthlayer_workers-2.0.0/pyproject.toml +84 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/__init__.py +4 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/cli.py +3 -1
- nthlayer_workers-2.0.0/src/nthlayer_workers/cli_output.py +26 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/cli.py +11 -10
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/changes.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/engine.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/temporal.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/ingestion/protocol.py +5 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/ingestion/severity.py +1 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/ingestion/webhook.py +9 -12
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/prometheus.py +6 -4
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/session.py +7 -7
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/snapshot/generator.py +6 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/snapshot/model.py +4 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/state.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/traces/tempo.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/types.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/verdict_chain.py +1 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/worker.py +7 -8
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/learn/__main__.py +2 -1
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_apply.py +274 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_gh.py +163 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_interactive.py +162 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_interactive_app.py +229 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_preview.py +86 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_trigger.py +40 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/_yaml.py +286 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/cli.py +543 -0
- nthlayer_workers-2.0.0/src/nthlayer_workers/learn/recommendations.py +597 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/learn/retrospective.py +166 -44
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/learn/worker.py +84 -20
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/__main__.py +2 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/devin.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/gastown.py +1 -1
- nthlayer_workers-2.0.0/src/nthlayer_workers/measure/adapters/prometheus.py +483 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/webhook.py +6 -9
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/calibration/loop.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/calibration/slos.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/calibration/verdict_calibration.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/cli.py +51 -203
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/config.py +19 -11
- nthlayer_workers-2.0.0/src/nthlayer_workers/measure/governance/engine.py +31 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/pipeline/router.py +4 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/trends/tracker.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/types.py +6 -6
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/worker.py +4 -5
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/__main__.py +2 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/assessment.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/cli.py +16 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/decision_records.py +3 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/consul.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/etcd.py +3 -6
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/kubernetes.py +3 -5
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/zookeeper.py +1 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/discovery/__init__.py +6 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/discovery/client.py +6 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/discovery/models.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/drift/analyzer.py +9 -14
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/explanation.py +1 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate/evaluator.py +14 -19
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate/policies.py +2 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/incident.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/slo/collector.py +0 -1
- nthlayer_workers-2.0.0/src/nthlayer_workers/observe/slo/spec_loader.py +91 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/sqlite_store.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/verification/extractor.py +5 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/worker.py +1 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/__main__.py +2 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/base.py +7 -5
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/communication.py +3 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/investigation.py +1 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/remediation.py +2 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/cli.py +21 -16
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/coordinator.py +15 -13
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/metrics.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notification_backends/ntfy_backend.py +4 -4
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notification_backends/slack_backend.py +5 -5
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notification_backends/stdout_backend.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/oncall/escalation.py +0 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/oncall/runner.py +6 -6
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/safe_actions/registry.py +5 -4
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/server.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/brief.py +2 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/delegation.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/suppression.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/types.py +3 -3
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/verdict_submission.py +0 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/worker.py +5 -6
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/worker_helpers.py +4 -4
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/runner.py +0 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/PKG-INFO +4 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/SOURCES.txt +8 -5
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/entry_points.txt +1 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/requires.txt +3 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/tests/test_cli_gate.py +0 -1
- nthlayer_workers-1.6.0/pyproject.toml +0 -47
- nthlayer_workers-1.6.0/src/nthlayer_workers/learn/cli.py +0 -164
- nthlayer_workers-1.6.0/src/nthlayer_workers/learn/recommendations.py +0 -313
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/adapters/prometheus.py +0 -277
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/api/normalise.py +0 -50
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/api/queue.py +0 -243
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/api/response.py +0 -51
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/api/server.py +0 -504
- nthlayer_workers-1.6.0/src/nthlayer_workers/measure/governance/engine.py +0 -163
- nthlayer_workers-1.6.0/src/nthlayer_workers/observe/slo/spec_loader.py +0 -40
- nthlayer_workers-1.6.0/src/nthlayer_workers/respond/safe_actions/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/README.md +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/setup.cfg +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/config.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/dedup.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/correlation/topology.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/ingestion/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/notifications.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/reasoning.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/snapshot/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/snapshot/token.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/store/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/store/protocol.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/store/sqlite.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/summary.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/traces/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/traces/protocol.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/traces/topology.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/learn/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/learn/trends.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/_parsing.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/_util.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/adapters/protocol.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/api → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/calibration}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/detection/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/detection/detector.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/detection/protocol.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/calibration → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/governance}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/manifest.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/notifications.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/governance → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/pipeline}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/pipeline/evaluator.py +2 -2
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/pipeline → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/store}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/store/protocol.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/store/sqlite.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/telemetry.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/store → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/tiering}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/tiering/classifier.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/measure/tiering/promotion.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/tiering → nthlayer_workers-2.0.0/src/nthlayer_workers/measure/trends}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/api/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/config.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/db/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/__init__.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/discovery.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/backstage.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/base.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/dependencies/providers/prometheus.py +2 -2
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/deployments/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/discovery/classifier.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/drift/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/drift/models.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/drift/patterns.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate/conditions.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate/correlator.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/gate_adapter.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/portfolio/__init__.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/portfolio/aggregator.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/portfolio/scorer.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/slo/__init__.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/store.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/verification/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/verification/exporter_guidance.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/verification/models.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/observe/verification/verifier.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/__init__.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/measure/trends → nthlayer_workers-2.0.0/src/nthlayer_workers/respond/agents}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/response_models.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/agents/triage.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/config.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/context_store.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notification_backends/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notification_backends/protocol.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/notifications.py +1 -1
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/oncall/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/oncall/schedule.py +0 -0
- {nthlayer_workers-1.6.0/src/nthlayer_workers/respond/agents → nthlayer_workers-2.0.0/src/nthlayer_workers/respond/safe_actions}/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/safe_actions/actions.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/safe_actions/webhook.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/__init__.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/post_incident.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/respond/sre/shift_report.py +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/dependency_links.txt +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers.egg-info/top_level.txt +0 -0
- {nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/tests/test_runner.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nthlayer-workers
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: NthLayer workers — Tier 2 background computation (observe, measure, correlate, respond, learn)
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: nthlayer-common<
|
|
7
|
+
Requires-Dist: nthlayer-common<3.0.0,>=2.1.2
|
|
8
8
|
Requires-Dist: httpx>=0.27
|
|
9
9
|
Requires-Dist: pyyaml>=6.0
|
|
10
10
|
Requires-Dist: structlog>=24.1.0
|
|
@@ -13,6 +13,8 @@ Requires-Dist: numpy>=1.24
|
|
|
13
13
|
Requires-Dist: starlette>=0.40
|
|
14
14
|
Requires-Dist: uvicorn>=0.30
|
|
15
15
|
Requires-Dist: python-multipart>=0.0.18
|
|
16
|
+
Requires-Dist: ruamel.yaml>=0.18
|
|
17
|
+
Requires-Dist: textual>=1.0
|
|
16
18
|
Provides-Extra: dev
|
|
17
19
|
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
18
20
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nthlayer-workers"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "NthLayer workers — Tier 2 background computation (observe, measure, correlate, respond, learn)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
# >=2.1.2, NOT >=2.1.0 or 2.1.1. 2.1.0 introduced foreign_yaml_reason with an
|
|
13
|
+
# INERT body-recovery step: it tested spec.slos as a list, but v1 writes
|
|
14
|
+
# a mapping and v2 has no spec.slos at all, so a header-stripped manifest
|
|
15
|
+
# was dropped with parse_failures == 0 — the exact silent drop this
|
|
16
|
+
# package uses the gate to prevent. Fixed across 2.1.1 and 2.1.2;
|
|
17
|
+
# 2.1.1 still counted every Backstage catalog-info.yaml as a broken
|
|
18
|
+
# manifest, which 2.1.2 corrected.
|
|
19
|
+
#
|
|
20
|
+
# CI resolves nthlayer-common from a sibling checkout, so it would not
|
|
21
|
+
# notice a bad resolve. tests/observe/test_slo_spec_loader.py's
|
|
22
|
+
# TestSharedGateResolvesCorrectly exists for that: it uses the shapes the
|
|
23
|
+
# PARSERS produce and fails against 2.1.0.
|
|
24
|
+
"nthlayer-common>=2.1.2,<3.0.0",
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"pyyaml>=6.0",
|
|
27
|
+
"structlog>=24.1.0",
|
|
28
|
+
"scipy>=1.11",
|
|
29
|
+
"numpy>=1.24",
|
|
30
|
+
"starlette>=0.40",
|
|
31
|
+
"uvicorn>=0.30",
|
|
32
|
+
# Required by ApprovalServer's Slack interaction endpoint at
|
|
33
|
+
# `respond/server.py:197` — `await request.form()` hard-imports
|
|
34
|
+
# python-multipart via Starlette. Operators running with
|
|
35
|
+
# `slack_signing_secret` set need this at runtime, not just for
|
|
36
|
+
# tests; declared as a top-level production dep.
|
|
37
|
+
"python-multipart>=0.0.18",
|
|
38
|
+
"ruamel.yaml>=0.18",
|
|
39
|
+
"textual>=1.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
nthlayer-workers = "nthlayer_workers.cli:main"
|
|
44
|
+
nthlayer-learn = "nthlayer_workers.learn.cli:main"
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=8.2",
|
|
49
|
+
"pytest-asyncio>=0.23",
|
|
50
|
+
"respx>=0.21",
|
|
51
|
+
"ruff>=0.8",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.uv.sources]
|
|
55
|
+
nthlayer-common = { path = "../nthlayer-common", editable = true }
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["src"]
|
|
59
|
+
|
|
60
|
+
[tool.pytest.ini_options]
|
|
61
|
+
asyncio_mode = "auto"
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
|
|
64
|
+
# Ruff configuration (opensrm-d96q floor). Explicit defaults so future
|
|
65
|
+
# violations show up as new findings rather than blending in. Tightening
|
|
66
|
+
# beyond E/F (isort/pyupgrade/simplify/bugbear) is tracked in
|
|
67
|
+
# opensrm-d96q.1 .. d96q.4 — each rule family is its own bead because
|
|
68
|
+
# the cumulative diff across all four is ~640 findings.
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
target-version = "py311"
|
|
71
|
+
line-length = 100
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
# Mirrors ruff's literal default select (E4/E7/E9 + F) plus I (isort,
|
|
75
|
+
# opensrm-ncy0), UP (pyupgrade, opensrm-6nn1), SIM (simplify,
|
|
76
|
+
# opensrm-y7dd), and B (bugbear, opensrm-po23). E501 / W are separate
|
|
77
|
+
# hygiene calls (line-too-long has 393 hits today).
|
|
78
|
+
select = ["E4", "E7", "E9", "F", "I", "UP", "SIM", "B"]
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.per-file-ignores]
|
|
81
|
+
# Correlate test files defer some imports until after pytest.importorskip
|
|
82
|
+
# / pytestmark blocks (the modules under test optional-import heavy deps).
|
|
83
|
+
# Pin E402 here instead of scattering noqa: E402 across the call sites.
|
|
84
|
+
"tests/correlate/test_*.py" = ["E402"]
|
|
@@ -4,12 +4,13 @@ import argparse
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
+
from nthlayer_workers import __version__
|
|
7
8
|
from nthlayer_workers.runner import ModuleRunner
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def main():
|
|
11
12
|
parser = argparse.ArgumentParser(description="NthLayer workers")
|
|
12
|
-
parser.add_argument("-V", "--version", action="version", version="%(prog)s
|
|
13
|
+
parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
13
14
|
sub = parser.add_subparsers(dest="command")
|
|
14
15
|
|
|
15
16
|
# -- serve: start all worker modules --
|
|
@@ -60,6 +61,7 @@ def main():
|
|
|
60
61
|
|
|
61
62
|
if args.command == "serve":
|
|
62
63
|
from nthlayer_common.api_client import CoreAPIClient
|
|
64
|
+
|
|
63
65
|
from nthlayer_workers.observe.worker import (
|
|
64
66
|
ObserveCollectModule,
|
|
65
67
|
ObserveDriftModule,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Operator-facing CLI output shared across worker modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def warn_parse_failures(parse_failures: int, specs_dir: str | Path) -> None:
|
|
10
|
+
"""Tell the operator when the SLOs that follow cover only part of a directory.
|
|
11
|
+
|
|
12
|
+
Surfaced on stderr rather than only logged: whoever reads SLO output is
|
|
13
|
+
the one who needs to know the view is partial, and a warning in a log
|
|
14
|
+
they are not tailing does not reach them (opensrm-3470).
|
|
15
|
+
|
|
16
|
+
Takes the count, not a ``LoadedSpecs`` — observe and measure each have
|
|
17
|
+
their own, carrying differently-shaped SLOs under differently-named
|
|
18
|
+
fields (``service_slos`` vs ``slos``). The only thing they share is this
|
|
19
|
+
number, so that is the whole parameter (opensrm-fxln).
|
|
20
|
+
"""
|
|
21
|
+
if parse_failures:
|
|
22
|
+
print(
|
|
23
|
+
f"Warning: {parse_failures} manifest(s) in {specs_dir} "
|
|
24
|
+
f"failed to parse and were not evaluated",
|
|
25
|
+
file=sys.stderr,
|
|
26
|
+
)
|
|
@@ -9,7 +9,7 @@ import signal
|
|
|
9
9
|
import sys
|
|
10
10
|
import tempfile
|
|
11
11
|
from dataclasses import replace
|
|
12
|
-
from datetime import datetime, timedelta
|
|
12
|
+
from datetime import UTC, datetime, timedelta
|
|
13
13
|
from typing import Any
|
|
14
14
|
|
|
15
15
|
import structlog
|
|
@@ -30,7 +30,7 @@ from nthlayer_workers.correlate.types import AgentState, EventType, SitRepEvent
|
|
|
30
30
|
|
|
31
31
|
logger = structlog.get_logger()
|
|
32
32
|
|
|
33
|
-
REFERENCE_TIME = datetime(2026, 1, 1, tzinfo=
|
|
33
|
+
REFERENCE_TIME = datetime(2026, 1, 1, tzinfo=UTC)
|
|
34
34
|
|
|
35
35
|
# Confidence decay window for temporal proximity heuristic (30 minutes)
|
|
36
36
|
_PROXIMITY_WINDOW_SECONDS = 1800.0
|
|
@@ -245,10 +245,7 @@ def status_command(config_path: str | None, store_dir: str | None = None) -> int
|
|
|
245
245
|
config = load_config(config_path) if config_path else SitRepConfig()
|
|
246
246
|
|
|
247
247
|
# Use store_dir if provided (for testing), otherwise use config path
|
|
248
|
-
if store_dir
|
|
249
|
-
db_path = os.path.join(store_dir, "sitrep-events.db")
|
|
250
|
-
else:
|
|
251
|
-
db_path = config.store_path
|
|
248
|
+
db_path = os.path.join(store_dir, "sitrep-events.db") if store_dir else config.store_path
|
|
252
249
|
|
|
253
250
|
store = SQLiteEventStore(db_path)
|
|
254
251
|
try:
|
|
@@ -332,7 +329,7 @@ async def _serve_loop(config: SitRepConfig) -> None:
|
|
|
332
329
|
try:
|
|
333
330
|
await asyncio.wait_for(shutdown.wait(), timeout=interval)
|
|
334
331
|
break # shutdown requested
|
|
335
|
-
except
|
|
332
|
+
except TimeoutError:
|
|
336
333
|
pass # normal cycle
|
|
337
334
|
|
|
338
335
|
# Drain buffered events into store (single-threaded, safe)
|
|
@@ -481,7 +478,7 @@ def correlate_command(
|
|
|
481
478
|
if trace_backend is not None:
|
|
482
479
|
try:
|
|
483
480
|
baseline_td = _parse_duration(trace_baseline_window)
|
|
484
|
-
end = datetime.now(tz=
|
|
481
|
+
end = datetime.now(tz=UTC)
|
|
485
482
|
start = end - timedelta(minutes=30)
|
|
486
483
|
trace_evidence = await trace_backend.get_trace_evidence(
|
|
487
484
|
services=sorted(affected),
|
|
@@ -540,7 +537,10 @@ def correlate_command(
|
|
|
540
537
|
reasoning_mode = "heuristic"
|
|
541
538
|
|
|
542
539
|
if reasoning:
|
|
543
|
-
from nthlayer_workers.correlate.reasoning import
|
|
540
|
+
from nthlayer_workers.correlate.reasoning import (
|
|
541
|
+
reason_about_correlations,
|
|
542
|
+
reasoning_available,
|
|
543
|
+
)
|
|
544
544
|
|
|
545
545
|
if reasoning_available():
|
|
546
546
|
kwargs = {}
|
|
@@ -629,6 +629,7 @@ def correlate_command(
|
|
|
629
629
|
# canonical primitive). correlation_snapshot is in ASSESSMENT_KINDS;
|
|
630
630
|
# producing a "correlation"-typed verdict here was a category error.
|
|
631
631
|
import uuid as _uuid
|
|
632
|
+
|
|
632
633
|
from nthlayer_workers.observe.assessment import Assessment as _Assessment
|
|
633
634
|
from nthlayer_workers.observe.sqlite_store import SQLiteAssessmentStore
|
|
634
635
|
|
|
@@ -656,7 +657,7 @@ def correlate_command(
|
|
|
656
657
|
}
|
|
657
658
|
corr_assessment = _Assessment(
|
|
658
659
|
id=corr_assessment_id,
|
|
659
|
-
created_at=datetime.now(
|
|
660
|
+
created_at=datetime.now(UTC),
|
|
660
661
|
kind="correlation_snapshot",
|
|
661
662
|
service=trigger_service,
|
|
662
663
|
producer="nthlayer-correlate",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Change candidate indexing. Deterministic transport."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from datetime import
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
5
|
|
|
6
6
|
from nthlayer_workers.correlate.store.protocol import EventStore
|
|
7
7
|
from nthlayer_workers.correlate.types import ChangeCandidate, TemporalGroup
|
|
@@ -11,7 +11,7 @@ def _parse_ts(ts: str) -> datetime:
|
|
|
11
11
|
"""Parse ISO 8601 timestamp. Always returns timezone-aware (UTC default)."""
|
|
12
12
|
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
13
13
|
if dt.tzinfo is None:
|
|
14
|
-
dt = dt.replace(tzinfo=
|
|
14
|
+
dt = dt.replace(tzinfo=UTC)
|
|
15
15
|
return dt
|
|
16
16
|
|
|
17
17
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import uuid
|
|
5
5
|
from dataclasses import replace
|
|
6
|
-
from datetime import datetime, timedelta
|
|
6
|
+
from datetime import UTC, datetime, timedelta
|
|
7
7
|
|
|
8
8
|
from nthlayer_workers.correlate.correlation.changes import find_change_candidates
|
|
9
9
|
from nthlayer_workers.correlate.correlation.dedup import deduplicate
|
|
@@ -36,7 +36,7 @@ class CorrelationEngine:
|
|
|
36
36
|
7. Assemble CorrelationGroups with priority scoring
|
|
37
37
|
"""
|
|
38
38
|
# Step 1: Query store for events in window
|
|
39
|
-
now = datetime.now(
|
|
39
|
+
now = datetime.now(UTC)
|
|
40
40
|
start = (now - timedelta(minutes=window_minutes)).isoformat()
|
|
41
41
|
end = now.isoformat()
|
|
42
42
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Temporal grouping — windowed aggregation by service. Deterministic transport."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from datetime import
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
5
|
|
|
6
6
|
from nthlayer_workers.correlate.types import SitRepEvent, TemporalGroup
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ def _parse_ts(ts: str) -> datetime:
|
|
|
10
10
|
"""Parse ISO 8601 timestamp. Always returns timezone-aware (UTC default)."""
|
|
11
11
|
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
12
12
|
if dt.tzinfo is None:
|
|
13
|
-
dt = dt.replace(tzinfo=
|
|
13
|
+
dt = dt.replace(tzinfo=UTC)
|
|
14
14
|
return dt
|
|
15
15
|
|
|
16
16
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Ingester protocol — the ingestion contract for SitRep."""
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
4
7
|
from nthlayer_workers.correlate.types import SitRepEvent
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class Ingester(Protocol):
|
|
8
11
|
async def start(self) -> None: ...
|
|
9
12
|
async def stop(self) -> None: ...
|
|
10
|
-
def on_event(self, handler: Callable[[SitRepEvent],
|
|
13
|
+
def on_event(self, handler: Callable[[SitRepEvent], Awaitable[None] | None]) -> None: ...
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
|
+
import contextlib
|
|
5
6
|
import json
|
|
6
7
|
import uuid
|
|
8
|
+
from collections.abc import Awaitable, Callable
|
|
7
9
|
from dataclasses import replace
|
|
8
|
-
from datetime import
|
|
9
|
-
from typing import Callable, Awaitable
|
|
10
|
+
from datetime import UTC, datetime
|
|
10
11
|
|
|
11
12
|
from nthlayer_workers.correlate.ingestion import severity as _severity
|
|
12
13
|
from nthlayer_workers.correlate.types import EventType, SitRepEvent
|
|
@@ -65,12 +66,12 @@ class WebhookIngester:
|
|
|
65
66
|
raw_type = data["type"]
|
|
66
67
|
try:
|
|
67
68
|
event_type = EventType(raw_type)
|
|
68
|
-
except ValueError:
|
|
69
|
-
raise ValueError(f"Unknown event type: {raw_type!r}")
|
|
69
|
+
except ValueError as exc:
|
|
70
|
+
raise ValueError(f"Unknown event type: {raw_type!r}") from exc
|
|
70
71
|
|
|
71
72
|
# Auto-generate id and timestamp when absent
|
|
72
73
|
event_id = data.get("id") or str(uuid.uuid4())
|
|
73
|
-
timestamp = data.get("timestamp") or datetime.now(
|
|
74
|
+
timestamp = data.get("timestamp") or datetime.now(UTC).isoformat()
|
|
74
75
|
|
|
75
76
|
event = SitRepEvent(
|
|
76
77
|
id=event_id,
|
|
@@ -101,12 +102,10 @@ class WebhookIngester:
|
|
|
101
102
|
await asyncio.wait_for(
|
|
102
103
|
self._handle_request(reader, writer), timeout=_CONNECTION_TIMEOUT
|
|
103
104
|
)
|
|
104
|
-
except
|
|
105
|
+
except TimeoutError:
|
|
105
106
|
writer.close()
|
|
106
|
-
|
|
107
|
+
with contextlib.suppress(Exception):
|
|
107
108
|
await writer.wait_closed()
|
|
108
|
-
except Exception:
|
|
109
|
-
pass
|
|
110
109
|
|
|
111
110
|
async def _handle_request(
|
|
112
111
|
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
@@ -191,7 +190,5 @@ class WebhookIngester:
|
|
|
191
190
|
|
|
192
191
|
finally:
|
|
193
192
|
writer.close()
|
|
194
|
-
|
|
193
|
+
with contextlib.suppress(Exception):
|
|
195
194
|
await writer.wait_closed()
|
|
196
|
-
except Exception:
|
|
197
|
-
pass
|
{nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/prometheus.py
RENAMED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import uuid
|
|
5
|
-
from datetime import
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
|
|
6
7
|
import httpx
|
|
7
8
|
import structlog
|
|
8
9
|
|
|
@@ -31,7 +32,7 @@ async def fetch_alerts(
|
|
|
31
32
|
continue
|
|
32
33
|
events.append(SitRepEvent(
|
|
33
34
|
id=f"prom-alert-{uuid.uuid4().hex[:8]}",
|
|
34
|
-
timestamp=alert.get("activeAt", datetime.now(
|
|
35
|
+
timestamp=alert.get("activeAt", datetime.now(UTC).isoformat()),
|
|
35
36
|
source="prometheus",
|
|
36
37
|
type=EventType.ALERT,
|
|
37
38
|
service=service,
|
|
@@ -56,7 +57,7 @@ async def fetch_metric_breaches(
|
|
|
56
57
|
) -> list[SitRepEvent]:
|
|
57
58
|
"""Query Prometheus for SLO metric breaches on given services."""
|
|
58
59
|
events: list[SitRepEvent] = []
|
|
59
|
-
now = datetime.now(
|
|
60
|
+
now = datetime.now(UTC)
|
|
60
61
|
|
|
61
62
|
for service in services:
|
|
62
63
|
# Check error budget
|
|
@@ -152,9 +153,10 @@ def load_dependency_graph(specs_dir: str) -> dict[str, dict]:
|
|
|
152
153
|
|
|
153
154
|
Returns dict mapping service name → {tier, dependencies, dependents}.
|
|
154
155
|
"""
|
|
155
|
-
import yaml
|
|
156
156
|
from pathlib import Path
|
|
157
157
|
|
|
158
|
+
import yaml
|
|
159
|
+
|
|
158
160
|
graph: dict[str, dict] = {}
|
|
159
161
|
specs_path = Path(specs_dir)
|
|
160
162
|
if not specs_path.is_dir():
|
|
@@ -13,7 +13,7 @@ are what make correlation novel vs. simple alert aggregation.
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
15
|
from dataclasses import dataclass, field
|
|
16
|
-
from datetime import
|
|
16
|
+
from datetime import UTC, datetime
|
|
17
17
|
|
|
18
18
|
from nthlayer_workers.correlate.types import SitRepEvent
|
|
19
19
|
|
|
@@ -39,8 +39,8 @@ class SessionWindow:
|
|
|
39
39
|
|
|
40
40
|
domain: CorrelationDomain
|
|
41
41
|
events: list[SitRepEvent] = field(default_factory=list)
|
|
42
|
-
opened_at: datetime = field(default_factory=lambda: datetime.now(
|
|
43
|
-
last_event_at: datetime = field(default_factory=lambda: datetime.now(
|
|
42
|
+
opened_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
43
|
+
last_event_at: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
44
44
|
has_trigger: bool = False
|
|
45
45
|
|
|
46
46
|
def _close_flags(
|
|
@@ -119,7 +119,7 @@ class SessionWindowManager:
|
|
|
119
119
|
# Cap opened_at at now — a stale backlogged event should not
|
|
120
120
|
# push opened_at into the past (which would trigger instant
|
|
121
121
|
# max_duration closure on the next cycle).
|
|
122
|
-
now = datetime.now(
|
|
122
|
+
now = datetime.now(UTC)
|
|
123
123
|
self._windows[domain] = SessionWindow(
|
|
124
124
|
domain=domain,
|
|
125
125
|
events=[],
|
|
@@ -141,7 +141,7 @@ class SessionWindowManager:
|
|
|
141
141
|
def close_ready(self, now: datetime | None = None) -> list[SessionWindow]:
|
|
142
142
|
"""Close and return all windows that meet their close conditions."""
|
|
143
143
|
if now is None:
|
|
144
|
-
now = datetime.now(
|
|
144
|
+
now = datetime.now(UTC)
|
|
145
145
|
|
|
146
146
|
closed: list[SessionWindow] = []
|
|
147
147
|
for domain in list(self._windows):
|
|
@@ -182,8 +182,8 @@ def _parse_event_ts(event: SitRepEvent) -> datetime:
|
|
|
182
182
|
"""Parse event timestamp to datetime. Always timezone-aware (UTC)."""
|
|
183
183
|
ts = event.timestamp
|
|
184
184
|
if isinstance(ts, datetime):
|
|
185
|
-
return ts if ts.tzinfo else ts.replace(tzinfo=
|
|
185
|
+
return ts if ts.tzinfo else ts.replace(tzinfo=UTC)
|
|
186
186
|
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
|
187
187
|
if dt.tzinfo is None:
|
|
188
|
-
dt = dt.replace(tzinfo=
|
|
188
|
+
dt = dt.replace(tzinfo=UTC)
|
|
189
189
|
return dt
|
|
@@ -51,9 +51,12 @@ class SnapshotGenerator:
|
|
|
51
51
|
content_hash = self._compute_hash(groups)
|
|
52
52
|
|
|
53
53
|
# Check cache (skip in INCIDENT mode)
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
if (
|
|
55
|
+
not force_refresh
|
|
56
|
+
and state != AgentState.INCIDENT
|
|
57
|
+
and self._is_cache_valid(content_hash, state)
|
|
58
|
+
):
|
|
59
|
+
return self._cached_prompt or "", True
|
|
57
60
|
|
|
58
61
|
# Sort groups by priority (P0 first)
|
|
59
62
|
sorted_groups = sorted(groups, key=lambda g: g.priority)
|
{nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/snapshot/model.py
RENAMED
|
@@ -13,12 +13,12 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
15
|
import uuid
|
|
16
|
-
from datetime import
|
|
16
|
+
from datetime import UTC, datetime
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
19
|
import structlog
|
|
20
|
-
|
|
21
20
|
from nthlayer_common.prompts import load_prompt
|
|
21
|
+
|
|
22
22
|
from nthlayer_workers.correlate.types import CorrelationGroup
|
|
23
23
|
from nthlayer_workers.observe.assessment import Assessment
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ logger = structlog.get_logger()
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def _now() -> datetime:
|
|
31
|
-
return datetime.now(
|
|
31
|
+
return datetime.now(UTC)
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def _new_id(service: str | None) -> str:
|
|
@@ -155,6 +155,7 @@ class ModelInterface:
|
|
|
155
155
|
async def _call_model(self, prompt: str) -> str:
|
|
156
156
|
"""Call the LLM via the shared nthlayer-common wrapper."""
|
|
157
157
|
import asyncio
|
|
158
|
+
|
|
158
159
|
from nthlayer_common.llm import llm_call
|
|
159
160
|
|
|
160
161
|
result = await asyncio.to_thread(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Agent state machine for SitRep."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from datetime import
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
5
|
|
|
6
6
|
from nthlayer_workers.correlate.types import AgentState, CorrelationGroup
|
|
7
7
|
|
|
@@ -20,7 +20,7 @@ class StateMachine:
|
|
|
20
20
|
model_healthy: bool = True,
|
|
21
21
|
) -> AgentState:
|
|
22
22
|
"""Evaluate state transition based on correlation output."""
|
|
23
|
-
now = datetime.now(
|
|
23
|
+
now = datetime.now(UTC)
|
|
24
24
|
|
|
25
25
|
# Model failure → DEGRADED (from any state except INCIDENT)
|
|
26
26
|
if not model_healthy and self.state != AgentState.INCIDENT:
|
|
@@ -65,7 +65,7 @@ class StateMachine:
|
|
|
65
65
|
"""Incident resolved → WATCHING."""
|
|
66
66
|
self.state = AgentState.WATCHING
|
|
67
67
|
self._alert_start = None
|
|
68
|
-
self._calm_since = datetime.now(
|
|
68
|
+
self._calm_since = datetime.now(UTC)
|
|
69
69
|
|
|
70
70
|
def get_interval(self) -> int:
|
|
71
71
|
"""Snapshot interval in seconds for current state."""
|
{nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/traces/tempo.py
RENAMED
|
@@ -11,7 +11,7 @@ import asyncio
|
|
|
11
11
|
import os
|
|
12
12
|
import time
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
|
-
from datetime import datetime, timedelta
|
|
14
|
+
from datetime import UTC, datetime, timedelta
|
|
15
15
|
|
|
16
16
|
import httpx
|
|
17
17
|
|
|
@@ -631,7 +631,7 @@ class TempoTraceBackend:
|
|
|
631
631
|
@staticmethod
|
|
632
632
|
def _parse_tempo_timestamp(nanos: int) -> datetime:
|
|
633
633
|
"""Convert nanosecond unix timestamp to datetime."""
|
|
634
|
-
return datetime.fromtimestamp(nanos / 1_000_000_000, tz=
|
|
634
|
+
return datetime.fromtimestamp(nanos / 1_000_000_000, tz=UTC)
|
|
635
635
|
|
|
636
636
|
@staticmethod
|
|
637
637
|
def _traceql_service_filter(services: list[str]) -> str:
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from enum import
|
|
5
|
+
from enum import StrEnum
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class EventType(
|
|
9
|
+
class EventType(StrEnum):
|
|
10
10
|
ALERT = "alert"
|
|
11
11
|
METRIC_BREACH = "metric_breach"
|
|
12
12
|
CHANGE = "change"
|
|
@@ -70,7 +70,7 @@ class CorrelationGroup:
|
|
|
70
70
|
event_count: int
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
class AgentState(
|
|
73
|
+
class AgentState(StrEnum):
|
|
74
74
|
WATCHING = "watching"
|
|
75
75
|
ALERT = "alert"
|
|
76
76
|
INCIDENT = "incident"
|
{nthlayer_workers-1.6.0 → nthlayer_workers-2.0.0}/src/nthlayer_workers/correlate/verdict_chain.py
RENAMED
|
@@ -38,11 +38,9 @@ Deferred:
|
|
|
38
38
|
from __future__ import annotations
|
|
39
39
|
|
|
40
40
|
from dataclasses import dataclass, field
|
|
41
|
-
from datetime import datetime
|
|
42
|
-
from typing import Any
|
|
41
|
+
from datetime import datetime
|
|
43
42
|
|
|
44
43
|
import structlog
|
|
45
|
-
|
|
46
44
|
from nthlayer_common.verdicts.models import Verdict
|
|
47
45
|
from nthlayer_common.verdicts.store import VerdictFilter, VerdictStore
|
|
48
46
|
|
|
@@ -10,11 +10,10 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import uuid
|
|
12
12
|
from dataclasses import dataclass
|
|
13
|
-
from datetime import datetime, timedelta
|
|
13
|
+
from datetime import UTC, datetime, timedelta
|
|
14
14
|
from typing import Any
|
|
15
15
|
|
|
16
16
|
import structlog
|
|
17
|
-
|
|
18
17
|
from nthlayer_common.api_client import CoreAPIClient
|
|
19
18
|
from nthlayer_common.cloudevents import wrap_assessment
|
|
20
19
|
|
|
@@ -147,7 +146,7 @@ class CorrelateSessionModule:
|
|
|
147
146
|
# Orphaned window from crash recovery with no re-fetched events.
|
|
148
147
|
# Skip rather than producing a phantom empty snapshot.
|
|
149
148
|
return
|
|
150
|
-
now = datetime.now(
|
|
149
|
+
now = datetime.now(UTC)
|
|
151
150
|
reason = window.close_reason(now, self.gap_seconds, self.max_duration_seconds)
|
|
152
151
|
|
|
153
152
|
# Verdict IDs of QUALITY_SCORE events that triggered this snapshot.
|
|
@@ -222,7 +221,7 @@ def verdict_to_event(verdict: dict) -> SitRepEvent:
|
|
|
222
221
|
"""Convert a core verdict dict to a SitRepEvent."""
|
|
223
222
|
return SitRepEvent(
|
|
224
223
|
id=verdict.get("id", "unknown"),
|
|
225
|
-
timestamp=verdict.get("created_at", datetime.now(
|
|
224
|
+
timestamp=verdict.get("created_at", datetime.now(UTC).isoformat()),
|
|
226
225
|
source=f"verdict:{verdict.get('type', 'unknown')}",
|
|
227
226
|
type=EventType.QUALITY_SCORE if verdict.get("type") == "quality_breach" else EventType.VERDICT,
|
|
228
227
|
service=verdict.get("service", "unknown"),
|
|
@@ -236,7 +235,7 @@ def assessment_to_event(assessment: dict) -> SitRepEvent:
|
|
|
236
235
|
"""Convert a core assessment dict to a SitRepEvent."""
|
|
237
236
|
return SitRepEvent(
|
|
238
237
|
id=assessment.get("id", "unknown"),
|
|
239
|
-
timestamp=assessment.get("created_at", datetime.now(
|
|
238
|
+
timestamp=assessment.get("created_at", datetime.now(UTC).isoformat()),
|
|
240
239
|
source=f"assessment:{assessment.get('kind', 'unknown')}",
|
|
241
240
|
type=EventType.METRIC_BREACH if _is_breach(assessment) else EventType.ALERT,
|
|
242
241
|
service=assessment.get("service", "unknown"),
|
|
@@ -350,7 +349,7 @@ class CorrelateTopologyModule:
|
|
|
350
349
|
# Get trace evidence for all services
|
|
351
350
|
service_names = list(declared_deps.keys())
|
|
352
351
|
try:
|
|
353
|
-
end = datetime.now(
|
|
352
|
+
end = datetime.now(UTC)
|
|
354
353
|
start = end - timedelta(hours=1)
|
|
355
354
|
evidence = await self.trace_backend.get_trace_evidence(
|
|
356
355
|
services=service_names,
|
|
@@ -380,7 +379,7 @@ class CorrelateTopologyModule:
|
|
|
380
379
|
return
|
|
381
380
|
|
|
382
381
|
# Emit one assessment per divergence (org-level)
|
|
383
|
-
now = datetime.now(
|
|
382
|
+
now = datetime.now(UTC)
|
|
384
383
|
assessment = {
|
|
385
384
|
"id": f"tdr-topology-{uuid.uuid4().hex[:8]}",
|
|
386
385
|
"created_at": now.isoformat(),
|
|
@@ -581,7 +580,7 @@ class CorrelateContractModule:
|
|
|
581
580
|
if not divergences:
|
|
582
581
|
continue
|
|
583
582
|
|
|
584
|
-
now = datetime.now(
|
|
583
|
+
now = datetime.now(UTC)
|
|
585
584
|
assessment = {
|
|
586
585
|
"id": f"cdv-{service}-{uuid.uuid4().hex[:8]}",
|
|
587
586
|
"created_at": now.isoformat(),
|