> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/installation/kubernetes/kubernetes-upgrade/release-specific-steps.md).

# Release-specific steps

Releases that need more than the standard redeploy — a database to provision before you start, a one-shot migration flag, an Oracle grant to make, or Groovy imports to update.

## What this is

Most upgrades need nothing beyond the procedure on the [minor](/installation/kubernetes/kubernetes-upgrade/minor-upgrade.md) or [major](/installation/kubernetes/kubernetes-upgrade/major-upgrade.md) page. Some releases add a database that must exist before you start — the service itself deploys automatically as part of the standard redeploy, but its database does not create itself.

**Work out which releases you are passing through, then open the tab for each of them and take its steps.** Passing through counts even if you are not stopping there: going from 2026.4.2 straight to 2026.8.2 crosses `pbac` and `privacy`, and needs both.

## The databases each release adds

4.2.1.x ran on two databases: `openiam` and `activiti`. By 2026.10.1 there are nine.

<table><thead><tr><th width="110">Release</th><th width="151">Database</th><th>Service</th><th>Carries data forward?</th></tr></thead><tbody><tr><td><strong>4.2.2</strong></td><td><code>groovy</code></td><td><code>groovy-manager</code></td><td>Yes — see <a href="/installation/kubernetes/kubernetes-upgrade/after-a-major-upgrade.md"><em>After a major upgrade</em></a></td></tr><tr><td><strong>2026.3.1</strong></td><td><code>auditlog</code></td><td><code>audit-log-manager</code></td><td>—</td></tr><tr><td><strong>2026.5.2</strong></td><td><code>batchtasks</code></td><td><code>batch-task-manager</code></td><td>Yes, on a one-shot flag</td></tr><tr><td><strong>2026.6.1</strong></td><td><code>certification</code></td><td><code>certification-manager</code></td><td>Yes, on a one-shot flag</td></tr><tr><td><strong>2026.7.1</strong></td><td><code>pbac</code></td><td><code>pbac-manager</code></td><td>—</td></tr><tr><td><strong>2026.8.1</strong></td><td><code>privacy</code></td><td><code>privacy-manager</code></td><td>New capability, no legacy data</td></tr><tr><td><strong>2026.10.1</strong></td><td><code>reporting</code></td><td><code>report-manager</code></td><td>New capability, no legacy data — but reads the other databases, see below</td></tr></tbody></table>

Nothing new arrives with 2026.8.2 — it extends Privacy Management rather than adding another database. 2026.9.2 adds no database either, but it does need a step of its own: the RabbitMQ broker encryption key, below, which is the one release-specific step that requires stopping the platform. 2026.10.1 adds `reporting`, and with it the one grant an Oracle installation has to make by hand (below). A [major upgrade](/installation/kubernetes/kubernetes-upgrade/major-upgrade.md) from 4.2.1.x to 2026.10.1 crosses **all seven** rows above.

{% hint style="danger" %}
**Each new database is one more thing your backup has to cover.** A backup routine written against 4.2.1.x captures two databases out of nine. It will still complete, still report success, and no longer be a rollback position.
{% endhint %}

## Provisioning a new database

On Kubernetes the configmap chart carries the database creation, the users and the grants for every schema the release knows about, so an in-cluster database is provisioned for you when the chart is current. **An external or managed database is not** — create the database there yourself before upgrading. One grant is outside the chart's reach on every Oracle database, in-cluster or not: the read access of the reporting user, under 2026.10.1 below.

Each service reads its database and schema names from its own block of the Helm `values.yaml` and injects them as environment variables. For `certification-manager`, the block and the variables it produces — `JDBC_CERTIFICATION_DATABASE_NAME` and `JDBC_CERTIFICATION_SCHEMA_NAME` — are:

{% code overflow="wrap" %}

```yaml
openiam:
  database:
    jdbc:
      certification:
        host: ""
        port: ""
        databaseName: ""
        schemaName: ""
```

{% endcode %}

The other services follow the same shape under their own key. Credentials come from `openiam.vault.secrets.jdbc.<name>.username` and `.password`, which the configmap chart's secret-generation job reads when it builds the deployment's secrets.

**Deploying each service needs nothing beyond the standard redeploy.** All four extracted services — `batch-task-manager`, `certification-manager`, `pbac-manager` and `privacy-manager` — are ordinary `helm_release` resources in the Terraform module, the same as every other component. Once you have checked out the release's tag, `./setup.sh` / `terraform apply` creates them along with everything else. There is no separate chart to deploy and no ArgoCD application to point at one.

Two of the seven then need a one-off data migration, below — that flag is a separate, manual step, unrelated to deployment — and the newest needs a grant on Oracle.

## Steps by release

Each tab holds the steps for one release. **Open every tab from the release after the one you are on through to your target** — the steps are cumulative, not alternatives.

{% tabs %}
{% tab title="2026.5.2" %}

### The batch task manager

The batch task subsystem — schedulers, initialisers, the `BATCH_CONFIG` and `BATCH_SCHEDULE` tables, and the Groovy task runners — moved out of `openiam-esb` into a standalone service. It scales independently, and a batch job failing no longer affects API traffic.

On its first start the service applies the `batchtasks` schema, then copies existing `openiam.BATCH_CONFIG` rows across. The runner short-circuits once the target table has rows, so restarts are no-ops.

**`BATCH_SCHEDULE` is deliberately not migrated.** The scheduler rebuilds future runs from each task's cron expression, and historical schedule rows have no operational value after cutover.
{% endtab %}

{% tab title="2026.6.1" %}

### The certification manager

Certification — the configurations, campaigns, review items, and the campaign compute — moved out of `openiam-esb` into a standalone service with its own database. The Activiti review workflow stays in the `workflow` service; its certification steps delegate the compute over RabbitMQ.

To carry existing certification data forward, start it once with:

{% code overflow="wrap" %}

```properties
org.openiam.certification.migrate-from-openiam=true
```

{% endcode %}

On that first start the migrations create the `certification` tables, then the runner copies the data out of `openiam` **preserving primary keys** — in-flight Activiti workflows hold reviewer and campaign ids as process variables, so the ids must not change. `ACCESS_CERTIFICATION*` becomes `CERTIFICATION*`, and the `CERTIFICATION`-typed rows of `APPROVER_ASSOC` and `ESCALATION` become `CERTIFICATION_REVIEWER` and `CERTIFICATION_REVIEWER_ESCALATION`.

Check the migrated row counts, then set the flag back to `false`. Leaving it on is safe — it is a no-op once the target tables are populated.
{% endtab %}

{% tab title="2026.7.1 and 2026.8.1" %}

### PBAC and Privacy

Both arrive as their own service and database, and both are new capability rather than an extraction, so there is no legacy data to carry forward — no migration flag applies to either.

* **`pbac` (2026.7.1)** backs the policy-based access control console — policies with shadow, percentage and full rollout modes, a simulation runner, and a decision log. It is a separately deployable microservice with its own evaluation endpoint, scaled independently of the ESB. Chart: `pbac-manager`.
* **`privacy` (2026.8.1)** backs Privacy Management — the consent ledger, the purpose and lawful authority registry, the privacy event backbone, and the Privacy Administration Console. Chart: `privacy-manager`. No seeding step is required before the console is usable.
  {% endtab %}

{% tab title="2026.9.2" %}

### The RabbitMQ broker encryption key

The key the services encrypt RabbitMQ message bodies with used to ship as a value in `rabbitmq.properties`, so every installation ran on the same key and anyone holding a copy of the distribution could read traffic off a broker. It is now a vault secret the deployment supplies, and vault bootstrap refuses to start without one.

**This is the one release-specific step that needs the platform stopped.** Every service resolves the same key, so a message written under the old key cannot be read under the new one. Anything still sitting on the broker when the key changes fails to decrypt rather than being processed.

{% hint style="danger" %}
**Messages queued under the old key are not recoverable.** Let in-flight work drain before you stop the services, rather than discarding it afterwards.
{% endhint %}

{% stepper %}
{% step %}
Scale the OpenIAM services to zero, so nothing is publishing to or consuming from the broker. Leave the infrastructure — the database, Redis, OpenSearch, RabbitMQ and Vault — running, because the next two steps need the broker and Vault up.
{% endstep %}

{% step %}
Purge the OpenIAM queues. With no service running, everything left on them was encrypted under the old key and cannot be read back. List the vhosts and purge each queue:

{% code overflow="wrap" %}

```bash
kubectl exec -it <rabbitmq-pod> -- rabbitmqctl list_vhosts
kubectl exec -it <rabbitmq-pod> -- rabbitmqctl list_queues -p <vhost> name messages
kubectl exec -it <rabbitmq-pod> -- rabbitmqctl purge_queue <queue> -p <vhost>
```

{% endcode %}

The management console does the same thing per queue if you would rather see the depths before you clear them.
{% endstep %}

{% step %}
Set the key, then redeploy. The configmap chart requires it and the deployment stops with a message naming the setting if it is missing:

{% code overflow="wrap" %}

```yaml
openiam:
  vault:
    secrets:
      mq:
        brokerEncryptionKey: ""   # 32 characters
```

{% endcode %}

Generate one with `openssl rand -hex 16`. Terraform deployments set `rabbitmq.brokerEncryptionKey` in the variables file instead, which is validated at plan time. An ArgoCD deployment that uses the secret-generation job needs nothing — the job generates a key per namespace.
{% endstep %}
{% endstepper %}

The chart carries the value into the `secrets` object as `mqBrokerEncryptionKey`, the vault bootstrap job reads it as `SECRET_ORG_OPENIAM_MQ_BROKER_ENCRYPTION_KEY`, and vault stores it under `vault.secret.org.openiam.mq.broker.encryption.key` — which is what every service resolves at startup. Changing it later means repeating all three steps.

### The RabbitMQ delayed-message plugin is retired

OpenIAM used a community plugin, `rabbitmq_delayed_message_exchange`, to hold a message back before delivering it. The plugin has no build past RabbitMQ 4.2, so keeping it meant the broker could never be upgraded again. It is gone from the images, and the delay now comes from RabbitMQ itself: the message is published with an expiry onto a queue nothing consumes, and when it expires the broker forwards it to the queue that does the work.

**An exchange cannot change type once it exists.** Two exchanges on your broker — `AM_EXCHANGE` on the `openiam_am` vhost and `ELASTIC_SEARCH_EXCHANGE` on `openiam_common` — were created as delayed exchanges. The new services ask for ordinary ones, the broker refuses the mismatch, and the pod crash-loops with `PRECONDITION_FAILED`. Deleting the two exchanges is the whole fix; the services build them again, with their queues and bindings, the first time they start.

{% hint style="warning" %}
**`AM_EXCHANGE` carries the entire auth-manager vhost.** Deleting it takes every queue bound to it with it, so this belongs in a window where the services are down — which an upgrade already is.
{% endhint %}

**The RabbitMQ chart does this for you.** It carries a `pre-upgrade` hook job, `rabbitmq-migrate-delayed-exchanges`, which runs before the broker's StatefulSet is rolled — while the old pod is still up and still has the plugin loaded, which is the only point at which the exchanges can be read and deleted cleanly. ArgoCD runs the same job as a `PreSync` hook, so a synced deployment needs nothing either. A fresh install has nothing to migrate and the job exits immediately.

Watch it during the upgrade:

{% code overflow="wrap" %}

```bash
kubectl logs -l job-name=rabbitmq-migrate-delayed-exchanges --tail=50
```

{% endcode %}

To see what it would do without deleting anything, set `delayedExchangeMigration.dryRun` and run the upgrade — the job reports its findings and changes nothing:

{% code overflow="wrap" %}

```yaml
delayedExchangeMigration:
  enabled: true
  dryRun: true
  waitSeconds: 120
```

{% endcode %}

Re-running the sync or the upgrade is safe. The job deletes an exchange only while that exchange is still a delayed one, so once the services have rebuilt them it finds nothing to do — which is why it does not need to be run exactly once, and why leaving it enabled indefinitely costs nothing.

{% hint style="info" %}
**`waitSeconds` has to stay well inside Helm's hook timeout.** The job is bounded at 280 seconds so it fails within Helm's default `--timeout` of five minutes. If you raise `waitSeconds` because your broker is slow to answer, raise `delayedExchangeMigration.activeDeadlineSeconds` and `--timeout` with it, or Helm abandons the release first and reports its own timeout instead of the real failure.
{% endhint %}

#### Checking it worked

No vhost should have an exchange of type `x-delayed-message` left:

{% code overflow="wrap" %}

```bash
kubectl exec -it <rabbitmq-pod> -- bash -c \
  'for v in $(rabbitmqctl list_vhosts --quiet); do rabbitmqctl list_exchanges -p "$v" --quiet name type | grep x-delayed-message && echo "  ^ in $v"; done'
```

{% endcode %}

That should print nothing. You can also confirm the replacement is in place — `openiam_audit` gains a queue called `AuditExportDispatchRetryQueue`, which is the holding queue the expiry mechanism uses:

{% code overflow="wrap" %}

```bash
kubectl exec -it <rabbitmq-pod> -- rabbitmqctl list_queues -p openiam_audit --quiet name | grep AuditExportDispatchRetryQueue
```

{% endcode %}

If a pod still crash-loops with `PRECONDITION_FAILED` naming an exchange, that exchange was not deleted — check the job's logs above, then re-run the upgrade.

{% hint style="info" %}
**Do not add `rabbitmq_delayed_message_exchange` back to `extraPlugins`.** The images no longer carry it, nothing declares an exchange that needs it, and naming it pins the broker to a version that can no longer be upgraded. Installs driven by `setup-no-tf.sh` or the OpenShift scripts no longer pass it.
{% endhint %}

{% hint style="info" %}
**Failed audit exports are retried properly now.** The retry interval was silently ignored before, because the audit exchange was never a delayed one — a failing export burned through its retries in a tight loop instead of pausing between them. `org.openiam.audit.export.retry.interval` now takes effect.
{% endhint %}
{% endtab %}

{% tab title="2026.10.1" %}

### The IdP stops publishing signing secrets

No database and no stop are needed, but three behaviours of the OAuth 2.0 identity provider (IdP) change, and an integration that depended on the old behaviour needs attention **before** you upgrade:

* **HS-signed clients publish an empty JWKS.** Before 2026.10.1 the JWKS URL of an `HS256` or `HS512` client returned the shared secret itself. Any relying party that fetched the secret from there must receive it out of band, or the client must move to `RS256` or `RS512` and the relying party must re-read the key set after the upgrade.
* **RSA clients publish only the public key.** The private exponent is no longer in the key set or in the IdP log. If an earlier release ever exposed it, regenerate the client's key pair as part of the upgrade.
* **`jwks_uri` in the discovery document is now correct.** Relying parties configured with the literal JWKS URL keep working; ones that were patched to strip the doubled issuer name can drop the patch.

One new property, `org.openiam.oauth.token.info.public`, ships `true`, so `/idp/oauth2/token/info` behaves exactly as before. Set it to `false` once every caller of that endpoint can authenticate as an OAuth client — see [OAuth endpoints, grants, scopes and signing](/administration-guide/federation/oauth-and-oidc/oauth-and-oidc-reference.md).

### The report manager, and the read grant Oracle cannot make for you

The browser-based report builder arrives as a standalone service, `report-manager`, with its own `reporting` database for report definitions, schedules and run history. Provision `reporting` the same way as the databases above — its block is `openiam.database.jdbc.reporting` and its credentials are `openiam.vault.secrets.jdbc.reporting.username` and `.password`. The service also mounts the shared `openiam-pvc-report-artifact-storage` claim, a `ReadWriteMany` volume from the `openiam-pvc` chart where report files are written and from which `email-manager` and the Webconsole read them.

The `report-manager` Helm chart ships with the release. Add it to your Terraform module or ArgoCD applications the same way `batch-task-manager` is deployed.

{% hint style="warning" %}
**`report-manager` requires a ReadWriteMany persistent volume.** The chart assumes one shared PVC across replicas for the report artifact store. Provision a `ReadWriteMany` PV before deploying; the pod stays in `Pending` without one. (OE-5320)
{% endhint %}

{% hint style="info" %}
**The `reporting` schema references rows in both `openiam` and `batchtasks`** (without foreign keys). Both those databases must exist and be populated before `report-manager` starts.
{% endhint %}

Two things set this release apart.

**The service reads every other database.** Reports are built over users, logins, certification items, policies, batch tasks and workflow requests, so `report-manager` also connects to `openiam`, `certification`, `privacy`, `pbac`, `batchtasks` and `activiti` — as its own read-only user, `REPORTING` unless you named it differently in `openiam.vault.secrets.jdbc.reporting.username`. It never writes to them.

**On Oracle, you grant that read access yourself.** MariaDB, PostgreSQL and SQL Server have a database-wide read grant, and the configmap chart's initialization makes it. Oracle has neither a schema-wide grant nor default privileges — `SELECT` is granted table by table, and the tables only exist once the Flyway job has migrated each schema — so neither the chart nor the Flyway job can grant it for you. Run the block below once after the Flyway job of 2026.10.1 has completed, as a database administrator (DBA).

{% hint style="warning" %}
**Without the grant, every relational report fails.** The pod starts, the catalog and the report designer work, but a run ends **Failed** with error code `QUERY_FAILED` and `ORA-00942: table or view does not exist`, and the `report-manager` pod log shows the same error for the `report-source-*` connection pools. Reports over OpenSearch data — the audit log, orphan accounts, provisioning results — keep working, which is how to tell this apart from a connectivity problem.
{% endhint %}

{% stepper %}
{% step %}
Confirm the names you are granting between. The reporting user is `openiam.vault.secrets.jdbc.reporting.username` in the configmap chart's values (Terraform deployments set it in the database connections map); the schema owners are the users the other services connect as, `openiam.vault.secrets.jdbc.<name>.username` for `certification`, `privacy`, `pbac`, `batchtasks` and `activiti`, and the core `openiam` user — typically `IAMUSER`, `CERTIFICATION`, `PRIVACY`, `PBAC`, `BATCHTASKS` and `ACTIVITI`.

Then, from any host with an Oracle client that reaches the database (or a `sqlplus` session inside the database pod when it runs in the cluster), check that the reporting user exists and is open — `SELECT username, account_status FROM dba_users WHERE username = 'REPORTING';` as `SYSDBA`. If that returns no row, create the user with the privileges the initialization gives it, using the password you set as `openiam.vault.secrets.jdbc.reporting.password`:

{% code overflow="wrap" %}

```sql
CREATE USER REPORTING IDENTIFIED BY <password>;
GRANT RESOURCE TO REPORTING;
GRANT CONNECT TO REPORTING;
GRANT CREATE VIEW TO REPORTING;
GRANT CREATE SESSION TO REPORTING;
GRANT UNLIMITED TABLESPACE TO REPORTING;
```

{% endcode %}
{% endstep %}

{% step %}
Grant `SELECT` on every table and view of the six schemas. Connect as `SYS` (or any user holding `GRANT ANY OBJECT PRIVILEGE`), edit the two lists at the top if your names differ, and run the block. It skips objects that are already granted, reports anything it could not grant, and is safe to run as often as you like:

{% code overflow="wrap" expandable="true" %}

```sql
SET SERVEROUTPUT ON

DECLARE
    -- the reporting user: openiam.vault.secrets.jdbc.reporting.username
    c_grantee CONSTANT VARCHAR2(128) := 'REPORTING';
    -- the owners of the six OpenIAM databases report-manager reads
    TYPE t_owners IS TABLE OF VARCHAR2(128);
    v_owners  t_owners := t_owners('IAMUSER', 'CERTIFICATION', 'PRIVACY', 'PBAC', 'BATCHTASKS', 'ACTIVITI');
    v_granted PLS_INTEGER := 0;
    v_skipped PLS_INTEGER := 0;
BEGIN
    FOR i IN 1 .. v_owners.COUNT LOOP
        FOR obj IN (
            SELECT owner, table_name AS object_name
              FROM dba_tables
             WHERE owner = v_owners(i) AND table_name NOT LIKE 'BIN$%'
            UNION
            SELECT owner, view_name
              FROM dba_views
             WHERE owner = v_owners(i)
            MINUS
            SELECT owner, table_name
              FROM dba_tab_privs
             WHERE owner = v_owners(i) AND grantee = c_grantee AND privilege = 'SELECT'
        ) LOOP
            BEGIN
                EXECUTE IMMEDIATE 'GRANT SELECT ON "' || obj.owner || '"."' || obj.object_name
                                  || '" TO "' || c_grantee || '"';
                v_granted := v_granted + 1;
            EXCEPTION
                WHEN OTHERS THEN
                    v_skipped := v_skipped + 1;
                    DBMS_OUTPUT.PUT_LINE('Skipped ' || obj.owner || '.' || obj.object_name || ': ' || SQLERRM);
            END;
        END LOOP;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('Granted SELECT on ' || v_granted || ' object(s) to ' || c_grantee
                         || ', skipped ' || v_skipped || '.');
END;
/
```

{% endcode %}

A schema that does not exist on your installation — `PRIVACY` on a deployment without the privacy module, say — simply contributes no objects; leave it in the list or take it out, the result is the same.
{% endstep %}

{% step %}
Verify as the reporting user, then from the product. Each query must return a count, not `ORA-00942`:

{% code overflow="wrap" %}

```sql
SELECT COUNT(*) FROM IAMUSER.USERS;
SELECT COUNT(*) FROM CERTIFICATION.CAMPAIGN;
SELECT COUNT(*) FROM PBAC.PBAC_POLICY;
SELECT COUNT(*) FROM BATCHTASKS.BATCH_CONFIG;
SELECT COUNT(*) FROM ACTIVITI.ACT_HI_PROCINST;
```

{% endcode %}

Then open the report builder in the Webconsole (`/webconsole-v2/audit/reports`) and run a shipped system report that reads relational data, such as **Users** — it should complete, with no pod restart needed, since the grants take effect on the next query. To review what was granted, as a DBA: `SELECT owner, COUNT(*) FROM dba_tab_privs WHERE grantee = 'REPORTING' AND privilege = 'SELECT' GROUP BY owner;`
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**Repeat the grant after every future upgrade whose Flyway job touches the schemas.** A grant covers the tables that exist when it is made; a later release that adds a table leaves reports over it failing with the same `ORA-00942` until you run the block again. It skips what is already granted, so re-running it costs nothing. And keep the user read-only — `SELECT` only, never ownership of or write privileges on the OpenIAM schemas.
{% endhint %}

### Proxy and cookie settings changed

**`OPENIAM_TrustedProxy`** is a new setting — a comma-separated list of IPs or CIDR ranges from which `X-Forwarded-For` and client-IP headers are trusted. It ships **empty (no proxy trusted)**. If OpenIAM sits behind a reverse proxy, load balancer or CDN, add that address here; otherwise audit logs and IP-based rules record the proxy's address instead of the real client. Set it in your Helm `values.yaml` or the relevant configmap. (OE-4325)

**`OPENIAM_CookieIntegrity`** is now `on` in the shipped reference configurations. Environments that explicitly set this value keep their current value; environments that never touched it switch from `off` to `on`. Check whether anything relies on cookies being unprotected before upgrading. (OE-5251)

### PBAC seed for user management

A new PBAC seed (`user-management-seed-role9`) grants the Super Security Admin role every action under `Openiam::UserManagement::Action`. Any custom role that previously served as a de-facto user administrator must be granted a matching narrower PBAC policy, or those users will be refused on the user management screens. (OE-4550)

### New `openiam-base` OCI Helm charts

The `openiam-base` charts (including the rproxy chart) are now published as OCI charts on the `openiam-base` repository. New Kubernetes installations should prefer these over the previous distribution. Existing deployments continue to work with the previous chart source. (OE-5299)

### SoD Groovy API changes

The SoD evaluator now **distinguishes pre-existing HARD violations from newly-introduced HARD violations** in its response. Any custom Groovy script that refuses a save on any HARD violation will now refuse legitimate saves — restrict the refusal to the newly-introduced set. (OE-5090)

SoD batch evaluation responses now include a **per-element status field**. Scripts that infer status from presence or absence of a result should switch to reading the explicit status field. (OE-5091)
{% endtab %}

{% tab title="2026.10.2" %}

