Skip to content

Epic: accounting correctness and fork hygiene (audit follow-ups) #87

Description

@axisrow

Follow-ups from an audit of how the fork and upstream protect calculation correctness. Finding: neither checks numbers against an external reference — tests prove the code agrees with itself, so a wrong price table passes all of them. This epic fixes the accounting layer (prices, cost, real-data validation) and the project hygiene around it (CI, dead scripts, upstream sync).

It is layer 1 of #78: the waste-detection evaluation there is only meaningful once the base numbers are right. In particular #72 blocks #82, and #74 overlaps #79.

Sub-issues

Найдено при проверке эпика #2 — ⚠️ выводы не проверены человеком, каждый пункт перепроверить дважды (см. раздел «Перепроверка» в каждом ишью):

Рекомендации по выполнению

Порядок

Фаза Ишью Почему так
0 перепроверка #88, #91, #92, #93 Выводы сделаны агентом, автор им не доверяет: сначала дважды подтвердить или закрыть как ложные.
1 — быстрые, параллельно #76, #75, #77 Мелкие и независимые от расчётов. #75 первым, чтобы следующие PR проверялись сборкой и на Windows.
2 #72, #88 Главные баги учёта: цены и потеря содержимого раундов. Можно начинать параллельно с фазой 1, но вливать после #75.
3 #73, #91 #73 переиспользует модуль цен из #72; #91 — общая функция активного времени.
4 #74 вместе с #79 #79 строит корпус и снапшоты, #74 добавляет внешний эталон на этом же корпусе.
5 #92, #93 Скиллы и документация — после того как CLI считает правильно, иначе описывать придётся дважды.

Общие правила: одно ишью — один PR; в PR, меняющих числа, прикладывать «было/стало» на своих реальных сессиях (после #79 — вывод corpus:diff); не менять пороги WASTE_THRESHOLDS и логику findings в тех же PR.

#76 — мёртвые скрипты (~15 мин)

  • test:chunks → vitest run test/main/services/analysis/ChunkBuilder.test.ts; test:noise — проверить, покрывает ли test/main/services/parsing/MessageClassifier.test.ts; для test:semantic и test:task-filtering соответствующих тестов, похоже, нет — удалить.
  • Одновременно поправить CLAUDE.md и .claude/rules/testing.md: агенты выполняют эти команды буквально.

#75 — CI

  • Взять за основу апстримный workflow (git show upstream/main:.github/workflows/ci.yml): шаг pnpm build + матрица ubuntu-latest / windows-latest с fail-fast: false. Добавить pnpm build:cli. Сохранить форковские --frozen-lockfile и claude plugin validate (последний достаточно на Ubuntu).
  • Critical coverage: сначала измерить текущее покрытие файлов расчётов (src/main/utils/jsonl.ts, src/shared/turnAccounting.ts, src/cli/analyzeSession.ts, src/renderer/utils/contextTracker.ts) и поставить пороги на этом уровне («храповик»), поднимать по мере tokensWasted mixes prices and ignores residency: express waste in $ and count re-reads #82/Non-overlapping waste attribution and session waste share; split observations from waste #84. Произвольный высокий порог сразу сделает CI красным.
  • Если Windows выявит падения — это реальные баги путей: чинить в этом же PR, тесты не отключать.

#77 — синхронизация с апстримом

  • Пробный merge upstream/main в текущий main: 13 файлов, +39/−5, 2 конфликта — src/renderer/components/chat/ChatHistory.tsx и src/renderer/components/chat/LastOutputDisplay.tsx. Остальное сливается автоматически.
  • Делать одним merge-коммитом (git merge upstream/main), а не cherry-pick: так сдвигается merge-base, и следующая синхронизация покажет только новое.
  • Сделать до App never computes session cost — costUsd is hardcoded to 0 #73, если App never computes session cost — costUsd is hardcoded to 0 #73 будет трогать UI — меньше конфликтов в renderer.
  • Обновить баннер и раздел Upstream Sync Policy в README.

#72 — таблица цен (главное)

  • Сразу класть таблицу и roundCostUsd в src/shared/ (напр. src/shared/constants/pricing.ts), а не в CLI — иначе в App never computes session cost — costUsd is hardcoded to 0 #73 придётся переносить второй раз.
  • Ключ — (family, major, minor) из parseModelString (src/shared/utils/modelParser.ts). Неизвестная версия → unpriced (costPartial), никогда не «цена семейства по умолчанию» — именно так и появился текущий баг.
  • Пять явных колонок: input, output, cache read, cache write 5m, cache write 1h. Cache read не выводить множителем: у Opus 5.5 и Fable 5.1 он не 0.1×. В комментарии — дата сверки и ссылка на прайс.
  • usage.cache_creation.ephemeral_5m/1h_input_tokens добавить в UsageMetadata; если разбивки нет (старые логи) — весь cache_creation_input_tokens считать 5-минутным. В проверенной сессии 100 % записей — 1-часовые (см. комментарий в CLI cost estimate uses outdated per-family prices (Opus overstated 3–3.75×) #72).
  • Haiku 5.5 тарифицируется ступенчато (промпт > 100K дороже) — считать по входной стороне каждого запроса или явно задокументировать упрощение.
  • Модели через роутеры (glm и т.п.) оставить отдельной веткой; detectBillingScheme уже различает схемы.
  • Ожидаемые значения в тестах выписывать вручную из прайса, не вычислять из таблицы.

#88 — потеря содержимого раундов

  • Не ломать подсчёт токенов: usage по-прежнему из последней строки запроса; склеивать только содержимое.
  • До фикса проверить на старых сессиях (корпус Regression corpus of real sessions (local, via .env) with numeric snapshots #79), не были ли строки там накапливающимися снимками — иначе склейка удвоит блоки.

#73 — стоимость в приложении

  • Считать по дедуплицированным сообщениям (deduplicateByRequestId) с моделью каждого сообщения: в одной сессии бывает несколько моделей.
  • Явно решить и задокументировать, входят ли subagents в стоимость сессии, и сделать так же, как в CLI — иначе parity-тест будет сравнивать разные вещи.
  • Добавить флаг частичной стоимости в SessionMetrics (src/main/types/domain.ts), а не возвращать 0 для неизвестных моделей.
  • Parity-тест CLI ↔ calculateMetrics по образцу test/shared/turnAccounting.parity.test.ts.

#74 — сверка с реальностью

  • Делать на корпусе из Regression corpus of real sessions (local, via .env) with numeric snapshots #79 (локально, путь через .env), не коммитить реальные сессии.
  • ccusage: токены должны совпадать точно; стоимость — с допуском, т.к. у ccusage свой источник цен. Расхождение стоимости по конкретной модели — сигнал ошибки цены у одной из сторон.
  • Один раз сверить итог за день с Usage в Console — единственный эталон, который не зависит ни от чьей таблицы цен.
  • Обезличенная мини-фикстура для CI: оставить только usage, model, requestId, timestamps, имена инструментов и размеры; тексты и пути вырезать.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestepicТребует декомпозиции

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions