Skip to content

[bug·P2] Adopting a book resets the add count of a position that was never sold #660

Description

@atomchung

Status

Discussion — confirmed defect, no owner ruling yet on where the count should live. Not on the current critical path.

Milestone

None. Found while implementing #539/#536; deliberately kept out of that PR rather than widening it.

Owning outcome

A position's record of how many times the user added to it survives the book being re-declared, or the surfaces that read it stop treating its absence as "never added to".

Current scope

ledger.derive_holdings builds add_count and decision_cursor by replaying trades on top of the latest declared anchor. An anchor position starts at add_count: 0 by construction — a declaration states a share count and cannot state how that count was reached — so every adoption of a newer book resets the count for every position, and the cursor with it.

Verified on ba314a9 (the #539/#536 fix branch), through the real CLI, with the user asked nothing:

BEFORE: cycle=ACME#2026-06-30#1 add_count=2 cursor=ACME#2026-06-30#1#add#2
refresh phase 1: ready, pending_confirmations: []
AFTER : cycle=ACME#2026-06-30#1 add_count=0 cursor=None

Fixture: one declared anchor, two later buys into the same position, then an ordinary later holdings view. Synthetic throughout.

Note what is not wrong there: the cycle id is stable, because #539 landed. This is the same class of defect one field over — a fact the record held, discarded by a declaration that could not restate it — and it is not fixed by #539's carry, which covers the cycle start and its sequence only.

Why it is worth an issue rather than a note

decision_cursor is {cycle_id}#add#{n}, and review.py's add_thesis candidate builder dedups against it. A cursor that resets means the engine's record of which adds have already been discussed resets too, so the same add can be raised again after an ordinary book update — the #429 shape (a question whose answer the product does not keep) reached through the ledger rather than through a missing reader.

Blast radius is wider than #539's sequence gap: that one needed a round trip in history, this one needs only a position the user ever added to.

Fix shapes, none decided

  1. Carry it like the cycle start ([bug·design·P2] Every new snapshot declaration re-asks for theses it was already given #539's mechanism). book_refresh.carry_recorded_starts already has the derived book in hand; add_count would become a fourth engine-assigned position key. Cheap and consistent — but it puts a count that is derived from trades into a row that is supposed to state facts, and it grows SNAPSHOT_POSITION_KEYS a third time.
  2. Stop deriving it from the anchor forward. Read the add history from the trade events for the cycle regardless of where the anchor sits. Truthful at the source, but the anchor exists precisely so the replay does not have to reach behind it, and [bug·contract·M1-prep] Host entry points still teach retired incomplete-snapshot semantics after #562 #549's round-trip equality invariant (test_a_recorded_derived_book_re_derives_to_exactly_the_same_book) constrains what may change here.
  3. Accept the reset and make its consumers honest. If a declaration genuinely cannot know the add history, then add_count: 0 after an adoption means unknown, not zero, and the dedup should not read it as "this add was never discussed". Closest to the ticker#unknown precedent — the engine already has a vocabulary for "the record cannot date this".

Shape 3 is worth ruling on first: it is the only one that asks whether the number is wrong or whether its readers are.

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions