Skip to content

fix(session-ingest): unblock catch-up convergence wedged by oversized reads and all-skipped refreshes - #3185

Closed
spa5k wants to merge 2 commits into
ScriptedAlchemy:masterfrom
spa5k:fix/session-ingest-convergence-wedges
Closed

spa5k wants to merge 2 commits into
ScriptedAlchemy:masterfrom
spa5k:fix/session-ingest-convergence-wedges

Conversation

@spa5k

@spa5k spa5k commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3183

Root causes

Two deterministic wedges combined to stall tracedecay sessions import historical catch-up on large host histories (repro: ~1,077 codex rollouts / ~3.4 GB raw; ingested store >10 GB).

1. Unbounded unoffered-record read in rewrite completion (tracedecay-global-db)

complete_observation_source_rewrite materialized every unoffered observation id for a source in one statement:

SELECT observation_id FROM observation_source_presence
WHERE source_key = ?1 AND generation <> ?2

The exact-SQL engine refuses queries that materialize more rows than its hard cap (MAX_QUERY_ROWS = 10_000). One long host session can hold more unoffered records than that cap (measured on the reproducing store: a source with 10,051 unoffered records; sources with 41k/21k/20k total records exist), so completion failed deterministically with projection storage operation read unoffered source records failed: exact SQL query materialization exceeded its limit.

The failure maps to a retryable projection_storage_retry_scheduled outcome, so catch-up re-attempted forever: each pass re-read the full transcript corpus, re-processed collision skips, and never committed a frontier. The retained session authority never mounted, so sessions search stayed unavailable.

Fix: keyset-paginate the unoffered ids (ordered pages of 1,000) inside the completion transaction. Retiring never touches observation_source_presence, so the cursor is stable across pages; the existing settle deletes remain the authoritative cleanup.

2. All-skipped refresh frontiers refused activation (tracedecay-session-temporal-store)

validate_candidate_frontier rejected any frontier advance that produced no canonical message outputs:

if expected.is_empty() && source_frontier != base_frontier { return Err(...) }

A refresh whose every new observation was deterministically skipped (effects recorded with output_count = 0 — e.g. host non-conversational records, 283k of them in the reproducing store) legitimately produces zero outputs. Activating that generation failed with candidate generation has no canonical message outputs past its base frontier and the temporal refresh worker retried the same session without bound (observed 332+ consecutive retries), stalling the convergence queue behind it.

Fix: keep the refusal only for an advance that processed no observation at all, detected via effects recorded past the base frontier. A frontier that moved over all-skipped observations activates normally.

Verification

  • New regression test rewrite_completion_retires_unoffered_records_beyond_the_query_cap seeds 10,003 unoffered records (past both the page size and the engine cap); it fails against the old code with the exact production error and passes with the fix.
  • Both crate test suites green: tracedecay-global-db (394), tracedecay-session-temporal-store (153).
  • Validated against the original wedged 10.6 GB store with a debug build:
    • catch-up failures: ~4/min sustained retry storm → 0
    • collision-skip handling that previously could never complete now records durable skips and advances (the observation cursor progresses past colliding items)
    • a previously wedged session refresh now reaches session refresh complete; receipt complete and activates its generation (verified in session_temporal_generations)

Note on observability

The retry outcome (projection_storage_retry_scheduled) never logged the underlying storage error anywhere, which made this class of wedge undiagnosable from logs alone — the root cause here was found with a temporary durable_detail() log line. Not included in this PR to keep the diff minimal, but worth considering a debug/warn line on that mapping.


Devin Review

spa5k added 2 commits October 8, 2026 16:02
… completion

Rewrite completion materialized every unoffered observation id for a
source in one statement. The exact-SQL engine refuses queries that
materialize more rows than its hard cap, and one long host session can
hold more unoffered records than that cap, so completing that source's
rewrite failed deterministically. Host catch-up mapped the failure to a
retryable storage outcome and re-attempted forever: every pass re-read
the full host transcript corpus, re-processed collision skips, and never
converged, which also kept the retained session authority unmounted.

Read the unoffered ids in ordered keyset pages inside the completion
transaction instead. Retiring never touches observation_source_presence,
so the cursor stays stable across pages; the settle deletes remain the
authoritative cleanup.

