Skip to content
Tracon

TraconSessionOwnershipOptions

Namespace Tracon · Assembly Tracon.Abstractions.dll

Turns per-user session ownership on, a second boundary drawn UNDER the tenant.

public sealed class TraconSessionOwnershipOptions

objectTraconSessionOwnershipOptions

object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

Read from the Tracon:SessionOwnership configuration section.

Default off, and off means nothing changes. An application that registers nothing keeps exactly its current behaviour: no owner is written, no listing is filtered, and sessions.owner_id stays NULL. The whole feature is inert until a deployment asks for it.

This type lives in the abstractions package because the ownership rule is enforced in four places that do not see each other — the session manager that stamps the owner (Tracon.Core), the listing endpoint that narrows the query (Tracon.AspNetCore), the SQL stores that carry the column (Tracon.PostgreSql, Tracon.SqlServer, Tracon.Sqlite) and ISessionStore’s own contract, whose SessionRecord.OwnerId documentation cannot describe the rule without naming this type. One shared type keeps them from drifting apart; the same reason TraconMcpSecurityOptions lives here.

Ownership is not a replacement for IRunAuthorizationHandler. The handler answers a question Tracon asks; this option lets Tracon answer part of it itself, so a consumer no longer has to keep its own session-to-user table just to build a “my conversations” list. With ownership on, a handler that used to reject a whole listing to keep users apart no longer needs to — the listing arrives already narrowed.

public TraconSessionOwnershipOptions()

Gets the configuration section name.

public const string SectionName = "Tracon:SessionOwnership"

string

Gets or sets whether a session records the user it belongs to. Default false: nothing changes.

public bool Enabled { get; set; }

bool

With this on, a session opened through the HTTP surface is stamped with the identity IRunAttributionContext resolves, and GET /api/sessions returns only the caller’s own sessions unless the caller satisfies TraconSessionOwnershipOptions.ManagementPolicy.

Rows written while it was off keep a null owner forever — Tracon cannot invent an owner for a session it did not watch being opened. Those rows disappear from owner-filtered listings and stay in management listings. Turning the option on is therefore safe for existing data, but it is not retroactive.

Gets or sets the authorization policy whose holders see the WHOLE tenant’s sessions instead of only their own. Default TraconPolicies.Operator.

public string? ManagementPolicy { get; set; }

string?

Evaluated per request. A caller who satisfies it gets today’s unfiltered tenant listing — including the unowned rows written before ownership was turned on, which is the only way those rows stay discoverable. Every other caller gets a listing narrowed to their own identity.

Fail-closed. If the policy is not registered in the application’s authorization configuration, or evaluating it throws, the caller is treated as an ordinary user and the listing is narrowed. A missing policy must not hand out an unfiltered listing — that is the one failure mode this whole option exists to prevent. Set it to null or an empty string to state deliberately that no caller gets an unfiltered listing over HTTP.

It grants no access to another user’s session: reading, deleting and branching a session that is OWNED by somebody else answers 404 regardless of policy, because a management role is not a reason to leak one user’s conversation to another. The asymmetry is deliberate — a listing is an operation with no single identity to leak; an owned session is a resource whose content belongs to one user.

The one individual access it does govern is TraconSessionOwnershipOptions.RefuseUnownedSessions: an UNOWNED row belongs to nobody, so there is no user whose conversation could leak, and refusing it to the same caller who can already see it in the management listing would leave support looking at a row it cannot open. That exemption covers reading a session, never continuing one — see that property.

The default is written as a literal because TraconPolicies lives in Tracon.AspNetCore, which this package cannot see, while Tracon.Core has to read the two options above. The two spellings are held together by a test rather than by the compiler; use the constant, not the literal, when you name this policy in your own code.

Gets or sets whether access to an EXISTING session row that carries no owner is REFUSED. Default false.

public bool RefuseUnownedSessions { get; set; }

bool

Only consulted while TraconSessionOwnershipOptions.Enabled is on; on its own this setting does nothing at all.

What it changes. Rows written before ownership was turned on carry a null owner forever, and ownership is not retroactive. By default those rows keep the tenant-wide reachability they had the day before the flip: they vanish from every owner-filtered LISTING but are still readable one by one by anybody in the tenant. Turn this on and they are refused instead — read, delete, branch and voice answer the endpoint’s own “session not found”, and a run that names one is refused with 403. Listing behaviour does not change; it already excluded them.

What it deliberately does not change. A session that does not exist yet is untouched: the first turn still opens it and claims it, which is how every owned session is born. “Not created yet” and “created without an owner” are different rows and get different answers.

A caller who satisfies TraconSessionOwnershipOptions.ManagementPolicy still reaches an unowned row through the session resource endpoints, so support and audit keep the access they had — the same reason those rows stay in the management listing. That exemption covers reading a session, not continuing one: a run that names an unowned session is refused for every caller, because starting a run appends to the conversation as somebody else.

Default off because turning it on strands every conversation that was live at the moment ownership was enabled. Turn it on once those conversations no longer matter, or in a deployment that enabled ownership from its first day and therefore has no unowned rows at all.

Gets or sets whether a session write is REJECTED when no authenticated identity can be resolved to own it. Default true.

public bool RequireAuthenticatedOwner { get; set; }

bool

Only consulted while TraconSessionOwnershipOptions.Enabled is on. The rejection is TraconSessionOwnerRequiredException, which the HTTP surface answers with 403.

Turning this off is a deliberate weakening, not a convenience: an unowned session is invisible to every owner-filtered listing, so the caller writes a conversation they can never list again. It exists for the mixed deployment that runs owned and unowned traffic side by side through the same endpoints during a migration.