> For the complete documentation index, see [llms.txt](https://mango-5m.gitbook.io/mango5m-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mango-5m.gitbook.io/mango5m-docs/resources/mango-mdt/features/jail-and-sentencing.md).

# Jail and Sentencing

Sentence a report's charges as one combined prison sentence, track IN PRISON status, and optionally raise an escape BOLO — on any supported prison resource.

Attach charges to a report, save it, and the MDT sentences each charged subject to **one combined prison sentence** — sent to whatever prison resource your server runs. The prison owns the countdown and time deduction; the MDT records the sentence, shows **IN PRISON** on the person's profile while it's being served, and clears it on release.

## How sentencing works

1. In the **report editor**, link the citizens involved and attach charges to them from the penal code ([Departments → Charges](/mango5m-docs/resources/mango-mdt/configuration/departments.md)).
2. **Save the report.** For every linked citizen whose charges carry custody time, the MDT sends the server their **charge IDs** — never durations.
3. The server re-reads each charge from the penal code, sums the custody months, converts to real minutes, applies the report's jail-leniency slider (reduce-only, capped at 50%), and sends **one total** to the prison:

```
Charge 1: 10 months  ┐
Charge 2: 15 months  ├─►  30 months × minutesPerMonth (1.0)  =  ONE 30-minute sentence
Charge 3:  5 months  ┘
```

Never three separate sentences — one jail call per subject per report.

{% hint style="info" %}
**Trust boundary:** the client can only *name* charges. Durations, totals, permission (`jail.sentence`, granted to every shipped rank), the subject's identity, and duplicate protection are all enforced server-side — a tampered client cannot invent jail time or double-sentence anyone.
{% endhint %}

**You can sentence yourself.** Write a report about yourself, charge yourself, save — deliberately allowed, and the fastest way to test the whole pipeline solo.

## Configuration

{% code title="config/jail.lua" %}

```lua
Config.Jail = {
    -- What one penal-code custody month costs in real prison minutes.
    minutesPerMonth = 1.0,

    -- Sentenced while already in prison: 'reject' (default) or 'replace'.
    alreadyJailed = 'reject',

    -- Auto-BOLO on prison escape (see below). Off by default.
    escapeBOLO = false,
}
```

{% endcode %}

The prison resource itself is a bridge selection like every other integration:

```lua
-- config/bridge.lua
Config.Bridge = {
    -- ...
    Jail = 'auto',   -- rcore_prison | tk_prison | qb-policejob | esx_jail | custom | none
}
```

## Supported prison resources

| Provider       | Sentenced via                                                                                                                                                                                                                                                   | Release detection   | Escape detection |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ---------------- |
| `rcore_prison` | `jailPlayer` export (minutes)                                                                                                                                                                                                                                   | ✅ event             | ✅ event          |
| `tk_prison`    | jail event (minutes)                                                                                                                                                                                                                                            | ⏱ expiry fallback   | ❌                |
| `qb-policejob` | `police:server:JailPlayer` (qb-prison)                                                                                                                                                                                                                          | ⏱ expiry fallback   | ❌                |
| `esx_jail`     | `esx_jail:sendToJail` (seconds) — **verified against the stock resource**; also covers `esx_jailer` (seconds). If **both** are started, esx\_jail is used and the console prints an advisory — run one jailer, not two. esx\_policejob ships no jail of its own | ✅ read-back polling | ❌                |

**✅ read-back polling:** esx\_jail fires no release event, but it *persists* the remaining sentence (`users.jail_time`) — so the MDT reads the truth back about once a minute. An early release (`/unjail`, an admin menu) reflects within that minute, and is also re-checked **live** the moment someone tries to sentence that person again. It even handles esx\_jail pausing the countdown for disconnected prisoners: IN PRISON stays on until they have actually served, not just until the wall clock runs out.

**⏱ expiry fallback:** where a prison fires no release signal *and* nothing can be read back, the MDT clears IN PRISON itself the moment the *original* sentence elapses — it can't see early releases. Providers that signal (or poll) beat the fallback. Custom adapters can opt into polling too — implement the optional `IsStillJailed` (documented in `bridge/jail/template.lua`).

{% hint style="warning" %}
Adapters are written against each resource's API (esx\_jail verified against the stock resource; the rest against documented defaults) and each carries a "fix here" header — if your build's export/event names differ, the correction is one line in `bridge/jail/<name>.lua`, never in the sentencing logic. If the configured prison **resource isn't started**, the sentence is refused and the console prints exactly which resource is missing — no phantom sentences. Forks that add release/escape events can forward them with one line (commented examples sit at the bottom of every adapter). A different prison entirely? Fill in `bridge/jail/custom.lua`.
{% endhint %}

## IN PRISON profile status

While a sentence is active, the person's [profile](/mango5m-docs/resources/mango-mdt/features/person-database.md) shows a red **IN PRISON** tag beside their flags. It reads from the MDT's own jail records (`mdt_jail_records` — one row per sentencing, with the original sentence, charges snapshot, officer, report reference, and provider), so it is never guesswork:

* Sentenced → tag appears.
* Released (prison signal, or the expiry fallback) → tag disappears.
* Escaped → tag disappears (they're *out*) — and the BOLO below takes over, if enabled.

Other resources can ask too: `exports.mango_mdt:IsImprisoned(identifier)`.

## Escape BOLO (optional, off by default)

With `Config.Jail.escapeBOLO = true` **and** a prison that detects escapes (see the table), a prisoner who breaks out automatically gets a **HIGH-priority active arrest warrant**: subject identity, *Escape from Custody*, the original sentence, time of escape, and the report reference — raised through the real warrant workflow, so it lands on the Warrants dashboard and as the ⚠ banner on their profile. One escape, one BOLO — duplicate-guarded.

If the active prison can't detect escapes, the MDT logs a clear "unsupported on this provider" warning at startup instead of silently never firing.

## Troubleshooting

* **"No prison resource is configured"** — `Config.Bridge.Jail` resolved to nothing. Check `mango_bridges` in the server console; make sure the prison starts before `mango_mdt`.
* **"The prison resource rejected the jail call"** — the server console says why: the prison resource isn't started (the message names it), or the adapter's export/event name doesn't match your prison build — fix the one line in `bridge/jail/<provider>.lua`.
* **Sentence accepted but nothing happens in-game (esx\_jail)** — look for `Unknown column 'jail_time'` in the server console. Stock esx\_jail needs its own `esx_jail.sql` imported (it adds `users.jail_time`); without it, esx\_jail accepts the jail call and then fails inside its database write — after the MDT has already recorded the sentence. Import the SQL, then retest with a **new** report: each report sentences a subject once, ever, so the failed report's reference is already spent.
* **"… was already sentenced from this report — not re-sentenced"** — the duplicate guard: this exact report already produced a sentence for that subject (one report = one sentence, ever, even across re-saves). To sentence them again, write a new report.
* **Two ESX jailers installed** — never run `esx_jail` **and** `esx_jailer` together (especially if one is a renamed copy of the other: both copies register the *same* events, so every jailing is processed twice). Delete one; the MDT prefers `esx_jail` and warns in the console when it sees both.
* **"X is offline"** — subjects must be online to be jailed (a prison needs a live ped); the sentence is not queued.
* **"X is already serving a sentence" right after you unjailed them** — on a read-back provider (esx\_jail) this self-heals: the MDT re-checks the prison's own state both on a timer (\~1 minute) and live at the next sentencing attempt, so at most you wait moments. On providers with no release signal *and* no read-back, an early release is invisible — the record clears only when the original sentence elapses.
* **IN PRISON never clears** — on expiry-fallback providers it clears when the original sentence elapses (±1 minute); if your prison released them early, the MDT can't see that unless the adapter forwards a release event or implements the `IsStillJailed` read-back.
