Skip to content

feat: device import from integrations#2802

Open
Marfuen wants to merge 12 commits intomainfrom
feat/device-import-from-integrations
Open

feat: device import from integrations#2802
Marfuen wants to merge 12 commits intomainfrom
feat/device-import-from-integrations

Conversation

@Marfuen
Copy link
Copy Markdown
Contributor

@Marfuen Marfuen commented May 8, 2026

Summary

  • Adds device_sync as a new IntegrationCapability so any integration (platform or dynamic) can declare device import support
  • Builds GenericDeviceSyncService — a two-phase sync service (import active devices, remove disappeared ones) mirroring the existing employee sync pattern
  • Extends the sync controller with discovery (?syncType=device), provider selection (device-sync-provider), and dynamic device sync trigger endpoints
  • Adds useDeviceSync hook and DeviceSyncProviderSelector component to the Devices tab in People
  • Extends the daily Trigger.dev schedule to trigger device syncs for orgs with a deviceSyncProvider set

Test plan

  • GenericDeviceSyncService has 7 unit tests covering: create, update, skip (no member), inactive exclusion, remove stale, keep current, false-delete guard
  • Trigger schedule tests pass (107/107)
  • Manual: navigate to People → Devices tab, verify sync provider selector renders
  • Manual: connect an integration with device_sync capability, select it, click "Sync now"
  • Manual: verify devices appear in the table after sync

🤖 Generated with Claude Code


Summary by cubic

Adds device import from integrations with a new device_sync capability, a two‑phase sync (import + prune), provider selection, and scheduled runs. Validates deviceSyncDefinition on dynamic integrations and safely handles ownership changes, unique conflicts, and pruning.

  • New Features

    • Platform: device_sync capability and SyncDevice schema in @trycompai/integration-platform; dynamic integrations can store deviceSyncDefinition.
    • API: GET/POST /v1/integrations/sync/device-sync-provider, GET /available-providers?syncType=device, and POST /dynamic/:provider/devices; OAuth refresh support; logs persisted to check runs.
    • Service: GenericDeviceSyncService imports active devices (member email match, serial/external ID), updates memberId on ownership changes, guards prune when none processed, and falls back on P2002 conflicts.
    • DB: Device.source enum + integrationConnectionId + externalDeviceId; Organization.deviceSyncProvider; DynamicIntegration.deviceSyncDefinition; index on Device.integrationConnectionId.
    • Scheduler: new run-device-sync task in @trigger.dev; daily orchestrator triggers device sync for orgs with a provider set; tasks triggered in batches.
    • App: useDeviceSync hook and DeviceSyncProviderSelector on People → Devices; better error handling when setting provider; “Sync now” button.
    • Tests: unit tests for GenericDeviceSyncService; schedule tests updated.
  • Migration

    • Run Prisma migration.
    • Ensure manifests declare device_sync and include a deviceSyncDefinition.
    • Configure scheduler envs (SERVICE_TOKEN_TRIGGER, BASE_URL) for @trigger.dev.
    • Adoption: connect a device_sync integration, set the provider in Devices, then click “Sync now”.

Written for commit a632419. Summary will update on new commits.

Marfuen and others added 9 commits May 8, 2026 11:05
…Integration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two-phase device sync: imports active devices (matching by member email,
serial number, or external ID) and removes disappeared devices from the
connection. Follows the same pattern as GenericEmployeeSyncService.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Guard Phase 2 deletion when no devices were successfully processed (prevents false deletes)
- Handle P2002 unique constraint violation on device create with fallback to update
- Replace `include: { user: true }` with `select: { id: true }` on member lookup
- Wrap Phase 2 deleteMany in try/catch to prevent uncaught DB errors
- Add test verifying no deletions occur when all devices are skipped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… endpoints

Add device sync endpoints to the SyncController:
- GET device-sync-provider: read the configured device sync provider
- POST device-sync-provider: set/clear the device sync provider with validation
- GET available-providers?syncType=device: filter providers by device_sync capability
- POST dynamic/:providerSlug/devices: run DSL-based device sync with schema validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validate body.deviceSyncDefinition through SyncDefinitionSchema (applying
defaults) and store it on both PUT upsert and POST create endpoints.
Repository upsertBySlug and create methods updated to accept and pass
through the new field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new run-device-sync Trigger.dev task that calls the existing
device sync API endpoint for a single org+connection. The daily
integration-checks-schedule orchestrator now also finds orgs with
deviceSyncProvider set and triggers device sync tasks for each.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment May 8, 2026 0:51am
comp-framework-editor Ready Ready Preview, Comment May 8, 2026 0:51am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped May 8, 2026 0:51am

