What Changes in 3.0
Find which 2.6 behaviours change in 3.0, such as the 32 day history cap and workspace topic paths, and what to do before and after upgrading.
- History cap
- 32 days
- Topic paths
- Include the workspace
- Removed
- Shared uns_reader MQTT login
- Old image
- maestrohub/lite:latest stays on 2.6.5
Some 2.6 behaviour changes on purpose in 3.0. Use the table to find the changes that affect you, then read only those sections. upgrade check reports each of these for your own data before anything is changed.
To run the upgrade, go to Upgrade from 2.6.x to 3.0.
Find what affects you
Act before you upgrade. These cannot be undone afterwards.
| Change | It affects you if | What to do |
|---|---|---|
| History is capped at 32 days | You keep more than 32 days of history | Export the history you need. The first start of 3.0 deletes the rest. |
| UNS topic paths include the workspace | MQTT clients or other systems outside MaestroHub use UNS topic paths | Plan the new paths for each client. |
The shared uns_reader MQTT login is gone | An MQTT subscriber signs in as uns_reader | Give each subscriber its own API client. |
| The external MQTT broker (EMQX) is removed | UNS uses an external EMQX broker, or your Helm values or config.yaml configure one | Plan to point every device and gateway at MaestroHub's MQTT address. Delete the EMQX values from your Helm values. |
| Knowledge model, schemas and alerts are rebuilt | You use schemas, asset instances or alert limits on 2.6 | Note what you have: it is not carried over. |
| An expired license no longer falls back to the trial | A license was applied to this instance and has expired or will expire soon, or the instance is licensed by a Fleet Manager | Have a valid license key ready. Without one, pipelines stop after the upgrade. |
Check after you upgrade. The upgrade handles these, and the report tells you what it did.
| Change | It affects you if | What to do |
|---|---|---|
| Organizations are now called workspaces | Always | Nothing for your data. Switch API clients, dashboards on metric labels and Helm values to the new names. |
| Access changes are reported per user | Always | Read the access notes in the report. |
| People are members of workspaces | Always | Nothing for people who had access. Add fleet-only people to the workspaces they need. Give older SSO providers a landing workspace on an installation with several workspaces. |
| Resources created on 2.6.0 may have no owner | You ever ran 2.6.0 | Assign an owner to each resource the report names. |
| Secrets are now encrypted | Always | Keep the new key files with your data. Delete the backup set once 3.0 runs well. |
| Store locations follow one rule | You changed store locations in config.yaml, or use PostgreSQL | Delete the old location keys when convenient. |
| A read from a disconnected device now fails the run | Pipelines read from devices that go offline | Set On Error to continue where a pipeline should go on. |
| A script failure has its own error code | A pipeline compares error codes | Check the pipelines the report lists. |
| A merged row holds only its branch's result | A Merge node in Append or Combine mode feeds a script or expression | Change the reads the report lists to read the branch itself. |
| Every Convert to File output has its header | A CSV Convert to File node feeds a writer with Append to File on, or an expression reads its totalRows | Turn Include Headers off on that node, or change the read. |
| Cron schedules run in their time zone | A cron schedule has a time zone other than UTC, or 2.6 ran directly on a host outside UTC | Nothing to keep the run times. Set the time zone back on the schedules where you want local times. |
| Node timeout and retry settings have limits | A node, or a pipeline's Error Handling default, sets a timeout or retry setting outside the limits the designer offers | Check the new values on the nodes the report lists. |
| Connector behaviours that change on purpose | You use RabbitMQ publish, FTP/SFTP rename, or S3 or Databricks Volumes writes | Read the note on each function the report lists. |
| Connector time settings carry their unit | An API client or script creates or edits connections or functions | Send time settings as durations ("30s"), and use the renamed keys. |
| The first start runs long database work | Your audit trail or execution history is large | Do not stop MaestroHub during the first start. |
| One setting keeps pipeline data on disk | Pipeline triggers, connector data or queued runs must survive a crash, or your config.yaml sets executionQueue.storage | Set nats.embedded.streamStorage: file. Delete executionQueue.storage. |
| AI clients connected over MCP | An AI client (Claude Code, Codex, Cursor and others) works with MaestroHub over MCP | Reissue tokens that hold asset_instances scopes. Update prompts and playbooks that name the removed tools. |
| Cross-origin API calls are refused by default | A web page served from another origin calls the MaestroHub API from the browser, or you rely on the old * default | List those origins in http.corsOrigins (Lite) or http.corsAllowedOrigins (per-service pods). |
| OPC UA servers' certificates are pinned | An OPC UA connection uses a Security Policy other than None | Nothing to keep it running. Compare each pinned thumbprint with the server's own on the connection's Authentication tab. |
| The deployment owns the MQTT broker's ports | You set the broker's port, mode, WebSocket or TLS on the settings page, on Kubernetes or Docker Compose | Nothing for the ports. On Compose, add deploymentOwns to your config.yaml. Move page-set values into the deployment when convenient. |
| Signing out ends the session at once | Always | Nothing. |
| Image and command names | You use the maestrohub/lite image | Nothing now. Move to the new names when convenient. |
History is capped at 32 days
3.0 keeps 1, 7, 14 or 32 days of history by default, for the built-in historian and for TimescaleDB. If 2.6 kept more, upgrade apply sets it to 32 days. This covers a retention over 32 days, a cold store kept forever, and a TimescaleDB retention of "keep forever". At the first 3.0 start, every value older than 32 days is deleted.
upgrade check tells you before anything is applied: the old value, the new value, and how much history the first start deletes. Export what you need first. The upgrade backup does not hold history.
The upgrade does not edit your config.yaml. If your config.yaml enables the cold store with a cold retention of 0 (forever) or more than 32 days, 3.0 refuses to start. The error names the setting to change.
UNS topic paths include the workspace
In 3.0, every UNS topic path contains its workspace: mHv1.0/test/line1 in the workspace durable-goods becomes mHv1.0/durable-goods/test/line1. The upgrade rewrites topics, their history, dashboards, pipeline topic paths and access grants on topics. Topics keep their IDs.
Plan for what the upgrade cannot see:
- MQTT clients and systems outside MaestroHub that subscribe to or publish on UNS topic paths need the new paths. A publish on a 2.6 path is refused: over the HTTP API it answers
400and asks to prefix the topic with your workspace, and over MQTT it is rejected. List every external publisher (operator screens, scripts, gateways) and switch it to the new path when you upgrade. - Retained messages and subscribers on an external broker are not changed. 3.0 no longer uses an external broker at all: see The external MQTT broker (EMQX) is removed.
- Scripts that pick a topic segment by position may need a different position.
checklists each one as needing a person, with the proposed change. - If a workspace has two topics that become the same path in 3.0,
applyrefuses.checknames both topics. Rename or delete one on 2.6, where you know its MQTT consumers, then upgrade.
Store locations follow one rule
In 3.0, two settings decide where every store lives:
storage.engine:sqlite(the default) orpostgres.storage.dataDir: the folder for SQLite stores and other local files. Each store is<storage.dataDir>/<store>.db.
With postgres, one storage.postgres block names the server, and each store gets its own database, <store>_db.
applymoves your stores. Stores that 2.6 kept elsewhere (next to the data folder, under an old name such aspipeline.db, or where your old config placed them) are moved to where 3.0 keeps them.checklists every move first. Stores on another volume need--copy-across-volumes(see Apply options).- Per-store location keys in
config.yamlare ignored now. Keys such asmodules.<name>.storageType,modules.<name>.sqlite.path, a module's ownpostgresblock, and the old secrets, NATS and historian path keys are not read. MaestroHub warns about each one at startup, naming the line to delete. Delete them when convenient. A read-only mounted 2.6 config still starts, with warnings. - The upgrade does not move data between engines, with one exception, below. If a store is in SQLite and your 3.0 configuration places it in PostgreSQL, or the other way round, 3.0 refuses to start and
applyrefuses too.
UNS metadata and the historian are separate
The UNS metadata store holds topics, dashboards and UNS settings. It is an ordinary store and follows storage.engine like every other store. The historian holds your time-series values, in the built-in store or in TimescaleDB. It stays a runtime setting, chosen in the UNS settings, and does not change where the UNS metadata store lives.
2.6 could keep the UNS metadata store in PostgreSQL (modules.uns.storage in the 2.6 config.yaml) while every other store was in SQLite. With storage.engine: sqlite, apply copies that store's tables into <storage.dataDir>/uns.db and converts the copy. It only reads the PostgreSQL database and never writes to it, and your time-series values stay where they are. check lists the copy under Copied out of Postgres, and restore removes it again. If your 3.0 config.yaml no longer names that database, pass the 2.6 file with --from-config.
Secrets are now encrypted
2.6 stored some secrets in plain text. upgrade apply encrypts them, and the report lists one repair per secret without showing its value:
- the SAML service provider's private key (the admin screens now show it masked);
- the OIDC client secret and LDAP bind password of identity providers;
- the SMTP password;
- the keys that sign personal access tokens (PATs) and OAuth2 tokens;
- the access key ID of AWS Lambda, SNS and SQS connections, including in their version history;
- the context engine's API key to a Fleet Manager.
Personal access tokens and OAuth2 tokens keep working. The signing keys are encrypted, not replaced, so every token issued on 2.6 stays valid.
Where a module had no encryption key yet, apply creates one next to the other generated keys (on the single binary, in data/secrets/). The report lists each key file under Encryption keys. Keep these files with your data: without them, the secrets cannot be read.
The backup set still holds the old plain-text values. Protect it like the data itself, and delete it once 3.0 runs well.
If check or apply prints a SECURITY: warning about a publicly known key, rotate that module's key with admin-cli reencrypt <module> after the upgrade.
The shared uns_reader MQTT login is gone
2.6 generated a shared read-only MQTT login, uns_reader, and stored its password in plain text. 3.0 has no such login: MQTT subscribers that sign in with it stop connecting after the upgrade.
Before you upgrade, list the subscribers that use it and plan a login of their own for each: an API client with the read access it needs. You can register the API clients under API Clients on 2.6 already; they are kept by the upgrade. On 3.0, give each one the Data Stream Viewer role (Data.StreamViewer, which subscribes to topic streams), or subscribe access (topic:subscribe) shared on only the topics it needs. The Data Stream Viewer role is new in 3.0. Then switch each subscriber to its client's ID and secret, and to the topics' 3.0 paths (see UNS topic paths include the workspace).
upgrade check notes the login, and upgrade apply removes its stored credential from the UNS settings and their saved copies, together with any access grants made to its subject, client:legacy-read-only.
The external MQTT broker (EMQX) is removed
In 3.0, MaestroHub's built-in MQTT broker is the only broker. UNS no longer connects to an external EMQX broker, and the deployments no longer ship one. In Enterprise the built-in broker runs in every UNS replica, and the replicas act as one broker: see The broker in Enterprise.
Devices and gateways that connected to EMQX must be pointed at MaestroHub's MQTT address. Nothing moves them for you. The address is shown under Connect a device on the MQTT Broker page. On Kubernetes it is the Service <release>-uns-mqtt, port 1883, or 8883 for MQTT over TLS once a certificate is set; the chart's UPGRADING.md (section EMQX removed) has the DNS name and the values. On a single node, MaestroHub publishes 1883, 8883 and 8083 itself. Retained messages and persistent sessions held by EMQX are not carried over: a device subscribes again when it connects, and retained values are retained again the next time they are published. Each device signs in with its own API client, as before.
The stored broker setting moves by itself. If the UNS broker settings say external, the first start of 3.0 moves the installation to the built-in broker. The previous settings stay in the settings history, and an audit event uns_broker.reconfigured is written. The MQTT Broker page shows a notice at the top of its Settings section naming the external broker it replaced and when, until someone clicks Dismiss.
config.yaml keys that are ignored now. MaestroHub still starts with them, and logs a warning for each one once at startup. Delete them when convenient:
modules.uns.defaults.broker.mode: external. It starts the built-in broker. Useembedded(the default) ordisabled.modules.uns.defaults.broker.external.*modules.uns.externalBrokerSessionExpirymodules.uns.ingress.substrateWatch
Helm values that fail the render. emqx, emqxTLS, global.emqxURL, global.emqxTLS, uns.mqtt.external and uns.mqtt.mode: external are no longer accepted, nor is uns.mqtt.embedded.auth (nothing read it: the built-in broker always authenticates every client). The render fails and names the key. Delete them, and run this one upgrade with --reset-then-reuse-values instead of --reuse-values, as UPGRADING.md describes.
Removed API endpoints and scope. Remove them from scripts and API clients:
POST /api/v1/system/uns/settings/test-broker-delegationPOST /api/v1/system/uns/settings/broker-credentialGET /api/v1/system/uns/settings/broker-profilesPOST /api/v1/uns/mqtt/aclPOST /api/v1/uns/mqtt/authenticateGET /api/v1/uns/mqtt/delegation-configPOST /api/v1/authz/mqtt/authenticate- the OAuth2 scope
broker.authz
PUT /api/v1/system/uns/settings refuses broker mode external. DELETE /api/v1/system/uns/settings/broker-replaced is new: it dismisses the notice. While the notice is up, GET /api/v1/system/uns/settings returns it as broker.replaced (from, host, port, at).
Overview issues and alerts that are gone. They watched EMQX's queues: the Overview issues broker_ingest_loss, broker_ingest_queue_filling and broker_ingest_identity, and the alerts MaestroHubUNSIngestLoss, MaestroHubUNSIngestLossUndetected, MaestroHubUNSIngestQueueFilling, MaestroHubUNSBrokerIdentityUnresolved and the maestrohub.emqx group. Remove them from your own dashboards and alert routing.
The licence feature. Custom UNS Infrastructure (custom_uns) now gates only the TimescaleDB historian.
An expired license no longer falls back to the trial
On 2.6, an instance whose license expired went back to the free trial, and every restart started a new trial. On 3.0, the free trial ends for good the first time the instance is licensed: a valid license key is applied, or a Fleet Manager licenses it. When that license expires, an administrator removes it, or the instance is disconnected from the Fleet Manager with no license key of its own, the instance is expired until a valid license is applied. Restarting does not start a trial, and Extend 2 hours is refused.
While an instance is expired, pipelines do not run and licensed features are off. Nothing is deleted.
upgrade check does not report this one. Open System Management → License on 2.6 before you upgrade: if the page shows a free trial on an instance you once licensed, the license has expired, and you need a valid key for 3.0. An instance that was never licensed keeps the trial as before.
Aggregates answer for exactly the window you asked
On TimescaleDB, an aggregate used to read whole rollup buckets. A window that did not start on a bucket boundary lost its first bucket, and its last bucket reached past the window. A stat or gauge panel showing "last 1 hour" could therefore show a different hour, on every historian.
On 3.0 an aggregate holds exactly the samples in the window. Stat and gauge panels ask for one value over the whole window. Numbers from POST /api/v1/uns/data/aggregated and from dashboards change for a window off the bucket grid, because they are now right.
- API clients: a first bucket appears, and the buckets at the edges hold only what is in the window. To get one value over a window, send
"wholeWindow": trueinstead ofbucketWidth. - Widths that are not whole minutes, such as
90s, read raw rows on TimescaleDB, and a window over 7 days at one is refused with a 400. Use a whole-minute width or a shorter window. - Very old windows: past 32 days the raw rows and the 1-minute rollup are gone, so the edges round inward to the finest rollup left, and
coverage.edgeResolutionMssays how far. - Right after a backfill: values can stay short for about a minute until the rollup catches up. The answer says so in
coverage.unrefreshed, and panels show a caption.
A read from a disconnected device now fails the run
On 2.6, a read group whose connection could not read (for example, a PLC that was offline) could complete with empty values, and the pipeline went on to publish nulls. On 3.0, that read fails the node, and the run shows the failure. If a pipeline should go on without the values, set the node's On Error setting to continue.
A script failure has its own error code
On 2.6, a JavaScript node or an expression that threw or did not compile reported UNKNOWN_ERROR, TRANSIENT_ERROR or PERMANENT_ERROR. Which of the three it reported depended on the words in the error message. On 3.0, it always reports SCRIPT_ERROR. A JavaScript node that runs past its timeout reports TIMEOUT.
If a pipeline compares an error code with one of the old codes, for example a condition on a pipeline-event trigger's errors[].errorCode, check that it still catches what you expect. upgrade check lists every pipeline and node that contains one of the old codes. The upgrade does not change them.
A merged row holds only its branch's result
On 3.0, every node's output has two parts: result, the value it produced, and _metadata, the facts about the run. The upgrade rewrites the reads of 2.6 output keys it can follow in scripts and expressions: records, data, results, items and the like become result or _metadata.
A Merge node in Append, Combine by Position or Combine by Key mode changed more than its keys. On 2.6, an item of the merged list could be a branch's whole output, including that branch's _metadata. On 3.0, an item holds only the branch's result, and the merge's own _metadata has counts only. A script that reads _metadata from a merged item, or a 2.6 key such as results, finds nothing on 3.0.
The upgrade cannot rewrite these reads, because which branch an item came from depends on which branches ran. upgrade check lists each one as needing a person. Read the branch directly instead, for example $node["Read Line 1"]._metadata.failed. In Object mode nothing changes beyond the keys.
Every Convert to File output has its header
On 2.6, a Convert to File node wrote its header row only on its first run. Every later file had no header, so a reader took the first data row as the column names, and the run still completed. On 3.0, every CSV and Excel file the node emits starts with its header, unless Include Headers is off. There is nothing to do for a report written with overwrite or to a new file each run: it gets its header back.
Check this if a CSV node feeds a writer with Append to File on, because each run now adds another header row to the file. Turn Include Headers off on that node. A file that already starts with a header then keeps growing with data rows only. A new file needs its header row written once by whatever creates it.
Two smaller changes follow from this:
_metadata.totalRowsnow counts the rows in the emitted file. With Append Mode off that is this run's rows, not a running count. Use a Counter node for a running count.- Always Include Headers has no effect any more, and the designer no longer shows it. Saved pipelines that set it keep loading.
Cron schedules run in their time zone
On 2.6, a cron schedule ignored its Time zone setting. It ran in the time zone of the MaestroHub process: UTC in the MaestroHub container images, or the host's zone when 2.6 ran directly on a host. On 3.0, a cron schedule runs in its time zone. A schedule written as 0 0 6 * * * with time zone Europe/Berlin ran at 06:00 UTC on 2.6, and would run at 06:00 Berlin time on 3.0.
So that every schedule keeps its run times, the upgrade sets the time zone of each such cron schedule to the zone 2.6 ran it in, and the report notes each one. To have a schedule run at local times instead, set its time zone back after the upgrade. If 2.6 ran directly on a host whose zone is not UTC, pass that zone to upgrade check and upgrade apply with --legacy-tz, for example --legacy-tz Europe/Berlin. The upgrade warns when it runs on such a host without the flag.
$trigger._metadata.timezone carries the schedule's time zone, so after the upgrade it reads the zone the schedule was set to. Interval schedules and @every schedules run as before.
On 3.0, the Time zone setting is the only place a schedule's zone is set. A cron expression that names its own zone, such as CRON_TZ=Europe/Berlin 0 0 9 * * *, is not saved: set the time zone to Europe/Berlin and write the expression without it, 0 0 9 * * *. The upgrade does this for you. 2.6 ran such a schedule in the zone its expression named, so the upgrade moves that zone into the time zone setting, and the schedule keeps its run times. If the expression was one 2.6 could not run (an unknown zone, or five fields after the zone), the upgrade leaves it as it was and the report names it.
Organizations are now called workspaces
3.0 calls the isolated set of connections, pipelines, UNS topics, dashboards and people a workspace. 2.6 called it an organization. Nothing about your data changes: every workspace keeps its ID, its slug, its topic paths and its members. A Context Engine has at least one workspace and can create more as the license allows; the license counts workspaces across the whole fleet.
The upgrade converts what is stored: role names, access grants, token scopes, SSO role mappings and the references pipelines make to the workspace. The names that change:
| Where | 2.6 | 3.0 | Old name on 3.0 |
|---|---|---|---|
| UI | Organizations page at /organizations | Workspaces page at /workspaces | Old links redirect |
| HTTP header | X-Organization-ID | X-Workspace-ID | Accepted, deprecated |
| API paths | /api/v1/organizations, /api/v1/authz/orgs/..., .../org-quotas, /api/v1/maestro/memory/org, /api/v1/maestro/org-settings, /api/v1/monitoring/organizations | /api/v1/workspaces, /api/v1/authz/workspaces/..., .../workspace-quotas, /api/v1/maestro/memory/workspace, /api/v1/maestro/workspace-settings, /api/v1/monitoring/workspaces | Accepted, deprecated |
| JSON fields | orgId, org_id, organizationId | workspaceId, workspace_id | Not accepted |
| Error codes | ORG_ACCESS_DENIED, MISSING_ORG, ORG_REQUIRED, ... | WORKSPACE_ACCESS_DENIED, MISSING_WORKSPACE, WORKSPACE_REQUIRED, ... | Not sent |
| Roles | Organization.Admin, Organization.Member | Workspace.Admin, Workspace.Member | Converted by the upgrade |
| Permissions and scopes | org:read, organizations:read, organizations:manage | workspace:read, workspaces:read, workspaces:manage | Tokens issued before the upgrade keep working |
| Pipeline references | $execution.orgId, _metadata.orgId | $execution.workspaceId, _metadata.workspaceId | Converted by the upgrade |
| App Studio SDK | useAppContext().workspace | ||
| MQTT reject reasons | unknown_org, foreign_org_topic | unknown_workspace, foreign_workspace_topic | Not sent |
| Metric labels and log fields | org_id | workspace_id | Not written |
| Parquet file metadata | maestrohub.org_id | maestrohub.workspace_id | Not written |
| Configuration | modules.organizations, modules.uns.ingress.perOrgRatePerSec | modules.workspaces, modules.uns.ingress.perWorkspaceRatePerSec | Read, with a warning at start |
| Helm | organizations: values, image organizations-server, database organizations_db | workspaces: values, image workspaces-server, database workspaces_db | Old values block refused with the rename to make; the database is renamed by the chart's init job |
| Fleet Manager events | org_added, org_removed | workspace_added, workspace_removed | Alert rules saved with the old names keep matching |
After the upgrade, everyone signs in again, because sign-in tokens carry the new names. Personal access tokens and agent tokens keep working until they expire.
Before you upgrade, list the scripts, integrations and dashboards that use the old names: API clients that send JSON fields or read error codes, Grafana or Elasticsearch dashboards on org_id, devices that match the MQTT reject reasons, and Helm values files with an organizations: block.
The audit trail keeps the names it recorded: entries written by 2.6 still say organization, and the audit page shows and filters them under the 3.0 names.
AI clients connected over MCP
An AI client set up against 2.6 keeps its settings: the endpoint is still /api/v1/mcp, and the X-Organization-ID header is still accepted, with Deprecation: true on every response. Switch the header to X-Workspace-ID when convenient.
Keep the address MaestroHub is reached at. A personal access token names the address that issued it. If 3.0 answers on another host or port, every token issued by 2.6 is refused as invalid. Either keep the address, set modules.oauth2.issuer to the old one, or issue new tokens.
Tokens issued by 2.6:
| Scope on the token | On 3.0 |
|---|---|
organizations:read, organizations:manage | Grant workspaces:read, workspaces:manage |
schemas:read, schemas:write | Grant semantics:read, semantics:write. They are read as data_schemas:read, data_schemas:write, which are aliases of the semantics scopes; no scope picker offers the aliases |
asset_instances:read, asset_instances:write | Grant nothing. The asset model is rebuilt as the plant model; issue a token with entities scopes |
Two actions need more than a 2.6 token usually holds. manage_pipelines dry_run needs pipelines:execute. manage_license history needs a signed-in System Administrator: no token scope grants it.
Tools that are gone. A call to one of these names is refused, and the refusal names the tool to use instead, where there is one.
| 2.6 tool | 3.0 tool |
|---|---|
manage_organizations | manage_workspaces; the list key organizations is now workspaces |
manage_schemas, and manage_data_schemas of earlier 3.0 builds | None. Both are removed with the data schemas: libraries, properties and meanings are managed in the UI and the REST API (/api/v1/semantics), and an MCP tool for them is #7530 |
manage_asset_instances | plant_find, plant_match, plant_describe, and plant_propose with plant_decide; giving a topic a meaning has no MCP tool yet (#7530) |
get_asset_context | plant_describe |
find_assets | plant_find, plant_match |
diagnose_asset | plant_diagnose |
recent_changes | plant_history, plant_timeline, or workspace_activity_feed |
Inputs and outputs that change:
manage_alertstakesproperty_id(an id) instead ofattribute_path, and addsshelveandunshelve.manage_accesstakesresource_type: "workspace"instead of"org";access_action: "reveal"is gone.uns_describe_topicreturns abindingsummary instead of the inlineschema. For a topic with a meaning it iskind: "semantic", withschema_idholding the property's id,unitwhat its values are in andlimitsthe rows ofGET /api/v1/uns/topics/{id}/limits.manage_topicsgethas the samebinding.query_topic_data(fetch_recent,fetch_range) andquery_topic_aggregatessay what a topic's values are in when it has a meaning:unitorunits,segmentsandboundaries, as the REST reads do, and anotewhen a value does not read normally. A topic with no meaning answers as before.query_executionslistreturns one summary row per run, andpipeline_idis optional.explore_node_typeslistdropsdescriptionand addsconfig_schemaandconfig_required;getstill returns the description.- An entity in an MCP answer is the REST API's row for it, the same type in the server: a topic is
GET /api/v1/uns/topics/{id}'s topic (list and search rows are the REST tree's and search's rows), a record isPOST /api/v1/uns/data/recent's record, an alert isGET /api/v1/uns/alerts/recent's row, a function isGET /api/v1/connections/{id}/functions/{functionId}'s function, a connection's status isGET /api/v1/connections/{id}/status, and dependency insights areGET /api/v1/dependencies/insights. In 2.6 several rows came back with Go field names (Name,ParentID,TopicPath,PipelineName) or snake_case (topic_id,first_fired_at). This affectsmanage_topics,query_topic_data,query_executions,manage_alerts(list rows),query_alarm_history,uns_describe_topic,uns_semantic_find_topics(hits aretopicId,topicPath,score,matchKind),search,manage_functions(get, create, update),monitor_connectionsstatusandquery_dependenciesinsights. A prompt that names a key, such as "readTopicPath", needs the new name (topicPath). Envelope keys likecount,total_runsandnext_start, thebindingsummary andmanage_schedulerstatsdon't change. Values that change meaning or type:- A topic's
versionis the topic-schema prefix ("mHv1.0"),namethe full path andpaththe path without the prefix. 2.6'sColdRetentionandVersion(the row's revision) are gone; the cold-retention setting is onGET /api/v1/uns/topics/{id}/config. Rows addchildCount,closedToNewTopics,writersandisDeleted; list and search rows also carry the freshness fields (lastSeenAt,freshness, …) as REST's do. uns_describe_topicis the topic row, with an object topic'sattributesas REST's single-topic read has them, plussegments,bindingandchildren. Itspathis now the path without the prefix (the full path isname), andretainis the topic's own setting (nullwhen it inherits); the retain in force iseffectiveRetain, withretainSourceTopicnaming the topic that set it (2.6 sent the effective value asretainandretain_source).- A record is
{id, value, timestamp, retain}plusquality(1 uncertain, 2 bad, absent when good, withqualityReason,qualityLimitandqualitySource) andingestedAt/latencyMswhen the arrival time is known. Timestamps have milliseconds. Who wrote a record is inprovenance, returned only withinclude_provenance: true; the top-levelsourceis gone.fetch_recentandfetch_rangegive the topic once, beside the records. - Alert rows' times have milliseconds (
2026-07-19T10:12:00.000Z).query_alarm_historyrows are the alert row plusactive, so they now also carryworkspaceId, the schema fields,lastRecordTsand the shelf fields. - A function's config is
functionConfig(2.6:Config);manage_functionscreate and update still acceptconfigas an alias.getaddsconnectionName,configValid,storeForwardEligibleand the other fields REST reports, and leaves outworkspaceId, which REST doesn't send.compiledConfig, the engine's internal template cache, is gone;requiredParameterslists the parameters the function's templates need. monitor_connectionsstatusreads the same cluster-wide per-slot report as REST, so it addsaggregatedState,aggregatedReason,circuitBreakerand, for multi-slot connections,owners.query_executionsgetreturns the run as the REST API does, with the pipeline name once (pipelineName).
- A topic's
- When
fetch_recentorfetch_rangemust shorten an answer to fit,notesays only what was done: large values cut to their shape, then "only the first N of M records are shown".fetch_rangestill says how to read on fromnext_start. manage_topicsget,listandsearchanduns_describe_topicrefuse or leave out a topic the caller is denied, as REST does: the same per-topic decision for one topic, the same scoped query for lists, so a denied topic is not a row, not intotaland not in a parent'schildCount. In 2.6 and the 3.0 betas a governance deny on a topic was not applied to them.- A topic read on its own (
GET /api/v1/uns/topics/{id}, the topic in the answers to create, rename, restore and the new-topics and writers settings, and MCPmanage_topicsgetandcreateanduns_describe_topic) now sends the realchildCount: the rows the caller's topic tree lists under it: the children they may read, plus, unlessstrictTopicVisibilityis set, each folder they reach only on the way to a deeper topic they may read (a breadcrumb,isSkeleton). It was always0there, so a branch read alone looked like a leaf.uns_describe_topiclists exactly those rows aschildren, breadcrumbs flaggedisSkeleton, so itschildCountis the length of itschildren.
The server keeps no session. Each request stands alone and Mcp-Session-Id is ignored, so a client never has to reconnect after a restart.
Choose fewer tools. 3.0 has 57 tools. Add ?toolsets=pipelines,connectors to the address, or send the X-MCP-Toolsets header, to list only the sets you use. Tools for licence features you don't have are not listed.
The prompts and playbooks you keep for an AI client are worth one read for the old tool names: the AI is told the replacement when it calls one, but each such call costs a round trip.
Access changes are reported per user
The upgrade keeps each person's access as it was, except where the report says otherwise. upgrade check names every user, group and API client whose access changes, and why. The main changes:
- Workspace settings belong to Workspace.Admin. Identity.Admin and Security.Admin no longer read or update the workspace's settings.
- Identity administration is platform-wide. User accounts and identity providers exist once for the platform, so Identity.Admin and Security.Admin are platform roles on 3.0. An assignment of either inside a workspace is revoked at the upgrade (kept in the grant history) and reported with its holder; it is never widened to the platform. To keep someone's access, grant the role under Identity & Access → System Administrators, or give them Workspace.Admin for the workspace's own roles, groups, agents and API clients, which Workspace.Admin now manages. A custom role that inherited either role no longer does, and one that lists user or identity-provider permissions keeps them, flagged, granting nothing.
- What the identity administrators can and can't do. They act on any account without a platform role (workspace administrators included, every action audited), but not on one holding a platform role they don't fully hold, and they can't grant or revoke such a role. They bring existing people into a workspace as members (see People are members of workspaces); a Workspace.Admin gives roles and group memberships only to people already in its workspace. An identity-provider role mapping that names Identity.Admin or Security.Admin is reported by the upgrade and assigns nothing.
- Platform administrators don't sign in through single sign-on. An account holding a platform role (System.Admin, Identity.Admin, Security.Admin) or a fleet role signs in with its password; an SSO sign-in into it is refused and does not link the account. An account created by SSO has no password: set one before granting it a platform role.
- An API client that acts as any user is a System Administrator's to register. Registering or changing an OAuth2 client with the
mcp:impersonatescope (the Enterprise Maestro service account) needs a System Administrator. Clients registered before keep their scope: review them under Identity & Access → API Clients after the upgrade. - A pipeline's execution identity is set by the workspace's administrator. Binding a pipeline to an OAuth2 client or agent needs
pipeline:updateand the right to manage that client or agent in the pipeline's workspace, instead ofuser:impersonate. - Roles that 2.6.5 failed to apply now apply. On the 2.6.5 single binary, after a restart, the Connect, Automate and Data roles, Compliance.Auditor and the Fleet roles granted nothing. On 3.0 their holders get what the role says. The report lists each holder as a gain.
- Roles that need Advanced Access Control grant nothing on a licence without it. The Connect, Automate, Data and Apps roles, Compliance.Auditor and the Fleet roles are part of Advanced Access Control, an Enterprise feature: a Foundation licence does not carry it. On a licence without it the assignments are kept, so they work again if you add it, but they grant nothing, and they cannot be assigned. Custom roles are Advanced Access Control too, but the ones you already have keep granting; they can be deleted, not edited or assigned. See Licence changes. Give the people and API clients who held them a role your licence offers: Identity & Access → Roles lists each role as available or locked, with the reason.
- Operator roles become Editor roles (Connect, Automate, Data).
- Secret.Revealer and Logging.Reader assignments are revoked. The report names each holder and what the role granted.
- Upgrading from 2.6.0: the report also covers the changes between 2.6.0 and 2.6.5, and names the release and change behind each one. For example, Workspace.Admin no longer manages users and identity providers (a platform Identity.Admin does; it keeps managing its workspace's groups, roles and API clients), Member no longer publishes to UNS topics, and Platform.Admin and Platform.Viewer grant nothing.
People are members of workspaces
On 2.6 a person belonged to a workspace because they held a role in it; removing their last role took them out, and any platform role put them in every workspace. On 3.0 belonging is its own fact, a membership, and roles are given on top of it.
- The upgrade keeps everyone where they were. Every person with a role or a group membership in a workspace becomes a member of it, with an end date when all their access there had one. A deleted workspace's members are set aside with it and come back if it is restored. A person bound to "every workspace" (
workspace:*) is revoked and reported: platform roles are what reach every workspace. - Who reaches a workspace without being a member. A platform role reaches every workspace when its permissions apply in workspaces (System.Admin, Identity.Admin, Security.Admin, Data.StreamViewer). The Fleet roles apply to fleet pages only, so a person who held only a Fleet role no longer enters any workspace; the report names them. Add them where they need to be.
- Adding a person makes them a Workspace Member. Identity administrators add people to workspaces under Identity & Access → Users → Workspaces or the workspace's Members tab, with an optional end date; the person gets the Workspace Member role there, and the workspace's administrator gives any further roles. A role or group membership is refused for someone who is not a member.
- Removing a person takes what they held there. Their roles, group memberships and shares in that workspace go with them (deny rules stay), and what they owned there is handed to the workspace's designated owner or an administrator. The workspace's own administrator removes members; never its last administrator, and never someone holding a platform role. Identity administrators remove anyone except holders of a platform role they don't fully hold.
- Access requests and approvals need a member. Someone outside a workspace cannot file an access request there, and an approval for someone who left grants nothing.
- Single sign-on places only new accounts, in its provider's landing workspace. Each provider now names, explicitly, the workspace its new users land in (Identity & Access → Single Sign-On). A provider from before 3.0 may name none: on an installation with one active workspace its new users join it; with several they are placed in none until an identity administrator adds them, and its role mapping is not applied to them. Give such providers a landing workspace after the upgrade. Signing in never adds an existing account to a workspace.
- A role mapping cannot grant more than its author could. Saving a provider, editing its role mapping or moving where its new users land is refused when a mapped role is one the person saving could not grant there directly. Mappings saved before 3.0 are not re-checked by the upgrade; they keep applying until the provider is next edited or moved, so review them.
- An API client lives in one workspace, shown and chosen. API Clients names the workspace whose clients it lists, and registering a client asks which workspace it lives in. Clients keep the workspace they had.
Resources created on 2.6.0 may have no owner
2.6.0 kept ownership in a way that later releases did not carry over. On 3.0, a pipeline, connection, dashboard or library panel created on 2.6.0 may have no owner. Its creator keeps what their roles grant, but not the owner's extra rights, such as sharing their own pipeline. upgrade check names each such resource and its 2.6.0 owner.
A workspace admin gives it an owner. The Pipelines, Connections and Dashboards lists mark such a resource No owner, and its row menu offers Assign owner. Library panels have no page action: use POST /api/v1/authz/resources/library_panel/<id>/ownership. A platform administrator assigns the owner of the workspace record.
Knowledge model, schemas and alerts are rebuilt
3.0 rebuilds schemas and alerts; the Knowledge Graph is coming soon and is not part of release 3.0. The 2.6 schema registry, schema bindings, asset instances, inline topic schemas, alert limits and live alert state are not carried over. upgrade check reports how many of each will be dropped.
Data schemas became libraries and properties
In 3.0 what a topic carries is its meaning: a property from a library, with its unit, rules, alert limits and sampling, given to the topic from its page (Giving a topic a meaning). Libraries and properties replace the data schemas, and meanings replace the schema bindings. A strict binding is a branch's validation rule set to enforce.
-
The Schemas page is gone. An old link to it (
/uns/schemas, a schema's editor,/compose/models) opens Libraries. -
The old API answers
410 Gone. The 20 data schema and observation binding routes answer410with the codeENDPOINT_REMOVED, anddetails.replacementnames the route to use instead. A caller without access still gets401or403, as before.Removed details.replacement/api/v1/uns/data-schemas…(12 routes)/api/v1/semantics/libraries/api/v1/uns/observation-bindings…(6 routes)/api/v1/semantics/bindingsPOST /api/v1/uns/topics/{id}/binding-preview/api/v1/semantics/bindingsGET /api/v1/uns/topics/{id}/binding/api/v1/uns/topics/{id}/limitsA topic's contract (
GET /api/v1/uns/topics/{id}/contractandGET /api/v1/uns/topics/contract?topic=, which App Studio apps read throughdata.contract) keeps working in the same shape: it is answered from the topic's meaning. -
Data schemas you already have are carried over. The source is a data schema of an earlier 3.0 build (a 2.6 schema registry is not carried, see above).
upgrade applyconverts them when three things hold: the licence includes the semantic layer, the semantics module is on (the default), and the UNS runs as a single process. Until then,upgrade checkreports it as a finding that needs a person, and the start is refused. On the free trial (no licence applied yet) the semantic layer is capped at 100 topics with a meaning: a carry-over that would give more topics a meaning is not carried at all, and waits in the same way for a Foundation or Enterprise licence, which have no such limit. A licence issued before 2026-11-15 (provisional) holds the semantic layer until renewal. A licence minted on or after that date withoutsemantic_layer(Core) has no meanings and no alarm limits. The conversion: each schema version becomes a library version, its attributes properties with their constraints, alert limits and sampling, and each binding an applied meaning on the same topic. A schema version could not change, so its library lands published, published when the schema version was written, by the upgrade; its alert limits, sampling and names stay editable, and a change to a definition is a new version (Publishing a version). A library an earlier 3.0 beta already carried as a draft is published by the upgrade the same way, unless a publish would refuse it (no property in use, a formula reading another draft, a broken reference): it then stays a draft, and the upgrade report names it with the reason. An existing semantics store, from a 3.0 beta or a 3.0 build before library publishing, needsupgrade applybefore the new version starts on it: the start is refused until then. Alarms that are open stay open, with their acknowledgements, shelves and out of service records, so nothing pages again at the first start; alarm history stays as written. A carried alarm keeps the schema's deadband rule, so it fires and clears exactly where it did (Alerts).upgrade checklists what each schema becomes, and names anything it cannot convert. -
Dropped with the data schemas: computed attributes (#7543), suggest a schema from samples and the AI schema draft (#7544), retyping a topic's series to its schema (#7544), binding a thing from the topic page and binding through Maestro (#7530).
upgrade checklists the computed attributes it finds. For a calculated value, use a pipeline that reads the inputs and publishes the result. -
A Build Payload node's fields stay. A node that filled its fields from a data schema keeps them; the node no longer offers that source, and its
schemaRefandschemaMinorare ignored (Build Payload). -
What
upgrade checkasks a person to accept. Each computed attribute, with its schema and the topics bound to it: accepting the schema acknowledges it. Each binding that cannot be carried, with the topic and the reason: accepting it ends the binding and closes its open alarms in history (open pages for those alarms are not resolved). A store that an earlier 3.0 beta already carried over is not asked again, so check its computed attributes yourself.
Node timeout and retry settings have limits
On 3.0, a node's Timeout and Retry settings, and the pipeline's Error Handling defaults for them, must lie within the limits the designer has always offered. 2.6 did not check values sent through the API or an import, and ran them as they were. 3.0 runs a stored value outside the limits too, but refuses to save the pipeline until it is corrected. So that every pipeline can still be saved, the upgrade brings each such value within the limits, and the report lists each change.
| Setting | 3.0 allows | A value above | A value below, but more than 0 | A negative value |
|---|---|---|---|---|
| Timeout | 1s to 1h | Set to 1h | Set to 1s | Removed |
| Max attempts | 1 to 10 | Set to 10 | Removed | |
| Initial delay | 100ms to 30s | Set to 30s | Set to 100ms | Removed |
| Max delay | 1s to 5m | Set to 5m | Set to 1s | Removed |
| Multiplier | 1 to 5 | Set to 5 | Set to 1 | Removed |
| Jitter factor | 0 to 0.5 | Set to 0.5 | Removed |
- A value set to a limit runs differently. A node whose timeout was 2h on 2.6 gives up after 1h on 3.0, and one with 25 retry attempts makes 10. The report names each node, the value 2.6 ran, and the value 3.0 now runs. A multiplier below 1 is set to 1, which waits the same: 2.6 never waited less than the initial delay.
- A negative value is removed. 2.6 ran the default for it, as 3.0 does for a setting that is not set, so the node runs as before. A node's negative timeout ran the pipeline's default timeout, or 30s when the pipeline set none.
- An initial delay longer than the max delay raises the max delay to the initial delay. 2.6 waited the initial delay before every retry in that case, and 3.0 does the same.
- A value of 0 is not set, and stays as it is. So does every value within the limits.
These changes are made by upgrade apply to pipelines stored by 2.6, and to a 2.6 pipeline sent to the API without schemaVersion. A pipeline already stored by a 3.0 release is not converted again: if it holds a value outside the limits, it still runs, and saving it is refused with a message that names the setting and its limit.
Connector behaviours that change on purpose
upgrade check gives each affected function a note:
- RabbitMQ publish waits for the broker's confirmation. On 2.6, a message the broker did not accept was lost without an error. On 3.0 that publish fails and can be retried. To publish without confirmation as on 2.6, set
publisherConfirms: falseon the function. - FTP/SFTP rename no longer replaces an existing file. On 3.0 a rename onto an existing file fails unless the function sets
overwrite: true. - S3 and Databricks Volumes writes take Data as text unless told otherwise. On 2.6 they decoded Data as base64 whenever it happened to be valid base64, so a short value such as
abcd,testor an order number was stored as a few bytes of binary, without an error. On 3.0 Data is written as sent unless the function's Input Format isbase64. Set it tobase64on every write that receives base64: output from a Data Serializer (whose Encoding isbase64by default), from a read of a file or object, or any other pre-encoded payload. Otherwise such a write stores the base64 text itself. GCS and Azure Blob writes have the same setting. - FTP/SFTP Upload names its content format Input Format (
inputFormat). On 2.6 an upload decoded Content from base64 when the function'sencodingwasbase64; that key was never shown in the form. 3.0 usesinputFormat, the name every write that takes text or base64 uses (S3, GCS, Azure Blob, Databricks Volumes, OneLake, SMB, Local File).upgrade applyrenamesencoding: base64toinputFormat: base64and removes any other value, which wrote text on 2.6 as 3.0 does by default;checklists each one as a repair. A request withoutschemaVersionis converted the same way. A request with 3.0'sschemaVersionthat still sendsencodingis refused with the key to use.
Connector settings that 2.6 stored but never applied are removed, and the report names each one. Everything else a connection or function did on 2.6 is kept.
Also in 3.0:
- Some connector node types have new names. Each connector node type is now named after the operation it runs,
connected.<operation>. The designer and the API use the new names. The 26 node types that shipped in 2.6 under another name are renamed in your stored pipelines byupgrade apply.checklists each one as a repair, naming the pipeline, the node, and the old and new type. A pipeline version saved before the upgrade is renamed too when you revert to it. A pipeline sent to the API withoutschemaVersionis read as a 2.6 pipeline and renamed the same way; one sent with 3.0'sschemaVersionmust use the new names. - Store-and-forward now covers writes it skipped on 2.6. Where a node's store-and-forward setting asks for it, it now also holds back S3, Azure Blob, OneLake, GCS, SMB and local-file writes, and write requests made through nodes that run several operations, such as a REST request that posts. On 2.6 these writes were silently not buffered.
- MQTT shared subscriptions work, and a scaled MQTT 5 connection shares its filters. A subscribe function whose filter starts with
$share/<group>/now receives its messages (before, the broker accepted the subscription and nothing arrived). An MQTT 5 connection that runs on several instances subscribes every filter in the groupmh-<connection id>, so each message runs one pipeline instead of one per instance, and, as MQTT defines for shared subscriptions, receives no retained messages on subscribe. Two functions on one connection whose filters overlap in different share groups are refused; the Subscriptions page shows why. A subscription the broker refuses (permissions, an invalid filter) is no longer reported as subscribed: the page shows subscription refused with the reason, retried every 30 seconds. The subscribe function's "Subscription Identifier" field is gone: no release read it, and MaestroHub now assigns identifiers itself.
Connector time settings carry their unit
Every connector time setting (timeouts, intervals, delays, keep-alives, expiries) is a duration with its unit in the value: "30s", "500ms", "5m". On 2.6 some were a bare number, in seconds for one setting and milliseconds for the next.
-
Stored settings are converted by
upgrade apply, each in the unit it was saved in: a Modbus timeout of5000(milliseconds) becomes"5s", an MQTT keep-alive of60(seconds) becomes"1m0s".checklists each one as a repair. -
Keys that named their unit are renamed, and their values converted the same way. The connect timeout of five connectors is renamed too, its value kept: every connection's connect timeout is now
connectTimeout, the one key MaestroHub reads to bound each connect, reconnect and Test Connection, and these five called itconnectionTimeout. A setting left under the old key would not be read, and each connect would be cut at the 30s default.Where 2.6 key 3.0 key SQS send delaySecondsdelaySQS receive waitTimeSecondswaitTimeS3 write object presignedUrlExpirySecondspresignedUrlExpiryMQTT connection connectionTimeoutconnectTimeoutNATS connection connectionTimeoutconnectTimeoutRabbitMQ connection connectionTimeoutconnectTimeoutRedis connection connectionTimeoutconnectTimeoutLoRaWAN connection connectionTimeoutconnectTimeout -
S3 Write Object reports when its presigned URL expires. The result now also carries
presignedUrlExpiresAt, the time the URL stops working (ISO 8601, UTC). The result'spresignedUrlExpiry, the URL's lifetime in seconds, is still delivered but deprecated: readpresignedUrlExpiresAtinstead. -
API clients: a request with 3.0's
schemaVersionmust send durations and the new keys; a bare number is refused withmust be a duration ("30s", "5m", "500ms"…). A request withoutschemaVersionis read as 2.6 and converted, as stored settings are. The forms refuse a value without a unit too, and say how to write it. -
Where a device or service takes whole units, a finer value is refused instead of being cut short. MQTT and Sparkplug keep-alives and session expiry, AMQP heartbeats and message TTL, and SQS times take whole seconds or milliseconds; a MELSEC CPU monitoring timer takes multiples of 250ms.
-
Ignition Subscribe has no Interval setting. The gateway pushes each tag change to the subscription as it happens, so no release ever applied it.
upgrade applyremoves a stored value, andchecklists it as a repair. -
Subscriptions have no Timeout. A subscribe, consume or monitor function runs until its pipeline stops, and no release ever bounded it by its Timeout.
upgrade applyremoves a stored value, andchecklists it as a repair.
The first start runs long database work
Some database changes in 3.0 run at the first start, before MaestroHub answers, and on a large store they take minutes. check and apply list each one under At first start: the store, the change, the table's estimated rows and the store's size, the expected time as a range, and the free disk space it needs next to what the disk has. Too little disk, or an estimate longer than the Helm charts' 60-minute startup window, is also a warning. The two you are most likely to see:
- The audit store gets eight indexes, so that every audit trail filter is fast. The store grows while they are built, and needs free space for that growth.
- The execution history gets its 3.0 columns and indexes, including the one that folds repeated failures into one row (see Other changes). 2.6 created this store without the database's version table. The upgrade still reads the table that is there, and reports the work with its row count. Minutes after the first start, the execution history's retention (
modules.pipelineEngine.executionRepository.sqlite.retention, carried over from your 2.6 config, 24h by default) deletes every run older than it; when that deletes any,checkandapplyadd a note under Findings with the number of runs, the oldest, and the retention that keeps them; set it before you start 3.0 if you want them.
Until this work finishes, MaestroHub answers nothing and its container reports unhealthy. Do not stop or restart it: a stop rolls the change back, and the next start begins it again. The log reports progress every 15 seconds. If an orchestrator probes the container, give its startup probe as long as the estimate.
One setting keeps pipeline data on disk
MaestroHub moves data from devices to pipelines through internal message streams. One setting, nats.embedded.streamStorage, decides where the busiest of them keep their messages: pipeline triggers from the UNS, connector data, and pipeline runs waiting to start.
memoryis the default. It is faster: these streams do no disk writes. What they hold when MaestroHub stops abruptly (killed, out of memory, crashed) is lost. The next start says so in the log and in a notification. A normal stop loses only what is still waiting then, and the log says how many.filekeeps them across a crash. Set it where a lost trigger or a lost queued run matters more than speed.- Events, audit records and store-and-forward queues are always on disk, whatever the setting.
| Stream | What it holds | Lost on a crash with memory (default) | With file |
|---|---|---|---|
Pipeline triggers (UNS_TRIGGERS) | UNS values that a UNS trigger matched, before their runs are queued | Yes. The values themselves are stored. | Kept, except up to 2,048 trigger events not yet handed to the stream |
Connector data (TELEMETRY, TELEMETRY_DLQ) | Device data on its way to pipeline triggers | Yes | Kept |
Pipeline run queue (EXECUTIONS, EXECUTIONS_DLQ) | Runs waiting to start, and runs that failed delivery | Yes | Kept |
| Lifecycle events, audit, store-and-forward | Configuration changes, audit records, pipeline outputs waiting for a target | No | No |
In memory, these streams are smaller, because their size is set aside in RAM at start: pipeline triggers 128 MiB, connector data 128 MiB. When the pipeline-trigger stream is full, new trigger events are refused and counted under Lost executions.
modules.pipelineEngine.executionQueue.storage is deprecated. 3.0 still uses it for the run queue and warns at start. Delete it and set nats.embedded.streamStorage instead.
Cross-origin API calls are refused by default
On 2.6 the API answered every website's browser (Access-Control-Allow-Origin: *). On 3.0 the default is same-origin only: the UI, which is served from the API's own address, works as before, and a page on another site is given nothing. App Studio apps are admitted on their own: on a single node the sandbox origin (modules.appstudio.sandboxDomain) is added to the list when apps are served.
If a page on another origin calls the API (your own HMI, a portal), list its origin:
http:
corsOrigins:
- https://hmi.example.com
Per-service pods read http.corsAllowedOrigins the same way. A value you already set, including "*", keeps working; with "*" MaestroHub logs a warning at startup. Credentials (cookies) are never allowed cross-origin, so "*" cannot be combined with them. Fleet Manager's http.corsOrigins now means what its documentation said: empty allows no origin (an empty list used to allow every origin).
OPC UA servers' certificates are pinned
On 2.6 an OPC UA connection with a Security Policy other than None trusted whatever certificate the server's endpoint list named. On 3.0 it secures its channel only with one certificate, and refuses a different one later.
Security mode None with a username and password keeps working, with a warning. The password travels under a policy the server's endpoint list picks, and that list is not authenticated, so the password is not protected from anyone who can answer on the server's address. The connection's Server certificate card and its log say so; use security mode Sign or SignAndEncrypt to protect it.
Connections that exist when you upgrade keep running. The upgrade marks each secure OPC UA connection to record the certificate its server presents on its first connect within 7 days of the upgrade. The certificate is recorded before the connection exchanges any data; if it cannot be recorded, that connect fails and is retried. A connection that does not connect within the 7 days needs an approval like a new one; its Server certificate card shows the deadline until then. That pin is written to the audit trail as connection.server_certificate_approved, marked pinned_at_upgrade, and the upgrade report lists each connection. From then on the connection behaves like any pinned one: if the server presents a different certificate, the connection is refused until someone approves the new one. Open each connection's Authentication → Server certificate card and compare the pinned SHA-256 thumbprint with the server's own; if they differ, treat it as a possible impersonation.
Connections created after the upgrade need an approval. Until someone compares the thumbprint and clicks Approve this certificate, such a connection exchanges no data, and starting it is refused with SERVER_CERTIFICATE_NOT_APPROVED, which names the thumbprint. See Server certificate. Connections with Security Mode None are not pinned.
The deployment owns the MQTT broker's ports
On Kubernetes the Helm chart now owns the broker's mode, listeners, WebSocket, TLS and the public address devices use (uns.mqtt.service.publicHost, uns.mqtt.service.publicPorts); the single-node chart and the shipped Docker Compose files own its mode and listener ports (modules.uns.defaults.broker.deploymentOwns). Those settings come from the deployment at every start, the MQTT Broker page shows them read-only with the key that sets them, and a save that changes one is refused with 409 BROKER_SETTING_MANAGED_BY_DEPLOYMENT. A broker save no longer restarts the broker of every replica.
On the first start, listener ports come from the deployment (devices already connected through its ports). A mode, WebSocket switch or TLS certificate set on the page keeps running, read-only, until the deployment states its own: the page and the start log name the key, and Use the deployment's value on the page hands it to the deployment. Compose installs keep their own config.yaml: add deploymentOwns: [mode, listeners] to it (a stack without a config.yaml sets MAESTROHUB_MODULES_UNS_DEFAULTS_BROKER_DEPLOYMENTOWNS=mode,listeners), or nothing is owned and the page stays editable as before. See Settings the deployment owns.
The MQTT Broker page is redesigned around this: one page that says whether the broker does its job, what to give a device, who is connected and which saved sessions wait, with charts of the values received and refused. See MQTT Broker.
Signing out ends the session at once
On 2.6 signing out deleted the refresh token, and the access token already issued kept working until it expired (15 minutes by default). On 3.0 signing out ends that session: its access token and its refresh token are refused on the next request, on every service of a single node or an Enterprise installation. Other sessions of the same person are not affected. Signing out through the browser does this for sessions that came from an identity provider too, because the browser signs out of MaestroHub before it visits the provider.
Every session of a person ends, on every service, when an administrator disables or deletes the account or resets its password, when the person resets the password from the emailed link or changes it, and when the identity provider signs the person out (OpenID Connect back-channel logout, SAML Single Logout). Changing your own password keeps you signed in on the tab where you changed it, on a fresh session that keeps its "stay signed in" choice; every other session signs in again. The sessions end before the new password is set: if either step fails, the change is refused and says which step failed. Reactivating or restoring the account does not bring those sessions back: the person signs in again.
No one is signed out by the upgrade. Session tokens issued before 3.0 carry no session generation and count as generation 0: they stay valid until they expire, or until the first time every session of that person ends (a password reset or change, a disable, a delete, an identity provider's logout).
These tokens are not sessions, so signing out, a password reset and a password change do not end them:
- A personal access token, and an agent's token: revoke it under Personal Access Tokens on your profile (or on the agent's page). A disabled or deleted owner's tokens are refused.
- A token an OAuth2 client obtained for a person (authorization code grant): revoke it, or disable the client, under API Clients. It is refused once the person is disabled or deleted.
- An App Studio app's viewer token lives at most 5 minutes; the app's frame renews it through the person's session, so it stops within 5 minutes once that session ends.
Other changes
- Repeated failures are one history row. When a pipeline fails the same way several times within one minute, the execution history shows one row, Failed ×N, with the first failed run kept in full and the last error. Every run is still counted. A success, or a different error, starts a new row.
- Old names are deprecated aliases. The
maestrohub/liteimage and themaestrohub-litecommand still work in 3.0, with a warning. Move tomaestrohub/context-platformandcontext-platformwhen convenient. - App Studio is in release 3.0 as Beta: a paid add-on that no edition includes by default, switched on by a licence or a trial licence that includes the
app_studiofeature. Its module runs by default (modules.appstudio.enabled: true); without the feature it is not in the rail. The free trial a fresh install runs does not include it. See App Studio. - Demo Plant is in release 3.0: an add-on for demos and education that no edition includes by default. The free trial includes it; without it, Demo Plant is not in the rail. Its module runs by default (
modules.demoplant.enabled: true) and starts nothing until a platform administrator enables a plant. See Demo Plant. - Maestro chat: the 2.6 chat module was never released, so there is nothing to migrate. The Maestro agent is in release 3.0 as Beta: it is not turned on by default, and is available on request as early access. See Maestro.
- Live MQTT subscribers receive a value with a meaning as stored. On a topic with a meaning, the live message carries the value converted to its property's unit. When the platform changed the value this way, it writes the value itself: object keys in alphabetical order, and numbers in their shortest form (
5.0arrives as5). A subscriber that compares raw payload bytes or relies on key order should parse the JSON instead. See Giving a topic a meaning. - Everything is created in a workspace. Creating a pipeline, a connection, a dashboard, a library panel or an app through the API with no workspace, or a blank one, answers
400 WORKSPACE_REQUIREDwith a remediation; before, some of these answered500. An OAuth2 client is always registered in a workspace. Stored rows are not checked again: loading, importing and the upgrade keep working on them. See Workspace Context. - Every workspace member reads its libraries and the meanings of the topics they read. Library read comes with membership, however a person joined, and the built-in Member role reads meanings. A deny rule on a library, a topic or a meaning still wins. Agents and API clients need a role for both. See Libraries.
- Webhooks without authentication: a webhook trigger with no authentication headers, and without "allow unauthenticated" set, answers
401on 3.0 until you configure it. The report gives it a note.
Image and command names
maestrohub/lite:<3.0 version>is the same image asmaestrohub/context-platform:<3.0 version>. The old name keeps working, but it is deprecated.maestrohub/lite:lateststays on 2.6.5 on purpose. An install or auto-updater that pulls:latestnever lands on 3.0 by accident. Moving to 3.0 is always a deliberate tag change plusupgrade apply.- The
maestrohub-litecommand inside the image also still works, with a warning that the name is deprecated.
To move off the old names, use image: maestrohub/context-platform:<version>, and context-platform instead of maestrohub-lite in command:. Both old names will be removed in a later release.