Skip to content

All versions since v3.0.0-rc.7 (2026-06-12)

v3.0.0-rc.7 (2026-06-12)

Breaking Changes

  • Observe endpoints require authentication by default — Anonymous requests to /observe/doctor, /observe/channels, and other Observe JSON endpoints now return 401 unless an operator session or accepted credential is present. Migration: For trusted local evaluation, set monitoring.observe.auth.enabled=false or SCRIBE_OBSERVE_AUTH_ENABLED=false. For shared environments, sign in or send an approved credential.
  • Browser sign-in sessions require production keys — Production deployments that enable browser sign-in now fail startup until session-cookie keys are explicitly configured. Migration: Configure session-cookie keys before enabling browser sign-in in production; development setups keep local convenience behavior.
  • GraphQL schema and introspection require authentication when anonymous access is disabled — Requests for schema SDL, introspection JSON, and helper routes (/graphql.json, /graphql.sdl, /graphql/schema.*) now match query execution access rules. Migration: Send an approved credential to schema endpoints, or enable anonymous access.
  • Explicit distinguished-name binds are confirmed by the directory — LDAP binds with an explicit dn: value are now accepted only when the directory confirms that distinguished name. Other authentication methods can no longer authorize a caller-supplied DN they cannot prove. Migration: Use username binds or an authentication method that can prove the DN.
  • Insecure authentication-provider TLS is development-only — Production deployments that set auth.http.ssl.insecure=true now fail startup. Migration: Configure auth.http.ssl.trust-store with the provider CA, remove the insecure setting, or keep auth.http.ssl.insecure=true only for local development.
  • Credentialed CORS cannot reflect every origin — HTTP configurations that combine allow-credentials=true with reflected origins now fail startup because they would grant credentialed browser access to any requesting origin. Migration: List trusted origins explicitly, or turn credentials off for reflected-origin development setups.

Added

  • Query diagnostic reports include corpus context — Reports now show directory scale, field coverage, statistics freshness, and replay guidance. When context is unavailable, the report and export call out the missing data directly.
  • Change history gains hot-entry ranking across UI and APIs — The Operator UI, REST (/api/changes/hot), GraphQL (hotChanges), and MCP (hotChanges) now rank the most frequently changed entries in a time window, with the same filters and cursor paging as the change feed.
  • GraphQL and MCP lookups can compare entry states over time — Integrations can now request one entry at two points in time through GraphQL or MCP. GraphQL returns the before and after entry states, while MCP lookup responses include before and after details plus a JSON Patch summary for the selected attributes.
  • Observe highlights slow operator workloads — Observe Inbox and Queries now surface when entry searches, lookups, change history, hot-entry rankings, and entry comparisons are slow or recurring, with grouped cards, timing summaries, and next steps for each pattern. Admin operators see a timing chip on entry search, lookup, change, and comparison views that links to the matching Observe workload detail. Support can download a redacted workload report without exposing request identities or filter values.
  • MCP change-history tools match REST and GraphQL — The MCP changes tool accepts since/until time bounds, range=all for unbounded history, and before for backward pagination. See MCP Channel.
  • Startup warns when legacy configuration keys are ignored — v2 configuration paths that are present but no longer read are now reported at startup so upgrades do not silently keep obsolete settings.
  • Directory servers with legacy RSA cipher suites can be reached with an explicit opt-in — TLS_RSA cipher suites are disabled by default because they lack forward secrecy. Listing the required suite in the LDAP connection’s ssl.cipher-suites setting re-enables it at startup (must be literal values, not environment-variable substitution). IdentityScribe logs a warning while the opt-in is active; prefer enabling modern (ECDHE) cipher suites on the directory server.

