feat(desktop): isolate platform accounts per desktop client
Frontend CI / Frontend (push) Successful in 3m4s
Backend CI / Backend (push) Successful in 14m24s

Scope desktop media accounts by the authenticated desktop client_id so
the same user logging in from Mac and Windows no longer sees a shared
account list. The list, identity lookup, upsert, patch, tombstone and
async health sink paths now all require matching client_id, and the
active uniqueness index moves from (workspace, platform, uid) to
(workspace, client, platform, uid).

Also strengthen the desktop client_id seed: when the OS machine id is
unavailable, fall back to a hashed fingerprint of stable hardware MAC
addresses before the random installation id, so the same hardware
keeps the same client across reinstalls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 13:13:11 +08:00
parent f6aed87e44
commit 1ba29b9a09
14 changed files with 378 additions and 24 deletions
+19 -1
View File
@@ -4,7 +4,7 @@
Continue the desktop AI monitoring implementation by first adding a client-side local scheduler with durable queueing and adaptive execution policy, then adding a reusable hidden Playwright CDP execution layer that attaches to Electron Chromium and reuses existing account partitions.
## Current Phase
Phase 45
Phase 47
## Phases
### Phase 1: Progress Verification
@@ -315,6 +315,20 @@ Phase 45
- [x] Commit only the reliability hardening files after verification
- **Status:** complete
### Phase 46: Desktop Client Account Isolation
- [x] Confirm current desktop media accounts were listed by `workspace_id + user_id`
- [x] Change desktop account listing/upsert/update/delete ownership to `workspace_id + client_id`
- [x] Add a migration that changes active platform-account uniqueness to include `client_id`
- [x] Strengthen hardware-derived desktop client ID fallback with stable network hardware fingerprinting
- **Status:** complete
### Phase 47: Desktop Client Account Isolation Verification
- [x] Regenerate sqlc code after query contract changes
- [x] Run targeted backend tests for tenant app/repository/transport paths
- [x] Run desktop client typecheck and targeted device-id tests
- [x] Record residual test caveats
- **Status:** complete
## Key Questions
1. How should the desktop client defer and locally optimize monitor-task execution without violating one-task-per-platform serialism?
2. What is the smallest durable local cache that allows same-day resume while safely dropping stale next-day monitor tasks?
@@ -365,6 +379,9 @@ Phase 45
| Materialize daily monitoring tasks with batched array INSERT | The per-brand cap is currently small, but one `INSERT ... SELECT FROM unnest(...) ON CONFLICT DO NOTHING` keeps DB round trips fixed if caps or retry batches grow |
| Split daily monitoring plan loading into selection and aggregation steps | Primary-client election and platform authorization aggregation have different explainability and testability concerns; two smaller queries are easier to inspect and later move to sqlc |
| Treat platform-account health as execution state, not authorization state | `health != live` should not suppress daily task planning, dashboard authorization, or detail history; only desktop-client online state gates immediate collection |
| Scope desktop account data to `client_id`, not `user_id` | The same user can log in on Mac and Windows; `workspace_id + user_id` describes the person, not the physical desktop client |
| Include account scope in hardware-derived desktop client IDs | Pure hardware IDs would collide when different users use the same computer, so the seed remains hardware fingerprint plus tenant/workspace/user |
| Prefer OS machine identity before network MAC fallback | macOS `IOPlatformUUID`, Windows `MachineGuid`, and Linux `machine-id` are more stable than interface lists; MAC hashes are a hardware fallback when OS IDs are unavailable |
| Start k3s with a self-contained stack that mirrors the existing compose deployment | The repo already has working Docker images and compose topology; k3s should first preserve service names and runtime assumptions before introducing managed cloud replacements |
| Publish deployable images only from frontend/backend CI | Deployment config has no runtime image ownership; `deploy-config-ci` stays a validation workflow while frontend/backend CI publish Gitea Registry images tagged by commit8 |
| Use commit hash first 8 chars as the canonical deploy image tag | CD/offline packaging can compare the pushed commit to Registry tags and fail fast when CI has not produced the matching image |
@@ -387,6 +404,7 @@ Phase 45
| Production compose PgBouncer smoke test could not start bundled Postgres containers because existing `server/docker-compose.yaml` containers already own `geo-postgres` / `geo-monitoring-postgres` names | 1 | Did not stop user/local DB containers; verified PgBouncer with a temporary test container attached to the existing `server_default` network and removed it after `SELECT 1` passed |
| Article generation state-check worker initially failed to compile because the local DB interface returned a custom rows type instead of `pgx.Rows` | 1 | Changed the interface to match `pgxpool.Pool.Query` directly and reran targeted/full Go tests |
| Prometheus last-event test depended on label output order | 1 | Changed the assertion to validate required labels independent of exporter ordering |
| `pnpm --filter @geo/desktop-client test -- src/main/device-id.test.ts` ran unrelated existing tests and failed in `deepseek.test.ts` because `electron/main` was not mocked there | 1 | Verified the new test directly with `pnpm --filter @geo/desktop-client exec vitest run src/main/device-id.test.ts`; leave the existing full-test mock gap separate |
## Notes
- Re-check task_plan.md before major implementation decisions.