Skip to content

Narrator signals

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

Two independent sources answer who narrated, at what grade, and whether they were active — the governance ledger, built from the Rada's monthly sprawozdania, and the lore repository's own narrator signal, maintained by the narrators themselves. This route reads both on one date and publishes where they disagree.

The repo signal has two sources, one per era. Until August 2026 it was Nerthus/Informacje/Narratorzy.md; that page left with the public website, and the current state now comes from nerthus.contributors.md. Every commit that carried the markdown table is still read, unchanged — that history is the audit layer, and it does not move because a file did.

Neither signal outranks the other. A disagreement is recorded with both sides and both evidence chains; nothing here picks a winner. Which one is right is an operator's call.

Routes

Method Path Cmdlet Cap Write
GET /governance/narrator-signals Get-NerthusNarratorSignals

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

The capability choice is expedient and is recorded as such. matches the five governance ledger reads beside it, and both underlying sources are public record: the Rada publishes its roster monthly, and the narrator table ships in the lore repository. What nothing here weighs is that a worklist naming people and implying an error is a different object from the facts beneath it. A future capability project decides; this paragraph is its worklist entry rather than a claim that the gate was analysed.

Route order is load-bearing. narrator-signals is a literal and is declared before /governance/{ref}, or it resolves to it and answers unknown ref: narrator-signals. It is not a ref name and never becomes one.

The two signals, and what each is

ledger signal repo signal
source governance/ledger/narratorzy.jsonl in the works repository nerthus.contributors.md now, Nerthus/Informacje/Narratorzy.md at any earlier commit
authored by the Rada, in a monthly sprawozdanie the narrators themselves, as commits
unit a dated act on a display name an undated state on a margonem account
history ledger rows, correctable by an ordinary commit git — log plus show answer as-of already
evidence a forum post id, its author, and a verbatim quote a commit sha, its date, and its author

There is no mirrored store for the repo signal. Git already answers as-of and log over the retired path, so a second copy could only drift from the thing it copies. Every read walks the history, and the contributor store is appended to it as the newest snapshot rather than held separately — so an as-of read, the bound and the comparison all work on one list.

What the contributor store carries, and why it grew to. @rola: narratorzy is membership. The signal this route compares is membership plus a grade, Rada standing, activity and six permission ceilings, so the store holds @ranga — the old table's cell verbatim — and one @uprawnienie line per ceiling, both range-capable like @rola. A repoint that had kept only the roles would have shrunk the signal to a name list while this comparison went on balancing and reporting agreement, over a different object.

Three things refuse rather than default, and each appears in unparsed with its reason: a narrator with no @ranga (unqualified Narrator is a real grade, zwyczajny, not the absence of one), an @uprawnienie naming a column outside the closed six, and an unknown @ranga — read through the same token table the markdown cell was read through, so the two eras cannot drift into two grammars.

The bound, and why an out-of-range date refuses

A read outside what the host's lore clone actually holds answers in_bound: false, no rows, and a refusal naming which end it fell off:

GET /v1/api/governance/narrator-signals?at=2026-08-03
{ "at": "2026-08-03", "in_bound": false,
  "refusal": "2026-08-03 wykracza poza ostatni commit tabeli w tym klonie (2025-12-04); klon jest starszy od produkcji, więc cisza po tej dacie jest cechą klonu, nie repozytorium",
  "bound": { "Ok": true, "First": "2023-07-11", "Last": "2025-12-04", "Commits": 85,
             "Path": "Nerthus/Informacje/Narratorzy.md", "Head": "6c04c68…" },
  "repo": null, "ledger": null, "agreement": null, "disagreements": [] }

That refusal is what a clone with no contributor store answers. On a repository that has one, the store is the newest snapshot, so Last is today, Head is the sentinel contributors — it is not a commit and no git show resolves it — and Path names both eras. A 2026 date is therefore answered rather than refused.

This is the whole defence against a false alert. A clone whose table history ends before the sprawozdania do would otherwise report the repository as silent for the gap and every report in it as unmatched — divergence caused entirely by the clone's age and indistinguishable from the real thing. An alert surface that cries wolf for months is one an operator learns to close, so the surface refuses instead of answering.

A date before the first commit refuses for the mirror reason: the absence of the file is not the absence of narrators.

An in-bound read

GET /v1/api/governance/narrator-signals?at=2024-06-01
{ "at": "2024-06-01", "in_bound": true, "refusal": null,
  "bound": { "Ok": true, "First": "2023-07-11", "Last": "2025-12-04", "Commits": 85 },
  "pair_window_days": 92,
  "repo": { "commit": "…", "date": "2024-05-27", "author": "Karendar :)", "narrators": 9, "rows": [  ] },
  "ledger": { "ref": "narratorzy", "holders": 11, "ever_known": 38, "applied": 71 },
  "agreement": { "compared": 12, "agree": 5, "disagree": 3, "not_modelled": 0,
                 "only_repo": 1, "only_ledger": 3, "unjoined": 0,
                 "reconciles": true, "items": [  ] },
  "not_modelled": [  ], "disagreements": [  ], "lag": {  },
  "lossy": [ { "Id": "L1", "Signal": "repo", "Detail": "…" },  ] }

repo.date is the newest commit on or before at, which is the snapshot in force that day. It is normally earlier than at, and that is the table being unchanged rather than missing.

The buckets, and the partition that reconciles

Every person compared lands in exactly one bucket, and reconciles says on the wire whether they sum to compared. A census that does not add up has silently dropped somebody.

bucket what it means
agree both signals carry the person and agree on everything comparable
disagree both carry the person and differ on grade or on activity, or one account reaches several ledger subjects
not_modelled the table states a fact the ledger carries no row for — it neither agrees nor contradicts
only_repo the table calls them a narrator; the ledger knows the name and does not hold them that day
only_ledger the ledger holds them; the table has no row for them that day
unjoined no name on that margonem account reaches any ledger subject

only_repo and unjoined are deliberately different answers. The first is a state difference about somebody both sides know; the second is an identity gap, and only the second is a question about names.

not_modelled is not a disagreement, and there are two reasons

absent is three-valued. null means the ledger carries no leave or return row for that person, so it neither confirms nor contradicts the table's Narrator nieaktywny. Publishing absent: false there would assert presence out of silence — the same defect suspended: false carried for a holder on urlop.

The second reason is legal. A narrator's absence is reportable to the Rada under rozporzadzenia-rady § 7 pkt 6 and carries no public act, unlike the Radny, Namiestnik and MC duties in nerthus-fabularny 6.8, 9.5 and 11.3, which are notices in the Miejsce Publiczne. So ledger silence about a narrator's leave is lawful and expected, and a surface that flagged it would be reporting the regulation working.

Direction and lag

lag pairs each repo event with a ledger event for the same person and comparable op, one to one, greedily by smallest absolute lag, inside pair_window_days.

{ "window_days": 92, "repo_events_in_bound": 91, "ledger_events_in_bound": 44,
  "paired": 26, "repo_first": 14, "same_day": 8, "inverted": 4,
  "unpaired_repo": 65, "unpaired_ledger": 18, "reconciles": true,
  "items": [ { "subject": "Achalen", "repo_op": "appoint", "repo_date": "2025-04-01",
               "ledger_op": "appoint", "ledger_date": "2025-03-31", "lag_days": -1,
               "direction": "inverted", "repo_evidence": [  ], "ledger_evidence": [  ] } ] }

lag_days is ledger.date − repo.date. The expectation is directional and it is an expectation, not a rule. The sprawozdania are monthly, so a repository change normally precedes the report carrying it and the lag is normally positive. A negative lag — inverted — is the interesting case and is counted rather than flagged: it may be a correction, a backdated edit, or a report of a decision the table had not caught up with. What it means is the operator's call.

