Class BowireTenancy

Namespace
Kuestenlogik.Bowire.Auth
Assembly
Kuestenlogik.Bowire.dll

Routes each call to the slot of whichever identity is currently being served (#97).

public sealed class BowireTenancy : IBowireUserStore, IBowireStorageRootProvider
Inheritance
BowireTenancy
Implements
Inherited Members

Remarks

The stores are static — EnvironmentStore, RecordingStore and the rest resolve their paths through BowireUserContext with no request in sight. That was the point of the Phase B seam: the stores keep their shape and the seam becomes identity-aware underneath them. Doing that in a server means the "who" has to travel with the execution context rather than through a parameter, so it rides an AsyncLocal<T>. That is ambient state, which is normally worth avoiding; here it is the only shape that reaches a static store from a request without rewriting every store to take a user.

What happens with no identity. Background work has no request — the plugin update check, a warm-up, a hosted service. Those fall through to the store the host was using before tenancy was enabled, which keeps process-wide state process-wide instead of filing it under whoever happened to be served last. Requests do not take that path: in multi-tenant mode Bowire's endpoints require an authenticated caller, so a request without a subject has already been rejected before a store is touched.

Constructors

BowireTenancy(string, IBowireUserStore)

Tenancy rooted at storageRoot.

public BowireTenancy(string storageRoot, IBowireUserStore shared)

Parameters

storageRoot string

The data root — resolved once, before this replaces Current. Reading it afterwards would ask this very object where the root is.

shared IBowireUserStore

Where calls with no identity go. Normally the store the host had before, i.e. the flat single-user layout.

Properties

CurrentSubject

The subject being served on this execution context, or null outside a request.

public static string? CurrentSubject { get; }

Property Value

string

StorageRoot

The storage root this store resolves under — the directory the Data scope means, not the per-identity slot inside it.

public string StorageRoot { get; }

Property Value

string

UsersRoot

The directory holding every identity's slot.

public string UsersRoot { get; }

Property Value

string

Methods

Enter(string)

Serve subject until the returned scope is disposed.

public static IDisposable Enter(string subject)

Parameters

subject string

Returns

IDisposable

Remarks

Restoring the previous value rather than clearing it: a nested scope is legitimate — an admin acting on another identity's behalf (#98) is exactly that — and clearing would silently drop the outer identity for the remainder of the request.

EnumerateSlots()

The slot directories that exist on disk, by name.

public IEnumerable<string> EnumerateSlots()

Returns

IEnumerable<string>

Remarks

Directory names, not subjects: the mapping only runs one way, which is what lets the slot name be readable without the subject having to be recoverable from disk. Callers that need the subject already have it.

For(string)

The store for subject, whoever is being served.

public ScopedBowireUserStore For(string subject)

Parameters

subject string

Returns

ScopedBowireUserStore

Remarks

Cached because GetUserPath(string) sits on read paths that run per request, and building a slot hashes the subject.

GetUserPath(string)

Absolute path to the file filename under the active user's scope. Implementations are free to colocate users (single shared dir, the default) or partition by identity (per-user subdirectory, the multi-tenant case).

public string GetUserPath(string filename)

Parameters

filename string

Trailing-component file name without any directory prefix -- e.g. "environments.json", "recordings.json". Implementations decorate it with whatever path makes sense for their model.

Returns

string