All versions since v3.0.0-rc.11 (2026-07-01)
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.suspectedmetric (alongsidescribe.memory.live_set.bytesandscribe.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 settingmonitoring.memory.heap-dump.directoryto 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 undermonitoring.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
objectClassorstructuralObjectClass— 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.structuralObjectClassfilters are matched the same way asobjectClassfilters. 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 answersinetOrgPersonandorganizationalPersonsearches with no extra setup; when the schema is unavailable it falls back to the configured classes and logs why. The newtranscribes.<type>.ldap.object-class-aliasesoption 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 memorymessage 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
protocolErrorresponse; 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 archive —
edirectory-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 examplescribe_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
Dockerfileshown in Getting Started now installsca-certificatesand downloads the release archive withcurl -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
srvprvlocalizednamesattribute on therequestdefentry type) could show a permanent failed index build in Observe → Storage and log anindex_create_failedwarning 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_VALUEinstead ofINVALID_ARGUMENT, and MCP search and change requests now fail instead of requesting one result. Migration: send a positivelimit, 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 towhere = "scribe.operation=Exact.Operation"and pattern filters towhere = "scribe.operation~=Prefix.*"; keep any duration or result predicates in the samewhereexpression.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 towherefilters such ashint-type=CONFIGorseverity=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
/apiand/observereference 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, andexecutor; 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-capremains 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
curlinstall 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 atunzip. -
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.redactedinstead 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-domainanduser.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-srivalue 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
/graphqlin 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. -
/readyzreports 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/readyzwithokand stayed in load-balancer rotation./readyznow also requires the services to be healthy, matching its documented “can safely receive traffic” contract./livez,/startedz, and/healthzare 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 waytype=personalready 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 documentation — BREAKING: Prometheus now exports JVM memory pressure as
jvm_memory_pressureinstead ofjvm_memory_pressure_ratio— custom dashboards and alerts that queryjvm_memory_pressure_ratiostop receiving samples. Migration: replacejvm_memory_pressure_ratiowithjvm_memory_pressure; the bundled dashboards are already updated. -
Workload evidence separates retained examples — BREAKING: Observe workload responses and support exports now separate aggregate observations from bounded retained examples — clients that read
seenCount, card recurrence fields, orsamplescan no longer parse those removed fields, and workload support exports now use schema 2. Migration: readobservedCount,evidenceCoverage, andretainedExamples; useretainedBecauseto explain each example; update support-export consumers to schema 2. -
Affected entry histories repair automatically — BREAKING: 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-timeoutfrom 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. Sizeauth.ldap.max-pendingfor the client connections that may bind within one upstream authentication round-trip, such as a client pool reconnecting after a restart; adjust waiting withauth.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.