Skip to content

2026.09.20 — 2026-09-13

Added

  • Read the plan as a diff and watch the apply land (c6e508d9)

    A plan step is now a difference rather than two columns to compare by eye: one row per key, before → after, the keys that move first and the keys the write also carries — because a management write replaces the whole entry — kept underneath, dimmed. On an address setting of eighteen keys of which one moves, the one that moves is now findable.

    A summary bar stays on screen while you scroll a long plan: how many writes, on how many nodes, which one goes first, how many hazards, and how many High ones are still unacknowledged. Per-node sections collapse on a larger cluster, with the canary and any node that failed always open.

    Continuing to the confirmation re-plans and compares the hash first. A cluster that moved is now "the cluster moved — this is a new plan", with the new plan on screen and the acknowledgements cleared, instead of a 409 arriving after the operator had already typed the cluster's name. Going back to the plan and returning keeps acknowledgements that are still about the same hazards.

    And the apply is no longer a spinner. Each node reports where it has got to while the run is still open — applying, step n of m, reading back to verify, done or halted — so the canary being written and verified before anything else is touched is something you watch rather than infer. The frames ride the existing config SSE topic and are advisory; the run's own response is still what reports the outcome.

    Deliberately not built: a button that fills in the confirmation name. A click that types the name turns a typed confirmation back into a second click, which is the thing typed confirmation exists to not be.

  • Suggest adoption and make drift state legible at a glance (1f4e3e6e)

    A cluster with live nodes and no declaration now opens on an offer: "Adopt what this cluster runs as revision 1", with the counts it would declare per section and any disagreement between nodes named before you open anything. It is the same batched read the drift pass makes, it saves nothing, and it says — on the screen, keyboard-reachable — why Studio will not adopt on its own: an adoption declares that whatever the brokers happen to be running is intended, including the setting someone changed by hand an hour ago. Only an operator can say that (ADR-0067 D8).

    The Drift tab now reads its evaluation as an age against the cadence it is measured on — "Last evaluated 4m ago · evaluated about every 5m" — with the absolute instant one hover or one focus away, and each node carrying its own state chip and its own age. An age alone cannot tell a fresh pass from a stopped scheduler, so the two are never shown apart.

    The tab also keeps itself current: the declaration carries the cluster's config.drift-interval so the view polls no slower than the pass it is watching, and the cluster stream now subscribes to the config topic the server has been publishing since the loop landed — an apply or an evaluation finishing refreshes the screen without a reload.

  • Apply the capability probe's recommendations from the declaration (a8dccafa)

    A capability gap used to be a dead end. The ledger told an operator the exact broker.xml that would close it, and then made them go and write it, restart the broker and come back — including for the three fragments that are address settings or security settings, which the management API takes at runtime and which Studio already declares, plans, applies and verifies.

    Those three now become a declaration in one action:

    • The connection check and a registered cluster both return the same recommendations, seeded from what the node is running. Registering a cluster with gaps lands on them.
    • Declaring saves an ordinary revision, source RECOMMENDED, and opens its plan. Nothing reaches a broker until the ordinary apply — canary first, hazards acknowledged, cluster name typed. ADR-0067 D8 stands: the operator acts.
    • The gaps Studio can never close — the notification plugin, a CORE acceptor, the management security setting — are named with their fragment and the reason, rather than left out of the list.
    • Recommended notification roles are prefilled from whoever holds consume on activemq.notifications today, editable, and a block naming no role is refused: it applies cleanly and grants nobody anything.

    Each recommended entry carries the keys the node already has, not only the ones it changes. addAddressSettings replaces the whole entry rather than merging into it (notes §15 M2), so an entry carrying only its own keys would have silently reset everything else on the match the moment it was applied.

    A capability is now read back rather than asserted, so a recommendation that has been applied stops being made:

    • slow-consumer detection was reported as permanently unknowable on the reading that the broker never echoes the threshold. It does, once one is set — measured on 2.44.0 against the dev pair, recorded as notes §16 M8. An absent threshold now reads as "none is configured", which is something an operator can act on.
    • message I/O reported "the broker truncates bodies" from a fixed sentence. It now reads management-message-attribute-size-limit from the same call.

    Fixed on the way: management-message-attribute-size-limit rejected -1, the one value its own snippet tells operators to paste, which made the recommendation undeclarable. Found by the live end-to-end run, not by a unit test.

    Also in the apply view: a plan filters by section and key, with the step numbers and the total staying the plan's own, and a node whose steps are all filtered out says so rather than disappearing. The address-setting editor offers templates built from the cluster's own dead-letter and expiry addresses — never invented names, which would declare a policy that routes nowhere.

    ADR-0068 records the decision, including why registering and applying stay two operator actions and why auto-applying is refused.

  • Declare, apply and verify broker configuration across a cluster (3933164f)

    A cluster now carries a declaration — the address settings, security settings, diverts, addresses and queues it should run — and Studio applies it over the management API or exports it as a broker.xml fragment, and measures every live node against it.

    What you get:

    • A Configuration view per cluster: Declared / Drift / History tabs, editors for each section on the house form pattern, Adopt from cluster, Import XML (paste-and-preview; static <core> settings are listed as not applied, never dropped), Export XML.
    • Preview & apply: a diff-driven plan per live node (at most two batched reads per node, never one per item), hazards classified before any write with High ones acknowledged by id, the cluster name typed to confirm, then canary → read back → next node, halting at the first failure. Nothing is rolled back; re-running converges. Every apply, dry runs included, is one audit event with the node × step outcome.
    • An apply never destroys a queue or an address and removes only what Studio itself applied unless you opt in per plan. Studio never writes broker.xml and never calls reloadConfigurationFile.
    • Drift: every live node evaluated on a schedule and after every apply; findings grouped by kind with declared beside observed; a CONFIG_DRIFT alert condition and a rule template. Evaluation is scheduled, action never.
    • MCP: broker_config (read) and broker_config_change (declare / apply, dry run by default, confirm = cluster name, acknowledge = hazard ids, expectedPlanHash).
    • The capability ledger (setup, settings, the gated views) links a snippet that is an address or security setting to the cluster's configuration: "Declare it in Configuration" opens the import preview on it. Import merges into the current declaration by default (per-match key merge for address settings) and accepts bare <address-setting> / <security-setting> elements as the snippets write them; Replace is one radio away.
    • Every address-setting key carries an explanation with an example, reachable by keyboard; the long tail has a finder. Declared keys, the revision comparison and drift findings render as key beside value with units read out (100 MiB, 2.5 s) instead of JSON.
    • Measured on 2.44.0: the view and edit role lists of addSecuritySettings are accepted but never reported back. They are sent, excluded from the plan / verification / drift comparison, and stated in the editor (broker-management-notes §15 M7).

    What changes for an upgrade:

    • Liquibase changeset 024 adds broker_config_declaration, _revision, _apply, _node_state and _owned_item. Applied on startup as usual.
    • Two new permissions, config:write and config:apply. ADMIN's * grants both; built-in OPERATOR and VIEWER are unchanged — grant them to a custom role as for queue:create and divert:write. config:apply never implies deleting a queue or address.
    • Two new settings: config.drift-interval (default PT5M) and config.apply-step-cap (default 100, overridable per apply and audited).
    • The SSE stream gains a config topic.
    • The MCP per-tool schema budget test allows 190 tokens (was 175): broker_config_change needs acknowledge and expectedPlanHash as separate parameters.
    • OpenSpec change 05-desired-state-drift is absorbed into this change and removed unapplied; its requirements live in the broker-configuration capability spec. Recorded in ADR-0067.
    • The docs site gains a Broker configuration guide (en/zh/fa) and the MCP guide's tool table now lists the tools that actually exist.
    • web: React 19.2.8, TanStack Router 1.170.33, router-plugin 1.168.36, @types/react-dom 19.2.7, eslint-plugin-react-refresh 0.5.6 (the dependabot minor/patch group). RouteError accepts unknown as the newer router requires. The eslint 10 bump is not included: eslint-plugin-react-hooks 5.2.0 does not support it.

Fixed

  • Refuse an apply the cluster cannot take, and bound the loop under load (ac6ea0af)

    Five ways the configuration loop could hurt a broker or lie about a document, found by driving it against the dev pair rather than by reading it.

    CONFIG_MANAGED is now enforced by the engine. ADR-0067 D2 promises Studio does not write to a cluster someone else's broker.xml owns, and the UI disabled the control with the reason — but the UI is not the enforcement point, and a plain POST or an MCP call applied anyway, with 200. A dry run is still allowed: a plan is a comparison, and a config-managed cluster is exactly the one whose drift an operator needs to read.

    Every write takes its own rate-limit permit. The per-node limiter was charged once, when the client was opened, after which an apply issued one POST per step on that client — so a fifty-step plan spent a single permit and sent fifty requests as fast as the broker would take them. That is the opposite of non-negotiable #1.

    An apply preflights the cluster it is about to write to, and refuses when a target is no longer live (the topology moved since the preview) or when the cluster is in split-brain, where "the live node" is not a single answer and a setting can land on the member that loses.

    Drift evaluation is single-flight and stays out of an apply's way. It used to take the capture reconciler's lock namespace, so the two waited for each other for no reason while two drift passes were free to stack; the on-demand endpoint took no lock at all and raced the scheduled pass, last write winning. It now has its own namespace, and a pass that finds an apply in flight is skipped rather than recording a half-applied node as DRIFTED.

    Import refuses what it cannot honour. An unknown address-setting key was carried as merely "unsupported" and dropped, which is how max-size-byte produced a declaration that looked applied, could never drift and did nothing — ADR-0067 D10 makes it a validation error. A 3.8 MB import was accepted; imports are now capped at 256 KiB, reported the same way malformed XML already is.

    Measured under load (notes §16, M10): with a producer and consumer running, a window of continuous applies and evaluations costs 7.1% of consumer throughput against an A/B/A baseline that reproduced itself exactly. That is a worst case the product never asks for — the default evaluates once per drift interval — and broker CPU fell rather than rose, which is contention, not Studio eating the CPU the broker needed.

  • Adoption discloses the drift it closes, and IN_SYNC states its evidence (f25e84f4)

    Drift was visible, the operator adopted, the drift disappeared — and no broker had been written. Adoption declares what the cluster already runs, so every open finding closes by definition; the screen said "in sync" and meant something entirely different from the "in sync" that follows a verified apply. Nothing distinguished them, and the revision did not even record that it came from an adoption: every save was written as an EDIT.

    A node now records WHY it agrees, and never agrees without saying so:

    VERIFIED_APPLY Studio wrote the values and its read-back matched ADOPTED the declaration was copied from this cluster; nothing was written OBSERVED_MATCH an evaluation found them equal — what a CONFIG_MANAGED cluster's agreement always is, and the honest answer for a broker.xml someone else deploys

    The adoption preview lists the findings it will erase and says no broker will be written. An adoption that would close open findings is refused without the cluster's name typed in, and refused outright while an apply holds the cluster, so a run in flight cannot have the declaration moved underneath it. An ordinary edit is not held to either rule.

    A verification MISMATCH now writes DRIFTED from inside the apply itself. It used to depend on the drift evaluation that follows an apply, whose exception is swallowed — so a mismatch that halted a run could leave the node reading IN_SYNC, and the CONFIG_DRIFT alert condition, which reads that table, stayed silent.

Apache-2.0. Apache ActiveMQ and Apache ActiveMQ Artemis are trademarks of the Apache Software Foundation. Artemis Studio is an independent project, not produced by, endorsed by, or affiliated with the ASF.