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

# Operating Plugin SISBAJUD

> Set up and run Plugin SISBAJUD: institutions and their Midaz connector, the direct channel, ledger routing, orders that need attention, credential key rotation, roles, listeners, and deploy constraints.

This page describes how to set up and run **Plugin SISBAJUD**. [Operating Lerian SISBAJUD](https://docs.lerian.studio/en/rails/sisbajud/sisbajud-operations.md) describes the Mensageria. [How Plugin SISBAJUD runs orders](https://docs.lerian.studio/en/interfaces/plugin-sisbajud/plugin-sisbajud-how-it-works.md) describes what the plugin does with each order.

## Institution setup

***

Register each institution on the Mensageria and on the plugin, with the same `institutionId`. [Operating Lerian SISBAJUD](https://docs.lerian.studio/en/rails/sisbajud/sisbajud-operations.md#institutions) describes the Mensageria request.

`POST /v1/institutions` registers an institution on the plugin. The body has these fields:

| Field | Meaning |
| - | - |
| `institutionId` | The UUID of the institution. Use the same value as on the Mensageria. |
| `connectorType` | The ledger connector. Set it to `midaz`. |
| `institutionCode` | The CNPJ root of the institution, 8 digits. |
| `enabled` | Whether the institution is active. Send `true`. |
| `connectorMetadata` | The Midaz configuration of the institution. The tables below describe it. |
| `retryPolicyConfig` | Optional. The retry policy, as JSON. |
| `branchFormat` | Optional. A Go RE2 regular expression, at most 64 characters, for the branch number that a court can name in a block restriction. |
| `accountFormat` | Optional. A Go RE2 regular expression, at most 64 characters, for the account number that a court can name in a block restriction. The plugin matches that number with `bankingDetails.account` of the CRM instruments, so the restriction needs `crmMode` `embedded`. |

The request also creates the tokenization keyset and the audit keyset of the institution. A second request with the same `institutionId` returns `409`.

The plugin records itself as the owner of the institution, from its own M2M identity (`EXECUTOR_M2M_SUBJECT`). No request field sets the owner, and a `PATCH` does not change it.

### Connector metadata

| Key | Required | Meaning |
| - | - | - |
| `organizations` | Yes | A list of `{organizationId, ledgers}` entries: the Midaz organizations that the plugin searches for the accounts of a subject. Without `ledgers`, the plugin searches every ledger of the organization. |
| `baseUrl` | Yes | The base URL of the Midaz ledger of the institution. |
| `authAddress` | When credentials are stored | The Access Manager address that issues the Midaz token of the institution. |
| `blockableBalances` | Yes | The Midaz balance keys that a block can use, for example `default`. These are balance keys, not the `available` or `onHold` amounts. |
| `blockableAccountTypes` | Yes | The account types that a block can use. |
| `salaryAccountTypes` | Yes | The account types that are salary accounts. Send `["NONE"]` when the institution has no salary account type. |
| `crmMode` | No | `legacy` or `embedded`. Without a value, the institution uses `MIDAZ_CRM_MODE`. |
| `crmBaseUrl` | No | The base URL of the standalone Midaz CRM, for `legacy`. With `embedded`, leave it empty: the plugin reads the CRM through `baseUrl`. |
| `permanentProducts` | To write permanent block results | The product catalog of the institution: `bankCode` and, in `types`, the product class and product code of each account type. Without it, the plugin holds the result of a later block and raises an alarm. |
| `executorLedger` | To run orders | The ledger where the plugin writes. The next table describes it. |

### Plugin SISBAJUD ledger

| Key | Meaning |
| - | - |
| `organizationId` | The Midaz organization of the writes. |
| `ledgerId` | The Midaz ledger of the writes. |
| `asset` | Set it to `BRL`, exactly. |
| `judicialDepositAccountAlias` | The alias of the judicial deposit account. It must follow the Midaz alias rule. |

The plugin refuses an institution request whose `executorLedger` is present and not valid, with `400`. At startup, the plugin leaves out of its ledger routing an institution that has no valid `executorLedger`.

### Connector credentials

`PUT /v1/institutions/{institutionId}/connector-credentials` writes the Midaz credentials of the institution:

* `ledger_client_id` and `ledger_client_secret` are always required.
* `crm_client_id` and `crm_client_secret` are required with `crmMode` `legacy`. With `embedded`, do not send them.

The plugin encrypts the credentials before it stores them. `PATCH /v1/institutions/{institutionId}` refuses a body with credentials. A new credential does not revoke the previous one at the Access Manager, so revoke it there.

The plugin gets the Midaz token of the institution from `{authAddress}/v1/login/oauth/access_token`. When the institution has no stored credentials, the plugin sends no `Authorization` header to Midaz.

### Change the configuration

* `PATCH /v1/institutions/{institutionId}` changes the fields of the institution. A `connectorMetadata` in the body replaces the whole bag, except the stored credentials.
* `PUT /v1/institutions/{institutionId}/connector-config` replaces `connectorType` and `connectorMetadata`. It keeps the stored credentials, and it refuses a body with credentials.
* `GET /v1/institutions/{institutionId}` returns the configuration without the credentials.

The plugin reads the configuration of its institutions at startup. After you create an institution or change its configuration, restart the plugin.

## Direct channel

***

[How Plugin SISBAJUD runs orders](https://docs.lerian.studio/en/interfaces/plugin-sisbajud/plugin-sisbajud-how-it-works.md#direct-channel) describes the channel. To put an institution on it, send `PUT /v1/institutions/{institutionId}/channel` with `DIRECT`. Send `MENSAGERIA` to switch it back. The call answers `200` with `changed` `false` when the institution is on that channel already.

The channel needs `PLUGIN_AUTH_ENABLED` on, one tenant, and the executor mode. The switch needs a person with the `platform-admin` role. The plugin refuses an application token and a tenant-scoped token with `403`.

The switch refuses with these codes:

* `SBJ-0040` / **422**: the deployment is multi-tenant.
* `SBJ-0041` / **422**: `PLUGIN_AUTH_ENABLED` is off.
* `SBJ-0042` / **422**: the stored institution code is not the 8-character CNPJ root of the responsible grouping.
* `SBJ-0043` / **409**: the institution has an open order, or a block that still holds money.
* `SBJ-0046` / **409**: another switch won. Nothing moved. Send the request again.
* `SBJ-0060` / **409**: another Plugin SISBAJUD installation owns the institution.

### Erase a direct order

A platform administrator calls two routes to erase direct-channel orders. [How Plugin SISBAJUD runs orders](https://docs.lerian.studio/en/interfaces/plugin-sisbajud/plugin-sisbajud-how-it-works.md#direct-channel) describes what an erase removes and what it keeps.

* `POST /v1/admin/direct/orders/{orderId}/erase` erases one order. The body carries `reason`, `ticket`, and an optional `note`. `reason` is `lgpd-subject-request` or `retention-purge`. `ticket` is up to 64 characters, and `note` is up to 256. None of them may hold personal data. The answer is `200` with `outcome` `erased`, or `noop` when the order is already erased, and the number of rows erased.
* `POST /v1/admin/direct/purge` lists the orders that can be erased now and changes nothing. The body carries `reason`, `ticket`, `limit` (1 to 100, and 100 when absent), and `dryRun`, which must be `true`. The answer holds `orderIds`, oldest first, `asOf`, and `truncated`. `truncated` is `true` when the list stopped before the plugin read every candidate.

The caller needs the token of a person, with no tenant. The plugin refuses an application token with `403` `SBJ-0044`, and a tenant-scoped token with `403` `SBJ-0045`. The routes exist only when the plugin runs in executor mode with `PLUGIN_AUTH_ENABLED` on. A multi-tenant deployment gets `422` `SBJ-0059`. An unknown order gets `404` `SBJ-0015`. The [Plugin SISBAJUD error list](https://docs.lerian.studio/en/reference/interfaces/plugin-sisbajud/plugin-sisbajud-error-list.md) lists `SBJ-0056` to `SBJ-0059`.

The gauge `sisbajud_executor_direct_intake_purge_eligible` counts the orders that can be erased now. The count is a lower bound. `EXECUTOR_DIRECT_PURGE_REFRESH_EVERY` sets how often the plugin counts.

## Ledger routing

***

With `SYSTEMPLANE_ENABLED` on, the plugin serves the tenant documents of its accounting routes under `/tenant-system`. The plugin reads two of them: `routing.organizations` turns on accounting routes for each organization, and `routing.ledger_bindings` holds the routes. With `SYSTEMPLANE_ENABLED` off, the plugin writes without routes.

`routing.organizations` accepts `schemaVersion` 1 or 2. At `schemaVersion` 2, each organization entry holds its own `routes` for each flow and its `ledgers`, the ledgers that post with those routes. The plugin then does not read `routing.ledger_bindings`, and a write to a ledger outside `ledgers` goes out without routes.

`GET /v1/admin/ledger-routing/preflight` checks the routing configuration against the ledger of the institution. It writes nothing. A single-tenant plugin needs the `institutionId` query parameter. The report covers these checks:

* The transaction routes and the operation routes exist, and they match the writes of the plugin.
* A ledger that validates account types has `sisbajud_block` registered.
* The judicial deposit account in `executorLedger` exists in the ledger and is not blocked.
* At `schemaVersion` 2, the ledger of each write is in the `ledgers` of its organization. A ledger outside the list gets the issue `ledger_not_listed`, and the bindings document gets the state `ignored`.

The `ready` field reports whether the configuration is ready. The report shows the stored `executorLedger`. The running plugin uses the configuration that it read at startup.

## Orders that need attention

***

| Operation | What it returns |
| - | - |
| `GET /v1/institutions/{institutionId}/executor/failed-orders` | The order revisions that the plugin failed or abandoned. Each item has the order ID, the revision, a reason token, and the ID of its journal entry. It does not list the revisions whose outcome is not known yet. |
| `GET /v1/institutions/{institutionId}/executor/human-decisions` | The order revisions that the plugin stopped on a human decision. |

Each human decision has a `humanDisposition`:

* `GAVE_UP`: the plugin attempted no ledger write and answered `EXECUTOR_GAVE_UP`.
* `HELD_MONEY_MOVED`: the plugin attempted a write. Money can have moved, and the plugin posted no answer.

The plugin also records a human decision when it parks a result after `EXECUTOR_RESULT_MAX_ATTEMPTS` failed posts, and when the ledger is down at the cut of an order. [How Plugin SISBAJUD runs orders](https://docs.lerian.studio/en/interfaces/plugin-sisbajud/plugin-sisbajud-how-it-works.md#ledger-down-at-the-cut) describes the cut.

### Resolve a human decision

`POST /v1/admin/executor/orders/{orderId}/revisions/{revision}/human-decision/resolution` closes the open human decision of an order revision. The body carries the `decisionId` of the decision, a `reason`, and a `resolution`:

* `proven-no-effect`: the ledger shows that the writes of the revision did not land.
* `effect-acknowledged`: you accept the effect as it is. For a release whose parent block landed without its origin legs, send `movedAmount` and a transaction as `ledgerEvidence`.

`ledgerEvidence` is required when the revision is uncertain. The `reason` has at most 200 characters and no personal data.

The plugin records the resolution and its audit entry in one transaction. It sends nothing to the court: the institution answers the court. The route needs `PLUGIN_AUTH_ENABLED` on and a person with the `platform-admin` role, and the plugin refuses an application token and a tenant-scoped token with `403`. A multi-tenant deployment answers `422` `SBJ-0053`.

The route can return these errors:

* `SBJ-0051` / **409**: the revision has no open human decision to resolve here.
* `SBJ-0052` / **422**: the resolution, the reason, or the evidence is not valid.
* `SBJ-0054` / **409**: the journal shows that money moved, so `proven-no-effect` is refused. An acknowledgment is also refused while the write is undetermined.
* `SBJ-0055` / **409**: the journal shows no landed effect, so there is nothing to acknowledge.
* `SBJ-0064` / **409**: the evidence is a transaction that the plugin itself recorded for the revision.

### Re-arm a parked result

`POST /v1/admin/executor/orders/{orderId}/revisions/{revision}/results/{executionVersion}/rearm` takes a result that the plugin parked at `EXECUTOR_RESULT_MAX_ATTEMPTS`. It sets the attempts back to 0, and the plugin posts the result again at once. The subsequent versions of the same order then follow. Send a `reason` of at most 200 characters, with no personal data.

A result that is pending but not parked answers `200` with the outcome `NOOP`. The route needs `PLUGIN_AUTH_ENABLED` on and a person with the `platform-admin` role, and it works in a single-tenant deployment only.

* `SBJ-0048` / **409**: the result is terminal. There is nothing to re-arm.
* `SBJ-0049` / **422**: the reason is missing, blank, too long, or holds personal data.
* `SBJ-0050` / **422**: the deployment is multi-tenant.

### Resolve a parked permanent attempt

`POST /v1/admin/executor/orders/{orderId}/permanent-attempts/{attemptId}/resolution` resolves a parked attempt of a permanent block on the direct channel. It follows the rules of the human-decision resolution, and it needs the same role and `PLUGIN_AUTH_ENABLED` on. With `proven-no-effect`, the new credits of the block resume. With `effect-acknowledged`, the plugin counts `movedAmount` toward the order amount. It adds these errors:

* `SBJ-0061` / **409**: the attempt is not parked, or the order is busy.
* `SBJ-0062` / **422**: the acknowledged amount is above what the open permanent block still holds, or above the write that the plugin journaled for a closed one.

The Mensageria also lists the orders that the plugin gave up on. [Operating Lerian SISBAJUD](https://docs.lerian.studio/en/rails/sisbajud/sisbajud-operations.md#orders-that-need-attention) describes that list.

## Credential key rotation

***

`POST /v1/institutions/{institutionId}/security/credential-kek:rotate` rotates the key that wraps the stored Midaz credentials of the institution. This key is not the institution-data key that the Mensageria rotates.

* The rotation moves the stored credentials to the new key version. The credential values do not change. To replace a credential, use `PUT /v1/institutions/{institutionId}/connector-credentials`.
* The response shows the previous and the new key version.
* An institution with no stored credentials returns `404`.
* With `STREAMING_ENABLED` on, the rotation publishes a `kek.rotated` event with `keyPurpose` `CREDENTIALS`.
* With AWS KMS in `provided` mode, the plugin does not rotate your key, and the operation returns `422`.

The rotation can return these errors:

* `SBJ-0007` / **409**: another rotation of the same institution is in progress. Send it again after that rotation ends.
* `SBJ-0012` / **409**: nothing rotated, and the same request fails again.
* **503**: the key manager failed before the rotation. Send it again later.
* `SBJ-0013` / **500**: the outcome of the rotation is not known. Do not send the rotation again.
* `SBJ-0014` / **500**: the key rotated, but a later step failed. Do not send the rotation again. Contact Lerian support.

With `PLUGIN_AUTH_ENABLED` off, the plugin does not serve this operation.

## Audit verification

***

`GET /v1/admin/audit/verify` checks a range of the audit log of the institution, from `fromIndex` to `toIndex`. It does not decrypt any payload. It reports tampering and deleted entries.

## Roles

***

Each operation needs one permission:

| Operation | Permission |
| - | - |
| Create or update an institution, its connector configuration, or its credentials | `institution_config:write` |
| Read an institution, the failed orders, the human decisions, or the audit verification | `admin:read` |
| Credential key rotation | `credential_kek:rotate` |
| Ledger-routing preflight, and reads under `/tenant-system` | `tenant_policy:read` |
| Writes under `/tenant-system` | `tenant_policy:write` |
| Switch the channel of an institution | `institution_channel:write` |
| Re-arm a parked result | `executor_result:rearm` |
| Resolve a human decision or a parked permanent attempt | `executor_human_decision:resolve` |
| Erase a direct order | `direct_order_erasure:erase` |
| Preview the purge of direct orders | `direct_order_erasure:purge_preview` |
| Submit a direct order | `order:submit` |
| Read direct orders and results | `result:read` |

The plugin declares the roles `viewer`, `operator`, `investigator`, `cross-org-reader`, `admin`, `platform-admin`, `order-submitter`, and `result-reader`. Each of the first six roles binds the group `plugin-br-sisbajud-<role>-group`, and `admin` also binds `admin-group`. The roles `order-submitter` and `result-reader` bind no group. Your M2M application holds `admin`, `order-submitter`, and `result-reader`.

The `admin` role holds the permissions of the first five rows of the table. The `viewer`, `operator`, `investigator`, and `cross-org-reader` roles hold `admin:read`. The `platform-admin` role holds `institution_channel:write`, `executor_result:rearm`, `executor_human_decision:resolve`, `direct_order_erasure:erase`, `direct_order_erasure:purge_preview`, and the `systemplane` permissions of `/system`. No other role holds them. The plugin refuses an application token and a tenant-scoped token on the five platform operations with `403`.

The `result-reader` role holds `result:read` only. The `order-submitter` role holds `order:submit`, `result:read`, and `metadata:read`. Only a caller with `metadata:read` reads the order `metadata` on the direct reads.

### The plugin on the Mensageria

The plugin calls the execution API of the Mensageria with its own M2M client. Set the client in `MENSAGERIA_CLIENT_ID` and `MENSAGERIA_CLIENT_SECRET`. The plugin gets its token from the Access Manager at `PLUGIN_AUTH_HOST`.

## Listeners

***

The plugin has two listeners:

* **Main API**, at `SERVER_ADDRESS`. Set it to `:4029`, the port that the image exposes. It serves the operations of the API reference. It also serves `/health`, `/readyz`, and `/version`. With `SYSTEMPLANE_ENABLED` on, it serves `/system` and `/tenant-system`. With `SWAGGER_ENABLED` on, it serves `/swagger`.
* **Webhook listener**, at `EXECUTOR_HUB_WEBHOOK_ADDRESS`. The default is `:9082`. It has one route, `POST` at `EXECUTOR_HUB_WEBHOOK_PATH`, whose default is `/v1/webhooks/hub`. Expose this port to the Streaming Hub, and use your ingress address plus this path as the subscription URL.

The webhook listener has no application authentication. It accepts a delivery only when the HMAC signature verifies with the subscription secret or with the previous secret. Outside `DEPLOYMENT_MODE=local`, the listener needs its own TLS certificate. As an alternative, terminate TLS in front of it and set `EXECUTOR_HUB_WEBHOOK_UPSTREAM_TLS_VALIDATED=true`.

`/readyz` includes two plugin checks:

* `hub_webhook`: the webhook listener.
* `executor_institutions`: the number of institutions that the plugin owns, and how many of them have ledger routing. This check does not fail readiness.

## Deploy constraints

***

* **One tenant for each process.** The plugin does not start when its tenant list holds more than one tenant.
* **Its own database.** The plugin runs on its own empty PostgreSQL database. Its migrations refuse a database that already holds another migration history.
* **Restart after configuration changes.** The plugin reads its institutions at startup.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.