* feat: send pseudonymized install report (tenant/user hash, tb version) to IoT Hub
* refactor: source install-report tb version/edition from ProjectInfo
Use the existing ProjectInfo component (getProjectVersion/getProductType)
instead of an ad-hoc optional BuildProperties field and a hard-coded "CE"
edition, so PE reports its own product type and the version is the canonical
cleaned value. Constructor-injected via @RequiredArgsConstructor.
If iotHubInstalledItemService.save throws after the local entity was already created, the entity was orphaned — visible in the tenant but invisible to IoT Hub update/delete. Extracted the per-type entity-deletion dispatch from deleteInstalledItem into a deleteEntityForDescriptor helper and call it from doInstallVersion and registerDeviceInstall on tracking-save failure. registerDeviceInstall now also wraps reportVersionInstalled best-effort to match doInstallVersion.
The post-install Thread.sleep was driven by installTimeoutMs from the uploaded solution.json with no upper bound — a malicious or misconfigured template could pin an HTTP worker thread indefinitely. Cap it at a configurable max (default 60s) exposed as iot-hub.max-install-timeout-ms with an IOT_HUB_MAX_INSTALL_TIMEOUT_MS env override.
Stream entries through a fixed buffer and abort with IOException when the archive crosses any of three configurable thresholds: total uncompressed bytes, per-entry uncompressed bytes, or entry count. Defaults — 200 MiB total, 50 MiB per entry, 10 000 entries — and env-var overrides IOT_HUB_MAX_UNCOMPRESSED_ARCHIVE_BYTES / IOT_HUB_MAX_UNCOMPRESSED_ENTRY_BYTES / IOT_HUB_MAX_ARCHIVE_ENTRY_COUNT are exposed under iot-hub in thingsboard.yml.
Root entries other than WIDGET or SOLUTION_TEMPLATE should always be installed even when a prior version is already installed — only widgets and solution templates are deduplicated by item id at install-plan resolution time.
Route all parse-failure throws through a single parseFailure(action, itemType[, section], cause) helper so the user-facing wording lives in one place and update paths share the template with install paths.
- IotHubRestClient.getVersionFileData now streams the response and
enforces a configurable size cap (iot-hub.max-file-data-size-bytes,
env IOT_HUB_MAX_FILE_DATA_SIZE_BYTES, default 100 MiB). Oversized
payloads are rejected up front via Content-Length and mid-stream
for chunked transfers, preventing OOMs from unknown bodies.
- DefaultIotHubService guards every iotHubRestClient.getVersionInfo
call against a null response, and rejects update attempts where
the installed item's type does not match the new version's type
("Installed item type does not match the new version's item type.").
Solution-template descriptor is also populated with
tenantTelemetryKeys / tenantAttributeKeys captured during install.
Drops the empty updateDeviceProfile stub.
- Add a tenant-scoped findByTenantIdAndId + deleteByTenantIdAndId to
IotHubInstalledItemDao (and the bulk deleteByTenantIdAndIdIn JPA
query). IotHubInstalledItemServiceImpl now uses these so reads
and deletes always honour the tenant boundary.
- Database: add lts schema_update.sql + schema-entities-idx.sql
indexes on iot_hub_installed_item(tenant_id),
(tenant_id, item_type), and (tenant_id, item_id) for the new
tenant-scoped lookups.
- DefaultSolutionService: minor cleanup touched alongside.
- .gitignore trailing-newline fix; remove stale superpowers
plan/spec docs that have been folded into the implementation.
Map data_coding 0 (SMSC default alphabet) to the GSM 7-bit charset instead
of US-ASCII, so GSM-alphabet characters are no longer lost to '?'. Fall back
to ISO-8859-1 for unrecognized coding schemes instead of failing the send,
and wrap send failures in SmsSendException so over-length and encoding errors
surface clearly. Assert exact wire bytes for the UCS-2, Cyrillic and GSM 7-bit
cases.
- 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)
Bump LangChain4j to 1.16.1-TB1, which adds frequencyPenalty and
presencePenalty support to the langchain4j-google-genai integration,
and pass both parameters through for Google Gemini and Google Vertex AI
Gemini models.
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
- Provide a scoped Angular ErrorHandler on the dynamic component's
injector in HtmlContainerWidgetComponent so template-runtime
exceptions raised inside user-authored Angular templates flow
through handleWidgetException instead of crashing the global
Angular ErrorHandler.
- Add `ctx.invokeAction($event, actionName, additionalParams?)` to
WidgetActionsApi / WidgetComponent and a `WidgetDestroyCallback` +
ctx.registerDestroyCallback() API on WidgetContext (callbacks run
in registration order on destroy, errors per-callback are caught
so one bad cleanup doesn't break the rest).
- Surface widget actions on the HTML Container basic config: render
tb-widget-actions-panel with the new strokedPanel input below
the html-container settings, and register a default "JavaScript"
multi-action source on the html_container widget JSON.
- Expand the widget-completion docs for `registerDestroyCallback`
(when it fires, what to use it for, lifecycle semantics, exact
callback signature `() => void`) and for `invokeAction`'s
`additionalParams` arg (forwarded to the configured JS action
handler, common payload examples).