ThingsBoard Monitoring Revival (Phase 0) Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Port the backend-proxy ThingsBoard monitoring implementation from the stale, unmerged issue-104 branch onto current master in a new branch revive/thingsboard-monitoring, resolving the tb_device_id/thingsboard_device_id schema conflict and removing the unused plaintext-credential flow, so the branch builds, tests pass, and it’s ready for its own PR.
Architecture: issue-104’s 29 commits are mostly review-fixup churn on top of a handful of genuinely new files under backend/src/monitoring/ and frontend/src/{hooks,pages,services,types}/monitoring*. Rather than replaying 29 individual commits (several bundle unrelated changes — see Task 10 note), this plan checks out the final state of each new file directly from the issue-104 tip via git checkout issue-104 -- <path>, which is safe because these are net-new files with no master history to conflict with. The few files that exist on both branches and diverged (app.module.ts, alerts.controller.ts, App.tsx, device.entity.ts) are edited by hand with exact, reviewed changes. Tasks 2, 3, 6, 8, 9 (checkout-based ports) don’t follow red-green TDD — the code and its tests already exist and were already reviewed on issue-104; the task is to bring both across together and confirm they still pass against current master. Tasks 4, 5, 7, 10, 11, 12 are hand-written edits to existing master files and get a verify step after each change.
Tech Stack: NestJS 11, TypeORM 0.3, Jest (backend), Vitest (frontend), Socket.IO, React Router, Bun workspaces.
Spec: docs/superpowers/specs/2026-07-04-thingsboard-monitoring-revival-design.md
Task 1: Create the revival branch
Files: none (branch operation only)
- Step 1: Fetch latest and confirm current
mastertip
git fetch origin
git log --oneline -1 origin/master
Expected: shows the current tip commit hash of master (at time of writing, 9d2b0e9c8, but use whatever origin/master reports — master may have moved further).
- Step 2: Create the branch from
origin/master
git checkout -b revive/thingsboard-monitoring origin/master
Expected: Switched to a new branch 'revive/thingsboard-monitoring'.
- Step 3: Confirm
issue-104is available locally for checkouts in later tasks
git fetch origin issue-104
git log --oneline -1 issue-104
Expected: shows bf3ac2008 feat(monitoring): add org-scoped ThingsBoard rpc proxy endpoint (or later, if that branch changed — it should not have).
Task 2: Port the epic architecture design doc
Files:
Create:
docs/superpowers/specs/2026-05-20-thingsboard-monitoring-epic-design.mdStep 1: Check out the file from
issue-104
git checkout issue-104 -- docs/superpowers/specs/2026-05-20-thingsboard-monitoring-epic-design.md
- Step 2: Confirm it was added
git status --short
Expected: A docs/superpowers/specs/2026-05-20-thingsboard-monitoring-epic-design.md
- Step 3: Commit
git add docs/superpowers/specs/2026-05-20-thingsboard-monitoring-epic-design.md
git commit -m "docs: add ThingsBoard monitoring epic architecture design"
Task 3: Port the backend monitoring module
Files:
- Create:
backend/src/monitoring/monitoring.module.ts - Create:
backend/src/monitoring/monitoring.constants.ts - Create:
backend/src/monitoring/controllers/monitoring.controller.ts(+.spec.ts) - Create:
backend/src/monitoring/dto/monitoring-contracts.spec.ts - Create:
backend/src/monitoring/dto/monitoring-event.dto.ts - Create:
backend/src/monitoring/dto/monitoring-history.dto.ts - Create:
backend/src/monitoring/dto/monitoring-snapshot.dto.ts - Create:
backend/src/monitoring/dto/query-monitoring-history.dto.ts - Create:
backend/src/monitoring/dto/monitoring-command.dto.ts - Create:
backend/src/monitoring/dto/monitoring-command-response.dto.ts - Create:
backend/src/monitoring/interfaces/thingsboard-telemetry-payload.interface.ts - Create:
backend/src/monitoring/gateways/monitoring-stream.gateway.ts(+.spec.ts) - Create:
backend/src/monitoring/services/monitoring-query.service.ts - Create:
backend/src/monitoring/services/monitoring-scope-resolver.service.ts(+.spec.ts) - Create:
backend/src/monitoring/services/monitoring-stream.service.ts(+.spec.ts) - Create:
backend/src/monitoring/services/monitoring-observability.service.ts(+.spec.ts) - Create:
backend/src/monitoring/services/monitoring-command.service.ts(+.spec.ts) - Create:
backend/src/monitoring/services/thingsboard-integration-adapter.service.ts(+.spec.ts)
This is the entire backend/src/monitoring/ tree at the issue-104 tip — 24 files, all net-new on master, so a directory checkout cannot conflict.
- Step 1: Check out the whole directory from
issue-104
git checkout issue-104 -- backend/src/monitoring
- Step 2: Confirm all 24 files were added
git status --short backend/src/monitoring | wc -l
Expected: 24
- Step 3: Install any new dependency this module needs
The module uses @nestjs/jwt and the ws WebSocket client library (used inside thingsboard-integration-adapter.service.ts). Check whether they’re already dependencies:
cd backend && grep -E '"@nestjs/jwt"|"ws"|"@types/ws"' package.json
If ws/@types/ws are missing, install them (skip whichever already show up):
cd backend && bun add ws && bun add -d @types/ws
- Step 4: Run the monitoring module’s own tests in isolation
cd backend && npx jest src/monitoring --silent=false
Expected: all suites in backend/src/monitoring/**/*.spec.ts pass. If any fail due to an import resolving to a module that doesn’t exist yet on this branch (e.g. MonitoringModule not yet registered in AppModule, handled in Task 4), note the failure and continue — full green is only expected after Task 4.
- Step 5: Commit
git add backend/src/monitoring
git commit -m "feat(monitoring): port ThingsBoard monitoring module from issue-104"
Task 4: Register MonitoringModule in AppModule
Files:
Modify:
backend/src/app.module.ts:45-46(import),backend/src/app.module.ts:152-153(module list)Step 1: Add the import
In backend/src/app.module.ts, insert this line after the InventoryModule import (line 45) and before the NotificationsModule import (line 46), keeping alphabetical order by path:
import { MonitoringModule } from './monitoring/monitoring.module';
- Step 2: Register the module
In the same file, add MonitoringModule, to the @Module({ imports: [...] }) array, right after AutomationModule, (currently the last entry):
ThingsboardModule,
AutomationModule,
MonitoringModule,
],
- Step 3: Run Biome to fix import ordering
cd backend && bun run lint:fix
Expected: no errors; import may get reordered automatically — verify with git diff backend/src/app.module.ts that MonitoringModule import and usage are both still present.
- Step 4: Rebuild and confirm the app boots
cd backend && bun run build
Expected: compiles with no TypeScript errors (confirms MonitoringModule’s own dependencies — Device entity, JwtModule — resolve correctly).
- Step 5: Re-run monitoring tests, now with the module registered
cd backend && npx jest src/monitoring --silent=false
Expected: all suites pass.
- Step 6: Commit
git add backend/src/app.module.ts
git commit -m "feat(monitoring): register MonitoringModule in AppModule"
Task 5: Add the alert acknowledge alias endpoint
Files:
- Modify:
backend/src/devices/controllers/alerts.controller.ts
The monitoring frontend service (monitoringService.acknowledgeAlert, ported in Task 6) calls POST /alerts/:id/ack — an alias for the existing PUT /alerts/:id/acknowledge. master’s alerts.controller.ts doesn’t have this alias yet.
- Step 1: Add the
Postimport
In backend/src/devices/controllers/alerts.controller.ts, change:
import { Body, Controller, Get, Param, Put, Query, UseGuards } from '@nestjs/common';
to:
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
- Step 2: Add the alias method
Insert this method directly after the existing acknowledge method (after its closing }, before the @Put(':id/resolve') method):
@Post(':id/ack')
@ApiOperation({ summary: 'Acknowledge alert (alias)' })
@ApiParam({ name: 'id', description: 'Alert ID' })
@ApiResponse({ status: 200, description: 'Alert acknowledged' })
@ApiResponse({ status: 401, description: 'Unauthorized' })
@ApiResponse({ status: 404, description: 'Alert not found' })
@ApiResponse({ status: 400, description: 'Alert is not active' })
@RequirePermissions('device:update:all', 'device:update:own')
async ack(
@CurrentUser() currentUser: CurrentUserData,
@Param('id') id: string,
@Body() dto: AcknowledgeAlertDto,
) {
return this.acknowledge(currentUser, id, dto);
}
- Step 3: Write a test for the alias
Find the existing acknowledge test in backend/src/devices/controllers/alerts.controller.spec.ts (or create the spec file if it doesn’t exist yet — check first: ls backend/src/devices/controllers/alerts.controller.spec.ts). Add:
it('POST /alerts/:id/ack delegates to acknowledge()', async () => {
const dto: AcknowledgeAlertDto = { notes: 'ack via alias' };
const currentUser = { organizationId: 'org-1' } as CurrentUserData;
const acknowledgeSpy = jest
.spyOn(controller, 'acknowledge')
.mockResolvedValue({ id: 'alert-1' } as never);
const result = await controller.ack(currentUser, 'alert-1', dto);
expect(acknowledgeSpy).toHaveBeenCalledWith(currentUser, 'alert-1', dto);
expect(result).toEqual({ id: 'alert-1' });
});
If the spec file doesn’t exist, check how the controller is instantiated in a sibling spec (e.g. devices.controller.spec.ts) for the Test.createTestingModule boilerplate and mirror it rather than guessing.
- Step 4: Run the test
make backend-test FILE=src/devices/controllers/alerts.controller.spec.ts
Expected: PASS, including the new ack test.
- Step 5: Commit
git add backend/src/devices/controllers/alerts.controller.ts backend/src/devices/controllers/alerts.controller.spec.ts
git commit -m "feat(alerts): add POST /alerts/:id/ack alias for monitoring contract"
Task 6: Port the frontend monitoring page, hooks, service, and types
Files:
Create:
frontend/src/types/monitoring.types.tsCreate:
frontend/src/services/monitoring.service.tsCreate:
frontend/src/hooks/monitoring/useMonitoringSnapshot.ts(+.spec.ts)Create:
frontend/src/hooks/monitoring/useMonitoringStream.tsCreate:
frontend/src/pages/dashboard/monitoring/MonitoringPage.tsx(+.spec.tsx)Step 1: Check out the files from
issue-104
git checkout issue-104 -- \
frontend/src/types/monitoring.types.ts \
frontend/src/services/monitoring.service.ts \
frontend/src/hooks/monitoring \
frontend/src/pages/dashboard/monitoring
- Step 2: Confirm the socket.io-client dependency exists
useMonitoringStream.ts imports socket.io-client. Check:
cd frontend && grep '"socket.io-client"' package.json
If missing, install it:
cd frontend && bun add socket.io-client
- Step 3: Run the new frontend tests
make frontend-test FILE=src/hooks/monitoring/useMonitoringSnapshot.spec.ts
make frontend-test FILE=src/pages/dashboard/monitoring/MonitoringPage.spec.tsx
Expected: both PASS.
- Step 4: Commit
git add frontend/src/types/monitoring.types.ts frontend/src/services/monitoring.service.ts frontend/src/hooks/monitoring frontend/src/pages/dashboard/monitoring
git commit -m "feat(frontend): port monitoring page, hooks, and service from issue-104"
Task 7: Wire a smoke-test route for the monitoring page
Files:
- Modify:
frontend/src/App.tsx
master’s dashboard routing has diverged from issue-104’s (role-based dashboard.routes.tsx component map vs. a flat routes file) — porting issue-104’s routing diff verbatim isn’t possible. Embedding the page inside “Tổng quan” is explicitly deferred to a later spec (#109). For this phase, just add a standalone route so the page is reachable for manual verification.
- Step 1: Add the import
In frontend/src/App.tsx, add this import in alphabetical position among the other @/pages/... imports (near the other dashboard-area imports, e.g. right after the DeviceOperationsPage import):
import { MonitoringPage } from '@/pages/dashboard/monitoring/MonitoringPage';
- Step 2: Add the route
Inside the <Route element={<DashboardLayout />}> block, add this route right after the /dashboard route:
<Route path="/dashboard" element={<DashboardRoute />} />
<Route path="/monitoring" element={<MonitoringPage />} />
- Step 3: Type-check the frontend
cd frontend && bun run build
Expected: builds with no TypeScript errors.
- Step 4: Commit
git add frontend/src/App.tsx
git commit -m "feat(frontend): wire standalone /monitoring route for smoke testing"
Task 8: Port the observability runbook doc
Files:
Create:
docs/monitoring/thingsboard-integration-runbook.mdStep 1: Check out the file
git checkout issue-104 -- docs/monitoring/thingsboard-integration-runbook.md
- Step 2: Commit
git add docs/monitoring/thingsboard-integration-runbook.md
git commit -m "docs: add ThingsBoard integration runbook baseline"
Task 9: Port the end-to-end contract tests
Files:
Create:
backend/src/monitoring/monitoring-contract.e2e-spec.tsCreate:
frontend/src/services/monitoring.contract.spec.tsStep 1: Check out both files
git checkout issue-104 -- backend/src/monitoring/monitoring-contract.e2e-spec.ts frontend/src/services/monitoring.contract.spec.ts
- Step 2: Run the backend e2e spec
cd backend && bun run test:e2e -- monitoring-contract
Expected: PASS. If it fails on database/setup issues unrelated to monitoring (e.g. missing e2e test infra), note this in the task’s PR description — that infra is outside this plan’s scope.
- Step 3: Run the frontend contract spec
make frontend-test FILE=src/services/monitoring.contract.spec.ts
Expected: PASS.
- Step 4: Commit
git add backend/src/monitoring/monitoring-contract.e2e-spec.ts frontend/src/services/monitoring.contract.spec.ts
git commit -m "test(monitoring): port end-to-end API and tenant-scope contract tests"
Task 10: Reconcile the tb_device_id / thingsboard_device_id schema conflict
Files:
- Delete:
backend/src/migrations/1778500000000-AddTbDeviceIdToDevices.ts - Create:
backend/src/migrations/1778600000000-AddThingsboardDeviceIdToDevices.ts - Modify:
backend/src/devices/entities/device.entity.ts:76-77 - Modify:
backend/src/devices/device-command-dispatch.service.ts:28,37 - Modify:
backend/src/devices/devices.service.ts:220,519,554 - Modify:
backend/src/devices/device-activity-monitor.service.ts:40,48 - Modify:
backend/src/telemetry/telemetry.service.ts:206,218,246 - Modify (rename in mocks):
backend/src/telemetry/telemetry.service.spec.ts,backend/src/devices/alert-config.service.spec.ts,backend/src/devices/alert.service.spec.ts,backend/src/devices/device-command-dispatch.service.spec.ts,backend/src/devices/system-device-inventory.service.spec.ts,backend/src/devices/devices.service.spec.ts,backend/src/devices/device-activity-monitor.service.spec.ts
master and issue-104 each independently added a device-id-mapping column to devices with different names/types (tb_device_id varchar(100) vs thingsboard_device_id uuid unique). No production data exists in either column (confirmed). issue-104’s version is adopted: it’s correctly typed (ThingsBoard entity IDs are UUIDs) and has a unique index.
- Step 1: Delete
master’s migration
git rm backend/src/migrations/1778500000000-AddTbDeviceIdToDevices.ts
- Step 2: Create the replacement migration
Create backend/src/migrations/1778600000000-AddThingsboardDeviceIdToDevices.ts:
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class AddThingsboardDeviceIdToDevices1778600000000 implements MigrationInterface {
name = 'AddThingsboardDeviceIdToDevices1778600000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE devices ADD COLUMN thingsboard_device_id uuid`);
await queryRunner.query(
`CREATE UNIQUE INDEX IDX_devices_thingsboard_device_id ON devices(thingsboard_device_id)`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX IDX_devices_thingsboard_device_id`);
await queryRunner.query(`ALTER TABLE devices DROP COLUMN thingsboard_device_id`);
}
}
- Step 3: Update the
Deviceentity
In backend/src/devices/entities/device.entity.ts, change:
@Column({ name: 'tb_device_id', length: 100, type: 'varchar', nullable: true })
tbDeviceId: string | null;
to:
@Column({ name: 'thingsboard_device_id', type: 'uuid', nullable: true })
thingsboardDeviceId: string | null;
- Step 4: Rename every entity-field usage across the backend
This renames the identifier tbDeviceId to thingsboardDeviceId everywhere it appears as a whole word in backend source (this includes a few local parameter names in thingsboard-client.service.ts that don’t strictly need it, which is harmless and keeps naming consistent):
cd backend
grep -rl '\btbDeviceId\b' src --include="*.ts" | xargs sed -i '' 's/\btbDeviceId\b/thingsboardDeviceId/g'
- Step 5: Verify no stray references remain
cd backend && grep -rn '\btbDeviceId\b' src --include="*.ts"
Expected: no output.
- Step 6: Run the affected test suites
cd backend
npx jest src/devices src/telemetry --silent=false
Expected: all PASS. If any fail, check that the rename didn’t accidentally change a string literal (e.g. a DTO key name unrelated to the entity) — only the identifier tbDeviceId should have changed, not JSON/API field names.
- Step 7: Test the migration up and down against a dev database
cd backend
bun run migration:run
bun run migration:revert
bun run migration:run
Expected: all three commands succeed with no errors. The final state should have thingsboard_device_id present on devices.
- Step 8: Commit
git add backend/src/migrations backend/src/devices backend/src/telemetry
git commit -m "fix(devices): reconcile tb_device_id/thingsboard_device_id schema conflict"
Task 11: Remove the plaintext ThingsBoard credentials endpoint (backend)
Files:
- Delete:
backend/src/thingsboard/thingsboard.controller.ts - Delete:
backend/src/thingsboard/thingsboard.service.ts - Delete:
backend/src/thingsboard/dto/tb-credentials.dto.ts - Modify:
backend/src/thingsboard/thingsboard.module.ts
Confirmed via full-repo search: no frontend page, component, or hook calls GET /thingsboard/credentials or imports anything from this controller/service. It exists only to read user.thingsboardUsername/user.thingsboardPassword and return the password in plaintext — dead code with a real security defect. ThingsBoardClientService (the actively-used backend-to-ThingsBoard REST client) is untouched.
- Step 1: Delete the dead files
git rm backend/src/thingsboard/thingsboard.controller.ts backend/src/thingsboard/thingsboard.service.ts backend/src/thingsboard/dto/tb-credentials.dto.ts
- Step 2: Update the module
Replace the contents of backend/src/thingsboard/thingsboard.module.ts with:
import { Module } from '@nestjs/common';
import { ThingsBoardClientService } from './thingsboard-client.service';
@Module({
providers: [ThingsBoardClientService],
exports: [ThingsBoardClientService],
})
export class ThingsboardModule {}
(This drops the UsersModule import and ThingsboardController/ThingsboardService — nothing else in the module needs UsersModule once getCredentials is gone.)
- Step 3: Confirm nothing else imports the deleted files
cd backend && grep -rn "thingsboard.controller\|thingsboard.service'\|tb-credentials.dto" src --include="*.ts"
Expected: no output (the only remaining thingsboard-client.service matches are fine — different file).
- Step 4: Build and run the thingsboard module’s tests
cd backend && bun run build
npx jest src/thingsboard --silent=false
Expected: builds clean; thingsboard-client.service.spec.ts still passes (it doesn’t touch the deleted files).
- Step 5: Commit
git add backend/src/thingsboard
git commit -m "fix(thingsboard): remove unused endpoint returning plaintext user credentials"
Task 12: Remove the dead credentials store (frontend) and drop the DB columns
Files:
Delete:
frontend/src/stores/thingsboard.store.tsModify:
backend/src/auth/entities/user.entity.ts:62-77Create:
backend/src/migrations/1778700000000-DropThingsboardCredentialsFromUsers.tsStep 1: Delete the unused frontend store
git rm frontend/src/stores/thingsboard.store.ts
- Step 2: Confirm nothing imports it
cd frontend && grep -rn "thingsboard.store\|useThingsboardStore" src
Expected: no output.
- Step 3: Remove the columns from the
Userentity
In backend/src/auth/entities/user.entity.ts, delete these two blocks:
@Column({
name: 'thingsboard_username',
type: 'varchar',
length: 255,
nullable: true,
})
thingsboardUsername: string | null;
@Column({
name: 'thingsboard_password',
type: 'varchar',
length: 255,
nullable: true,
})
thingsboardPassword: string | null;
- Step 4: Confirm no other code references these fields
cd backend && grep -rn "thingsboardUsername\|thingsboardPassword" src --include="*.ts"
Expected: no output (the deletions in Task 11 already removed the only consumer).
- Step 5: Create the drop-columns migration
Create backend/src/migrations/1778700000000-DropThingsboardCredentialsFromUsers.ts:
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class DropThingsboardCredentialsFromUsers1778700000000 implements MigrationInterface {
name = 'DropThingsboardCredentialsFromUsers1778700000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE users DROP COLUMN IF EXISTS thingsboard_username`);
await queryRunner.query(`ALTER TABLE users DROP COLUMN IF EXISTS thingsboard_password`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE users ADD COLUMN IF NOT EXISTS thingsboard_username character varying(255)`,
);
await queryRunner.query(
`ALTER TABLE users ADD COLUMN IF NOT EXISTS thingsboard_password character varying(255)`,
);
}
}
- Step 6: Test the migration up and down
cd backend
bun run migration:run
bun run migration:revert
bun run migration:run
Expected: all succeed; final state has no thingsboard_username/thingsboard_password columns on users.
- Step 7: Run the full backend build and auth tests
cd backend
bun run build
npx jest src/auth --silent=false
Expected: builds clean, auth tests pass.
- Step 8: Commit
git add frontend/src/stores backend/src/auth/entities/user.entity.ts backend/src/migrations
git commit -m "fix(users): drop unused plaintext ThingsBoard credential columns"
Task 13: Full verification pass
Files: none (verification only)
- Step 1: Run the full backend test suite
cd backend && bun run test
Expected: all suites pass (never use bun test — see AGENTS.md).
- Step 2: Run the full frontend test suite
cd frontend && bun run test
Expected: all suites pass.
- Step 3: Run Biome across the whole repo
bun run check
Expected: no lint/format errors. Fix any that appear and re-run.
- Step 4: Run the backend e2e suite
cd backend && bun run test:e2e
Expected: all pass (or only pre-existing unrelated failures — compare against a test:e2e run on master before this branch if anything looks new).
- Step 5: Manual smoke test — snapshot API
Start the stack (make dev from repo root or equivalent), log in to get a JWT, then:
curl -s -H "Authorization: Bearer <token>" http://localhost:3000/api/v1/monitoring/snapshot | jq
Expected: 200 OK with a JSON body containing a data array (empty is fine if no devices are provisioned in ThingsBoard yet) scoped to the caller’s organization.
- Step 6: Manual smoke test — monitoring page
Open http://localhost:5173/monitoring in a browser while logged in. Expected: page renders with a connection-status indicator and a snapshot table (or the empty/error state if no ThingsBoard backend is reachable) — no unhandled exceptions in the browser console.
- Step 7: Push the branch and open a draft PR
git push -u origin revive/thingsboard-monitoring
Then open a PR against master titled Revive ThingsBoard monitoring integration (Phase 0: rebase & reconciliation), describing that this ports issue-104’s work forward and that later specs (tracked under sub-issues #105–#110 of epic #104) will close the remaining gaps (error model, RBAC matrix docs, observability, UI placement inside “Tổng quan”, RPC audit trail).
Self-Review Notes
- Spec coverage: Branch strategy (Task 1), commit porting via file checkout (Tasks 2–3, 6, 8–9) with the compose/JWT-websocket/topnav commits explicitly dropped per the spec’s “Commits dropped” list, schema reconciliation (Task 10), dead-code removal for both the backend endpoint and frontend store and the
userscolumns (Tasks 11–12), and the verification plan (Task 13) — all spec sections are covered. - Placeholder scan: no TBD/TODO; every code step shows exact content.
- Type consistency:
Device.thingsboardDeviceId(Task 10) matches the identifier used by the rename script and is the same nameissue-104’s monitoring services already expect (they referencedevice.entity.tsonly indirectly viaThingsBoardClientService/DevicesModule, not directly — no cross-task type mismatch to reconcile there).