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
nthlayer_workers/cli.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""NthLayer workers CLI — run all worker modules."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from nthlayer_workers.runner import ModuleRunner
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(description="NthLayer workers")
|
|
12
|
+
parser.add_argument("-V", "--version", action="version", version="%(prog)s 1.5.0a1")
|
|
13
|
+
sub = parser.add_subparsers(dest="command")
|
|
14
|
+
|
|
15
|
+
# -- serve: start all worker modules --
|
|
16
|
+
serve_parser = sub.add_parser("serve", help="Start the workers process (all modules)")
|
|
17
|
+
serve_parser.add_argument("--core-url", default="http://localhost:8000",
|
|
18
|
+
help="Core API URL (default: http://localhost:8000)")
|
|
19
|
+
serve_parser.add_argument("--instance-id", default="workers-1",
|
|
20
|
+
help="Instance ID for heartbeats (default: workers-1)")
|
|
21
|
+
serve_parser.add_argument("--prometheus-url", default="http://localhost:9090",
|
|
22
|
+
help="Prometheus URL (default: http://localhost:9090)")
|
|
23
|
+
serve_parser.add_argument("--collect-interval", type=int, default=60,
|
|
24
|
+
help="SLO collection + portfolio cycle interval in seconds (default: 60)")
|
|
25
|
+
serve_parser.add_argument("--drift-interval", type=int, default=1800,
|
|
26
|
+
help="Drift analysis cycle interval in seconds (default: 1800)")
|
|
27
|
+
serve_parser.add_argument("--topology-interval", type=int, default=86400,
|
|
28
|
+
help="Topology/blast-radius cycle interval in seconds (default: 86400)")
|
|
29
|
+
serve_parser.add_argument("--correlate-interval", type=int, default=10,
|
|
30
|
+
help="Correlate session window cycle interval in seconds (default: 10)")
|
|
31
|
+
serve_parser.add_argument("--topology-drift-interval", type=int, default=3600,
|
|
32
|
+
help="Correlate topology drift cycle interval in seconds (default: 3600)")
|
|
33
|
+
serve_parser.add_argument("--contract-interval", type=int, default=3600,
|
|
34
|
+
help="Correlate contract divergence cycle interval in seconds (default: 3600)")
|
|
35
|
+
serve_parser.add_argument("--tempo-endpoint", default=None,
|
|
36
|
+
help="Tempo endpoint URL (optional — topology module is no-op if not set)")
|
|
37
|
+
serve_parser.add_argument("--measure-interval", type=int, default=60,
|
|
38
|
+
help="Measure evaluation cycle interval in seconds (default: 60)")
|
|
39
|
+
serve_parser.add_argument("--outcome-interval", type=int, default=60,
|
|
40
|
+
help="Learn outcome resolution cycle interval in seconds (default: 60)")
|
|
41
|
+
serve_parser.add_argument("--retrospective-interval", type=int, default=30,
|
|
42
|
+
help="Learn retrospective cycle interval in seconds (default: 30)")
|
|
43
|
+
serve_parser.add_argument("--expiry-threshold-days", type=int, default=7,
|
|
44
|
+
help="Verdict expiry threshold in days (default: 7)")
|
|
45
|
+
serve_parser.add_argument("--min-resolution-age-hours", type=int, default=1,
|
|
46
|
+
help="Minimum verdict age before resolution attempts in hours (default: 1)")
|
|
47
|
+
serve_parser.add_argument("--respond-interval", type=int, default=30,
|
|
48
|
+
help="Respond worker cycle interval in seconds (default: 30)")
|
|
49
|
+
|
|
50
|
+
# -- gate: deploy gate evaluation (CLI-only, not a worker module) --
|
|
51
|
+
gate_parser = sub.add_parser("gate", help="Evaluate deployment gate for a service")
|
|
52
|
+
gate_parser.add_argument("--service", required=True, help="Service name")
|
|
53
|
+
gate_parser.add_argument("--tier", default=None,
|
|
54
|
+
help="Service tier (default: resolved from manifest)")
|
|
55
|
+
gate_parser.add_argument("--commit-sha", default=None, help="Commit SHA (informational)")
|
|
56
|
+
gate_parser.add_argument("--core-url", default="http://localhost:8000",
|
|
57
|
+
help="Core API URL (default: http://localhost:8000)")
|
|
58
|
+
|
|
59
|
+
args = parser.parse_args()
|
|
60
|
+
|
|
61
|
+
if args.command == "serve":
|
|
62
|
+
from nthlayer_common.api_client import CoreAPIClient
|
|
63
|
+
from nthlayer_workers.observe.worker import (
|
|
64
|
+
ObserveCollectModule,
|
|
65
|
+
ObserveDriftModule,
|
|
66
|
+
ObserveTopologyModule,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
client = CoreAPIClient(base_url=args.core_url)
|
|
70
|
+
runner = ModuleRunner(
|
|
71
|
+
core_url=args.core_url,
|
|
72
|
+
instance_id=args.instance_id,
|
|
73
|
+
client=client,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
collect = ObserveCollectModule(client=client, prometheus_url=args.prometheus_url)
|
|
77
|
+
drift = ObserveDriftModule(client=client, prometheus_url=args.prometheus_url)
|
|
78
|
+
topology = ObserveTopologyModule(client=client, prometheus_url=args.prometheus_url)
|
|
79
|
+
|
|
80
|
+
runner.register(collect, interval_seconds=args.collect_interval)
|
|
81
|
+
runner.register(drift, interval_seconds=args.drift_interval)
|
|
82
|
+
runner.register(topology, interval_seconds=args.topology_interval)
|
|
83
|
+
|
|
84
|
+
# P3-D: Correlate modules — session windows, topology drift, contract divergence
|
|
85
|
+
from nthlayer_workers.correlate.worker import (
|
|
86
|
+
CorrelateContractModule,
|
|
87
|
+
CorrelateSessionModule,
|
|
88
|
+
CorrelateTopologyModule,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
trace_backend = None
|
|
92
|
+
if args.tempo_endpoint:
|
|
93
|
+
from nthlayer_workers.correlate.traces.tempo import TempoTraceBackend
|
|
94
|
+
trace_backend = TempoTraceBackend(endpoint=args.tempo_endpoint)
|
|
95
|
+
|
|
96
|
+
correlate_session = CorrelateSessionModule(client=client)
|
|
97
|
+
correlate_topology = CorrelateTopologyModule(client=client, trace_backend=trace_backend)
|
|
98
|
+
correlate_contract = CorrelateContractModule(client=client, prometheus_url=args.prometheus_url)
|
|
99
|
+
|
|
100
|
+
runner.register(correlate_session, interval_seconds=args.correlate_interval)
|
|
101
|
+
runner.register(correlate_topology, interval_seconds=args.topology_drift_interval)
|
|
102
|
+
runner.register(correlate_contract, interval_seconds=args.contract_interval)
|
|
103
|
+
|
|
104
|
+
# P3-F: Learn modules — outcome resolution + retrospective
|
|
105
|
+
from nthlayer_workers.learn.worker import LearnOutcomeModule, LearnRetrospectiveModule
|
|
106
|
+
|
|
107
|
+
learn_outcome = LearnOutcomeModule(
|
|
108
|
+
client=client,
|
|
109
|
+
expiry_threshold_days=args.expiry_threshold_days,
|
|
110
|
+
minimum_resolution_age_hours=args.min_resolution_age_hours,
|
|
111
|
+
)
|
|
112
|
+
learn_retro = LearnRetrospectiveModule(client=client)
|
|
113
|
+
|
|
114
|
+
runner.register(learn_outcome, interval_seconds=args.outcome_interval)
|
|
115
|
+
runner.register(learn_retro, interval_seconds=args.retrospective_interval)
|
|
116
|
+
|
|
117
|
+
# P3-C: Measure module — judgment SLO evaluation
|
|
118
|
+
from nthlayer_workers.measure.worker import MeasureModule
|
|
119
|
+
|
|
120
|
+
measure = MeasureModule(client=client, prometheus_url=args.prometheus_url)
|
|
121
|
+
runner.register(measure, interval_seconds=args.measure_interval)
|
|
122
|
+
|
|
123
|
+
# P3-E: Respond module — incident response coordinator
|
|
124
|
+
from nthlayer_workers.respond.config import RespondConfig
|
|
125
|
+
from nthlayer_workers.respond.worker import RespondModule
|
|
126
|
+
|
|
127
|
+
respond_config = RespondConfig(cycle_interval_seconds=float(args.respond_interval))
|
|
128
|
+
respond = RespondModule(client=client, config=respond_config)
|
|
129
|
+
runner.register(respond, interval_seconds=args.respond_interval)
|
|
130
|
+
|
|
131
|
+
asyncio.run(runner.run())
|
|
132
|
+
|
|
133
|
+
elif args.command == "gate":
|
|
134
|
+
exit_code = _run_gate(args)
|
|
135
|
+
sys.exit(exit_code)
|
|
136
|
+
|
|
137
|
+
else:
|
|
138
|
+
parser.print_help()
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _run_gate(args: argparse.Namespace) -> int:
|
|
143
|
+
"""Run deploy gate evaluation.
|
|
144
|
+
|
|
145
|
+
Exit codes:
|
|
146
|
+
0 = APPROVED or WARNING (deploy allowed; WARNING text on stderr)
|
|
147
|
+
1 = evaluation error (couldn't evaluate; deploy scripts decide)
|
|
148
|
+
2 = BLOCKED (do not deploy)
|
|
149
|
+
"""
|
|
150
|
+
return asyncio.run(_gate_async(args))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def _gate_async(args: argparse.Namespace) -> int:
|
|
154
|
+
"""Async gate evaluation — single event loop for all core API calls."""
|
|
155
|
+
import json
|
|
156
|
+
|
|
157
|
+
from nthlayer_common.api_client import CoreAPIClient
|
|
158
|
+
|
|
159
|
+
from nthlayer_workers.observe.assessment import Assessment, create, from_dict, to_dict
|
|
160
|
+
from nthlayer_workers.observe.gate.evaluator import check_deploy
|
|
161
|
+
from nthlayer_workers.observe.store import AssessmentFilter, MemoryAssessmentStore
|
|
162
|
+
|
|
163
|
+
client = CoreAPIClient(base_url=args.core_url)
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# 1. Resolve tier from manifest if not specified
|
|
167
|
+
tier = args.tier
|
|
168
|
+
if tier is None:
|
|
169
|
+
manifest_result = await client.get_manifest(args.service)
|
|
170
|
+
if not manifest_result.ok:
|
|
171
|
+
print(f"Error: could not fetch manifest for {args.service}: {manifest_result.error}", file=sys.stderr)
|
|
172
|
+
return 1
|
|
173
|
+
if not isinstance(manifest_result.data, dict):
|
|
174
|
+
print(f"Error: malformed manifest response for {args.service}", file=sys.stderr)
|
|
175
|
+
return 1
|
|
176
|
+
tier = manifest_result.data.get("tier", "standard")
|
|
177
|
+
|
|
178
|
+
# 2. Fetch SLO assessments from core into a MemoryAssessmentStore
|
|
179
|
+
# Single fetch — used by both check_deploy and parent_ids extraction.
|
|
180
|
+
api_result = await client.get_assessments(service=args.service, kind="slo_status")
|
|
181
|
+
if not api_result.ok:
|
|
182
|
+
print(f"Error: could not fetch assessments: {api_result.error}", file=sys.stderr)
|
|
183
|
+
return 1
|
|
184
|
+
|
|
185
|
+
store = MemoryAssessmentStore()
|
|
186
|
+
slo_ids: list[str] = []
|
|
187
|
+
for d in (api_result.data or []):
|
|
188
|
+
a = from_dict(d)
|
|
189
|
+
store.put(a)
|
|
190
|
+
slo_ids.append(a.id)
|
|
191
|
+
|
|
192
|
+
# 3. Evaluate gate
|
|
193
|
+
result = check_deploy(args.service, tier, store)
|
|
194
|
+
|
|
195
|
+
# 4. Submit deploy_gate assessment to core
|
|
196
|
+
assessment = create("deploy_gate", args.service, {
|
|
197
|
+
"action": "deploy",
|
|
198
|
+
"decision": result.result.name.lower(),
|
|
199
|
+
"budget_remaining_pct": result.budget_remaining_pct,
|
|
200
|
+
"warning_threshold": result.warning_threshold,
|
|
201
|
+
"blocking_threshold": result.blocking_threshold,
|
|
202
|
+
"slo_count": result.slo_count,
|
|
203
|
+
"reasons": [result.message] + result.recommendations,
|
|
204
|
+
"commit_sha": args.commit_sha,
|
|
205
|
+
"parent_ids": slo_ids,
|
|
206
|
+
})
|
|
207
|
+
await client.submit_assessment(to_dict(assessment))
|
|
208
|
+
|
|
209
|
+
# 5. Output result
|
|
210
|
+
output = {
|
|
211
|
+
"service": result.service,
|
|
212
|
+
"tier": result.tier,
|
|
213
|
+
"decision": result.result.name,
|
|
214
|
+
"budget_remaining_pct": round(result.budget_remaining_pct, 1),
|
|
215
|
+
"message": result.message,
|
|
216
|
+
"recommendations": result.recommendations,
|
|
217
|
+
}
|
|
218
|
+
print(json.dumps(output, indent=2))
|
|
219
|
+
|
|
220
|
+
if result.result.name == "BLOCKED":
|
|
221
|
+
return 2
|
|
222
|
+
if result.result.name == "WARNING":
|
|
223
|
+
print(f"WARNING: {result.message}", file=sys.stderr)
|
|
224
|
+
return 0
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
print(f"Error: gate evaluation failed: {e}", file=sys.stderr)
|
|
228
|
+
return 1
|
|
229
|
+
finally:
|
|
230
|
+
await client.close()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SitRep — Situational awareness through automated signal correlation."""
|