replica-omnisciente/firmware/telemetry/INGEST_API.md
Raphael Cautus (Maestro) 39fb44fe0e feat(infra): proxmox IaC, firmware, savearth realm, lab-gateway ESP, CI
- infrastructure/proxmox/: CT provisioning configs
- infrastructure/fabric/gitops/: GitOps layer
- fleet.yaml: GPU inventory (Dell GTX 1050 vfio-pci passthrough)
- firmware/: ESP32 firmware tree (67 files, 6.9MB)
- realms/savearth/: Savearth team realm with heteronyms
- lab-gateway: ESP client + manager
- CI: Forgejo + GitHub Actions workflows

Co-authored-by: Álvaro de Campos <campos@portugalfuturista.org>
2026-07-31 14:57:41 +01:00

133 lines
4.5 KiB
Markdown

# Telemetry Ingest API — Bare-Metal Backend Spec
Version: 1.0
Target: Any HTTP-capable 32-bit CPU with MMU/MPU (ARM Cortex-A, RISC-V, x86)
Runtime: Rust (Axum) or C++ (CivetWeb) — NO Python on bare metal.
## Purpose
Single HTTP endpoint that receives telemetry from ALL surfaces:
| Surface | Language | How it reaches this endpoint |
|---------|----------|------------------------------|
| Bare-metal agent (ESP32, STM32, RISC-V) | C++/Rust/Zig/Go | `HttpTransport::transmit()` POSTs JSON |
| aurelio-vscode extension | TypeScript | `HttpTransport` (fetch) POSTs JSON |
| aurelio-theia backend | TypeScript | `HttpTransport` (axios/fetch) POSTs JSON |
| Python data-sharing CLI | Python | `http` transport POSTs JSON |
| Web portal (aurelio-web) | TypeScript | Same `HttpTransport` from `@aurelio/shared` |
All surfaces produce the SAME JSON envelope (WIRE_FORMAT.md, schema_version 1).
## Endpoints
### `POST /api/telemetry/ingest`
Receives a telemetry payload. Validates schema. Stores for aggregation.
**Request headers:**
```
Content-Type: application/json
X-Aurelio-Source: <source-identifier> (e.g. "vscode", "theia", "esp32-s3-01", "cli")
X-Aurelio-Transport: http
Authorization: Bearer <token> (optional, per-client)
```
**Request body** — the wire format envelope (see WIRE_FORMAT.md):
```json
{
"schema_version": 1,
"collected_at": "2026-07-30T17:00:00Z",
"device_id": "esp32-s3-01",
"platform": "esp-idf v5.5.4",
"consent": {
"categories": ["tool_calls", "environment"],
"retention_days": 90,
"redact_secrets": true
},
"environment": { "os": "FreeRTOS", "chip": "ESP32-S3" },
"tool_calls": [...],
"_summary": { "environment": 1, "tool_calls": 3 }
}
```
**Response:**
- `202 Accepted` — payload valid, queued for storage
- `400 Bad Request` — invalid schema (missing required fields, wrong version)
- `401 Unauthorized` — bad/missing token (when auth is required)
- `413 Payload Too Large` — body exceeds size limit (default 64KB)
**Validation rules:**
1. `schema_version` MUST be `1`
2. `collected_at` MUST be a valid ISO 8601 timestamp
3. `device_id` MUST be a non-empty string
4. `consent` MUST be present with a `categories` array
5. Every top-level category key MUST be in `consent.categories` (no unconsented data)
6. Body size MUST NOT exceed 64KB (bare-metal friendly)
### `GET /api/telemetry/health`
Health check. Returns `{"status":"ok","uptime_s":<seconds>}`.
### `GET /api/telemetry/stats`
Aggregate stats (for dashboards). Returns counts per source, per category, per day.
## Storage
On bare metal, payloads are stored in a ring buffer on flash/NVRAM. On
server-class targets (Linux), payloads go to SQLite or append-only JSONL.
Storage layout (JSONL, one line per payload):
```
/data/telemetry/2026-07-30.jsonl
/data/telemetry/2026-07-31.jsonl
```
Retention: `consent.retention_days` (default 90). Older files are deleted
by a background task (on server) or on boot (bare metal).
## Implementation Targets
### Rust (Axum) — primary
Single binary, ~2MB static. Targets: ARM Cortex-A (Linux), RISC-V (Linux),
x86_64 (Linux). Can also run as a service on Proxmox CTs.
```toml
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
```
### C++ (CivetWeb) — minimal footprint
Single binary, ~500KB. Targets: ARM Cortex-A (Linux), RISC-V (Linux),
x86 (Linux). For constrained environments where Rust toolchain is unavailable.
### Go (TinyGo) — microcontroller
For ESP32/STM32 with networking (WiFi/Ethernet). Runs the ingest endpoint
directly on the device — peer-to-peer telemetry without a server.
## Deployment
| Target | How |
|--------|-----|
| Proxmox CT | systemd service, binary at /usr/local/bin/aurelio-telemetry-ingest |
| Docker | `docker run -p 8080:8080 aurelio/telemetry-ingest` |
| Bare metal (Yocto) | `bitbake aurelio-telemetry-ingest` — systemd unit |
| ESP32 (TinyGo) | `tinygo flash -target=esp32-coreboard-v2 ./cmd/ingest` |
| STM32 (Rust) | `probe-rs run --chip STM32F407VGTx target/thumbv7em-none-eabihf/release/ingest` |
## Relationship to existing infra
This endpoint REPLACES the Proxmox-hardcoded `sync.py --push` for telemetry.
The old `POST /api/brain/push` endpoint (Gabinete daemon) remains for brain
session sync — this new endpoint is for the consent-gated telemetry stream.
```
Before: device → sync.py --push → ssh root@192.168.0.38 → pct push → CT 208
After: device → POST /api/telemetry/ingest → any backend (bare metal, CT, Docker)
```