> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading from 0.22.x to 0.23.x

> Redeclare RabbitMQ durable subscriber queues, then adopt namespaces on the surfaces your project uses.

<Warning>
  0.23 also removes engine-managed project workers and the `iii worker` / `worker::*` lifecycle
  surface. Complete [Move workers from config.yaml to
  Compose](./workers-to-compose) before starting an existing project.
</Warning>

0.23.x adds namespaces as a routing dimension. Workers that declare no namespace land in `default`
and behave exactly as before, so most projects upgrade with no code change. The one hard break is
durable queue naming on the RabbitMQ adapter. Apply the steps below that touch surfaces your project
uses.

## Step 1: Redeclare RabbitMQ durable subscriber queues

Durable subscriber queue names are now namespace-qualified, so two subscribers of the same topic and
function id in different namespaces get two queues instead of competing for one. After the upgrade,
a 0.23 subscriber declares and consumes a new namespace-qualified queue. The RabbitMQ adapter does
not rename, consume, or migrate the old queue. The old queue and its durable binding remain in
RabbitMQ until you delete the queue. Messages in it remain unread, and the binding can continue to
route copies of new events to it.

| Version | Subscriber queue                       | Dead-letter queue                    |
| ------- | -------------------------------------- | ------------------------------------ |
| 0.22.x  | `iii.{topic}.{function_id}.queue`      | `iii.{topic}.{function_id}.dlq`      |
| 0.23.x  | `iii.{topic}.{function_id}@{ns}.queue` | `iii.{topic}.{function_id}@{ns}.dlq` |

`{ns}` is the subscriber's namespace, `default` when it declared none. Any `@` or `\` inside a
topic, function id, or namespace is backslash-escaped before the join.

<Warning>
  Drain the old queues before upgrading. Messages left in a pre-0.23 queue are not migrated and no
  consumer will read them afterwards.
</Warning>

For each affected subscriber:

1. Stop publishing to the topic.
2. Let the existing consumers drain the old queue to empty.
3. Upgrade and restart. The adapter declares the new namespace-qualified queue on the next subscribe.
4. Delete the drained queue and its dead-letter queue from the broker.

## Step 2: Preserve queue storage before moving the worker

The queue implementation is no longer supplied by the engine. Before upgrading, stop publishing
and let 0.22.x drain active work, then move `iii-queue` to the standalone `queue` Compose package.
Keep the same `file_path` or broker settings under its `config_override`; changing the path creates
an empty store and leaves the old jobs behind.

Follow the queue row in [Move workers from config.yaml to
Compose](./workers-to-compose), including the configuration-id change from `iii-queue` to `queue`.
Back up the queue data before the first 0.23 start. iii does not rewrite or relocate it
automatically.

## Step 3: Tune the registration grace period, if needed

A connection gets its namespace from `engine::workers::register`. A client can send registration
messages before this call. The engine holds these messages until the namespace is known, or until
the grace period expires and the connection is set to `default`.

The default grace period is 5000 ms. Raise it if workers on slow links register but land in `default`
unexpectedly:

```yaml worker-compose.yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
engine:
  registration_namespace_grace_ms: 10000
  workers: {}
```

Set `III_NAMESPACE_GRACE_MS` in the engine process environment, not in a worker environment. It
applies to namespace resolution for all new worker connections and overrides
`registration_namespace_grace_ms`. A directly supervised engine keeps the same field at the top
level of `config.yaml`.

## Step 4: Check for `engine::*` function ids outside `default`

<Note>
  Most custom workers do not use the `engine::` prefix. Check this step because the new rule rejects
  an existing custom function that uses the prefix outside `default`.
</Note>

Every worker has a namespace. A worker uses `default` if you do not specify a namespace.
`engine::*` is reserved for engine infrastructure, which is registered in `default`. If a worker in
a non-default namespace registers an `engine::*` function id, the engine rejects that registration
with `FUNCTION_NAMESPACE_CONFLICT`. The connection stays open, and its other functions continue to
serve requests. Rename the function with your own `service::name` prefix.

The standalone queue worker registers its infrastructure functions in `default`. The rule does not
reject those functions. It applies to a custom worker connection that registers an `engine::*`
function in a non-default namespace.

## Step 5: Scope RBAC rules when adopting a namespace

Two RBAC surfaces changed, and both matter only for a worker that leaves `default`.

First, `expose_functions` rules on the `iii-worker-manager` RBAC listener are namespace-scoped. A
rule that names no namespace applies to `default` only, so it will not expose a function reached in
another namespace. When you move a worker into a namespace, scope its rules to match:

```yaml theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
expose_functions:
  - match: "orders::*"
    namespace: orders
```

Add one rule for each namespace the session reaches. See
[Namespace-scoped function rules](../creating-workers/worker-manager#namespace-scoped-function-rules)
for the matching syntax.

Second, `allowed_functions` on the auth result now applies in `default` only. It used to apply in
every namespace, so a grant issued for one tenant answered for every other tenant exposing the same
function id. If your auth function returns `allowed_functions` for a worker that runs in a
namespace, move those grants into `namespaces`, which the auth result now carries. See
[Scope a session to namespaces](../creating-workers/worker-manager#scope-a-session-to-namespaces).

`forbidden_functions` is unchanged and still applies in every namespace. An auth function that
returns no `namespaces` keeps the behaviour it had.

## Migration checklist

* [ ] Drain and delete pre-0.23 RabbitMQ subscriber and dead-letter queues (Step 1)
* [ ] Stop publishers and drain active `iii-queue` jobs before the upgrade (Step 2)
* [ ] Back up the queue data and preserve its `file_path` or broker settings (Step 2)
* [ ] Rename the queue configuration id from `iii-queue` to `queue` (Step 2)
* [ ] Raise `registration_namespace_grace_ms` if workers land in `default` unexpectedly (Step 3)
* [ ] Rename `engine::*` function ids registered by namespaced workers (Step 4)
* [ ] Add `namespace:` to `expose_functions` rules for namespaced workers (Step 5)
* [ ] Move `allowed_functions` grants for namespaced workers into `namespaces` (Step 5)

## Result

The project runs on 0.23.x with durable subscriptions consuming from namespace-qualified queues.
Workers that declare no namespace keep routing through `default` exactly as they did on 0.22.x, and
workers that declare one own their function ids inside it.

<Note>
  To configure workers and calls, see [Use namespaces](../using-iii/namespaces). To understand strict
  routing, see [Namespaces](../understanding-iii/namespaces).
</Note>