Changed

  • API reference pages use newer embedded explorers — REST, Observe, and GraphQL documentation pages ship with refreshed bundled assets for better schema browsing, search, and local request trials. Same-origin Try Request calls keep browser sign-in credentials, while offline deployments continue to use bundled assets.
  • Change history opens with a broader recent window — The Operator UI and generated API examples now start with the last 24 hours instead of one hour, making recent activity easier to discover in low-change directories. API clients can still request one hour, range=all with paging, or any supported time range explicitly.
  • Entry comparison is clearer and shareable — In the Operator UI, compare mode keeps time range and event filters with the event timeline they affect, while the comparison keeps its before and after controls in the diff. When a selected range does not include the compared points, the timeline dims the outside-range area. Operators can choose before and after points from a change timeline, a compact change-density view, or explicit timestamps. The selected comparison stays in the URL so the view can be shared.
  • Observe UI is easier to scan and act on — The Services view focuses on status and action items first, with LDAP forwarding details on their own tab including forwarding rates, reason breakdowns, and guidance for strict-isolation setups. Recommendation detail pages now summarize linked evidence and show next steps, related views, and verification guidance. The configuration reference explains default protection on Observe endpoints and which settings control write actions.
  • Observe evidence pages are easier to compare and share — Workload evidence, storage coverage, Inbox recommendation detail, and Query Diagnostic Report pages now follow a consistent evidence-first layout with action summaries, related links, and support export controls where available. Diagnostic reports keep timing and captured request samples together, while lower-level request details stay in a collapsed appendix. Dates, durations, counts, byte sizes, percentages, and rates use the same display rules across rows, detail panels, reports, and exports.
  • Documentation improvements — Attribute modeling guidance explains when to store relationship data as regular attributes versus virtual attributes, with group, role, manager, and owner examples. The deployment guide now covers single-writer topology (one non-readonly instance for transcription and maintenance, with optional read-only replicas for query channels). See Attribute Modeling and Deployment.
  • Wide operational logs omit raw URL query strings by default — HTTP error diagnostics no longer write full query strings into wide logs unless an operator configures log redaction otherwise. Trace export already omitted this field; wide logs now match that default.
  • REST searches have a default server-side deadline — REST searches now use a 30-second server-side deadline when no request-specific search limit is configured, matching GraphQL behavior and preventing slow searches from running indefinitely.
  • Single-entry lookups are faster in large directories — REST, GraphQL, and LDAP requests that ask for one exact identity now avoid unnecessary broader search work. Exact LDAP base-DN lookups were about 40× faster in benchmarks; typed REST/GraphQL lookups by DN, UUID, numeric ID, or not-found result were about 10–25× faster. Selected attributes, access checks, errors, and broader searches keep their existing behavior.
  • Change history and entry comparison requests are faster in large directories — Change history responses that do not request event details now avoid extra work, and entry comparisons return faster when comparing two points in time.
  • High-entropy values are stored without wasted compression effort — Attribute values that are already random or compressed (certificates, encrypted or binary blobs) now skip the compression step, lowering write-time work on incompressible data. Values read back are unchanged; existing stored entries are unaffected.
  • Large sorted pages have a higher safety ceiling — The default continuation safety limit now leaves more headroom for very large directories, so eligible sorted pages can finish without falling back too early. If you override database.continuation.window.absolute-scan-cap, review that value against the new default before upgrading.