Adds a regression test that seeds more unoffered records than the
engine cap and fails against the unbounded read with the production
error.
…re all skipped

Candidate-generation validation refused any frontier advance that
produced no canonical message outputs. A refresh whose every new
observation was deterministically skipped (recorded effects with zero
outputs, e.g. host non-conversational records) legitimately produces no
outputs, so activating that generation failed forever and the temporal
refresh worker retried the same session without bound, stalling the
convergence queue behind it.

Keep the refusal only for an advance that processed no observation at
all, detected via effects recorded past the base frontier. A frontier
that moved over all-skipped observations now activates normally.
@changeset-bot

changeset-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e841b14

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration 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.

🔍 Devin Review: 2 flags

Not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

@spa5k

spa5k commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@codex review this

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

ScriptedAlchemy added a commit that referenced this pull request Oct 9, 2026
* chore(deps): update rust to v1.99.0

* fix(sessions): converge skipped and rewritten source histories

Paginate unoffered source records and accept empty refresh output when
canonical effects support the bound frontier. Cover skipped refreshes
across reopen and keep unsupported frontier advancement refused.

Integrates the production fixes contributed in PR #3185.
Fixes #3183.
Fixes #3199.

Co-authored-by: spa5k <79936503+spa5k@users.noreply.github.com>

* build(bazel): bound Windows compiler wrapper arguments

Fixes #3186.

* test(hosts): use portable paths and declared runtime inputs

Fixes #3189.
Fixes #3190.
Fixes #3191.
Fixes #3192.
Fixes #3194.

* test(runtime): settle owners before offline fixture operations

Fixes #3193.
Fixes #3195.
Fixes #3196.
Fixes #3198.

* test(dashboard): synchronize asynchronous curator receipt checks

Fixes #3187.

* fix(storage): keep codec compression consistent across platforms

Use the workspace flate2 backend for Windows ZIP extraction without
activating another backend for content-addressed sealed storage.

Fixes #3197.

* fix(sessions): require evidence for the exact refresh frontier

* fix(bazel): include memory evaluation fixture package

* test(sessions): batch large history fixture writes

* fix(graph): serve source bodies without catalog rewarming

* fix(bazel): verify packaged CLI through workspace test targets

* test(sessions): remove redundant source generation clone

* test(runtime): cover real retirement and rollback in source rewrite

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(hooks): prepare spool durability before binding publication

* fix(hooks): bind prepared checkpoints to empty records

* fix(bazel): route formatting and generation through native targets

* fix(bazel): use native CLI outputs in developer harnesses

* fix(bazel): execute custom benchmark harnesses with declared data

* fix(benchmarks): remove unpublished results after interruption

* fix(bazel): format sources without production dependency analysis

* fix(build): align Bazel with the Rust 1.99 toolchain

* fix(dashboard): bound transcript pages before context hydration

* fix(dashboard): preserve completed transcript page coverage

* fix(sessions): reserve foreground readers during convergence

* ci(hawk): run the lint on the pinned Rust toolchain

hawk.yml pinned Rust 1.97.1 while rust-toolchain.toml moved to 1.99.0. Install through rustup and drop the +version override, matching the ci.yml format gate, so hawk's diagnostics match the compiler the repo builds with. Bump the README Rust badge to 1.99.0.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* build(rust): pin the Bazel formatter to Rust 1.99

* fix(chatgpt): refresh embedded app for locked build inputs

* fix(rust): preserve runtime behavior under Rust 1.99 lints

* fix(test): use canonical frontiers and current lifecycle contracts

* fix(build): preserve explicit benchmark execution semantics

* fix(bench): mount graph owners and publish temporal relations

* test(work): interleave history read scaling measurements

* test(diagnostics): fence proof renewal during stale reads

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: ScriptedAlchemy <zackary.l.jackson@gmail.com>
Co-authored-by: spa5k <79936503+spa5k@users.noreply.github.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ScriptedAlchemy <zackaryjackson@bytedance.com>
@spa5k
spa5k deleted the fix/session-ingest-convergence-wedges branch October 9, 2026 07:35
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.

tracedecay sessions import catch-up wedges in retry/skip loop on large Codex history — search stays "retained session authority not mounted"

2 participants