Request Review

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

7 issues found across 20 files

Confidence score: 2/5

  • There is high regression risk in apps/api/src/integration-platform/services/generic-device-sync.service.ts: skipped devices are omitted from syncedIdentifiers, which can cause valid provider devices to be deleted in Phase 2, and existing-device updates do not refresh memberId ownership changes.
  • apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts currently updates local provider state even when apiClient.post fails, creating a concrete user-facing inconsistency where failed saves appear successful.
  • apps/api/src/trigger/integration-platform/run-device-sync.ts and apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts can misreport system health (expiring valid credentials on generic 401s and returning success: true on failed batch triggering), which makes operational failures harder to detect and recover from.
  • Pay close attention to apps/api/src/integration-platform/services/generic-device-sync.service.ts, apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts, apps/api/src/trigger/integration-platform/run-device-sync.ts, apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts, and apps/api/src/integration-platform/controllers/dynamic-integrations.controller.ts - data deletion risk, false-success UI/state, incorrect connection erroring, misleading run status, and unhandled validation exceptions should be addressed before merge.
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="apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts">

<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts:82">
P1: Check `apiClient.post` errors before updating local provider state; this currently treats failed saves as success.</violation>
</file>

<file name="apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts">

<violation number="1" location="apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts:219">
P2: `success` is always returned as `true` even when task batch triggering throws, so partial/failed runs are reported as successful.</violation>
</file>

<file name="apps/api/src/trigger/integration-platform/run-device-sync.ts">

<violation number="1" location="apps/api/src/trigger/integration-platform/run-device-sync.ts:52">
P1: Don’t mark connection credentials as expired on every 401; internal service-token auth failures also return 401 and will incorrectly flip valid connections to error.</violation>
</file>

<file name="apps/api/src/integration-platform/controllers/dynamic-integrations.controller.ts">

<violation number="1" location="apps/api/src/integration-platform/controllers/dynamic-integrations.controller.ts:70">
P2: Handle invalid `deviceSyncDefinition` with `safeParse` and return a 400 error instead of letting `parse` throw.</violation>

<violation number="2" location="apps/api/src/integration-platform/controllers/dynamic-integrations.controller.ts:183">
P2: Use `safeParse` for `deviceSyncDefinition` in create flow to avoid unhandled parse exceptions.</violation>
</file>

<file name="apps/api/src/integration-platform/services/generic-device-sync.service.ts">

<violation number="1" location="apps/api/src/integration-platform/services/generic-device-sync.service.ts:98">
P1: Devices skipped for missing member are excluded from `syncedIdentifiers`, which can cause Phase 2 to delete still-present provider devices.</violation>

<violation number="2" location="apps/api/src/integration-platform/services/generic-device-sync.service.ts:135">
P1: Existing-device updates never refresh `memberId`, so device ownership changes from the provider are not applied.</violation>
</file>

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

Comment thread apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts Outdated
Comment thread apps/api/src/trigger/integration-platform/run-device-sync.ts Outdated
};
}
return {
success: true,

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not a regression — this is a pre-existing pattern. The schedule task returned success: true before this PR. Changing it here would be unrelated scope creep.

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.

Thanks for the feedback! I already have a similar learning that covers this, so I'm keeping the existing one.


const rawDeviceSyncDefCreate = body.deviceSyncDefinition;
const validatedDeviceSyncDefCreate = rawDeviceSyncDefCreate
? SyncDefinitionSchema.parse(rawDeviceSyncDefCreate)

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same as above — follows the existing syncDefinition pattern which also uses parse. Both should be migrated to safeParse together in a separate PR for consistency.

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.

Thanks for the feedback! I already have a similar learning that covers this, so I'm keeping the existing one.


const rawDeviceSyncDef = body.deviceSyncDefinition;
const validatedDeviceSyncDef = rawDeviceSyncDef
? SyncDefinitionSchema.parse(rawDeviceSyncDef)

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentionally matches the existing syncDefinition validation pattern on the line above, which also uses parse. Changing only the device variant to safeParse would be inconsistent — both should be changed together in a separate cleanup.

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.

Thanks for the feedback! I've saved this as a new learning to improve future reviews.

- Check apiClient error before updating state in setSyncProvider
- Remove incorrect 401 connection error-marking in trigger task
- Track all active device identifiers before member lookup in Phase 2
- Include memberId in device update for ownership changes

Co-Authored-By: Claude Opus 4.6 (1M context) <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