Skip to content

AuditingSessionStore

Namespace AgentPrism · Assembly AgentPrism.Core.dll

Wraps ISessionStore in a decorator that writes an audit trail only for delete operations.

public sealed class AuditingSessionStore : ISessionStore, IAuditDecorated

objectAuditingSessionStore

ISessionStore, IAuditDecorated

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

Session saves occur on every turn. Auditing them would make the audit trail the highest-volume table. Only deletion, an irreversible operation, is audited.

AuditingSessionStore(ISessionStore, IAuditLog, ITenantContext, IAuditActorResolver, ILogger<AuditingSessionStore>)

Section titled “ AuditingSessionStore(ISessionStore, IAuditLog, ITenantContext, IAuditActorResolver, ILogger<AuditingSessionStore>)”

Initializes a new audited session store.

public AuditingSessionStore(ISessionStore inner, IAuditLog auditLog, ITenantContext tenantContext, IAuditActorResolver actorResolver, ILogger<AuditingSessionStore> logger)

inner ISessionStore

auditLog IAuditLog

tenantContext ITenantContext

actorResolver IAuditActorResolver

logger ILogger<AuditingSessionStore>

Gets the real store instance the decorator wraps.

public object AuditedInner { get; }

object

Deletes the session.

public ValueTask<bool> DeleteAsync(string sessionId, CancellationToken cancellationToken = default)

sessionId string

The session identifier.

cancellationToken CancellationToken

The cancellation token.

ValueTask<bool>

true if the delete happened.

Fetches the session.

public ValueTask<SessionRecord?> GetAsync(string sessionId, CancellationToken cancellationToken = default)

sessionId string

The session identifier.

cancellationToken CancellationToken

The cancellation token.

ValueTask<SessionRecord?>

The session; null if it does not exist.

GetOwnerTenantIdAsync(string, CancellationToken)

Section titled “ GetOwnerTenantIdAsync(string, CancellationToken)”

Returns the tenant that owns a session identifier, WITHOUT applying the ambient tenant filter read from ITenantContext.

public ValueTask<string?> GetOwnerTenantIdAsync(string sessionId, CancellationToken cancellationToken = default)

sessionId string

The session identifier.

cancellationToken CancellationToken

The cancellation token.

ValueTask<string?>

The identifier of the owning tenant if the identifier has been used; null if the identifier has never been used.

ISessionStore.GetAsync is filtered by the ambient tenant; because of this, it can never answer “does this identifier belong to ANOTHER tenant” — the caller is already inside their own tenant’s context, and another tenant’s record is NEVER VISIBLE from that context, so the result is always null. This is exactly why the OpenAI-compatible endpoints’ cross-tenant ownership check was dead code — the rejection branch never fired, the identifier was silently treated as “never used” and a new session was opened.

The default implementation calls ISessionStore.GetAsync — so it CARRIES THE BUG ABOVE and can never correctly answer the cross-tenant question. This exists only so old/custom stores that have not yet overridden this method keep compiling. The real stores (SqlSessionStore, InMemorySessionStore) override this method with a genuinely correct implementation that is INDEPENDENT of the tenant.

QueryAsync(SessionQuery, CancellationToken)

Section titled “ QueryAsync(SessionQuery, CancellationToken)”

Lists sessions by filter. The most recently updated is returned first.

public ValueTask<IReadOnlyList<SessionRecord>> QueryAsync(SessionQuery query, CancellationToken cancellationToken = default)

query SessionQuery

The filter.

cancellationToken CancellationToken

The cancellation token.

ValueTask<IReadOnlyList<SessionRecord>>

The sessions.

SaveAsync(SessionRecord, CancellationToken)

Section titled “ SaveAsync(SessionRecord, CancellationToken)”

Saves the session. Overwrites an existing record with the same identifier.

public ValueTask SaveAsync(SessionRecord record, CancellationToken cancellationToken = default)

record SessionRecord

The session to save.

cancellationToken CancellationToken

The cancellation token.

ValueTask

The completion task.

TryCreateAsync(SessionRecord, CancellationToken)

Section titled “ TryCreateAsync(SessionRecord, CancellationToken)”

Creates a new session record only if it does not already exist.

public ValueTask<bool> TryCreateAsync(SessionRecord record, CancellationToken cancellationToken = default)

record SessionRecord

The session to create.

cancellationToken CancellationToken

The cancellation token.

ValueTask<bool>

true if created; false if a record with the same identifier already exists.

When two concurrent calls arrive with the same SessionRecord.Id, ONLY one must return true; the loser must get false and read the winner’s record with ISessionStore.GetAsync.

The default implementation is not ATOMIC (check-then-create) — it exists only so old stores that have not yet overridden this method keep compiling. The real stores (SqlSessionStore, InMemorySessionStore) override this method with a genuinely atomic implementation. Without atomicity, two concurrent first requests to the same new session, unaware of each other, generate two different conversation identifiers; the second ISessionStore.SaveAsync unconditionally overwrites the first, and the loser’s messages become silently unreachable.