Skip to content

feat(ingester): rfc 0025 green c — permanent-error quarantine; RFC green - #386

Merged
jensholdgaard merged 7 commits into
mainfrom
rfc0025-green-quarantine
Jul 6, 2026
Merged

feat(ingester): rfc 0025 green c — permanent-error quarantine; RFC green#386
jensholdgaard merged 7 commits into
mainfrom
rfc0025-green-quarantine

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Final RFC 0025 slice, discharging RFC0025.4 and RFC0025.5 — and with them all five §5 scenarios: status red → green. Closes #362.

What

  • record_quarantined audit event — kind 7 / quarantine_partition + quarantine_error columns, additive per the §3.7 discipline (old readers surface it via the Unknown tolerance path; old files read unchanged — absent-column tolerance verified by the existing accessors). Round-trip pinned alongside template + compaction kinds in one file.
  • The quarantine rule, on both wedge paths. A permanent BatchError during flush bisects the buffer (O(k·log n) probes via the batch conversion — deterministic, so a failing subset always shrinks to its poison), emits one audit event per poisoned record (tenant, partition key, error text — the WAL retains the record itself), counts each on the existing flush-error counter with error.type (no new metric name), and publishes the remainder. The cadence-drain publish_owned path applies the same rule — it would otherwise requeue poison forever, the same Absent-body OTLP records permanently wedge their partition buffer (UnsupportedAbsentBody) #362 mechanism through the second door. Transient store errors keep today's retain-and-retry.
  • Wiring: the server hands the shared audit sink to the record sink; BatchError::error_type() provides the stable snake-case attribute values.

Scenario coverage

  • .4 — poison record between healthy ones → healthy rows persist (read back and verified), one record_quarantined event naming the partition and the permanence, and the partition flushes normally afterward.
  • .5 — the exported metric stream carries error.type = timestamp_overflow on ourios.sink.flush.errors; no new instrument.

Invariants

§3.4 (WAL-before-ack) untouched — quarantine happens post-ack on the sink side, and the WAL remains the durability of record; the audit event is the operator's replay pointer. §3.3 prose in the RFC was aligned with the implementation (the event carries the partition key + error; a WAL-position pointer isn't plumbed to the sink — noted in the doc).

Verification

fmt clean; clippy clean across core/parquet/ingester/server (RC checked); quarantine scenarios 2/2; audit round-trips 8/8; miner/parquet/querier suites green from the prior slices.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added RFC0025 quarantine handling for permanently rejected ingest records, with quarantine audits emitted to the Parquet audit stream.
    • Extended audit events with record_quarantined including partition and error fields.
    • Added stable batch error type identifiers and optional flush error labeling for metrics.
  • Bug Fixes

    • Prevented partition buffer wedges when Parquet batch encoding fails.
    • Improved audit replay/projection tolerance for quarantined audit payloads.
  • Tests

    • Expanded quarantine, audit round-trip, and telemetry assertions (including schema pin updates).

The record_quarantined audit event (kind 7, quarantine_* columns,
additive per §3.7) plus the sink-side rule: a permanent BatchError
bisects the buffer, quarantines the poison record(s) to the audit
stream with the error text and partition key, counts them on the
existing flush-error counter with error.type, and publishes the
remainder — on both the buffered-flush and cadence-drain paths.
Discharges RFC0025.4/.5; all five §5 scenarios now live; status
red -> green. Closes #362.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jensholdgaard
jensholdgaard requested a review from Copilot July 6, 2026 01:16
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jensholdgaard, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d7a0003-b566-4d37-9a7e-e8e2f9c97748

📥 Commits

Reviewing files that changed from the base of the PR and between 53f61e3 and 7bf0d0c.

📒 Files selected for processing (1)
  • crates/ourios-ingester/src/record_sink.rs
📝 Walkthrough

Walkthrough

Adds a new RecordQuarantined audit event and propagates it through Parquet encoding/decoding, ingester quarantine handling, metrics, server wiring, tests, and RFC documentation. Permanently rejected records are now quarantined instead of wedging partition flushes.

Changes

RFC 0025 Quarantine Feature

Layer / File(s) Summary
RecordQuarantined audit payload and event kind/type
crates/ourios-core/src/audit.rs, crates/ourios-core/src/alias.rs
Adds AuditPayload::RecordQuarantined, stable event kind/type constants, and updates event mapping and merge classification; alias replay ignores the new variant.
Parquet quarantine columns and round-trip support
crates/ourios-parquet/src/lib.rs, crates/ourios-parquet/src/audit_record_batch.rs, crates/ourios-parquet/src/audit_reader.rs, crates/ourios-parquet/tests/audit_round_trip.rs, crates/ourios-parquet/tests/schema_pin.rs
Adds quarantine schema columns and builders, writes and reads quarantine rows, and extends round-trip/schema pin coverage.
BatchError telemetry classification
crates/ourios-parquet/src/record_batch.rs, crates/ourios-ingester/src/metrics.rs, crates/ourios-ingester/tests/perf_metrics.rs
Adds stable error type strings for batch errors and lets flush metrics record an optional error.type attribute.
ParquetRecordSink quarantine handling
crates/ourios-ingester/src/record_sink.rs
Adds quarantine audit sink wiring, batch splitting, quarantine helpers, and updated flush/publish behavior for permanently rejected records.
Shared audit sink wiring
crates/ourios-server/src/receiver.rs
Clones the shared audit sink for quarantine handling and passes it into the record sink.
RFC 0025 quarantine and telemetry tests
crates/ourios-ingester/tests/rfc0025_quarantine.rs
Adds quarantine integration tests, telemetry assertions, and supporting helpers.
RFC documentation update
docs/rfcs/0025-absent-body-representation.md
Changes the RFC status and revises the quarantine design text.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the main change: RFC 0025 quarantine handling and green status.
Description check ✅ Passed It includes a strong summary, linked issue reference, scenario coverage, invariants, and verification, though not the exact template headings.
Linked Issues check ✅ Passed The changes implement the sink-side quarantine guard, new audit event, and compatibility behavior required for #362 without breaking WAL semantics.
Out of Scope Changes check ✅ Passed The added tests, docs, metrics, schema, and server wiring all support the RFC 0025 quarantine work; no unrelated changes stand out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc0025-green-quarantine

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes RFC 0025 by implementing sink-side quarantine for permanently-encodable record failures (preventing partition wedges), adding a new record_quarantined audit event that round-trips through the audit parquet format, and wiring telemetry (error.type) onto the existing flush-error counter. It also updates the RFC status to green and adds/updates tests to cover the final scenarios.

Changes:

  • Add AuditPayload::RecordQuarantined (kind 7 / record_quarantined) plus new audit parquet columns (quarantine_partition, quarantine_error) with backward-compatible absent-column handling.
  • Implement quarantine behavior in both flush paths (inline flush_partition and cadence publish_owned) and attach error.type to ourios.sink.flush.errors for quarantined rejections.
  • Add tests for audit round-trip and the RFC0025.4/.5 quarantine + telemetry scenarios; update server wiring to pass the shared audit sink into the record sink.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/rfcs/0025-absent-body-representation.md Marks RFC 0025 green and aligns §3.3 prose with implementation details (no WAL position in event).
crates/ourios-server/src/receiver.rs Wires the shared audit sink into the record sink for quarantine events.
crates/ourios-parquet/tests/audit_round_trip.rs Adds a round-trip test for the new record_quarantined audit payload.
crates/ourios-parquet/src/record_batch.rs Adds BatchError::error_type() for stable error.type attribute values.
crates/ourios-parquet/src/lib.rs Extends the audit schema with quarantine_* columns (additive).
crates/ourios-parquet/src/audit_record_batch.rs Writes RecordQuarantined rows with quarantine columns populated and other payload groups NULL.
crates/ourios-parquet/src/audit_reader.rs Reads quarantine columns with absent-column tolerance; decodes kind 7 into AuditPayload::RecordQuarantined.
crates/ourios-ingester/tests/rfc0025_quarantine.rs Implements RFC0025.4 and RFC0025.5 tests (quarantine behavior + error.type telemetry).
crates/ourios-ingester/tests/perf_metrics.rs Updates for the new record_flush_error(Option<...>) signature.
crates/ourios-ingester/src/record_sink.rs Implements buffer bisection/quarantine on WriterError::Batch in both flush paths.
crates/ourios-ingester/src/metrics.rs Adds optional error.type attribute support on the existing flush-error counter.
crates/ourios-core/src/audit.rs Adds new audit payload + kind/type constants and mapping for record_quarantined.
crates/ourios-core/src/alias.rs Treats RecordQuarantined as a non-alias event kind (ignored in alias map fold).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ourios-ingester/src/record_sink.rs Outdated
Comment thread crates/ourios-ingester/src/record_sink.rs Outdated
The schema-pin gate fired exactly as designed; RFC 0025 §3.3 is the
amendment that authorizes the additive column pair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jensholdgaard
jensholdgaard requested a review from Copilot July 6, 2026 01:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/ourios-parquet/src/audit_record_batch.rs (2)

366-387: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale comment / inconsistent null-call placement for Compaction arm.

