- Extract the tenant-profile fetch lock stripe count to a named constant (PR #15744)
- Trim the stale global-lock sentence from the per-tenant lock comment (PR #15744)
- Rename the rate-limit onCreate callback to onMiss and document its idempotency requirement (PR #15744)
- Reuse a single tenant profile local in update(TenantProfileUpdateResult) (PR #15744)
- Make the transport callback thread pool size configurable via transport.callback_thread_pool_size (PR #15744)
- Add a parameterized test locking the TransportLimitsType enum-to-profile-field mapping (PR #15744)
- Add device/gateway rate-limit coverage asserting update(tenantId) reaches tracked entities (PR #15744)
- Add a same-tenant fetch-dedup test and pin the cross-tenant test to distinct stripes (PR #15744)
Construct CoapServer and the LwM2M bootstrap server inside the init try block so a failure in the constructor or build() is cleaned up by the existing catch. Guard CoAP shutdown() against a null server. Add a DTLS-enabled CoAP test that covers the dtlsSessionsExecutor shutdown branch.
The defective code lives in common/transport/transport-api and is shared by all
transports (MQTT, HTTP, CoAP, LwM2M, SNMP); the production incident happened to
surface on MQTT.
On a cold tenant-profile cache (e.g. after a cache clear + restart), a device
reconnect storm could serialize the whole transport instance behind tenant-profile
resolution, saturating the callback pool and stalling the node for ~15 minutes.
Two compounding causes are addressed:
- DefaultTransportTenantProfileCache held a single process-wide ReentrantLock across
the synchronous cross-service getEntityProfile round-trip, so every tenant-profile
cache miss in the whole process was serialized one-at-a-time. Replace it with a
bounded set of per-tenant locks (Guava Striped) so different tenants resolve
concurrently while concurrent misses for the same tenant are still de-duplicated.
- DefaultTransportRateLimitService performed that blocking fetch inside
ConcurrentHashMap.computeIfAbsent's mapping function, holding a CHM bin lock across
the remote round-trip. Pre-fetch the tenant profile before computeIfAbsent so no bin
lock is held across I/O.
Also de-duplicate the four near-identical getXRateLimits methods into one generic
helper, move the per-type rate-limit getters onto the TransportLimitsType enum, and
avoid fetching the tenant profile four times in update(TenantId).
Upgrade LangChain4j to 1.15.1-TB1 and migrate GitHub Models to the
official OpenAI integration (OpenAiOfficialChatModel), enabling JSON
Schema (strict) structured output for GitHub Models and exposing JSON
Schema as a response format for Anthropic and Amazon Bedrock.
- Rename model capability flags to supportsSchemalessJsonOutput() and
supportsJsonSchemaOutput(), declared explicitly per provider
- Push response-format capability checks into TbResponseFormat and
simplify response-format handling in TbAiNode
- Remove obsolete manual JSON escaping for GitHub Models, which now
double-escaped requests under the official OpenAI SDK
- Drop the now-obsolete opennlp-tools version pin (the upgraded
LangChain4j fork ships opennlp-tools >= 2.5.9)
- Update the AI rule node UI to offer response formats per provider,
refresh available model lists, and migrate toggle options to @if
- Expand configurer tests to verify per-provider model configuration
Extend the IoT Hub install dialog so installing a rule chain can also set
it as the Default rule chain on a Device or Asset profile in one step.
The confirm step shows three actions: Cancel, Install (creates the chain
without targeting any profile), and Set for profile (opens a picker step).
The picker step has Cancel, Back, and Install — confirming with a profile
that already has a non-null defaultRuleChainId routes through a
confirm-overwrite step before replacing it. CALCULATED_FIELD keeps its
existing single-button flow unchanged.
Backend:
* RuleChainInstalledItemDescriptor gains a nullable EntityId
targetProfileId field, persisted in the existing descriptor JSON column
(no schema migration; @JsonIgnoreProperties(ignoreUnknown=true) handles
pre-existing rows).
* DefaultIotHubService.installRuleChain() now accepts SecurityUser and
JsonNode data; a new setAsDefaultRuleChain() helper applies the chain
as Default rule chain on the selected DEVICE_PROFILE or ASSET_PROFILE
via the existing tbDeviceProfileService / tbAssetProfileService save
paths (so the change shows up in the tenant audit log as a normal
profile update).
Frontend:
* iot-hub-install-dialog.component grows a per-ItemType selectEntityConfig
map, a 'confirm-overwrite' state, and the methods
installAsEntityProfileDefault, selectEntityBack, resolveOverwrite,
confirmOverwriteReplace, confirmOverwriteCancel.
* tb-entity-select gets a 512px min-width above the gt-sm breakpoint so
the picker renders at a consistent width regardless of the prompt text
length (mirrors the pattern in recipient-notification-dialog).
* New i18n keys: rule-chain-install-desc, rule-chain-install-as-default,
select-profile-for-rule-chain, rule-chain-overwrite-title,
rule-chain-overwrite-body, rule-chain-overwrite-replace.
SimpleCalculatedFieldState.formatResult cast the double result down to
int via TbUtils.toInt when "Decimals by default" was 0 (the UI default).
BigDecimal.intValue() returns only the low-order 32 bits, so a sum
above ~2.1B wrapped to a negative number (e.g. 3,980,173,734 ->
-314,793,562).
Add TbUtils.toLong(double) alongside toInt (toInt is left untouched to
preserve TBEL script behavior), switch the CF precision=0 path to it,
and add a Long branch in createResultJson so the JSON node is emitted
as a numeric long
- Group tomcat, commons-lang3 version properties under spring-boot.version
- Drop thymeleaf override (PE-only dependency, not present in CE)
- Drop lz4 plumbing: kafka-clients 3.9.2 and cassandra-all 5.0.7 now transitively ship at.yawk.lz4:lz4-java, making the Dec 2025 CVE hack obsolete
AbstractTbQueueConsumerTemplate.poll() returned emptyList() immediately
when partitions was empty, bypassing both doPoll() and the secondary
sleep guard (which is also skipped for backends that report
isLongPollingSupported()==true, e.g. Kafka). The result was a
permanent CPU-burning loop on consumers whose partition assignment
ended up empty after a rebalance cascade — observed on 26
ie-downlink-consumer threads (~244% total CPU) until container restart.
Route the empty-partition path through sleepAndReturnEmpty() so the
caller honors durationInMillis regardless of long-polling support.
Moved NioEventLoopGroup allocations into the try block so that a
constructor failure for the second group no longer leaks the first.
Channel close failures during cleanup now attach via addSuppressed
instead of replacing the original BindException. Narrowed the outer
catch from Throwable to Exception, removing the brittle (Error) cast
that would have masked any direct Throwable subclass.
* feat(iot-hub): scaffold ALARM_RULE item type and CREATOR_VISIBLE_ITEM_TYPES
* feat(iot-hub): wire ALARM_RULE through browse, item card, detail dialog, installed items, and install dialog
* feat(iot-hub): reorder home cards, hide Dashboards, add Alarm Rules
* feat(iot-hub): scaffold ALARM_RULE descriptor and reject install with v4.3 upgrade hint
Registers AlarmRuleInstalledItemDescriptor in the Jackson polymorphism so
4.2 can browse Alarm Rule items from the marketplace without descriptor
deserialization failures.
The install handler explicitly rejects ALARM_RULE with a friendly message
asking the user to upgrade to v4.3+. The full install/update/delete
implementation depends on CalculatedFieldType.ALARM (added in v4.3) and
will land once master is merged into this branch.
* feat(iot-hub): hide redundant Type filter on Alarm Rules browse
* feat(iot-hub): show v4.3 update info dialog directly when installing an Alarm Rule
SolutionTemplateInstalledItemDescriptor and SolutionInstallResponse now
carry tenantTelemetryKeys and tenantAttributeKeys lists. SolutionService
.deleteSolution takes the full descriptor (instead of just the
created-entity list) so uninstall can also clean up tenant-scoped
telemetry/attributes. Update IotHub install descriptor population and
delete call sites; force-update path logs and continues if delete throws.
Creates missing system images from application/src/main/data/resources/images
during LTS patch startup, mirroring the upgrade-path loadSystemResources logic.
Existing system images in the DB are left untouched.
Prevents UnrecognizedPropertyException during rolling upgrades when a
newer node writes a cached entity with an added field and an older node
reads it back. The Redis-backed TbJsonRedisSerializer now uses
JacksonUtil.IGNORE_UNKNOWN_PROPERTIES_JSON_MAPPER instead of the strict
OBJECT_MAPPER used by JacksonUtil.fromBytes.
Store install state (form values, entity outputs, selected connectivity)
in DeviceInstalledItemDescriptor during registration. Installed Items
info icon for DEVICE type re-fetches the ZIP and opens the wizard in
read-only review mode:
- All stepper steps clickable (jump to any step)
- Form fields populated with stored values and disabled
- Entity progress shows all items as completed
- Markdown instructions rendered with resolved variables
- Download buttons and image galleries still work
- Single "Close" button in footer
Backend: add selectedConnectivity and installState fields to descriptor
Test: verify installState deserialization round-trip
The admin settings endpoint requires SYS_ADMIN authority but the device
install wizard runs as TENANT_ADMIN.
Fix: add getConnectivityInfo(baseUrl) to DeviceConnectivityService which
resolves host/port with baseUrl fallback (reusing existing getHost/getPort
logic). Expose via new TENANT_ADMIN endpoint GET /api/iot-hub/connectivity.
Remove AdminService dependency from the dialog component.
* Add device-package.models.ts with install wizard types
Defines ConnectivityType, InstallStepType, ENTITY_STEP_TYPES,
stepTypeAliasMap, DevicePackageInfo, DeviceInstallStep, form field
interfaces, and EntityStepProgress types for the device install wizard.
* Add device install wizard dialog component for IoT Hub
Multi-step dialog that provisions TB entities from a device package ZIP:
- Connectivity type selector, instruction/markdown views, dynamic forms
- Entity creation with progress tracking (device profile, device, dashboard, rule chain)
- Variable resolution across steps (form values, entity outputs, positional refs)
* fix(iot-hub): fix device install dialog template and translations
- Use correct tb-markdown inputs ([data] and [usePlainMarkdown])
- Fix translation key format (hyphens, not dots)
- Remove unused ConnectivityType import and resolveImageUrl method
- Auto-advance to first step when single connectivity type
- Use inline data URIs for helpImage directly
* feat(iot-hub): wire device install dialog into all install entry points
Branch DEVICE item type to fetch ZIP and open TbDeviceInstallDialogComponent
instead of the simple install dialog. Applied to:
- Item detail dialog (install button)
- Browse component (card install button)
- Home component (card install button)
* fix(iot-hub): fix build errors in device install dialog
- Fix tb-markdown input: [markdownText] → [data] in done view
- Fix JSZip entry type annotation for strict TS compilation
* fix(iot-hub): reuse existing device profiles and add missing retry translation
- Find device profile by name before creating; reuse if exists
- Add action.retry translation key (was showing raw key)
* feat(iot-hub): add find-or-create for rule chains in device install
Reuse existing rule chains by name (same pattern as device profiles).
Shared entities (device profile, rule chain) are looked up before creation.
Per-install entities (device, dashboard) are always created fresh.
* feat(iot-hub): refactor device install dialog to use mat-stepper
- Replace single-panel view switching with mat-horizontal-stepper
- Group consecutive entity steps into a single "Provisioning" stepper step
- Each SHOW_INSTRUCTION and SHOW_FORM is its own stepper step
- Add 2s minimum display time per entity creation step
- Connectivity selector shown before stepper (not a stepper step)
- Progress step auto-advances to next step after completion
- Stepper shows completed checkmarks for finished steps
* fix(iot-hub): fix stepper navigation by tracking step completion
Linear mat-stepper requires steps to be marked completed before
advancing. Added completed flag to WizardStep, set it when user
clicks Next or when entity steps finish.
* feat(iot-hub): add device package install tracking with entity cleanup
Backend:
- Expand DeviceInstalledItemDescriptor with createdEntityIds and dashboardId
- Add POST /api/iot-hub/device/register endpoint to save installed item
- Implement reverse-order entity deletion for DEVICE uninstall
- Update DEVICE case in updateItemVersion to clean old entities
Frontend:
- Expand DeviceInstalledItemDescriptor TS model
- Add registerDeviceInstall API method
- Wire wizard to collect entity IDs and register after completion
- Enable dashboard navigation for installed device packages
* fix(iot-hub): add type discriminator to device register request body
Jackson requires the 'type' field for @JsonTypeInfo polymorphic
deserialization of DeviceInstalledItemDescriptor.
* feat(iot-hub): show entity type in provisioning progress rows
Each progress row now displays "Device Profile — ESP32 Dev Kit" instead
of just "ESP32 Dev Kit", making it clear what type of entity is being
provisioned.
* fix(iot-hub): suppress global error toast and add Back button on provisioning error
- Add ignoreErrors: true to all entity creation API calls so errors
are only shown in the dialog, not as global toast notifications
- Add Back button on provisioning error to navigate back to the last
form step, allowing users to fix parameters (e.g. device name)
- Make form steps editable so stepper allows backward navigation
- Reset progress step state when going back so it re-runs with
updated form values
* fix(iot-hub): resolve ExpressionChangedAfterItHasBeenCheckedError in device install dialog
Inject ChangeDetectorRef and call detectChanges() after async state
mutations (entity step status changes, stepper auto-advance, ngOnInit
completion). The NG0100 error occurred because async operations
(setTimeout, HTTP calls) mutated component state outside Angular's
change detection cycle.
* fix(iot-hub): fix device register endpoint JSON deserialization
Accept JsonNode and use JacksonUtil.treeToValue() instead of direct
@RequestBody DeviceInstalledItemDescriptor. The @JsonTypeInfo on the
parent interface caused Spring's message converter to fail when
deserializing the concrete class directly.
* fix(iot-hub): fix dashboardId serialization in device register request
DashboardId implements EntityId which uses EntityIdDeserializer requiring
both entityType and id fields. Frontend was sending {"id":"..."} without
entityType, causing "Missing entityType or id!" 400 error.
Fix: include entityType: "DASHBOARD" in the dashboardId payload.
Add deserialization unit test to verify the exact frontend payload format.
- Add SolutionService with install/delete/rollback flow, entity provisioning
(customers, assets, devices, dashboards, edges, rule chains, CFs, users),
telemetry emulators, instructions templating, and public customer support
- Add SolutionInstallDialogComponent in home/components/solution with markdown
details display and "Go to main dashboard" navigation
- Update IoT Hub install dialog to open solution dialog on template success
- Add SOLUTION_TEMPLATE type support across frontend (item cards, detail dialog,
installed items table with dashboard entity mapping)
- Add solution instructions button to item detail dialog for installed templates
- Update edge detail URLs and dashboard links for correct CE routing
The alreadyProcessed() method used separate get() and put() calls on the
local cache, allowing concurrent threads in the notification executor pool
to both read null and bypass deduplication, creating duplicate notifications.
Replace with a single compute() call that atomically checks and updates
the cache entry, preventing the race between concurrent trigger processing.
Also fix: discard external cache timestamps that are more than 1 hour in
the future (clock skew protection), and avoid reading back from the SOFT
ref local cache when writing to external cache (GC could null it out).