Skip to content

feat: Framer + CriAggregator — zero-copy, Kani-proven line splitting (Steps 4-5) - #311

Merged
strawgate merged 3 commits into
masterfrom
step4/framer-aggregator
Mar 31, 2026
Merged

strawgate merged 3 commits into
masterfrom
step4/framer-aggregator

Conversation

@strawgate

Copy link
Copy Markdown
Owner

Summary

Steps 4-5 of the zero-copy pipeline plan. Adds two new proven modules to logfwd-core:

framer.rs — Line boundary detection

Identifies line boundaries without copying data. Returns byte ranges into the input buffer.

  • NewlineFramer: splits on \n using plain byte loops (no memchr)
  • CriFramer: splits on \n, then extracts CRI timestamp/stream/flags/message ranges
  • find_byte: plain loop replacement for memchr, Kani-provable

No memchr, no Vec, no heap allocation in the hot path. LLVM auto-vectorizes the byte scan loops. Fixed-size FrameOutput (4096 lines, 64KB stack).

aggregator.rs — CRI P/F partial line merging

  • F-only (99% case): returns reference to input — zero copy
  • P lines: copies message into internal Vec (frees read buffer refcount)
  • P+F: concatenates from internal buffer
  • max_message_size enforced on ALL paths including F-only

Kani proofs (7 new, 22 total)

Proof Property Time
verify_newline_framer_no_panic No panics for any 32-byte input 1.1s
verify_newline_framer_ranges_valid All ranges are valid sub-ranges 65s
verify_newline_framer_remainder_correct No newlines in remainder 0.1s
verify_find_byte First-match-or-none correct for any 16-byte input 0.1s
verify_aggregator_f_only_max_size F output ≤ max_message_size 0.2s
verify_aggregator_pf_max_size P+F output ≤ max_message_size 0.9s
verify_aggregator_p_returns_pending P lines never produce output 0.3s

Why no memchr?

memchr uses SIMD internally but Kani can't model it (issue #632). By writing plain byte loops, we get:

  1. Kani formal proofs (verified for all possible inputs)
  2. LLVM auto-vectorization (same SIMD perf in practice)
  3. One version to maintain (not scalar + SIMD)

Test plan

  • 128 unit tests pass (8 new framer + aggregator tests)
  • 7 Kani proofs pass
  • cargo clippy -- -D warnings clean
  • cargo fmt --check clean

Part of #303 (zero-copy pipeline). Parent: #262.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Mar 31, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fea580ab-964e-424d-bf84-ec2e42102a8f

📥 Commits

Reviewing files that changed from the base of the PR and between 17e3d99 and 0b4d358.

📒 Files selected for processing (3)
  • crates/logfwd-core/src/aggregator.rs
  • crates/logfwd-core/src/framer.rs
  • dev-docs/PROOF_AUDIT.md

Walkthrough

Adds three new public modules to logfwd-core: aggregator (CRI partial-line aggregator with bounded buffering, zero-copy complete results when possible, truncation, reset, and an AggregateResult), framer (newline-based framing that emits line byte ranges and a remainder offset), and byte_search (forward/reverse byte-search helpers and an iterator). Exports for these modules were added in lib.rs. cri.rs now uses byte_search::find_byte for delimiter scanning. Adds unit tests, Kani verification proofs, and a proof-audit document (dev-docs/PROOF_AUDIT.md).


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

@strawgate
strawgate force-pushed the step4/framer-aggregator branch from 020f50c to 01253d0 Compare March 31, 2026 02:06

@github-actions github-actions Bot 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.

Requesting changes due to two correctness issues introduced in this PR:

  1. crates/logfwd-core/src/framer.rs (around line 93): when MAX_LINES_PER_FRAME is reached, the loop still advances start on later newlines. As a result, extra complete lines are neither emitted nor preserved in remainder (data loss). A concrete trigger is input with more than 4096 newline-terminated lines.

  2. crates/logfwd-core/src/framer.rs (around line 194): CRI flags parsing treats any token starting with F (e.g. FOO) as full. This can incorrectly flush pending partial aggregates on malformed CRI input. Please require exact F/P flag tokens (or reject malformed lines).


What is this? | From workflow: AI: PR Review

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/logfwd-core/src/aggregator.rs`:
- Around line 20-31: The doc comment for the aggregator currently describes
returning Some/None but the public API now returns AggregateResult; update the
documentation in crates/logfwd-core/src/aggregator.rs to describe the
AggregateResult enum and its variants (AggregateResult::Complete(payload) for
finished messages and AggregateResult::Pending for buffered partial lines),
adjust the lifecycle examples to use Complete/Pending instead of Some/None, and
ensure any example text mentions the zero-copy fast path for Complete to keep
behavior accurate (reference AggregateResult and the aggregator function that
produces it).

In `@crates/logfwd-core/src/framer.rs`:
- Around line 157-159: The CRI flag parsing in frame (which uses NewlineFramer
and returns CriFrameOutput) currently treats any non-'F' first byte as partial
and accepts multi-byte/missing flags; change the parser so it only accepts a
single-byte ASCII flag that is exactly 'F' (full) or 'P' (partial) and rejects
(i.e., skip) lines where the flag is missing, multi-byte, or any other value;
update the is_full/is_partial decision in the frame parsing code (and the same
logic around the block at/near the existing check referenced by lines ~192-197)
to skip malformed CRI lines instead of coercing them to partial, preserving
aggregation boundaries.
- Around line 130-132: CriFrameOutput currently uses heap-allocating
Vec<CriLineInfo> (and Vec::with_capacity in frame()), which violates the
"fixed-size, no heap allocation" contract; change CriFrameOutput to mirror
FrameOutput by using a fixed-capacity array/buffer and an explicit count field
instead of Vec: replace lines: Vec<CriLineInfo> with something like lines:
[CriLineInfo; MAX_CRI_LINES_PER_FRAME] plus a lines_len: usize, remove any
Vec::with_capacity/new allocations in frame() and update push/iteration code to
use the count and indexed access, and ensure all places that constructed or
returned CriFrameOutput use the new fixed-size struct and the
MAX_CRI_LINES_PER_FRAME constant.
- Around line 91-101: In NewlineFramer::frame the loop currently skips emitting
empty lines and continues advancing start past un-emitted data when output.count
reaches MAX_LINES_PER_FRAME; update the loop so blank lines are emitted (change
the i > start check so a zero-length line at i==start is recorded into
output.line_ranges/output.count) and when output.count == MAX_LINES_PER_FRAME
stop processing further bytes instead of advancing start (break the loop and
leave output.remainder_offset pointing at start so the caller receives the
remaining data). Make these changes in the function handling input, i, start,
output.count, MAX_LINES_PER_FRAME, output.line_ranges and
output.remainder_offset.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9298e702-e84d-4737-aab6-39bc0c512f99

📥 Commits

Reviewing files that changed from the base of the PR and between 0652581 and 020f50c.

📒 Files selected for processing (3)
  • crates/logfwd-core/src/aggregator.rs
  • crates/logfwd-core/src/framer.rs
  • crates/logfwd-core/src/lib.rs

Comment thread crates/logfwd-core/src/aggregator.rs Resolved
Comment thread crates/logfwd-core/src/framer.rs Resolved
Comment thread crates/logfwd-core/src/framer.rs Resolved
Comment thread crates/logfwd-core/src/framer.rs Resolved
@strawgate
strawgate force-pushed the step4/framer-aggregator branch from 01253d0 to 3ce71ba Compare March 31, 2026 02:13

@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.

♻️ Duplicate comments (1)
crates/logfwd-core/src/framer.rs (1)

93-103: ⚠️ Potential issue | 🟠 Major

Lines are silently dropped when MAX_LINES_PER_FRAME is reached.

When output.count == MAX_LINES_PER_FRAME, the loop continues advancing start without emitting lines. The final remainder_offset = start then skips those un-emitted lines entirely. The caller loses data.

The fix: break the loop when capacity is exhausted, leaving remainder_offset at the first byte of the un-emitted line.

🐛 Suggested fix
         while i < input.len() {
             if input[i] == b'\n' {
-                if i > start && output.count < MAX_LINES_PER_FRAME {
+                if output.count == MAX_LINES_PER_FRAME {
+                    // Capacity full — stop and leave remainder at start
+                    break;
+                }
+                if i > start {
                     output.line_ranges[output.count] = (start, i);
                     output.count += 1;
                 }
                 start = i + 1;
             }
             i += 1;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/logfwd-core/src/framer.rs` around lines 93 - 103, The loop in
framer.rs that scans input by newline can advance start past lines when
output.count reaches MAX_LINES_PER_FRAME, causing un-emitted lines to be
dropped; change the logic in the while loop that updates output.line_ranges and
start so that when output.count == MAX_LINES_PER_FRAME you stop emitting further
lines and break out of the loop (do not update start to skip the first
un-emitted line), leaving output.remainder_offset pointing at the start of that
un-emitted line; touch the variables/locations referenced (input, start, i,
output.count, output.line_ranges, MAX_LINES_PER_FRAME, output.remainder_offset)
to ensure the loop exits early instead of continuing to advance start.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/logfwd-core/src/framer.rs`:
- Around line 93-103: The loop in framer.rs that scans input by newline can
advance start past lines when output.count reaches MAX_LINES_PER_FRAME, causing
un-emitted lines to be dropped; change the logic in the while loop that updates
output.line_ranges and start so that when output.count == MAX_LINES_PER_FRAME
you stop emitting further lines and break out of the loop (do not update start
to skip the first un-emitted line), leaving output.remainder_offset pointing at
the start of that un-emitted line; touch the variables/locations referenced
(input, start, i, output.count, output.line_ranges, MAX_LINES_PER_FRAME,
output.remainder_offset) to ensure the loop exits early instead of continuing to
advance start.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2605e128-9dad-48e4-aff0-d6d34fb00a82

📥 Commits

Reviewing files that changed from the base of the PR and between 020f50c and 3ce71ba.

📒 Files selected for processing (5)
  • crates/logfwd-core/src/aggregator.rs
  • crates/logfwd-core/src/byte_search.rs
  • crates/logfwd-core/src/cri.rs
  • crates/logfwd-core/src/framer.rs
  • crates/logfwd-core/src/lib.rs

@strawgate
strawgate force-pushed the step4/framer-aggregator branch from 3ce71ba to 66f9605 Compare March 31, 2026 02:32
@github-actions

Copy link
Copy Markdown
Contributor

Implemented and pushed fixes for all 3 unresolved review threads in crates/logfwd-core/src/framer.rs.

Addressed with code changes:

  • PRRT_kwDORzg8fM531Tnd: NewlineFramer::frame now emits empty lines and stops at MAX_LINES_PER_FRAME without advancing past un-emitted data.
  • PRRT_kwDORzg8fM531Tnf: CriFrameOutput now uses fixed-capacity storage ([CriLineInfo; MAX_CRI_LINES_PER_FRAME] + count) with no Vec allocation in frame().
  • PRRT_kwDORzg8fM531Tng: CRI flags now accept only single-byte F or P; malformed/multi-byte flags are rejected.

Replied instead of fixing:

  • None.
Validation run
  • RUSTC_WRAPPER='' cargo fmt --check ✅
  • RUSTC_WRAPPER='' cargo test -p logfwd-core ✅ (134 unit tests + compliance + conformance + vectorization canary)
  • RUSTC_WRAPPER='' cargo clippy -p logfwd-core -- -D warnings ✅

Note

🔒 Integrity filtering filtered 1 item

Integrity filtering activated and filtered the following item during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.


What is this? | From workflow: AI: Address PR Review Feedback

Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.

New modules for the zero-copy pipeline:

framer.rs — identifies line boundaries without memchr or Vec:
  - NewlineFramer: splits on \n, returns fixed-size FrameOutput
  - CriFramer: splits on \n, extracts CRI field ranges (zero-copy)
  - find_byte: plain loop (no memchr) for Kani provability
  - LLVM auto-vectorizes the byte scan loops
  - 4 Kani proofs: no-panic (32 bytes), ranges-valid, remainder-correct, find_byte

aggregator.rs — CRI P/F partial line merging:
  - F-only fast path: zero-copy (returns reference to input)
  - P accumulation: copies into internal Vec (frees read buffer)
  - P+F emission: concatenated result from internal buffer
  - max_message_size enforced on all paths (including F-only)
  - 3 Kani proofs: F-only max size, P+F max size, P returns Pending

Design choices:
  - No memchr dependency — plain byte loops are Kani-provable AND
    LLVM auto-vectorizes them (verified with vectorization_canary pattern)
  - Fixed-size FrameOutput (4096 entries, 64KB) — no heap allocation
  - CriFramer delegates line splitting to NewlineFramer, adds field parsing

Total Kani proofs in codebase: 22 (13 prior + 4 framer + 3 aggregator + 2 CRI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@strawgate
strawgate force-pushed the step4/framer-aggregator branch from 3259622 to 17e3d99 Compare March 31, 2026 02:34

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/logfwd-core/src/cri.rs (1)

98-112: 🧹 Nitpick | 🔵 Trivial

CriReassembler F-only path copies (unlike CriAggregator).

Lines 100-111 always copy into partial_buf, even when no partials are pending. This differs from CriAggregator::feed which returns a zero-copy slice for F-only.

The difference stems from lifetime constraints: CriReassembler::feed takes &CriLine<'_> where the message borrows external data, so returning it directly would tie the output to the caller's input lifetime. CriAggregator uses &'a mut self, message: &'a [u8] to enable zero-copy.

Consider documenting this design difference or consolidating if callers can be migrated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/logfwd-core/src/cri.rs` around lines 98 - 112, CriReassembler::feed
currently always copies F-only messages into partial_buf (via
partial_buf.extend_from_slice) because it accepts cri: &CriLine<'_> whose
message borrows an external, shorter lifetime, unlike CriAggregator::feed which
accepts message: &'a [u8] and can return a zero-copy slice; pick one of two
fixes: either document this design difference in the CriReassembler::feed doc
comment explaining why F-only lines are copied (referencing
CriReassembler::feed, partial_buf, max_line_size and contrast to
CriAggregator::feed), or change the API so CriReassembler::feed takes cri with a
matching output lifetime (e.g. accept the message as &'a [u8] or change the
CriLine lifetime parameter) allowing you to return &self-like slices and avoid
the copy (update signatures of callers accordingly); implement the approach that
matches call-site ownership (prefer documentation if callers cannot be
migrated).
♻️ Duplicate comments (4)
crates/logfwd-core/src/framer.rs (3)

132-137: ⚠️ Potential issue | 🟠 Major

CriFrameOutput heap-allocates despite "no heap allocation" doc.

Line 132 claims "Fixed-size, no heap allocation" but line 134 uses Vec<CriLineInfo>. Line 163 calls Vec::with_capacity() on every frame() call.

CriLineInfo contains only (usize, usize) tuples and bool — all Copy types. It can use a fixed array like FrameOutput does.

This was flagged previously. Based on learnings: "Avoid per-line heap allocations in hot paths (scanner, CRI parser)".

♻️ Use fixed-size array
+impl Copy for CriLineInfo {}
+impl Clone for CriLineInfo {
+    fn clone(&self) -> Self { *self }
+}
+
 /// Output of CRI framing. Fixed-size, no heap allocation for line storage.
 pub struct CriFrameOutput {
-    lines: Vec<CriLineInfo>, // CriLineInfo is non-Copy (contains ranges), use Vec
+    lines: [CriLineInfo; MAX_CRI_LINES_PER_FRAME],
+    count: usize,
     /// Byte offset of the first unprocessed byte.
     pub remainder_offset: usize,
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/logfwd-core/src/framer.rs` around lines 132 - 137, CriFrameOutput
currently uses a Vec<CriLineInfo> and calls Vec::with_capacity() in frame(),
which contradicts the "no heap allocation" doc and causes per-frame heap work;
change CriFrameOutput to store a fixed-size array (e.g. [CriLineInfo; N]) plus a
length counter (like FrameOutput does) and make CriLineInfo derive/copy types
(implement Copy/Clone if needed) so you can push entries without allocation;
update the frame() implementation to write into the array and increment the
count instead of creating a Vec, and adjust any consumers that iterate lines to
use the length counter and array slice.

91-105: ⚠️ Potential issue | 🔴 Critical

Lines are silently dropped when MAX_LINES_PER_FRAME is reached.

When output.count hits MAX_LINES_PER_FRAME, the loop continues advancing start past complete lines without recording them. On exit, remainder_offset = start skips those lines entirely — they're neither in line_ranges nor in the remainder for the next cycle.

This was flagged in a previous review but remains unaddressed.

🐛 Fix: break when output is full
     while i < input.len() {
         if input[i] == b'\n' {
-            if i > start && output.count < MAX_LINES_PER_FRAME {
+            if output.count == MAX_LINES_PER_FRAME {
+                // Output full — leave remaining bytes for next frame
+                break;
+            }
+            if i > start {
                 output.line_ranges[output.count] = (start, i);
                 output.count += 1;
             }
             start = i + 1;
         }
         i += 1;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/logfwd-core/src/framer.rs` around lines 91 - 105, The loop in framer
that scans input bytes currently continues advancing start after
MAX_LINES_PER_FRAME is reached, causing completed lines to be skipped; update
the loop so that when input[i] == b'\n' and output.count == MAX_LINES_PER_FRAME
you stop processing further lines (break/return) without advancing start so
those remaining lines become the remainder. Concretely: in the branch handling
newline, check output.count against MAX_LINES_PER_FRAME before assigning
output.line_ranges/output.count and before setting start; if full, set
output.remainder_offset = start (or leave start unchanged) and break/return
immediately so no lines are silently dropped; adjust use of output.line_ranges,
output.count and output.remainder_offset accordingly.

194-199: ⚠️ Potential issue | 🟠 Major

Malformed CRI flags accepted instead of rejected.

Only the first byte is checked (line[flags_start] == b'F'). Invalid flags like X, FP, or multi-byte flags parse "successfully" with is_full = false. This corrupts downstream aggregation.

Previously flagged but unaddressed.

🛠️ Validate flag strictly
     let flags_start = sp2 + 1;
-    let (is_full, msg_offset) = if let Some(sp3) = find_byte(line, b' ', flags_start) {
-        (line[flags_start] == b'F', sp3 + 1)
-    } else {
-        (line.get(flags_start) == Some(&b'F'), line.len())
-    };
+    let flag = *line.get(flags_start)?;
+    let is_full = match flag {
+        b'F' => true,
+        b'P' => false,
+        _ => return None, // Invalid flag
+    };
+    let msg_offset = match line.get(flags_start + 1) {
+        Some(b' ') => flags_start + 2,
+        None => line.len(),
+        _ => return None, // Multi-byte flag or missing space
+    };
crates/logfwd-core/src/aggregator.rs (1)

20-31: ⚠️ Potential issue | 🟡 Minor

Doc comments still reference Some/None instead of AggregateResult.

The examples mention None and Some("...") but the actual API returns AggregateResult::Pending and AggregateResult::Complete. This was flagged previously and marked addressed, but the text remains inconsistent.

📝 Suggested fix
 /// Feed CRI message bytes with their P/F flag. The aggregator returns
-/// `Some(message)` when a complete message is ready (on F lines), or
-/// `None` for P lines (buffered internally).
+/// `AggregateResult::Complete(message)` when a complete message is ready
+/// (on F lines), or `AggregateResult::Pending` for P lines (buffered internally).
 ///
 /// # Lifecycle
 ///
 /// ```text
-/// P "hello "  → None (buffered)
-/// P "world"   → None (buffered)
-/// F "!"       → Some("hello world!")
-/// F "simple"  → Some("simple")  // zero-copy fast path
+/// P "hello "  → Pending (buffered)
+/// P "world"   → Pending (buffered)
+/// F "!"       → Complete("hello world!")
+/// F "simple"  → Complete("simple")  // zero-copy fast path
 /// ```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/logfwd-core/src/aggregator.rs` around lines 20 - 31, Doc comments in
aggregator.rs still use Some/None in the examples but the API returns
AggregateResult::Pending and AggregateResult::Complete; update the example lines
inside the doc block (the P/F lifecycle example) to replace "None" with "Pending
(buffered)" and "Some(\"...\")" with "Complete(\"...\")" (or explicitly
`AggregateResult::Pending` / `AggregateResult::Complete` if you prefer
fully-qualified names) so the examples match the actual return type used by the
aggregator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@crates/logfwd-core/src/cri.rs`:
- Around line 98-112: CriReassembler::feed currently always copies F-only
messages into partial_buf (via partial_buf.extend_from_slice) because it accepts
cri: &CriLine<'_> whose message borrows an external, shorter lifetime, unlike
CriAggregator::feed which accepts message: &'a [u8] and can return a zero-copy
slice; pick one of two fixes: either document this design difference in the
CriReassembler::feed doc comment explaining why F-only lines are copied
(referencing CriReassembler::feed, partial_buf, max_line_size and contrast to
CriAggregator::feed), or change the API so CriReassembler::feed takes cri with a
matching output lifetime (e.g. accept the message as &'a [u8] or change the
CriLine lifetime parameter) allowing you to return &self-like slices and avoid
the copy (update signatures of callers accordingly); implement the approach that
matches call-site ownership (prefer documentation if callers cannot be
migrated).

---

Duplicate comments:
In `@crates/logfwd-core/src/aggregator.rs`:
- Around line 20-31: Doc comments in aggregator.rs still use Some/None in the
examples but the API returns AggregateResult::Pending and
AggregateResult::Complete; update the example lines inside the doc block (the
P/F lifecycle example) to replace "None" with "Pending (buffered)" and
"Some(\"...\")" with "Complete(\"...\")" (or explicitly
`AggregateResult::Pending` / `AggregateResult::Complete` if you prefer
fully-qualified names) so the examples match the actual return type used by the
aggregator.

In `@crates/logfwd-core/src/framer.rs`:
- Around line 132-137: CriFrameOutput currently uses a Vec<CriLineInfo> and
calls Vec::with_capacity() in frame(), which contradicts the "no heap
allocation" doc and causes per-frame heap work; change CriFrameOutput to store a
fixed-size array (e.g. [CriLineInfo; N]) plus a length counter (like FrameOutput
does) and make CriLineInfo derive/copy types (implement Copy/Clone if needed) so
you can push entries without allocation; update the frame() implementation to
write into the array and increment the count instead of creating a Vec, and
adjust any consumers that iterate lines to use the length counter and array
slice.
- Around line 91-105: The loop in framer that scans input bytes currently
continues advancing start after MAX_LINES_PER_FRAME is reached, causing
completed lines to be skipped; update the loop so that when input[i] == b'\n'
and output.count == MAX_LINES_PER_FRAME you stop processing further lines
(break/return) without advancing start so those remaining lines become the
remainder. Concretely: in the branch handling newline, check output.count
against MAX_LINES_PER_FRAME before assigning output.line_ranges/output.count and
before setting start; if full, set output.remainder_offset = start (or leave
start unchanged) and break/return immediately so no lines are silently dropped;
adjust use of output.line_ranges, output.count and output.remainder_offset
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb43c1c4-fe01-4960-b255-42bc8a90d8c2

📥 Commits

Reviewing files that changed from the base of the PR and between 3ce71ba and 17e3d99.

📒 Files selected for processing (6)
  • crates/logfwd-core/src/aggregator.rs
  • crates/logfwd-core/src/byte_search.rs
  • crates/logfwd-core/src/cri.rs
  • crates/logfwd-core/src/framer.rs
  • crates/logfwd-core/src/lib.rs
  • dev-docs/PROOF_AUDIT.md

strawgate and others added 2 commits March 30, 2026 21:55
- Remove CriFramer/CriFrameOutput/CriLineInfo from framer.rs — CRI
  framing architecture deferred to #313 (unified SIMD benchmark).
  CRI parsing remains in cri.rs (parse_cri_line + CriReassembler).
- Fix aggregator doc comments to reference AggregateResult instead
  of Option (addresses CodeRabbit review feedback).
- Update PROOF_AUDIT.md: add content_correct oracle proof, close
  NewlineFramer correctness gap, bump proof count to 31.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant