sediment-api 0.1.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.
- sediment_api/__init__.py +6 -0
- sediment_api/config.py +230 -0
- sediment_api/database.py +43 -0
- sediment_api/deps.py +191 -0
- sediment_api/main.py +212 -0
- sediment_api/mirror_gc.py +151 -0
- sediment_api/reports/__init__.py +9 -0
- sediment_api/reports/abandonment_report.py +123 -0
- sediment_api/reports/attribution_share_report.py +298 -0
- sediment_api/reports/dataset_diagnostics.py +311 -0
- sediment_api/reports/label_confidence_inspection.py +438 -0
- sediment_api/reports/lifecycle_report.py +73 -0
- sediment_api/reports/merge_retention_report.py +165 -0
- sediment_api/reports/model_report.py +1534 -0
- sediment_api/reports/precision_report.py +182 -0
- sediment_api/reports/recovery_yield_report.py +132 -0
- sediment_api/routers/__init__.py +3 -0
- sediment_api/routers/ci_vendor.py +149 -0
- sediment_api/routers/forge.py +452 -0
- sediment_api/routers/gateway.py +137 -0
- sediment_api/routers/otlp.py +120 -0
- sediment_api/routers/query.py +1010 -0
- sediment_api/routers/reports.py +116 -0
- sediment_api/routers/v1.py +170 -0
- sediment_api/services/__init__.py +2 -0
- sediment_api/services/operational_reports.py +391 -0
- sediment_api/worker.py +242 -0
- sediment_api/workers.py +382 -0
- sediment_api-0.1.0.dist-info/METADATA +20 -0
- sediment_api-0.1.0.dist-info/RECORD +32 -0
- sediment_api-0.1.0.dist-info/WHEEL +4 -0
- sediment_api-0.1.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
"""
|
|
3
|
+
POST /v1/logs — OTLP/HTTP JSON receiver for coding-agent telemetry.
|
|
4
|
+
|
|
5
|
+
The path is fixed by the OTLP spec (``<endpoint>/v1/logs``), so no
|
|
6
|
+
``/ingest`` prefix. Translation lives in ``packages/capture`` (one
|
|
7
|
+
translator per agent, self-filtering by event name); this route walks each
|
|
8
|
+
translated fact into the store.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
18
|
+
from sediment_capture import parse_otlp_logs
|
|
19
|
+
from sediment_core import FactStore
|
|
20
|
+
from starlette.concurrency import run_in_threadpool
|
|
21
|
+
|
|
22
|
+
from ..config import settings
|
|
23
|
+
from ..deps import get_store, verify_ingest_token
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("sediment.api.otlp")
|
|
26
|
+
router = APIRouter(tags=["otlp"])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@router.post("/v1/logs")
|
|
30
|
+
async def ingest_otlp_logs(
|
|
31
|
+
request: Request,
|
|
32
|
+
_: None = Depends(verify_ingest_token),
|
|
33
|
+
store: FactStore = Depends(get_store),
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
try:
|
|
36
|
+
payload = await request.json()
|
|
37
|
+
except HTTPException:
|
|
38
|
+
# The body-size middleware's 413 surfaces inside the read — it must
|
|
39
|
+
# stay a 413, not collapse into the 400 below.
|
|
40
|
+
raise
|
|
41
|
+
except Exception as exc:
|
|
42
|
+
# Malformed body: 400 so the exporter drops it rather than retrying.
|
|
43
|
+
raise HTTPException(status_code=400, detail="invalid OTLP/JSON body") from exc
|
|
44
|
+
if not isinstance(payload, dict):
|
|
45
|
+
raise HTTPException(status_code=400, detail="OTLP body must be a JSON object")
|
|
46
|
+
|
|
47
|
+
capture = parse_otlp_logs(payload, org_id=settings.org_id)
|
|
48
|
+
stored_counts = await run_in_threadpool(
|
|
49
|
+
_store_translated_facts,
|
|
50
|
+
store,
|
|
51
|
+
capture.decisions,
|
|
52
|
+
capture.edit_observations,
|
|
53
|
+
capture.rejected_edits,
|
|
54
|
+
capture.retry_linkages,
|
|
55
|
+
)
|
|
56
|
+
record_counts = {
|
|
57
|
+
"received": capture.records_received,
|
|
58
|
+
"translated": capture.records_translated,
|
|
59
|
+
"untranslated": capture.records_untranslated,
|
|
60
|
+
"malformed": capture.records_malformed,
|
|
61
|
+
}
|
|
62
|
+
fact_counts = {
|
|
63
|
+
name: {
|
|
64
|
+
"candidates": len(facts),
|
|
65
|
+
"stored": stored,
|
|
66
|
+
"duplicates": len(facts) - stored,
|
|
67
|
+
}
|
|
68
|
+
for (name, facts), stored in zip(
|
|
69
|
+
(
|
|
70
|
+
("developer_decisions", capture.decisions),
|
|
71
|
+
("edit_observations", capture.edit_observations),
|
|
72
|
+
("rejected_edits", capture.rejected_edits),
|
|
73
|
+
("retry_linkages", capture.retry_linkages),
|
|
74
|
+
),
|
|
75
|
+
stored_counts,
|
|
76
|
+
strict=True,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
retry_linkage_skips = {
|
|
80
|
+
reason.value: count for reason, count in capture.retry_linkage_skips.items()
|
|
81
|
+
}
|
|
82
|
+
# A later storage failure can leave a committed prefix. Only completed
|
|
83
|
+
# requests get a receipt; database dedup accounts for that prefix on replay.
|
|
84
|
+
logger.info(
|
|
85
|
+
"otlp_logs_received org_id=%s record_counts=%s malformed_containers=%d"
|
|
86
|
+
" fact_counts=%s retry_linkage_skips=%s",
|
|
87
|
+
settings.org_id,
|
|
88
|
+
json.dumps(record_counts, sort_keys=True),
|
|
89
|
+
capture.malformed_containers,
|
|
90
|
+
json.dumps(fact_counts, sort_keys=True),
|
|
91
|
+
json.dumps(retry_linkage_skips, sort_keys=True),
|
|
92
|
+
extra={
|
|
93
|
+
"org_id": settings.org_id,
|
|
94
|
+
"record_counts": record_counts,
|
|
95
|
+
"malformed_containers": capture.malformed_containers,
|
|
96
|
+
"fact_counts": fact_counts,
|
|
97
|
+
"retry_linkage_skips": retry_linkage_skips,
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
# Empty ExportLogsServiceResponse = full success (OTLP/HTTP JSON).
|
|
101
|
+
return {}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _store_translated_facts(
|
|
105
|
+
store: FactStore,
|
|
106
|
+
decisions: list,
|
|
107
|
+
observations: list,
|
|
108
|
+
rejected: list,
|
|
109
|
+
retry_linkages: list,
|
|
110
|
+
) -> tuple[int, int, int, int]:
|
|
111
|
+
"""Store one translated OTLP request outside the event loop."""
|
|
112
|
+
stored = sum(store.store_decisions(decisions))
|
|
113
|
+
observations_stored = sum(
|
|
114
|
+
store.store_edit_observation(observation) for observation in observations
|
|
115
|
+
)
|
|
116
|
+
rejected_stored = sum(store.store_rejected_edit(edit) for edit in rejected)
|
|
117
|
+
retry_linkages_stored = sum(
|
|
118
|
+
store.store_retry_linkage(linkage) for linkage in retry_linkages
|
|
119
|
+
)
|
|
120
|
+
return stored, observations_stored, rejected_stored, retry_linkages_stored
|