Fixed

  • Search correctness and pagination — Paginated searches now handle several edge cases that could omit, duplicate, or misorder results:

    • Sorted searches that filter on one attribute and sort on another, as well as unfiltered sorted searches, now include entries that match the filter but have no value for the sort attribute. Entries with no sort value are placed after all valued entries for ascending sort and before them for descending sort, matching RFC 2891. Paging through no-value entries stays fast on large directories.
    • OR-filter searches now deduplicate overlapping matches and keep entries without a sort value in page cursors.
    • Searches sorted by entry name now use consistent continuation values, so moving through pages does not skip or duplicate rows when full DNs and short entry names sort differently.
    • Virtual-attribute searches (such as group membership) now avoid unnecessary directory-wide work while preserving matches, ordering, and pagination.
    • Large sorted searches that combine filters, prefix matching, cursor paging, or virtual attributes now avoid unnecessary broad work more often while preserving matches, ordering, and pagination on large directories.
    • Searches that combine an exact match with additional conditions now apply all of them instead of returning broader results.
    • Requests that include an empty alternative inside a larger filter now preserve the client’s intended match behavior.
    • Filters on mixed-case attribute names (uppercase letters) now match and sort correctly.
    • Query diagnostic reports now explain when sorted searches with OR alternatives cannot use the faster optimization.
    • During startup, upgrade, or maintenance windows, sorted searches over multi-valued attributes wait for prepared acceleration data before using it, preserving correctness.
  • Operator UI reliability and display

    • Opening /ui now loads the Operator UI reliably in packaged deployments instead of showing a missing-page fallback.
    • Sign-in recovery works for expired and missing sessions: navigation and background refreshes show a sign-in prompt that returns operators to the same page after login; opening a protected page without a session redirects directly to sign-in.
    • The browser sign-in screen now follows the selected Auto, Dark, or Light color mode instead of always rendering in dark mode.
    • Recommendation times stay stable when browser clocks differ: future-dated observations from the server are treated as current local observations, so negative “ago” labels no longer appear.
    • Observe refreshes show partial data honestly: if one panel fails, the UI marks data as partial, clears stale health details, and points operators at the affected area.
    • Entry history and change views keep a recent time range selected, preserve that range while moving through timeline entries, and open entry links through the app router instead of briefly changing and reverting the page.
    • Modals and panels close with Esc or an outside click. The attribute change sparkline keeps its most recent point. Database Health and Coverage panels ignore superseded background refreshes. The Observe view no longer persists raw diagnostic text. Free-text ~= searches load back into the query builder. Entry comparisons stay distinct when two points share a timestamp. Observe scope filtering matches regardless of letter case.
    • Light mode no longer looks disabled: cards, tables, status chips, and recommendation rows use clearer contrast.
    • Multi-value diffs read consistently: an attribute side with no removed or added values no longer shows a contradictory “(no changes)” label, and before and after value lists now page together.
    • Comparison markers on the change-density timeline keep their position while events load and when the time range changes. The timeline axis matches the selected range.
    • Dense bursts in entry history and attribute change indicators now expand across their local time span instead of appearing as a single bar.
    • Header copy buttons now report success or failure and use a fallback when direct clipboard access is unavailable.
    • The UI recovers after a new deployment: when a browser has an outdated app open, the view surfaces an error with a reload option instead of showing a blank view. Entry list links encode the entry type so types with reserved URL characters open correctly.
    • Observe Inbox badges now count grouped recommendations that still need review, so dashboard and sidebar totals match the Inbox list.
    • Point-in-time links restored from change cursors now open the intended historical entry view. Change-history toolbar controls keep a stable layout on narrower screens.
  • Change history fixes

    • Previous-page navigation works correctly: moving back requests entries before the current cursor instead of showing the next page again.
    • Affected-attribute filters handle punctuation safely: quotes, semicolons, and other punctuation are treated as ordinary text.
    • REST change-history filters accept the same singular and plural entry-type names as other directory requests.
    • Search cursors keep working when clients rely on the default page size.
    • Copied entry links keep special characters intact.
    • URL-restored Operator UI filters no longer rewrite active searches.
    • Per-type change URLs such as /ui/entries/users/changes now redirect to the global Changes view with the entry-type filter applied.
    • Hot-entry ranking requests now honor deadlines and disconnects like other change-history requests.
  • Startup and operations

    • Startup after an interrupted initial sync no longer skips entries: IdentityScribe detects the incomplete state and performs a full re-enumeration. Affected entries are reprocessed and brought up to date. This also resolves a startup hang where a restarted sync worker could wait indefinitely for a startup gate already released in a previous run.
    • Directory synchronization repairs stale ownership conflicts automatically: when an entry moves in the directory while IdentityScribe is down or retrying, synchronization re-checks the directory, releases stale local ownership, and retries the affected entry without advancing past unresolved work. Readiness stays unavailable while repair is pending and resumes after the database has converged. Monitoring now reports repair-pending and repair-completed states separately, and only unresolved repair failures are logged as errors.
    • Transient directory errors during startup are retried automatically (up to twice) before startup is treated as failed.
    • Threshold-gated maintenance jobs no longer run when every configured threshold signal is missing. IdentityScribe warns about the missing signal so operators can fix the setting instead of unknowingly bypassing the gate.
    • Startup and monitoring logs are quieter during healthy operation: successful LDAP startup checks and database statistics refreshes no longer emit routine informational lines. Failures and warnings still appear.
    • Intentional shutdown during startup is treated as a clean cancellation instead of a spurious failed start.
    • Startup fails fast when a service fails during parallel startup: the failure surfaces promptly instead of waiting on the still-starting service and appearing to hang.
    • Invalid HTTP socket protocol settings fail startup clearly instead of silently falling back to an unintended listener shape.
    • Stalled initial synchronization is visible in monitoring: when a transcribe’s baseline has been restored but its initial scan is incomplete and the sync worker is stuck in a restart loop, the scribe_ingest_sync_stalled gauge increments and the ingest.sync_stalled doctor check turns degraded. The readiness snapshot reason changes from coherent-restored-baseline to restored-baseline-sync-incomplete. Set monitoring.ingest.fail-readyz-on-failed-state=true to also take the transcribe out of the readiness gate when its sync worker enters a terminal-failed state.
    • Ingest health now reports initial completion separately from current lag, so complete-but-lagging transcribes are not mistaken for incomplete startup work.
    • Successful write batches still commit when statement cleanup reports an error after execution, preventing completed persistence work from being rolled back by cleanup failures.
    • Fatal maintenance errors now surface to service failure handling instead of being lost during scheduled cleanup.
    • Native-image deployments keep embedded documentation and UI assets available across repeated requests.
  • Security and redaction

    • Authentication logs are safe and useful for access debugging: wide logs show bounded details such as failure reason, method, issuer, and key id. Subject identifiers, usernames, and client addresses use marked short hashes. Passwords, bearer tokens, cookies, and Authorization headers remain always replaced. Wide logs also note when a single-valued attribute was truncated on read.
    • JWT and token-introspection authentication accept configured public issuer URLs while still using the configured provider endpoint for discovery, so deployments with separate internal and public issuer addresses validate tokens consistently.
    • Browser session cookies choose the secure-cookie variant from the trusted request security state instead of raw forwarded-protocol headers.
    • Exported traces apply redaction consistently: linked trace metadata and unlisted numeric/boolean attributes are now covered, so configured identity and credential protections are applied before data leaves IdentityScribe.
    • MCP directory tool responses render returned values safely: text is wrapped in a safe plain-text form before presenting as Markdown, so directory values cannot appear as commands, headings, or links.
    • MCP lookup errors escape invalid identifiers before rendering Markdown, so rejected input cannot appear as headings, links, or code.
    • Invalid API requests now name the rejected parameter and why. The Operator UI shows that detail in search and change views and clears stale search cursors.
    • Unauthenticated browser asset access is limited to known static asset prefixes; application routes that contain /-/ now require authentication as configured.
    • LDAP proxy-authorization controls are rejected by local LDAP handling instead of trusting a caller-supplied delegated identity.
    • Hyphenated API-key attributes are now covered by default telemetry redaction.
  • REST, GraphQL, and LDAP API correctness

    • REST requests with blank optional parameters (fields, range, since, until) now use the documented defaults.
    • Single-entry REST lookups report service failures correctly: if IdentityScribe cannot load an entry after the request has been validated, the response reports a service failure with clearer text instead of a conflict response.
    • Point-in-time entry lookups accept the same identifiers (DN, UUID, numeric ID) as current lookups. Numeric-ID lookups can also return deleted historical entries, including response validators, instead of missing the reconstructed entry.
    • GraphQL type names no longer collide with built-in scalars.
    • GraphQL typed lookups return null for malformed identifiers instead of surfacing resolver failures.
    • GraphQL schema helper requests follow the configured schema access rules, and hot-entry fields avoid naming collisions with configured entry types.
    • Generated REST API descriptions remain valid for punctuation-heavy type names.
    • Generated REST API descriptions now document lookup comparison responses accurately.
    • REST and GraphQL searches honor cancellation, deadlines, and disconnects: they stop promptly and report cancellation or timeout instead of returning late results or a 500 error.
    • Typed entry lookups accept short distinguished names: single-RDN identifiers such as uid=jdoe resolve under the requested entry type’s configured base. Generic node(id:) lookups and plain names keep existing behavior.
    • LDAP search controls meant for the backend directory are now forwarded instead of dropped.
    • LDAP Virtual List View requests with extreme before or after counts are bounded safely instead of overflowing.
    • LDAP type-detection searches preserve the attribute spelling clients depend on: objectClass and similar filters keep that spelling in local responses when clients use default attributes or * selection. Explicitly requested spellings still take precedence; matching remains case-insensitive.
    • LDAP responses preserve canonical attribute names for synchronization metadata while keeping client-requested casing for explicitly requested attributes.
    • LDAP product fingerprints are more stable: detection no longer changes its fingerprint only because a directory serves the same UUID attribute in a different value form.
    • Forwarded LDAP searches no longer look like setup failures: they are recorded with bounded informational details instead of repeated warnings, keeping logs focused on real failures.
    • Stored entries are checked for truncation and malformed content on read: incomplete records are rejected instead of returning partial data.
    • MCP change-history responses escape missing identifiers safely, and MCP reference lookups remain available when the embedded reference index is unavailable.
    • Time-range inputs that are too large to represent now return bounded validation errors instead of unexpected server failures.
  • Directory and reconciliation

    • Reconciliation reports missing-entry deletes more reliably: it waits only for the deletes it just scheduled, and repeated delete batches that exceed the completion timeout are visible in telemetry.
    • Reconciliation telemetry distinguishes entry types so operators can tell which transcribe a stuck or slow pass affects.
    • Reconciliation verifies fallback matches before scheduling missing-entry deletes, avoiding deletes based on an unrelated distinguished name.
    • Health checks report unhealthy during automatic restart backoff so load balancers stop sending traffic until recovery.
    • MCP directory tools honor client cancellation: search, lookup, and change-history tools stop running when the client cancels.
    • Range-filter searches keep their lower and upper bound behavior in large sorted result sets. Explicit entry-type filters keep the requested type scope for current and point-in-time lookups.
  • Recommendations and Observe

    • Recommendation configuration examples are copy-paste safe: entry-type comments now appear after the quoted attribute value instead of inside it.
    • Slow-query recommendations open the right diagnostic report: Observe matches slow-query rows to the recorded directory query even when the database truncates or normalizes the displayed text. The match is retained across restarts after a query shape has been observed once.
    • Inbox cards focus on slow directory searches that operators can act on and ignore routine maintenance noise.
    • Observe Inbox and recommendation detail views stay responsive when many observations support the same fix.
    • Pending recommendation evidence is saved during shutdown, so recent observations remain available after restart.
    • Shutdown waits for in-flight directory ownership repairs before closing, keeping repair state consistent across restarts.

