Skip to content

All versions since v3.0.0-rc.10 (2026-06-25)

v3.0.0-rc.10 (2026-06-25)

Added

  • eDirectory Identity Manager role and group setups have a ready-to-import profile — Deployments that track role and group membership on user entries can now include edirectory-idm.conf after their base config. The profile adds the IDM role, resource, and group attributes IdentityHub commonly queries, keeps high-fanout reverse membership views virtual, and documents the expected user, role, and group mappings in eDirectory Identity Manager Roles.
  • Query defaults can be set once and overridden per channelquery.default-count, query.default-limit, query.default-include, query.default-fields, and query.default-sort now provide shared defaults for integrations that do not set their own values. Named channel overrides such as channels.ldap.default-count, channels.rest.default-fields, channels.graphql.default-sort, and channels.mcp.default-limit still let each integration choose its own behavior.
  • Recent forwarded LDAP searches are visible in Observe, with a value-free request shape on every trace — When IdentityScribe forwards a directory search to the connected directory, its trace and wide log now describe the request without exposing directory data: the search scope, a structural filter pattern with the values stripped (for example objectclass?,uid=), stable digests of the base DN and filter, the requested controls, and a correlation id — under ldap.request.* and ldap.delegation.sample_id. This makes it possible to identify which forwarded search returned a “service unavailable” (or any other result) and trace it back to the responsible client call. The Operator UI’s LDAP Forwarding view adds an expandable list of recent forwarded searches for each forwarding reason, and the same samples are available at /observe/ldap/delegation with a downloadable export. The literal base DN and filter are held in a bounded in-memory window and shown only to operators with Observe write access; capture is on by default and can be turned off with monitoring.observe.capture-request-values = false. The window keeps the most recent monitoring.observe.max-samples searches (default 200) for monitoring.observe.ttl (default 15 minutes) and is never written to the database. Traces, metrics, and the redacted export never carry the literal base DN or filter.
  • MCP endpoints record tool usage, expose per-tool metrics, and limit concurrent tool calls — The data (/mcp) and Observe (/observe/mcp) MCP endpoints now record one audit log line per tool invocation (endpoint, tool name, result, duration, and request id — never the tool arguments), and export per-tool request-count and latency metrics under scribe.mcp.tool.*. Both endpoints bound how many tool calls may run at once: the cap auto-derives from the configured concurrency setting (the same knob that sizes the database pool and HTTP limits) when unset, and can be set explicitly via channels.mcp.max-concurrent-tools / monitoring.mcp.max-concurrent-tools (0 disables the limit). When the limit is reached, an incoming call waits for a free slot before being rejected with a clear error; the wait defaults to 1 second and is tunable via channels.mcp.tool-acquire-timeout / monitoring.mcp.tool-acquire-timeout.
  • Operators are alerted when the directory sends incomplete change notifications — The health checks (/observe/doctor) and the Observe Inbox now raise a signal when the connected directory sends change notifications that omit objectClass. A critical alert appears when such an update could not be completed and was held back — the stored entry keeps its previous values until the directory is fixed; a warning appears when IdentityScribe re-read the full entry from the directory to recover. Each card shows how many occurred in the last 5 minutes and points to the directory-side fix (check the directory’s objectClass attribute mapping and re-sync the affected entries); the exact attributes the directory dropped are recorded in the ingest logs. The same signals are available for dashboards under scribe.ingest.incomplete_notification.total (reason=repaired / reason=skipped).

Fixed

  • Stored entries keep their attributes when the directory sends an incomplete change notification — When IdentityScribe syncs a change from the connected directory, a change notification that arrives missing objectClass (a mandatory attribute every live entry has) is no longer treated as if the attribute had been removed. Such an update could previously drop objectClass — and potentially other attributes — from the stored entry, leaving an entry that clients relying on objectClass may reject or that attribute searches may miss. IdentityScribe now recognizes the notification as incomplete and re-reads the full entry from the directory; if a complete copy still cannot be obtained, it leaves the stored entry unchanged rather than corrupting it. Incomplete notifications are recorded under the new scribe.ingest.incomplete_notification.total metric (reason=repaired when the full entry was re-read, reason=skipped when the update was held back), so operators can spot a directory that is sending incomplete change notifications.
  • Pattern-based LDAP bind attributes accept quoted OID keys — Pattern entries under auth.ldap.bind-attribute keyed by a quoted object identifier (for example "1.2.840.113556.1.4.221") are now read with the correct attribute name. The surrounding quotes were previously kept as part of the name, so any bind that relied on that attribute was rejected. Unquoted dotted keys and ordinary attribute names are unchanged.
  • LDAP paged-search counts keep useful estimates — Simple paged LDAP responses now keep a server-side estimate when it is available, and the reported size is never lower than the returned page proves. The final page still reports the completed result size when IdentityScribe can determine it.
  • Bundled monitoring dashboards use the current planner metrics — The packaged Grafana and Helm dashboards now read the planner metrics introduced in the latest release, so planner-health panels continue to show data after upgrading.
  • Forwarded LDAP searches recover from a dropped backend connection — When IdentityScribe forwards a directory search to the connected directory and the backend connection has gone stale — for example after the directory restarted, or an idle connection was dropped by the network — the search now reconnects and retries once instead of returning a “service unavailable” error. First-page and non-paged searches recover automatically; a search already paging through results with a server-side cursor surfaces the interruption so the client can restart the sequence.
  • Operator UI API errors keep their request id — An error returned from an Operator UI action now retains the server-assigned request id, so a failure seen in the console can be correlated with its server-side trace and logs.

Changed

  • LDAP paging counts now default to exact totals unless a client opts out — LDAP Simple Paged Results and virtual-list-view responses use exact totals by default, so clients that display role or group member counts do not see a low estimate as the total. Clients that only need navigation can still opt out of counts, and operators can choose estimated or no-count behavior through the new count defaults.
  • MCP search and lookup default to data attributes — When an MCP client omits fields, search and lookup now request data attributes (*) by default, matching REST and the shared query default. Clients that need operational attributes can still pass fields="+" or fields="*,+", and operators can restore the previous default with channels.mcp.default-fields = "*,+".
  • Forwarded directory searches record why a “service unavailable” was returned — Traces and wide logs for a forwarded directory search now carry two diagnostic attributes. ldap.delegation.page shows whether the search started a result set (first) or resumed a server-side cursor (continuation); ldap.delegation.retry shows whether an automatic reconnect was not needed (none), recovered the search (recovered), or was exhausted because the directory stayed unreachable (exhausted). Together they separate a transient stale-connection blip that self-healed from a directory that is genuinely down, and flag the one case that cannot be retried automatically — a search resuming a cursor — so it is clear the client must restart that paging sequence.

v3.0.0-rc.11 (2026-07-01)

Added

  • Early warning for memory leaks, with a downloadable diagnostic report — A new always-on sensor watches whether the memory that survives each garbage collection is trending upward without bound. When it is, IdentityScribe logs a warning, raises the scribe.memory.leak.suspected metric (alongside scribe.memory.live_set.bytes and scribe.memory.growth.per_hour) so unbounded growth shows up in Grafana, and raises a card in the Observe Inbox. From the card’s System → JVM view you can download a memory diagnostic report — a redacted version safe to attach to a support ticket and a full version for local investigation — capturing the JVM’s heap, memory pools, garbage-collector counters, and the sensor’s growth assessment, in the same way as the existing slow-query and workload exports. For deep analysis, an operator with Observe write access can trigger an on-demand heap dump (POST /observe/memory/heap-dump) for tools such as Eclipse MAT; only one runs at a time. Because a heap dump is a full copy of the heap — including credentials and personal data — capture is disabled by default, and enabling it requires setting monitoring.memory.heap-dump.directory to a private path (there is no default): the directory and every dump are created readable only by the account IdentityScribe runs as. The redacted report is safe to attach to a support ticket, and the report view’s email action opens a pre-addressed draft for you to attach the downloaded Markdown to. Tune the sensor under monitoring.memory — thresholds, observation window, and poll interval; it runs by default, independently of whether telemetry export is configured.

  • Searches that identify entries by object class resolve without forwarding — A directory search that selects entries by objectClass or structuralObjectClass — for example (structuralObjectClass=inetOrgPerson) against a base that several transcribes share — now resolves to the right entry type and is served by IdentityScribe instead of being forwarded to the connected directory. structuralObjectClass filters are matched the same way as objectClass filters. When the directory publishes its schema, IdentityScribe recognizes the structural subclasses of each transcribe’s configured object class automatically, so a transcribe filtered by (objectClass=person) also answers inetOrgPerson and organizationalPerson searches with no extra setup; when the schema is unavailable it falls back to the configured classes and logs why. The new transcribes.<type>.ldap.object-class-aliases option lists additional object class values that identify a transcribe for cases automatic detection cannot cover. All of this affects inbound query routing only — it never changes what is synced, stored, or forwarded upstream.

Fixed

  • Memory no longer grows without bound under sustained search load — Continuous LDAP, REST, or GraphQL search traffic caused heap use to climb for the life of the process until garbage collection thrashed and the LDAP channel stopped responding, typically after several hours of uptime. Memory now stays flat under the same load.

  • Substring-search row-count learning stays bounded when persistence is disabled — With database.gate-learning.enabled = false, the record of which substring-search row counts are exact was never trimmed, so memory could climb slowly under varied substring searches. It is now bounded regardless of that setting.

  • Full re-sync startup no longer fails on large stored datasets — When a subscription fingerprint change triggers a full re-sync, IdentityScribe now clears the old stored entries in a way that does not exhaust PostgreSQL lock memory. Large directories no longer fail startup with an out of shared memory message during that reset.

  • Searches return each matching entry only once — Free-text and fuzzy searches over several attributes no longer return duplicate rows when the same entry matches through more than one attribute or through several values of a multi-valued attribute. Sorting and cursor pagination keep their existing order.

  • Malformed LDAP paged-search cookies are recorded as client errors — An LDAP Simple Paged Results request with a corrupted continuation cookie already received the correct protocolError response; its trace, wide log, and error-rate signals now also classify it as a client error rather than a server-side failure, so a client sending a bad cookie no longer counts against server-error monitoring.

  • The eDirectory Identity Manager profile ships in the release archiveedirectory-idm.conf, documented since v3.0.0-rc.10 as an additive profile to layer on top of your base config, was missing from the downloadable release archive. The archive now includes it under the name used in eDirectory Identity Manager Roles.

  • Shutdown waits for sort-index backfill work to stop — When IdentityScribe is backfilling a sort index in the background (for example after adding a multi-valued attribute to indices.sortable), shutdown now waits for in-progress backfill workers to exit instead of signaling them and returning immediately. A shutdown that must wait past a bounded timeout logs a warning naming the workers still running.

  • Cancelled single-entry lookups no longer run their query — A request that resolves to a single entry uses a fast single-row lookup path; if the caller cancelled or timed out while that lookup’s parameters were still being bound, the query could still run against PostgreSQL. It is now skipped as soon as the cancellation is detected, so a request nobody is waiting on no longer does database work.

  • Reference deep links resolve Prometheus metric names, not just OpenTelemetry names/ref/* links and the embedded reference index now also recognize a metric’s Prometheus-exposition name (the underscored form scraped from /metrics, for example scribe_ingest_lag_seconds), alongside its dotted name (scribe.ingest.lag.seconds). Both forms resolve to the same reference page.

  • The Getting Started Dockerfile example fails clearly on a bad download — The custom-image Dockerfile shown in Getting Started now installs ca-certificates and downloads the release archive with curl -fSL --show-error, so an unauthorized or failed download (for example an expired token) stops the build with a visible error instead of continuing with an invalid archive.

  • Observe’s index-build detail list stays within its configured limit during large bursts — When many background index builds complete or fail within the same short window, the detail list behind Observe’s index-build status view could briefly hold more entries than its configured cap. The list is now trimmed to the cap after every burst.

  • Long sortable attribute names no longer report a false index-build failure — A sortable attribute whose combined type-and-attribute name is long (for example the srvprvlocalizednames attribute on the requestdef entry type) could show a permanent failed index build in Observe → Storage and log an index_create_failed warning on every startup, even though the index and its background maintenance were working correctly. These healthy builds are no longer reported as failed.

v3.0.0 (2026-07-31)

Breaking

  • BREAKING: Negative pagination limits now fail consistently across REST, GraphQL, and MCP — GraphQL directory searches that previously used the configured default now fail, GraphQL change-history errors now use ARGUMENT_INVALID_VALUE instead of INVALID_ARGUMENT, and MCP search and change requests now fail instead of requesting one result. Migration: send a positive limit, or omit it to use the configured default.

  • Monitoring log-rule operation shortcuts are no longer accepted through monitoring.log.rules[].name — startup fails if the shortcut remains in a rule. Migration: move exact operation filters to where = "scribe.operation=Exact.Operation" and pattern filters to where = "scribe.operation~=Prefix.*"; keep any duration or result predicates in the same where expression.

    monitoring.log.rules = [
    { action = exclude, name = "Transcription.WorkItem", where = "duration.seconds<=500ms" }
    { action = exclude, where = "scribe.operation=Transcription.WorkItem duration.seconds<=500ms" }
    ]
  • Hint persistence operation shortcuts are no longer accepted through monitoring.hints.persistence.rules[].name — startup fails if the shortcut remains in a rule. Migration: move rule matching to where filters such as hint-type=CONFIG or severity=ERROR.

    monitoring.hints.persistence.rules = [
    { action = exclude, name = "CONFIG" }
    { action = exclude, where = "hint-type=CONFIG" }
    ]

Added

  • Observe shows where query time goes, and how it trends — Queries gains an Insights tab that separates planning, query-capacity wait, database-connection time, and time to the first result, and charts short-term history for the signals behind a slowdown: query concurrency and rejections, which kind of search is getting slower, ingest lag, throughput and queue pressure, service restarts, and memory growth. The ingest and system views gain matching history panels, and the golden-signal strips become sparklines. A Query Diagnostic Report shows the same breakdown for one captured request, and redacted support exports keep these value-free timings, so performance problems can be investigated without exposing query values. History covers roughly the last two hours at three zoom levels — recent minutes in detail, older data coarser — and lives only in memory: after a restart the charts show an explicit gap and start fresh instead of reporting zeros, as do captures taken before this release. It is on by default and costs well under a megabyte; disable or bound it under monitoring.stats-history.

  • Performance insight report for support — When you report a performance problem, one download now carries the evidence: the performance insight report (Observe → Queries → Insights) packages recent metrics history, query stage timings, the slowest search patterns, ingest health, and database pressure indicators into a single redacted JSON file safe to attach to a support ticket. You need no Prometheus or Grafana of your own. Search identifiers stay stable and usable after redaction, each disclosure tier enforces its documented size limit, and large downloads use gzip when the client accepts it. A full variant for local investigation is available at /observe/performance-insight/export/full. As with the other reports, the email action opens a pre-addressed draft for you to attach the download to; the report itself is never embedded in the email.

  • Repeated searches can use a faster database planning choice — IdentityScribe measures alternatives against live traffic and applies a faster choice to search patterns whose evidence is current, keeping the standard choice active by default. Once the evidence is stable it promotes the choice without an operator enabling it per pattern, and it keeps rechecking, so a choice returns to the standard one when its evidence weakens or changes. Decisions are never stored or keyed by the values you searched for. Observe shows the effective choice, the evidence behind it, and whether a decision is still learning, settled, or being reconsidered; the global learning control remains available for emergency rollback.

  • Observe shows which index build failed and why — When a background index build fails, the Observe → Storage → Build Queue view now lists each failed build by name with the reason it failed, instead of only a failure count. When there are more failures than the list can show, or the details have not loaded yet, the view says so alongside the count. If the same build returns to pending after a completed or failed attempt, the new lifecycle stays visible. Operators can see what needs attention without reading the server logs.

  • Incomplete-notification Inbox cards name the attributes dropped on repair — When the directory sends change notifications without objectClass, the Inbox card for repaired notifications now lists the attribute names recently dropped (for example objectClass, mail) instead of pointing operators only to ingest logs. It keeps a bounded set of the newest samples per entry type, so one noisy type cannot crowd out the others, and a recording failure never blocks the repaired entry. Samples contain attribute names only, never entry values.

  • Browser responses carry baseline security protections — IdentityScribe’s HTTP channels and published documentation now send headers that prevent framing, MIME-type guessing, and referrer disclosure.

Changed

  • The embedded REST API reference is updated — The /api and /observe reference pages now use the current stable release, including fixes for schema display and keeping request examples in sync.

  • Telemetry labels now use bounded, documented values — MCP tool counters and timings use fixed endpoint, tool, and result values; ingest failures use the stable reasons process, persist, and executor; and request-provided attribute names outside the configured catalog no longer create new metric label values. Operator dashboards can group these signals without labels growing from unexpected traffic values.

Fixed

  • Sorted searches with missing sort values are faster and remain complete during concurrent updates — IdentityScribe now chooses the fastest verified complete execution for common first-page searches automatically. Fast searches no longer spend milliseconds on planning work that can exceed the search itself, and entries without the requested sort value cannot be omitted because the data changed between planning and execution. Results, ordering, and cursors are unchanged; no configuration is required.

  • Operator UI presets survive immediate reload or tab close — Saving, updating, or removing a preset previously waited briefly before writing to browser storage, so reloading or closing the page right afterwards could silently discard a change that already appeared saved. Preset changes are now stored immediately.

  • Telemetry shutdown problems are now visible — A failed or timed-out flush of buffered telemetry during shutdown previously looked identical to a clean close. IdentityScribe now logs a warning naming the affected provider and step, so operators can detect telemetry loss at shutdown, and always continues closing the remaining providers.

  • Sizes below one byte no longer render an undefined unit — Fractional byte values in the operator UI displayed output such as 512 undefined; they now always render with a valid unit.

  • Large sorted pages no longer fall back too early after finding matching entries — In very large directories, a cursor-paged sorted search with a long list of possible attribute values could switch to slower behavior even while it was still returning matches. A search that is producing results now keeps going for longer before falling back, so the same page returns much faster, with results and ordering unchanged. To restore the previous behavior, set database.continuation.budget-extension-enabled=false; database.continuation.window.absolute-scan-cap remains the hard ceiling.

  • Synchronization stops safely after a transcription worker fails — A worker failure could leave accepted entries waiting forever during drain or allow queued work to begin after the failure. IdentityScribe now wakes drain and shutdown waiters, accounts for accepted work that cannot run, refuses later processor admissions, and keeps checkpoint advancement disabled so a restart cannot skip abandoned work.

  • Cursor-paged searches keep requested virtual attributes — A later page that selected a virtual attribute could omit it from the results. Such requests now preserve every requested virtual field on every page.

  • Range filters keep the exact bounds you send — Multiple range conditions on the same attribute could be narrowed through case-insensitive value comparisons before execution, changing the intended result for mixed-case or locale-sensitive values. IdentityScribe now preserves the original bounds and lets the configured directory comparison rules decide the match.

  • Range filters match entries whose bounds are met by different values — Lower and upper bounds on a multi-valued attribute are now evaluated independently, as required by LDAP filter semantics. Entries are no longer omitted when one value satisfies the lower bound and another value satisfies the upper bound.

  • Interrupted database write confirmation is no longer replayed blindly — If the database connection failed while confirming a completed entry update or delete, IdentityScribe could retry work whose outcome was unknown, risking duplicate history or a repeated delete. It now stops the affected worker and keeps its checkpoint pinned until the outcome can be resolved. Failed write preparation also releases partially opened resources.

  • Browser session cookies reject unexpected encryption choices — Session-cookie decryption now accepts only the key-wrapping and authenticated-encryption algorithms IdentityScribe itself uses. Existing valid sessions keep working; cookies that advertise a different algorithm fail authentication before their contents are processed.

  • Cancelled MCP change-history requests stay cancelled — A client cancellation during history streaming was wrapped as a generic server failure. MCP clients now receive the cancellation result, while real history failures keep their existing error handling.

  • Disabled database time limits remain disabled after temporary request settings — A connection that began with no query or lock time limit could retain a temporary limit after a request-specific override. The original disabled state is now restored, preventing unrelated later requests from failing early.

  • Machine-readable tokens stay stable under non-English host locales — Configuration values, HTTP preference handling, GraphQL change types, telemetry labels, export labels, and MCP reference lookups now use locale-independent casing. Hosts using Turkish or another locale with special casing rules produce the same protocol values and accept the same documented inputs as other hosts.

  • Observe’s API reference covers every shipped diagnostic endpoint — The generated Observe OpenAPI document now includes memory snapshots and exports, heap-dump capture outcomes, query diagnostic reports, the setup support report, and the history and performance insight downloads, with their real access levels, disclosure tiers, status codes, and response schemas.

  • Startup fails with a clear error instead of hanging when a service never finishes starting — Services that start in parallel during process startup now share the same five-minute ceiling that sequential startup already enforced. A service stuck in its startup phase previously left the whole process waiting forever without becoming ready or exiting; startup now stops the other services and fails with an error naming the stuck service, so process managers can restart or alert instead of waiting on a process that will never come up.

  • Shutdown during startup no longer briefly starts further services — When a shutdown request arrives in the short window while startup is still bringing service groups up, a group could slip past the cancellation broadcast, start its services after shutdown had already completed, and only then be torn down. Startup now re-checks for a pending shutdown at the moment each group is registered, so no service starts once shutdown has begun.

  • A failed checkpoint read no longer looks like a first-time sync — When reading the stored sync checkpoint failed after retries (for example the database was briefly unreachable during startup), IdentityScribe treated the result as “no checkpoint exists” and started a fresh full synchronization, discarding the saved position. A failed read now surfaces as an error and stops startup of the affected sync worker instead, so the durable checkpoint is preserved and the worker resumes from it once the database is reachable again.

  • The Getting Started curl download examples fail clearly on a bad download — The curl install snippets in Getting Started now use the same fail-fast, redirect-following flags as the Dockerfile example (curl -fSL --show-error). An unauthorized or failed download — for example an expired token — stops with a visible error at download time instead of saving an error response that only fails later at unzip.

  • Exported trace names can no longer carry directory or request data — Trace redaction covered the attributes attached to a trace but not the trace’s own name. A request whose URL matched no known endpoint previously used its raw request path — which can contain entry names — as the trace name; such requests are now named by their HTTP method alone. Any trace name outside the expected naming scheme leaves IdentityScribe as span.redacted instead of its literal text. Attributes remain governed by the existing redaction policy; no configuration change is needed.

  • Two transcribe types can no longer silently share the same storage — Transcribe type names that differ only in non-alphanumeric characters (for example user-domain and user.domain) resolve to the same storage, which would mix both types’ entries. Startup now rejects such a configuration with an error naming both colliding types, so the conflict is caught before any data is written. Existing configurations with distinct type names are unaffected.

  • Root DSE discovery requesting operational attributes is answered by IdentityScribe — An LDAP Root DSE search (empty base DN, base scope) that requests the all-operational-attributes projection + — the form many LDAP clients and browsers use for capability discovery — was forwarded to the connected directory, so clients saw the upstream directory’s capabilities instead of IdentityScribe’s. It is now answered locally like every other Root DSE search. Tree-browsing requests on real entries keep their existing forwarding behavior.

  • GraphiQL enforces the configured script integrity value — The channels.graphql.ui.asset.js-sri value was accepted but never reached the browser, so a tampered script from a custom CDN would still load. The GraphiQL page now carries that integrity value where the browser verifies it before executing the script. Browsers that do not support this check keep the previous behavior; the stylesheet integrity check is unchanged.

  • Opening GraphiQL while signed out redirects to the login page — With login enabled and anonymous API access disabled, opening /graphql in a browser returned a raw JSON authentication error instead of redirecting to the login page. The browser now lands on the login page and returns to GraphiQL after signing in. Schema and API requests keep the JSON authentication error.

  • /readyz reports not-ready when core services are unhealthy — The readiness probe only reflected synchronization readiness, so an instance whose core services (for example the database connection or a channel listener) turned unhealthy after startup kept answering /readyz with ok and stayed in load-balancer rotation. /readyz now also requires the services to be healthy, matching its documented “can safely receive traffic” contract. /livez, /startedz, and /healthz are unchanged, and sync or index backlogs still do not pull an instance out of rotation.

  • Searches naming several entry types with an OR filter are answered — A search over a base that spans several entry types and a filter listing the wanted types as alternatives — for example (|(type=person)(type=group)), alone or combined with further conditions — was rejected as if no type had been named at all. Such filters now narrow the search to exactly the listed types, the same way type=person already did. Filters that mix a type alternative with conditions on other attributes keep the previous behavior, because narrowing those could drop matching entries.

  • Breadcrumb links work for entries with long names — In the Operator UI, the breadcrumb trail on an entry page cut the entry identifier at 100 characters when building its link, so clicking a breadcrumb for an entry with a long distinguished name led to a page that does not exist. The link now always carries the full identifier; only the displayed text stays shortened.

  • Observe history panels preserve the evidence that still works — A malformed metric series no longer drops healthy series from the same collection. Query, ingest, JVM, service, and database views distinguish loading, first-load errors, stale data, and unavailable in-memory history; a later refresh or tier-switch failure keeps the last good charts visible with a retry action and stale-data notice.

  • Observe Inbox counts, lists, and details stay in sync — The sidebar count, Inbox list, and recommendation detail now share one bounded-freshness snapshot. Concurrent refreshes share one generation, and applying, dismissing, snoozing, or reopening a recommendation invalidates that snapshot before the next read, preventing a badge count from describing different evidence than the page.

  • Observe history and report downloads use the documented access checks — The in-memory history API now applies the same Observe read access as the other Observe JSON APIs, and the redacted and full report variants each require their documented access level.

  • Performance insight reports include incomplete-notification totals — The support report now includes recent skipped and repaired incomplete-notification counts when history is available. The Inbox card for skipped notifications also stays focused on the held-back updates, rather than listing dropped attributes, which apply only to repaired ones.

  • Heap dump capture no longer reveals server filenames — When on-demand heap dumps are enabled, the capture response now returns an opaque capture ID and size rather than the server location. Repeated capture attempts are also rate-limited with a retry time, so operators get a clear response instead of starting expensive captures back to back.

  • Monitoring configuration mistakes fail validation clearly — Telemetry log filters now reject conflicting operation-name constraints at startup, and one-sided saturation-pressure overrides are checked against the effective default for the other side. Invalid configurations fail with the normal monitoring configuration error instead of surfacing a lower-level startup exception.

  • Exact telemetry log filters stay exact — Monitoring log filters that name one operation exactly no longer also match near-named operations. Wildcard and regular-expression operation filters keep their existing pattern behavior.

  • Observe keeps configured attribute names in index-build labels — Storage build status now preserves the original configured attribute descriptor when reporting planned index work, so mixed-case attributes remain recognizable in the Operator UI instead of being shown through a normalized fallback.

  • Support downloads use safe filenames — Browser-triggered report downloads now sanitize unusual suggested names before setting the saved filename, preventing directory separators or control characters from appearing in download names.

v3.1.0 (2026-08-31)

Added

  • Query diagnostics expose each stage — The Query Diagnostic Report now breaks a captured operation’s time into Normalize, Plan, Prepare, and Execute stages and shows how much time preparation used. The share covers one executed search or count operation; Execute includes admission wait, database work, and result delivery.

Breaking

  • Memory pressure metric matches documentationBREAKING: Prometheus now exports JVM memory pressure as jvm_memory_pressure instead of jvm_memory_pressure_ratio — custom dashboards and alerts that query jvm_memory_pressure_ratio stop receiving samples. Migration: replace jvm_memory_pressure_ratio with jvm_memory_pressure; the bundled dashboards are already updated.

  • Workload evidence separates retained examplesBREAKING: Observe workload responses and support exports now separate aggregate observations from bounded retained examples — clients that read seenCount, card recurrence fields, or samples can no longer parse those removed fields, and workload support exports now use schema 2. Migration: read observedCount, evidenceCoverage, and retainedExamples; use retainedBecause to explain each example; update support-export consumers to schema 2.

  • Affected entry histories repair automaticallyBREAKING: On upgrade, histories affected by the former identity reset are repaired automatically when the stored evidence is unambiguous, restoring the original entry ID as the single identity, removing the false Created event, and reducing later changes to the attributes that actually changed — temporary entry IDs created during affected refreshes no longer resolve. Migration: re-resolve affected entries by UUID or distinguished name and refresh any cached entry IDs; ambiguous histories remain unchanged and available for support diagnostics.

Fixed

  • Entry history survives configuration changes — Existing entries retain their entry ID and complete history when synchronized attributes or other source settings are added, changed, or removed and a full refresh follows. Only real object changes create history events; newly configured attributes that are absent on an object and attributes removed from synchronization update the current view without inventing changes. An interrupted or incomplete refresh remains pending instead of deleting entries that were not safely checked.

  • Attribute Coverage lists every attribute — The Operator UI now lists every stored attribute, including attributes without index or sort coverage. Those rows use a neutral “Not applicable” state and do not report healthy coverage signals.

  • Slow shares use every operation — Observe calculates each slow share from the complete workload seen by the current process. Drill-downs keep a bounded typical comparison beside noteworthy examples, while the overview stays focused on actionable patterns. Slow or failed examples no longer act as the denominator for the aggregate.

v3.2.0 (2026-09-18) Latest

Added

  • Technical accounts can authenticate locally — Configure one or more local accounts with a DN, password, and optional short-name alias for LDAP, HTTP Basic, or browser login without a backend authentication round trip. Forwarded LDAP operations use the account’s explicit backend credentials or the root LDAP credentials as a complete pair, and disabled delegation fails before a backend connection is opened.
  • Thread dumps from a running service — Sending the quit signal (kill -3) now writes every thread’s state and code location to the service output and lets the process carry on serving. Operators can see what a stalled instance is waiting on without restarting it, and the dump holds no directory entries or credentials. Earlier versions shut down on that signal.

Changed

  • Authentication settings keep their method scope — LDAP and ROPC cache lifetimes, disabled caching, and token-expiry revalidation now apply to the selected authentication method. Concurrent checks for the same backend credentials share bounded work; local passwords are never cached, and bearer validation remains unchanged.

Fixed

  • LDAP sign-ins stop waiting indefinitely — Authentication and identity lookups now have a ten-second default timeout, with limited simultaneous upstream work. Configure auth.ldap.request-timeout from 1 millisecond to 1 minute. Brief bursts can wait up to two seconds at each capacity limit within the authentication deadline. The default pending limit is 256 callers, including those joining a check for the same credential. Size auth.ldap.max-pending for the client connections that may bind within one upstream authentication round-trip, such as a client pool reconnecting after a restart; adjust waiting with auth.ldap.capacity-wait. Capacity rejections return LDAP busy (51), and authentication timeouts return unavailable (52); HTTP Basic sign-ins return 503 for either. These temporary failures skip the credential-failure delay. Invalid credentials retain LDAP 49, HTTP 401, and their configured minimum response duration.