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:
parththakkar106
2026-08-18 19:14:07 +05:30
committed by Parth
co-authored by Claude Opus 5
parent 2d38a162d4
commit 4af6e17406
14 changed files with 330 additions and 17 deletions
+14
View File
@@ -171,6 +171,9 @@ def forget_node(db: Session, adventure: models.Adventure, action: models.Action)
the node before it, which is a depth whether or not anything still sits
there.
The opening node is the one exception, because migration 62 parked the
whole pre-coordinate bank on it — see the comment on `lineage.ROOT_DEPTH`.
Returns how many memories were withdrawn.
"""
if action.branch_id is None or action.depth is None:
@@ -184,6 +187,17 @@ def forget_node(db: Session, adventure: models.Adventure, action: models.Action)
)
.all()
)
if action.depth == lineage.ROOT_DEPTH:
# The opening node is special, and only for memories that describe no
# stretch of story. Migration 62 parked every memory written before
# memories had coordinates at depth 0 — that was the choice that took
# nothing away from anybody, but it also collected them all onto one
# node, so withdrawing that node would retire a player's whole bank in
# a single click. A memory with no `source_start` was typed (or
# migrated), describes nothing that can fall off the end, and so has
# nothing to be withdrawn *from*: it stays. A summary that genuinely
# ends here is still withdrawn, because the text it describes is going.
doomed = [m for m in doomed if m.source_start is not None]
if not doomed:
return 0
starts = [m.source_start for m in doomed if m.source_start is not None]