Skip to content

The regulation corpus

Nerthus.Core (until cutover). This page describes the frozen system that runs today and is deleted at cutover. Replaced by: not yet written.

The rules a chat sanction is issued under, as data: the documents, their versions, which version was in force on a given day, the citable points of each, and the sanction ranges.

Every READ in the corpus carries no capability. A player contesting a sanction has to be able to read the rule that was applied to them, in the version that was applied — a corpus readable only by the people who enforce it is not a corpus.

Routes

Method Path Cmdlet Cap Write
GET /regulations
GET /regulations/effective
GET /regulations/tariff
GET /regulations/{doc}
GET /regulations/{doc}/{version}
GET /regulations/{doc}/{version}/points Get-NerthusRegulationPoint
GET /regulations/{doc}/{version}/points/{point} Get-NerthusRegulationPoint -Point
POST /regulations/{doc}/versions regulation.write

Cap is the required capability ( = public, no token). Paths are relative to /v1/api; the cross-cutting contract is on the API reference index.

Six of these rows carried a cmdlet name that named nothing. Get-NerthusRegulation, Get-NerthusTariff and New-NerthusRegulationVersion exist in no client/*.ps1 file and are exported by no manifest — the reference asserted a shell client this family never had. The cells are empty now, following the editor's precedent for a browser-only family: this corpus is read from the dashboard, and an empty cell is a true statement where a name was a false one.

Get-NerthusRegulationPoint is the exception and it was written for a reason. A citation has to resolve from a shell as well as from a browser, and the narrator absence duty (rozporzadzenia-rady § 7 pkt 6) is addressable only through the single-point read — an addressing scheme whose read no command can call satisfies the letter of resolvable and nothing else.

The route-coverage guard cannot see this column. It compares paths, capabilities and write flags, and says so about itself; whether a Cmdlet cell names a command that exists is checked by nobody. That is why six of eight rows here were wrong for as long as they were.

Every GET row is public, and that is a deliberate property rather than an oversight. A player contesting a sanction has to be able to fetch the exact rule that was applied to them, in the version that was applied — a regulation readable only by the people who enforce it is not a regulation. Every GET row is also replica-safe and poolable: committed files, read whole, no principal consulted. The corpus lives at .nerthus/regulations in the moderators tree (corpora.mc) when that directory exists, with the lore tree as a migration fallback only where the moderators tree carries no corpus — so two hosts agree only when both moderators clones sit on the same commit, the caveat the moderator roster states.

The one write is the SMC's. An amendment changes the text every future sanction is judged against and every past one is contested against, which is the same class of act as publishing a keyring epoch and not the same class as issuing a mute. admin.all satisfies it.

§-numbered documents address two levels

The rozporządzenia number paragraphs as ### § 7. headings and their points as 6) list items in the body, and the corpus cites the two together — „§ 7, pkt. 1, 2 i 4", „§ 9, pkt. 2". Both levels are citable, so both are returned:

id what it names
§7 the paragraph
§7.6 point 6 inside it — the narrator's duty to report an absence to the Rada

The corpus is left as transcribed. Renumbering ### § 7. to ### 7. would make the transcription disagree with the raw capture it is checked against, and would invent numbering across three versions of a document that prints none.

One version file may transcribe several instruments, and each restarts its § numbering. rozporzadzenia-rady/2026-06-15 carries the 2021 rozporządzenie plus the two 2026 blocking instruments, so § 1 names three different provisions in one file. A ## heading beginning Rozporządzenie opens a new scope, and a paragraph outside the first scope carries that scope's printed ordinal — §1, 3§1, 4§1 in that version, the gap because the amendment heading between them opens a scope carrying no paragraph of its own. The prefix is numbering the document does not print, and it is the narrower of two evils: without it three provisions answer to one address and the reader gets whichever comes first. Each row also carries scope, the instrument heading verbatim.

Point counts for rozporzadzenia-rady, which returned 0 before this scheme: 56 for 2021-10-18, 54 for 2023-08-02, 62 for 2026-06-15. The decimal documents are untouched — nerthus-fabularny/2021-12-06 still returns 110, nerthus-swiat/2021-05-11 53, taryfikator/do-2026-02-17 183, and none of their rows carries a § id.

What the answers say about themselves

GET /regulations/{doc}/{version}/points/{point} returns ONE point — the regulation justification a record cites. It answers RegulationPointNotFound when the version exists and the point does not, which a caller must tell apart from RegulationVersionNotFound: the first means the point was renumbered or removed and the citation is unresolved, the second means the version was never captured. Collapsing them would leave a client unable to say which.

POST /regulations/{doc}/versions lands a new version and closes its predecessor in the same change, setting the predecessor's effective_to to the day before the new effective_from. The bounds are inclusive, so a shared day would make a citation from it resolve to two texts. Closing used to be a separate manual edit, and forgetting it produced an overlapping corpus that was only reported after the fact. A dry run reports which version it would close and with what date; the route refuses to overwrite an existing version directory, because a version directory is the evidence a past sanction is judged against.

GET /regulations lists the five documents and their versions. A document with no versions is still a row, and version_count: 0 with has_text: false is the corpus telling the truth about itself — known and empty is a different statement from does not exist. A citation into a document with no text resolves to a version and renders nothing, which is the right failure: the alternative is rendering the nearest text instead, and that silently justifies a sanction under a rule that was not in force.

rozporzadzenia-rady carries three versions, and the three are one document's history rather than four separate instruments. The 2021 narratorskie rozporządzenie takes effect on 2021-10-18, seven days after publication — the notice_days: 7 this table declares, showing up as a real answer, because ?at=2021-10-17 resolves to nothing. The 2023 Zmieniające lands as the next version, so ?diff= across the pair renders exactly what that amendment states: § 8 loses two points and the list renumbers. The two 2026 blocks arrive in a third version. What a version cannot say about them is on the ledger instead — that one is bounded to 2026-11-02 and expires by clock, that each formalises a decision from an earlier date, and that each names one player. Those are facts about instruments, and a version directory models texts. The governance ledger holds them.

Point extraction does not reach this document, measured rather than assumed. A point is a heading whose first token is a dotted id rooted in a roman numeral or a decimal, and rozporzadzenia-rady numbers its provisions § 1. through § 18. — no such root. All three versions answer count: 0 where the other four documents answer 183, 310, 53 and 112. Citations in the corpus read § 9, pkt. 2 Rozporządzenia, so the numbering a record would cite exists; the regex does not express it.

GET /regulations/effective?at=<yyyy-MM-dd> answers which version of each document was in force on a day, defaulting to today. A document with nothing in force is a row with a null version and not an omission — a large share of the historic mutelog predates the Nerthus regulation entirely, and an omission reads as not asked about. An ?at= that does not parse answers 400 rather than falling back to today: a view that asked what applied on the day of the offence and silently got what applies now renders the wrong rule beside a real sanction, and nothing on the page would say so.

GET /regulations/{doc}/{version} renders one version with its transcription, and ?diff=<other version> renders the amendment against another version of the same document.

GET /regulations/{doc}/{version}/points lists the citable points of one version, each joined to its tariff row where one exists. A point with no tariff row carries nulls rather than being omitted: the most frequently cited conduct carries no fixed duration, and dropping it would hide the sanction the corpus prescribes most often.

GET /regulations/tariff is the sanction ranges as data, so a client can refuse an out-of-range duration at compose time rather than leaving a discrepancy nobody ever measures. The Margonem Taryfikator's doubling rule is deliberately absent: it excludes chat offences under its own section, so a Nerthus chat sanction escalates by the Nerthus recydywa ladder alone and applying both would double-punish.

Ingest is not a route

Change detection over the published regulations takes a fetcher and writes nothing on its own. The one place a real outbound HTTP call belongs is a scheduled workload with a timeout and a rate limit, keyed on the same schedules.<workload>.owner mechanism the rest of the estate uses. Hiding one inside a public route is how an endpoint anyone may call acquires an outbound network dependency nobody declared.