Skip to content

Repository files navigation

Nornis

Nornis

It's your epic. Every source leaves a mark.

Nornis is a world memory engine for tabletop roleplaying games.

You feed it the raw material of your game — session notes, handwritten pages, uploads, images, maps — and it reads that material and proposes structured knowledge: characters, locations, factions, items, events, storylines, and the facts and relationships that connect them. Nothing enters the record without a human accepting it.

What accumulates is a searchable, cited record of your world that you can ask questions of in plain language, browse as a codex or a graph, walk along a timeline, trace across a map, and selectively share with your players.

Nornis is not a wiki. Sources are raw material; artifacts are memory. The product is the transformation between them.


The loop

Capture a source
      ↓
Async AI extraction
      ↓
Review proposals  ──→  Accept / Edit / Reject
      ↓
Artifacts, facts, and relationships update
      ↓
Ask the Loremaster

What that looks like

You paste a session note:

We questioned Captain Voss in Black Harbor. He denied knowing about the missing caravan, but Tavrin found the Silver Key in his quarters.

Nornis proposes — and waits for you to decide on each one:

  • Create or update Captain Voss, and connect him to Black Harbor
  • Add the claim "Captain Voss denied knowing about the missing caravan"
  • Create or update Silver Key, with the fact "found in Voss's quarters"
  • Open the storyline Missing Caravan, linking Voss, Black Harbor, and the Silver Key

Accept, edit, or reject each proposal. What you accept becomes canon, and every accepted claim keeps a link back to the sentence in the source that produced it.


What it does

Area What it gives you
Capture Session notes, GM prep, handwritten pages (transcribed), an in-app ink canvas, images (vision-read), PDFs and documents, maps (place names and pins read off the image), and links.
Review Every AI suggestion lands in a queue with its confidence, its source, and its rationale. Accept, edit the values first, or reject — individually or in dependency-safe bulk.
Codex Browse everything the world knows as cards, a collapsible tree, or a live force-directed graph. Every artifact shows its facts, truth states, relationships, open questions, and source excerpts.
Ask the Loremaster Ask your world questions in plain language. Answers are grounded strictly in your accepted record plus your indexed library, every claim cited, with a confidence rating — and it says so when it doesn't know.
Timeline, journey & locations Storylines laid out over the real session calendar, the party's trail across a map walked session by session, and the reverse view — pick a place, get every session that visited it.
World digest A short read on where the world stands, on the dashboard, written twice: the full record for GMs, and a party version drawn only from what the party can see.
Secrets & reveals Everything carries a scope — Private, GM only, or Party visible. When the fiction discloses a secret, the GM ticks exactly what the party now learns; Nornis checks the reveal leaves no dangling references. One-way. A convergence gauge ranks what the party is ready to learn next, and players get a "what you learned" view of everything disclosed since they last looked.
Campaigns & characters Campaigns are runs of play with their own page — cast, places, and record assembled from what their sessions cite. A character is somewhere to go: what the record knows about them, grouped for reading about a person, beside a free-form sheet Nornis stores but never interprets and dated snapshots of the paper one.
Library Upload sourcebooks, maps, and handouts. PDFs are indexed into passages so the Loremaster can quote them with page citations.
Continuity health A read on how coherent the record is — contradictions, dangling threads, stale storylines, timeline conflicts, summary drift, and duplicates — each finding with a severity, a jump to the artifact, and most drafting into a reviewable fix.
Onboarding A demo world of five already-extracted sessions plus a sixth left as raw notes, and a two-chapter checklist that detects its own completion from actual state. Dismissable permanently.
Sharing Invite players by link with a role. Optionally give the world a public address for a read-only, party-visible-only view, with a public Ask the Loremaster behind a monthly cap the GM sets.
Cost visibility Every AI call is metered by operation, model, and user, against a per-world daily budget.

Roles are GM, Player, and Observer — and they see genuinely different worlds. A player and a GM asking the Loremaster the same question get different answers, because retrieval respects visibility.

A fuller tour lives on the in-app Features page at /features. Per-feature design docs are in docs/features/, numbered in build order.


Architecture

Three independently deployable services over a shared Clean Architecture core:

nornis-web      Blazor Web App (MudBlazor) — the UI
nornis-api      ASP.NET Core — the HTTP API
nornis-worker   Background processor — drains the extraction queue
Project Role
src/Nornis.Domain Entities, enums, repository interfaces. No EF Core, no Azure, no UI.
src/Nornis.Application Use cases, services, authorization, AI orchestration. Depends on interfaces only.
src/Nornis.Infrastructure EF Core repositories, migrations, blob storage, Service Bus, Azure OpenAI.
src/Nornis.Api Controllers, request/response contracts, auth filters.
src/Nornis.Web Blazor pages and components, API client.
src/Nornis.Worker Queue-driven extraction, indexing, and transcription jobs.

Stack: .NET 10 · Blazor · ASP.NET Core · Azure SQL (EF Core, repository pattern) · Azure Blob Storage · Azure Service Bus · Azure OpenAI · Auth0 (Discord) · Azure Container Apps · GitHub Actions.

Every project has a matching test project under tests/; NUnit throughout.


Running locally

Prerequisites: .NET 10 SDK (see global.json), Docker Desktop, PowerShell 7+.

The local stack runs SQL Server and the Azure Service Bus emulator in Docker, applies migrations, and launches all three apps with connection strings pointed at the containers — so a local run can never touch cloud SQL or compete with the cloud worker for queue messages.

