Skip to content
Tracon

IRunAuthorizationHandler

Namespace Tracon · Assembly Tracon.Abstractions.dll

Decides whether a caller may start a run or read/write a session.

public interface IRunAuthorizationHandler

Tracon draws ownership at the TENANT level. Without this handler, every Operator in a tenant can start a run as, read, and delete every other user’s runs and sessions in the same tenant. This interface does not teach Tracon that boundary — it asks the consumer to enforce their own.

One narrower boundary IS built in, for sessions only: TraconSessionOwnershipOptions records which user opened a session and narrows the session listing to that user. It is off by default, and it does not replace this handler — it answers “which user owns this session” so that a handler no longer has to reject a whole listing just to keep users apart, and leaves every other question (runs, attachments, approvals, scores, per-project rules) to the consumer’s own policy. The two compose: both are consulted, and either can refuse.

The default implementation (AllowAllRunAuthorizationHandler, registered with TryAdd) allows every call, so an installation that registers nothing keeps today’s behavior exactly. A consumer replaces the registration to enforce their own rule.

If this handler throws, the call is denied (fail-closed). A gate that fails open on an exception is not a gate. The same rule IToolAuthorizationHandler follows.

A throw is reported as a failed check, not as a decision. Tracon writes one error log line in the Tracon.RunAuthorization category with the handler type, the access kind and the tenant, agent, run and session identifiers, and the exception. The exception text never reaches the HTTP response. A 403 then says that the check failed and can be retried; a 404 stays identical to the answer for a resource that does not exist. An OperationCanceledException that the request’s own cancellation did not cause (for example, an HttpClient timeout inside the handler) counts as a failure too.

The implementation must be a singleton, for the same reason as IRunAttributionContext: singleton services take a dependency on it, and a scoped registration would be a captive dependency. Resolve per-request state through IHttpContextAccessor.

Tenant mode: expected tenant. Every request carries its own TenantId, resolved by the caller before the handler is invoked; the handler never reads ITenantContext itself. This is deliberate: a queued run’s authorization is checked before the tenant’s ambient scope (AmbientTenantScope) is necessarily active.

Delivery guarantee: no delivery guarantee applies. This is a synchronous decision, not a retried or queued operation — each call is made once, on the request thread, and its result is not persisted or replayed.

AuthorizeRunAsync(RunAuthorizationRequest, CancellationToken)

Section titled “ AuthorizeRunAsync(RunAuthorizationRequest, CancellationToken)”

Decides whether the run described by request may start.

ValueTask<RunAuthorizationResult> AuthorizeRunAsync(RunAuthorizationRequest request, CancellationToken cancellationToken = default)

request RunAuthorizationRequest

The run being authorized.

cancellationToken CancellationToken

The cancellation token.

ValueTask<RunAuthorizationResult>

The decision.

AuthorizeSessionAsync(SessionAuthorizationRequest, CancellationToken)

Section titled “ AuthorizeSessionAsync(SessionAuthorizationRequest, CancellationToken)”

Decides whether the session access described by request may proceed.

ValueTask<RunAuthorizationResult> AuthorizeSessionAsync(SessionAuthorizationRequest request, CancellationToken cancellationToken = default)

request SessionAuthorizationRequest

The session access being authorized.

cancellationToken CancellationToken

The cancellation token.

ValueTask<RunAuthorizationResult>

The decision.