.NET API
TraconSessionOwnershipOptions
Tracon.Abstractions.dllTurns per-user session ownership on, a second boundary drawn UNDER the tenant.
public sealed class TraconSessionOwnershipOptionsInheritance
Section titled “Inheritance”object ← TraconSessionOwnershipOptions
Inherited Members
Section titled “Inherited Members”object.GetType(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Remarks
Section titled “Remarks”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.
Constructors
Section titled “Constructors”TraconSessionOwnershipOptions()
Section titled “ TraconSessionOwnershipOptions()”public TraconSessionOwnershipOptions()Fields
Section titled “Fields”SectionName
Section titled “ SectionName”Gets the configuration section name.
public const string SectionName = "Tracon:SessionOwnership"Field Value
Section titled “Field Value”Properties
Section titled “Properties”Enabled
Section titled “ Enabled”Gets or sets whether a session records the user it belongs to. Default false: nothing changes.
public bool Enabled { get; set; }Property Value
Section titled “Property Value”Remarks
Section titled “Remarks”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.
ManagementPolicy
Section titled “ ManagementPolicy”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; }Property Value
Section titled “Property Value”Remarks
Section titled “Remarks”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.
RefuseUnownedSessions
Section titled “ RefuseUnownedSessions”Gets or sets whether access to an EXISTING session row that carries no owner is REFUSED. Default false.
public bool RefuseUnownedSessions { get; set; }Property Value
Section titled “Property Value”Remarks
Section titled “Remarks”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.
RequireAuthenticatedOwner
Section titled “ RequireAuthenticatedOwner”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; }Property Value
Section titled “Property Value”Remarks
Section titled “Remarks”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.