Skip to content

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).

This section covers upgrades from any v2 release to v3.0.

  • Update environment variables to the unified SCRIBE_* prefix.
  • Migrate renamed config paths (monitoring.telemetry.*monitoring.*, hints.*monitoring.hints.*).
  • Migrate indices configuration from ldap.indices and transcribes.*.ldap.indices to 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 set monitoring.observe.auth.enabled=false only 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/readyz
  • curl -fsS http://localhost:8080/metrics | head
  • curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor | jq
  • curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/config | jq

Some configuration paths were reorganized for consistency. Old paths are ignored — IdentityScribe logs a deprecation warning at startup when they are still present.

  • monitoring.telemetry.*monitoring.*
  • hints.*monitoring.hints.*
# Old
monitoring.telemetry.traces.enabled = true
hints.enabled = true
# New
monitoring.traces.enabled = true
monitoring.hints.enabled = true
  • Start IdentityScribe and check the resolved config at /observe/config (secrets are redacted).
  • If you rely on hints endpoints, confirm /observe/hints and /observe/signatures are reachable (or explicitly disabled).

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.

Old PathNew Path
ldap.indices.*indices.* (root level)
transcribes.<type>.ldap.indices.*transcribes.<type>.indices.*
Old Environment VariableNew Environment Variable
SCRIBE_LDAP_INDICES(removed - was unused)
(none)SCRIBE_INDICES_PARTIAL_MATCH
(none)SCRIBE_INDICES_VALUE_MATCH
(none)SCRIBE_INDICES_SORTABLE
# Old
ldap {
indices {
partial-match = "cn, sn"
}
}
transcribes {
user {
ldap {
base = "ou=users,o=data"
indices {
value-match = "uid, mail"
}
}
}
}
# New
indices {
partial-match = "cn, sn"
}
transcribes {
user {
ldap {
base = "ou=users,o=data"
}
indices {
value-match = "uid, mail"
}
}
}
  • Start IdentityScribe and check the resolved config at /observe/config.
  • Verify that /observe/indexes shows the expected index configuration for each transcribe.

Environment variable prefixes are standardized. If you still use the old prefixes, your settings will not be applied.

  • IDENTITY_SCRIBE_*SCRIBE_*
  • SCRIBE_TELEMETRY_*SCRIBE_*
Terminal window
# Old
IDENTITY_SCRIBE_READONLY=true
SCRIBE_TELEMETRY_TRACES_ENABLED=true
# New
SCRIBE_READONLY=true
SCRIBE_TRACES_ENABLED=true

See the Configuration guide for naming conventions and examples.

Logger configuration has been consolidated to five canonical loggers with standardized environment variable overrides. Old logger keys are removed.

OldNew
log.EventStore / log.Scribelog.Ingest
log.Settingslog.Config
log.LDAP, log.AsyncSearch, log.TaskExecutorRemoved (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.

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.

# Everything on one port
http {
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"

Several operational endpoints were renamed and legacy routes were removed. No aliases are provided, so probes and dashboards must be updated.

OldNew
/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 routesRemoved

Note: /observe now serves interactive OpenAPI documentation for the Observe endpoints. The status JSON moved to /observe/status.

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: 90
livenessProbe:
httpGet:
path: /livez
port: 8080
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
failureThreshold: 1
successThreshold: 1

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.

The monitoring.hints.endpoints.* configuration is no longer supported. Hints endpoints use fixed paths under /observe/*.

  • 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 = false

See the Monitoring Configuration Reference for the full hints configuration.

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.

v2.x Metricv3.x MetricNotes
scribe_directory_queries_pressurescribe_query_permit_pressureSame 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}RemovedUse /observe/signatures
scribe_directory_query_*{attribute}Debug-only or removedUse /observe/stats/*
scribe_query_sql_chars, scribe_query_sql_paramsRemovedTrace events instead
v2.x Pathv3.x Path
/status/observe/status
/status/indexes/observe/indexes
/status/hints/observe/hints
/status/signatures/observe/signatures
/status/stats/*/observe/stats/*
v2.x Labelv3.x Label
entryTypeentry_type
protocolchannel
outcomeresult
service (with wrappers)service (normalized, no wrappers/ports)
v2.x Valuev3.x Value
successok
error, failure, deadline, etc.See Error Handling for the full list of result kinds

Virtual attribute templates use new placeholder names.

  • {{current.*}}{{self.*}}
# Old
filter = "(member={{current.entryDN}})"
# New
filter = "(member={{self.entryDN}})"

If legacy syntax is detected, IdentityScribe fails fast on startup with a clear, actionable error.

Broad searches that could span multiple entry types now require an explicit type constraint. Ambiguous queries are rejected early with a clear error.

  • Add a type constraint such as entryType=<type> or an identifying condition like objectClass=... to broad queries.
  • If you rely on wildcard bases or very broad filters, test queries in staging and update filters/config accordingly.

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.

  • 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.

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 inetOrgPerson everywhere instead of mixing inetOrgPerson and inetorgperson.
  • 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.

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/readyz and curl -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 and OpenAPI now apply strict startup validation for type-derived names.

  • Inflector collision checks — startup fails when two entry types would produce the same REST path or OpenAPI operationId after 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.
  • Audit transcribe type names for singular/plural ambiguity (user vs users, etc.).
  • If startup reports collisions, rename one side to a distinct canonical type name and redeploy.
  • Regenerate API clients from /api.json or /api.yaml after renames.

Once the service is running, a quick “is it healthy?” sweep:

Terminal window
curl -fsS http://localhost:8080/readyz
curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/convergence | jq
curl -fsS http://localhost:8080/metrics | head
curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor | jq
curl -fsS -H "Authorization: Bearer <token>" http://localhost:8080/observe/channels | jq

The 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.