Logs & audit¶
Transcripts and the audit trail. The daemon archives Margonem session logs under
nerthus.logs/, parses transcript text into structured lines, and keeps three rolling
operational streams (operational, request, audit) plus two derived audit views —
the transaction ledger and the notification log. This page covers fetching, parsing,
refreshing a session's logs, reading a stream, and reading the audit views.
Reads need log.read; the transaction ledger needs currency.read; fetching and
refreshing need log.fetch and are Write routes (they write the committed archive,
so they pass the schema/write gate). The examples stay offline: the
fixture session
2026-07-01, Eraster rozmawia z Tussalem, Anward carries no @Logi, so nothing reaches
the network. Every example runs against a live daemon — see
how the reference is tested.
Routes¶
| Method | Path | Cmdlet | Cap | Write |
|---|---|---|---|---|
| POST | /logs/fetch |
Invoke-NerthusSessionLogFetch |
log.fetch |
✓ |
| POST | /logs/parse |
Get-NerthusSessionLogParsed |
log.read |
— |
| POST | /sessions/{header}/logs/refresh |
Get-NerthusSessionLog -Refresh / -FetchMissing |
log.fetch |
✓ |
| GET | /logs/{stream} |
Get-NerthusLog (operational/request/audit) |
log.read |
— |
| GET | /audit/changes |
Get-NerthusChangeLog |
log.read |
— |
| GET | /audit/ledger |
Get-NerthusTransactionLedger |
currency.read |
— |
| GET | /audit/notifications |
Get-NerthusNotificationLog |
log.read |
— |
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.
The log-fetch routes carry the Write flag because every fetch writes the committed nerthus.logs/ transcript archive (Logs).
Fetch logs into the archive¶
POST /v1/api/logs/fetch pulls transcripts into nerthus.logs/. A target is chosen from
the body (request, url, or a session header); with none given it mass-fetches every
session's @Logi. The summary counts each URL by outcome. No session carries @Logi
here, so the mass run has nothing to pull and stays offline. This is a Write route.
Response 200:
{ "Total": 0, "Fetched": 0, "Unchanged": 0, "Archived": 0, "Failed": 0, "Skipped": 0, "FailedUrls": [] }
Parse transcript text¶
POST /v1/api/logs/parse parses inline content (or fetches a url) into a structured
transcript: the detected Format, a LineCount, resolved Lines, and any
LocationSegments. Passing content keeps it offline and deterministic.
Response 200 (trimmed):
Rejected: nothing to parse¶
With neither content nor url, parse is 400.
Response 400:
Refresh a session's logs¶
POST /v1/api/sessions/{header}/logs/refresh re-pulls and re-parses the session's @Logi,
returning the header and each log's fetch state. { "mode": "missing" } fetches only the
uncached logs; otherwise it forces a full re-pull. The fixture session has no @Logi, so
Logs comes back empty and nothing hits the network. This is a Write route.
const header = "2026-07-01, Eraster rozmawia z Tussalem, Anward";
await fetch(`https://evocation.nerthus.pl/v1/api/sessions/${encodeURIComponent(header)}/logs/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
body: JSON.stringify({}),
}).then((r) => r.json());
Response 200:
Read a log stream¶
GET /v1/api/logs/{stream} returns the last 200 lines of a rolling stream as the
{ stream, count, items } envelope. The stream is one of operational, request, or
audit. Here, the operational stream.
Response 200 (trimmed):
The request stream logs each HTTP request:
The audit stream logs each mutation:
Rejected: unknown stream¶
A stream name outside the three is 404.
Response 404:
The change audit¶
GET /v1/api/audit/changes is the audit stream under an audit-facing path — the
{ stream, count, items } envelope of recorded mutations.
Response 200 (trimmed):
The transaction ledger¶
GET /v1/api/audit/ledger returns the dated ledger of applied @Transfer moves, newest
first, as { count, items } — needs currency.read. No transfers have been applied, so
it is empty.
Response 200:
The notification log¶
GET /v1/api/audit/notifications reconstructs @Intel notification intent from the
sessions as { count, items }; filter with ?operation=. The fixture session records no
@Intel, so it is empty.
Response 200: