Table of Contents

Class SupportCase

Namespace
Tharga.Team
Assembly
Tharga.Team.dll

A support conversation belonging to a team.

public record SupportCase : IEquatable<SupportCase>
Inheritance
SupportCase
Implements
Inherited Members

Remarks

The team owns the case; the author is a link that may dangle. A case is raised by a person but is about the team's account, and the team is what still exists a year later. Deleting a user runs ITeamService.RemoveUserFromAllTeamsAsync, which strips that person's membership from every team — so a case keyed on a member would be orphaned by design. Authorization keys on TeamKey alone, and no check requires the author to still resolve.

The transcript is not carried here. A case can accumulate years of messages, so history is read separately and paged; see GetMessagesAsync(string, string, string, int, CancellationToken). This record is the header.

Properties

AssistantState

Whether an assistant is answering this case.

public SupportAssistantState AssistantState { get; init; }

Property Value

SupportAssistantState

Remarks

Defaults to None, which is what every case raised before assistants existed deserializes as — so nothing already stored changes meaning.

AuthorIdentity

The stable authentication subject of whoever raised the case. May no longer resolve to a user or a member, which is expected rather than exceptional.

public required string AuthorIdentity { get; init; }

Property Value

string

AuthorName

The author's display name as it was when the case was raised.

public required string AuthorName { get; init; }

Property Value

string

Bindings

External projections. Empty until the channel work lands.

public SupportChannelBinding[] Bindings { get; init; }

Property Value

SupportChannelBinding[]

ClosedAt

public DateTime? ClosedAt { get; init; }

Property Value

DateTime?

ClosedBy

The stable subject of whoever closed the case, or null while it is open.

public string ClosedBy { get; init; }

Property Value

string

Remarks

May be AutoClose, when the case closed itself rather than being closed by a person.

ClosedReason

Why the case is closed, or null while it is open.

public SupportCaseClosureReason? ClosedReason { get; }

Property Value

SupportCaseClosureReason?

Remarks

Derived from ClosedBy rather than stored, and deliberately so. The store already records who closed a case; adding a reason to CloseCaseAsync(string, string, DateTime, string, SupportMessage, CancellationToken) would change a signature that hosts implement for their own storage, which is a compile-time break in somebody else's repository for a value that can be read from what is already there.

It follows that the reason is exactly as trustworthy as the actor, which is the right coupling: a case closed by the sweeper is closed for inactivity by definition, not by a flag that could disagree with who closed it.

CreatedAt

public required DateTime CreatedAt { get; init; }

Property Value

DateTime

Id

public required string Id { get; init; }

Property Value

string

MessageCount

Number of entries in the transcript, so a list can be rendered without reading it.

public required int MessageCount { get; init; }

Property Value

int

Status

public required SupportCaseStatus Status { get; init; }

Property Value

SupportCaseStatus

Subject

public required string Subject { get; init; }

Property Value

string

TeamKey

The owning team, or null when the case is not assigned to one.

public string TeamKey { get; init; }

Property Value

string

Remarks

Unassigned is a durable state, not a staging area. A case that arrived by mail from somebody who belongs to several teams — or to none the toolkit can see — belongs to no team until a support agent says which, and it can be answered, closed and reopened meanwhile. The person who knows which tenant a problem concerns is a human reading it, not an algorithm reading a From: header.

The invariant it replaces still holds where there is a team. A case with one is loaded through it and authorized by membership exactly as before, so holding a case id from another tenant still gains nothing. What changed is that "no team" became expressible, not that team scoping became optional.

An unassigned case is governed system-wide, because a team scope has no team to be held against. See AssignCaseAsync(string, string, CancellationToken).