The comment at lines 373-377 documents that compaction events null "every template-specific and alias column" but omits that quarantine columns are also nulled. Additionally, append_quarantine_nulls() (line 386) is called after the compaction values are written, unlike every other arm where all *_nulls() calls are grouped together up front. Functionally correct (append order across independent builders doesn't matter), but the drift makes it easy for a future contributor extending this match to miss nulling a new group.

♻️ Suggested cleanup
                 // Compaction events leave every template-specific
                 // and alias column NULL (§3.7 relaxed the former to
                 // OPTIONAL; the latter are alias-kind-only), and the
                 // facts live in the `compaction_*` columns — `reason`
-                // stays NULL.
+                // stays NULL. Quarantine columns are likewise NULL.
                 self.append_template_nulls();
                 self.append_alias_nulls();
+                self.append_quarantine_nulls();
                 self.reason.append_null();
                 self.compaction_partition.append_value(partition);
                 append_string_list(&mut self.compaction_input_files, input_files);
                 self.compaction_output_file.append_value(output_file);
                 self.compaction_generation.append_value(*generation);
                 self.compaction_rows.append_value(*rows);
-                self.append_quarantine_nulls();
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-parquet/src/audit_record_batch.rs` around lines 366 - 387, In
the AuditPayload::Compaction match arm, the inline comment is stale and the
null-appends are split inconsistently. Update the comment to mention quarantine
columns are also nulled, and move append_quarantine_nulls() into the grouped
*_nulls() calls at the top with append_template_nulls(), append_alias_nulls(),
and reason.append_null() so the nulling pattern matches the other arms in
audit_record_batch.rs.

213-214: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Quarantine fields interleaved into compaction group.

quarantine_partition/quarantine_error are declared and initialized between compaction_partition and the rest of the compaction_* fields, splitting up the compaction group. Field order doesn't affect correctness (struct-literal init is by name), but grouping them with the other quarantine-adjacent code (e.g., after alias_actor, matching audit_schema()'s column order) would keep the mental model consistent with the rest of the file.

Also applies to: 270-271

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-parquet/src/audit_record_batch.rs` around lines 213 - 214, The
`AuditRecordBatch` field initialization in `audit_record_batch.rs` has
`quarantine_partition` and `quarantine_error` interleaved into the
`compaction_*` block, which breaks the logical grouping. Reorder the struct
fields and their initialization in `AuditRecordBatch::new` (and any matching
setup around `audit_schema()`) so the quarantine fields sit with the other
quarantine-adjacent fields, ideally after `alias_actor`, while keeping the
`compaction_*` fields together.
crates/ourios-ingester/src/record_sink.rs (2)

374-398: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider a structured log alongside the metric/audit event for quarantined records.

Right now a quarantine is only visible via the OTel counter and the buffered audit-Parquet event (not queryable until that sink flushes). A warn-level structured log at quarantine time would give operators immediate visibility into a permanently-dropped record without waiting on the audit sink.

As per path instructions, **/crates/ourios-{ingester,querier,server}/**/*.rs: "Use Prometheus metrics for every subsystem, emit structured logs via Ourios on hot paths, and trace every RPC."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-ingester/src/record_sink.rs` around lines 374 - 398, Add a
warn-level structured log when records are quarantined in quarantine_owned so
operators get immediate visibility before the audit sink flushes. Use the
existing quarantine flow in record_sink.rs around split_poisoned, the per-record
loop, and AuditEvent emission to log key context such as the partition,
tenant_id, and the error from the poisoned record. Keep the Prometheus metric
and audit payload behavior unchanged, and place the log alongside the existing
record_flush_error call so all quarantine signals stay aligned.

Source: Path instructions


145-174: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct unit tests for split_poisoned's bisection cases. The current coverage only exercises one poisoned record through rfc0025_quarantine.rs; add a small unit test module here for multiple poisoned records, non-contiguous poison, and the all-poisoned buffer edge case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/ourios-ingester/src/record_sink.rs` around lines 145 - 174, Add direct
unit tests for split_poisoned in record_sink.rs to cover the bisection logic
itself, since current coverage only hits a single quarantined record elsewhere.
Create a small test module that exercises split_poisoned with multiple poisoned
records, non-contiguous poison in the input, and the all-poisoned buffer edge
case, and assert the returned kept versus poisoned partitions are correct. Use
split_poisoned and mined_records_to_batch_with_promoted as the key symbols when
locating the behavior under test.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ourios-ingester/src/record_sink.rs`:
- Around line 315-332: Full-quarantine handling in the `WriterError::Batch`
branch of `flush_partition` leaves an empty `PartitionBuffer` in `self.buffers`
and skips the normal cleanup/accounting path. When `quarantine_poisoned(key)`
empties the buffer, make sure the code still removes the entry and reconciles
`self.total_bytes`/`buffer_usage` instead of returning early. Update the
early-return path around `self.buffers.get(key)` so it either performs the same
cleanup as the success path or funnels through the existing
`self.buffers.remove(key)` logic, and ensure `oldest` is cleared/updated so
`flush_aged` does not keep reselecting the partition.

---

Nitpick comments:
In `@crates/ourios-ingester/src/record_sink.rs`:
- Around line 374-398: Add a warn-level structured log when records are
quarantined in quarantine_owned so operators get immediate visibility before the
audit sink flushes. Use the existing quarantine flow in record_sink.rs around
split_poisoned, the per-record loop, and AuditEvent emission to log key context
such as the partition, tenant_id, and the error from the poisoned record. Keep
the Prometheus metric and audit payload behavior unchanged, and place the log
alongside the existing record_flush_error call so all quarantine signals stay
aligned.
- Around line 145-174: Add direct unit tests for split_poisoned in
record_sink.rs to cover the bisection logic itself, since current coverage only
hits a single quarantined record elsewhere. Create a small test module that
exercises split_poisoned with multiple poisoned records, non-contiguous poison
in the input, and the all-poisoned buffer edge case, and assert the returned
kept versus poisoned partitions are correct. Use split_poisoned and
mined_records_to_batch_with_promoted as the key symbols when locating the
behavior under test.

In `@crates/ourios-parquet/src/audit_record_batch.rs`:
- Around line 366-387: In the AuditPayload::Compaction match arm, the inline
comment is stale and the null-appends are split inconsistently. Update the
comment to mention quarantine columns are also nulled, and move
append_quarantine_nulls() into the grouped *_nulls() calls at the top with
append_template_nulls(), append_alias_nulls(), and reason.append_null() so the
nulling pattern matches the other arms in audit_record_batch.rs.
- Around line 213-214: The `AuditRecordBatch` field initialization in
`audit_record_batch.rs` has `quarantine_partition` and `quarantine_error`
interleaved into the `compaction_*` block, which breaks the logical grouping.
Reorder the struct fields and their initialization in `AuditRecordBatch::new`
(and any matching setup around `audit_schema()`) so the quarantine fields sit
with the other quarantine-adjacent fields, ideally after `alias_actor`, while
keeping the `compaction_*` fields together.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4113d0aa-15d4-45eb-b6b0-ad98d32ca230

📥 Commits

Reviewing files that changed from the base of the PR and between d22c0cc and 42491a6.

📒 Files selected for processing (13)
  • crates/ourios-core/src/alias.rs
  • crates/ourios-core/src/audit.rs
  • crates/ourios-ingester/src/metrics.rs
  • crates/ourios-ingester/src/record_sink.rs
  • crates/ourios-ingester/tests/perf_metrics.rs
  • crates/ourios-ingester/tests/rfc0025_quarantine.rs
  • crates/ourios-parquet/src/audit_reader.rs
  • crates/ourios-parquet/src/audit_record_batch.rs
  • crates/ourios-parquet/src/lib.rs
  • crates/ourios-parquet/src/record_batch.rs
  • crates/ourios-parquet/tests/audit_round_trip.rs
  • crates/ourios-server/src/receiver.rs
  • docs/rfcs/0025-absent-body-representation.md

Comment thread crates/ourios-ingester/src/record_sink.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/record_sink.rs
BatchError::Arrow is an internal invariant violation, not a
per-record rejection — it retains the buffer (bisect keeps the
record too); an all-quarantined partition drops its emptied entry
and releases byte accounting so the ceiling logic and buffer gauge
can't drift. Both pinned by tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/record_sink.rs
Partial quarantine now subtracts the dropped records' estimate (the
emit-side estimator) from the buffer entry, total_bytes, and the
buffer gauge at quarantine time — no transient overcount window for
the ceiling triggers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/src/record_sink.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment thread crates/ourios-ingester/tests/rfc0025_quarantine.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Comment thread crates/ourios-ingester/src/record_sink.rs Outdated
Comment thread crates/ourios-ingester/src/record_sink.rs
Comment thread crates/ourios-ingester/src/record_sink.rs Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

@jensholdgaard
jensholdgaard merged commit a1257cf into main Jul 6, 2026
22 checks passed
@jensholdgaard
jensholdgaard deleted the rfc0025-green-quarantine branch July 6, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Absent-body OTLP records permanently wedge their partition buffer (UnsupportedAbsentBody)

2 participants