Upgrading IdentityScribe
Upgrade IdentityScribe between versions safely. This guide focuses on breaking changes and the smallest steps to restore a healthy system.
If you haven’t deployed IdentityScribe before, start with the Deployment and Configuration guides instead.
Organized by upgrade path (e.g., v2.x → v3.0).
From v2.x to v3.0
Section titled “From v2.x to v3.0”This section covers upgrades from any v2 release to v3.0.
TL;DR (upgrade checklist)
Section titled “TL;DR (upgrade checklist)”- Update environment variables to the unified
SCRIBE_*prefix. - Migrate renamed config paths (
monitoring.telemetry.*→monitoring.*,hints.*→monitoring.hints.*). - Migrate indices configuration from
ldap.indicesandtranscribes.*.ldap.indicesto new paths. - Migrate HTTP configuration to the unified
http {}server and (optionally) named sockets. - Update probes and tooling to the new endpoints (
/observe/*,/observe/health/*,/metrics,/*z). - Update dashboards/queries for the new metric namespace (
scribe.*/scribe_*). - Update virtual attribute templates (
{{current.*}}→{{self.*}}). - Fix broad queries that now require an explicit type constraint.
- Update any client-side error-code matching to
SCREAMING_SNAKE_CASE. - Update scripts that call
/observe/*endpoints to sign in, send an accepted credential, or setmonitoring.observe.auth.enabled=falseonly in trusted local environments. - Plan a migration window proportional to directory size — the first v3 startup runs a one-time data preparation step that scales with the number of stored entries and values.
After deploying, verify:
curl -fsS http://localhost:8080/readyzcurl -fsS http://localhost:8080/metrics | headcurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor | jqcurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/config | jq
Config restructure
Section titled “Config restructure”Some configuration paths were reorganized for consistency. Old paths are ignored — IdentityScribe logs a deprecation warning at startup when they are still present.
What changed
Section titled “What changed”monitoring.telemetry.*→monitoring.*hints.*→monitoring.hints.*
Migration
Section titled “Migration”# Oldmonitoring.telemetry.traces.enabled = truehints.enabled = true
# Newmonitoring.traces.enabled = truemonitoring.hints.enabled = trueVerify
Section titled “Verify”- Start IdentityScribe and check the resolved config at
/observe/config(secrets are redacted). - If you rely on hints endpoints, confirm
/observe/hintsand/observe/signaturesare reachable (or explicitly disabled).
Indices configuration
Section titled “Indices configuration”Index configuration moved from ldap.indices to a dedicated indices {} block at root and transcribe levels. Indices control how the local database indexes transcribed data and are transcribe-specific, not LDAP-specific.
What changed
Section titled “What changed”| Old Path | New Path |
|---|---|
ldap.indices.* | indices.* (root level) |
transcribes.<type>.ldap.indices.* | transcribes.<type>.indices.* |
| Old Environment Variable | New Environment Variable |
|---|---|
SCRIBE_LDAP_INDICES | (removed - was unused) |
| (none) | SCRIBE_INDICES_PARTIAL_MATCH |
| (none) | SCRIBE_INDICES_VALUE_MATCH |
| (none) | SCRIBE_INDICES_SORTABLE |
Migration
Section titled “Migration”# Oldldap { indices { partial-match = "cn, sn" }}transcribes { user { ldap { base = "ou=users,o=data" indices { value-match = "uid, mail" } } }}
# Newindices { partial-match = "cn, sn"}transcribes { user { ldap { base = "ou=users,o=data" } indices { value-match = "uid, mail" } }}Verify
Section titled “Verify”- Start IdentityScribe and check the resolved config at
/observe/config. - Verify that
/observe/indexesshows the expected index configuration for each transcribe.
Env prefix
Section titled “Env prefix”Environment variable prefixes are standardized. If you still use the old prefixes, your settings will not be applied.
What changed
Section titled “What changed”IDENTITY_SCRIBE_*→SCRIBE_*SCRIBE_TELEMETRY_*→SCRIBE_*
Migration
Section titled “Migration”# OldIDENTITY_SCRIBE_READONLY=trueSCRIBE_TELEMETRY_TRACES_ENABLED=true
# NewSCRIBE_READONLY=trueSCRIBE_TRACES_ENABLED=trueSee the Configuration guide for naming conventions and examples.
Logging
Section titled “Logging”Logger configuration has been consolidated to five canonical loggers with standardized environment variable overrides. Old logger keys are removed.
Migration
Section titled “Migration”| Old | New |
|---|---|
log.EventStore / log.Scribe | log.Ingest |
log.Settings | log.Config |
log.LDAP, log.AsyncSearch, log.TaskExecutor | Removed (use monitoring/wide logs instead) |
New canonical loggers:
log.SuperVisor(SCRIBE_LOG_SUPERVISOR)log.Ingest(SCRIBE_LOG_INGEST)log.Monitoring(SCRIBE_LOG_MONITORING)log.License(SCRIBE_LOG_LICENSE)log.Config(SCRIBE_LOG_CONFIG)
Example:
log { level = "info"
Ingest = "debug" Config = "warn" Monitoring = "off" # Disable wide-log output entirely}See the Log Configuration Reference for the full set of logger settings and format options.
HTTP sockets
Section titled “HTTP sockets”All HTTP traffic (REST API, monitoring endpoints, site/docs) uses a unified HTTP server configured under http {}. You can keep everything on one port, or split traffic using named sockets.
For full details and patterns, see API & Networking.
Migration (single socket)
Section titled “Migration (single socket)”# Everything on one porthttp { host = "0.0.0.0" port = 8080}Migration (split monitoring to an internal port)
Section titled “Migration (split monitoring to an internal port)”http { host = "0.0.0.0" port = 8080
sockets.internal { host = "localhost" port = 9001 }}
monitoring.socket = "internal"Endpoints
Section titled “Endpoints”Several operational endpoints were renamed and legacy routes were removed. No aliases are provided, so probes and dashboards must be updated.
What changed
Section titled “What changed”| Old | New |
|---|---|
/status | /observe/status |
/status/indexes | /observe/indexes |
/status/hints | /observe/hints |
/status/signatures | /observe/signatures |
/healthy | /observe/health/ready (JSON) or /readyz (plain text) |
/ (metrics) | /metrics |
/-/* legacy monitoring routes | Removed |
Note: /observe now serves interactive OpenAPI documentation for the Observe endpoints. The status JSON moved to /observe/status.
Kubernetes probes
Section titled “Kubernetes probes”Use the plain-text aliases for Kubernetes. Readiness means the pod can safely receive traffic; scripts that need full initial ingest and index completion should poll /observe/convergence.
startupProbe: httpGet: path: /startedz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 90livenessProbe: httpGet: path: /livez port: 8080 periodSeconds: 10 failureThreshold: 3readinessProbe: httpGet: path: /readyz port: 8080 periodSeconds: 5 failureThreshold: 1 successThreshold: 1Migration window
Section titled “Migration window”The first startup after a v3 database migration includes a one-time data preparation step. This step runs automatically and is idempotent — if the process is interrupted, the next startup picks up where it left off. No manual action is needed.
The step scales with the number of stored entries and values. On a small or freshly seeded directory it completes quickly. On a large directory — one holding many millions of stored values — it may take substantially longer than a blank-database migration. Plan a maintenance window accordingly.
Ensure no statement or migration timeout is short enough to abort this step mid-run. The Kubernetes startupProbe configuration above uses a high failureThreshold (ninety ten-second periods, giving fifteen minutes before the probe gives up) precisely to accommodate this window. If your deployment enforces a shorter startup deadline at the orchestration or database level, raise it before upgrading a large directory.
Once the step completes, subsequent restarts are unaffected — the preparation is not repeated.
Hints endpoints
Section titled “Hints endpoints”The monitoring.hints.endpoints.* configuration is no longer supported. Hints endpoints use fixed paths under /observe/*.
What to do
Section titled “What to do”- If you used custom paths, update your tooling to the fixed endpoints:
/observe/hints/observe/signatures
- If you don’t use Observe recommendations, disable them explicitly:
monitoring.hints.enabled = falseSee the Monitoring Configuration Reference for the full hints configuration.
Metrics names
Section titled “Metrics names”Metric names are now standardized to the scribe.* namespace (Prometheus auto-converts dotted names to snake_case scribe_*).
Update Prometheus queries and Grafana dashboards to use the new names. If you depend on specific label keys/values, verify them against the Telemetry Reference.
Metric name mapping
Section titled “Metric name mapping”| v2.x Metric | v3.x Metric | Notes |
|---|---|---|
scribe_directory_queries_pressure | scribe_query_permit_pressure | Same semantics |
scribe_directory_pipeline_duration_seconds{stage,outcome} | scribe_query_stage_duration_seconds{phase,result} | Label normalization |
scribe_directory_query_time_seconds{op,result} | scribe_channel_request_duration_seconds{channel,op,result} | Front door metric |
scribe_tasks_pressure{entryType} | scribe_ingest_task_pressure{entry_type} | Label key normalization |
scribe_transcription_queue_pressure{entryType} | scribe_ingest_queue_pressure{entry_type} | Label key normalization |
scribe_events_count_total{entryType,event,op,target} | scribe_ingest_events_written_total{entry_type,event_type} | Collapsed labels |
scribe_service_transition_seconds{service,type} | scribe_service_restarts_total{service} + scribe_service_up{service} | Simplified |
scribe_hints_query_signature_*{signature} | Removed | Use /observe/signatures |
scribe_directory_query_*{attribute} | Debug-only or removed | Use /observe/stats/* |
scribe_query_sql_chars, scribe_query_sql_params | Removed | Trace events instead |
Endpoint mapping
Section titled “Endpoint mapping”| v2.x Path | v3.x Path |
|---|---|
/status | /observe/status |
/status/indexes | /observe/indexes |
/status/hints | /observe/hints |
/status/signatures | /observe/signatures |
/status/stats/* | /observe/stats/* |
Label key mapping
Section titled “Label key mapping”| v2.x Label | v3.x Label |
|---|---|
entryType | entry_type |
protocol | channel |
outcome | result |
service (with wrappers) | service (normalized, no wrappers/ports) |
Result value mapping
Section titled “Result value mapping”| v2.x Value | v3.x Value |
|---|---|
success | ok |
error, failure, deadline, etc. | See Error Handling for the full list of result kinds |
Virtual attributes
Section titled “Virtual attributes”Virtual attribute templates use new placeholder names.
What changed
Section titled “What changed”{{current.*}}→{{self.*}}
Migration
Section titled “Migration”# Oldfilter = "(member={{current.entryDN}})"
# Newfilter = "(member={{self.entryDN}})"If legacy syntax is detected, IdentityScribe fails fast on startup with a clear, actionable error.
Query scoping
Section titled “Query scoping”Broad searches that could span multiple entry types now require an explicit type constraint. Ambiguous queries are rejected early with a clear error.
What to do
Section titled “What to do”- Add a type constraint such as
entryType=<type>or an identifying condition likeobjectClass=...to broad queries. - If you rely on wildcard bases or very broad filters, test queries in staging and update filters/config accordingly.
Error codes
Section titled “Error codes”Error codes are now standardized to SCREAMING_SNAKE_CASE. If your clients match on error codes, you must update them.
See Error Handling for the stable error contract and troubleshooting workflow (including which IDs/headers to include when opening support tickets).
GraphQL type naming and storage-name hardening
Section titled “GraphQL type naming and storage-name hardening”IdentityScribe now treats similar type and attribute spellings consistently across GraphQL and storage naming. The upgrade process includes automatic hardening and clear startup errors when naming collisions are detected.
What changed
Section titled “What changed”- Predictable GraphQL names — Entry types with hyphens, underscores, or mixed case (for example
organizational-unit,organizational_unit,OrganizationalUnit) resolve to the same GraphQL type and field names. Invalid leading characters (for example digits) get an underscore prefix. - Safer storage-name handling — On startup, IdentityScribe verifies that generated storage names map to one expected type or attribute. If a name is already associated with a different value, startup stops before making further changes.
- Automatic legacy repair — Upgrade automatically repairs known legacy storage names when there is no conflict. The repair is idempotent.
- Early collision checks — Startup now fails before applying storage changes when two entry-type spellings or two observed attributes would normalize to the same target name.
What to do
Section titled “What to do”For most upgrades, no manual action is required. The upgrade steps run automatically.
If startup fails with a naming-collision error:
- Entry type collision — Use one consistent spelling for each entry type across all transcribes, for example
inetOrgPersoneverywhere instead of mixinginetOrgPersonandinetorgperson. - Observed attribute collision — Use distinct catalog attribute names. Hyphen, underscore, and case differences can collide after normalization, so keep one canonical form per attribute.
- Existing storage-name conflict — Do not rename or remove database objects manually. Preserve the failed upgrade logs and database backup, then contact support or run the repair procedure provided for your environment.
Verify
Section titled “Verify”After upgrade:
- Confirm service starts and upgrade checks complete without errors.
- Confirm GraphQL exposes the expected normalized names (GraphiQL or introspection).
- Run
curl -fsS http://localhost:8080/readyzandcurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor. - If the upgrade process needs strict initial ingest and index completion, run
curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/convergence.
REST/OpenAPI naming hardening
Section titled “REST/OpenAPI naming hardening”REST and OpenAPI now apply strict startup validation for type-derived names.
What changed
Section titled “What changed”- Inflector collision checks — startup fails when two entry types would produce the same REST path or OpenAPI
operationIdafter singular/plural normalization. - Strict OpenAPI component naming — generated schema component names are sanitized to conservative identifiers (
^[A-Za-z][A-Za-z0-9_]*$) for broad code-generator compatibility. - Fail-fast on sanitized collisions — if two types sanitize to the same component key, startup stops with a clear collision error.
What to do
Section titled “What to do”- Audit transcribe type names for singular/plural ambiguity (
uservsusers, etc.). - If startup reports collisions, rename one side to a distinct canonical type name and redeploy.
- Regenerate API clients from
/api.jsonor/api.yamlafter renames.
After upgrade: quick verification
Section titled “After upgrade: quick verification”Once the service is running, a quick “is it healthy?” sweep:
curl -fsS http://localhost:8080/readyzcurl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/convergence | jqcurl -fsS http://localhost:8080/metrics | headcurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor | jqcurl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels | jqThe Observe calls above require an operator session or accepted credential unless the deployment explicitly opts out with monitoring.observe.auth.enabled=false for a trusted local environment.
Related
Section titled “Related”- Deployment — Installation and platform-specific setup
- Configuration — HOCON, env vars, inheritance patterns
- Health and Monitoring — Health checks and dashboards
- Signals — Golden signals and error classification
- PromQL Recipes — Alert queries and dashboards