Skip to content

Nerthus.Lang.Annotations

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

Two sample stores' contracts and their gates. It began as one and gained a second on 2026-08-31.

Read at nerthus.lang.annotations 980bd0b (2026-08-26), plus the finetuning half added 2026-08-31.

What it owns

The labelled-sample store — span labels over a closed vocabulary:

  • schema.json - what a labelled sample must look like.
  • tools/validate.py, the validator that enforces it, and the back-fills that cut rows out of each project's measured output.

The finetuning store — chat exchanges for training a generative model:

  • finetune-schema.json - what a finetuning sample must look like.
  • tools/validate_finetune.py, its gate, and the harvest and seed scripts that fill it.

The rows themselves live in repozytorium-adnotacji and repozytorium-probek, which are data repositories. This one holds only the contracts and the things that check them.

Why two stores rather than one with a flag

An annotation row says which of a closed set of labels applies to a span. A finetuning row carries a chat exchange whose assistant turn is free text. Everything the first store's machinery is built out of — the label vocabulary, the claim key and its fold, the evidence form, the rejection-expressibility partition — has no analogue in the second, and the census the first publishes is computed over exactly those axes.

Putting free-text rows into that store would not have been a widening; it would have made three published figures meaningless and put a class on neither side of a partition that reconciles. So the second store copies the machinery and shares none of the files: the same posture about grades, the same all-or-nothing deposit, the same env-var refusal, a validator of its own.

They do share one hazard, and it is guarded: NERTHUS_FINETUNE_ROOT pointed at the annotation store is refused by name, because that store also has a samples/ directory and the run would otherwise report one failure per class and per row instead of one refusal naming the mistake.

It is dependency-free Python 3, standard library only, and that is why it is not part of Nerthus.Lang. Lang's environment is Morfeusz plus a spaCy model on a runner of its own. Putting a zero-dependency gate behind that environment would make the gate unrunnable whenever the annotation job is, which is precisely when you want a gate.

Where its CI runs

Two jobs, gate-controls and store-schema, on the nerthus-amd64 runner.

store-schema assembles a whole programme checkout before it validates anything: the sample store, the lore repository, the transcript archive, the annotation tree and the works repository, plus the governance corpus fetched over HTTP. The rows it checks carry locators into those trees, so validating them without the trees would check the shape of a row and not whether it points at anything.

What it pins, and what pins it

Nothing either way. It is a gate, and what it holds is a schema rather than a version.

What breaks if it stops

Rows enter the sample stores ungated. Nothing fails at the moment it happens, which is the whole argument for the gate: a malformed row is discovered by whatever reads it next, and the surfaces that read these stores are the ones that decide what the tooling believes about the corpus.

For the finetuning half the consequence is one step further out, because its rows leave the estate. An unchecked row is trained on, and what it taught is discovered — if ever — in a model's output.

What the gate cannot do, stated because it was learned the hard way

It checks that a row is shaped like a row. It cannot check that the text is right. The first harvest into the finetuning store deposited 1 304 rows that passed every schema check and whose assistant turns carried, in 100 % of cases, the URL of the transcript they were cut from — the gold answer teaching a model to cite its own source. A URL inside a summary is well-formed.

Content tests therefore sit beside the schema gate rather than inside it (tools/test_harvest_cleaner.py), and every case in that file is a defect that shipped.

See also