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:
{ "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¶
{ "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¶
- The governance ledger — the first signal, its refs, ops and evidence
- Governance —
/governance/permissions, a different subject on the same prefix - Record a sprawozdanie — the monthly chore behind the ledger signal