Both sides are clipped to the bound before pairing. Without that, every ledger event after the clone's last commit is unpairable by construction and unpaired_ledger would measure the clone's age rather than the corpus.

The window lives in data and is published on every read, because it decides a published number. Widening it manufactures pairs; narrowing it manufactures inversions-as-absences.

What is lost projecting each signal, published on every read

lossy ships with the answer rather than living in prose here, because a reader deciding what a disagreement means needs to know which differences the instrument can manufacture on its own.

id signal what is lost
L1 repo the date granularity is a commit, not a day — a change made and reverted between two commits is invisible, and a batching commit dates several changes to one day
L2 repo the table states no interval; presence and absence of a row are the only bounds, so a resignation and a deleted row read alike
L3 both the grade vocabularies overlap rather than match — comparison runs over the shared part only
L4 repo the Ranga cell also carries Rada membership, which the ledger keeps on the rada ref; that difference is not compared and is not a disagreement
L5 both the join runs on display name, and 8 accounts carry more than one — an account with no hit stays unjoined, never guessed
L6 both the table records a permission as state and the ledger records raise as an event; a permission value is not comparable to an op

How the two are joined

The repo signal keys on the margonem profile id in the nick's href, not on the label: 22 accounts appear under 37 labels and 8 carry more than one, so diffing on the label emits a departure and an appointment for the same person on the same day. The #char_… fragment is deliberately not part of the key — that is a character on the account, not an identity.

An account joins a ledger subject when one of its labels matches that subject, or matches a surface that governance/ledger/identity.json states as an alias of it. That file is reused rather than rivalled: it cites the sentence behind each join and abstains where the corpus states none.

An account reaching two or more ledger subjects is not resolved. It becomes an identity_split row carrying every candidate, because a table putting two ledger people on one margonem account is either a join the ledger is missing or a claim it declined, and neither is a parser's to settle.

Reading the Ranga cell

One free-text cell carries three orthogonal facts — grade, Rada membership, activity — and the corpus writes them in any order. The parser collapses whitespace and then consumes known tokens wherever they sit, so Narrator nieaktywny (Radny) and Narrator (Radny) nieaktywny reduce identically and a doubled space stops being a distinct form.

cell function grade rada active
Narrator narrator zwyczajny false true
Narrator (Radny) / Narrator (Radna) narrator zwyczajny true true
Narrator początkujący narrator początkujący false true
Narrator nieaktywny narrator zwyczajny false false
Narrator nieaktywny (Radny) narrator zwyczajny true false
Radny null null true true

A cell the token table cannot fully consume refuses the row and the row is returned under unparsed with its residue named. Narratorka leaves ka and is refused — a matcher testing for the substring narrator would have filed that person into the roster.

Radny alone carries no function token, so the answer is function: null and that person is not in the narrator population. A parser reading that cell as a narrator asserts what it never says.

The permission columns are resolved by name: the header carries Proste narracje in the earliest snapshots and not later, so a reader indexing by position reads the wrong column for those.

A data row need not end with a pipe, and requiring one deletes a tenth of the table in silence

The trailing | is optional here, and that is not tolerance for its own sake. A reader that requires one stops at the first row lacking it and drops every row beneath it — no error, no short read, nothing to notice.

Measured over the 85 commits: a strict reader sees 839 data rows where a tolerant one sees 953. It silently drops 114 rows, 12% of the table, across 26 of the 85 commits, and the first census written for this surface did exactly that and reported no anomalies. At one commit the offending row is second from last, so one narrator vanishes; at another it is the first data row, so eight of nine do.

The distinct-form count is 10 under both readers, which is the reassuring half: the blind spot cost rows, not shapes. It is recorded here rather than only in the code because an instrument narrower than its input returns a clean pass, and a clean pass is what the next reader will see.

See also