test(pod): paginable long-history seed scenario (Refs #4100) - #9415
test(pod): paginable long-history seed scenario (Refs #4100)#9415pepmach wants to merge 1 commit into
Conversation
No shipped seed scenario could paginate. The slot-detail fast path answers has_more=false unless a session has size-rotated archive/ segments, and rotation only fires once a transcript outgrows a 10 MiB budget, so every scenario built from a handful of messages leaves "load earlier" structurally unreachable and a paging trace cannot be performed through the real routes. sessions-long-history seeds that state: one pinned session whose oldest 60 rows sit below a real rotation boundary. The initial slot load returns the 8 live rows with has_more=true and next_before=60, and paging with that cursor returns rows out of the archive. Both shipped files are real ConversationLog output. The segment is _maybe_rotate output at the production budgets, and the live transcript is compaction output holding rows append wrote, so the fixture cannot encode a rotation shape the writer does not produce. Only the rotate segment and the live transcript are kept; the ~10 MiB compact segment the recipe produces is discarded, which is the state a home reaches once archive retention reclaims it. That keeps the fixture at ~20 KB of package data, inside the 64 KB per-fixture cap. The contract test pins the design point through the real readers and pins the shipped segment's header and row shape against a rotation driven in-test, so a change to the rotation format turns red instead of leaving the fixture describing a shape the product no longer writes.
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A real, documented capability gap (#4100 stand-down) closed with real writer output, size-arithmetic-aware, and provenance-pinned against the generator — sound and proportionate. Suggestions
[DESIGN-REVIEWED] c7d8191 |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All checks complete. The claims in the description survived verification: the two pre-existing First-Principles-Verdict: PASS The Green traces are route-level; the shipped tests pin the readers, not What this change shipsIntent: make a history-paging trace performable through the real routes by shipping a seed scenario whose session genuinely paginates — an ADDITION (test infrastructure), grounded in the #4100 stand-down. Inventory (5 items)
[FIRST-PRINCIPLES-REVIEWED] c7d8191 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Problem
No shipped seed scenario can paginate, so a history-paging trace cannot be
performed through the real routes at all.
The slot-detail fast path (
api_chat_slot_detail, the no-limit branch) hardcodeshas_more = Falseand only flips it whenread_rotated_messages_chainedreturnsrows — i.e. only when the session has size-rotated
archive/segments. Rotationfires only once a transcript outgrows a 10 MiB byte budget while holding more
than 200 lines, so a scenario made of a handful of messages can never reach that
state and "load earlier" stays structurally unreachable.
This is the missing capability named in
#4100's stand-down
("a seed that can actually paginate (a long-history fixture)").
Refs #4100—this PR does not fix that issue, it unblocks verifying it.
Red
Unmodified worktree, pod seeded with the existing
sessions-a-few, initialslot load through the real authenticated route:
has_more=falsewithnext_before=0, so there is no cursor to page with andthe "load earlier" affordance has nothing to reach. Reproduced across the whole
registry: every session in all 13 shipped scenarios returns 0 rotate-archived
rows.
Why the fixture has to be generated, and why it is still checked in
A real rotation splits a >10 MiB transcript into a kept tail and an archived
head, so archive bytes + live bytes always exceed 10 MiB — a static fixture
that simply contains a post-rotation session cannot be small. Fixtures ship as
package data in the wheel and the sdist, and
test_pod_seed_scenarios.pycapseach one at 64 KB precisely because every install pays for it.
The way out is to keep only the halves the paging seam needs:
lines still fit it — that rotation archives exactly the 60 head rows
the 10 MiB of filler out into a
compactsegmentThe fixture ships the
rotatesegment and the live transcript, both realConversationLogoutput at the production budgets. Thecompactsegment isdiscarded — a home reaches that same state once archive retention reclaims it,
and
read_rotated_messagesignorescompactsegments by design. Result: ~20 KBof package data for a session that genuinely paginates.
No product code changed, and the rotation budgets were not touched or hooked.
Green
Pod booted on this branch, seeded
sessions-long-history, real authenticatedroute:
The second page is real archived content, and the walk terminates at the true
first message of the corpus instead of retiring at the rotation boundary.
Contract test
test/test_fixture_sessions_long_history.py, two tests, 0.7s:the full chained corpus equals
rotated + live(so the archived rows are acontiguous prefix and a single cursor is exact),
chain_mid_rotationfalse,the collapsed rotated count that becomes
next_beforeis 60, and the sessionis pinned so it restores as a slot whatever the mtime restore window is.
segment's header key set,
reason=rotate,count, and row key set match what_maybe_rotatewrites today. A change to the rotation format turns this redrather than leaving the fixture describing a shape the product no longer
produces.
The scenario also picks up the existing per-fixture invariants (manifest parses,
seeds into a fresh home, every JSON/JSONL line parses, no credential-shaped
text, under the 64 KB cap).
Scope
In: the scenario, its contract test, and the seed-scenario guide. Out:
chatSlice/ChatPageand anything else on the paging code path — open#9130 is live in those files
and the traces above ran against this branch with no frontend change.
Gates
pytest -n0on the new test plus the fixture registry, seed, helper, podscenario, sibling
sessions-a-fewand archive-pagination suites: 202 passed.flake8andmypyclean on the touched file.scripts/docs-lint.shpasses.Pattern harvest
fixture. Check the arithmetic of the writer's own trigger before designing
the tree: here the 10 MiB rotation gate makes "the whole post-rotation session"
unshippable, while "the halves the seam reads" is 20 KB. The 64 KB cap in
test_pod_seed_scenarios.pyis the load-bearing constraint, and it is a test,not a guideline.
Running the real compaction path and then dropping its 10 MiB
compactsegment lands a state the product reaches on its own, whereas hand-writing a
small live tail next to a real archive segment would have paired files no
single writer run ever produced.
reader. Asserting the shipped segment's shape against a rotation driven
in-test is what keeps "produced by the real writer" true a year from now;
without it the claim decays into a commit-message assertion.
be pinned: an unpinned fixture session is admitted to the slot list only while
it is inside the mtime restore window, so the scenario would have worked when
freshly seeded and silently produced no slot later.