### The IdP accepts Client ID Metadata Document clients

No database and no stop are needed. The OAuth 2.0 identity provider (IdP) gains a tenant-level metadata document, clients that register themselves from a Client ID Metadata Document (the way Claude and other Model Context Protocol clients identify themselves), the `resource` parameter and the `iss` response parameter. Four behaviours change for existing integrations; check them **before** you upgrade:

* **Public PKCE clients must use `S256`.** A client on `AUTHORIZATION_CODE_PKCE` with no client secret that sends `code_challenge_method=plain`, or no method at all, is refused. Fix the client, or set `org.openiam.oauth.pkce.plain.allowed=true` in `idp.ui.properties`.
* **Redirect parameters are URL-encoded.** A client that read `code`, `state` or `error` out of the redirect without decoding them now sees encoded values; a standard OAuth library is unaffected.
* **The `refresh_token` grant checks the client.** A refresh token presented by a client other than the one it was issued to, or presented a second time, is refused with `invalid_grant` instead of honoured.
* **Every authorization response carries `iss`.** A client that rejects parameters it does not expect on the redirect needs to accept it.

Three properties are worth a decision at upgrade time, all in `idp.ui.properties` under the deployment's `conf/properties` directory:

* `org.openiam.oauth.cimd.allowed.origins` ships as `https://claude.ai`, which lets Claude register itself as an OAuth client on a user's first sign-in. Set it to an empty value if you do not want that yet.
* `org.openiam.oauth.issuer` ships empty, so the issuer is derived from each request's host. Set it to `https://<your host>/idp/oauth2` when the IdP is reachable under more than one host name.
* `org.openiam.oauth.token.refresh.legacy.enabled` ships `true`, so the unauthenticated `/idp/oauth2/token/refresh` endpoint keeps answering. Set it to `false` once every client refreshes through `POST /idp/oauth2/token`; a later release defaults it to `false`.

Every property the release adds, with its shipped value, is listed in [OAuth endpoints, grants, scopes and signing](/administration-guide/federation/oauth-and-oidc/oauth-and-oidc-reference.md), and the procedure for letting an MCP client register itself is in [OAuth 2.0 and OpenID Connect](/administration-guide/federation/oauth-and-oidc.md).

### The MCP server

2026.10.2 adds the OpenIAM Model Context Protocol (MCP) server, which lets an AI assistant such as Claude answer questions from OpenIAM data on behalf of a signed-in user. It is optional; skip this section if you do not want it yet. No database and no stop are needed.

1. **Install the `mcp` chart.** It ships next to the `idp` chart, and the deployment's Terraform configuration does not install it in this release. Install it with Helm, reusing the IdP release's values, as shown in [Deploy the server](/administration-guide/federation/mcp-server.md#deploy-the-server). The chart runs the new `mcp` image as a cluster-internal service named `mcp` on port 8080.
2. **Use the 2026.10.2 nginx image.** The gateway sends the MCP patterns to the UI nginx, and the `openiam-nginx` image of 2026.10.2 forwards them to the `mcp` service. A customised nginx configuration needs the three locations listed in [Publish it through the Access Gateway](/administration-guide/federation/mcp-server.md#publish-it-through-the-access-gateway).
3. **Create the gateway patterns.** The four MCP patterns are seeded only on a new content provider. On every existing one, press **Create default URI patterns** in Webconsole, after exporting the provider if you customised its default patterns.
4. **Rebuild the authorization graph** once the ESB runs 2026.10.2, so users receive the MCP scopes the migration grants to their roles — see [Rebuild the authorization graph](/administration-guide/operations/rebuild-the-authorization-graph.md).

Each step, with the values to use and how to verify the result, is in [Connect AI assistants through the MCP server](/administration-guide/federation/mcp-server.md).

### MCP agents and tool-call policies

The second half of the MCP server in 2026.10.2 gives every MCP client an **AI Agent record** in the non-human identity inventory, lets an agent act with no person behind it, and puts every tool call through a policy-based access control (PBAC) decision. No stop is needed, and the migration runs with the release's others; nothing it adds is enforced until you switch it on. Three things change for an upgraded deployment:

* **The migration touches `openiam` and `pbac`.** `NHI_IDENTITY` gains the client binding, `NHI_DRIFT_FINDING` is new, `OAUTH_TOKEN` and `OAUTH_CODE` gain the audience, the consented scopes and the resource, the user type `NHI service account` is seeded, and the `pbac` database receives seven policies, `MCP-01` to `MCP-07`, active in shadow: they record what they would decide and change no decision until promoted. On SQL Server the binding is a filtered unique index, so a `sqlcmd` or `bcp` session that writes to `NHI_IDENTITY` needs `QUOTED_IDENTIFIER ON` (`sqlcmd -I`); JDBC, SSMS and the platform itself have it on.
* **Set `org.openiam.oauth.cimd.agent.owner` before the first Claude sign-in**, in `idp.ui.properties`, to the user ID of an administrator account. The IdP creates an AI Agent record for every client that registers itself and refuses to register one while the property is empty. A client registered before the upgrade receives its record on its next sign-in.
* **`client_credentials` tokens change subject.** A client bound to an AI Agent identity receives a token for the identity's service user, with its scopes filtered by that user's entitlements. A client that is not bound keeps the built-in `0001` subject only while `org.openiam.oauth.client_credentials.legacy.subject` is `true`, which 2026.10.2 ships; the next release ships it `false`, after which such a client is refused at the token endpoint. Find every client-credentials client you depend on and bind it before then.

Two properties are worth a decision at upgrade time:

* `org.openiam.mcp.agent.required` under `openiam.mcp.properties` in the `mcp` chart's values ships `false`, so MCP clients without a record keep working. Leave it for this release while the records are created, then set it to `true` so that an unregistered client is refused.
* `org.openiam.pbac.mcp.catalog.url` in the policy service's properties ships empty. Point it at the MCP server's catalog document, `http://mcp:8080/.well-known/mcp-catalog`, so policies can name tool groups; without it a policy can only name tools one by one.

Three more things are worth doing once the upgrade is through, none of them urgent:

* **Point Webconsole at the catalog too.** `org.openiam.mcp.catalog.url` in `webconsole.ui.properties` ships empty; the policy editor's autocomplete and the declared-tools picker offer no MCP vocabulary until it names the same catalog document.
* **Schedule the two new governance jobs.** The tool-drift job (`TOOL_DRIFT`) and the orphaned-sponsor job (`ORPHANED_AGENTS`) ship without a batch task; create one for each as described in [Schedule the jobs](/administration-guide/federation/mcp-server/mcp-agents.md#schedule-the-jobs). The sponsor job's mail template, `NHI_SPONSOR_INACTIVE`, is not seeded either.
* **Decide on approval.** `org.openiam.oauth.cimd.agent.approval` ships as `auto`. With `required`, a person's first sign-in through a new Claude client ends with `access_denied` until an administrator approves the agent record.

The record and the kill switch are in [Govern AI agents as non-human identities](/administration-guide/federation/mcp-server/mcp-agents.md); the modes, the no-policy rule and the order to promote the seven policies in are in [Decide MCP tool calls with policies](/administration-guide/federation/mcp-server/mcp-policies.md).
{% endtab %}
{% endtabs %}

## Custom Groovy scripts must be re-pointed

The extractions moved the base classes that customer scripts extend. **Scripts referencing the old packages will not compile until their imports are updated.** The scripts shipped under `conf/iamscripts/` were updated for you; anything you customised was not.

The package moves are the same on both platforms — the table is on [*Release-specific steps — RPM*](/installation/rpm/rpm-upgrade/release-specific-steps.md#custom-groovy-scripts-must-be-re-pointed).

## Related

* [Minor upgrade](/installation/kubernetes/kubernetes-upgrade/minor-upgrade.md) — the standard within-line redeploy
* [Major upgrade](/installation/kubernetes/kubernetes-upgrade/major-upgrade.md) — crossing to a different release line
* [After a major upgrade](/installation/kubernetes/kubernetes-upgrade/after-a-major-upgrade.md) — what to put right once the upgrade completes; most of it applies to both platforms

*Last reviewed 20 October 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/installation/kubernetes/kubernetes-upgrade/release-specific-steps.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
