Skip to content

ADR-0015 — Bail, trials, and sentencing

Status: Proposed Date: 2026-07-31 Supersedes:Superseded by:


Context

Legacy's crime system stopped at warrants and arrest — a fixed 300-second detention with no trial of any kind (Legacy/01_FEATURE_INVENTORY.md). Evidence, fines, criminal records, and bail/trials/sentencing are all listed ❌ New there. This ADR covers only the last and largest of the three: ROADMAP.md's Milestone 17 deliberately splits Fines/Records (Wave 1, small and additive) and Evidence (Wave 2) from this, because a real trial is a different shape of problem — a multi-party process with state that persists across multiple play sessions, not a single service call.

What already exists to build on. ICrimeService/CrimeStore (one active warrant per character, persisted with an absolute expiry; arrest persists an absolute release time — 17_CRIME_FRAMEWORK.md), ILawService/LawStore (ten player-authored law slots — 18_LAW_FRAMEWORK.md), and the mechanism/gate/policy split Legacy/06_CRIME_AND_POLICE.md §1 calls "the best architectural decision in the entire legacy codebase"Crime provides mechanism, Law/policy decides who may invoke it. Milestone 12's target-resolution-by-name is a hard prerequisite: a trial fundamentally needs to name a defendant, and today nothing in this codebase can resolve "the connected character named X" without side effects (the same gap that has blocked /warrant's own command layer since Milestone 7).

Decision

Not yet decided — open to resolve:

  1. Does a trial require a human judge/jury to be online, or can it resolve automatically (e.g., a timed default-verdict window)? Legacy had zero precedent either way. Requiring a human judge is more true to "trial" as a concept but risks a defendant waiting indefinitely if nobody with the right permission is online — needs an explicit timeout/fallback either way, mirroring how Crime's arrest already uses an absolute expiry rather than an open-ended hold.
  2. What "sentencing" actually changes. Does a guilty verdict extend/replace the existing Crime.Arrest mechanism (reusing its absolute-release-time persistence), or introduce a distinct Sentence concept with its own duration and release logic? Reusing Arrest's existing persisted-expiry pattern is the lower-risk option and avoids two competing "how long is this character detained" answers — the same class of two-sources-of-truth problem D-16 already names as a legacy defect.
  3. State machine shape. At minimum: Charged → Bail(pending/posted/denied) → TrialScheduled → TrialInProgress → Verdict(guilty/not guilty) → Sentenced/Released. Whether bail is a fixed formula (e.g., a multiple of the fine, per Wave 1) or set per-case by a judge needs deciding; the former is simpler and matches this project's general preference for config-driven values over freeform admin discretion.
  4. Command surface. Does this need new permissions beyond Law's existing "law.edit" and Crime's existing arrest/warrant gates, or a new "crime.judge"-shaped permission? Given Character.HasPermission/PermissionSet's existing flat, named-permission model (Milestone 4), a new named permission is the low-risk default rather than reusing an existing one for a different purpose.

Alternatives considered

No trial — guilty/not-guilty resolved instantly by whoever files the charge

The cheapest possible interpretation of "criminal justice," and arguably closer to what legacy's fixed-300s-arrest already implied (there was never a trial to begin with). Rejected as the sole answer because Milestone 17 explicitly names bail/trials/sentencing as distinct concepts to build, not merely rename arrest — but recorded here because it's a legitimate fallback scope if the full state machine in Decision point 3 proves too large for one milestone wave, the same way Milestone 10's admin-panel work was explicitly phased down when scoped mid-flight.

Model a trial as a long-lived Interaction

Interaction (10_INTERACTION_FRAMEWORK.md) is this codebase's existing timed/cancellable-action primitive, but it is shaped for single-actor, short-duration actions (equip, lockpick, breach) with a cancel predicate evaluated every tick — not a multi-party process that may span a real-world timeout while both participants are offline. Rejected as the base primitive for this reason; a trial needs its own persisted state machine, closer to Crime.Arrest's shape than Interaction's.

Consequences

Positive

  • Reuses Crime's existing persisted-expiry pattern for detention rather than inventing a second one, if Decision point 2 resolves toward extending Arrest.
  • Gives Law's ten-slot document a real downstream consumer for the first time — today nothing in this codebase reads the ten laws programmatically; Crime deliberately must never read them either (Legacy/01_FEATURE_INVENTORY.md's own "Keep — code must never read them" note on the laws), so a trial's human participants read the laws, not the system.

Negative

  • Be honest: this is the single largest new state machine in Milestones 12–18. Whichever cardinality is chosen for "who may act as judge," this needs real design attention before implementation, not a same-day build the way Milestone 12's items were.
  • A trial that can span a real-world timeout while participants are offline is genuinely new territory for this codebase — no other feature persists a process, only a fact (arrest expiry, warrant expiry). This may surface persistence-shape questions 06_PERSISTENCE.md hasn't had to answer yet.

Neutral

  • Depends on Crime, Law, Character (for the defendant and, per Milestone 13, their biography/record), and Economy (for bail/fines) — no new module dependencies beyond what Milestone 17's Wave 1 already establishes.

Compliance

  • Every new [Rpc.Host] surface (file charges, post bail, deliver verdict) is walked against the exploit checklist, with particular attention to who is authorised to move the state machine forward at each stage — the single most likely place for a permission gap.
  • A regression test proves a trial cannot silently stall forever with no defined outcome (the timeout/fallback from Decision point 1, tested explicitly).

Notes

Open questions, deliberately deferred: whether a criminal record (Wave 1) is updated automatically on a guilty verdict here, or requires a separate explicit action — should default to automatic, to avoid the two-systems-need-to-agree failure mode this project consistently avoids elsewhere, but confirm once Wave 1 and this ADR are both further along. Appeals are explicitly out of scope for the first version — name it, don't build it, following this project's consistent "explicitly deferred, not silently dropped" convention.