Skip to content

feat: device import from integrations#2802

Merged
tofikwest merged 19 commits into
mainfrom
feat/device-import-from-integrations
Jun 3, 2026
Merged

feat: device import from integrations#2802
tofikwest merged 19 commits into
mainfrom
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 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

    • Platform: device_sync capability, SyncDevice schema, and interpretDeclarativeDeviceSync with devicesPath in @trycompai/integration-platform; dynamic manifests can store deviceSyncDefinition.
    • API: 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/devices verifies the connectionId belongs to providerSlug, uses the device interpreter, refreshes OAuth, saves logs, records connection.lastSyncAt, and backfills serialNumber on updates matched by external ID.
    • Service: GenericDeviceSyncService imports 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 behind isDirectorySource (default false) with P2002 fallback updates by serial or external ID.
    • Scheduler: run-device-sync task 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.
    • App: useDeviceSync and DeviceSyncProviderSelector in People → Devices; controls and hook gated by integration: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

    • Run Prisma migrations to add device sync fields (source, integrationConnectionId, externalDeviceId), organization.deviceSyncProvider, dynamicIntegration.deviceSyncDefinition, and a unique index on (integrationConnectionId, externalDeviceId).
    • Ensure manifests declare device_sync and include a deviceSyncDefinition (set devicesPath if needed).
    • Configure scheduler envs (SERVICE_TOKEN_TRIGGER, BASE_URL) for @trigger.dev/sdk.

Written for commit 5c4d7af. Summary will update on new commits.

Review in cubic

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)
comp-framework-editor Ready Ready Preview, Comment Jun 3, 2026 8:21pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
app Skipped Skipped Jun 3, 2026 8:21pm
portal Skipped Skipped Jun 3, 2026 8:21pm

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
Comment thread apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts Outdated
- 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>
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.

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, maxDuration is set as 1000 * 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.ts and apps/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

Comment thread apps/api/src/trigger/integration-platform/run-device-sync.ts Outdated

return (
<div className="space-y-6">
<DeviceSyncProviderSelector />
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with cubic

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>
@tofikwest
Copy link
Copy Markdown
Contributor

@cubic-dev-ai review it

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 3, 2026

@cubic-dev-ai review it

@tofikwest I have started the AI code review. It will take a few minutes to complete.

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.

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 report success: true even when dispatch fails, which can hide real integration-check failures and delay detection.
  • apps/api/src/integration-platform/controllers/sync.controller.ts allows a connectionId/providerSlug mismatch, and packages/db/prisma/migrations/20260508100534_add_device_sync_fields/migration.sql lacks 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, and packages/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

Comment thread apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts Outdated

// Device sync provider (e.g., 'jamf', 'kandji')
// When set, the scheduled sync will import devices from this provider
deviceSyncProvider String?
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with cubic

@@index([organizationId])
@@index([isCompliant])
@@index([agentSessionId])
@@index([integrationConnectionId])
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 3, 2026

Choose a reason for hiding this comment

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

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>
Suggested change
@@index([integrationConnectionId])
@@index([integrationConnectionId, externalDeviceId])
Fix with cubic

ALTER TABLE "Organization" ADD COLUMN "deviceSyncProvider" TEXT;

-- CreateIndex
CREATE INDEX "Device_integrationConnectionId_idx" ON "Device"("integrationConnectionId");
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with cubic

Comment thread apps/app/src/app/(app)/[orgId]/people/devices/hooks/useDeviceSync.ts Outdated
Comment thread apps/api/src/integration-platform/controllers/sync.controller.ts
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>
@tofikwest
Copy link
Copy Markdown
Contributor

@cubic-dev-ai review it

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 3, 2026

@cubic-dev-ai review it

@tofikwest I have started the AI code review. It will take a few minutes to complete.

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.

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.ts also misses serialNumber backfill 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 provider input in apps/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({
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jun 3, 2026

Choose a reason for hiding this comment

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

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>
Fix with cubic

Comment thread apps/api/src/integration-platform/controllers/sync.controller.ts Outdated
Comment thread apps/api/src/trigger/integration-platform/run-integration-checks-schedule.ts Outdated
- 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>
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.

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

Comment thread apps/api/src/integration-platform/controllers/sync.controller.ts
… 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>
@vercel vercel Bot temporarily deployed to Preview – portal June 3, 2026 20:20 Inactive
@vercel vercel Bot temporarily deployed to Preview – app June 3, 2026 20:20 Inactive
@tofikwest tofikwest merged commit 1fd3cdf into main Jun 3, 2026
11 checks passed
@tofikwest tofikwest deleted the feat/device-import-from-integrations branch June 3, 2026 20:56
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