Bifrost/src/Bifrost.Web/Domain/BifrostModels.cs
Heimdall dad45faeac Passkeys: Fido2NetLib, credential store, enrolment behind a fresh sign-in, assertion and app-asserted step-up for unlink
Heimdall-backed members enrol WebAuthn passkeys on the Linked accounts page (ticket from a fresh ordinary Heimdall sign-in, 48 h cooling-off, cap 5,
FixedWindowBudget on ceremonies) and unlink with a strong passkey: the assertion is verified at Bifrost, then inside the completion that holds the bearer
Bifrost calls Heimdall's POST /v1/apps/bifrost/step-up and the unlink with the returned token. The Google step-up closes once a strong passkey exists.
Migration AddPasskeyCredentials is additive. Tests use a software authenticator and a fake Heimdall step-up route modeled on the contract.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 20:48:37 +00:00

1257 lines
32 KiB
C#

using System.ComponentModel.DataAnnotations.Schema;
namespace Bifrost.Web.Domain;
public enum MembershipStatus
{
Authenticated,
PendingApproval,
Active,
Suspended
}
public enum MemberRole
{
StandardMember,
Producer,
Maintainer,
LedgerReviewer,
PlatformAdmin
}
public enum TierSnapshotKind
{
Patron,
Contributor
}
public enum ProjectStatus
{
Proposed,
Active,
Paused,
Archived
}
public enum WorkItemSourceType
{
GitHubIssue,
Internal
}
public enum WorkItemStatus
{
Proposed,
Open,
Volunteered,
Assigned,
InProgress,
SubmittedForReview,
ChangesRequested,
Approved,
Completed,
Blocked,
Archived
}
public enum WorkItemSkillLevel
{
Routine,
Specialized,
Senior,
Lead
}
public enum VolunteerClaimStatus
{
Active,
Withdrawn,
Accepted
}
public enum WorkLogApprovalStatus
{
Submitted,
Approved,
Rejected
}
public enum WorkReviewStatus
{
Pending,
ChangesRequested,
Approved
}
public enum MotionScope
{
Management,
Project
}
public enum MotionCategory
{
Bugs,
Cosmetics,
BalanceChanges,
Features,
NewContent,
FundamentalDesignChanges
}
public enum MotionStatus
{
Open,
Passed,
Failed,
Closed
}
public enum VoteChoice
{
For,
Against,
Abstain
}
public enum LedgerEntryType
{
PatronCredit,
ContributionCredit,
NominalCompensation,
Adjustment
}
public enum LedgerEntryStatus
{
Draft,
Approved,
IncludedInProposal
}
public enum PayoutProposalBatchStatus
{
Draft,
Reviewed,
Closed
}
public enum GitHubEntityState
{
Open,
Closed,
Merged
}
public enum GitHubReviewDecision
{
None,
Commented,
ChangesRequested,
Approved
}
public enum PointTransactionType
{
ManualAdjustment,
ApprovedWork,
ContinuousRoleCredit,
PatronSupport,
Decay
}
public enum PatronSupportEventKind
{
OneTimeDonation,
RecurringSupportSnapshot,
SupportAdjustment
}
public enum ExternalPatronProvider
{
Manual,
Patreon,
PayPal,
Stripe
}
public enum BridgeActorKind
{
Member,
Agent,
Persona,
Service
}
public enum BridgeTargetSurface
{
GitHub,
Discord,
Reddit,
CultNet,
CultCache,
Other
}
public enum BridgeActionKind
{
GitHubDraftPullRequest,
GitHubPullRequestComment,
GitHubIssueComment,
DiscordPost,
DiscordDirectMessage,
RedditPost,
AgentUpdateRequest,
Other
}
public enum BridgeActionStatus
{
Requested,
Authorized,
Denied,
InProgress,
Completed,
Failed,
Cancelled
}
public enum AgentDispatchRunStatus
{
Started,
Completed,
Cancelled,
Failed
}
public enum AgentTransportReceiptKind
{
Queued,
Claimed,
Released,
Closed
}
public enum GovernanceActivityReceiptKind
{
TopicOpened,
TopicCommented,
TopicApproved,
TopicPromoted
}
public sealed class UserAccount
{
public Guid Id { get; set; } = Guid.NewGuid();
public string BifrostIdentity { get; set; } = string.Empty;
public string NormalizedBifrostIdentity { get; set; } = string.Empty;
public long? GitHubUserId { get; set; }
public string HeimdallAccountId { get; set; } = string.Empty;
public string GitHubLogin { get; set; } = string.Empty;
public string NormalizedGitHubLogin { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string AvatarUrl { get; set; } = string.Empty;
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset LastSeenAtUtc { get; set; }
public MemberProfile? MemberProfile { get; set; }
public Membership? Membership { get; set; }
public ICollection<Project> OwnedProjects { get; set; } = [];
public ICollection<WorkItem> RequestedWorkItems { get; set; } = [];
public ICollection<WorkItem> AssignedWorkItems { get; set; } = [];
public ICollection<VolunteerClaim> VolunteerClaims { get; set; } = [];
public ICollection<Assignment> Assignments { get; set; } = [];
public ICollection<WorkLog> WorkLogs { get; set; } = [];
public ICollection<WorkReview> WorkReviews { get; set; } = [];
public ICollection<Motion> CreatedMotions { get; set; } = [];
public ICollection<Vote> Votes { get; set; } = [];
public ICollection<LedgerEntry> LedgerEntries { get; set; } = [];
public ICollection<PointTransaction> PointTransactions { get; set; } = [];
public ICollection<PatronSupportEvent> PatronSupportEvents { get; set; } = [];
public ICollection<BridgeAction> BridgeActions { get; set; } = [];
public ICollection<AgentDispatchRun> AgentDispatchRuns { get; set; } = [];
public ICollection<AgentTransportReceipt> AgentTransportReceipts { get; set; } = [];
public ICollection<GovernanceActivityReceipt> GovernanceActivityReceipts { get; set; } = [];
public ICollection<AuditEvent> AuditEvents { get; set; } = [];
}
public sealed class MemberProfile
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public string Nickname { get; set; } = string.Empty;
public string Headline { get; set; } = string.Empty;
public string PortfolioUrl { get; set; } = string.Empty;
public string Skills { get; set; } = string.Empty;
public string Availability { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
public DateTimeOffset UpdatedAtUtc { get; set; }
public UserAccount UserAccount { get; set; } = null!;
}
public sealed class Membership
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public MembershipStatus Status { get; set; } = MembershipStatus.Authenticated;
/// <summary>Why an admin suspended the account; set and cleared only by <c>MembershipSuspensionService</c> and projected only to admins.</summary>
public string? SuspensionReason { get; set; }
public bool IsPlatformAdmin { get; set; }
public bool CanManageProjects { get; set; }
public bool CanManageLedger { get; set; }
public bool CanModerateMotions { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset? ApprovedAtUtc { get; set; }
public string Notes { get; set; } = string.Empty;
public UserAccount UserAccount { get; set; } = null!;
public ICollection<RoleAssignment> RoleAssignments { get; set; } = [];
public ICollection<TierSnapshot> TierSnapshots { get; set; } = [];
[NotMapped]
public decimal EffectiveVotingWeight =>
Status != MembershipStatus.Active
? 0m
: Math.Max(1m, TierSnapshots.Where(x => x.IsCurrent).Sum(x => x.Weight));
}
public sealed class RoleAssignment
{
// Left empty until saved: a key preset on a row added to an already-saved membership makes EF treat it as existing and fail the save.
public Guid Id { get; set; }
public Guid MembershipId { get; set; }
public MemberRole Role { get; set; } = MemberRole.StandardMember;
public Guid? AssignedByUserAccountId { get; set; }
public DateTimeOffset AssignedAtUtc { get; set; }
public string Notes { get; set; } = string.Empty;
public Membership Membership { get; set; } = null!;
public UserAccount? AssignedByUserAccount { get; set; }
}
public sealed class TierSnapshot
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid MembershipId { get; set; }
public TierSnapshotKind Kind { get; set; }
public string Label { get; set; } = string.Empty;
public decimal Weight { get; set; }
public bool IsCurrent { get; set; } = true;
public DateTimeOffset EffectiveFromUtc { get; set; }
public DateTimeOffset CapturedAtUtc { get; set; }
public string Notes { get; set; } = string.Empty;
public Membership Membership { get; set; } = null!;
}
public sealed class MembershipInvitation
{
public Guid Id { get; set; } = Guid.NewGuid();
public string GitHubLogin { get; set; } = string.Empty;
public string NormalizedGitHubLogin { get; set; } = string.Empty;
public Guid? IssuedByUserAccountId { get; set; }
public Guid? AcceptedByUserAccountId { get; set; }
public DateTimeOffset IssuedAtUtc { get; set; }
public DateTimeOffset? AcceptedAtUtc { get; set; }
public DateTimeOffset? RevokedAtUtc { get; set; }
public string Notes { get; set; } = string.Empty;
public UserAccount? IssuedByUserAccount { get; set; }
public UserAccount? AcceptedByUserAccount { get; set; }
}
public sealed class Project
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid OwnerUserAccountId { get; set; }
public string Slug { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string GitHubRepository { get; set; } = string.Empty;
public ProjectStatus Status { get; set; } = ProjectStatus.Active;
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset UpdatedAtUtc { get; set; }
public UserAccount OwnerUserAccount { get; set; } = null!;
public ICollection<WorkItem> WorkItems { get; set; } = [];
public ICollection<Motion> Motions { get; set; } = [];
public ICollection<LedgerEntry> LedgerEntries { get; set; } = [];
public ICollection<PointTransaction> PointTransactions { get; set; } = [];
public ICollection<PatronSupportEvent> PatronSupportEvents { get; set; } = [];
public ICollection<RevenueEvent> RevenueEvents { get; set; } = [];
public ICollection<RevenueShareLine> RevenueShareLines { get; set; } = [];
}
public sealed class WorkItem
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid ProjectId { get; set; }
public Guid RequestedByUserAccountId { get; set; }
public Guid? AssignedToUserAccountId { get; set; }
public WorkItemSourceType SourceType { get; set; } = WorkItemSourceType.Internal;
public string ExternalSourceId { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public WorkItemSkillLevel SkillLevel { get; set; } = WorkItemSkillLevel.Routine;
public WorkItemStatus Status { get; set; } = WorkItemStatus.Open;
public WorkReviewStatus ReviewStatus { get; set; } = WorkReviewStatus.Pending;
public decimal EstimatedHours { get; set; }
public decimal ContributionPoints { get; set; }
public DateTimeOffset? TargetDateUtc { get; set; }
public DateTimeOffset? SubmittedAtUtc { get; set; }
public DateTimeOffset? CompletedAtUtc { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset UpdatedAtUtc { get; set; }
public Project Project { get; set; } = null!;
public UserAccount RequestedByUserAccount { get; set; } = null!;
public UserAccount? AssignedToUserAccount { get; set; }
public ICollection<VolunteerClaim> VolunteerClaims { get; set; } = [];
public ICollection<Assignment> Assignments { get; set; } = [];
public ICollection<WorkLog> WorkLogs { get; set; } = [];
public ICollection<WorkReview> WorkReviews { get; set; } = [];
public ICollection<LedgerEntry> LedgerEntries { get; set; } = [];
public ICollection<PointTransaction> PointTransactions { get; set; } = [];
public GitHubIssueLink? GitHubIssueLink { get; set; }
public ICollection<GitHubPullRequestLink> GitHubPullRequestLinks { get; set; } = [];
}
public sealed class VolunteerClaim
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public Guid UserAccountId { get; set; }
public VolunteerClaimStatus Status { get; set; } = VolunteerClaimStatus.Active;
public string Note { get; set; } = string.Empty;
public DateTimeOffset CreatedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
public UserAccount UserAccount { get; set; } = null!;
}
public sealed class Assignment
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public Guid UserAccountId { get; set; }
public Guid AssignedByUserAccountId { get; set; }
public string Note { get; set; } = string.Empty;
public DateTimeOffset AssignedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
public UserAccount UserAccount { get; set; } = null!;
public UserAccount AssignedByUserAccount { get; set; } = null!;
}
public sealed class WorkLog
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public Guid UserAccountId { get; set; }
public decimal Hours { get; set; }
public string Note { get; set; } = string.Empty;
public WorkLogApprovalStatus ApprovalStatus { get; set; } = WorkLogApprovalStatus.Submitted;
public Guid? ReviewedByUserAccountId { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset? ReviewedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
public UserAccount UserAccount { get; set; } = null!;
public UserAccount? ReviewedByUserAccount { get; set; }
}
public sealed class WorkReview
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public Guid? ReviewerUserAccountId { get; set; }
public string ReviewerName { get; set; } = string.Empty;
public WorkReviewStatus Status { get; set; }
public string Note { get; set; } = string.Empty;
public DateTimeOffset ReviewedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
public UserAccount? ReviewerUserAccount { get; set; }
}
public sealed class GitHubIssueLink
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public string RepositoryFullName { get; set; } = string.Empty;
public int IssueNumber { get; set; }
public GitHubEntityState State { get; set; } = GitHubEntityState.Open;
public string IssueUrl { get; set; } = string.Empty;
public string TitleSnapshot { get; set; } = string.Empty;
public DateTimeOffset LastSynchronizedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
}
public sealed class GitHubPullRequestLink
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid WorkItemId { get; set; }
public string RepositoryFullName { get; set; } = string.Empty;
public int PullRequestNumber { get; set; }
public GitHubEntityState State { get; set; } = GitHubEntityState.Open;
public bool IsMerged { get; set; }
public string PullRequestUrl { get; set; } = string.Empty;
public string TitleSnapshot { get; set; } = string.Empty;
public GitHubReviewDecision ReviewDecision { get; set; } = GitHubReviewDecision.None;
public DateTimeOffset LastSynchronizedAtUtc { get; set; }
public WorkItem WorkItem { get; set; } = null!;
}
public sealed class Motion
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid? ProjectId { get; set; }
public Guid CreatedByUserAccountId { get; set; }
public MotionScope Scope { get; set; } = MotionScope.Project;
public MotionCategory Category { get; set; } = MotionCategory.Features;
public MotionStatus Status { get; set; } = MotionStatus.Open;
public string Title { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public decimal ApprovalThreshold { get; set; } = 0.50m;
public DateTimeOffset OpensAtUtc { get; set; }
public DateTimeOffset ClosesAtUtc { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
public DateTimeOffset? ResolvedAtUtc { get; set; }
public string ResolutionNote { get; set; } = string.Empty;
public MotionKind Kind { get; set; } = MotionKind.Standard;
// Sealed-ballot motions only: commitments are accepted until this instant, reveals from then until ClosesAtUtc.
public DateTimeOffset? CommitClosesAtUtc { get; set; }
// Sealed-ballot motions only: candidate names, one per line.
public string Candidates { get; set; } = string.Empty;
// Sealed-ballot motions only: Delphi round number and the motion whose result this round answers.
public int Round { get; set; } = 1;
public Guid? PriorMotionId { get; set; }
// Sealed-ballot motions only: bumped by every accepted reveal. A concurrency token, so a reveal and the close cannot both
// succeed on the same row version: the close freezes exactly the reveals it counted.
public int RevealSeq { get; set; }
// Sealed-ballot motions only: SHA-256 over the sorted commitments, written once at finalization.
public string ResultDigest { get; set; } = string.Empty;
public Project? Project { get; set; }
public UserAccount CreatedByUserAccount { get; set; } = null!;
public ICollection<Vote> Votes { get; set; } = [];
public ICollection<SealedBallot> SealedBallots { get; set; } = [];
}
public enum MotionKind
{
Standard,
SealedBallot
}
public sealed class SealedBallot
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid MotionId { get; set; }
public Guid UserAccountId { get; set; }
// Hex SHA-256 of the canonical ballot document; the only ballot-derived value stored before the reveal step.
public string Commitment { get; set; } = string.Empty;
public int Revision { get; set; } = 1;
public DateTimeOffset CommittedAtUtc { get; set; }
// Reveal step. Held unread by every code path until the motion is Closed.
public string? RevealedCandidate { get; set; }
public string? RevealedReason { get; set; }
public string? Salt { get; set; }
public DateTimeOffset? RevealedAtUtc { get; set; }
public Motion Motion { get; set; } = null!;
public UserAccount UserAccount { get; set; } = null!;
}
public sealed class Vote
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid MotionId { get; set; }
public Guid UserAccountId { get; set; }
public VoteChoice Choice { get; set; }
public decimal Weight { get; set; }
public string Comment { get; set; } = string.Empty;
public DateTimeOffset CastAtUtc { get; set; }
public Motion Motion { get; set; } = null!;
public UserAccount UserAccount { get; set; } = null!;
}
public sealed class LedgerEntry
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public Guid? ProjectId { get; set; }
public Guid? WorkItemId { get; set; }
public Guid CreatedByUserAccountId { get; set; }
public LedgerEntryType EntryType { get; set; } = LedgerEntryType.ContributionCredit;
public LedgerEntryStatus Status { get; set; } = LedgerEntryStatus.Draft;
public decimal Points { get; set; }
public decimal NominalAmount { get; set; }
public string Note { get; set; } = string.Empty;
public DateTimeOffset EffectiveAtUtc { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
public UserAccount UserAccount { get; set; } = null!;
public UserAccount CreatedByUserAccount { get; set; } = null!;
public Project? Project { get; set; }
public WorkItem? WorkItem { get; set; }
}
public sealed class PayoutProposalBatch
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid CreatedByUserAccountId { get; set; }
public PayoutProposalBatchStatus Status { get; set; } = PayoutProposalBatchStatus.Draft;
public string Summary { get; set; } = string.Empty;
public DateTimeOffset CreatedAtUtc { get; set; }
public UserAccount CreatedByUserAccount { get; set; } = null!;
}
public sealed class PointTransaction
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public Guid? ProjectId { get; set; }
public Guid? WorkItemId { get; set; }
public PointTransactionType Type { get; set; } = PointTransactionType.ManualAdjustment;
public decimal Amount { get; set; }
public bool IsDecaying { get; set; } = true;
public string Note { get; set; } = string.Empty;
public DateTimeOffset CreatedAtUtc { get; set; }
public UserAccount UserAccount { get; set; } = null!;
public Project? Project { get; set; }
public WorkItem? WorkItem { get; set; }
}
public sealed class PatronSupportEvent
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public Guid? ProjectId { get; set; }
public string ExternalSupportId { get; set; } = string.Empty;
public ExternalPatronProvider Provider { get; set; } = ExternalPatronProvider.Manual;
public string ProviderEventId { get; set; } = string.Empty;
public string ProviderPayerId { get; set; } = string.Empty;
public string ProviderSubscriptionId { get; set; } = string.Empty;
public PatronSupportEventKind Kind { get; set; } = PatronSupportEventKind.OneTimeDonation;
public decimal Amount { get; set; }
public string CurrencyCode { get; set; } = "USD";
public bool IsCurrentRecurringSupport { get; set; }
public DateTimeOffset SupportedAtUtc { get; set; }
public DateTimeOffset RecordedAtUtc { get; set; }
public string Notes { get; set; } = string.Empty;
public UserAccount UserAccount { get; set; } = null!;
public Project? Project { get; set; }
}
public sealed class DecayRun
{
public Guid Id { get; set; } = Guid.NewGuid();
public decimal Rate { get; set; }
public string Notes { get; set; } = string.Empty;
public DateTimeOffset RanAtUtc { get; set; }
}
public sealed class RevenueEvent
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid? ProjectId { get; set; }
public decimal Amount { get; set; }
public string CurrencyCode { get; set; } = "USD";
public string Notes { get; set; } = string.Empty;
public DateTimeOffset ReceivedAtUtc { get; set; }
public Project? Project { get; set; }
public ICollection<RevenueShareBatch> RevenueShareBatches { get; set; } = [];
}
public sealed class RevenueShareBatch
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid? RevenueEventId { get; set; }
public Guid CreatedByUserAccountId { get; set; }
public string Summary { get; set; } = string.Empty;
public DateTimeOffset CreatedAtUtc { get; set; }
public RevenueEvent? RevenueEvent { get; set; }
public UserAccount CreatedByUserAccount { get; set; } = null!;
public ICollection<RevenueShareLine> Lines { get; set; } = [];
}
public sealed class RevenueShareLine
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid RevenueShareBatchId { get; set; }
public Guid? UserAccountId { get; set; }
public Guid? ProjectId { get; set; }
public decimal Amount { get; set; }
public string Basis { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
public RevenueShareBatch RevenueShareBatch { get; set; } = null!;
public UserAccount? UserAccount { get; set; }
public Project? Project { get; set; }
}
public sealed class BridgeAction
{
public Guid Id { get; set; } = Guid.NewGuid();
public BridgeActorKind ActorKind { get; set; } = BridgeActorKind.Agent;
public Guid? ActorUserAccountId { get; set; }
public string ActorName { get; set; } = string.Empty;
public BridgeTargetSurface TargetSurface { get; set; } = BridgeTargetSurface.GitHub;
public BridgeActionKind ActionKind { get; set; } = BridgeActionKind.GitHubDraftPullRequest;
public BridgeActionStatus Status { get; set; } = BridgeActionStatus.Requested;
public Guid? WorkItemId { get; set; }
public Guid? MotionId { get; set; }
public string TargetRepositoryFullName { get; set; } = string.Empty;
public string TargetLocator { get; set; } = string.Empty;
public string TargetSurfaceName { get; set; } = string.Empty;
public string SourceKind { get; set; } = string.Empty;
public string SourceId { get; set; } = string.Empty;
public string AuthorityReference { get; set; } = string.Empty;
public string BifrostIdentity { get; set; } = string.Empty;
public string HeimdallCapabilityReference { get; set; } = string.Empty;
public string EpiphanyRunId { get; set; } = string.Empty;
public string EpiphanyLaneId { get; set; } = string.Empty;
public string EpiphanyAgentIdentity { get; set; } = string.Empty;
public string PolicyDecision { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Summary { get; set; } = string.Empty;
public string ReceiptUrl { get; set; } = string.Empty;
public string ExternalReceiptId { get; set; } = string.Empty;
public string ReceiptPayload { get; set; } = string.Empty;
public string FailureReason { get; set; } = string.Empty;
public DateTimeOffset RequestedAtUtc { get; set; }
public DateTimeOffset UpdatedAtUtc { get; set; }
public DateTimeOffset? StartedAtUtc { get; set; }
public DateTimeOffset? CompletedAtUtc { get; set; }
public UserAccount? ActorUserAccount { get; set; }
public WorkItem? WorkItem { get; set; }
public Motion? Motion { get; set; }
}
public sealed class AgentDispatchRun
{
public Guid Id { get; set; } = Guid.NewGuid();
public string RequestId { get; set; } = string.Empty;
public string TargetRepoName { get; set; } = string.Empty;
public string TargetRepositoryFullName { get; set; } = string.Empty;
public string TargetAgentIdentity { get; set; } = string.Empty;
public string LaunchMode { get; set; } = string.Empty;
public AgentDispatchRunStatus Status { get; set; } = AgentDispatchRunStatus.Started;
public Guid? StartedByUserAccountId { get; set; }
public int? WorkerProcessId { get; set; }
public string ThreadId { get; set; } = string.Empty;
public string TurnId { get; set; } = string.Empty;
public string LogPath { get; set; } = string.Empty;
public string ResultPath { get; set; } = string.Empty;
public string Note { get; set; } = string.Empty;
public string Error { get; set; } = string.Empty;
public DateTimeOffset StartedAtUtc { get; set; }
public DateTimeOffset UpdatedAtUtc { get; set; }
public DateTimeOffset? CompletedAtUtc { get; set; }
public UserAccount? StartedByUserAccount { get; set; }
}
public sealed class AgentTransportReceipt
{
public Guid Id { get; set; } = Guid.NewGuid();
public string RequestId { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string TargetRepoName { get; set; } = string.Empty;
public string TargetRepositoryFullName { get; set; } = string.Empty;
public string TargetAgentIdentity { get; set; } = string.Empty;
public AgentTransportReceiptKind ActivityKind { get; set; } = AgentTransportReceiptKind.Queued;
public string Status { get; set; } = string.Empty;
public Guid? ActorUserAccountId { get; set; }
public string ActorName { get; set; } = string.Empty;
public string Note { get; set; } = string.Empty;
public DateTimeOffset OccurredAtUtc { get; set; }
public UserAccount? ActorUserAccount { get; set; }
}
public sealed class GovernanceActivityReceipt
{
public Guid Id { get; set; } = Guid.NewGuid();
public string TopicId { get; set; } = string.Empty;
public string CommentId { get; set; } = string.Empty;
public string DispatchRequestId { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string JurisdictionRepoName { get; set; } = string.Empty;
public string JurisdictionAgentIdentity { get; set; } = string.Empty;
public GovernanceActivityReceiptKind ActivityKind { get; set; } = GovernanceActivityReceiptKind.TopicOpened;
public string ActorKind { get; set; } = string.Empty;
public string ActorName { get; set; } = string.Empty;
public string Note { get; set; } = string.Empty;
public Guid? ActorUserAccountId { get; set; }
public DateTimeOffset OccurredAtUtc { get; set; }
public UserAccount? ActorUserAccount { get; set; }
}
public sealed class AuditEvent
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid? ActorUserAccountId { get; set; }
public string EntityType { get; set; } = string.Empty;
public string EntityId { get; set; } = string.Empty;
public string Action { get; set; } = string.Empty;
public string Detail { get; set; } = string.Empty;
public DateTimeOffset OccurredAtUtc { get; set; }
public UserAccount? ActorUserAccount { get; set; }
}
/// <summary>
/// An account's consent to the training notice, recorded with the notice version it accepted. Withdrawal sets
/// <see cref="WithdrawnAtUtc"/>; the row is never deleted, so what was agreed and when stays answerable.
/// </summary>
public sealed class ConsentReceipt
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public int NoticeVersion { get; set; }
public DateTimeOffset AcceptedAtUtc { get; set; }
public DateTimeOffset? WithdrawnAtUtc { get; set; }
public UserAccount? UserAccount { get; set; }
}
/// <summary>
/// A WebAuthn passkey a Heimdall-backed member enrolled (docs/passkeys.md). Holds only what verifying an assertion needs:
/// the credential id, the public key and the sign count. No secret exists to hold. It counts as strong proof only from
/// <see cref="StrongFromUtc"/> (the cooling-off after enrolment) and only while not revoked.
/// </summary>
public sealed class PasskeyCredential
{
public Guid Id { get; set; } = Guid.NewGuid();
public Guid UserAccountId { get; set; }
public byte[] CredentialId { get; set; } = [];
public byte[] PublicKey { get; set; } = [];
public long SignCount { get; set; }
// Comma-separated WebAuthn transport hints the authenticator reported at enrolment; a hint for the browser, never trusted.
public string Transports { get; set; } = string.Empty;
public Guid? AaGuid { get; set; }
public string Label { get; set; } = string.Empty;
public DateTimeOffset CreatedUtc { get; set; }
public DateTimeOffset StrongFromUtc { get; set; }
public DateTimeOffset? RevokedUtc { get; set; }
public DateTimeOffset? LastUsedUtc { get; set; }
public UserAccount? UserAccount { get; set; }
}
/// <summary>A Mind's registered Ed25519 public key. Bifrost never holds a private key.</summary>
public sealed class MindKey
{
public Guid Id { get; set; } = Guid.NewGuid();
public string PersonaId { get; set; } = string.Empty;
// The member account this persona votes as; fixed by the first registration for the persona.
public Guid UserAccountId { get; set; }
// Base64 of the 32-byte public key.
public string PublicKey { get; set; } = string.Empty;
public Guid RegisteredByUserAccountId { get; set; }
public DateTimeOffset RegisteredAtUtc { get; set; }
public DateTimeOffset? RevokedAtUtc { get; set; }
public Guid? RevokedByUserAccountId { get; set; }
}
/// <summary>A nonce already spent by a persona; a verified envelope with a seen nonce is refused as a replay.</summary>
public sealed class MindNonce
{
public Guid Id { get; set; } = Guid.NewGuid();
public string PersonaId { get; set; } = string.Empty;
public string Nonce { get; set; } = string.Empty;
public DateTimeOffset SeenAtUtc { get; set; }
}
/// <summary>The one member account a persona votes as. Unique on both columns: a persona has one account and an account one persona.</summary>
public sealed class MindPersona
{
public Guid Id { get; set; } = Guid.NewGuid();
public string PersonaId { get; set; } = string.Empty;
public Guid UserAccountId { get; set; }
}