Skip to content

feat: cross-machine usage aggregation with device-level dedup - #252

Closed
Yeachan-Heo wants to merge 1 commit into
junhoyeo:mainfrom
Yeachan-Heo:feat/cross-machine-client-aggregation
Closed

feat: cross-machine usage aggregation with device-level dedup#252
Yeachan-Heo wants to merge 1 commit into
junhoyeo:mainfrom
Yeachan-Heo:feat/cross-machine-client-aggregation

Conversation

@Yeachan-Heo

@Yeachan-Heo Yeachan-Heo commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Ports the device-level deduplication logic from PR feat: cross-machine usage aggregation + automatic sync #55 (feat/cross-machine-aggregation-sync) into the current client-based codebase structure
  • Each client (e.g., "claude", "cursor") now tracks per-device contributions via a devices field keyed by API token ID
  • On resubmit from the same device, only that device's data is replaced — other devices' data is preserved, preventing double-counting
  • Legacy data (without device tracking) is gracefully migrated to a __legacy__ device key on first merge
  • Client-level totals and model breakdowns are recalculated from the sum of all devices

Changes

  • helpers.ts: Added DeviceClientData interface, devices? field on ClientBreakdownData, recalculateClientAggregate helper, and updated mergeClientBreakdowns to accept deviceId and perform device-level merge with legacy migration
  • route.ts: INSERT path now initializes devices[tokenId] from the start; UPDATE path passes tokenRecord.tokenId to the merge function
  • schema.ts: Added devices? to the sourceBreakdown JSONB type definition
  • submit.test.ts: Added 8 new tests covering device-level dedup scenarios (resubmit, multi-device aggregation, legacy migration, device removal, cross-device model aggregation, multi-client multi-device)

Test plan

  • All 28 tests pass (20 existing + 8 new device-level dedup tests)
  • No TypeScript errors in modified files
  • Manual verification: submit from device A, submit from device B, verify totals = A + B
  • Manual verification: resubmit from device A with updated data, verify totals = A' + B (not A + A' + B)
  • Verify backward compatibility with existing data (no devices field) — should auto-migrate to __legacy__

🤖 Generated with Claude Code


Summary by cubic

Add per-device usage tracking and dedup to client aggregation to support cross‑machine submissions. Resubmits now replace only that device’s data, preventing double counting and keeping totals accurate.

  • New Features
    • Track per-device contributions in client breakdowns via devices[tokenId]; mergeClientBreakdowns now replaces only the submitting device and recalculates aggregates.
    • POST /api/submit initializes devices[tokenId] on insert and passes tokenId on update to avoid legacy paths and resubmit inflation.
    • Legacy data auto-migrates to a legacy device on first merge; client totals and model breakdowns are rebuilt from device data.
    • Schema adds devices to sourceBreakdown; 8 tests cover resubmit, multi-device aggregation, legacy migration, device removal, and cross-device model sums.

Written for commit 556afd9. Summary will update on new commits.

Port device-level tracking from PR junhoyeo#55 into the client-based structure.
Each client now tracks per-device contributions (keyed by API token ID)
to prevent double-counting when the same user submits from multiple
machines. On resubmit, only the device's data is replaced while other
devices' data is preserved. Legacy data without device tracking is
gracefully migrated to a __legacy__ device key.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

@Yeachan-Heo is attempting to deploy a commit to the Inevitable Team on Vercel.

A member of the Team first needs to authorize it.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/frontend/src/lib/db/helpers.ts">

<violation number="1" location="packages/frontend/src/lib/db/helpers.ts:222">
P2: Legacy incoming payloads that only provide modelId (no models) will drop model-level attribution after recalculation because device contributions only copy incomingClient.models. recalculateClientAggregate rebuilds client.models exclusively from device.models, so legacy modelId data is lost.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/frontend/src/lib/db/helpers.ts
@junhoyeo

junhoyeo commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Hi @Yeachan-Heo — closing this together with #55, with thanks. Detailed context:

Main now implements device-level dedup, but with two deliberate design differences from this approach:

  1. Device identity: devices are identified by a persistent client-side UUID (crates/tokscale-cli/src/device.rs) rather than the API token id. Keying by token id would mint a "new device" on every token renewal and double-count history — your own follow-up discussion in feat: multi-device support with UUID device id and multi device TUI aggregation #329 surfaced exactly this issue.
  2. Storage: device attribution lives in a relational submitted_devices table plus daily_breakdown.submitted_device_id (unique on (submission_id, submitted_device_id, date)), instead of a devices map nested in the sourceBreakdown JSONB. That makes devices first-class — users can see and rename them via /api/settings/devices — and lets the submit route lock and replace exactly one device's day rows.

The scenarios your 8 tests covered (multi-device aggregation, same-device replace, legacy migration) are all covered by main's submit tests against the relational model, including the savepoint-protected legacy adoption race in #609/#611.

Your two PRs were the first to clearly frame the overwrite bug, and that framing shaped the fix — genuinely appreciated. 🙇

@junhoyeo junhoyeo closed this Jun 9, 2026
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.

2 participants