mirror of
https://github.com/azaion/autopilot.git
synced 2026-06-21 22:41:09 +00:00
[AZ-659] [AZ-660] [AZ-661] Implement frame publisher + gRPC detection client
AZ-659: FramePublisher with per-consumer drop accounting (Arc<Bytes> zero-copy fan-out). Adds ConsumerId enum, PublisherStats, FrameReceiver wrapper, and publisher integration tests (AC-1, AC-2, AC-3). AZ-660: Bi-directional tonic gRPC stream to ../detections. Reconnect with bounded exponential backoff (1 s → 30 s cap). Drop-oldest in-flight budgeting (max_concurrent_in_flight = 2). ai_locked frame skipping. Integration tests against fixture in-process server (AC-1: happy path 30 fps/10 s, AC-2: reconnect, AC-3: budget drops, AC-4: ai_locked skipping). AZ-661: Schema validation (hard SchemaMismatch error on version mismatch), model_version latch with ModelVersionChanged events, sliding-window p99 latency tracker with Tier1Degraded/Tier1Recovered transitions. Integration tests (AC-1, AC-2, AC-3). Also: update module-layout.md for frame_ingest and detection_client to reflect the streaming API shape; code review report batch_18. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// AZ-660 / AZ-661 — vendored copy of the `../detections` gRPC contract.
|
||||
//
|
||||
// The authoritative schema lives in the `../detections` repository
|
||||
// (per `_docs/02_document/architecture.md §10`). This vendored copy
|
||||
// is kept in lock-step with that schema via the `schema_version`
|
||||
// field on `DetectionResponse`: any breaking schema change MUST
|
||||
// bump the version, and the client (built against the version pinned
|
||||
// in `DetectionClientConfig::expected_schema_version`) MUST emit a
|
||||
// hard `schema_mismatch` error if the server reports a different
|
||||
// version. The schema version is the explicit handshake that lets
|
||||
// the autopilot run alongside an evolving detection service without
|
||||
// silently downcasting unknown response shapes.
|
||||
//
|
||||
// Wire shape (one bi-directional stream per session):
|
||||
// client ─► FrameRequest stream ────► server (../detections)
|
||||
// client ◄── DetectionResponse stream ◄── server
|
||||
//
|
||||
// `FrameRequest` carries the encoded pixel buffer and the source
|
||||
// frame's monotonic timestamp; the response correlates back via
|
||||
// `frame_seq`. Frames with `ai_locked = true` upstream are filtered
|
||||
// by the client and never sent — the server therefore never sees a
|
||||
// FrameRequest for an AI-locked frame.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package azaion.detection.v1;
|
||||
|
||||
service DetectionService {
|
||||
// One bi-directional stream per client session. The server may
|
||||
// close the stream at any time; the client reconnects with
|
||||
// bounded backoff (`DetectionClientConfig::reconnect_*`).
|
||||
rpc Stream(stream FrameRequest) returns (stream DetectionResponse);
|
||||
}
|
||||
|
||||
// Pixel formats mirrored from `shared::models::frame::PixelFormat`.
|
||||
// Encoded as a proto enum so the wire is self-describing.
|
||||
enum PixelFormat {
|
||||
PIXEL_FORMAT_UNSPECIFIED = 0;
|
||||
PIXEL_FORMAT_NV12 = 1;
|
||||
PIXEL_FORMAT_YUV420P = 2;
|
||||
PIXEL_FORMAT_RGB24 = 3;
|
||||
}
|
||||
|
||||
// One inference request per frame. The client tracks `frame_seq`
|
||||
// for response correlation (the response carries the same value
|
||||
// in `frame_seq`).
|
||||
message FrameRequest {
|
||||
uint64 frame_seq = 1;
|
||||
// Capture timestamp (monotonic, ns) — used by the client to
|
||||
// compute per-frame round-trip latency from the response.
|
||||
uint64 capture_ts_monotonic_ns = 2;
|
||||
uint32 width = 3;
|
||||
uint32 height = 4;
|
||||
PixelFormat pix_fmt = 5;
|
||||
bytes pixels = 6;
|
||||
}
|
||||
|
||||
// Bounding box in [0,1] normalized coordinates (mirrors
|
||||
// `shared::models::frame::BoundingBox`).
|
||||
message BoundingBox {
|
||||
float x_min = 1;
|
||||
float y_min = 2;
|
||||
float x_max = 3;
|
||||
float y_max = 4;
|
||||
}
|
||||
|
||||
// One detection inside a `DetectionResponse`.
|
||||
message Detection {
|
||||
uint32 class_id = 1;
|
||||
string class_name = 2;
|
||||
float confidence = 3;
|
||||
BoundingBox bbox_normalized = 4;
|
||||
optional bytes mask_or_polyline = 5;
|
||||
uint64 source_frame_seq = 6;
|
||||
}
|
||||
|
||||
// Server-streamed response. `schema_version` is the handshake the
|
||||
// client validates against `expected_schema_version`; any mismatch
|
||||
// is a hard `schema_mismatch` error and the response is rejected.
|
||||
// `model_version` may change at runtime when the inference model
|
||||
// is hot-swapped — the client emits a `ModelVersionChanged` event
|
||||
// on the first response with a new version.
|
||||
message DetectionResponse {
|
||||
uint32 schema_version = 1;
|
||||
string model_version = 2;
|
||||
uint64 frame_seq = 3;
|
||||
// Server-side processing latency for THIS frame, in milliseconds.
|
||||
// The client also computes its own round-trip latency from
|
||||
// `capture_ts_monotonic_ns` so it can detect transport latency
|
||||
// independently of server-internal latency.
|
||||
uint32 latency_ms = 4;
|
||||
repeated Detection detections = 5;
|
||||
}
|
||||
Reference in New Issue
Block a user