Answer the review, and keep the opening node's bank
Nine findings from a review of the phase-14 stack. The one about a retry withdrawing a memory is not a bug — a memory anchored to a node describes that node, and it goes when the node goes. The root is the exception, and it is the only one: migration 62 parked every memory written before memories had coordinates on depth 0, so withdrawing the opening node would retire a whole bank nobody attached there. A memory with no source range covers no story and now stays; a summary that genuinely ends there is still withdrawn. The rest are repairs. * The adventure list quoted whichever attempt was written last rather than the one the story tells, so switching back left the index disagreeing with the page. * A v1 import gave a typed memory no depth, rebuilding the NULL the migration exists to remove — invisible until the imported adventure forked. * The action cap counted a v1 file's turns, and a turn expands into a row per saved attempt, so a file inside the cap could write a multiple of it. * Forking a live node on a borrowed ancestor promoted a sibling on a branch the caller never named. It is a branch switch, and now says so. * Switching attempts left the state, status and memory panels reading the previous take: the story does not change length, so nothing keyed on its length noticed. Same class as the branch-switch bug this phase already fixed. * A retry after switching back numbered the new attempt into the middle of the group instead of the end. * The cursor backfill numbered every action in the table once per adventure; correlated to the adventure being updated, it is an index lookup instead. * Renaming a branch answered own_actions=0. And one behaviour change recorded rather than repaired: script-visible history and actionCount no longer count blank-text rows. That is the right shape and there is no reading compatible with both, so plan/14 says so. 409 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015H5qiyiR7gtFQaoDphHZ3g
This commit is contained in:
committed by
Parth
co-authored by
Claude Opus 5
parent
2d38a162d4
commit
4af6e17406
@@ -585,6 +585,17 @@ def _backfill_cursor_anchors(conn) -> None:
|
||||
Guarded on `_depth = -1` so a run that dies halfway resumes: every
|
||||
adventure this has already converted is skipped, and one it has not is
|
||||
indistinguishable from an untouched row.
|
||||
|
||||
**The row numbering is correlated, not ranked-then-filtered.** Numbering
|
||||
every action in the table and picking one row out of the result reads the
|
||||
whole of `actions` per adventure — the window function is what stops the
|
||||
correlation being pushed down, so the planner has no way to make it cheaper
|
||||
— and this runs inside the one transaction that holds the schema, at boot,
|
||||
against a database with real stories in it. Restricting the scan to the
|
||||
adventure being updated makes each pass an index lookup on
|
||||
`actions.adventure_id` instead, and `PARTITION BY` is then a partition of
|
||||
one. The two forms give the same answer for the same reason: the rows the
|
||||
partition would have separated are exactly the rows the filter removes.
|
||||
"""
|
||||
sqlite = conn.dialect.name == "sqlite"
|
||||
story = _story_text_sql("text", sqlite)
|
||||
@@ -594,13 +605,14 @@ def _backfill_cursor_anchors(conn) -> None:
|
||||
SET {name}_cursor_branch_id = {_root_branch_of('adventures.id')},
|
||||
{name}_cursor_depth = COALESCE(
|
||||
(SELECT ranked.depth FROM (
|
||||
SELECT adventure_id, depth, ROW_NUMBER() OVER (
|
||||
PARTITION BY adventure_id ORDER BY depth, id
|
||||
SELECT depth, ROW_NUMBER() OVER (
|
||||
ORDER BY depth, id
|
||||
) AS rn
|
||||
FROM actions WHERE {story}
|
||||
FROM actions
|
||||
WHERE {story}
|
||||
AND actions.adventure_id = adventures.id
|
||||
) AS ranked
|
||||
WHERE ranked.adventure_id = adventures.id
|
||||
AND ranked.rn = adventures.{name}_cursor),
|
||||
WHERE ranked.rn = adventures.{name}_cursor),
|
||||
(SELECT MAX(a.depth) FROM actions a
|
||||
WHERE a.adventure_id = adventures.id
|
||||
AND {_story_text_sql('a.text', sqlite)}),
|
||||
|
||||
Reference in New Issue
Block a user