feat: device import from integrations#2802
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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 fromsyncedIdentifiers, which can cause valid provider devices to be deleted in Phase 2, and existing-device updates do not refreshmemberIdownership changes. apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.tscurrently updates local provider state even whenapiClient.postfails, creating a concrete user-facing inconsistency where failed saves appear successful.apps/api/src/trigger/integration-platform/run-device-sync.tsandapps/api/src/trigger/integration-platform/run-integration-checks-schedule.tscan misreport system health (expiring valid credentials on generic 401s and returningsuccess: trueon 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, andapps/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.
- 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>
There was a problem hiding this comment.
2 issues found
Confidence score: 2/5
- I’m scoring this as high risk because both findings are medium-high severity (7/10) with solid confidence, and each can materially affect production behavior.
- In
apps/api/src/trigger/integration-platform/run-device-sync.ts,maxDurationis set as1000 * 60 * 10; Trigger.dev expects seconds, so this becomes ~600,000 seconds (~7 days) instead of 10 minutes, which can cause unexpectedly long-running jobs and operational regressions. - In
apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx, device sync controls appear to be available without the expected RBAC gate, creating a concrete risk that unauthorized users can change org-level sync settings or trigger sync actions. - Pay close attention to
apps/api/src/trigger/integration-platform/run-device-sync.tsandapps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx- duration units and missing permission gating are the key merge risks.
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/components/DevicesTabContent.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx:64">
P1: Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
|
||
| return ( | ||
| <div className="space-y-6"> | ||
| <DeviceSyncProviderSelector /> |
There was a problem hiding this comment.
P1: Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx, line 64:
<comment>Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.</comment>
<file context>
@@ -60,6 +61,7 @@ export function DevicesTabContent({ isCurrentUserOwner }: DevicesTabContentProps
return (
<div className="space-y-6">
+ <DeviceSyncProviderSelector />
<DeviceComplianceChart
fleetDevices={filteredFleetDevices}
</file context>
Addresses the cubic review plus critical issues found in an adversarial review of PR #2802 (device import from integrations). Make it work: - device sync ran through the EMPLOYEE interpreter (SyncEmployeeSchema), so every device was dropped and nothing ever imported. Add a standalone interpretDeclarativeDeviceSync that resolves devicesPath and validates each item with SyncDeviceSchema; the controller now uses it (redundant re-validate removed). - allow authoring device-sync integrations: add the device_sync capability and deviceSyncDefinition (+ devicesPath) to the dynamic integration schema. Make it safe: - gate Phase 2 removal behind isDirectorySource (default false), mirroring the employee sync. A non-authoritative or partial provider response can no longer hard-delete devices (which cascade-delete their Findings). Hard delete stays behind the gate with a warning to convert it to a soft removal first. - Phase 1 and the P2002 fallback no longer hijack agent/Fleet devices that share a hardware serial; devices with no serialNumber and no externalId are skipped. Cubic findings: - maxDuration was in milliseconds (~7 days); Trigger.dev expects seconds. - DeviceSyncProviderSelector controls are gated on integration:update. - the P2002 fallback update now refreshes memberId on ownership change. UX: - partial-failure syncs now surface a toast instead of failing silently. - connection.lastSyncAt is recorded so the selector shows "Last synced". Tests: device interpreter (4), service gating/source-scoping/identifier guard, maxDuration regression, and RBAC component tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
8 issues found across 24 files
Confidence score: 2/5
- There are multiple high-confidence, user-impacting correctness issues (sev 6–7) across API and data layers, so merge risk is elevated rather than routine.
- The most severe issue is in
apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts: the scheduler can reportsuccess: trueeven when dispatch fails, which can hide real integration-check failures and delay detection. apps/api/src/integration-platform/controllers/sync.controller.tsallows aconnectionId/providerSlugmismatch, andpackages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sqllacks a uniqueness guard for(integrationConnectionId, externalDeviceId), creating concrete cross-provider and duplicate-device risks.- Pay close attention to
apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts,apps/api/src/integration-platform/controllers/sync.controller.ts, andpackages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql- they contain the highest-impact correctness and data-integrity concerns.
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/components/DevicesTabContent.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx:64">
P1: Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.</violation>
</file>
<file name="packages/db/prisma/schema/organization.prisma">
<violation number="1" location="packages/db/prisma/schema/organization.prisma:30">
P2: Normalize `deviceSyncProvider` to `null` before persistence. Empty string values can be saved as a configured provider and then get picked up by the scheduler, which will try to sync against a blank slug.</violation>
</file>
<file name="packages/db/prisma/schema/device.prisma">
<violation number="1" location="packages/db/prisma/schema/device.prisma:41">
P2: Add an index covering `externalDeviceId` for device-sync lookups. The new sync service queries by `externalDeviceId` + `integrationConnectionId`, but the schema only indexes `integrationConnectionId`, so the fallback lookup can degrade to a scan as integration device counts grow.</violation>
</file>
<file name="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql">
<violation number="1" location="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql:16">
P2: Add a unique constraint for integration-managed devices; the current plain index does not prevent duplicate rows for the same `(integrationConnectionId, externalDeviceId)` key.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
|
|
||
| // Device sync provider (e.g., 'jamf', 'kandji') | ||
| // When set, the scheduled sync will import devices from this provider | ||
| deviceSyncProvider String? |
There was a problem hiding this comment.
P2: Normalize deviceSyncProvider to null before persistence. Empty string values can be saved as a configured provider and then get picked up by the scheduler, which will try to sync against a blank slug.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/schema/organization.prisma, line 30:
<comment>Normalize `deviceSyncProvider` to `null` before persistence. Empty string values can be saved as a configured provider and then get picked up by the scheduler, which will try to sync against a blank slug.</comment>
<file context>
@@ -25,6 +25,10 @@ model Organization {
+ // Device sync provider (e.g., 'jamf', 'kandji')
+ // When set, the scheduled sync will import devices from this provider
+ deviceSyncProvider String?
+
apiKeys ApiKey[]
</file context>
| @@index([organizationId]) | ||
| @@index([isCompliant]) | ||
| @@index([agentSessionId]) | ||
| @@index([integrationConnectionId]) |
There was a problem hiding this comment.
P2: Add an index covering externalDeviceId for device-sync lookups. The new sync service queries by externalDeviceId + integrationConnectionId, but the schema only indexes integrationConnectionId, so the fallback lookup can degrade to a scan as integration device counts grow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/schema/device.prisma, line 41:
<comment>Add an index covering `externalDeviceId` for device-sync lookups. The new sync service queries by `externalDeviceId` + `integrationConnectionId`, but the schema only indexes `integrationConnectionId`, so the fallback lookup can degrade to a scan as integration device counts grow.</comment>
<file context>
@@ -29,15 +29,26 @@ model Device {
@@index([organizationId])
@@index([isCompliant])
@@index([agentSessionId])
+ @@index([integrationConnectionId])
}
</file context>
| @@index([integrationConnectionId]) | |
| @@index([integrationConnectionId, externalDeviceId]) |
| ALTER TABLE "Organization" ADD COLUMN "deviceSyncProvider" TEXT; | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "Device_integrationConnectionId_idx" ON "Device"("integrationConnectionId"); |
There was a problem hiding this comment.
P2: Add a unique constraint for integration-managed devices; the current plain index does not prevent duplicate rows for the same (integrationConnectionId, externalDeviceId) key.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql, line 16:
<comment>Add a unique constraint for integration-managed devices; the current plain index does not prevent duplicate rows for the same `(integrationConnectionId, externalDeviceId)` key.</comment>
<file context>
@@ -0,0 +1,16 @@
+ALTER TABLE "Organization" ADD COLUMN "deviceSyncProvider" TEXT;
+
+-- CreateIndex
+CREATE INDEX "Device_integrationConnectionId_idx" ON "Device"("integrationConnectionId");
</file context>
Fixes the second cubic review on PR #2802: - schedule: report success based on whether every queued task batch was dispatched, instead of always returning success: true (was masking batchTrigger failures; this schedule now also dispatches device syncs). - device sync endpoint: verify the connectionId actually belongs to providerSlug, so a connection can't be driven through the wrong provider manifest/sync logic. - set-device-sync-provider: normalize blank/whitespace provider to null so an empty string is never persisted and later picked up by the scheduler. - Device: add @@unique([integrationConnectionId, externalDeviceId]) (+ migration) to prevent duplicate integration devices and back the fallback lookup; the P2002 fallback now resolves the conflict by serial OR externalDeviceId (and no longer queries with an undefined identifier). - DeviceSyncProviderSelector: show the provider picker when the saved provider is no longer connected (was leaving the UI stuck); gate the useDeviceSync hook on the integration:update permission so users without it make no API calls. - useDeviceSync: abort the sync when persisting the provider choice fails, instead of syncing with a stale/unsaved provider. Tests: externalId P2002 fallback (service), picker visibility + hook-disabled gating (component), abort-on-set-failure + no-fetch-when-disabled (hook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@cubic-dev-ai review it |
@tofikwest I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
5 issues found across 26 files
Confidence score: 2/5
- There is a high-impact data integrity risk in
apps/api/src/integration-platform/services/generic-device-sync.service.ts: Phase 2 hard-deletes missing devices, which can irreversibly remove device/history data instead of allowing recovery. apps/api/src/integration-platform/services/generic-device-sync.service.tsalso missesserialNumberbackfill on updates, which can leave records unlinkable and increase chances of later pruning/duplication; combined with API/orchestration handling gaps, this is more than a minor edge case.- This can likely be stabilized with targeted fixes, but current evidence points to concrete regression risk (data loss + incorrect success reporting + 500 on invalid
providerinput inapps/api/src/integration-platform/controllers/sync.controller.ts). - Pay close attention to
apps/api/src/integration-platform/services/generic-device-sync.service.ts,apps/api/src/integration-platform/controllers/sync.controller.ts,apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts,apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts- prevent data loss/duplication, validate provider input, ensure failure propagation, and refresh stale sync metadata.
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/components/DevicesTabContent.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx:64">
P1: Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.</violation>
</file>
<file name="packages/db/prisma/schema/organization.prisma">
<violation number="1" location="packages/db/prisma/schema/organization.prisma:30">
P2: Normalize `deviceSyncProvider` to `null` before persistence. Empty string values can be saved as a configured provider and then get picked up by the scheduler, which will try to sync against a blank slug.</violation>
</file>
<file name="packages/db/prisma/schema/device.prisma">
<violation number="1" location="packages/db/prisma/schema/device.prisma:41">
P2: Add an index covering `externalDeviceId` for device-sync lookups. The new sync service queries by `externalDeviceId` + `integrationConnectionId`, but the schema only indexes `integrationConnectionId`, so the fallback lookup can degrade to a scan as integration device counts grow.</violation>
</file>
<file name="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql">
<violation number="1" location="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql:16">
P2: Add a unique constraint for integration-managed devices; the current plain index does not prevent duplicate rows for the same `(integrationConnectionId, externalDeviceId)` key.</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:326">
P1: Phase 2 uses hard delete for missing devices, causing irreversible loss of device/history data instead of recoverable removal.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| const idsToDelete = toRemove.map((d) => d.id); | ||
|
|
||
| try { | ||
| await db.device.deleteMany({ |
There was a problem hiding this comment.
P1: Phase 2 uses hard delete for missing devices, causing irreversible loss of device/history data instead of recoverable removal.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/integration-platform/services/generic-device-sync.service.ts, line 326:
<comment>Phase 2 uses hard delete for missing devices, causing irreversible loss of device/history data instead of recoverable removal.</comment>
<file context>
@@ -0,0 +1,356 @@
+ const idsToDelete = toRemove.map((d) => d.id);
+
+ try {
+ await db.device.deleteMany({
+ where: { id: { in: idsToDelete } },
+ });
</file context>
- backfill serialNumber on updates so an externalId-matched device becomes serial-linkable once the provider reports one (prevents later mismatch / duplication / pruning). - setDeviceSyncProvider: accept only a non-empty string; non-string input no longer throws 500 on .trim() and normalizes to null. - schedule: success now also reports failure when any device-sync dispatch throws, not just task-check batches. - useDeviceSync: revalidate provider metadata after a sync so the selector's "Last synced" reflects the new value instead of staying stale. Note: the Phase 2 hard delete (cubic P1) is intentionally gated behind isDirectorySource (default false) so it never runs; converting it to a recoverable soft-removal is deferred per the chosen "no pruning yet" approach (see the warning comment on Phase 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
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/components/DevicesTabContent.tsx">
<violation number="1" location="apps/app/src/app/(app)/[orgId]/people/devices/components/DevicesTabContent.tsx:64">
P1: Device sync controls are mounted for every Devices-tab visitor, but they mutate org-level sync settings and trigger syncs without any RBAC gate. Please hide this behind the same permission model used for other device actions.</violation>
</file>
<file name="packages/db/prisma/schema/organization.prisma">
<violation number="1" location="packages/db/prisma/schema/organization.prisma:30">
P2: Normalize `deviceSyncProvider` to `null` before persistence. Empty string values can be saved as a configured provider and then get picked up by the scheduler, which will try to sync against a blank slug.</violation>
</file>
<file name="packages/db/prisma/schema/device.prisma">
<violation number="1" location="packages/db/prisma/schema/device.prisma:41">
P2: Add an index covering `externalDeviceId` for device-sync lookups. The new sync service queries by `externalDeviceId` + `integrationConnectionId`, but the schema only indexes `integrationConnectionId`, so the fallback lookup can degrade to a scan as integration device counts grow.</violation>
</file>
<file name="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql">
<violation number="1" location="packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql:16">
P2: Add a unique constraint for integration-managed devices; the current plain index does not prevent duplicate rows for the same `(integrationConnectionId, externalDeviceId)` key.</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:326">
P1: Phase 2 uses hard delete for missing devices, causing irreversible loss of device/history data instead of recoverable removal.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
… 400 setDeviceSyncProvider now distinguishes null (explicit clear, allowed) from malformed input (non-string, or blank/whitespace), returning 400 instead of silently coercing to null and clearing the org's configured provider. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
device_syncas a newIntegrationCapabilityso any integration (platform or dynamic) can declare device import supportGenericDeviceSyncService— a two-phase sync service (import active devices, remove disappeared ones) mirroring the existing employee sync pattern?syncType=device), provider selection (device-sync-provider), and dynamic device sync trigger endpointsuseDeviceSynchook andDeviceSyncProviderSelectorcomponent to the Devices tab in PeopledeviceSyncProvidersetTest plan
GenericDeviceSyncServicehas 7 unit tests covering: create, update, skip (no member), inactive exclusion, remove stale, keep current, false-delete guarddevice_synccapability, select it, click "Sync now"🤖 Generated with Claude Code
Summary by cubic
Adds device import from integrations with a new
device_synccapability, a dedicated device sync interpreter, provider selection, and scheduled runs. Validates devices correctly, backfills serials, enforces a unique external ID per connection, and hardens validation to block wrong‑provider syncs, unsafe deletes, and malformed provider inputs.New Features
device_synccapability,SyncDeviceschema, andinterpretDeclarativeDeviceSyncwithdevicesPathin@trycompai/integration-platform; dynamic manifests can storedeviceSyncDefinition.GET/POST /v1/integrations/sync/device-sync-provider(POST rejects non‑string/blank; accepts non‑empty string or null),GET /available-providers?syncType=device,POST /dynamic/:provider/devicesverifies theconnectionIdbelongs toproviderSlug, uses the device interpreter, refreshes OAuth, saves logs, recordsconnection.lastSyncAt, and backfillsserialNumberon updates matched by external ID.GenericDeviceSyncServiceimports active devices (serial first, then connection‑scoped external ID), backfills serials on updates, updates ownership, skips when no stable ID, protects agent/fleet devices, tracks identifiers before pruning, and gates prune behindisDirectorySource(default false) with P2002 fallback updates by serial or external ID.run-device-synctask in@trigger.dev/sdk(maxDuration in seconds), and the daily orchestrator triggers device sync for orgs with a provider set and reports failure if any batch or device‑sync dispatch fails.useDeviceSyncandDeviceSyncProviderSelectorin People → Devices; controls and hook gated byintegration:update, provider picker appears when the saved provider isn’t connected, aborts syncing if setting the provider fails, shows and revalidates “Last synced,” and supports “Sync now.”Migration
source,integrationConnectionId,externalDeviceId),organization.deviceSyncProvider,dynamicIntegration.deviceSyncDefinition, and a unique index on(integrationConnectionId, externalDeviceId).device_syncand include adeviceSyncDefinition(setdevicesPathif needed).SERVICE_TOKEN_TRIGGER,BASE_URL) for@trigger.dev/sdk.Written for commit 5c4d7af. Summary will update on new commits.