Skip to content

Fix what the second pre-release hand test of dev found - #2577

Merged
AbirAbbas merged 12 commits into
devfrom
relprep/draft-restore
Nov 10, 2026
Merged

AbirAbbas merged 12 commits into
devfrom
relprep/draft-restore

Conversation

@AbirAbbas

@AbirAbbas AbirAbbas commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

The second full pre-release hand test, on dev@b0dfe9702 after #2567, drove the real binary against a real
model in throwaway homes on Linux and macOS. It included the v0.8.2 upgrade, rollbacks and the live tmux suite. This
PR fixes the majors it found. Each fix is its own commit with a test that fails with the fix reverted.

Draft until the hand check of this branch passes. Each finding's exact repro is being redone on a binary built
from this branch.

Majors

  • An unsent draft is lost when its chat is reopened from Home after Ctrl+C, and an empty composer could then
    overwrite the saved words. The cold open restored the draft only over a shared handle, which engine conversations
    no longer use. It now restores on every road, and a composer that never loaded the saved draft can't write over
    it. v0.8.2 had the restore half of this too.
  • A remote task's spend was billed twice on the worker computer when its conversation arrived there. Paid calls
    now carry a portable identity from the worker through the remote record. Reconstruction never writes a ledger row
    for a call the ledger already holds: one rule, covering Fix what the pre-release hand test of dev found #2567's folded-usage case too.
  • The older-turns notice from Fix what the pre-release hand test of dev found #2567 went silent after a chat moved away and back, because take-back and
    materialize rewrote the copy-ready stamp empty. The stamp is now carried forward.
  • A late reconciled provider receipt didn't reach Home's card, so Home stayed below /cost by exactly that
    receipt. The reconciliation path now stamps the conversation's spend.
  • A landing whose work was kept on a branch didn't name the branch (since Group task and process completions under collapsed work #1893 folded routine endings). It stays
    visible again, and says branch kept · <branch>.

The UsageCallID contract (for the app/v3 re-merge)

session.UsageCallID(line UsageLine) string (in internal/session/usage_identity.go) is the one identity of a paid
call, independent of the computer, folder or parent conversation that later carries it:

  • Contract:
    • A transported identity wins: the CallID that a remote record or a cell receipt carries (call_id on the wire
      and in cellstore types).
    • LOCAL ledger rows keep their original schema and store no call_id. Their identity is derived as call: + the
      SHA-256 of [At (UTC RFC3339Nano), Session, Model, Role, Calls, Input, Output, USD]. That's stable because the
      ledger is append-only: a late reconciliation appends its own row and never edits one. Older rows match without
      rewriting.
    • An empty At or Session gives no identity (such a row is never deduped).
  • Where it flows:
    • The worker's ledger row carries it.
    • The remote task record (task_remote_money.go) carries CallID beside RemoteEvent, and dedupes on either.
    • The journal's usage entry keeps it.
    • internal/cellindex/usage.go reconstruction skips any call whose identity the ledger already holds, and writes
      the rest once. That includes Fix what the pre-release hand test of dev found #2567's folded-usage case.
  • Remote append and reconstruction share one process lock (UsageLedgerLockSuffix).

Any other identity layer (for example app/v3's) should unify on this one rule: one function, the same precedence
(an explicit id first, then the content hash), and the same "no identity means no dedupe" behaviour.

Suite and manual

The tmux suite and the manual follow decisions the product already made: the tab word Memory (#1943), the
first-conversation starter screen (#680), and folded task endings (#1893). The folded receipts are asserted on the
decision receipt and the named row's state.

Minor findings that aren't fixed here are filed in #2575. The tmux word gate's weakness is #2572.

🤖 Generated with Claude Code

AbirAbbas and others added 12 commits November 10, 2026 09:32
…ox never overwrites it

Reopening a conversation from Home after Ctrl+C on the engine road showed an empty composer although the draft had been written to disk: the cold beside-open restored the saved draft only over a shared handle (a.shared), which engine conversations no longer use. Worse, the empty composer then saved over the person's words. A cold open now restores the saved draft on every road, and a composer that never loaded the saved draft cannot write over it; a person who clears their own draft still can. v0.8.2 had the restore half of this too. Found by the second pre-release hand test (F-1.q4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n the worker

Continuing a remote task's parent conversation on the computer that ran the task rebuilt the parent's usage under the parent's id, and could not see that the same calls were already on that computer's ledger under the worker's sessions, so the task was billed twice there. Paid calls now carry a portable identity from the worker through the remote record into the arriving conversation, and reconstruction - here and for R8's folded usage alike - never writes a ledger row for a call the ledger already holds; genuinely new spend still lands once. Found by the second pre-release hand test (F-6.q2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rns notice still shows

#2567's notice for turns typed in an older codeaf compares the original against a stamp in the copy's copy-ready marker, but a take-back or a materialize rewrote that marker empty, so after a round trip the notice went silent and the rolled-back turns were hidden again with no word. Every writer of copy-ready now carries the existing stamp forward. Found by the second pre-release hand test (F-6b.k round trip).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2567 made auxiliary bills owe the conversation's spend stamp, but a late provider receipt banked through reconciliation still did not, so Home's card stayed below /cost by exactly those receipts. The reconciliation path now owes the same stamp. Found by the second pre-release hand test (F-5.k1 remainder).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #1893 folded routine endings into background work, a landing whose change could not merge into the checkout was folded too, and nothing on screen named the branch the finished work was kept on - only the journal had it. A landing with unlanded changes now stays visible and says 'branch kept · <branch>', as tasks.md promises; a routine landed ending stays quiet. Found by TestTUIE2E in the second pre-release hand test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestTUIE2E had not been run on a release candidate, and three of its needles
waited for presentations the product deliberately changed:

- The memory place's tab word is `Memory`: #1830 named it `Memories` and
  #1943 set it back when the knowledge vault moved in, without updating the
  needle. The needle, the command catalogue's "Memory tab" lines and the
  manual pages now all say `Memory` (the /memories command keeps its name).
- A first conversation shows #680's starter screen, so the spend test waits
  for that, not the ordinary greeting's `/task <brief> starts work`.
- Since #1893 routine task endings fold into background work, so the nested
  accept and refused landing assert the decision receipt and the named row's
  `done` / `incomplete` state - never bare words model prose could match - and
  tasks.md no longer promises the old `✓ N tasks done` rollup or a second card.

Found by the second pre-release hand test. The word gate's weakness that let
the Memory needle go stale is #2572.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ble record is still kept aside

The guard that keeps a composer which never loaded the saved draft from writing over it also blocked the person's own actions: after a send the sent words stayed saved and came back in the box, and an unreadable or newer record was no longer kept beside the replacement. Sending, editing and a deliberate clear are now the person's own claim on the composer and always reach the save (and its keep-aside path); only a passive empty write from an unread composer is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, not stored

Stamping call_id onto every local ledger row changed a persisted record older builds read (the compatibility law in TestALocalTaskKeepsItsRunLandingAndRecords). Local rows keep their original schema and the receipt derives UsageCallID from the row's existing, append-only facts; remote rows keep the identity they were transported with. The receipt test now expects that derived id (the UsageCallID decision).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y ceiling

The portable call identity brought it to 17 decisions against 15; its phases move into named helpers with no change to the ceiling or the debt table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A conflicted notice has an unlanded branch, which the kept-branch decision (517cbbf) now keeps visible on purpose; the no-click-target contract is about routine, merged results, so the fixture merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tip and a settings door still said 'Memories tab' after the tab word returned to Memory (#1943); they, and their manual lines, now match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AbirAbbas
AbirAbbas marked this pull request as ready for review November 10, 2026 16:07
@AbirAbbas
AbirAbbas merged commit ace7888 into dev Nov 10, 2026
9 checks passed
@AbirAbbas
AbirAbbas deleted the relprep/draft-restore branch November 10, 2026 16:07
AbirAbbas added a commit that referenced this pull request Nov 10, 2026
One rule now recognises a paid call already in the spending ledger:
- session.UsageCallID decides it everywhere, and UsageLine.ChargeID delegates to it.
- A carried explicit id wins: CallID, "remote:"+RemoteEvent, or the row ID.
- Otherwise dev's eight-field fingerprint decides: At, Session, Model, Role, Calls, Input, Output and USD.
- A line with neither is still counted against its session's uncovered rows,
  as both parents did. A row with no identity is never another call's duplicate.
- Two different explicit ids are two calls, even when their fingerprints match.
  This is app/v3 #2332's take law.

PRODUCT BUG fixed (from the earlier landing merge, and also present on app/v3
alone): reopening a chat with no receipts booked its paid turns and folded task
spend again. All four TestReopeningFoldedTaskSpendNeverRebillsWorkerCalls
variants now pass. Seven contract tests in usage_remerge_contract_test.go pin
the rule.

Conflicts resolved, keeping both sides:
- cellindex/usage.go, cellstore/receipt.go, cellstore/types.go, executor/modelcall.go
- session/digest.go, loop.go, task_remote_money.go, usage_identity.go (add/add),
  usage_ledger.go, usage_receipt_test.go
- tui3/commands.go keeps app/v3's slashcat table, and dev's "Memory tab" wording
  moves into slashcat/catalog.go
- manual/chat/settings.md and what-i-remember.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas added a commit that referenced this pull request Nov 10, 2026
PRODUCT BUG from the dev ace7888 merge. dev #2577 and app/v3 fixed the
same defect, a late charge missing from Home's figure, in different places:
- dev stamped at the end of every bank;
- app/v3 stamped after each late door (reconciled, memory billing, aux usage).

Keeping both stamped a turn's own calls as they were banked, and again when
the turn sealed. An interrupted turn therefore saved its spend twice
(TestLateRecoveredCancellationSavesCumulativeSpendOnce).

Now bank stamps every bill that is not a step of a live turn, and nothing
else does. The redundant stamps in reconciled and the memory billing are
gone. Both laws hold:
- TestPostSealChargesStampTheHomeTotal (dev);
- TestLateRecoveredCancellationSavesCumulativeSpendOnce (app/v3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant