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:
- 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. - What "sentencing" actually changes. Does a guilty verdict extend/replace the existing
Crime.Arrestmechanism (reusing its absolute-release-time persistence), or introduce a distinctSentenceconcept with its own duration and release logic? ReusingArrest'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. - 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. - Command surface. Does this need new permissions beyond
Law's existing"law.edit"andCrime's existing arrest/warrant gates, or a new"crime.judge"-shaped permission? GivenCharacter.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 extendingArrest. - 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), andEconomy(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.