# Everything: containers, migrations, API + Worker + Web
./scripts/start-local.ps1

# Just SQL + Service Bus + migrations
./scripts/start-local.ps1 -InfraOnly

# Tear down
docker compose -f compose.local.yaml down

The only calls that leave your machine are to Azure OpenAI, so the AI does real work. The API and Worker have separate user-secret stores and separate deployments — extraction runs in the Worker, everything conversational runs in the API:

# Worker — extraction (deployment: nornis-extract)
dotnet user-secrets --project src/Nornis.Worker set "Extraction:AiEndpoint" "https://<resource>.openai.azure.com/"
dotnet user-secrets --project src/Nornis.Worker set "Extraction:AiApiKey"  "<your-key>"

# API — Ask the Loremaster, continuity health, retrospectives, library
# retrieval (deployment: nornis-ask)
dotnet user-secrets --project src/Nornis.Api set "Loremaster:AiEndpoint" "https://<resource>.openai.azure.com/"
dotnet user-secrets --project src/Nornis.Api set "Loremaster:AiKey"      "<your-key>"

Neither is required to boot. Without them the app runs fine — capture, browse, review, share — and the AI paths fail with an explicit "not configured" message rather than a crash.

The AiModel values in appsettings.json are Azure OpenAI deployment names, not model names, and the ModelPricing key must match, or cost tracking silently records $0.

Build and test

dotnet build Nornis.sln
dotnet test --solution Nornis.sln

# One project
dotnet test --project tests/Nornis.Application.Tests/

# One fixture
dotnet test --project tests/Nornis.Application.Tests/ --filter "FullyQualifiedName~LibraryServiceTests"

Warnings are errors (Directory.Build.props), so a clean build is the bar.

Coverage and risk

Coverage is signal here, never a gate — see .kiro/steering/testing-strategy.md.

./scripts/coverage.ps1              # collect, merge, open the HTML report
./scripts/coverage.ps1 -Projects Domain,Application
./scripts/crap-report.ps1           # rank methods by complexity against coverage

Every push to main publishes both to https://status.nornis.app — per-assembly trend and the CRAP hotspot table, which is the test-writing backlog ordered by risk rather than by whatever a percentage is shouting about. The full annotated report stays a build artifact on the workflow run.


Deployment

Pushing to main deploys. .github/workflows/deploy.yml builds the three images in parallel with the test run, rolls the Azure Container Apps forward, then polls /health until the new revision serves and fails loudly if it never does — gated on tests passing, so images for a failing commit are tagged but never deployed. Pull requests run ci.yml instead: restore, build, test, vulnerable-package scan, format.

EF migrations are not applied by the pipeline. They are run by hand, before pushing the commit that needs them, and must stay additive so the old revision keeps serving through the rollout:

dotnet ef database update --project src/Nornis.Infrastructure --startup-project src/Nornis.Api

The design-time factory reads the API's user secrets. Set ConnectionStrings:DefaultConnection there to the production server and database with Authentication=Active Directory Default and no password: your az login is then the credential, and it must be the SQL server's Entra admin (or a contained user). If Visual Studio is signed in as a different account it sits ahead of the CLI in the credential chain and the login fails — pin the chain with AZURE_TOKEN_CREDENTIALS=AzureCliCredential, which .claude/launch.json already does for the local API. Since 2026-09-08 the deployed apps reach SQL, Blob Storage and Service Bus the same way, as their managed identities — see .kiro/steering/azure-hosting.md.

Miss that step and /health returns 503 until it is run — see docs/runbooks/migration-missed.md.

Infrastructure is provisioned by scripts/provision-azure.ps1.


Operations

Surface Answers
GET /health (API) Is this deploy broken? Pending migrations only; names whatever is failing. Backs the Container Apps readiness probe, the deploy poll, and the ping-nornis-api-health availability test.
GET /status (API) Are the dependencies healthy? SQL, blob storage, Service Bus, Azure OpenAI, worker heartbeat. Anonymous; names and verdicts only.
https://status.nornis.app Both dashboard faces. Hosted on GitHub Pages, deliberately outside Azure, so it still loads when the system it reports on does not.

docs/runbooks/ has one doc per nameable failure mode, and every Azure alert links to its own from its description. ./scripts/dlq.ps1 peeks, resubmits and purges dead-lettered messages; ./scripts/ai-pause.ps1 stops every paid AI call without a redeploy. The apps hold no credentials for SQL, Blob Storage or Service Bus — each reaches them as its managed identity, and so do these scripts, as your az login.


Repository layout

src/            Application source (see the table above)
tests/          One test project per source project
ci/pages/       The static engineering dashboard published to status.nornis.app
docs/features/  Per-feature design docs, numbered in build order (index in its README)
docs/plans/     Backlog specs; docs/future-features.md holds the execution order
docs/runbooks/  One doc per nameable failure mode, linked from every Azure alert
docs/           Images
scripts/        Local stack, provisioning, note import, coverage, CRAP, dead-letter queue
.kiro/steering/ Product vision, architecture, and standards that guide the build

Two documents are worth reading before changing anything: .kiro/steering/product-vision.md for what Nornis is trying to be, and .kiro/steering/coding-standards.md for how it's built.


Conventions

  • Domain vocabulary is deliberate: Storyline (never "Thread"), Source (never "Evidence"), Artifact, Fact, Relationship, Canon, Reveal.
  • Repository pattern over EF Core — application services never touch DbContext.
  • Authorization is enforced server-side, in application services.
  • AI proposes; a human decides. Nothing mutates canon on its own.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages