Skip to content

feat(ingestion): report parsing and chunking as stages - #146

Merged
kl3inIT merged 1 commit into
mainfrom
feat/ingestion-stage-telemetry
Jul 30, 2026
Merged

feat(ingestion): report parsing and chunking as stages#146
kl3inIT merged 1 commit into
mainfrom
feat/ingestion-stage-telemetry

Conversation

@kl3inIT

@kl3inIT kl3inIT commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes the rest of the unblocked stage gap. Stage declares fourteen values; production now emits thirteen.

What was invisible

Source ingestion held no event sink at all. The revision status already moved through PARSING and CHUNKING, but a status says where a job is, not how long it stayed there — so an upload that spent four minutes parsing a scanned PDF and one that spent four milliseconds on a text file left exactly the same trace.

Shape

  • PARSE — one source document in, canonical blocks out
  • CHUNK — those blocks in, chunks out

Both under the same jobId the graph indexing stages already use, so one upload reads as one operation across two processors rather than two unrelated halves.

The engine measures each window itself and carries both out on ProcessedSourceDocument. Its caller makes one process call and cannot see where parsing ended; reporting the pair as one duration would hide which half a slow document spent its time in, and the two have unrelated causes — a parser handed a large scanned file, against a chunker falling back to a different algorithm. That failover stays inside the chunk window, because it is time the document really did spend chunking.

The sink is composed exactly as the indexing processor composes it, failureTolerant(composite(...)), so ingestion telemetry fails the same way and never becomes an availability dependency.

A test I wrote and then deleted

I first asserted that parseDuration + chunkDuration <= wholeCall, reasoning that two disjoint windows cannot exceed the call containing them, and that this would catch a chunk clock started before parsing.

I mutated the engine to start the chunk clock at parseStartedAt. The test still passed. On a fixture that small, parsing is fast enough that double-counting it stays under the call's own overhead — the assertion could not fail for the reason it existed.

So it is gone, and the comment in its place says why rather than leaving a future reader to re-derive it. The stages are proven end to end in SourceIngestionPipelineIntegrationTests instead: real Postgres, real upload, through a recorder contributed as an ordinary sink bean so the composition is exercised and not merely the emit call. It asserts chunking consumes exactly the blocks parsing produced, and that both carry the one job identifier.

Confirmed the integration test actually executes rather than silently skipping: tests="3" skipped="0".

Remaining

GENERATE is the only unproduced stage left, and it is blocked on the boundary decision in challenge-generation-telemetry.md (added in #145). Deletion/rebuild is absent from the enum entirely and is recorded as its own decision.

Verification

:apps:worker:test in full, including Testcontainers. Green.

🤖 Generated with Claude Code

Source ingestion held no event sink at all. The revision status already moved
through PARSING and CHUNKING, but a status says where a job is, not how long it
stayed there — so an upload that spent four minutes parsing a scanned PDF and
one that spent four milliseconds on a text file left exactly the same trace.

Both stages are emitted under the same jobId the graph indexing stages use, so
one upload reads as one operation across two processors rather than as two
unrelated halves.

The engine measures each window and carries both durations out on
ProcessedSourceDocument. Its caller makes one process call and cannot see where
parsing ended, and reporting the pair as a single duration would hide which half
a slow document spent its time in — the two have unrelated causes. A semantic
chunker failing over to the recursive one stays inside the chunk window, being
time the document really did spend chunking.

The sink is composed the same way the indexing processor composes it, through
failureTolerant(composite(...)), so ingestion telemetry fails the same way and
never becomes an availability dependency.

A timing assertion was written for the engine and removed. Starting the chunk
clock before parsing still passed every bound it could state on a fixture that
small, so it proved nothing it claimed. The stages are proven end to end in the
pipeline integration test instead, against a real database, through a sink
contributed as an ordinary bean so the wiring is exercised and not just the
emit call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 12 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ea132019-8532-49f5-8c07-08c852473e26

📥 Commits

Reviewing files that changed from the base of the PR and between fcb88fb and 5909030.

⛔ Files ignored due to path filters (3)
  • docs/increments/active/2026-07-29-observability-pipeline/plan.md is excluded by !docs/**
  • docs/specs/domains/secure-graph-rag.md is excluded by !docs/**
  • docs/tests/domains/secure-graph-rag.md is excluded by !docs/**
📒 Files selected for processing (5)
  • apps/worker/src/main/java/com/orgmemory/worker/ingestion/DocumentProcessingEngine.java
  • apps/worker/src/main/java/com/orgmemory/worker/ingestion/ProcessedSourceDocument.java
  • apps/worker/src/main/java/com/orgmemory/worker/ingestion/SourceIngestionProcessor.java
  • apps/worker/src/test/java/com/orgmemory/worker/ingestion/DocumentProcessingEngineTests.java
  • apps/worker/src/test/java/com/orgmemory/worker/ingestion/SourceIngestionPipelineIntegrationTests.java

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.

@kl3inIT

kl3inIT commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kl3inIT
kl3inIT merged commit 4a3d2d7 into main Jul 30, 2026
14 checks passed
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