diff --git a/crates/ourios-ingester/src/lib.rs b/crates/ourios-ingester/src/lib.rs index b7a414c22..f62f2b03e 100644 --- a/crates/ourios-ingester/src/lib.rs +++ b/crates/ourios-ingester/src/lib.rs @@ -4,9 +4,11 @@ //! durably (WAL-before-ack), lands Parquet in object storage, and runs //! background maintenance. It spans three RFCs at different maturities: //! -//! - **OTLP receiver** (RFC 0003, `drafted`) — the gRPC/HTTP ingest -//! front door + mining pipeline. [`receiver`] is a placeholder until -//! RFC 0003 reaches `red`; no ingest is implemented here yet. +//! - **OTLP receiver** (RFC 0003, `specified` → `red`) — the gRPC/HTTP +//! ingest front door + mining pipeline. The §5 acceptance criteria +//! (RFC0003.1–.15) are now enumerated as `#[ignore]`'d tests under +//! `tests/rfc0003_*`; [`receiver`] stays a placeholder until the +//! green slices land the ingest pipeline + WAL-before-ack path. //! - **WAL-before-ack** (RFC 0008 / `CLAUDE.md` §3.4) — durability //! before acknowledgement, via the shipped `ourios-wal`. Wired into //! the ingest path once the receiver lands; not exercised here. diff --git a/crates/ourios-ingester/src/receiver.rs b/crates/ourios-ingester/src/receiver.rs index cefea3c02..4afe82e23 100644 --- a/crates/ourios-ingester/src/receiver.rs +++ b/crates/ourios-ingester/src/receiver.rs @@ -1,9 +1,11 @@ -//! OTLP receiver — **placeholder** (RFC 0003, `drafted`). +//! OTLP receiver — **placeholder** (RFC 0003, `specified` → `red`). //! //! The ingest front door (OTLP logs over gRPC/HTTP), the Drain-derived //! mining pipeline, and the WAL-before-ack durability path -//! (`CLAUDE.md` §3.4, RFC 0008) live here once RFC 0003 reaches `red`. -//! Nothing is implemented yet: this scaffold lands the crate and the -//! background compaction runner ([`crate::compactor`]) only, so the -//! receiver is deliberately empty rather than a half-built ingest path -//! ahead of its RFC. +//! (`CLAUDE.md` §3.4, RFC 0008) live here. The RFC is now `specified`, +//! and its §5 acceptance criteria (RFC0003.1–.15) are enumerated as +//! `#[ignore]`'d tests under `crates/ourios-ingester/tests/rfc0003_*` — +//! the `red` gate. This module stays deliberately empty until the green +//! slices land the ingest pipeline (wire decode → tenant fan-out → +//! WAL-before-ack → miner), rather than a half-built path ahead of +//! review. diff --git a/crates/ourios-ingester/tests/rfc0003_10_dropped_attributes_count.rs b/crates/ourios-ingester/tests/rfc0003_10_dropped_attributes_count.rs new file mode 100644 index 000000000..1d9f4e5fc --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_10_dropped_attributes_count.rs @@ -0,0 +1,16 @@ +//! RFC0003.10 — `dropped_attributes_count` preserved verbatim. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.10 — `dropped_attributes_count` preserved verbatim. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.10)"] +#[test] +fn rfc0003_10_dropped_attributes_count_is_reflected_not_recomputed() { + unimplemented!( + "RFC0003.10 — a wire dropped_attributes_count of 42 yields \ + OtlpLogRecord.dropped_attributes_count == 42 exactly; the receiver \ + reflects the wire claim and never recomputes it." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_11_transport_errors.rs b/crates/ourios-ingester/tests/rfc0003_11_transport_errors.rs new file mode 100644 index 000000000..dfb687344 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_11_transport_errors.rs @@ -0,0 +1,18 @@ +//! RFC0003.11 — Transport-level errors are controlled, not panics. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.11 — Transport-level errors are controlled, not panics. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.11)"] +#[test] +fn rfc0003_11_transport_errors_are_controlled() { + unimplemented!( + "RFC0003.11 — malformed protobuf, oversize body, unrecognised \ + Content-Type, wrong path, and mid-decode gRPC cancellation each yield a \ + controlled transport error (gRPC INVALID_ARGUMENT / RESOURCE_EXHAUSTED / \ + CANCELLED; HTTP 400 / 413 / 415 / 404). No panic, process stays alive, \ + and no OtlpBatch frame is appended." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_12_empty_request_success.rs b/crates/ourios-ingester/tests/rfc0003_12_empty_request_success.rs new file mode 100644 index 000000000..2008154c8 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_12_empty_request_success.rs @@ -0,0 +1,18 @@ +//! RFC0003.12 — Empty `ExportLogsServiceRequest` returns success without WAL write. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.12 — Empty `ExportLogsServiceRequest` returns success without WAL write. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.12)"] +#[test] +fn rfc0003_12_empty_request_succeeds_without_persisting() { + unimplemented!( + "RFC0003.12 — a zero-LogRecord request (empty resource_logs; empty \ + scope_logs; empty log_records — all three shapes) returns success with \ + partial_success unset (OTLP 'empty is success'), and invokes neither \ + Wal::append/sync nor MinerCluster::ingest (asserted via a counting Wal \ + wrapper)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_13_compression.rs b/crates/ourios-ingester/tests/rfc0003_13_compression.rs new file mode 100644 index 000000000..fd3e87b6e --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_13_compression.rs @@ -0,0 +1,17 @@ +//! RFC0003.13 — Compression over HTTP: identity and gzip MUST be supported. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.13 — Compression over HTTP: identity and gzip MUST be supported. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.13)"] +#[test] +fn rfc0003_13_identity_and_gzip_decode_equally_unsupported_is_415() { + unimplemented!( + "RFC0003.13 — the same payload sent with Content-Encoding: identity (or \ + absent) and with Content-Encoding: gzip yields equal OtlpLogRecord \ + sequences (both encodings are an OTLP MUST). An unsupported encoding \ + (zstd, br) is rejected with HTTP 415; zstd is deferred per §9." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_14_path_config.rs b/crates/ourios-ingester/tests/rfc0003_14_path_config.rs new file mode 100644 index 000000000..e76a1bd0e --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_14_path_config.rs @@ -0,0 +1,17 @@ +//! RFC0003.14 — Default `/v1/logs` path with configurable override. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.14 — Default `/v1/logs` path with configurable override. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.14)"] +#[test] +fn rfc0003_14_default_path_and_configurable_override() { + unimplemented!( + "RFC0003.14 — a POST to the default /v1/logs is handled via the §6.2 HTTP \ + path; a POST to any other path returns HTTP 404; and an operator-\ + configured override path (e.g. /otlp/v1/logs) replaces /v1/logs as the \ + accepted path without changing any other receiver behaviour." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_15_concurrent_wal_before_ack.rs b/crates/ourios-ingester/tests/rfc0003_15_concurrent_wal_before_ack.rs new file mode 100644 index 000000000..9a52a3bd8 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_15_concurrent_wal_before_ack.rs @@ -0,0 +1,17 @@ +//! RFC0003.15 — Concurrent `Export` calls each obey WAL-before-ack independently `[§3.4]`. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.15 — Concurrent `Export` calls each obey WAL-before-ack independently. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.15)"] +#[test] +fn rfc0003_15_concurrent_exports_each_ack_after_their_own_sync() { + unimplemented!( + "RFC0003.15 — N >= 2 concurrent gRPC Export calls from independent \ + connections each emit their ack only after their OWN batch's Wal::sync \ + returns Ok and their own batch's records are durable. A per-call ordering \ + probe checks the invariant independently per in-flight call." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_1_wal_before_ack.rs b/crates/ourios-ingester/tests/rfc0003_1_wal_before_ack.rs new file mode 100644 index 000000000..e3be01f9f --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_1_wal_before_ack.rs @@ -0,0 +1,19 @@ +//! RFC0003.1 — WAL-before-ack `[§3.4]`. +//! +//! Red gate (`specified → red`): this acceptance test enumerates +//! the §5 contract and is `#[ignore]`'d until the OTLP receiver +//! lands. The implementing PR removes the `#[ignore]` and the +//! `unimplemented!()` body together. + +/// Scenario RFC0003.1 — WAL-before-ack. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.1)"] +#[test] +fn rfc0003_1_no_ack_before_every_record_is_durable() { + unimplemented!( + "RFC0003.1 — a request receives a 2xx / gRPC OK only after every record \ + in the batch is durably WAL-written (Wal::sync returns Ok). An ordering \ + probe set after sync returns is asserted true by the response writer and \ + false by every pre-sync stage (mirrors the RFC0008.1 probe)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_2_crash_before_ack.rs b/crates/ourios-ingester/tests/rfc0003_2_crash_before_ack.rs new file mode 100644 index 000000000..e83e4b437 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_2_crash_before_ack.rs @@ -0,0 +1,19 @@ +//! RFC0003.2 — Crash-before-ack: at-least-once with retry tolerance `[§3.4]`. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. Per §8 this uses a child-process harness mirroring +//! `wal_crash_fixture` (PR #126). + +/// Scenario RFC0003.2 — Crash-before-ack: at-least-once with retry tolerance. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.2)"] +#[test] +fn rfc0003_2_crash_before_ack_is_at_least_once_not_lossy() { + unimplemented!( + "RFC0003.2 — SIGKILL the receiver between Wal::sync return and ack-emit, \ + restart, re-issue the export; the post-restart WAL holds TWO OtlpBatch \ + frames each round-tripping (prost) to a request equivalent to the input. \ + Asserts no loss + safe retry; explicitly does NOT assert dedup \ + (at-least-once per the OTLP duplicate-data section)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_3_tenant_fanout.rs b/crates/ourios-ingester/tests/rfc0003_3_tenant_fanout.rs new file mode 100644 index 000000000..76e6ac2c1 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_3_tenant_fanout.rs @@ -0,0 +1,17 @@ +//! RFC0003.3 — Tenant fan-out `[§3.7]`. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.3 — Tenant fan-out. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.3)"] +#[test] +fn rfc0003_3_two_resources_fan_out_without_cross_contamination() { + unimplemented!( + "RFC0003.3 — a single export with two ResourceLogs from different sources \ + produces two distinct per-tenant streams; no record from Resource A \ + appears in tenant B's stream. Asserted via an instrumented MinerCluster \ + stub recording every accepted (tenant_id, OtlpLogRecord) pair." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_4_tenant_resolution_failure.rs b/crates/ourios-ingester/tests/rfc0003_4_tenant_resolution_failure.rs new file mode 100644 index 000000000..48437b58c --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_4_tenant_resolution_failure.rs @@ -0,0 +1,17 @@ +//! RFC0003.4 — Tenant resolution failure rejects the entire batch `[§3.7]`. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.4 — Tenant resolution failure rejects the entire batch. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.4)"] +#[test] +fn rfc0003_4_unresolvable_tenant_rejects_whole_batch() { + unimplemented!( + "RFC0003.4 — an export whose Resource attributes do not resolve to a \ + configured tenant rule is rejected with a transport-level error naming \ + the failing Resource and the missing attribute; no records are accepted \ + and no OtlpBatch frame is appended (all-or-nothing per §6.3)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_5_grpc_http_protobuf_equivalence.rs b/crates/ourios-ingester/tests/rfc0003_5_grpc_http_protobuf_equivalence.rs new file mode 100644 index 000000000..736c6ae70 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_5_grpc_http_protobuf_equivalence.rs @@ -0,0 +1,16 @@ +//! RFC0003.5 — gRPC ≡ HTTP/protobuf decode equivalence. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.5 — gRPC ≡ HTTP/protobuf decode equivalence. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.5)"] +#[test] +fn rfc0003_5_grpc_and_http_protobuf_decode_identically() { + unimplemented!( + "RFC0003.5 — a byte-equal protobuf payload delivered over gRPC and over \ + HTTP/x-protobuf produces the identical in-memory ExportLogsServiceRequest \ + (per §8, exercised by a proptest strategy over the proto value space)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_6_json_protobuf_equivalence.rs b/crates/ourios-ingester/tests/rfc0003_6_json_protobuf_equivalence.rs new file mode 100644 index 000000000..ac2086022 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_6_json_protobuf_equivalence.rs @@ -0,0 +1,18 @@ +//! RFC0003.6 — HTTP/JSON ↔ gRPC/protobuf equivalence with OTLP-JSON encoding rules. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.6 — HTTP/JSON ↔ gRPC/protobuf equivalence with OTLP-JSON encoding rules. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.6)"] +#[test] +fn rfc0003_6_json_decodes_to_the_same_anyvalue_tree_as_protobuf() { + unimplemented!( + "RFC0003.6 — a valid OTLP/JSON request (whitespace + field-ordering \ + variation; hex trace/span IDs, base64 bytes, integer-valued enums, \ + unknown fields ignored) decodes to the same in-memory AnyValue tree the \ + same logical record produces over gRPC + protobuf. Equivalence asserted \ + at the AnyValue level (canonicalisation is the storage layer's, per §6.4)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_7_body_structured_verbatim.rs b/crates/ourios-ingester/tests/rfc0003_7_body_structured_verbatim.rs new file mode 100644 index 000000000..b276bffd2 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_7_body_structured_verbatim.rs @@ -0,0 +1,17 @@ +//! RFC0003.7 — `Body::Structured` carries the decoded `AnyValue` verbatim. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.7 — `Body::Structured` carries the decoded `AnyValue` verbatim. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.7)"] +#[test] +fn rfc0003_7_structured_body_reaches_miner_as_verbatim_anyvalue() { + unimplemented!( + "RFC0003.7 — a structured body reaches the miner as \ + Body::Structured(AnyValue) structurally equal to the wire AnyValue (no \ + canonicalisation, no reshape, no dropped fields), and the same equality \ + holds across all three transports." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_8_body_string_lraw.rs b/crates/ourios-ingester/tests/rfc0003_8_body_string_lraw.rs new file mode 100644 index 000000000..f8b42761e --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_8_body_string_lraw.rs @@ -0,0 +1,17 @@ +//! RFC0003.8 — `Body::String` reaches the miner as the unwrapped `L_raw`. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.8 — `Body::String` reaches the miner as the unwrapped `L_raw`. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.8)"] +#[test] +fn rfc0003_8_string_body_reaches_miner_unwrapped() { + unimplemented!( + "RFC0003.8 — a string body becomes OtlpLogRecord.body = \ + Some(Body::String(s)) where s is the original UTF-8 string (no wrapping, \ + quoting, or escaping); the value handed to MinerCluster::ingest equals s \ + byte-for-byte (instrumented MinerCluster stub records the body argument)." + ); +} diff --git a/crates/ourios-ingester/tests/rfc0003_9_edge_otlp_fields.rs b/crates/ourios-ingester/tests/rfc0003_9_edge_otlp_fields.rs new file mode 100644 index 000000000..df7355294 --- /dev/null +++ b/crates/ourios-ingester/tests/rfc0003_9_edge_otlp_fields.rs @@ -0,0 +1,18 @@ +//! RFC0003.9 — Edge OTLP fields pass through unchanged. +//! +//! Red gate (`specified → red`): `#[ignore]`'d until the receiver +//! lands. + +/// Scenario RFC0003.9 — Edge OTLP fields pass through unchanged. +/// See `docs/rfcs/0003-otlp-receiver.md` §5. +#[ignore = "RFC 0003 red gate — implementation pending (RFC0003.9)"] +#[test] +fn rfc0003_9_edge_otlp_fields_are_not_coalesced() { + unimplemented!( + "RFC0003.9 — severity_number = 0 (UNSPECIFIED) is kept as 0, scope_name = \ + None, and wire observed_time_unix_nano = 0 maps to None (the Option \ + conversion this scenario owns). The record is accepted by \ + MinerCluster::ingest without rejection, coalescing, substitution, or \ + downcast to a default." + ); +}