client-core: an unasked page is not an empty one, and two guarded invariants
Review of 73251d6's port of TranscriptSource/joinPages. `TranscriptSource::page` answered `before == 0` with an empty `Vec`, which is the same value it answers "this conversation has no more history" with. That is the state the Kotlin keeps apart: `loadOlderPage` returns false at `oldestSeq == 0` *without* touching `moreHistory`, and returns false on an empty page *by latching it*. Collapsing the two moved AGENTS.md's paging bug one layer down rather than fixing it. `page` returns `OlderPage` now -- `Events(vec![])` is the start of the conversation, `NothingLoaded` is not an answer about the conversation at all. `join_pages`' `debug_assert!` on seq ordering across the boundary is not a true invariant: a peer note carries the seq its turn began at, which can be older than the page it arrived in, so an ordinary transcript would have panicked a debug build there. Replaced with the one the function exists to enforce -- no tool id surviving in both halves. `fetch_transcript_lines` stores `RawValue`'s exact server bytes, so the "neither source can produce a newline" comment in `SessionCache::append` now rests on the server's serializer staying compact rather than on a local normalization. Checked with a `debug_assert!` in `append` and `store_page` rather than trusted. Tests for the failure half, which the port had none of: a 500 mid-page, a cached line this build cannot read, and the `after` bound in the case that actually carries one (the existing test asserted only the case with no bound). `cargo fmt`, `cargo clippy --all-targets`, `cargo test` (112) clean in client-core; `cargo check -p desktop-app` clean.
This commit is contained in:
1 parent
312455956d
commit
bf3479f5c4
4 files changed
+164
-45
No files matched your search
@@ -361,6 +361,10 @@ impl SessionCache {
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
debug_assert!(
|
||||
lines.iter().all(|l| !l.contains('\n')),
|
||||
"a stored page's lines must each be one line"
|
||||
);
|
||||
fs::create_dir_all(&this.dir)?;
|
||||
let kind = if rows { "rows" } else { "raw" };
|
||||
let mut content = lines.join("\n");
|
||||
@@ -389,8 +393,16 @@ impl SessionCache {
|
||||
return Ok(());
|
||||
};
|
||||
// Written as it arrived. A newline inside it would split one
|
||||
// event into two unreadable halves, but neither source can
|
||||
// produce one.
|
||||
// event into two unreadable halves. No source here can produce
|
||||
// one -- an SSE `data:` field cannot hold a raw newline, and a
|
||||
// fetched line is one element of a compact JSON array -- but
|
||||
// that is a fact about the *server's* serializer rather than
|
||||
// anything this file controls, so it is checked rather than
|
||||
// trusted.
|
||||
debug_assert!(
|
||||
!line.contains('\n'),
|
||||
"a cached transcript line must be one line: {line}"
|
||||
);
|
||||
use std::io::Write;
|
||||
writer.write_all(line.as_bytes())?;
|
||||
writer.write_all(b"\n")?;
|
||||
|
||||
Reference in new issue
Block a user