2026.09.15 — 2026-09-08
Added
Rebuild the console around the result, and keep queries out of URLs (5a083c1f)
Execution is now POST-then-stream. The query text goes in a request body and the stream is opened by a short-lived, single-use reference. An
EventSourcecan only issue a GET, so until now an operator's predicates — and the values they were searching for — were written into every proxy access log on the path, and a long query hit a URL length limit. A query that can be judged before it runs is now refused by the POST, where a status code carries a body the caller can read; anything the stream itself refuses still arrives as afailedframe.The screen is laid out for an incident. A resizable editor pane sits above a results pane that owns the rest of the viewport, so the rows stay on screen. The stack of up to five full-width alerts is gone: everything they said is in one meta bar with a disclosure, in full, and none of it pushes the data below the fold. A running static query can be stopped, not just a tail.
A tail can now be paused without being stopped — the broker is still being read, the view is just held, with a count of what is waiting — and scrolling away from the head of a live feed pauses it for you rather than moving the rows under you.
A captured tail states the stronger claim, and only when every target is captured on the node it is read from. One node without a tap and it is a sampled tail again, with the gap named. Per row, provenance is three words rather than two: live, sampled, captured.
Full-text search over stored message bodies (78f91aaa)
MATCH (body) AGAINST ('order 4471')searches the stored bodies in the message index from a GIN index rather than by scanning, andORDER BY match_ranksorts by how well each row matched. Quoted phrases,-exclusionandorwork the way they do in a search box, and a malformed query is a no-match rather than an error.Index only. There is no selector a broker could evaluate for this and no honest approximation of one, so a MATCH against
broker."..."is refused by name rather than silently meaning something else.match_rankwithout a MATCH to rank against is refused for the same reason.The
simpletext configuration, notenglish: message bodies are identifiers, JSON and codes, and English stemming mapsorderstoorderwhile leaving4471alone — which corrupts exactly the lookups this exists for. Binary bodies are not indexed at all, because a tsvector over base64 is garbage tokens and index bloat for no retrieval value; useLIKEfor those, which is unchanged.Three new index-only columns come with it:
origin,origAddressandsourceMessageId.The spelling is
MATCH (body) AGAINST ('terms')rather than the two-argumentMATCH(body, 'terms')this was designed with.MATCHis a reserved word in the SQL grammar the parser implements and the comma form cannot be parsed at all — measured, not assumed — so the dialect takes the grammar's own form instead of forking a parser.See ADR-0063.
Capture every message an address routes, not just what a poll saw (8bde177e)
The message index and the live tail both worked by browsing a queue every few seconds and recording what was sitting on it. A queue that drains faster than that interval is invisible to both — which is most healthy queues, most of the time, and the reason the index only ever seemed to fill up with expired messages.
Capture closes that gap. Set a subscription's mode to CAPTURE and Studio installs a non-exclusive divert on every live node, copying everything the address routes into a ring-bounded queue it owns and drains over the Core protocol. A message produced and consumed in the same millisecond is still there to query afterwards, by its original queue name.
Turning it on needs the new
capture:writepermission. It is deliberately notsettings:write: capture mutates broker routing on a schedule and creates a second copy of production payload, which is a different authority from changing how often Studio polls. Every install and removal is audited before the broker call and updated with the outcome.Bounded by construction, because Studio must never be why a broker falls over:
ring-sizemakes the broker drop the oldest rather than grow,address-full-policy=DROPkeeps it from blocking or paging, anexpiry-delaywithauto-create-expiry-resources=falsebounds an abandoned tap in age as well as in size, and a per-message body cap keeps a large message out of Postgres. Asecurity-settingrestricts the capture queue to Studio's own broker role, and capture is refused outright if that cannot be set — an unguarded second copy of production traffic is not something to create by accident. It is also refused when an exclusive divert on the source address would shadow it, because that installs cleanly and captures nothing.None of these objects disappears on a broker restart. Deleting the subscription is what removes them; nothing else will. The screen says so before you start.
See and manage diverts across every node of a cluster (6ea8d1eb)
The Routing view lists every divert and every bridge a cluster has, merged across nodes and attributed to the ones each was found on. A divert whose source, destination or filter differs on one node appears as two rows rather than one, because that divergence is the thing you opened the view to find.
Diverts can be created and deleted, behind
divert:write, with the same dry run, per-node outcome and audit record every other lifecycle operation has. Bridges stay read-only.Two things this deliberately does not claim. It never says where a divert came from: Artemis records nothing on a divert about whether it was declared in broker.xml or created over management, so any such badge would be a guess. What it does say is ownership, drawn from Studio's own records — a capture divert by its reserved name, an operator's divert from the audit trail.
And a divert created here is not temporary. Diverts, address settings and security settings created over the management API survive a broker restart — measured on Artemis 2.56.0, and the opposite of what this project previously assumed. The hazard is therefore configuration drift, not evaporation: the running broker will disagree with the broker.xml your estate deploys from until you add it there, and nothing removes it for you. The create dialog says exactly that and hands you the snippet to paste.
See ADR-0065.