Skip to content

Law Framework

The ten-laws noticeboard — deliberately, provably inert. See Legacy/06_CRIME_AND_POLICE.md §7 for the full legacy trace.


1. What this is, and — pointedly — what it is not

"Ten laws. Free text. Editable by the Mayor... The laws are not enforced by code at all. Nothing reads them. They exist so that police can point at them and players can argue about them. This is the purest expression of the design philosophy in the codebase: the framework provides a noticeboard, and the players provide the legal system."

ILawService is exactly that: ten fixed text slots, a way to read them, and a way to change one. Nothing in this codebase, now or later, should make a law's text gate a mechanism — if a law needs to be enforced, that's a permission check or a CancellableEvent subscriber written directly against the rule, same as everything else this milestone builds; the noticeboard stays a noticeboard.

namespace Applejack.Law;

public interface ILawService
{
    Task<Result> LoadOrCreateAsync();                    // one global document, not per-character
    IReadOnlyList<string?> GetLaws();                      // ten slots, null = unset
    Task<Result> SetLawAsync(Character editor, int index, string text);
}

One document (law/server, following Code/Bulletin/README.md's identical single-document precedent), not per-character — there is exactly one set of laws per server.

2. Editing: permission, not a job flag; a cooldown, via IClock

Legacy gates changes to the Mayor specifically and a 120-second cooldown between changes. This reuses PermissionSet ("law.edit") rather than a job-specific check — the same replacement Commands/Jobs already made for their own access flags — so an operator decides who may edit laws without this module needing to know what a "Mayor" job even is. The 120-second cooldown is checked against the new IClock, the same shape Jobs' rejoin cooldown and Crime's warrant/arrest expiry already use — one absolute timestamp (NextEditAllowedAtUtc), not a running timer.

3. /laws and /setlaw — Law's own first real commands

Unlike Crime's /warrant//arrest (17_CRIME_FRAMEWORK.md §4), editing the noticeboard targets yourself — resolved the same way every other self-targeting command already is (Rpc.Caller.GetCharacter()), so there's no missing-target-resolution blocker here. /laws (no arguments) lists all ten slots; /setlaw <index> <text...> edits one, registered by LawModule.Ready() against ICommandRegistry — the third module to prove Commands' registry after Jobs' /team.

4. Dependencies, deliberately not matching the module table

Legacy/13_DEPENDENCY_GRAPH.md's own table lists Law | Policy: who may police whom | Core, Crime, Jobs. That row describes the policy concept — deciding who may exercise Crime's mechanisms — which this pass does not build (17_CRIME_FRAMEWORK.md §1 leaves that gate unsubscribed on purpose). The noticeboard, as actually specified in Legacy/06_CRIME_AND_POLICE.md §7, needs neither Crime nor Jobs — only Core, Character (for PermissionSet), Persistence, and Commands. LawModule.DependsOn reflects what's actually built, not the table's broader framing; noted here rather than adding a dependency edge with nothing behind it.

5. Testing

LawStore is plain C#, tested against the shared InMemoryPersistenceBackend fixture and a fake IClock, following CrimeStoreTests.cs's/JobStoreTests.cs's setup: cooldown enforcement, permission enforcement, and index-bounds validation.