The fortress of the Æsir, where law is declared and the cosmos answers to it.
Image credit: @norsemythologyclips — go follow them.
Declared law for the Norse Architecture — Norse.Abstractions: the contracts and rules every realm must honor. No implementations live here, by design. Seven assemblies plus two source generators, split by dependency wall and consumer context:
| Assembly | Upstream | Purpose |
|---|---|---|
Norse.Abstractions.Contracts |
Norse.Primitives |
Outcome<T> with Problem/ErrorCategory/BoolResponse/Unit, and ErasureReceipt; Problem.Code/Problem.NextUrl (a machine-readable refinement and an advisory server-authored next hop) with NorseErrorCodes (reauthentication-required) |
Norse.Abstractions.Components |
Abstractions.Contracts, ASP.NET Core Components |
Razor component base abstractions (AsyncComponentBase, IAppShellLayout, IDashboardWidget) plus the outcome-form seam hoisted from Heimdall (OutcomeFormComponentBase — forms submit only when interactive (a statically rendered submit never dispatches; bind Disabled="!CanSubmit") — and the ServerValidation/ bridge) — MAUI/WASM-safe, no server-side types; the non-form outcome dispatch sibling OutcomeComponentBase (click triggers bind Disabled="!CanAct" — interactive and not dispatching; loaders are not gated); both bases soft-navigate a reauthentication-required refusal to its NextUrl when LocalNavigation passes it, and render every other failure; and the authorization vocabulary — policy names (NorsePolicies, including RecentAuthentication), lanes (NorseLanes), audiences (NorseAudiences), auth_time (NorseClaimTypes) and the policy helpers (NorseLanePolicyExtensions: RequireLane, AllowAnyLane, RequireAudience, CloseLaneByDefault) |
Norse.Abstractions.Backend |
.Contracts |
The read contract (IReadRepository<TView>, IViewBearer<TView>), the serialization seam, and the key custody seam |
Norse.Abstractions.Web.Server |
.Backend, FluentValidation |
The mediator law (IRequest/ICommandRequest/IQueryRequest, ISender, IRequestHandler, IBehavior, CommandRequest, CommandRequestValidator), ReauthenticationLandingAttribute (the local page a request lands back on after reauthentication), the gRPC facade (GrpcControllerBase, ProblemErrorEntry), and IDeferredSignIn |
Norse.Abstractions.Worker |
.Backend |
Declared and deliberately empty — worker-side contracts land with their first consumer |
Norse.Abstractions.Migrations |
none | IMigrationContributor and ISeedContributor (EF-free) |
Norse.Abstractions.Emit |
none | The generator-authoring toolkit (CSharpEmit, Utf8NoBom) — netstandard2.0 so source generators across the platform can consume it |
Abstractions.Web.Server.Generator |
.Emit |
HandlerRegistrationGenerator — compile-time handler/dispatch/validator registration (AddNorse{Realm}Handlers()), bundled inside the Norse.Abstractions.Web.Server package |
Abstractions.Policy.Analyzers |
none | NorsePolicyDeclarationAnalyzer (NORSE015) — validates [NorsePolicy] declarations at the source that authors them, bundled inside the Norse.Abstractions.Components package |
Worker and Web.Server are mutually invisible — neither references the other.
Arrows point at the thing depended on. The isolated nodes are deliberate: Migrations and Emit ride on nothing so anything can ride on them, and Components touches only the Razor framework so it compiles into WASM and MAUI unburdened.
flowchart BT
subgraph Asgard["Asgard — Norse.Abstractions"]
Contracts
Components
Backend
WebServer["Web.Server"]
Worker
Migrations
Emit
Generator["Web.Server.Generator (gen)"]
end
subgraph Svartalfheim["Svartálfheim"]
Primitives["Norse.Primitives"]
end
Contracts --> Primitives
Backend --> Contracts
WebServer --> Backend
Worker --> Backend
Generator --> Emit
Generator -.->|"bundled analyzer inside the Web.Server package"| WebServer
Outcome<T>— the platform's second discriminated union and the interior half of the two unions: it faces operations inside the host and describes an event — "this operation ran; here is how it went" — where Svartálfheim'sResult<T>faces the boundary and describes data. A hand-rolledsealed classunion,[MustConsume]-decorated, deliberately starved API (Ok/Err/Match— no typed happy-path accessors, so the unhappy path cannot be politely glanced past). It is never serialized, stored, or compared; each transport edge translates it into its native tongue. Live today: Blazor components consumeTask<Outcome<T>>from the gRPC service contracts and pattern-match the result.- The mediator law is server-only, deliberately — the marker family lives in
Web.Serverso wire records shipped to WASM stay pure[DataContract]shapes with zero mediator coupling. Realms give a wire DTO server-side identity by derivingCommandRequest<TRequest,TResponse>, and the same client-side FluentValidation class runs again on the server throughCommandRequestValidator. - The registration generator rides the contract package — it discovers a realm's
IRequestHandler/IValidatorimplementations at compile time and emits DI registration, replacing assembly scanning. It lives here (not in the infrastructure realm) because handler-bearing realms may legally depend only on declared law; duplicate handlers and requests without an[Authorize]policy are build errors (NORSE010/NORSE011). - A lane is the kind of credential, and every policy is closed by default —
NorseLanesnames the three ways in:Identity(the browser's cookie),User(a user bearer token from the authorization code flow) andMachine(a machine token from client credentials, for the REST façade). Midgard stamps the lane itself, admits a bearer into a lane only when its audience matches that kind of token, and closes every declared policy that names no lane toIdentityalone (CloseLaneByDefault);AllowAnyLane()is the explicit, greppable opt-out. A bearer-admitting policy names its lane withRequireLaneand pins its audience withRequireAudience.NorsePolicies.Machinenow means exactly that: theMachinelane plus theFacadeaudience — never a cookie, never a user bearer. - Freshness stacks on an operation's own policy —
NorsePolicies.RecentAuthenticationchecksauth_timeagainst the configured window and never narrows the lanes. A stale caller is refused asForbidden+reauthentication-required, with a server-authoredNextUrlto sign in again and land back on the request's[ReauthenticationLanding]page; the component bases take that hop only when the URL is local. IMigrationContributor— the thinnest contract in the platform, live on NuGet, and the seed behind the migrations framework proven end to end across six realms (the full story is on Bifröst's README). NoOrder, noDependsOn: sequencing between contributors would couple bounded contexts.
Egress contracts (Norse.Abstractions.Backend.Egress) are staged, not yet executed. Every new surface follows the spec-first discipline: brainstorm → spec → plan in Glitnir's docs/Asgard/, greenlit by the human, then code.
dotnet build Asgard.slnx # warnings are errors — a single warning fails
dotnet test Asgard.slnx # xUnit v3 + Shouldly on Microsoft.Testing.PlatformRequires the .NET 11 preview SDK pinned by global.json. The realm builds standalone — it is its own clone target, not only a Bifröst submodule.
Project folders and .csproj files are brand-free (src/Abstractions.Contracts/Abstractions.Contracts.csproj); the realm's root Directory.Build.props injects AssemblyName and RootNamespace as Norse.$(MSBuildProjectName). Fork it, change Norse once, and every build output carries your brand — the namespace Norse.* declarations in code are yours to cull deliberately, with no filesystem change either way.
Asgard is one realm of the Norse Architecture. The whole platform composes at Bifröst — clone once, cross the bridge. Every design is tried in Glitnir, the design court, before law is declared here; this realm's specs and plans live in the court's docs/Asgard/.