v3.0.0-rc.8 (2026-06-22)

Breaking Changes

  • Subtree searches that don’t name an entry type are answered locally — A directory search across a base that spans more than one configured context, without naming an entry type (for example a virtual-list-view browse from a directory client), is now answered from IdentityScribe’s own data instead of being forwarded to the upstream directory. Searches that already name a type, or target a single configured context, keep their current behavior. Migration: To keep forwarding these searches to the upstream directory, set channels.ldap.untyped-search = delegate (or SCRIBE_LDAP_UNTYPED_SEARCH=delegate). Mark any subtree that must be served by the upstream directory as an ignore-contexts entry.
  • Planner telemetry uses one consistent naming scheme — The metrics and trace attributes that describe how the query planner executed a search were spread across several inconsistent names and namespaces. They now live under a single scribe.plan.* metric namespace, and a single per-search-strategy latency histogram replaces the former gate-only one — so every routed search is measured, not only the gated ones. Migration: Dashboards or alerts keyed on the previous planner metric names (scribe.directory.plan.*, scribe.planner.*, scribe.database.plan_mode.*) or on the retired scribe.gate.query.duration histogram must be repointed to the scribe.plan.* series — per-search latency is now scribe.plan.route.duration.seconds. Custom queries that grouped planner metrics by older route, gate-source, or dispatch-leading attributes should move to the consolidated scribe.plan.route, scribe.plan.lead, scribe.gate.route, and scribe.gate.route_source attributes; reason labels are shortened under the new schema.
  • The Observe MCP endpoint requires authentication by default — Requests to the Observe MCP endpoint (/observe/mcp) now require an operator session or accepted credential; anonymous requests are denied. This endpoint previously inherited the global monitoring auth setting, which left it open whenever global authentication was off. Migration: For trusted local evaluation, set monitoring.mcp.auth.enabled=false or SCRIBE_MONITORING_MCP_AUTH_ENABLED=false. For shared environments, sign in or send an approved credential.

