Skip to content

Name resolution

Every name in the lore is Polish and inflected — a session note writes Erastera, Lordowi Tussalowi, Opatowi Perrinowi, and the resolver maps each surface form back to the canonical entity. It runs a four-stage pipeline (exact → declension → consonant alternation → fuzzy) over an in-memory name index built from the entity model. This page covers resolving one name, resolving a batch, inspecting a single index token, and rebuilding the index. The examples use the canonical session fixtures: the characters Eraster and Lord Tussal, and the NPC Opat Perrin.

Resolution reads need entity.read; rebuilding the index needs admin.index. Every example runs against a live daemon — see how the reference is tested. The resolution model is specified in name resolution.

Routes

Method Path Cmdlet Cap Write
POST /resolve Resolve-NerthusName entity.read
POST /resolve/batch Resolve-NerthusName (array input) entity.read
GET /name-index/lookup/{token} — (raw index diagnostic) entity.read
POST /name-index/rebuild — (in-memory rebuild) admin.index

Cap is the required capability ( = public, no token); Write () marks routes that pass the write gate; a in Cmdlet is reached directly, no wrapper. Paths are relative to /v1/api; the cross-cutting contract — middleware, envelopes, status codes — is on the API reference index.

POST /resolve body: name (or queries[] on /resolve/batch), typ (alias type), within, activeOn, noFuzzy, topN, maxDistance — resolver semantics in Name resolution.

Resolve one name

POST /v1/api/resolve takes { name } and returns the winning owner: { name, typ, plik, stage, confidence, ambiguous, candidates }. stage records which pipeline step matched (exact here); ambiguous is true only when a homonymous token has more than one owner. An unresolvable name returns 200 with a null body.

POST /v1/api/resolve
Content-Type: application/json
Authorization: Bearer <token>

{ "name": "Eraster" }
await fetch("https://evocation.nerthus.pl/v1/api/resolve", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Eraster" }),
}).then((r) => r.json());

Response 200:

{ "name": "Eraster", "typ": "Postać", "plik": null, "stage": "exact",
  "confidence": 1.0, "ambiguous": false, "candidates": ["Eraster"] }

Resolve a multi-word name

The full canonical form resolves at the exact stage too — the whole string is a priority-1 token in the index.

POST /v1/api/resolve
Content-Type: application/json
Authorization: Bearer <token>

{ "name": "Lord Tussal" }
await fetch("https://evocation.nerthus.pl/v1/api/resolve", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ name: "Lord Tussal" }),
}).then((r) => r.json());

Response 200:

{ "name": "Lord Tussal", "typ": "Postać", "stage": "exact",
  "confidence": 1.0, "ambiguous": false, "candidates": ["Lord Tussal"] }

Resolve a batch

POST /v1/api/resolve/batch takes { queries } and returns the { count, items } envelope, one { token, resolved, typ, stage } per query in request order. A miss keeps its token with resolved: null and stage: "miss". Here all three resolve.

POST /v1/api/resolve/batch
Content-Type: application/json
Authorization: Bearer <token>

{ "queries": ["Eraster", "Lord Tussal", "Opat Perrin"] }
await fetch("https://evocation.nerthus.pl/v1/api/resolve/batch", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
  body: JSON.stringify({ queries: ["Eraster", "Lord Tussal", "Opat Perrin"] }),
}).then((r) => r.json());

Response 200:

{ "count": 3, "items": [
  { "token": "Eraster", "resolved": "Eraster", "typ": "Postać", "stage": "exact" },
  { "token": "Lord Tussal", "resolved": "Lord Tussal", "typ": "Postać", "stage": "exact" },
  { "token": "Opat Perrin", "resolved": "Opat Perrin", "typ": "NPC", "stage": "exact" }
] }

Inspect an index token

GET /v1/api/name-index/lookup/{token} reads one raw token straight out of the index, without running the pipeline — it returns { source, ambiguous, owners }. The index is case-insensitive, so the lowercase eraster finds the Eraster token.

GET /v1/api/name-index/lookup/eraster
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/name-index/lookup/eraster", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 200:

{ "source": "Eraster", "ambiguous": false,
  "owners": [{ "name": "Eraster", "type": "Postać", "priority": 1 }] }

Rejected: token not indexed

A token that is not a surface form of any entity is 404 — the lookup is a direct index probe, not a fuzzy search.

GET /v1/api/name-index/lookup/nieistniejacy
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/name-index/lookup/nieistniejacy", {
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 404:

{ "error": "token not indexed" }

Rebuild the index

POST /v1/api/name-index/rebuild re-parses the entity model and rebuilds the index in place, returning { rebuiltAt, buildMs, indexStats } — where indexStats carries the token and stem counts. It needs admin.index.

POST /v1/api/name-index/rebuild
Authorization: Bearer <token>
await fetch("https://evocation.nerthus.pl/v1/api/name-index/rebuild", {
  method: "POST",
  headers: { "Authorization": `Bearer ${token}` },
}).then((r) => r.json());

Response 200:

{ "rebuiltAt": "2026-07-01T12:00:00.0000000Z", "buildMs": 8,
  "indexStats": { "tokenCount": 61, "stemCount": 54 } }