Repository navigation
Releases: sudoitir/rahdar
Release list
2026.10.53
Fixed
-
Apply a sooner schedule as soon as its setting changes (3f203acd)
Changing a job's schedule took effect only after the run already planned under
the old one: moving the audit purge from its daily 03:30 UTC run to hourly still
waited for 03:30. Every replica now re-arms its waiting jobs when a setting
changes, so a schedule that comes due sooner applies at once. A schedule moved
later takes over after the next run, and a job that has not run yet keeps its
planned time when an unrelated setting changes.
2026.10.52
Fixed
-
Describe each flow node with a supported ARIA attribute (7c9b030b)
A flow node carried its sentence (role, health, rates) in aria-description, an
ARIA 1.3 attribute that not every screen reader announces. The sentence now sits
in a hidden element the node references with aria-describedby, so it is read as
the node's description everywhere, while the node's name stays its visible lines.
2026.10.51
Fixed
-
Make every control's accessible name include its visible text (27e7054f)
WCAG 2.5.3 (Label in Name): an operator who drives the console by speech says
what they see on a control, so the control's accessible name must contain its
visible text. axe's label-content-name-mismatch rule now checks this and
failed the browser accessibility tests.Changed controls (visible text -> accessible name):
- Sort buttons of columns with a short label ("Cons.", "Deliv.", "Sched."):
"Consumers" -> "Cons. (Consumers)", in the shared header cell. - "Close consumers" on an address row: "Close every consumer on "
-> "Close consumers on ". - "Apply this" on a declared item: "Apply " -> "Apply this ".
- The apply-mode button: the name is now its visible text followed by
". Change apply mode". - "Reset to default" in a setting row: "Reset to its default, "
-> "Reset to default: , ". - Topology boxes: the box is named by its own lines (name, version, liveness,
role, address); the sentence stays as its description. - Flow nodes: same, named by their lines with the sentence as description.
- The collapsed cluster switcher: the name starts with the monogram the rail
shows ("PR, Switch cluster, now ...").
Tests that query these controls by name are updated to the new names.
- Sort buttons of columns with a short label ("Cons.", "Deliv.", "Sched."):
2026.10.50
Release 2026.10.50
2026.10.49
Breaking
-
Rename the product to Rahdar (4b484955)
The product is now Rahdar (Persian راهدار, "road-keeper": rah, route, + dar,
keeper). The name no longer names one broker, and "Artemis" now means only the
broker Rahdar manages. Every published name changes with it, and nothing keeps
the old ones working (ADR-0201).
Added
-
Give Rahdar its own mark (770b7f15)
The crescent bow named Artemis; Rahdar's mark is Kept Road: one road cut
through a tile as a lowercase r that runs on past two edges, the way kept open.
It is pixel-exact at 16 and 32 px, so one favicon.svg serves the browser tab,
the app header, the README and the docs. The app and the site also get a
favicon.ico fallback and an Apple touch icon, and the site's social card is a
new image with the mark and the wordmark.brand/ holds the lockups, the constructed wordmark, the icon set and the usage
guide (clear space, minimum sizes, colours). -
Rename the product to Rahdar (4b484955)
The product is now Rahdar (Persian راهدار, "road-keeper": rah, route, + dar,
keeper). The name no longer names one broker, and "Artemis" now means only the
broker Rahdar manages. Every published name changes with it, and nothing keeps
the old ones working (ADR-0201).
2026.10.48
Breaking
-
Performance hardening — UUIDv7 keys, partitioned history, bounded jobs, connections and streams, external PostgreSQL, load test (#236) (73e3b437)
-
docs(openspec): design and tasks for 19-performance-hardening
-
docs(openspec): external PostgreSQL support in 19-performance-hardening
-
perf(ui): scan a table's rows for column widths once per page of data
Sizing a table's columns read every row of the page for its longest values on each
change of columns. The longest values are now kept per page of data and per column,
so a change of density or of shown columns no longer costs more as the list grows.- feat(ui): find in large tables
The browser's find cannot see the rows a large table has not drawn. Ctrl+F (Cmd+F on a
Mac) in a table now opens its own find, which searches every row, says how many
matches there are, and moves between them with Enter and Shift+Enter, scrolling to and
marking the row. Escape closes it and returns focus to the matched row.- perf(ui): list a user's sessions in the table
An account's sessions are not limited and were drawn as a plain list, so a very long
list drew every row. They are now a table that draws only the rows in view.- perf(db): bound database connections in count and time
The connection pool now has 16 connections per replica (ARTEMIS_STUDIO_DB_POOL_SIZE) and a request
that waits more than 5 s for one fails instead of queueing. Every connection starts with a 30 s
statement timeout, a 5 s lock timeout and a 60 s idle-in-transaction timeout, and the driver sets a
5 s connect timeout, a 15 min socket timeout and TCP keep-alive, so a stuck query or a partitioned
network no longer holds a thread without end. The limits are set per connection, not on the server,
so they hold on any PostgreSQL and through a session-mode pooler. Plugin pools take the same limits.
The plugin connection budget counts Studio's 16 connections instead of 10.The unused spring.task.scheduling setting is removed.
- perf(db): raise the statement timeout for long work inside its own transaction
The audit export, the lifecycle preview and the partition maintenance of the metric and message
index tables run with a statement timeout of up to ten minutes (SET LOCAL), in a transaction of their
own, so the 30 s limit on every other connection stays. The message index purge now detaches and
drops a partition in one transaction.- perf(db)!: ship one PostgreSQL configuration for the bundled database
The production, HA and development compose files mount deploy/postgres/postgresql.conf read-only
instead of each carrying its own list of server flags. It adds statement statistics
(pg_stat_statements with compute_query_id, created on first start), lock-wait logging, an explicit
io_method=worker, autovacuum logging, max_connections=150 and a five-minute idle-in-transaction
backstop to the earlier tuning. deploy/postgres/postgresql.tuning.conf is removed. -
Changed
-
Performance hardening — UUIDv7 keys, partitioned history, bounded jobs, connections and streams, external PostgreSQL, load test (#236) (73e3b437)
-
docs(openspec): design and tasks for 19-performance-hardening
-
docs(openspec): external PostgreSQL support in 19-performance-hardening
-
perf(ui): scan a table's rows for column widths once per page of data
Sizing a table's columns read every row of the page for its longest values on each
change of columns. The longest values are now kept per page of data and per column,
so a change of density or of shown columns no longer costs more as the list grows.- feat(ui): find in large tables
The browser's find cannot see the rows a large table has not drawn. Ctrl+F (Cmd+F on a
Mac) in a table now opens its own find, which searches every row, says how many
matches there are, and moves between them with Enter and Shift+Enter, scrolling to and
marking the row. Escape closes it and returns focus to the matched row.- perf(ui): list a user's sessions in the table
An account's sessions are not limited and were drawn as a plain list, so a very long
list drew every row. They are now a table that draws only the rows in view.- perf(db): bound database connections in count and time
The connection pool now has 16 connections per replica (ARTEMIS_STUDIO_DB_POOL_SIZE) and a request
that waits more than 5 s for one fails instead of queueing. Every connection starts with a 30 s
statement timeout, a 5 s lock timeout and a 60 s idle-in-transaction timeout, and the driver sets a
5 s connect timeout, a 15 min socket timeout and TCP keep-alive, so a stuck query or a partitioned
network no longer holds a thread without end. The limits are set per connection, not on the server,
so they hold on any PostgreSQL and through a session-mode pooler. Plugin pools take the same limits.
The plugin connection budget counts Studio's 16 connections instead of 10.The unused spring.task.scheduling setting is removed.
- perf(db): raise the statement timeout for long work inside its own transaction
The audit export, the lifecycle preview and the partition maintenance of the metric and message
index tables run with a statement timeout of up to ten minutes (SET LOCAL), in a transaction of their
own, so the 30 s limit on every other connection stays. The message index purge now detaches and
drops a partition in one transaction.- perf(db)!: ship one PostgreSQL configuration for the bundled database
The production, HA and development compose files mount deploy/postgres/postgresql.conf read-only
instead of each carrying its own list of server flags. It adds statement statistics
(pg_stat_statements with compute_query_id, created on first start), lock-wait logging, an explicit
io_method=worker, autovacuum logging, max_connections=150 and a five-minute idle-in-transaction
backstop to the earlier tuning. deploy/postgres/postgresql.tuning.conf is removed. -
2026.10.47
Fixed
-
Register a plugin's settings before its jobs start (0b173563)
A plugin whose scheduled job reads one of its own settings when it is
scheduled (for example in its interval supplier) no longer fails to
activate with "Unknown setting key". Plugin bridges now attach in
Spring's @order, with the settings bridge first, and detach in the
reverse order.
2026.10.46
Added
-
Say how many events an export will hold (38a51082)
The Export menu names the number of matching events before a format is chosen.
-
Alert when an audit receiver is behind or lost records (623de17a)
A new installation alert condition, AUDIT_DELIVERY, is active for each audit
receiver that has a gap it has not been told about, or whose oldest
unacknowledged record is older than audit.delivery.alert-after. The subject is
the receiver's plugin id and the value is the age of that record. A default
Audit delivery rule is created, enabled and bound to no channel, like the
storage rules, and the condition is offered when editing an installation rule. -
Retention waits for receivers and leaves held records alone (1bb820e2)
The audit purge no longer deletes a record a receiver has not acknowledged until
it is older than audit.delivery.max-unacknowledged-age. When it does delete such
records, the receiver is told with an AuditGap, an AUDIT_GAP audit event records
it, and AuditFeed.retainedFrom reports the time the trail is complete from.A plugin that declares audit.retentionHold provides one AuditRetentionHold
naming time ranges, optionally narrowed by cluster, user, action and target. The
purge leaves every record a hold covers, whatever the retention. It asks before
every batch; a hold that throws, takes longer than audit.delivery.timeout, names
more than 200 entries or belongs to a plugin that is not running on the purging
replica makes the run purge nothing, and the purge status says why. A plugin that
provides an AuditRetentionHold without declaring audit.retentionHold is not
activated. -
Deliver the audit trail to receiver plugins in commit order (4434d00d)
A plugin that declares audit.receive in its manifest provides one AuditReceiver.
Studio calls it with batches of up to 500 records after its cursor, in commit
order, from a job that runs once per installation every two seconds. Returning
from the call acknowledges the batch; throwing or taking longer than
audit.delivery.timeout delivers the same batch again later, after a delay that
doubles from 2 seconds to 5 minutes. Delivery is at least once, so a receiver
identifies a record by its id and position. A receiver never holds up the
audited operation.A new receiver starts at the beginning of the retained trail. Its cursor is
removed when the plugin is uninstalled, not when it is disabled. A plugin that
provides an AuditReceiver without declaring audit.receive is not activated.
When retention removes records a receiver has not acknowledged, the receiver is
told with an AuditGap and an AUDIT_GAP audit event records it.New settings: audit.delivery.max-unacknowledged-age (30d),
audit.delivery.timeout (30s) and audit.delivery.alert-after (1h). -
Add an Export menu to the audit log (8c5bfdb9)
CSV, JSON and NDJSON are plain download links carrying the current filters,
so the browser streams the file to disk and the download can be cancelled.
The button is disabled with its reason without audit:export. -
Export the audit trail as CSV, JSON or NDJSON (a58b48ed)
GET /clusters/{clusterId}/audit/export streams the trail under the same
filters as the list, oldest first, as a file. It needs the new cluster
permission audit:export and holds only what the caller could read in the
list, redacted the same way. CSV cells a spreadsheet would run as a formula
get a leading quote. Each export is itself audited as EXPORT_AUDIT with its
format and filters, and records how far it got if the client cancels. -
Plugins declare audit access in their manifest (de54fa7d)
A plugin.json may carry an audit block with receive and retentionHold flags,
both false by default. The install review lists each flag, the diff shows
flags an update adds or removes, and a plugin that newly declares one is
activated only after the audit-access-added acknowledgement. -
Give every audit record a commit-order position and a feed API (7e46bcb3)
Each audit record now carries a position, the writing transaction's id and a
sequence value stamped by the database. AuditFeed.after returns records in that
order and only once every earlier transaction has ended, so a reader following
the trail never skips a record that commits late. A record whose outcome is
filled in takes a later position, so a reader that already passed it sees the
final state. AuditFeed, AuditRecord and AuditPosition are part of the plugin API.A database guard now keeps the trail honest: only a record's outcome, error,
count and outcome detail can change, only while it is pending (a refusal's count
may keep rising), and nothing else is ever rewritten.
Fixed
-
Delete settled gap rows and fix the tests the audit delivery broke (30474c29)
A gap row is deleted once it is audited and delivered, so the table holds only
open gaps. Tests: the module slices that pull in the data lifecycle mock the
retention guard, the schema baseline and store coverage lists name the new
tables, the audit-access plugin fixture provides the beans it declares, and the
alert rules test no longer depends on the seeded rule order. -
Record a completed second-factor sign-in as a new event (b9f30d2e)
A sign-in finished by its second factor is now a new successful login event
whose parent is the attempt, instead of changing the attempt's outcome. The
attempt's own record stays as it was written. -
Keep gap counts, in-flight calls and export outcomes honest (24e0468a)
A purge writes a new gap row each time instead of adding to one a delivery may
have read, so an acknowledgement cannot mark an unseen count delivered. The
AUDIT_GAP claim reads the row in the same statement, and the claim is given
back only when the event could not be started. A receiver or hold that ignores
its timeout is not called again until it has returned. An export records its
outcome after the sink is flushed and closed. -
Record an audit outcome once and ignore a second one (71dfb2d0)
A row that already has its outcome is no longer overwritten by a later success or
failure for the same operation. The first outcome stands and the second is logged
as a warning, now that the database refuses rewriting a finished audit record.
2026.10.45
Fixed
-
Let a plugin inject the ApproverPool (5dd05aaa)
ApproverPool is plugin API, and an approval provider asks it whether there are enough approvers before it starts
holding, but its implementation was never handed to plugin contexts: Studio exposes only beans whose own class is
marked @PluginApi. A provider plugin that injected it failed to activate with "No qualifying bean of type
ApproverPool". It is now exposed like the other plugin-facing services.
2026.10.44
Release 2026.10.44