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.