All versions since v2.7.0 (2025-11-14)
v2.7.0 (2025-11-14)
Features
-
Virtual Attributes: Compute attribute values on-demand instead of storing them, solving performance and storage issues with massive multi-valued attributes (100k+ values).
- Define attributes using LDAP filter expressions with variable substitution
- Two patterns supported:
- Cross-reference: Compute relationships to other entries (e.g., finding all groups a user belongs to via
groupMembership) - Self-reference: Filter based on current entry’s attributes (e.g., computing
userLevelbased ondepartmentNumber)
- Cross-reference: Compute relationships to other entries (e.g., finding all groups a user belongs to via
- Zero storage overhead and always up-to-date
- Transparent to LDAP clients - virtual attributes work in searches, filters, and sorting
- Optimized SQL queries leverage existing partitioning and indexing
Configuration Example:
ldap {virtualAttributes {# Cross-reference: Find all groups where this user is a membergroupMembership {filter = "(member={{current.entryDN}})"value = "{{other.entryDN}}"}# Self-reference: Compute user level based on departmentuserLevel {filter = "(departmentNumber=Executive)"value = "senior"}}}transcribes = [{type = "user"ldap {attributes = """cn, sn, mail, groupMembership, userLevel"""# groupMembership and userLevel will be computed virtually}}]Use Cases:
- Massive multi-valued attributes like
equivalentToMeon roles (prevents timeouts and database bloat) - Reverse lookups without storing redundant data (e.g., “show me all groups this user belongs to”)
- Computed attributes based on entry’s own properties (e.g., access levels, flags)
See
reference.conffor complete documentation and additional examples.
Fixes
- Checkpoint Advancement Under Load: Checkpoint timestamps now advance incrementally during continuous LDAP synchronization. Previously, checkpoints only updated when the system was idle, causing delays in offline delete detection and reconciliation. This fix improves responsiveness and ensures timely change detection even under sustained traffic.
- Reduced Monitoring Overhead: Simplified Prometheus metric buckets for compression and entropy monitoring, reducing time series by ~40-50%. This lowers memory usage and speeds up dashboard queries while maintaining all actionable thresholds. Existing dashboards remain compatible with no changes required.
- LDAP Connection Monitoring: Fixed an issue where LDAP connection metrics could show negative values in Prometheus monitoring. Connection tracking is now more accurate and reliable.
- LDAP Search Performance: Improved search filter processing for extended DN match filters, ensuring more accurate results.
- Enhanced Logging: Better diagnostic logging when operations are forwarded to the source LDAP server. Logs now include more context about entry types to help with configuration and troubleshooting.
- Query Optimization: Improved performance of LDAP filter processing by optimizing queries for common operational attributes, reducing unnecessary database overhead.
- SQL Function Optimization: Replaced PL/pgSQL functions with SQL functions to improve performance and reduce database overhead.
- SQL Index Optimization: Gather statistics for function based indexes to help the query planner to use the right indexes.
- Prometheus Metrics Restructuring: Reorganized LDAP forwarding metrics to ensure reliable metric collection and prevent registration errors. This change improves monitoring reliability but requires dashboard updates.
v3.0.0-rc.3 (2026-05-15)
IdentityScribe v3 is a major platform release for operating and integrating identity data at scale. It adds REST, GraphQL, and MCP access; the built-in Operator UI; embedded documentation; OpenTelemetry-native operations; and large-directory query performance improvements for sorted, filtered, prefix, substring, and cursor-paginated searches.
This release includes breaking database, configuration, endpoint, health, and metrics changes. Plan an upgrade window and start with Upgrading IdentityScribe.
Highlights
- Multi-channel identity APIs — REST/OpenAPI, GraphQL/GraphiQL, MCP, API discovery, streaming export, and consistent identifiers across channels. See REST, GraphQL, and MCP.
- Operator UI and embedded docs — Portal, Entries, Changes, Observe,
/docs, and/ref/*deep links ship with the product. See Monitoring. - Authentication and authorization — OIDC bearer tokens, browser sessions, access rules, channel-specific auth, LDAP bind refinements, and safer ProxyAuth behavior. See Authentication.
- OpenTelemetry-native operations —
/observe/*, MicroProfile health, Prometheus/OTLP metrics, wide logs, pressure signals, Grafana dashboards, Docker Compose, and Helm assets. See Observability and Monitoring. - Large-directory performance — Faster equality, prefix, substring, range, OR, sorted, partial-match, long-text, and cursor-paginated searches, with automatic fallback when acceleration is not suitable. See Database Tuning.
- Change history and point-in-time access — REST and GraphQL access to entry history, temporal lookups, verification timestamps, and operational naming attributes. See Data Model.
- Safer upgrades and supportability — PostgreSQL 17/18 support, partition hardening, startup validation, clearer error codes, support IDs, and cleaner shutdown/cancellation behavior. See Upgrading and Failures.
Breaking Changes
Database support
PostgreSQL 15 and 16 are no longer supported. Upgrade to PostgreSQL 17 or later before installing v3; PostgreSQL 18 is recommended. See Upgrading IdentityScribe.
HTTP, health, and endpoint model
HTTP channels now use one socket configuration model. Deployments that configure old monitoring listener keys such as monitoring.hostname or monitoring.port must move those bindings to http.host, http.port, or http.sockets.*. See the HTTP Server Guide.
Status, health, and legacy monitoring paths changed. Monitoring probes and dashboards using /status, /healthy, or /-/* must move to /observe/*, /readyz, /livez, /startedz, /healthz, or /metrics. JSON health responses now follow MicroProfile Health, so clients parsing the old /healthy body must update their response handling. See Upgrading: endpoints.
Configuration and environment
Environment variable prefixes are standardized on SCRIBE_*. Replace IDENTITY_SCRIBE_* and SCRIBE_TELEMETRY_*. See Upgrading: environment variables.
Connection hints now use a typed session-flags {} object. String-based flag lists and flat work-mem configuration fail startup. See Database Configuration.
Logger configuration is consolidated to the canonical loggers. Old logger keys are removed. See Upgrading: logging.
ldap.authentication-required is removed. Use channels.ldap.auth.enabled. See Authentication.
Observability and client contracts
Metrics now use the scribe.* namespace. Update Prometheus queries, alerts, and dashboards. See Observability.
Error codes are standardized to SCREAMING_SNAKE_CASE. Clients matching on error codes must update their mappings. See Failures.
Broad queries that can span multiple entry types must include an explicit type constraint. Ambiguous requests fail early instead of running against an unintended scope. See Upgrading: query scoping.
Virtual attribute placeholders use {{self.*}}. Legacy {{current.*}} placeholders fail startup with migration guidance. See Upgrading: virtual attributes.
The bundled Grafana datasource now uses OAuth passthrough on the monitoring socket. Existing anonymous Grafana setups must configure OAuth or explicitly opt into temporary anonymous access during migration. See Monitoring.
Added
APIs and integrations
- REST API and OpenAPI UI — Query entries, look up individual identities, stream CSV/JSON/NDJSON/LDIF/JSON-LD exports, read change history, use conditional requests, choose strict or lenient attribute validation, and work with JSON, FleX, SCIM, or LDAP filters from HTTP clients. See REST Channel.
- GraphQL API and GraphiQL UI — Query entries, history, point-in-time state, connection counts, persisted queries, and schema-generated types with Relay-compatible identifiers. See GraphQL Channel.
- MCP channel — AI assistants can query identity data, reference docs, schemas, health, and diagnostics through
/mcpand/observe/mcp. See MCP Channel. - API discovery —
/.well-known/api-catalog, REST specs, and GraphQL schema endpoints make the enabled API surface discoverable. See Channels. - FleX query language — A forgiving filter syntax is available for runtime queries and configuration filters. See Filters Reference.
Operator UI and documentation
- Operator UI — Portal, Entries, Changes, and Observe views provide browser access to system status, identity data, history, pressure, health, JVM metrics, and remediation guidance. See Monitoring.
- Embedded documentation site —
/docsships with guides, reference docs, and/ref/*deep links so operators can jump from config, telemetry, and error references into the matching documentation.
Authentication and access control
- Bearer token authentication — Validate OIDC JWTs offline, support multiple providers, and use the same identity model across REST, GraphQL, MCP, and LDAP. See Authentication.
- Access rules — Define first-match policies using identity, service, request, time, and environment context. See Access Rules.
- Browser sessions — Built-in UI sessions, secure cookies, OIDC redirect handling, and per-UI auto enablement. See Authentication.
- LDAP auth refinements — DN/username bind handling, channel-specific auth settings, unified failure delay, operation-specific gating, strict base enforcement, bind-filter checks, and backend delegation when channel auth is disabled. See LDAP Channel.
Observability and operations
- OpenTelemetry-native observability — Prometheus metrics, OTLP traces/metrics, authenticated metrics export, exemplars, pressure metrics, and structured wide event logs. See Observability.
- Observe endpoints and API UI — Status, health, doctor, pressure, services, indexes, channels, config, license, hints, recommendation history, attribute coverage, query recommendations, and operational statistics are available under
/observe/*. See Monitoring. - Monitoring bundle — Grafana dashboards, Docker Compose, and Helm assets ship with the distribution. See Monitoring.
- Rule-based log filtering and error classification — Include or suppress wide-log events with glob patterns and LDAP-style filters, and separate noisy client errors from server-health signals. See Logging and Observability.
- Support workflow — Failure responses expose IDs and headers that operators can send to support. See Failures.
Data access and history
- Change history API — REST and GraphQL access to global and per-entry changes, patches, merged views, metadata, and time filters. See REST Channel and GraphQL Channel.
- Point-in-time lookup — Retrieve entry state at a timestamp, relative time, or change cursor. See GraphQL Channel.
- Operational identity attributes —
entryID,entryName,entryRDN, andverifiedTimestampare available across channels. See Data Model. - Virtual attribute improvements — Virtual attributes can be delegated to upstream LDAP backends, scoped by entry type, and validated more strictly in REST and GraphQL. See LDAP Configuration.
Performance and scale
- Large-directory query acceleration — Common equality, prefix, substring, range, OR, sorted, partial-match, long-text, and cursor-paginated searches now use faster paths where safe. Broad or unsupported requests fall back automatically. See Database Tuning.
- Cursor pagination at scale — Sortable single-valued and multi-valued attributes now page more predictably at deep cursors, avoiding several previously pathological large-directory plans. See Database Configuration.
- Adaptive search behavior — Large directories use observed query traffic and maintenance statistics to keep search performance predictable across data distributions. Safe exact plans are used while the system is still warming. See Database Tuning.
- Search and sort controls — Operators can tune advanced search, sort, and preparation behavior when needed. See Database Configuration.
- Backpressure under load — HTTP APIs fail fast with
503andRetry-After; LDAP honors configured time limits. See Database Configuration. - Transcription throughput controls — Bounded workers and queue capacity reduce memory pressure during high ingest. See Transcribes Configuration.
Upgrade and data safety
- Schema hardening — Startup detects naming collisions, repairs legacy database layout issues, and avoids writing to ambiguous storage locations. See Upgrading.
- Startup and schema validation — REST/OpenAPI and GraphQL naming collisions fail early with actionable errors. See REST Channel and GraphQL Channel.
- Transcribe safety checks — Startup can warn, fail, or ignore orphaned transcribe types found in the database but absent from configuration. See Database Configuration.
- Ignored LDAP contexts — Selected LDAP base DNs can bypass local storage and delegate directly to the upstream LDAP backend. See LDAP Channel.
- Faster startup and safer maintenance — Service startup, index builds, readiness reporting, and request cancellation have been tightened for large deployments. See Deployment and Monitoring.
Changed
- Unified HTTP configuration — REST, GraphQL, Observe, docs, and browser UIs share one socket model with named sockets, CORS, compression, request limits, and graceful shutdown. See HTTP Server Guide.
- REST cache semantics — Lookup responses support ETag and Last-Modified validators; search/export responses default to no-store. See REST Channel.
- REST strict/lenient validation — Clients can request strict unknown-attribute handling through
Prefer: handling=strict. See REST Channel. - GraphQL and REST naming — Type, field, and component names are sanitized deterministically with collision guardrails. See REST Channel and GraphQL Channel.
- Transcribes object syntax — Object syntax is preferred and deterministic. Legacy array syntax still works but logs a deprecation warning. See Transcribes Configuration.
- Logging and operator output — Startup summaries, pretty logs, exception logging, third-party logger controls, and routine log noise filtering are cleaner. See Configuration and Observability.
- Observe entry point and layout —
/ui/observelands on Inbox so open recommendations are visible first. System status renders as a collapsible header with per-operator collapsed-state, and previously closed recommendations that re-appear surface above Open in their own Regressed section. See Monitoring.
Fixed
-
Faster sorted substring search — Sorted substring (contains) searches now pick the faster path more reliably at small and medium directory sizes. See Database Tuning.
-
Authentication fails closed — Empty bearer tokens and unsupported ProxyAuth forms return authorization failures instead of inconsistent behavior.
-
Bearer DN claims enforce scope — Bearer
dnclaims now honor the configured LDAP base, matching DN and username ProxyAuth paths. -
UI auth gating respects
auth.enabled=false—/uiand deep links no longer redirect to login when auth is disabled. -
Beta upgrade startup — Migration checksum handling no longer blocks affected beta-based upgrade paths.
-
Time and shutdown correctness — Timestamps are handled consistently across time zones, shutdown is quieter, failed or timed-out requests cancel remaining database work, and search time limits cancel database work promptly.
-
Pagination correctness — Cursor metadata and backward paging now report and stream correctly.
-
Virtual attribute behavior — Delegated virtual attributes are forwarded correctly and self-referencing warnings are no longer false positives.
-
Authentication and access-rule hardening: OpenID Connect, token introspection, trusted client IP handling, and HTTPS certificate-chain limits now fail closed more consistently.
-
Data consistency during reconciliation and history: Distinguished-name changes, temporal lookups, history rewind, and change-history streams now preserve correct results and surface invalid requests as structured errors instead of silent success.
-
Query correctness at scale: Negated filters, large value ranges, cursor pagination, and sorted result boundaries now avoid edge cases that could return incorrect rows.
-
REST, GraphQL, LDAP, and MCP contract fixes: Exports enforce row-limit and pagination rules before committing responses, GraphQL returns standard error fields and per-entry changes reliably, LDAP Virtual List View jumps work for common single-sort requests, and MCP tools honor standard arguments and numeric limits.
-
Observability and metrics reliability: Collector endpoint configuration, telemetry lifecycle handling, Prometheus compression negotiation, and index-build status tracking now report operational state more reliably.
-
Operator UI reliability and safety: Login redirects preserve query state, entry and history views avoid stale route data, Observe deep links and sidebar states render consistently, and externally supplied recommendation links are sanitized.
-
Documentation and reference accuracy: Fixed generated and reference links and examples that pointed to unavailable fields or stale commands.
Deprecated
- Legacy config key spellings — camelCase and underscore_case keys still work but log warnings. Use kebab-case keys. See Configuration Reference.
- Legacy transcribes array syntax — Array syntax still works but logs a deprecation warning. Use object syntax keyed by transcribe type. See Transcribes Configuration.
- Legacy channel auth fallback — Use
channels.<name>.auth.enabledinstead ofchannels.<name>.authentication-required. See Authentication.
v3.0.0-rc.4 (2026-05-28)
Fixed
- Approximate (
~=) searches return more useful matches and run faster — LDAP~=filters and the equivalent REST/GraphQL approximate match now find compound names such as “Michael Schmidt” more reliably, while single-field searches on large directories complete faster. Very short search terms still return no matches to protect large directories from expensive broad scans. - Approximate searches across multiple fields avoid unnecessary full-directory work — Searches that combine approximate conditions across fields configured for substring search now use the faster available lookup with the same result set as before. Fields that are not configured for substring search continue to use the safe compatibility path.
- Combined prefix searches across configured search fields are faster — Searches that combine several prefix-searchable attributes with entry name or distinguished name filters now avoid unnecessary full-directory work when only a few entries match. This improves broad search-box queries in large directories while preserving result order and cursor pagination.
- Custom attributes missing from equality and prefix indexes —
indices.value-matchandindices.partial-matchsettings (and theSCRIBE_INDICES_VALUE_MATCH/SCRIBE_INDICES_PARTIAL_MATCHenvironment variables) were silently ignored when set. Only the built-in baseline attributes received equality and prefix indexes. Custom equality and prefix indexes are now created as configured. - Abandoned REST count/HEAD and GraphQL requests stop sooner — REST
?include=count, RESTHEAD, and GraphQL directory queries no longer keep database work running indefinitely after the client stops waiting. REST count/HEAD requests followchannels.rest.connection-hints.statement-timeout(SCRIBE_REST_STATEMENT_TIMEOUT) when configured and use a 30-second default otherwise; GraphQL requests use a 30-second execution deadline. - Searches preserve requested filters and result shapes more consistently — Directory searches with repeated attribute predicates, sorted filters, multi-type results, continuation pages, and point-in-time lookups now preserve the caller’s filters, connection hints, selected fields, and result column names more reliably.
- Cancelled searches release resources more safely — Count-only searches, point-in-time lookups, and streams cancelled while opening a database connection now use the same cancellation, timeout, and connection policy as normal searches, avoiding leaked connections or permits after callers stop waiting.
- GraphQL connection responses are more predictable — Connection queries now return requested
pageInfoand counts together, support larger configured pagination limits correctly, and handle malformed POST bodies without surprising parsing behavior. - REST/OpenAPI export contracts are documented more accurately — Change events now expose their runtime entry identifiers in the API schema, and search export responses document the supported media types and download headers.
- Query diagnostic reports are safer to share and easier to act on — Redacted support exports avoid raw search values, preserve the context Kenoxa support needs, explain when timing evidence is unavailable, and avoid recommending coverage changes that are already satisfied.
- Prefix searches recommend the right index setting — Queries that filter by a “starts with” attribute now point operators to
indices.value-matchinstead ofindices.partial-match. Attributes that are only ever searched by prefix can be removed fromindices.partial-match; combined prefix-and-substring workloads keep both recommendations. - Observe recommendations resolve more consistently — Inbox and query recommendation links now open the matching detail views more reliably, recommendations that point to expired query evidence are hidden, and storage coverage views use the available screen width more effectively.
- Ingestion and maintenance edge cases are handled more safely — Interrupted throttling no longer drops a live LDAP entry, reconciliation startup failures clean up active sync work, transient LDAP search interruptions reconnect with bounded warnings, and maintenance scheduling now recognizes threshold-only soft schedules.
- Monitoring and deployment bundles start more reliably — Helm health probes, Grafana OAuth passthrough, production replica defaults, monitoring-only datasources, full-stack Compose startup, and shutdown error reporting now match the runnable deployment shapes.
Changed
- Single-attribute approximate searches on short common names may return fewer matches — Approximate matching is more selective for short terms against attributes that usually hold a single word, such as
givenname. A search likegivenname ~= "mic"may return a smaller set than in prior releases. Multi-field approximate searches and approximate searches against attributes that commonly hold multiple words, such ascnordescription, are unaffected. Contact support if a specific attribute needs different matching behavior. - Index attributes are fetched automatically — Attributes listed in a transcribe’s
indices.value-match,indices.partial-match, orindices.sortableare now fetched and stored automatically. They no longer need to be duplicated inldap.attributes. - Kubernetes readiness now tracks traffic safety —
/readyzand JSON readiness report when the pod can safely receive requests. Scripts that need to wait for initial ingest and required index work to finish should use/observe/convergence. See Endpoints. - Observe recommendations are retained across restarts by default — Recommendation history and query diagnostic reports now remain available after restart. Set
monitoring.hints.persistence.enabled = falseto keep recommendations memory-only, ormonitoring.hints.persistence.clear-on-startup = trueto start each run with an empty recommendation history. - Query diagnostics are easier to share — Slow-query rows, coverage evidence, and recommendation links now open a report page that starts with operator actions and can export a Redacted or Full support bundle when Kenoxa support requests it.
- Diagnostics and operator signals are easier to interpret — Reports preserve troubleshooting context and count repeated observations consistently, ingest lag severity matches the documented thresholds, and Observe recommendation pruning avoids deleting concurrent lifecycle updates.
v3.0.0-rc.5 (2026-05-31)
Added
- The Queries tabs now explain how to turn on query statistics — When query statistics are unavailable, the Slow Log and Stats tabs show the missing setup step, copy-paste commands, and a link to the docs. The hint distinguishes a missing extension from a database library that still needs to be loaded.
- Optional auto-enable of query statistics for development and staging — Set
database.auto-create-query-stats-extension = trueto let IdentityScribe create the query statistics extension at startup after PostgreSQL has loaded the required library. The default isauto(on in development, off everywhere else), so production is unchanged unless you opt in. See Enabling query statistics.
Changed
- Query diagnostic reports explain multi-field substring searches more clearly — Support reports now show whether each searched field used the faster path or a safe fallback, with the reason when fallback was required. These rows previously reported that estimation was not captured, which made shared reports harder to act on.
Fixed
- Licensed deployments start without configuring an audience — License verification no longer fails when
license.audienceis left unset. The licensee your license was issued to is read from the signed license itself, so a valid license works out of the box. Setlicense.audience(or theSCRIBE_LICENSE_AUDIENCEenvironment variable) only when you want a deployment to match a specific licensee name. Previously, a deployment that did not setlicense.audiencecould fail to start with anInvalid audienceerror. - Prefix searches across substring-search fields are faster — Searches that combine several “starts with” conditions across configured search fields no longer fall back to full-directory work when a faster bounded lookup is safe. Large-directory searches return sooner with the same matches, ordering, and cursor pagination as before. Broad searches keep the safer path automatically, and no configuration change is required.
- Query statistics appear as soon as the extension is enabled — Turning on query statistics after IdentityScribe was first deployed no longer needs a redeploy or a follow-up migration; the Queries tabs and
/observe/stats/queriespick it up on the next refresh. Creating the extension before loading the database library no longer makes the endpoint fail; it reports the missing setup step and points to the fix. - Long prefix searches no longer admit neighboring values — Prefix searches with very long values now check the full requested value after using accelerated narrowing, so entries that only share the stored leading segment are no longer included. Normal short prefix searches keep the same fast path.
- GraphQL field-name docs match the served schema for mixed-case hyphenated attributes — The GraphQL guide now shows the exact field name exposed for attributes such as
DirXML-EntitlementRef, so generated examples, schema exploration, and documentation agree.
v3.0.0-rc.6 (2026-06-03)
Added
- LDAP setup checks now appear at startup and in Observe diagnostics — Startup now checks whether the configured LDAP bind mode and account can read the attributes needed for reliable transcription. Fatal problems stop startup with remediation, while non-fatal warnings appear in startup output and Observe diagnostics without changing Kubernetes readiness. This helps operators distinguish empty data from authentication or directory permission problems.
- Download a redacted setup report for support — The Operator UI dashboard can prepare a setup report to attach to a support ticket for first-run problems such as a missing REST API, a UI that shows no entries, documentation that does not load, or directory data that never appears. The report captures which APIs are enabled and where they are served, sockets and runtime ports, sign-in state, documentation availability, and whether the service can read your directory, with each check labeled passed, failed, skipped, or unavailable. Passwords, tokens, URL credentials, and other secrets are removed before download. See Reporting to support.
Changed
- Exported traces no longer carry raw directory identity by default — With trace export enabled (
monitoring.traces.enabled = true), exported spans previously included raw distinguished names, usernames, subject identifiers, entry UOID and UUID, and request URLs, which reached whichever collector or APM you export to. These identity values are now removed by default; enumerations, counts, durations, request outcome, and query shape are still sent unchanged, and internal wide logs keep full detail. To send specific identifiers to a collector you trust, set per-key strategies undermonitoring.traces.redaction— for example"scribe.entry_uoid" = identityto correlate spans by entry, or"auth.effective_dn" = hashto group by subject without the name. Distinguished names, usernames, and URLs accepthashbut are never sent raw.
Fixed
- Wide logs apply the configured per-key redaction rules — Wide logs now redact attribute values according to
monitoring.log.redaction, including the built-in defaults that hash distinguished names (*dn*) and truncate email addresses (*email*). Previously, glob patterns and dotted attribute keys in this setting were silently skipped, so distinguished names and email addresses could appear in wide logs in full despite the configuration. The always-on secret redaction (passwords, tokens, credentials, cookies) and trace export redaction were unaffected. - The Inbox groups repeated recommendations into one card per fix — When the same coverage recommendation (for example, missing sortable, value-match, or partial-match coverage on an attribute) is observed across many searches, the Inbox now shows a single card for each distinct fix instead of one card per observation. Each card shows how many observations support it and when it was last seen, and snooze, apply, and dismiss act on the whole group at once. The full evidence behind a card stays available from its detail view. Previously, a handful of real fixes could appear as hundreds of duplicate cards, and acting on one card did not affect the others.
- The Operator UI works when public API documentation is private — With production defaults, the REST data API remains available while the public API reference is off. The Entries view now loads its type information without depending on the public
/apipage, and dashboards and startup output no longer present documentation links that would return 404. Previously, a secure first-run setup could make the UI look empty or point operators to/api. - LDAP search responses preserve requested attribute-name casing — When an LDAP client requests
objectClass,ENTRYUUID, or another explicit spelling, IdentityScribe returns the attribute using that requested spelling while matching stays case-insensitive. Previously, local LDAP responses could return the configured spelling instead, which surprised clients that compare response attribute names. - Observe API documentation matches the channel status response — The OpenAPI document now describes the REST, GraphQL, MCP, and authentication availability fields returned by
/observe/channels, so clients that validate Observe responses against the spec no longer reject the channel status response. Previously, the response was valid at runtime but missing from the generated API contract. - Observe API documentation matches doctor status responses — The OpenAPI document now includes the
noisystatus returned by/observe/doctorfor informational checks, so clients that validate Observe responses against the spec no longer reject a valid doctor response. Health and readiness behavior are unchanged. - The API catalog lists only documentation links that are served —
/.well-known/api-catalogno longer advertises REST or GraphQL reference, schema, or playground URLs that return 404 when public API documentation is turned off (the production default). Each enabled channel’s base URL stays listed so the data API stays discoverable, and the documentation, schema, and UI links reappear once you enable them. Previously, the catalog could send API discovery clients to/api.json, the/apireference page, or the GraphQL schema even when those endpoints were disabled. - Searches on attributes from older databases are repaired automatically on upgrade — On databases first created by earlier versions, searches filtering on certain attributes (for example
objectClass) could fail at runtime. Upgrading now reconciles the affected databases automatically, so those searches succeed with no change to your attribute configuration — keepobjectClassspelled asobjectClass. No manual steps are required.
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 return401unless an operator session or accepted credential is present. Migration: For trusted local evaluation, setmonitoring.observe.auth.enabled=falseorSCRIBE_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=truenow fail startup. Migration: Configureauth.http.ssl.trust-storewith the provider CA, remove the insecure setting, or keepauth.http.ssl.insecure=trueonly for local development. - Credentialed CORS cannot reflect every origin — HTTP configurations that combine
allow-credentials=truewith 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
changestool acceptssince/untiltime bounds,range=allfor unbounded history, andbeforefor 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-suitessetting 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=allwith 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
/uinow 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.
- Opening
-
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/changesnow 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_stalledgauge increments and theingest.sync_stalleddoctor check turns degraded. The readiness snapshot reason changes fromcoherent-restored-baselinetorestored-baseline-sync-incomplete. Setmonitoring.ingest.fail-readyz-on-failed-state=trueto 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
nullfor 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=jdoeresolve under the requested entry type’s configured base. Genericnode(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:
objectClassand 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.
- REST requests with blank optional parameters (
-
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(orSCRIBE_LDAP_UNTYPED_SEARCH=delegate). Mark any subtree that must be served by the upstream directory as anignore-contextsentry. - 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 retiredscribe.gate.query.durationhistogram must be repointed to thescribe.plan.*series — per-search latency is nowscribe.plan.route.duration.seconds. Custom queries that grouped planner metrics by older route, gate-source, or dispatch-leading attributes should move to the consolidatedscribe.plan.route,scribe.plan.lead,scribe.gate.route, andscribe.gate.route_sourceattributes; 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, setmonitoring.mcp.auth.enabled=falseorSCRIBE_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 (
uuidoruoid), 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
401response, and the browser login form; the response a client sees is otherwise unchanged. The fixed time isauth.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 to0disables 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
detailsobject 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’sdistinguishedName) 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.confafter 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 channel —
query.default-count,query.default-limit,query.default-include,query.default-fields, andquery.default-sortnow provide shared defaults for integrations that do not set their own values. Named channel overrides such aschannels.ldap.default-count,channels.rest.default-fields,channels.graphql.default-sort, andchannels.mcp.default-limitstill 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 — underldap.request.*andldap.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/delegationwith 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 withmonitoring.observe.capture-request-values = false. The window keeps the most recentmonitoring.observe.max-samplessearches (default 200) formonitoring.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 underscribe.mcp.tool.*. Both endpoints bound how many tool calls may run at once: the cap auto-derives from the configuredconcurrencysetting (the same knob that sizes the database pool and HTTP limits) when unset, and can be set explicitly viachannels.mcp.max-concurrent-tools/monitoring.mcp.max-concurrent-tools(0disables 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 viachannels.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 omitobjectClass. 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’sobjectClassattribute 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 underscribe.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 dropobjectClass— and potentially other attributes — from the stored entry, leaving an entry that clients relying onobjectClassmay 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 newscribe.ingest.incomplete_notification.totalmetric (reason=repairedwhen the full entry was re-read,reason=skippedwhen 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-attributekeyed 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 passfields="+"orfields="*,+", and operators can restore the previous default withchannels.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.pageshows whether the search started a result set (first) or resumed a server-side cursor (continuation);ldap.delegation.retryshows 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.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.