BOLT-1322 [5-NEW] domains/device: refreshVehicleKeyPairing service + GraphQL mutation (sync orchestration)
completedNew service method + GraphQL mutation that orchestrates the sync key-pairing check end to end.
Location:
- Service: domains/device/src/services/command/checkPairingStatus.ts (new file). Wire into domains/device/src/services/command/index.ts.
- GraphQL: domains/device/src/subgraph/schema/mutations/refreshVehicleKeyPairing.ts (new file). Wire into the mutation barrel.
Service signature:
async function checkPairingStatus({
deviceOnWorkspaceId: string,
authorization?: Pick<AuthorizationService, "device">
}): Promise<{
success: boolean,
keyPaired: boolean | null,
applicable: boolean,
extendedCapabilities?: Record<string, unknown>,
manufacturer: string,
stale?: boolean,
command: Command
}>
Logic:
1. Authorize (canUpdate; mirrors sendCommand semantics for /commands/* surface).
2. Load deviceOnWorkspace + device.
3. If device.manufacturer !== "tesla":
- Write Command row with status="completed", type="vehicle:check-pairing-status", output={ keyPaired: null, applicable: false }, executedAt=now()
- Return synchronously: { success: true, keyPaired: null, applicable: false, manufacturer, command }
4. If tesla:
- Resolve manufacturerUserId for the active connection (look up via deviceOnWorkspace → connection helper that already exists; check repositories/connectionRepository or similar)
- Call adapter HTTP endpoint POST /internal/manufacturers/tesla/vehicle/check-pairing-status with { manufacturerDeviceId: device.manufacturerDeviceId, manufacturerUserId } — 5s timeout.
- On adapter success:
- output = { keyPaired, applicable: true, extendedCapabilities }
- Write Command row with status="completed", output, executedAt=now()
- Upsert vehicleStaticState via deviceStaticStateRepository.upsertVehicleStaticState({ deviceId: device.id, vehicleStaticState: { keyPaired, extendedCapabilities } })
- Return { success: true, keyPaired, applicable: true, extendedCapabilities, manufacturer: "tesla", command }
- On adapter timeout/5xx:
- Read current vehicleStaticState.keyPaired
- Write Command row with status="failed", reason="<adapter error>", output={ keyPaired: <cached>, applicable: true, stale: true }
- Return { success: false, keyPaired: <cached>, applicable: true, stale: true, manufacturer: "tesla", command }
- DO NOT throw — let the REST handler decide whether to surface as 502 or graceful 200 with stale flag.
GraphQL mutation typeDef:
type Mutation {
refreshVehicleKeyPairing(deviceOnWorkspaceId: ID!): KeyPairingResult!
}
type KeyPairingResult {
success: Boolean!
keyPaired: Boolean
applicable: Boolean!
extendedCapabilities: JSON
manufacturer: String!
stale: Boolean
command: Command!
}
Tests:
- non-Tesla short-circuit (no adapter call mocked, no upsert call, Command row created with applicable:false)
- Tesla happy path (adapter mock called, Command row + staticState upsert verified)
- Tesla adapter timeout (Command status=failed, stale=true, staticState NOT upserted)
- Tesla adapter 5xx (same as timeout)
- Unauthorized (canUpdate=false) → throws auth error
Branch: havoc/bolt-1322-device-orchestration
Worktree: ../mono-bolt-1322-device-orchestration
Depends on: [1/7] [3/7] [6/7-adapter-endpoint]
Done when: service + mutation tests pass, branch builds, federation lock regenerates cleanly.
Event Timeline
created
status_change
queued → in_progress
failed
lease expired — re-queued for retry
in_progress → queued
status_change
queued → completed