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 channel — query.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.