Added

  • Observe surfaces the query planner’s preparation cost — The planner does extra database work around a search — readiness and sizing checks it runs before the result query itself — and that cost used to be invisible. The Observe Inbox evidence-health panel now adds an “Estimation & GUC cost” section showing how much per-query sizing work ran and which connection pools still pay a per-request connection-setup cost, so you can spot pools that would benefit from a baked session profile. The underlying timing is exported for your own dashboards, and with trace export on (monitoring.traces.enabled = true) the background estimation and plan-mode checks show up on the trace timeline with their timing and frequency. Numbers read “evidence pending” until the first sample arrives. The same per-query sizing and connection-setup cost also appears in a query’s diagnostic report. See Signals.
  • Clearer LDAP sign-in diagnostics — When an LDAP sign-in succeeds but the directory service account cannot read the account back — an access-rights or filter configuration issue rather than a wrong password — operators now get a distinct signal that separates the two, and sign-in failures appear on the request timeline. The response sent to the client is unchanged.
  • Every search records which strategy ran, and how fast each strategy is — Each query’s trace now carries the search strategy it took, so one trace shows which kind of search ran. Per-strategy latency is now recorded for every routed search, including merge and range searches that previously went unmeasured, and the Observe evidence-health panel shows per-strategy p95 latency and a recent (5-minute) mix beside the cumulative totals — so a slowdown or shift in one kind of search shows up without waiting for process-lifetime aggregates. A query’s diagnostic report now names its search strategy too, and Observe workload cards group by strategy — so two slow searches of different kinds no longer collapse into one card.
  • Failure detail in logs can be redacted for shared log backends — Failure messages, failure details, and exception stack traces written to the structured and application logs can now be redacted before they leave the process, so query filter text, distinguished names, and attribute values that appear in error detail need not reach a shared SIEM. Redaction is opt-in and off by default — local incident logs keep full detail — and is set per field under monitoring.log.redaction (exception.message, exception.stacktrace, failure.details, exception.cause.*, warning.details), using the same hash and omit options as trace export. With no redaction rule configured, log output is unchanged.

Changed

  • Sorted searches that narrow to a small set of entries return quickly over large directories — A sorted or virtual-list-view browse that narrows to a small set — by entry distinguished names (a single one or a long list), by a unique identifier (uuid or uoid), or by a selective attribute value such as a rare surname — now returns in a fraction of a second across very large directories, where the same search could previously take many seconds. This applies when the search names the entry types it spans (or its base maps to specific types) and sorts on an attribute the directory keeps sorted, on the first page and when paging forward by cursor or page offset. Results and ordering are unchanged.
  • Broad sorted browses served locally return quickly over large directories — A sorted or virtual-list-view browse over a base that spans several entry types, with no narrowing filter, now returns in a fraction of a second across very large directories — on its first page, when paging forward by cursor, and at a deep page offset — where the same browse could previously take tens of seconds. This applies when IdentityScribe answers the browse from its own data rather than forwarding it (see the subtree-search entry above) and the spanned types keep the sort attribute indexed. Results and ordering are unchanged.
  • Observe presents evidence consistently across its views — The Observe Inbox, Workloads, recurring-workload groups, query diagnostic report, and storage-coverage views now share one evidence model, so a given signal reads the same way in every view. Expandable rows respond to the keyboard like native controls, and the views render correctly on small and mobile screens. The signals shown are unchanged.

Fixed

  • Operator views label searches by strategy — The Observe planner evidence-health panel now labels each search by an outcome-framed search strategy (Filtered search, Sorted listing, Combined-condition search, Membership search, Range search), matching the per-query diagnostic report. The information shown is otherwise unchanged.
  • Failed sign-ins return at a constant time — A failed credential check now returns at the same fixed time whether the username does not exist, the password is wrong, or the password was correct but the account could not be read back. Response timing no longer reveals which of these happened, so it cannot be used to confirm a valid password or probe which accounts exist. This covers credential validation at the LDAP bind, the HTTP 401 response, and the browser login form; the response a client sees is otherwise unchanged. The fixed time is auth.failure-delay (SCRIBE_AUTH_FAILURE_DELAY), default 2 seconds — set it comfortably above your slowest failed sign-in for the guarantee to hold (the 2s default covers a healthy directory). Setting it to 0 disables both the delay and the constant-time response.
  • Sorted prefix searches keep accented matches — Sorted and virtual-list-view searches that filter the sorted attribute with “starts with” now keep entries whose names contain accents or other non-ASCII characters. These searches no longer omit valid matches while paging, and result ordering is unchanged.
  • Sorted and paged directory searches no longer fail with an error — An LDAP search that has to be forwarded to the upstream directory (for example a sorted or virtual-list-view browse from a directory client) could return an “invalid request” error instead of the intended “this search cannot be served here” response. These searches now return the correct response.
  • Directory searches that select entries by distinguished name no longer need an entry type — A subtree search that filters on one or more entry DNs (for example an Identity Hub lookup that names users and roles by their distinguished names) across a base that spans more than one configured context previously returned an error asking for an entry type. IdentityScribe now recognizes the entry type from each distinguished name and returns the matching entries. Searches whose DNs fall outside the configured contexts keep their existing behavior.
  • REST error responses no longer expose internal diagnostic details — Error responses from the REST API previously included a details object that could carry internal directory layout (the configured base DNs for each entry type) and internal attribute names. REST error responses now omit that object, returning only the safe code, message, and HTTP status — matching the GraphQL and MCP channels. The code, message, and status are unchanged.

v3.0.0-rc.9 (2026-06-22)

Fixed

  • LDAP discovery stays license-compatible across harmless directory metadata differences — Startup license checks no longer fail only because the connected directory exposes an equivalent identity dataset with a discovery-only distinguished-name metadata difference. Directory-specific UUID value forms still affect the fingerprint when they change how entries are identified.
  • LDAP paged-search counts no longer understate complete result sets — Simple paged LDAP responses now report an exact size only when IdentityScribe knows the complete result count. Clients that page through a result set, including Identity Hub, no longer see a final response that suggests fewer available entries than were actually returned.
  • Documentation pages render wider content and tables correctly — The published documentation now gives desktop pages more reading space and renders tables reliably.
  • Redacted Observe support exports work for signed-in operators — Operators can now prepare redacted Query Diagnostic and Workload support exports without extra Observe write access. Full exports remain limited to operators with write access, and the Operator UI no longer shows the Full option to operators who cannot use it.
  • Entry count toggle explains exact-count fallbacks — When an exact entry count takes too long, the Operator UI now keeps the exact-count toggle selected, shows that the displayed number is still an estimate, and labels the timeout instead of appearing to ignore the toggle.
  • Forwarded LDAP searches can use entry distinguished-name filters — LDAP searches forwarded to the connected directory can now use entryDN (or Active Directory’s distinguishedName) when filtering, sorting, requesting attributes, or evaluating assertions. Identity Hub lookups by entry DN no longer fail with “Request cannot be delegated to backend server” when IdentityScribe forwards the search.
  • LDAP bind failures stay attached to the bind log — Failed LDAP binds that already run inside an LDAP bind operation no longer also emit a separate “Untraced error” line. Operators still get the same bind events and client response; the failure is now recorded with the operation log.

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, while ensuring 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) Latest

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.