fix(ingest): anchor the inter-turn chain on the resolved belief, not the inserted one (#1364) - #1390
Conversation
Reviewer's GuideRefactors turn ingestion to expose both newly-inserted and per-sentence resolved belief IDs, and switches inter-turn DERIVED_FROM edge wiring to anchor on the resolved chain head instead of only newly-inserted beliefs, while adding regression tests, a measurement benchmark, and changelog documentation for the defect in #1364. Sequence diagram for updated inter-turn DERIVED_FROM anchoringsequenceDiagram
participant ingest_jsonl
participant _ingest_turn
participant TurnIngest
ingest_jsonl->>_ingest_turn: _ingest_turn(store, text, source, session_id, created_at, bulk, role)
_ingest_turn-->>ingest_jsonl: TurnIngest
ingest_jsonl->>TurnIngest: head()
TurnIngest-->>ingest_jsonl: head_id
alt head_id is None or session_id is None
ingest_jsonl-->>ingest_jsonl: [skip inter-turn DERIVED_FROM edge]
else head_id is not None
ingest_jsonl-->>ingest_jsonl: [use head_id and last_per_session to chain]
end
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
Next review available in: 34 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
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. Comment |
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
|
[claim:review:Garsecg:2026-08-06T04:23:19Z] |
Review — the fix is right, the measurement discipline is the best part, one untested contract closed in
|
…the inserted one `_ingest_turn_ids` returns newly-inserted ids; `ingest_jsonl` read it as per-sentence ids. A turn whose sentences all corroborated therefore returned [], hit the `continue`, and left `last_per_session` pointing at the turn before it — so the next turn linked across it. The resulting edge is not merely missing, it is wrong: it claims turn N+1 derives from turn N-1, which the transcript does not support. Per the operator ruling of 2026-08-05 the public contract stays put: `ingest_turn` returns this list's length as its count of newly-inserted beliefs and that predates #264. The chain gets `TurnIngest.resolved` and `.head` instead — the per-sentence view was already computed as `log_belief_ids` and simply never returned. Measured on the development store via the script this ships with: 795 of 2,517 transcript turns are corroboration-only, and 244 of 1,041 same-session inter-turn edges (23.4%) span one. The larger 814-edge "spans some turn" figure is NOT this defect — 570 of those span only turns that did insert, which is `last_per_session` resetting between `ingest_jsonl` invocations, by design. Folding the two together would overstate this by ~2.3x. Note the belief table cannot measure any of it: a corroborating turn creates no row, so it contributes no timestamp of its own. The turn boundary exists only in `ingest_log`. Closes #1364.
…tprint States the attributable figure (244 of 1,041) separately from the larger "spans some turn" count (814), because 570 of those are last_per_session resetting between ingest_jsonl invocations rather than this defect. Refs #1364.
…no belief `TurnIngest.resolved` documents "one entry per sentence ... None where the sentence produced no belief", and that alignment is the only reason a future consumer can zip it against the turn's sentences — which is the whole purpose of exposing the view. Nothing pinned it. `head` scans for the last non-None, so it is identical whether the Nones are present or filtered, and every other test reaches `resolved` through `head`. Replacing `resolved=log_belief_ids` with a None-filtered copy left all 27 tests in this file and #1354's green, so compacting the list was a silent break of the contract the dataclass exists to state. The fixture holds a full-length interrogative between two statements: it survives the noise filter and the sub-floor demotion, reaches the log as its own row, and resolves to no belief — so it occupies a slot without filling it. A short question does not work; it is filtered before it becomes a candidate. Mutation-verified: the filtered variant now fails this test alone.
548ed4e to
aae8126
Compare
|
merge-train: merged aae8126 → |
|
[release:review:Garsecg:2026-08-06T04:54:30Z] |
Closes #1364.
ingest_jsonlanchored the inter-turnDERIVED_FROMchain on_ingest_turn_ids, which returns newly-inserted ids — not per-sentenceids, despite its docstring. A turn whose sentences all corroborated existing
beliefs returned
[], hit thecontinue, and leftlast_per_sessionpointingat the turn before it. The next turn then linked across it.
The defect is a wrong edge, not a missing one. The resulting edge claims
turn N+1 derives from turn N-1 — a statement the transcript does not support.
Per the ruling, the public contract does not move
Operator ruling 2026-08-05:
_ingest_turn_idskeeps returning newly-insertedids.
ingest_turnreturns its length as the public count of newly-insertedbeliefs and that contract predates #264; changing it to fix a defect confined to
the edge loop would have moved a number for every counting caller.
So the chain gets a sibling view instead.
TurnIngest.resolvedis theper-sentence list — it was already being computed as
log_belief_idsfor theintra-turn edge wiring and simply never returned — and
.headis the lastbelief the turn resolved to, new or corroborated.
Measured, with the script shipped alongside
benchmarks/inter_turn_chain_gaps.py, read-only against the development store:The 814 figure is deliberately not reported as the footprint. 570 of those
edges span only turns that did insert, which is
last_per_sessionresettingbetween
ingest_jsonlinvocations — by design, not this defect. Folding the twotogether would overstate this by about 2.3x.
The belief table cannot measure this, and that is worth recording
My first attempt measured off
beliefs.created_atand produced aplausible-looking 775. It counts something else. A corroborating turn creates no
belief row — the corroborated belief keeps the
created_atof whichever turnfirst inserted it — so a corroboration-only turn contributes no timestamp of
its own and is invisible in that table. The turn boundary exists only in
ingest_log, which carries one row per sentence with the turn'stswhetherthe sentence inserted or corroborated. The shipped script uses that.
Tests
tests/test_inter_turn_chain_1364.py, four tests, each stating what it kills:The load-bearing assertion is
(C, A) not in edges; asserting merely thatsome edge exists passes on both behaviours. Under the pre-fix(ingest): _ingest_turn_ids returns newly-inserted ids, not per-sentence ids, so the inter-turn DERIVED_FROM chain skips corroborating turns #1364 call site
the edge set collapses to exactly
{(C, A)}with(B, A)absent, which isthe defect reproduced rather than described.
insertedbut non-emptyresolved. If those agreed there would be no defect, so everything else restson it.
headis last-resolved, not last-inserted — a turn whose last sentencecorroborates and whose first is new. A turn where every sentence is new cannot
tell the two apart.
C->B->A.Passes on main and here, so the fix is shown not to disturb the path that
was already correct.
Mutation-checked: reverting the call site to
inserted[-1]fails 1 (thedistinguishing test); pointing
headatinsertedfails all 4.Full suite: 7241 passed, 70 skipped, 71 xfailed. Discretion grep clean, both
commits signed.
Out of scope
Backfilling or rewriting the 244 existing edges. Whether the historical chain is
repaired is a separate decision — the same watermark reasoning as #1354 applies.
Summary by Sourcery
Clarify ingest turn outputs to distinguish newly-inserted beliefs from per-sentence resolved beliefs and fix inter-turn DERIVED_FROM chaining over corroborating turns, while documenting and measuring the impact of the defect.
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: