Skip to content

feat(datastore): add device_id column, v6→v7 migration (UNIQUE(device_id, name)) - #786

Open
TimeToBuildBob wants to merge 21 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/device-id-column
Open

TimeToBuildBob wants to merge 21 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/device-id-column

Conversation

@TimeToBuildBob

@TimeToBuildBob TimeToBuildBob commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Schema groundwork for the hostname-free bucket ID redesign in ActivityWatch/activitywatch#302 and ActivityWatch/activitywatch#1445. Adds Bucket.device_id and migrates v6→v7 from UNIQUE(name) to UNIQUE(device_id, name).

This PR does not enable same-name buckets in the public datastore API. That API and its cache still address buckets by name. Until a device-aware resolver lands, creating a second bucket with an existing name returns BucketAlreadyExists, regardless of device. Loading a v7 database that already contains ambiguous names fails explicitly rather than silently hiding one device's history or redirecting writes. The database constraint is preparation for that follow-up, not a claim that device-scoped addressing is implemented here.

Changes

  • aw-models: Bucket.device_id: String with #[serde(default)] for older JSON.
  • aw-datastore: v6→v7 table recreation preserves row IDs, the AUTOINCREMENT high-water mark, events, current metadata, and the archival data_deprecated column. The archive is retained separately, including NULL or malformed legacy bytes; it is not promoted into live metadata. Existing rows receive device_id = 'local'. Foreign-key enforcement is disabled outside the transaction for recreation and restored afterward, including on SQL failure.
  • Older read-only peer databases (v4–v6) remain readable using a literal 'local' device ID.
  • Empty device IDs on creation are stamped 'local'; explicit device IDs are preserved.
  • Duplicate names are rejected atomically in SQL for creation and rename; ambiguous stored names fail cache loading without deleting either row.
  • Legacy re-import refuses to merge into a same-name bucket owned by a different device; same-device re-import remains idempotent.
  • Dump-restored v7 schemas are recognized by device_id before earlier-version inference checks.
  • Client, test, benchmark, and example Bucket constructors include the new field.

Scope boundary

Device-aware cache keys, bidirectional bucket resolution, same-name coexistence through the public API, GET /api/0/devices, and watcher adoption of hostname = "!local" remain follow-up work. They must land together before lifting the name guard; removing the guard alone loses cache visibility and redirects name-based operations.

Verification

On c921d8973176: cargo test -p aw-datastore -p aw-models — 104 passed, 1 ignored; targeted strict clippy, formatting, diff whitespace checks, and commit hooks passed. The archival-preservation and cross-device legacy-import regressions failed before their fixes and pass afterward. Additional coverage includes separate current/archival payloads, SQL NULL and malformed archival bytes, missing archive columns, dump-restored early/current schemas, stale-connection creation/rename guards, sequence preservation, migration rollback/FK restoration, and version rechecking under the migration lock.

Latest-head CI and normal AI-review verification remain pending. The required push wrapper honored its existing 2/2 review cap; no fresh local AI review is claimed. Both fresh findings have one evidence reply each, with resolution left to the normal reviewer's verification.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI-green and mergeable — waiting only on a maintainer click.

This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted.

@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Critical impact] Database schema migration adds device_id column and changes uniqueness constraint.

Fix the concurrent-open race before merging so a repeated migration cannot overwrite device IDs.

Findings

  1. P1 Repeated migration overwrites device IDs ▶

Summary

Adds Bucket.device_id and upgrades storage to v7 with UNIQUE(device_id, name).

  • Bucket records now carry a device ID in schema v7.
  • The datastore keeps bucket names unambiguous for callers.
  • Legacy imports only merge into buckets from the same device.

The supplied previous threads are unnumbered. Their constructor, duplicate-name, and storage-test concerns are addressed in the current code.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Open database] --> B{Schema version}
    B -->|v4 to v6, read-only| C[Read device ID as local]
    B -->|Older, writable| D[Upgrade to v6]
    D --> E[Rebuild buckets as v7]
    E --> F[Preserve history and stamp local]
    B -->|v7| G[Read stored device IDs]
    F --> H[Load buckets by name]
    G --> H
    H --> I{Duplicate names?}
    I -->|Yes| J[Return error without deleting rows]
    I -->|No| K[Use name-based API]
Loading

Reviews (3) · Last reviewed commit: "ci: retry intermittent Windows client st..." · Reviewed by Greptile

Comment thread aw-models/src/bucket.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/tests/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

CI fix pushed (233c73d): the last three commits introduced new tests calling setup_datastore_empty() which was never defined. Replaced all three call sites with Datastore::new_in_memory(false) — the pattern all other tests use. All three tests pass locally. New CI run is running.

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.21362% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.97%. Comparing base (656f3c9) to head (8eda29e).
⚠️ Report is 235 commits behind head on master.

Files with missing lines Patch % Lines
aw-datastore/src/datastore.rs 93.06% 7 Missing ⚠️
aw-datastore/tests/datastore.rs 99.02% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master     #786       +/-   ##
===========================================
+ Coverage   70.81%   85.97%   +15.15%     
===========================================
  Files          51       89       +38     
  Lines        2916    13167    +10251     
===========================================
+ Hits         2065    11320     +9255     
- Misses        851     1847      +996     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Rebased onto master (8338954). Conflicts were only in the clippy attributes on aw-query/src/{lib,parser}.rs; master already fixed that in #771, so I kept master's version and this PR no longer touches those files. The v6→v7 device_id migration is unchanged, since master has no competing v7. cargo test -p aw-datastore passes locally.

@TimeToBuildBob

TimeToBuildBob commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 AI code review

Adds Bucket.device_id and a v6→v7 migration that rebuilds buckets with UNIQUE(device_id, name), assigning existing rows to 'local'. Datastore reads adapt to older schemas, while creation, renaming, legacy import, and recovery add name or device ownership checks. Bucket constructors and migration tests are updated for the new field and schema.

Safe to merge — no P0/P1 findings

Confidence 5/5

⚠️ 6 of 19 changed files were not fully read. Claims that an identifier is never assigned, used, or called are discarded unless verification names only fully preloaded files. This coverage describes the bounded context preload; the diff was reviewed.

✅ No findings. The diff looks correct to me on this pass.

Files changed (19) — the diff as I read it
  • aw-client-rust/src/lib.rs — Adds an empty device_id to the bucket constructed by create_bucket_simple.
  • aw-datastore/benches/interval_reads.rs — Adds device_id: "local" to the interval benchmark bucket.
  • aw-datastore/examples/export_memory.rs — Adds device_id: "local" to the synthetic export bucket.
  • aw-datastore/src/datastore.rs — Adds v7 migration and inference, device-aware bucket loading, atomic duplicate-name guards, ownership checks during Android bucket migration, and migration tests.
  • aw-datastore/src/export.rs — Adds device_id: "local" to export test buckets.
  • aw-datastore/src/legacy_import.rs — Assigns legacy buckets to 'local', checks existing ownership before merging, and adds a cross-device collision test.
  • aw-datastore/src/recovery.rs — Defers recovery on conflicting bucket ownership and adds a collision-and-retry test.
  • aw-datastore/tests/datastore.rs — Updates schema expectations and adds device identity, external migration, stale connection, duplicate-name, and ambiguous database tests.
  • aw-models/src/bucket.rs — Adds the serde-defaulted public Bucket.device_id field and updates the model test constructor.
  • aw-query/benches/benchmark.rs — Adds device_id: "local" to query benchmark buckets.
  • aw-query/tests/query.rs — Adds device_id: "local" to query test bucket constructors.
  • aw-query/tests/regex_fuzz.rs — Adds device_id: "local" to the regex test fixture bucket.
  • aw-server/benches/query_cache.rs — Adds device_id: "local" to the query cache benchmark bucket.
  • aw-server/src/dirs.rs — Adds device_id: "local" to the directory recovery fixture bucket.
  • aw-sync/src/legacy_clean.rs — Adds device_id: "local" to the legacy cleanup test bucket.
  • aw-sync/src/status.rs — Adds device_id: "local" to status test buckets.
  • aw-sync/src/v2/segment.rs — Adds device_id: "local" to segment test buckets.
  • aw-sync/tests/sync_roundtrip.rs — Adds device_id: "local" to the sync roundtrip bucket helper.
  • aw-transform/src/find_bucket.rs — Adds device_id: "local" to the bucket lookup test constructor.
Previous review passes
commit score findings engine when
51c818c85f55 3/5 2 llm 2026-10-10 15:22 UTC
2bdf6a297bff 5/5 0 llm 2026-10-10 17:37 UTC
2bdf6a297bff 5/5 0 llm 2026-10-10 18:23 UTC
ebd4ad0e3344 5/5 0 llm 2026-10-10 19:07 UTC
d74c27eb425b 4/5 1 llm 2026-10-17 20:03 UTC
d74c27eb425b 5/5 0 llm 2026-10-17 21:03 UTC
18e5f1c1ee75 2/5 3 subscription 2026-10-27 00:22 UTC
1e850133eae8 3/5 2 subscription 2026-10-27 01:41 UTC
3cf44de58913 3/5 1 subscription 2026-10-27 07:41 UTC
df7278ac4f25 5/5 0 subscription 2026-10-27 08:42 UTC

Reviewed 8eda29ec7c33 · panel: codex:gpt-6.1-sol · subscription engine · 86s · about this reviewer

Maintainer commands

@TimeToBuildBob review (own line) — fresh review · @TimeToBuildBob fix — a worker acts on the findings. Once per comment; 👀 = received.

Outage coverage: 1-pass, not merge evidence. Subscription review supplied findings while full consensus capacity was unavailable. The consensus merge gate remains unsatisfied.

Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Fixed in ca57e35: removed the device_id guard in create_bucket — as the AI review (3/3 consensus) correctly pointed out, it rejected legitimate same-name/different-device inserts, making UNIQUE(device_id, name) unusable for its stated purpose. The UNIQUE constraint is now the sole duplicate guard (same device_id + name still returns BucketAlreadyExists via the constraint-violation path). Test flipped from asserting rejection to asserting coexistence plus the same-device duplicate rejection; cargo test -p aw-datastore passes (34/34), clippy and fmt clean.

Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/tests/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Pushed 7c9ad1a to fix the real history-hiding bug. Duplicate names now return BucketAlreadyExists before insertion, and loading an already-ambiguous v7 database fails explicitly without deleting either row. Both new regressions failed on the previous head, then passed with the fix; 85 tests passed, 1 ignored, workspace clippy and commit hooks passed.

This corrects my earlier ca57e35 update: permitting two SQL rows did not make them safely addressable through the name-keyed cache. The PR body now scopes this as schema groundwork, not completed same-name coexistence. Keep the guard until device-aware cache keys and addressing land together.

The in-band review resolved the original P1 on the new head. Its new migration claim assumes duplicate names in a v6 schema that enforces UNIQUE(name); rejected with schema evidence on that thread. The test-coverage nit is covered by the separate ambiguity regression and was also replied to and resolved. No open findings from our reviewer remain; its historical raw score is retained. No Greptile re-trigger.

New-head CI: 7 checks in progress, not yet verified green. No merge performed; the next monitoring cycle checks those results.

TimeToBuildBob added a commit to TimeToBuildBob/aw-core that referenced this pull request Oct 7, 2026
…_id, id))

Adds `device_id` to the `buckets` table so buckets from different devices can
share a bucket name without colliding. Python mirror of ActivityWatch/aw-server-rust#786.

**Before:** `UNIQUE(id)` — two devices' `aw-watcher-window_hostname` buckets
can't coexist in the same db.
**After:** `UNIQUE(device_id, id)` — each device's buckets are scoped independently.

Changes:
- `LATEST_VERSION` 1 → 2 (new file: `sqlite.v2.db`)
- `CREATE_BUCKETS_TABLE`: add `device_id TEXT NOT NULL DEFAULT 'local'`, replace
  inline `UNIQUE` on `id` with table-level `UNIQUE(device_id, id)`
- `create_bucket()`: add `device_id` param (default `"local"`)
- `buckets()`, `buckets_with_last_updated()`, `get_metadata()`: include `device_id`
- `migration.py`: add `sqlite_v1_to_v2()` to copy v1 data with `device_id='local'`;
  `check_for_migration()` now detects and runs it on first open of the new db file

Part of ActivityWatch/activitywatch#302 (hostname-free bucket IDs).

Git-Session-Id: 02ce
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/tests/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/tests/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merge-order hazard across three of my datastore PRs. Each one is green on its own, but no pairing of them is safe:

Proposed order, with me rebasing each PR after the previous one lands: #776 → #786 (extend inference: device_id column ⇒ 7) → #782 (renumber to v8, inference: sidecar table ⇒ 8). If you'd rather merge in a different order, the PR merged second takes v8, and #776's inference gets extended either way.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merged master now that #776 landed, and fixed the hazard from my earlier comment in 51c818c: _infer_db_version checks device_id first and returns 7. Without that, a dump-restored v7 DB inferred as v2 because v7 drops data_deprecated. The early-schema dump-restore test now asserts v7 dropped data_deprecated. cargo test -p aw-datastore: 44 + 43 passed. #782 still needs to renumber to v8 once this merges.

Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Triggered one fresh CI attempt with tree-identical commit 2bdf6a297bff after the failed-job rerun was denied (no repository admin rights). The failing Windows step was cargo test -p aw-client-rust --features cli: test_wait_for_start_retries_without_blocking_executor timed out after 10s. That same test passed in the preceding workspace step of the same job, and its implementation is unchanged from successful master Build run 38055186799. Windows build, datastore/model tests, and sync-v2 tests passed. This supports an intermittent client test failure, not a migration regression; no client behavior or deadlines were changed. Push completed through the PM wrapper, honoring its already-spent review cap. GitHub confirms the new head; checks have not yet appeared, so CI is not claimed green. Next monitoring cycle checks this single retry; a repeated failure needs a focused client-test investigation rather than another automatic retry.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Rebased onto master (ebd4ad0) to clear the conflict introduced when #806/#807 landed.

The single conflict was in get_stored_buckets: master now reads a bucket-list signature before the query so a read-only reader can detect cache staleness (#807), while this PR builds the SELECT with a version-gated {device_id_expr}. Resolution keeps both — signature read first, then the v6-safe 'local' fallback for read-only peer DBs opened at v4–v6.

Verified on the resolved head:

  • cargo test -p aw-datastore -p aw-models — 46 unit + 44 integration + 15 model tests passed (1 ignored)
  • cargo clippy -p aw-datastore -- -D warnings — clean
  • cargo check --workspace --all-targets — clean (only pre-existing warnings)

CI is re-running on ebd4ad0; mergeable again. Still waiting only on a maintainer click.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merge recommendation

Adjudicating the new P1s on head 18e5f1c (AI review of that SHA, 2026-10-27 00:22 UTC). Did not trigger Greptile (attempt cap). Pushed 1e85013 with the two blocking fixes.

Fixed (this session, head 1e85013):

  • 0b2bef1a2cbe P1 _migrate_v3_to_v4 version regress — exclusive lock + version recheck, same pattern as v5/v6/v7. Regression: pre_v7_migrations_recheck_version_after_taking_the_lock.
  • 8289d8f1fe81 P1 migrate_test_bucket_names unguarded rename — SQL destination lookup, same-device merge only, NOT EXISTS name guard. Regression: test_stale_connection_migrate_test_bucket_names_rejects_ambiguous_name.

Remaining (non-blocking):

  • a5a3f870bc07 P1 recover_database matches by name (recovery.rs:154, outside the diff, no inline thread). Dismissed, accepted tradeoff. Windows recovery unions Local/Roaming of the same install. Both source snapshot and target run DatastoreInstance::new(..., true) before the name match, so v7 stamps every existing bucket device_id='local'. Cross-device recovery is not this path; device-aware addressing is the stacked follow-up (feat(datastore): preserve history through legacy bucket aliases #836). Matching on (device_id, name) belongs there, not as a silent production bug of this schema PR.

Verified against: classifications checked on 18e5f1c1ee75; fixes landed in 1e850133eae8.

CI: 7/7 green on 18e5f1c. New head 1e85013 just pushed; hosted checks not claimed green yet.

Domain risk: v6→v7 table recreation; concurrent startup from v3; Android aw-watcher-android-test* rename at startup. After merge, #782 must renumber its schema to v8.

Convergence: Greptile detector verdict: converged, unresolved P1/P2/P3 = 0. round_convergence.stable_rounds = 0 (only the 2026-10-06 round is ingested; quote with that caveat). Our two inline threads stay open for reviewer verification on 1e85013.

Not merge-ready until CI is green on 1e85013 and the reviewer clears those two threads. Maintainer owns the merge click (pull-only). No Greptile re-trigger.

Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merge recommendation — fixes pushed; new-head verification pending

Pushed 3cf44de. Correcting my earlier recovery dismissal: migration stamps pre-v7 buckets as local, but preserves explicit ownership in an already-v7 database. Same-install Local/Roaming recovery therefore cannot safely assume matching names imply matching devices.

  • Fixed: P1 37eee57831f6, cross-device recovery union. Recovery now compares device IDs under the existing target write transaction and defers the whole source on a mismatch, rolling back any union work and leaving no completion marker. The regression failed on 1e85013 (one foreign event inserted), then passed. It also verifies a later retry succeeds when both buckets have the same non-local device ID.
  • Fixed: P2 9e21f5546518, stale reader ownership. Cache reload refreshes schema version, including inference for dump-restored files, rather than permanently using the version seen at open. The two-connection regression failed with local != peer-abc before the fix and passes now. Replied on its inline thread and resolved it with a verified-fix disposition; no unresolved threads from our reviewer remain. The P1 was summary-only, outside the diff.
  • Remaining: no newly dismissed findings. The previously accepted non-blocking rename_bucket error-label race remains unchanged: the atomic UPDATE protects state; only a concurrent failure's error label can vary. This is not a new dismissal or a claim of whole-head clearance.
  • Verified against: findings reproduced on reviewed head 1e850133eae8; fixes and local verification on 3cf44de58913. 137 tests passed, 1 ignored (cargo test -p aw-datastore -p aw-models); strict datastore clippy, formatting, diff whitespace and commit hooks passed.
  • CI: all seven checks passed on the old head. No check runs had appeared for 3cf44de at the post-push check; new-head CI is not verified green. The push wrapper honored its existing review cap, so no fresh in-band AI review is claimed. Await automatic AI-review-sweep coverage of this head before final adjudication.
  • Domain risk: SQLite table recreation, concurrent migration, and Windows preserved-history recovery. Native Windows recovery remains a useful maintainer smoke test; this session ran real SQLite fixtures on Linux, not a native Windows upgrade. feat(categories): local manual event overrides outside watcher data #782 still needs its schema renumbering after this PR merges.
  • Convergence: detector verdict: converged, Greptile unresolved P1/P2/P3 = 0; round_convergence.stable_rounds = 0 (only the 2026-10-06 round is ingested, so this is not evidence of two stable review rounds). Greptile's footer remains at ebd4ad0; the current-head AI review supplied the actionable findings. No Greptile re-trigger and no merge.

Hold for CI and automatic review of 3cf44de; then maintainer judgment.

Comment thread aw-datastore/src/datastore.rs Resolved
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Merge recommendation — hold for new-head verification

Pushed df7278a, fixing the P1 found by the latest AI review of 3cf44de58913.

  • Fixed this session: 9ee6839ce33f, stale inferred-version persistence. The version write now locks exclusively and re-reads the version/schema before persisting, so an opener cannot overwrite a completed v7 migration with its stale v6 inference. The two-connection regression failed with peer-abc becoming local before the fix and passes afterward. Replied on the inline finding and resolved it with a verified-fix disposition; no open findings from our reviewer remain.
  • Remaining: no new dismissals. The previously accepted non-blocking rename error-label race remains: the atomic UPDATE prevents duplicate names; only the follow-up error classification can race. Its existing inline dismissal is unchanged.
  • Verified against: P1 reproduced on reviewed head 3cf44de58913; fix and local verification on df7278ac4f25. 138 tests passed, 1 ignored across datastore/model suites. Strict datastore clippy, formatting, whitespace checks and commit hooks (including workspace clippy) passed.
  • CI: on df7278ac4f25, format passed; the other six checks were running at the snapshot. New-head CI is not yet green. The push wrapper performed no fresh review, so this is not whole-head review clearance. Await automatic AI-review-sweep coverage before final adjudication.
  • Domain risk: concurrent SQLite startup and the v6→v7 table rebuild; native Windows preserved-history recovery remains a maintainer smoke-test boundary. feat(categories): local manual event overrides outside watcher data #782 still needs schema renumbering after this PR merges.
  • Convergence: detector verdict converged, Greptile unresolved P1/P2/P3 = 0; round_convergence.stable_rounds = 0 (only the 2026-10-06 round is ingested). Greptile's footer still names ebd4ad0; the newer AI pass supplied this actionable P1. No Greptile re-trigger or merge.

Next: automatic CI/review of df7278ac4f25, then maintainer judgment.

TimeToBuildBob and others added 21 commits November 5, 2026 06:40
…_id,name))

Adds `device_id` to the `buckets` table so buckets from different devices
can share a bucket name without colliding. The new uniqueness constraint is
`UNIQUE(device_id, name)`, replacing the old `UNIQUE(name)`.

Migration (v6→v7):
- Recreates the buckets table with the new schema (SQLite cannot alter
  UNIQUE constraints in-place).
- Stamps all existing rows with `device_id = 'local'` (locally-created).
- Also drops the stale `data_deprecated` column (unused since v3).
- FK enforcement is disabled around the recreation to avoid the immediate
  events→buckets FK check firing on DROP TABLE mid-batch.

Read-only opens (aw-sync peer DBs at v4-v6):
- `get_stored_buckets` falls back to `'local'` literal when `db_version < 7`,
  so aw-sync can still read older peer databases without migrating them.

`create_bucket` stamps `device_id = "local"` for any bucket created without
an explicit device_id (local watchers). Peer buckets imported via aw-sync
carry their own device_id.

Also fixes pre-existing duplicate `#[allow]` attributes in aw-query that
were breaking the clippy pre-commit hook under the current toolchain.

Git-Session-Id: 77b9
Git-Session-Id: c66cd073-07ec-4980-883b-2f3c24dd5be1
Complete the fix across all test and benchmark Bucket struct literals
that weren't updated when device_id was added to the model.

Git-Session-Id: da0eaaaf-bdea-5882-addc-bba90b82acea
buckets_cache is keyed by bucket name only. A second insert with the
same name but a different device_id would silently overwrite the first
cache entry, hiding its history and routing new events to the wrong
bucket.

Reject until the cache key includes device_id (follow-up refactor).

Git-Session-Id: da0eaaaf-bdea-5882-addc-bba90b82acea
…te rejection

Add three tests for the storage guarantees introduced by the device_id
column:
- empty device_id is stamped "local" on creation
- explicit device_id survives the round-trip
- same-name bucket with a different device_id is rejected (cache guard)

Git-Session-Id: da0eaaaf-bdea-5882-addc-bba90b82acea
…new_in_memory(false)

Git-Session-Id: dde9bcf8-3b8e-589f-bdc8-7b22fa16c358
…ckets

The guard added in d446f37 rejected same-name buckets with a different
device_id to protect the name-keyed buckets_cache, but UNIQUE(device_id,
name) exists precisely to allow (A, "x") and (B, "x") to coexist — the
guard made the feature unusable for its stated purpose.

Remove the guard and let the UNIQUE constraint be the sole duplicate
guard (same device_id + name still maps to BucketAlreadyExists). Test
flipped from asserting rejection to asserting coexistence plus the
same-device duplicate rejection path.

Git-Session-Id: 836f0e43-c054-59df-bd31-567140d6ae5d
Git-Session-Id: 9295d79d-f715-58c1-aca0-8588355870ff
Git-Session-Id: 18163d54-c6a9-5fd9-a7c3-e85d1aed668f
Git-Session-Id: b59ff0c6-06f5-5ed5-8eaa-6141ff07f3d9
Git-Session-Id: b59ff0c6-06f5-5ed5-8eaa-6141ff07f3d9
… migration lock

- rename_bucket checks the destination name inside the UPDATE so a stale
  cache cannot create an ambiguous (device_id, name) pair
- v6->v7 migration carries the AUTOINCREMENT high-water mark over the
  table rebuild so deleted bucket ids are never reused
- re-read user_version after taking the exclusive lock so a concurrent
  opener's finished migration is not rebuilt over its device IDs

Git-Session-Id: b51e5cce-319b-4ec1-8c25-2a1eb9a205e6
After merging master (ActivityWatch#776), _infer_db_version returned 2 for the v7
schema: v7 drops data_deprecated, which the v2/v3 check reads as 'no
rename yet'. Recognise device_id first. The early-schema dump-restore
test now asserts v7 dropped data_deprecated instead of preserving it.

Git-Session-Id: 0c1512d2-dbce-4257-97f6-9b08bbe8dcb8
Git-Session-Id: d8c65c8d-a38a-5d02-bbdc-63034939f80f
Git-Session-Id: b01e1b01-a683-5ca8-805e-c6dd7d8436c0
A concurrent opener that read v5 before the first opener finished the
v7 migration would regress user_version back to 6 (or 5) when its own
migration step finally took the exclusive lock, causing the v7 rebuild
to re-run and overwrite stored device IDs with 'local'. Mirror the
existing v6->v7 in-lock recheck in _migrate_v4_to_v5 and
_migrate_v5_to_v6: after taking the lock, skip the step if the version
is already applied.

Git-Session-Id: ea4f2eca-681c-5fde-b350-3502103e7d4c
Git-Session-Id: 9749f553-4dc2-5998-9ffd-3d98a62bae37
ActivityWatch#824 and the aw-sync legacy cleanup tests construct Bucket without the
device_id field this PR adds. The merge is textually clean but neither
crate's tests compiled, so a squash-merge would have broken master.

Git-Session-Id: 5f08faae-877d-49bf-8a88-03199ec2ec47
_migrate_v3_to_v4 wrote user_version=4 without an exclusive lock, so a
concurrent opener that started at v3 could regress a finished v7 database
and rebuild every device_id as 'local'. Take the same lock+recheck as v5/v6/v7.

migrate_test_bucket_names's uncached UPDATE could rename into a name already
owned by another device, leaving a database later opens reject. Look the
destination up in SQL, merge only same-device rows, and use rename_bucket's
NOT EXISTS name guard.

Git-Session-Id: 1cacfbb0-ad0a-531d-aaae-e99d330eb7eb
Git-Session-Id: b31642da-88db-5d04-9f40-883bb5e45355
…migration

Git-Session-Id: a3bada7b-b236-563b-9bdb-badffac7036e
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