diff --git a/CHANGELOG.md b/CHANGELOG.md index e3cab07d1..f4795859e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.28.5 + +### Patch Changes + +- 01127ac: Allow OAuth consent submissions to reach only the validated registered client callback while preserving a strict content security policy. + ## 0.28.4 ### Patch Changes diff --git a/VERSION b/VERSION index 097bc9362..16b6bcee6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.28.4 +0.28.5 diff --git a/apps/Agentweaver.Api.Data/Auth/OAuth/OAuthServerRecords.cs b/apps/Agentweaver.Api.Data/Auth/OAuth/OAuthServerRecords.cs index 01d9220f7..80a6ec050 100644 --- a/apps/Agentweaver.Api.Data/Auth/OAuth/OAuthServerRecords.cs +++ b/apps/Agentweaver.Api.Data/Auth/OAuth/OAuthServerRecords.cs @@ -22,6 +22,7 @@ public sealed class OAuthAuthorizationTransaction public string? ClientState { get; set; } public string? BrowserSessionId { get; set; } public string? Subject { get; set; } + public string? ContinuationDecision { get; set; } public DateTimeOffset ExpiresAt { get; set; } public DateTimeOffset? ConsumedAt { get; set; } } diff --git a/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.Designer.cs b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.Designer.cs new file mode 100644 index 000000000..f7f20948d --- /dev/null +++ b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.Designer.cs @@ -0,0 +1,3437 @@ +// +using System; +using Agentweaver.Api.Memory; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Agentweaver.Api.Migrations.Postgres.Migrations +{ + [DbContext(typeof(MemoryDbContext))] + [Migration("20260904224046_AddOAuthConsentContinuationDecisionPostgres")] + partial class AddOAuthConsentContinuationDecisionPostgres + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.BrowserEntraSession", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PlatformRoles") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("BrowserEntraSessions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.EntraOAuthState", b => + { + b.Property("State") + .HasColumnType("text"); + + b.Property("CodeVerifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Nonce") + .HasColumnType("text"); + + b.Property("ReturnHandle") + .HasColumnType("text"); + + b.HasKey("State"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("EntraOAuthStates"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthAuthorizationTransaction", b => + { + b.Property("HandleHash") + .HasColumnType("text"); + + b.Property("BrowserSessionId") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClientState") + .HasColumnType("text"); + + b.Property("CodeChallenge") + .IsRequired() + .HasColumnType("text"); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContinuationDecision") + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RedirectUri") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .HasColumnType("text"); + + b.HasKey("HandleHash"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("OAuthAuthorizationTransactions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthConsentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("text"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Subject", "ClientId") + .IsUnique(); + + b.ToTable("OAuthConsents"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthDynamicRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisabledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RegisteredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceHash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SourceHash", "RegisteredAt"); + + b.ToTable("OAuthDynamicRegistrations"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthMaintenanceLease", b => + { + b.Property("Name") + .HasColumnType("text"); + + b.Property("LeaseExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Name"); + + b.ToTable("OAuthMaintenanceLeases"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthRefreshTokenFamily", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorizationId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RevocationReason") + .HasColumnType("text"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId") + .IsUnique(); + + b.ToTable("OAuthRefreshTokenFamilies"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.WebSessionExchangeCode", b => + { + b.Property("Code") + .HasColumnType("text"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Login") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Code"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("WebSessionExchangeCodes"); + }); + + modelBuilder.Entity("Agentweaver.Api.Coordinator.CoordinatorAssemblyReviewRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AggregateTreeHash") + .HasColumnType("text"); + + b.Property("CoordinatorFailedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CoordinatorFailureReason") + .HasColumnType("text"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DecisionJson") + .HasColumnType("text"); + + b.Property("DecisionSubmittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IntegrationBranch") + .HasColumnType("text"); + + b.Property("OwnerUser") + .HasColumnType("text"); + + b.Property("Reviewer") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId") + .IsUnique(); + + b.ToTable("AssemblyReviews"); + }); + + modelBuilder.Entity("Agentweaver.Api.Diagnostics.HeartbeatStatusRecord", b => + { + b.Property("PodName") + .HasColumnType("text"); + + b.Property("ActedCount") + .HasColumnType("integer"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("ErrorCount") + .HasColumnType("integer"); + + b.Property("IntervalSeconds") + .HasColumnType("integer"); + + b.Property("LastTickUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("PodName"); + + b.ToTable("HeartbeatStatuses"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AgentMemory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Importance") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SessionId") + .HasColumnType("text"); + + b.Property("SourceIdentity") + .HasColumnType("text"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("TrustState") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("legacy"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "AgentName"); + + b.HasIndex("ProjectId", "Type"); + + b.ToTable("AgentMemory"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationActivationRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("activated_at"); + + b.Property("AutomationKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("automation_key"); + + b.Property("ByokProviderId") + .HasColumnType("text") + .HasColumnName("byok_provider_id"); + + b.Property("CopilotBindingGrantDigest") + .HasColumnType("text") + .HasColumnName("copilot_binding_grant_digest"); + + b.Property("CopilotBindingId") + .HasColumnType("text") + .HasColumnName("copilot_binding_id"); + + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("InvalidatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("invalidated_at"); + + b.Property("ModelProviderSource") + .HasColumnType("integer") + .HasColumnName("model_provider_source"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("RepositoryGrantDigest") + .HasColumnType("text") + .HasColumnName("repository_grant_digest"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique() + .HasDatabaseName("UX_automation_activations_active_project") + .HasFilter("status = 0"); + + b.HasIndex("InstallationId", "RepositoryId"); + + b.ToTable("automation_activations", null, t => + { + t.HasCheckConstraint("CK_automation_activations_snapshot_tuple", "status <> 0 OR (\n (\n (installation_id IS NULL AND repository_id IS NULL AND repository_grant_digest IS NULL)\n OR\n (installation_id IS NOT NULL AND installation_id > 0 AND\n repository_id IS NOT NULL AND repository_id > 0 AND\n repository_grant_digest IS NOT NULL AND repository_grant_digest <> '')\n ) AND (\n (model_provider_source = 1 AND\n byok_provider_id IS NOT NULL AND byok_provider_id <> '' AND\n (copilot_binding_id IS NULL OR copilot_binding_id = '') AND\n (copilot_binding_grant_digest IS NULL OR copilot_binding_grant_digest = ''))\n OR\n (model_provider_source <> 1 AND\n copilot_binding_id IS NOT NULL AND copilot_binding_id <> '' AND\n copilot_binding_grant_digest IS NOT NULL AND copilot_binding_grant_digest <> '' AND\n (byok_provider_id IS NULL OR byok_provider_id = ''))\n ))"); + }); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationInvocationRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("ActivationId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("activation_id"); + + b.Property("BacklogTaskId") + .HasColumnType("text") + .HasColumnName("backlog_task_id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("DeliveryId") + .HasColumnType("text") + .HasColumnName("delivery_id"); + + b.Property("EventName") + .HasColumnType("text") + .HasColumnName("event_name"); + + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("OccurrenceKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("occurrence_key"); + + b.Property("Outcome") + .HasColumnType("integer") + .HasColumnName("outcome"); + + b.Property("PendingBacklogTaskId") + .HasColumnType("text") + .HasColumnName("pending_backlog_task_id"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("ReceivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("received_at"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.HasKey("Id"); + + b.HasIndex("BacklogTaskId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_backlog_task_id") + .HasFilter("backlog_task_id IS NOT NULL"); + + b.HasIndex("DeliveryId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_delivery_id") + .HasFilter("delivery_id IS NOT NULL"); + + b.HasIndex("PendingBacklogTaskId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_pending_backlog_task_id") + .HasFilter("pending_backlog_task_id IS NOT NULL"); + + b.HasIndex("ProjectId"); + + b.HasIndex("ActivationId", "OccurrenceKey") + .IsUnique(); + + b.ToTable("automation_invocations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationProjectGuardRecord", b => + { + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("RepositoryAttached") + .HasColumnType("boolean") + .HasColumnName("repository_attached"); + + b.HasKey("ProjectId"); + + b.ToTable("automation_project_guards", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BacklogTaskDependencyRecord", b => + { + b.Property("TaskId") + .HasColumnType("text") + .HasColumnName("task_id"); + + b.Property("DependsOnTaskId") + .HasColumnType("text") + .HasColumnName("depends_on_task_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.HasKey("TaskId", "DependsOnTaskId"); + + b.HasIndex("DependsOnTaskId") + .HasDatabaseName("IX_backlog_task_dependencies_prerequisite"); + + b.HasIndex("ProjectId", "TaskId") + .HasDatabaseName("IX_backlog_task_dependencies_project_task"); + + b.ToTable("backlog_task_dependencies", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BacklogTaskRecord", b => + { + b.Property("TaskId") + .HasColumnType("text") + .HasColumnName("task_id"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("archived_at"); + + b.Property("CapturedBy") + .IsRequired() + .HasColumnType("text") + .HasColumnName("captured_by"); + + b.Property("CapturedByUserId") + .HasColumnType("text") + .HasColumnName("captured_by_user_id"); + + b.Property("ClaimedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("claimed_at"); + + b.Property("CommittedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("committed_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsAutomationInvocationPending") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("automation_invocation_pending"); + + b.Property("OrderKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("order_key"); + + b.Property("ParentPrdRunId") + .HasColumnType("text") + .HasColumnName("parent_prd_run_id"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("PromotionKey") + .HasColumnType("text") + .HasColumnName("promotion_key"); + + b.Property("PromotionReason") + .HasColumnType("text") + .HasColumnName("promotion_reason"); + + b.Property("RunId") + .HasColumnType("text") + .HasColumnName("run_id"); + + b.Property("SourceFilePath") + .HasColumnType("text") + .HasColumnName("source_file_path"); + + b.Property("State") + .IsRequired() + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("WorkflowOverrideId") + .HasColumnType("text") + .HasColumnName("workflow_override_id"); + + b.HasKey("TaskId"); + + b.HasIndex("RunId") + .IsUnique() + .HasDatabaseName("IX_backlog_tasks_run") + .HasFilter("run_id IS NOT NULL"); + + b.HasIndex("ParentPrdRunId", "PromotionKey") + .IsUnique() + .HasDatabaseName("IX_backlog_tasks_parent_promotion_key") + .HasFilter("parent_prd_run_id IS NOT NULL AND promotion_key IS NOT NULL"); + + b.HasIndex("ProjectId", "State", "OrderKey") + .IsUnique() + .HasDatabaseName("IX_backlog_tasks_orderkey_unique") + .HasFilter("state IN ('backlog','ready') AND archived_at IS NULL"); + + b.ToTable("backlog_tasks", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BlueprintPackageAcquisitionRecord", b => + { + b.Property("OwnerId") + .HasColumnType("text") + .HasColumnName("owner_id"); + + b.Property("PackageId") + .HasColumnType("text") + .HasColumnName("package_id"); + + b.Property("CanonicalVersionKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("canonical_version_key"); + + b.Property("Ordinal") + .HasColumnType("integer") + .HasColumnName("ordinal"); + + b.Property("AcquiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("acquired_at"); + + b.Property("CanonicalVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("canonical_version"); + + b.Property("Producer") + .HasColumnType("text") + .HasColumnName("producer"); + + b.Property("Repository") + .HasColumnType("text") + .HasColumnName("repository"); + + b.Property("RequestedRef") + .HasColumnType("text") + .HasColumnName("requested_ref"); + + b.Property("Revision") + .HasColumnType("text") + .HasColumnName("revision"); + + b.Property("Source") + .IsRequired() + .HasColumnType("text") + .HasColumnName("source"); + + b.HasKey("OwnerId", "PackageId", "CanonicalVersionKey", "Ordinal"); + + b.ToTable("blueprint_package_acquisitions", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BlueprintPackageLibraryRecord", b => + { + b.Property("OwnerId") + .HasColumnType("text") + .HasColumnName("owner_id"); + + b.Property("PackageId") + .HasColumnType("text") + .HasColumnName("package_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.HasKey("OwnerId", "PackageId"); + + b.ToTable("blueprint_package_library", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BlueprintPackagePayloadRecord", b => + { + b.Property("OwnerId") + .HasColumnType("text") + .HasColumnName("owner_id"); + + b.Property("PackageId") + .HasColumnType("text") + .HasColumnName("package_id"); + + b.Property("CanonicalVersionKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("canonical_version_key"); + + b.Property("Path") + .HasColumnType("text") + .HasColumnName("path"); + + b.Property("Bytes") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("bytes"); + + b.Property("CanonicalVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("canonical_version"); + + b.HasKey("OwnerId", "PackageId", "CanonicalVersionKey", "Path"); + + b.ToTable("blueprint_package_payloads", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.BlueprintPackageVersionRecord", b => + { + b.Property("OwnerId") + .HasColumnType("text") + .HasColumnName("owner_id"); + + b.Property("PackageId") + .HasColumnType("text") + .HasColumnName("package_id"); + + b.Property("CanonicalVersionKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("canonical_version_key"); + + b.Property("CanonicalVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("canonical_version"); + + b.Property("ContainerSha256") + .HasColumnType("text") + .HasColumnName("container_sha256"); + + b.Property("ContentDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("content_digest"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("PayloadSetDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("payload_set_digest"); + + b.Property("RawManifest") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("raw_manifest"); + + b.Property("RawManifestSha256") + .IsRequired() + .HasColumnType("text") + .HasColumnName("raw_manifest_sha256"); + + b.HasKey("OwnerId", "PackageId", "CanonicalVersionKey"); + + b.ToTable("blueprint_package_versions", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.CastProposalRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text") + .HasColumnName("owner"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("ProposalJson") + .IsRequired() + .HasColumnType("text") + .HasColumnName("proposal_json"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .HasDatabaseName("IX_cast_proposals_project_id"); + + b.ToTable("cast_proposals", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Decision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ApprovedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ApprovedBy") + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Rationale") + .HasColumnType("text"); + + b.Property("SourceIdentity") + .HasColumnType("text"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("SupersededById") + .HasColumnType("integer"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrustState") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("legacy"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("SupersededById"); + + b.HasIndex("ProjectId", "AgentName"); + + b.HasIndex("ProjectId", "Status"); + + b.ToTable("Decisions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DecisionInboxEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DecisionId") + .HasColumnType("integer"); + + b.Property("MergedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Rationale") + .HasColumnType("text"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("text"); + + b.Property("SourceIdentity") + .HasColumnType("text"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DecisionId"); + + b.HasIndex("ProjectId", "Slug") + .IsUnique(); + + b.HasIndex("ProjectId", "Status"); + + b.ToTable("DecisionInbox"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DismissedNotification", b => + { + b.Property("User") + .HasColumnType("text") + .HasColumnName("user"); + + b.Property("NotificationId") + .HasColumnType("text") + .HasColumnName("notification_id"); + + b.Property("DismissedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("dismissed_at"); + + b.HasKey("User", "NotificationId"); + + b.ToTable("dismissed_notifications", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAppAuthorizationRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("Purpose") + .HasColumnType("integer") + .HasColumnName("purpose"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("revoked_at"); + + b.HasKey("Id"); + + b.HasIndex("EntraObjectId", "AppKind", "Purpose"); + + b.ToTable("github_app_authorizations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuditRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("integer") + .HasColumnName("action"); + + b.Property("ActorKind") + .HasColumnType("integer") + .HasColumnName("actor_kind"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("CapabilityPurpose") + .HasColumnType("integer") + .HasColumnName("capability_purpose"); + + b.Property("CorrelationId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("correlation_id"); + + b.Property("EntraObjectId") + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("occurred_at"); + + b.Property("Outcome") + .HasColumnType("integer") + .HasColumnName("outcome"); + + b.Property("ReasonCode") + .HasColumnType("integer") + .HasColumnName("reason_code"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("resource_id"); + + b.HasKey("Id"); + + b.HasIndex("OccurredAt"); + + b.ToTable("github_audit_records", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuthorizationRecord", b => + { + b.Property("State") + .HasColumnType("text") + .HasColumnName("state"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("BrowserSessionId") + .HasColumnType("text") + .HasColumnName("browser_session_id"); + + b.Property("CallbackCookieHash") + .IsRequired() + .HasColumnType("text") + .HasColumnName("callback_cookie_hash"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAtUnixMilliseconds") + .HasColumnType("bigint") + .HasColumnName("expires_at_unix_ms"); + + b.Property("ExternalTransactionId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("external_transaction_id"); + + b.Property("PkceVerifierProtected") + .IsRequired() + .HasColumnType("text") + .HasColumnName("pkce_verifier_protected"); + + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("integer") + .HasColumnName("purpose"); + + b.Property("ReturnRouteKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("return_route_key"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("State"); + + b.HasIndex("ExpiresAtUnixMilliseconds"); + + b.HasIndex("ExternalTransactionId") + .IsUnique(); + + b.HasIndex("ProjectId"); + + b.HasIndex("EntraObjectId", "State") + .IsUnique(); + + b.ToTable("github_authorizations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubInstallationRecord", b => + { + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("revoked_at"); + + b.HasKey("InstallationId"); + + b.HasIndex("ProjectId"); + + b.ToTable("github_installations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubLifecycleDeliveryRecord", b => + { + b.Property("DeliveryId") + .HasColumnType("text") + .HasColumnName("delivery_id"); + + b.Property("EventName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("event_name"); + + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("ReceivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("received_at"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.HasKey("DeliveryId"); + + b.ToTable("github_lifecycle_deliveries", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", b => + { + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.Property("FullNameDisplay") + .IsRequired() + .HasColumnType("text") + .HasColumnName("full_name_display"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("granted_at"); + + b.Property("PermissionDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("permission_digest"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("revoked_at"); + + b.HasKey("InstallationId", "RepositoryId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("InstallationId", "RepositoryId") + .IsUnique(); + + b.ToTable("github_repository_grants", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositorySelectionCodeRecord", b => + { + b.Property("CodeHash") + .HasColumnType("text") + .HasColumnName("code_hash"); + + b.Property("ConsumedAtUnixMilliseconds") + .HasColumnType("bigint") + .HasColumnName("consumed_at_unix_ms"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAtUnixMilliseconds") + .HasColumnType("bigint") + .HasColumnName("expires_at_unix_ms"); + + b.Property("RepoAppAuthorizationId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("repo_app_authorization_id"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.HasKey("CodeHash"); + + b.HasIndex("ExpiresAtUnixMilliseconds"); + + b.HasIndex("EntraObjectId", "ExpiresAtUnixMilliseconds"); + + b.ToTable("github_repository_selection_codes", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.IntegrationBuildLockRecord", b => + { + b.Property("ProjectId") + .HasColumnType("text"); + + b.Property("AcquiredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OwnerPodId") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerToken") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("ProjectId"); + + b.ToTable("IntegrationBuildLocks"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.OutcomeSpec", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowTaskPromotion") + .HasColumnType("boolean"); + + b.Property("Assumptions") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClarifyingQuestions") + .HasColumnType("text"); + + b.Property("ConfirmedBy") + .HasColumnType("text"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DesiredOutcome") + .IsRequired() + .HasColumnType("text"); + + b.Property("Goal") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "CoordinatorRunId"); + + b.ToTable("OutcomeSpecs"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.PlatformDefaultCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.ToTable("platform_default_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique() + .HasDatabaseName("UX_project_copilot_bindings_active_project") + .HasFilter("status = 0"); + + b.ToTable("project_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectModelProviderCapabilityRecord", b => + { + b.Property("CapabilityRef") + .HasColumnType("text") + .HasColumnName("capability_ref"); + + b.Property("ClaimLeaseExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("claim_lease_expires_at"); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("consumed_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("IssuedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("issued_at"); + + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Purpose") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("purpose"); + + b.Property("SourceBindingId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("source_binding_id"); + + b.HasKey("CapabilityRef"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_marketplace_copilot_capabilities_expiry_cleanup"); + + b.HasIndex("ProjectId", "EntraObjectId", "ExpiresAt") + .HasDatabaseName("IX_marketplace_copilot_capabilities_expiry"); + + b.ToTable("marketplace_copilot_capabilities", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectRecord", b => + { + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("ActiveReviewPolicyName") + .HasColumnType("text") + .HasColumnName("active_review_policy_name"); + + b.Property("AllowedWorkflowIds") + .HasColumnType("text") + .HasColumnName("allowed_workflow_ids"); + + b.Property("BlueprintGenerationModel") + .HasColumnType("text") + .HasColumnName("blueprint_generation_model"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("DefaultBranch") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("main") + .HasColumnName("default_branch"); + + b.Property("DefaultModelCopilot") + .HasColumnType("text") + .HasColumnName("default_model_copilot"); + + b.Property("DefaultModelFoundry") + .HasColumnType("text") + .HasColumnName("default_model_foundry"); + + b.Property("DefaultProvider") + .IsRequired() + .HasColumnType("text") + .HasColumnName("default_provider"); + + b.Property("DefaultWorkflowId") + .HasColumnType("text") + .HasColumnName("default_workflow_id"); + + b.Property("MaxReadyPerHeartbeat") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(3) + .HasColumnName("max_ready_per_heartbeat"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OriginKind") + .IsRequired() + .HasColumnType("text") + .HasColumnName("origin_kind"); + + b.Property("OutcomeSpecGenerationModel") + .HasColumnType("text") + .HasColumnName("outcome_spec_generation_model"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("text") + .HasColumnName("owner"); + + b.Property("PickupAutoApproveTools") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("pickup_auto_approve_tools"); + + b.Property("PickupAutopilot") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("pickup_autopilot"); + + b.Property("PreviewApprovalTimeoutMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30) + .HasColumnName("preview_approval_timeout_minutes"); + + b.Property("SandboxProfile") + .HasColumnType("text") + .HasColumnName("sandbox_profile"); + + b.Property("SourceBlueprintId") + .HasColumnType("text") + .HasColumnName("source_blueprint_id"); + + b.Property("SourceBlueprintType") + .HasColumnType("text") + .HasColumnName("source_blueprint_type"); + + b.Property("SourceRepository") + .HasColumnType("text") + .HasColumnName("source_repository"); + + b.Property("State") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("active") + .HasColumnName("state"); + + b.Property("TeamRevision") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("team_revision"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.Property("WebhookSecret") + .HasColumnType("text") + .HasColumnName("webhook_secret"); + + b.Property("WorkflowGenerationModel") + .HasColumnType("text") + .HasColumnName("workflow_generation_model"); + + b.Property("WorkingDirectory") + .IsRequired() + .HasColumnType("text") + .HasColumnName("working_directory"); + + b.HasKey("ProjectId"); + + b.HasIndex("State") + .HasDatabaseName("IX_projects_state"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectRoleAssignmentRecord", b => + { + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("PrincipalId") + .HasColumnType("text") + .HasColumnName("principal_id"); + + b.Property("GrantedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("granted_at"); + + b.Property("GrantedBy") + .IsRequired() + .HasColumnType("text") + .HasColumnName("granted_by"); + + b.Property("Role") + .IsRequired() + .HasColumnType("text") + .HasColumnName("role"); + + b.HasKey("ProjectId", "PrincipalId"); + + b.HasIndex("PrincipalId") + .HasDatabaseName("IX_project_role_assignments_principal_id"); + + b.HasIndex("ProjectId", "Role") + .HasDatabaseName("IX_project_role_assignments_project_role"); + + b.ToTable("project_role_assignments", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunAuthorshipCapability", b => + { + b.Property("RunId") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("run_id"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("bytea") + .HasColumnName("token_hash"); + + b.HasKey("RunId"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("run_authorship_capabilities", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubCapabilitySnapshotRecord", b => + { + b.Property("SnapshotRef") + .HasColumnType("text") + .HasColumnName("snapshot_ref"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("CapturedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("captured_at"); + + b.Property("CredentialReference") + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("integer") + .HasColumnName("purpose"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("run_id"); + + b.Property("SnapshotExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("snapshot_expires_at"); + + b.Property("SourceAuthorizationId") + .HasColumnType("text") + .HasColumnName("source_authorization_id"); + + b.Property("SourceBindingId") + .HasColumnType("text") + .HasColumnName("source_binding_id"); + + b.Property("SourceKind") + .HasColumnType("integer") + .HasColumnName("source_kind"); + + b.HasKey("SnapshotRef"); + + b.HasIndex("ProjectId"); + + b.HasIndex("RunId", "Purpose") + .IsUnique() + .HasDatabaseName("UX_run_github_capability_snapshots_run_purpose"); + + b.ToTable("run_github_capability_snapshots", null, t => + { + t.HasCheckConstraint("CK_run_github_capability_snapshots_purpose_mapping", "(purpose = 0 AND app_kind = 0 AND source_kind = 0 AND entra_object_id IS NOT NULL AND source_authorization_id IS NOT NULL AND source_binding_id IS NULL AND installation_id IS NULL AND repository_id IS NOT NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)\nOR (purpose = 1 AND app_kind = 0 AND source_kind = 0 AND entra_object_id IS NOT NULL AND source_authorization_id IS NOT NULL AND source_binding_id IS NULL AND installation_id IS NULL AND repository_id IS NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)\nOR (purpose = 2 AND app_kind = 0 AND source_kind = 1 AND entra_object_id IS NULL AND source_authorization_id IS NULL AND source_binding_id IS NULL AND installation_id IS NOT NULL AND repository_id IS NOT NULL AND credential_reference IS NULL AND credential_version IS NULL)\nOR (purpose = 3 AND app_kind = 1 AND source_kind = 2 AND entra_object_id IS NULL AND source_authorization_id IS NULL AND source_binding_id IS NOT NULL AND installation_id IS NULL AND repository_id IS NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubIdentitySnapshotRecord", b => + { + b.Property("RunId") + .HasColumnType("text") + .HasColumnName("run_id"); + + b.Property("AppKind") + .HasColumnType("integer") + .HasColumnName("app_kind"); + + b.Property("CapturedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("captured_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("InstallationId") + .HasColumnType("bigint") + .HasColumnName("installation_id"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("integer") + .HasColumnName("purpose"); + + b.Property("RepositoryId") + .HasColumnType("bigint") + .HasColumnName("repository_id"); + + b.HasKey("RunId"); + + b.HasIndex("ProjectId"); + + b.ToTable("run_github_identity_snapshots", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunRecord", b => + { + b.Property("RunId") + .HasColumnType("text") + .HasColumnName("run_id"); + + b.Property("AgentCharter") + .HasColumnType("text") + .HasColumnName("agent_charter"); + + b.Property("AgentName") + .HasColumnType("text") + .HasColumnName("agent_name"); + + b.Property("ApprovalGeneration") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1) + .HasColumnName("approval_generation"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("archived_at"); + + b.Property("Attempt") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasColumnName("attempt"); + + b.Property("Diff") + .HasColumnType("text") + .HasColumnName("diff"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ended_at"); + + b.Property("FencingToken") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(0L) + .HasColumnName("fencing_token"); + + b.Property("HeartbeatAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("heartbeat_at"); + + b.Property("LeaseExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("lease_expires_at"); + + b.Property("MergeConflicts") + .HasColumnType("text") + .HasColumnName("merge_conflicts"); + + b.Property("MergedCommitHash") + .HasColumnType("text") + .HasColumnName("merged_commit_hash"); + + b.Property("ModelId") + .HasColumnType("text") + .HasColumnName("model_id"); + + b.Property("ModelSource") + .IsRequired() + .HasColumnType("text") + .HasColumnName("model_source"); + + b.Property("Origin") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("interactive") + .HasColumnName("origin"); + + b.Property("OriginatingBranch") + .IsRequired() + .HasColumnType("text") + .HasColumnName("originating_branch"); + + b.Property("OwnerId") + .HasColumnType("text") + .HasColumnName("owner_id"); + + b.Property("ParentRunId") + .HasColumnType("text") + .HasColumnName("parent_run_id"); + + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("RepositoryPath") + .IsRequired() + .HasColumnType("text") + .HasColumnName("repository_path"); + + b.Property("Result") + .HasColumnType("text") + .HasColumnName("result"); + + b.Property("RetriedFrom") + .HasColumnType("text") + .HasColumnName("retried_from"); + + b.Property("ReviewReadyAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("review_ready_at"); + + b.Property("ReviewedBy") + .HasColumnType("text") + .HasColumnName("reviewed_by"); + + b.Property("SandboxBackend") + .HasColumnType("text") + .HasColumnName("sandbox_backend"); + + b.Property("SandboxClaimName") + .HasColumnType("text") + .HasColumnName("sandbox_claim_name"); + + b.Property("SandboxNamespace") + .HasColumnType("text") + .HasColumnName("sandbox_namespace"); + + b.Property("SandboxPodName") + .HasColumnType("text") + .HasColumnName("sandbox_pod_name"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text") + .HasColumnName("status"); + + b.Property("SubmittingUser") + .IsRequired() + .HasColumnType("text") + .HasColumnName("submitting_user"); + + b.Property("SubtaskId") + .HasColumnType("text") + .HasColumnName("subtask_id"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text") + .HasColumnName("task"); + + b.Property("TreeHash") + .HasColumnType("text") + .HasColumnName("tree_hash"); + + b.Property("WorkflowRunId") + .HasColumnType("text") + .HasColumnName("workflow_run_id"); + + b.Property("WorkflowSelectionReason") + .HasColumnType("text") + .HasColumnName("workflow_selection_reason"); + + b.Property("WorktreeBranch") + .HasColumnType("text") + .HasColumnName("worktree_branch"); + + b.Property("WorktreePath") + .HasColumnType("text") + .HasColumnName("worktree_path"); + + b.HasKey("RunId"); + + b.HasIndex("WorkflowRunId") + .HasDatabaseName("IX_runs_workflow_run_id"); + + b.HasIndex("Origin", "Status") + .HasDatabaseName("IX_runs_origin_status"); + + b.HasIndex("ParentRunId", "SubtaskId") + .HasDatabaseName("IX_runs_parent_subtask"); + + b.HasIndex("ProjectId", "Status") + .HasDatabaseName("IX_runs_project_status"); + + b.ToTable("runs", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunRevisionRecord", b => + { + b.Property("RunId") + .HasColumnType("text") + .HasColumnName("run_id"); + + b.Property("RevisionNumber") + .HasColumnType("integer") + .HasColumnName("revision_number"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("PreviousTreeHash") + .IsRequired() + .HasColumnType("text") + .HasColumnName("previous_tree_hash"); + + b.Property("RawComment") + .IsRequired() + .HasColumnType("text") + .HasColumnName("raw_comment"); + + b.Property("ReviewerUser") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reviewer_user"); + + b.Property("SanitizedComment") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sanitized_comment"); + + b.HasKey("RunId", "RevisionNumber"); + + b.ToTable("run_revisions", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SessionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveIssues") + .HasColumnType("text"); + + b.Property("EndedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FocusArea") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SerializedState") + .HasColumnType("text"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Summary") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "EndedAt"); + + b.HasIndex("ProjectId", "SessionId") + .IsUnique(); + + b.ToTable("SessionContexts"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillAssignmentRecord", b => + { + b.Property("ProjectId") + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("SkillId") + .HasColumnType("text") + .HasColumnName("skill_id"); + + b.Property("AgentName") + .HasColumnType("text") + .HasColumnName("agent_name"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.HasKey("ProjectId", "SkillId", "AgentName"); + + b.HasIndex("ProjectId", "AgentName") + .HasDatabaseName("IX_skill_assignments_agent"); + + b.ToTable("skill_assignments", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillMarketplaceSourceRecord", b => + { + b.Property("SourceId") + .HasColumnType("text") + .HasColumnName("source_id"); + + b.Property("Branch") + .HasColumnType("text") + .HasColumnName("branch"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Enabled") + .HasColumnType("boolean") + .HasColumnName("enabled"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ParseStrategy") + .HasColumnType("text") + .HasColumnName("parse_strategy"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Repository") + .IsRequired() + .HasColumnType("text") + .HasColumnName("repository"); + + b.Property("Subpath") + .HasColumnType("text") + .HasColumnName("subpath"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("SourceId"); + + b.HasIndex("ProjectId"); + + b.ToTable("skill_marketplace_sources", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillRecord", b => + { + b.Property("SkillId") + .HasColumnType("text") + .HasColumnName("skill_id"); + + b.Property("ContentHash") + .IsRequired() + .HasColumnType("text") + .HasColumnName("content_hash"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Instructions") + .IsRequired() + .HasColumnType("text") + .HasColumnName("instructions"); + + b.Property("MarketplaceName") + .HasColumnType("text") + .HasColumnName("marketplace_name"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("Provenance") + .IsRequired() + .HasColumnType("text") + .HasColumnName("provenance"); + + b.Property("Resources") + .HasColumnType("text") + .HasColumnName("resources"); + + b.Property("SourceLocation") + .HasColumnType("text") + .HasColumnName("source_location"); + + b.Property("SourceRepository") + .HasColumnType("text") + .HasColumnName("source_repository"); + + b.Property("Status") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("active") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("SkillId"); + + b.HasAlternateKey("ProjectId", "SkillId") + .HasName("AK_skills_project_id_skill_id"); + + b.ToTable("skills", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SteeringDirective", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActionAttempt") + .HasColumnType("integer"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("DecidedAction") + .HasColumnType("text"); + + b.Property("ExecStartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExecutionAttempts") + .HasColumnType("integer"); + + b.Property("Instruction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Kind") + .IsRequired() + .HasColumnType("text"); + + b.Property("RelayedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Severity") + .HasColumnType("text"); + + b.Property("Source") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TargetChildRunId") + .HasColumnType("text"); + + b.Property("TargetScopeJson") + .HasColumnType("text"); + + b.Property("TreeHash") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId", "Status"); + + b.ToTable("SteeringDirectives"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SteeringRevisionExecution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActionAttempt") + .HasColumnType("integer"); + + b.Property("CheckpointWatermark") + .HasColumnType("integer"); + + b.Property("ConfirmedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectState") + .IsRequired() + .HasColumnType("text"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("SteeringDirectiveId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("SteeringDirectiveId", "ActionAttempt", "RunId") + .IsUnique(); + + b.ToTable("SteeringRevisionExecutions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Subtask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentCharter") + .HasColumnType("text"); + + b.Property("AssignedAgent") + .IsRequired() + .HasColumnType("text"); + + b.Property("ChildRunId") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeclaredOutputPathsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("InfrastructureRetryCount") + .HasColumnType("integer"); + + b.Property("InfrastructureRetryEligibleAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsolationStrategy") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastResetAttempt") + .HasColumnType("integer"); + + b.Property("LastResetDirectiveId") + .HasColumnType("integer"); + + b.Property("LockedOutAgents") + .HasColumnType("text"); + + b.Property("Phase") + .IsRequired() + .HasColumnType("text"); + + b.Property("PriorChildRunId") + .HasColumnType("text"); + + b.Property("RecoveryAttempts") + .HasColumnType("integer"); + + b.Property("RecoveryGuidance") + .HasColumnType("text"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("text"); + + b.Property("SelectedModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("SteeringRetentionUntil") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkPlanId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("WorkPlanId"); + + b.ToTable("Subtasks"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SubtaskDependency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DependsOnSubtaskId") + .HasColumnType("integer"); + + b.Property("SubtaskId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("DependsOnSubtaskId"); + + b.HasIndex("SubtaskId"); + + b.ToTable("SubtaskDependencies"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.UserCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("text") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("text") + .HasColumnName("grant_digest"); + + b.Property("Status") + .HasColumnType("integer") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("EntraObjectId") + .IsUnique() + .HasDatabaseName("UX_user_copilot_bindings_active_user") + .HasFilter("status = 0"); + + b.ToTable("user_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.UserModelProviderSettingsRecord", b => + { + b.Property("EntraObjectId") + .HasColumnType("text") + .HasColumnName("entra_object_id"); + + b.Property("ByokAzureApiVersion") + .HasColumnType("text") + .HasColumnName("byok_azure_api_version"); + + b.Property("ByokBaseUrl") + .HasColumnType("text") + .HasColumnName("byok_base_url"); + + b.Property("ByokCredentialReference") + .HasColumnType("text") + .HasColumnName("byok_credential_reference"); + + b.Property("ByokHeadersJson") + .HasColumnType("text") + .HasColumnName("byok_headers_json"); + + b.Property("ByokModel") + .HasColumnType("text") + .HasColumnName("byok_model"); + + b.Property("ByokName") + .HasColumnType("text") + .HasColumnName("byok_name"); + + b.Property("ByokProviderId") + .HasColumnType("text") + .HasColumnName("byok_provider_id"); + + b.Property("ByokType") + .HasColumnType("text") + .HasColumnName("byok_type"); + + b.Property("ByokWireApi") + .HasColumnType("text") + .HasColumnName("byok_wire_api"); + + b.Property("Preference") + .HasColumnType("integer") + .HasColumnName("preference"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("EntraObjectId"); + + b.ToTable("user_model_provider_settings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AssemblyStage") + .HasColumnType("text"); + + b.Property("AssemblyStartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AssemblyStatusReason") + .HasColumnType("text"); + + b.Property("AssemblyTerminalStage") + .HasColumnType("text"); + + b.Property("CoordinatorPodId") + .HasColumnType("text"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("HumanReviewRoundTrips") + .HasColumnType("integer"); + + b.Property("IntegrationBranch") + .HasColumnType("text"); + + b.Property("IsolationSummary") + .HasColumnType("text"); + + b.Property("OutcomeSpecId") + .HasColumnType("integer"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("SteeringIterations") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkflowId") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId"); + + b.HasIndex("OutcomeSpecId"); + + b.ToTable("WorkPlans"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkflowCheckpointRecord", b => + { + b.Property("StoreName") + .HasColumnType("text") + .HasColumnName("store_name"); + + b.Property("SessionId") + .HasColumnType("text") + .HasColumnName("session_id"); + + b.Property("CheckpointId") + .HasColumnType("text") + .HasColumnName("checkpoint_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("HasParentMetadata") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasColumnName("has_parent_metadata"); + + b.Property("ParentCheckpointId") + .HasColumnType("text") + .HasColumnName("parent_checkpoint_id"); + + b.Property("Payload") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("payload"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at"); + + b.HasKey("StoreName", "SessionId", "CheckpointId"); + + b.HasIndex("StoreName", "SessionId") + .HasDatabaseName("IX_workflow_checkpoints_store_session"); + + b.ToTable("workflow_checkpoints", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkflowRunRecord", b => + { + b.Property("WorkflowRunId") + .HasColumnType("text") + .HasColumnName("workflow_run_id"); + + b.Property("OrchestrationWorktreePath") + .HasColumnType("text") + .HasColumnName("orchestration_worktree_path"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("project_id"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("SubmittingUser") + .IsRequired() + .HasColumnType("text") + .HasColumnName("submitting_user"); + + b.Property("Task") + .IsRequired() + .HasColumnType("text") + .HasColumnName("task"); + + b.HasKey("WorkflowRunId"); + + b.HasIndex("ProjectId") + .HasDatabaseName("IX_workflow_runs_project_id"); + + b.ToTable("workflow_runs", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.CoordinatorDeferredDecisionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DecisionJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("DeferredDecisions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.PendingRequestRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OwnerUser") + .IsRequired() + .HasColumnType("text"); + + b.Property("RequestJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("PendingRequests"); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.RunEventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("text"); + + b.Property("PayloadJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Sequence") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RunId"); + + b.HasIndex("RunId", "Sequence") + .IsUnique(); + + b.ToTable("RunEvents"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationActivationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_activations_projects_project_id"); + + b.HasOne("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", null) + .WithMany() + .HasForeignKey("InstallationId", "RepositoryId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_automation_activations_repository_grants_installation_id_repository_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationInvocationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.AutomationActivationRecord", null) + .WithMany() + .HasForeignKey("ActivationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_invocations_activations_activation_id"); + + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_invocations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Decision", b => + { + b.HasOne("Agentweaver.Api.Memory.Decision", null) + .WithMany() + .HasForeignKey("SupersededById"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DecisionInboxEntry", b => + { + b.HasOne("Agentweaver.Api.Memory.Decision", null) + .WithMany() + .HasForeignKey("DecisionId"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuthorizationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_github_authorizations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubInstallationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_github_installations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.GitHubInstallationRecord", null) + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_github_repository_grants_installations_installation_id"); + + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_github_repository_grants_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectCopilotBindingRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_project_copilot_bindings_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectModelProviderCapabilityRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_marketplace_copilot_capabilities_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubCapabilitySnapshotRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_run_github_capability_snapshots_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubIdentitySnapshotRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_run_github_identity_snapshots_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillAssignmentRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_skill_assignments_projects_project_id"); + + b.HasOne("Agentweaver.Api.Memory.SkillRecord", null) + .WithMany() + .HasForeignKey("ProjectId", "SkillId") + .HasPrincipalKey("ProjectId", "SkillId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_skill_assignments_skills_project_id_skill_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillMarketplaceSourceRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_skill_marketplace_sources_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SkillRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_skills_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Subtask", b => + { + b.HasOne("Agentweaver.Api.Memory.WorkPlan", null) + .WithMany() + .HasForeignKey("WorkPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SubtaskDependency", b => + { + b.HasOne("Agentweaver.Api.Memory.Subtask", null) + .WithMany() + .HasForeignKey("DependsOnSubtaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Agentweaver.Api.Memory.Subtask", null) + .WithMany() + .HasForeignKey("SubtaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkPlan", b => + { + b.HasOne("Agentweaver.Api.Memory.OutcomeSpec", null) + .WithMany() + .HasForeignKey("OutcomeSpecId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.cs b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.cs new file mode 100644 index 000000000..dcc26d375 --- /dev/null +++ b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/20260904224046_AddOAuthConsentContinuationDecisionPostgres.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Agentweaver.Api.Migrations.Postgres.Migrations +{ + /// + public partial class AddOAuthConsentContinuationDecisionPostgres : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ContinuationDecision", + table: "OAuthAuthorizationTransactions", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ContinuationDecision", + table: "OAuthAuthorizationTransactions"); + } + } +} diff --git a/apps/Agentweaver.Api.Migrations.Postgres/Migrations/MemoryDbContextModelSnapshot.cs b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/MemoryDbContextModelSnapshot.cs index 54de01dbb..00440e222 100644 --- a/apps/Agentweaver.Api.Migrations.Postgres/Migrations/MemoryDbContextModelSnapshot.cs +++ b/apps/Agentweaver.Api.Migrations.Postgres/Migrations/MemoryDbContextModelSnapshot.cs @@ -92,6 +92,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ConsumedAt") .HasColumnType("timestamp with time zone"); + b.Property("ContinuationDecision") + .HasColumnType("text"); + b.Property("ExpiresAt") .HasColumnType("timestamp with time zone"); diff --git a/apps/Agentweaver.Api/Endpoints/OAuthAuthorizationServerEndpoints.cs b/apps/Agentweaver.Api/Endpoints/OAuthAuthorizationServerEndpoints.cs index 6963861cd..3734204e9 100644 --- a/apps/Agentweaver.Api/Endpoints/OAuthAuthorizationServerEndpoints.cs +++ b/apps/Agentweaver.Api/Endpoints/OAuthAuthorizationServerEndpoints.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; @@ -26,6 +27,7 @@ public static void MapOAuthAuthorizationServerEndpoints(this IEndpointRouteBuild .RequireRateLimiting("oauth-registration") .ProtocolManaged(); app.MapGet("/oauth/resume", ResumeAsync).ProtocolManaged(); + app.MapGet("/oauth/continue", ContinueAsync).ProtocolManaged(); app.MapPost("/oauth/token", TokenAsync).ProtocolManaged(); } @@ -102,7 +104,23 @@ private static async Task AuthorizeAsync( if (browser is null) { var handle = await SaveTransactionAsync( - db, request, scope, browserSessionId: null, subject: null, ct).ConfigureAwait(false); + db, + request, + scope, + browserSessionId: null, + subject: null, + continuationDecision: HttpMethods.IsPost(context.Request.Method) ? "reauthenticate" : null, + ct).ConfigureAwait(false); + if (HttpMethods.IsPost(context.Request.Method)) + { + var reauthenticationStyleNonce = Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(18)); + context.Response.Headers.CacheControl = "no-store"; + context.Response.Headers.ContentSecurityPolicy = + $"default-src 'none'; style-src 'nonce-{reauthenticationStyleNonce}'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'"; + return Results.Content( + RenderReauthentication(handle, reauthenticationStyleNonce), + "text/html; charset=utf-8"); + } return Results.Redirect($"/auth/entra/authorize?oauth_return_handle={Uri.EscapeDataString(handle)}"); } @@ -114,7 +132,23 @@ private static async Task AuthorizeAsync( var transaction = await ClaimTransactionAsync(db, handle, browser, request, ct).ConfigureAwait(false); if (transaction is null) return OAuthForbid(Errors.InvalidRequest, "The consent transaction is invalid or expired."); - if (decision != "approve") + var consentApproved = decision == "approve"; + if (IsIpv6CallbackRedirect(transaction.RedirectUri)) + { + if (consentApproved) + await UpsertConsentAsync(db, browser.EntraObjectId, request.ClientId!, scope, ct).ConfigureAwait(false); + var continuationHandle = await SaveContinuationAsync( + db, transaction, consentApproved ? "approve" : "deny", ct).ConfigureAwait(false); + var continuationStyleNonce = Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(18)); + context.Response.Headers.CacheControl = "no-store"; + context.Response.Headers["Referrer-Policy"] = "no-referrer"; + context.Response.Headers.ContentSecurityPolicy = + $"default-src 'none'; style-src 'nonce-{continuationStyleNonce}'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'"; + return Results.Content( + RenderCallbackContinuation(continuationHandle, consentApproved, continuationStyleNonce), + "text/html; charset=utf-8"); + } + if (!consentApproved) return OAuthForbid(Errors.AccessDenied, "The resource owner denied the request."); await UpsertConsentAsync(db, browser.EntraObjectId, request.ClientId!, scope, ct).ConfigureAwait(false); @@ -130,16 +164,22 @@ private static async Task AuthorizeAsync( return SignIn(browser.EntraObjectId, scope, configuration.Resource.AbsoluteUri); var consentHandle = await SaveTransactionAsync( - db, request, scope, browser.Id, browser.EntraObjectId, ct).ConfigureAwait(false); + db, request, scope, browser.Id, browser.EntraObjectId, continuationDecision: null, ct) + .ConfigureAwait(false); var application = await applications.FindByClientIdAsync(request.ClientId!, ct).ConfigureAwait(false); + if (application is null) + return OAuthForbid(Errors.InvalidRequest, "The OAuth client is not registered."); + var registeredRedirects = await applications.GetRedirectUrisAsync(application, ct).ConfigureAwait(false); + var formAction = BuildConsentFormActionDirective(request.RedirectUri!, registeredRedirects); + if (formAction is null) + return OAuthForbid(Errors.InvalidRequest, "The OAuth client redirect is invalid."); var descriptor = new OpenIddictApplicationDescriptor(); - if (application is not null) - await applications.PopulateAsync(descriptor, application, ct).ConfigureAwait(false); + await applications.PopulateAsync(descriptor, application, ct).ConfigureAwait(false); var clientName = descriptor.DisplayName ?? request.ClientId!; var styleNonce = Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(18)); context.Response.Headers.CacheControl = "no-store"; context.Response.Headers.ContentSecurityPolicy = - $"default-src 'none'; style-src 'nonce-{styleNonce}'; img-src 'self'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'"; + $"default-src 'none'; style-src 'nonce-{styleNonce}'; img-src 'self'; form-action {formAction}; base-uri 'none'; frame-ancestors 'none'"; return Results.Content( RenderConsent(request, scope, consentHandle, clientName, browser.EntraObjectId, styleNonce), "text/html; charset=utf-8"); @@ -161,6 +201,7 @@ private static async Task ResumeAsync( var transaction = await db.OAuthAuthorizationTransactions.AsNoTracking() .SingleOrDefaultAsync(x => x.HandleHash == hash, ct).ConfigureAwait(false); var claimed = transaction is not null + && transaction.ContinuationDecision is null or "reauthenticate" && transaction.ExpiresAt > DateTimeOffset.UtcNow && await db.OAuthAuthorizationTransactions .Where(x => x.HandleHash == hash && x.ConsumedAt == null) @@ -179,6 +220,73 @@ private static async Task ResumeAsync( ["code_challenge"] = transaction.CodeChallenge, ["code_challenge_method"] = CodeChallengeMethods.Sha256, ["resource"] = configuration.Resource.AbsoluteUri, + ["prompt"] = transaction.ContinuationDecision == "reauthenticate" ? "consent" : null, + }; + return Results.Redirect(QueryString.Create(query!).ToUriComponent().Insert(0, "/oauth/authorize")); + } + + private static async Task ContinueAsync( + HttpContext context, + string? handle, + BrowserEntraSessionService browserSessions, + MemoryDbContext db, + IOpenIddictApplicationManager applications, + OAuthServerConfiguration configuration, + CancellationToken ct) + { + var browser = await browserSessions.GetCurrentAsync(context, ct).ConfigureAwait(false); + if (browser is null || string.IsNullOrWhiteSpace(handle)) + return Results.Unauthorized(); + + var hash = OAuthCertificateLoader.HashOpaque(handle); + var continuation = await db.OAuthAuthorizationTransactions.AsNoTracking() + .SingleOrDefaultAsync(x => x.HandleHash == hash, ct).ConfigureAwait(false); + if (continuation is null + || continuation.ExpiresAt <= DateTimeOffset.UtcNow + || continuation.ConsumedAt is not null + || continuation.BrowserSessionId != browser.Id + || continuation.Subject != browser.EntraObjectId + || continuation.ContinuationDecision is not ("approve" or "deny") + || !IsIpv6CallbackRedirect(continuation.RedirectUri)) + return Results.BadRequest(new { error = "invalid_request" }); + + var application = await applications.FindByClientIdAsync(continuation.ClientId, ct).ConfigureAwait(false); + if (application is null) + return Results.BadRequest(new { error = "invalid_request" }); + var redirects = await applications.GetRedirectUrisAsync(application, ct).ConfigureAwait(false); + if (!redirects.Contains(continuation.RedirectUri, StringComparer.Ordinal)) + return Results.BadRequest(new { error = "invalid_request" }); + + var claimed = await db.OAuthAuthorizationTransactions + .Where(x => x.HandleHash == hash && x.ConsumedAt == null) + .ExecuteUpdateAsync(setters => setters.SetProperty( + x => x.ConsumedAt, DateTimeOffset.UtcNow), ct).ConfigureAwait(false); + if (claimed != 1) + return Results.BadRequest(new { error = "invalid_request" }); + + if (continuation.ContinuationDecision == "deny") + { + return Results.Redirect(QueryHelpers.AddQueryString( + continuation.RedirectUri, + new Dictionary + { + ["error"] = Errors.AccessDenied, + ["error_description"] = "The resource owner denied the request.", + ["state"] = continuation.ClientState, + ["iss"] = configuration.PublicOrigin.AbsoluteUri, + })); + } + + var query = new Dictionary + { + ["client_id"] = continuation.ClientId, + ["redirect_uri"] = continuation.RedirectUri, + ["response_type"] = ResponseTypes.Code, + ["scope"] = continuation.Scope, + ["state"] = continuation.ClientState, + ["code_challenge"] = continuation.CodeChallenge, + ["code_challenge_method"] = CodeChallengeMethods.Sha256, + ["resource"] = configuration.Resource.AbsoluteUri, }; return Results.Redirect(QueryString.Create(query!).ToUriComponent().Insert(0, "/oauth/authorize")); } @@ -248,6 +356,7 @@ private static async Task SaveTransactionAsync( string[] scopes, string? browserSessionId, string? subject, + string? continuationDecision, CancellationToken ct) { var handle = Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(32)); @@ -261,6 +370,7 @@ private static async Task SaveTransactionAsync( ClientState = request.State, BrowserSessionId = browserSessionId, Subject = subject, + ContinuationDecision = continuationDecision, ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(10), }); await db.SaveChangesAsync(ct).ConfigureAwait(false); @@ -280,6 +390,7 @@ private static async Task SaveTransactionAsync( var transaction = await db.OAuthAuthorizationTransactions.AsNoTracking() .SingleOrDefaultAsync(x => x.HandleHash == hash, ct).ConfigureAwait(false); if (transaction is null + || transaction.ContinuationDecision is not null || transaction.ExpiresAt <= DateTimeOffset.UtcNow || transaction.BrowserSessionId != browser.Id || transaction.Subject != browser.EntraObjectId @@ -295,6 +406,30 @@ private static async Task SaveTransactionAsync( return changed == 1 ? transaction : null; } + private static async Task SaveContinuationAsync( + MemoryDbContext db, + OAuthAuthorizationTransaction transaction, + string decision, + CancellationToken ct) + { + var handle = Base64UrlEncoder.Encode(RandomNumberGenerator.GetBytes(32)); + db.OAuthAuthorizationTransactions.Add(new OAuthAuthorizationTransaction + { + HandleHash = OAuthCertificateLoader.HashOpaque(handle), + ClientId = transaction.ClientId, + RedirectUri = transaction.RedirectUri, + CodeChallenge = transaction.CodeChallenge, + Scope = transaction.Scope, + ClientState = transaction.ClientState, + BrowserSessionId = transaction.BrowserSessionId, + Subject = transaction.Subject, + ContinuationDecision = decision, + ExpiresAt = DateTimeOffset.UtcNow.AddMinutes(2), + }); + await db.SaveChangesAsync(ct).ConfigureAwait(false); + return handle; + } + private static async Task UpsertConsentAsync( MemoryDbContext db, string subject, @@ -324,6 +459,35 @@ private static async Task UpsertConsentAsync( private static string[] NormalizeScopes(IEnumerable scopes) => scopes.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); + internal static string? BuildConsentFormActionDirective( + string requestedRedirectUri, + IEnumerable registeredRedirectUris) + { + var registered = registeredRedirectUris.FirstOrDefault(uri => + string.Equals(uri, requestedRedirectUri, StringComparison.Ordinal)); + if (registered is null + || registered.Any(c => char.IsWhiteSpace(c) || char.IsControl(c)) + || !OAuthRedirectUriValidator.IsValid(registered, allowDynamicLoopbackPort: true) + || !Uri.TryCreate(registered, UriKind.Absolute, out var uri)) + return null; + + if (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.Ordinal) + || string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal)) + { + if (uri.HostNameType == UriHostNameType.IPv6) + return "'self'"; + return $"'self' {uri.GetLeftPart(UriPartial.Authority)}"; + } + + return $"{uri.Scheme}:"; + } + + private static bool IsIpv6CallbackRedirect(string redirectUri) => + Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) + && (string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.Ordinal) + || string.Equals(uri.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal)) + && uri.HostNameType == UriHostNameType.IPv6; + private static async Task HasExactResourceAsync( HttpContext context, OpenIddictRequest request, @@ -457,4 +621,74 @@ private static string RenderConsent( """; } + + private static string RenderReauthentication(string handle, string styleNonce) + { + static string Encode(string value) => HtmlEncoder.Default.Encode(value); + var href = $"/auth/entra/authorize?oauth_return_handle={Uri.EscapeDataString(handle)}"; + return $$""" + + + + + + Sign in again | Agentweaver + + + +
+

Sign in again

+

Your Agentweaver browser session expired before consent was submitted.

+ Continue to sign in +
+ + + """; + } + + private static string RenderCallbackContinuation(string handle, bool approved, string styleNonce) + { + static string Encode(string value) => HtmlEncoder.Default.Encode(value); + var href = $"/oauth/continue?handle={Uri.EscapeDataString(handle)}"; + var title = approved ? "Access approved" : "Request denied"; + var description = approved + ? "Continue to return to your MCP client and finish connecting." + : "Continue to return to your MCP client."; + return $$""" + + + + + + {{Encode(title)}} | Agentweaver + + + +
+

{{Encode(title)}}

+

{{Encode(description)}}

+ Continue +
+ + + """; + } } diff --git a/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.Designer.cs b/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.Designer.cs new file mode 100644 index 000000000..854816167 --- /dev/null +++ b/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.Designer.cs @@ -0,0 +1,2489 @@ +// +using System; +using Agentweaver.Api.Memory; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Agentweaver.Api.Migrations +{ + [DbContext(typeof(MemoryDbContext))] + [Migration("20260904224040_AddOAuthConsentContinuationDecision")] + partial class AddOAuthConsentContinuationDecision + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.10"); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.BrowserEntraSession", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("PlatformRoles") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("BrowserEntraSessions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.EntraOAuthState", b => + { + b.Property("State") + .HasColumnType("TEXT"); + + b.Property("CodeVerifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Nonce") + .HasColumnType("TEXT"); + + b.Property("ReturnHandle") + .HasColumnType("TEXT"); + + b.HasKey("State"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("EntraOAuthStates"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthAuthorizationTransaction", b => + { + b.Property("HandleHash") + .HasColumnType("TEXT"); + + b.Property("BrowserSessionId") + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ClientState") + .HasColumnType("TEXT"); + + b.Property("CodeChallenge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ConsumedAt") + .HasColumnType("TEXT"); + + b.Property("ContinuationDecision") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("RedirectUri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("HandleHash"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("OAuthAuthorizationTransactions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthConsentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("Scopes") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Subject", "ClientId") + .IsUnique(); + + b.ToTable("OAuthConsents"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthDynamicRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DisabledAt") + .HasColumnType("TEXT"); + + b.Property("RegisteredAt") + .HasColumnType("TEXT"); + + b.Property("SourceHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.HasIndex("SourceHash", "RegisteredAt"); + + b.ToTable("OAuthDynamicRegistrations"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthMaintenanceLease", b => + { + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("LeaseExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Name"); + + b.ToTable("OAuthMaintenanceLeases"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.OAuthRefreshTokenFamily", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AuthorizationId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("RevocationReason") + .HasColumnType("TEXT"); + + b.Property("RevokedAt") + .HasColumnType("TEXT"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId") + .IsUnique(); + + b.ToTable("OAuthRefreshTokenFamilies"); + }); + + modelBuilder.Entity("Agentweaver.Api.Auth.OAuth.WebSessionExchangeCode", b => + { + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("Login") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Code"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("WebSessionExchangeCodes"); + }); + + modelBuilder.Entity("Agentweaver.Api.Coordinator.CoordinatorAssemblyReviewRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AggregateTreeHash") + .HasColumnType("TEXT"); + + b.Property("CoordinatorFailedAt") + .HasColumnType("TEXT"); + + b.Property("CoordinatorFailureReason") + .HasColumnType("TEXT"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DecisionJson") + .HasColumnType("TEXT"); + + b.Property("DecisionSubmittedAt") + .HasColumnType("TEXT"); + + b.Property("IntegrationBranch") + .HasColumnType("TEXT"); + + b.Property("OwnerUser") + .HasColumnType("TEXT"); + + b.Property("Reviewer") + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId") + .IsUnique(); + + b.ToTable("AssemblyReviews"); + }); + + modelBuilder.Entity("Agentweaver.Api.Diagnostics.HeartbeatStatusRecord", b => + { + b.Property("PodName") + .HasColumnType("TEXT"); + + b.Property("ActedCount") + .HasColumnType("INTEGER"); + + b.Property("DurationMs") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("ErrorCount") + .HasColumnType("INTEGER"); + + b.Property("IntervalSeconds") + .HasColumnType("INTEGER"); + + b.Property("LastTickUtc") + .HasColumnType("TEXT"); + + b.HasKey("PodName"); + + b.ToTable("HeartbeatStatuses"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AgentMemory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ApprovedAt") + .HasColumnType("TEXT"); + + b.Property("ApprovedBy") + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Importance") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasColumnType("TEXT"); + + b.Property("SourceIdentity") + .HasColumnType("TEXT"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("TEXT"); + + b.Property("Tags") + .HasColumnType("TEXT"); + + b.Property("TrustState") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("legacy"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "AgentName"); + + b.HasIndex("ProjectId", "Type"); + + b.ToTable("AgentMemory"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationActivationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ActivatedAt") + .HasColumnType("TEXT") + .HasColumnName("activated_at"); + + b.Property("AutomationKey") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("automation_key"); + + b.Property("ByokProviderId") + .HasColumnType("TEXT") + .HasColumnName("byok_provider_id"); + + b.Property("CopilotBindingGrantDigest") + .HasColumnType("TEXT") + .HasColumnName("copilot_binding_grant_digest"); + + b.Property("CopilotBindingId") + .HasColumnType("TEXT") + .HasColumnName("copilot_binding_id"); + + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("InvalidatedAt") + .HasColumnType("TEXT") + .HasColumnName("invalidated_at"); + + b.Property("ModelProviderSource") + .HasColumnType("INTEGER") + .HasColumnName("model_provider_source"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("RepositoryGrantDigest") + .HasColumnType("TEXT") + .HasColumnName("repository_grant_digest"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique() + .HasDatabaseName("UX_automation_activations_active_project") + .HasFilter("status = 0"); + + b.HasIndex("InstallationId", "RepositoryId"); + + b.ToTable("automation_activations", null, t => + { + t.HasCheckConstraint("CK_automation_activations_snapshot_tuple", "status <> 0 OR (\n (\n (installation_id IS NULL AND repository_id IS NULL AND repository_grant_digest IS NULL)\n OR\n (installation_id IS NOT NULL AND installation_id > 0 AND\n repository_id IS NOT NULL AND repository_id > 0 AND\n repository_grant_digest IS NOT NULL AND repository_grant_digest <> '')\n ) AND (\n (model_provider_source = 1 AND\n byok_provider_id IS NOT NULL AND byok_provider_id <> '' AND\n (copilot_binding_id IS NULL OR copilot_binding_id = '') AND\n (copilot_binding_grant_digest IS NULL OR copilot_binding_grant_digest = ''))\n OR\n (model_provider_source <> 1 AND\n copilot_binding_id IS NOT NULL AND copilot_binding_id <> '' AND\n copilot_binding_grant_digest IS NOT NULL AND copilot_binding_grant_digest <> '' AND\n (byok_provider_id IS NULL OR byok_provider_id = ''))\n ))"); + }); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationInvocationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("ActivationId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("activation_id"); + + b.Property("BacklogTaskId") + .HasColumnType("TEXT") + .HasColumnName("backlog_task_id"); + + b.Property("CompletedAt") + .HasColumnType("TEXT") + .HasColumnName("completed_at"); + + b.Property("DeliveryId") + .HasColumnType("TEXT") + .HasColumnName("delivery_id"); + + b.Property("EventName") + .HasColumnType("TEXT") + .HasColumnName("event_name"); + + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("OccurrenceKey") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("occurrence_key"); + + b.Property("Outcome") + .HasColumnType("INTEGER") + .HasColumnName("outcome"); + + b.Property("PendingBacklogTaskId") + .HasColumnType("TEXT") + .HasColumnName("pending_backlog_task_id"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("ReceivedAt") + .HasColumnType("TEXT") + .HasColumnName("received_at"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.HasKey("Id"); + + b.HasIndex("BacklogTaskId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_backlog_task_id") + .HasFilter("backlog_task_id IS NOT NULL"); + + b.HasIndex("DeliveryId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_delivery_id") + .HasFilter("delivery_id IS NOT NULL"); + + b.HasIndex("PendingBacklogTaskId") + .IsUnique() + .HasDatabaseName("UX_automation_invocations_pending_backlog_task_id") + .HasFilter("pending_backlog_task_id IS NOT NULL"); + + b.HasIndex("ProjectId"); + + b.HasIndex("ActivationId", "OccurrenceKey") + .IsUnique(); + + b.ToTable("automation_invocations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationProjectGuardRecord", b => + { + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("RepositoryAttached") + .HasColumnType("INTEGER") + .HasColumnName("repository_attached"); + + b.HasKey("ProjectId"); + + b.ToTable("automation_project_guards", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Decision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ApprovedAt") + .HasColumnType("TEXT"); + + b.Property("ApprovedBy") + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Rationale") + .HasColumnType("TEXT"); + + b.Property("SourceIdentity") + .HasColumnType("TEXT"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SupersededById") + .HasColumnType("INTEGER"); + + b.Property("Tags") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TrustState") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("legacy"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SupersededById"); + + b.HasIndex("ProjectId", "AgentName"); + + b.HasIndex("ProjectId", "Status"); + + b.ToTable("Decisions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DecisionInboxEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgentName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DecisionId") + .HasColumnType("INTEGER"); + + b.Property("MergedAt") + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Rationale") + .HasColumnType("TEXT"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SourceIdentity") + .HasColumnType("TEXT"); + + b.Property("SourceKind") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("legacy"); + + b.Property("SourceRunId") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DecisionId"); + + b.HasIndex("ProjectId", "Slug") + .IsUnique(); + + b.HasIndex("ProjectId", "Status"); + + b.ToTable("DecisionInbox"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DismissedNotification", b => + { + b.Property("User") + .HasColumnType("TEXT") + .HasColumnName("user"); + + b.Property("NotificationId") + .HasColumnType("TEXT") + .HasColumnName("notification_id"); + + b.Property("DismissedAt") + .HasColumnType("TEXT") + .HasColumnName("dismissed_at"); + + b.HasKey("User", "NotificationId"); + + b.ToTable("dismissed_notifications", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAppAuthorizationRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("Purpose") + .HasColumnType("INTEGER") + .HasColumnName("purpose"); + + b.Property("RevokedAt") + .HasColumnType("TEXT") + .HasColumnName("revoked_at"); + + b.HasKey("Id"); + + b.HasIndex("EntraObjectId", "AppKind", "Purpose"); + + b.ToTable("github_app_authorizations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuditRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Action") + .HasColumnType("INTEGER") + .HasColumnName("action"); + + b.Property("ActorKind") + .HasColumnType("INTEGER") + .HasColumnName("actor_kind"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("CapabilityPurpose") + .HasColumnType("INTEGER") + .HasColumnName("capability_purpose"); + + b.Property("CorrelationId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("correlation_id"); + + b.Property("EntraObjectId") + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("OccurredAt") + .HasColumnType("TEXT") + .HasColumnName("occurred_at"); + + b.Property("Outcome") + .HasColumnType("INTEGER") + .HasColumnName("outcome"); + + b.Property("ReasonCode") + .HasColumnType("INTEGER") + .HasColumnName("reason_code"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("resource_id"); + + b.HasKey("Id"); + + b.HasIndex("OccurredAt"); + + b.ToTable("github_audit_records", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuthorizationRecord", b => + { + b.Property("State") + .HasColumnType("TEXT") + .HasColumnName("state"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("BrowserSessionId") + .HasColumnType("TEXT") + .HasColumnName("browser_session_id"); + + b.Property("CallbackCookieHash") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("callback_cookie_hash"); + + b.Property("CompletedAt") + .HasColumnType("TEXT") + .HasColumnName("completed_at"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAtUnixMilliseconds") + .HasColumnType("INTEGER") + .HasColumnName("expires_at_unix_ms"); + + b.Property("ExternalTransactionId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("external_transaction_id"); + + b.Property("PkceVerifierProtected") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("pkce_verifier_protected"); + + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("INTEGER") + .HasColumnName("purpose"); + + b.Property("ReturnRouteKey") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("return_route_key"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasColumnName("status"); + + b.HasKey("State"); + + b.HasIndex("ExpiresAtUnixMilliseconds"); + + b.HasIndex("ExternalTransactionId") + .IsUnique(); + + b.HasIndex("ProjectId"); + + b.HasIndex("EntraObjectId", "State") + .IsUnique(); + + b.ToTable("github_authorizations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubInstallationRecord", b => + { + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("RevokedAt") + .HasColumnType("TEXT") + .HasColumnName("revoked_at"); + + b.HasKey("InstallationId"); + + b.HasIndex("ProjectId"); + + b.ToTable("github_installations", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubLifecycleDeliveryRecord", b => + { + b.Property("DeliveryId") + .HasColumnType("TEXT") + .HasColumnName("delivery_id"); + + b.Property("EventName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("event_name"); + + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("ReceivedAt") + .HasColumnType("TEXT") + .HasColumnName("received_at"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.HasKey("DeliveryId"); + + b.ToTable("github_lifecycle_deliveries", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", b => + { + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.Property("FullNameDisplay") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("full_name_display"); + + b.Property("GrantedAt") + .HasColumnType("TEXT") + .HasColumnName("granted_at"); + + b.Property("PermissionDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("permission_digest"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("RevokedAt") + .HasColumnType("TEXT") + .HasColumnName("revoked_at"); + + b.HasKey("InstallationId", "RepositoryId"); + + b.HasIndex("ProjectId"); + + b.HasIndex("InstallationId", "RepositoryId") + .IsUnique(); + + b.ToTable("github_repository_grants", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositorySelectionCodeRecord", b => + { + b.Property("CodeHash") + .HasColumnType("TEXT") + .HasColumnName("code_hash"); + + b.Property("ConsumedAtUnixMilliseconds") + .HasColumnType("INTEGER") + .HasColumnName("consumed_at_unix_ms"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasColumnName("created_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAtUnixMilliseconds") + .HasColumnType("INTEGER") + .HasColumnName("expires_at_unix_ms"); + + b.Property("RepoAppAuthorizationId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("repo_app_authorization_id"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.HasKey("CodeHash"); + + b.HasIndex("ExpiresAtUnixMilliseconds"); + + b.HasIndex("EntraObjectId", "ExpiresAtUnixMilliseconds"); + + b.ToTable("github_repository_selection_codes", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.IntegrationBuildLockRecord", b => + { + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("AcquiredAt") + .HasColumnType("TEXT"); + + b.Property("OwnerPodId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OwnerToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ProjectId"); + + b.ToTable("IntegrationBuildLocks"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.OutcomeSpec", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowTaskPromotion") + .HasColumnType("INTEGER"); + + b.Property("Assumptions") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ClarifyingQuestions") + .HasColumnType("TEXT"); + + b.Property("ConfirmedBy") + .HasColumnType("TEXT"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DesiredOutcome") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Goal") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "CoordinatorRunId"); + + b.ToTable("OutcomeSpecs"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.PlatformDefaultCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("TEXT") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("TEXT") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.ToTable("platform_default_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("TEXT") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("TEXT") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId") + .IsUnique() + .HasDatabaseName("UX_project_copilot_bindings_active_project") + .HasFilter("status = 0"); + + b.ToTable("project_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectModelProviderCapabilityRecord", b => + { + b.Property("CapabilityRef") + .HasColumnType("TEXT") + .HasColumnName("capability_ref"); + + b.Property("ClaimLeaseExpiresAt") + .HasColumnType("TEXT") + .HasColumnName("claim_lease_expires_at"); + + b.Property("ConsumedAt") + .HasColumnType("TEXT") + .HasColumnName("consumed_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT") + .HasColumnName("expires_at"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("IssuedAt") + .HasColumnType("TEXT") + .HasColumnName("issued_at"); + + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("Purpose") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0) + .HasColumnName("purpose"); + + b.Property("SourceBindingId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("source_binding_id"); + + b.HasKey("CapabilityRef"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_marketplace_copilot_capabilities_expiry_cleanup"); + + b.HasIndex("ProjectId", "EntraObjectId", "ExpiresAt") + .HasDatabaseName("IX_marketplace_copilot_capabilities_expiry"); + + b.ToTable("marketplace_copilot_capabilities", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectRecord", b => + { + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("ActiveReviewPolicyName") + .HasColumnType("TEXT"); + + b.Property("AllowedWorkflowIds") + .HasColumnType("TEXT"); + + b.Property("BlueprintGenerationModel") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DefaultBranch") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DefaultModelCopilot") + .HasColumnType("TEXT"); + + b.Property("DefaultModelFoundry") + .HasColumnType("TEXT"); + + b.Property("DefaultProvider") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DefaultWorkflowId") + .HasColumnType("TEXT"); + + b.Property("MaxReadyPerHeartbeat") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OriginKind") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OutcomeSpecGenerationModel") + .HasColumnType("TEXT"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PickupAutoApproveTools") + .HasColumnType("INTEGER"); + + b.Property("PickupAutopilot") + .HasColumnType("INTEGER"); + + b.Property("PreviewApprovalTimeoutMinutes") + .HasColumnType("INTEGER"); + + b.Property("SandboxProfile") + .HasColumnType("TEXT"); + + b.Property("SourceBlueprintId") + .HasColumnType("TEXT"); + + b.Property("SourceBlueprintType") + .HasColumnType("TEXT"); + + b.Property("SourceRepository") + .HasColumnType("TEXT"); + + b.Property("State") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TeamRevision") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WebhookSecret") + .HasColumnType("TEXT"); + + b.Property("WorkflowGenerationModel") + .HasColumnType("TEXT"); + + b.Property("WorkingDirectory") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("ProjectId"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunAuthorshipCapability", b => + { + b.Property("RunId") + .HasMaxLength(128) + .HasColumnType("TEXT") + .HasColumnName("run_id"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT") + .HasColumnName("expires_at"); + + b.Property("TokenHash") + .IsRequired() + .HasColumnType("BLOB") + .HasColumnName("token_hash"); + + b.HasKey("RunId"); + + b.HasIndex("ExpiresAt"); + + b.ToTable("run_authorship_capabilities", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubCapabilitySnapshotRecord", b => + { + b.Property("SnapshotRef") + .HasColumnType("TEXT") + .HasColumnName("snapshot_ref"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("CapturedAt") + .HasColumnType("TEXT") + .HasColumnName("captured_at"); + + b.Property("CredentialReference") + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("ProjectId") + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("INTEGER") + .HasColumnName("purpose"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("run_id"); + + b.Property("SnapshotExpiresAt") + .HasColumnType("TEXT") + .HasColumnName("snapshot_expires_at"); + + b.Property("SourceAuthorizationId") + .HasColumnType("TEXT") + .HasColumnName("source_authorization_id"); + + b.Property("SourceBindingId") + .HasColumnType("TEXT") + .HasColumnName("source_binding_id"); + + b.Property("SourceKind") + .HasColumnType("INTEGER") + .HasColumnName("source_kind"); + + b.HasKey("SnapshotRef"); + + b.HasIndex("ProjectId"); + + b.HasIndex("RunId", "Purpose") + .IsUnique() + .HasDatabaseName("UX_run_github_capability_snapshots_run_purpose"); + + b.ToTable("run_github_capability_snapshots", null, t => + { + t.HasCheckConstraint("CK_run_github_capability_snapshots_purpose_mapping", "(purpose = 0 AND app_kind = 0 AND source_kind = 0 AND entra_object_id IS NOT NULL AND source_authorization_id IS NOT NULL AND source_binding_id IS NULL AND installation_id IS NULL AND repository_id IS NOT NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)\nOR (purpose = 1 AND app_kind = 0 AND source_kind = 0 AND entra_object_id IS NOT NULL AND source_authorization_id IS NOT NULL AND source_binding_id IS NULL AND installation_id IS NULL AND repository_id IS NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)\nOR (purpose = 2 AND app_kind = 0 AND source_kind = 1 AND entra_object_id IS NULL AND source_authorization_id IS NULL AND source_binding_id IS NULL AND installation_id IS NOT NULL AND repository_id IS NOT NULL AND credential_reference IS NULL AND credential_version IS NULL)\nOR (purpose = 3 AND app_kind = 1 AND source_kind = 2 AND entra_object_id IS NULL AND source_authorization_id IS NULL AND source_binding_id IS NOT NULL AND installation_id IS NULL AND repository_id IS NULL AND credential_reference IS NOT NULL AND credential_version IS NOT NULL)"); + }); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubIdentitySnapshotRecord", b => + { + b.Property("RunId") + .HasColumnType("TEXT") + .HasColumnName("run_id"); + + b.Property("AppKind") + .HasColumnType("INTEGER") + .HasColumnName("app_kind"); + + b.Property("CapturedAt") + .HasColumnType("TEXT") + .HasColumnName("captured_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("EntraObjectId") + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("InstallationId") + .HasColumnType("INTEGER") + .HasColumnName("installation_id"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("project_id"); + + b.Property("Purpose") + .HasColumnType("INTEGER") + .HasColumnName("purpose"); + + b.Property("RepositoryId") + .HasColumnType("INTEGER") + .HasColumnName("repository_id"); + + b.HasKey("RunId"); + + b.HasIndex("ProjectId"); + + b.ToTable("run_github_identity_snapshots", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SessionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActiveIssues") + .HasColumnType("TEXT"); + + b.Property("EndedAt") + .HasColumnType("TEXT"); + + b.Property("FocusArea") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SerializedState") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProjectId", "EndedAt"); + + b.HasIndex("ProjectId", "SessionId") + .IsUnique(); + + b.ToTable("SessionContexts"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SteeringDirective", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActionAttempt") + .HasColumnType("INTEGER"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DecidedAction") + .HasColumnType("TEXT"); + + b.Property("ExecStartedAt") + .HasColumnType("TEXT"); + + b.Property("ExecutionAttempts") + .HasColumnType("INTEGER"); + + b.Property("Instruction") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Kind") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RelayedAt") + .HasColumnType("TEXT"); + + b.Property("Severity") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TargetChildRunId") + .HasColumnType("TEXT"); + + b.Property("TargetScopeJson") + .HasColumnType("TEXT"); + + b.Property("TreeHash") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId", "Status"); + + b.ToTable("SteeringDirectives"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SteeringRevisionExecution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActionAttempt") + .HasColumnType("INTEGER"); + + b.Property("CheckpointWatermark") + .HasColumnType("INTEGER"); + + b.Property("ConfirmedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EffectState") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SteeringDirectiveId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SteeringDirectiveId", "ActionAttempt", "RunId") + .IsUnique(); + + b.ToTable("SteeringRevisionExecutions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Subtask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgentCharter") + .HasColumnType("TEXT"); + + b.Property("AssignedAgent") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ChildRunId") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeclaredOutputPathsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("InfrastructureRetryCount") + .HasColumnType("INTEGER"); + + b.Property("InfrastructureRetryEligibleAt") + .HasColumnType("TEXT"); + + b.Property("IsolationStrategy") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastResetAttempt") + .HasColumnType("INTEGER"); + + b.Property("LastResetDirectiveId") + .HasColumnType("INTEGER"); + + b.Property("LockedOutAgents") + .HasColumnType("TEXT"); + + b.Property("Phase") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PriorChildRunId") + .HasColumnType("TEXT"); + + b.Property("RecoveryAttempts") + .HasColumnType("INTEGER"); + + b.Property("RecoveryGuidance") + .HasColumnType("TEXT"); + + b.Property("Scope") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SelectedModelId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SteeringRetentionUntil") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WorkPlanId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("WorkPlanId"); + + b.ToTable("Subtasks"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SubtaskDependency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DependsOnSubtaskId") + .HasColumnType("INTEGER"); + + b.Property("SubtaskId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DependsOnSubtaskId"); + + b.HasIndex("SubtaskId"); + + b.ToTable("SubtaskDependencies"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.UserCopilotBindingRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasColumnName("id"); + + b.Property("BoundAt") + .HasColumnType("TEXT") + .HasColumnName("bound_at"); + + b.Property("CredentialReference") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_reference"); + + b.Property("CredentialVersion") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("credential_version"); + + b.Property("DeactivatedAt") + .HasColumnType("TEXT") + .HasColumnName("deactivated_at"); + + b.Property("EntraObjectId") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("GrantDigest") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("grant_digest"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasColumnName("status"); + + b.HasKey("Id"); + + b.HasIndex("EntraObjectId") + .IsUnique() + .HasDatabaseName("UX_user_copilot_bindings_active_user") + .HasFilter("status = 0"); + + b.ToTable("user_copilot_bindings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.UserModelProviderSettingsRecord", b => + { + b.Property("EntraObjectId") + .HasColumnType("TEXT") + .HasColumnName("entra_object_id"); + + b.Property("ByokAzureApiVersion") + .HasColumnType("TEXT") + .HasColumnName("byok_azure_api_version"); + + b.Property("ByokBaseUrl") + .HasColumnType("TEXT") + .HasColumnName("byok_base_url"); + + b.Property("ByokCredentialReference") + .HasColumnType("TEXT") + .HasColumnName("byok_credential_reference"); + + b.Property("ByokHeadersJson") + .HasColumnType("TEXT") + .HasColumnName("byok_headers_json"); + + b.Property("ByokModel") + .HasColumnType("TEXT") + .HasColumnName("byok_model"); + + b.Property("ByokName") + .HasColumnType("TEXT") + .HasColumnName("byok_name"); + + b.Property("ByokProviderId") + .HasColumnType("TEXT") + .HasColumnName("byok_provider_id"); + + b.Property("ByokType") + .HasColumnType("TEXT") + .HasColumnName("byok_type"); + + b.Property("ByokWireApi") + .HasColumnType("TEXT") + .HasColumnName("byok_wire_api"); + + b.Property("Preference") + .HasColumnType("INTEGER") + .HasColumnName("preference"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasColumnName("updated_at"); + + b.HasKey("EntraObjectId"); + + b.ToTable("user_model_provider_settings", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssemblyStage") + .HasColumnType("TEXT"); + + b.Property("AssemblyStartedAt") + .HasColumnType("TEXT"); + + b.Property("AssemblyStatusReason") + .HasColumnType("TEXT"); + + b.Property("AssemblyTerminalStage") + .HasColumnType("TEXT"); + + b.Property("CoordinatorPodId") + .HasColumnType("TEXT"); + + b.Property("CoordinatorRunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("HumanReviewRoundTrips") + .HasColumnType("INTEGER"); + + b.Property("IntegrationBranch") + .HasColumnType("TEXT"); + + b.Property("IsolationSummary") + .HasColumnType("TEXT"); + + b.Property("OutcomeSpecId") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SteeringIterations") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WorkflowId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CoordinatorRunId"); + + b.HasIndex("OutcomeSpecId"); + + b.ToTable("WorkPlans"); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.CoordinatorDeferredDecisionRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DecisionJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("DeferredDecisions"); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.PendingRequestRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("OwnerUser") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RequestJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("RunId") + .IsUnique(); + + b.ToTable("PendingRequests"); + }); + + modelBuilder.Entity("Agentweaver.Api.Runs.RunEventRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EventType") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PayloadJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RunId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Sequence") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RunId"); + + b.HasIndex("RunId", "Sequence") + .IsUnique(); + + b.ToTable("RunEvents"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ClientSecret") + .HasColumnType("TEXT"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("JsonWebKeySet") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("TEXT"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedirectUris") + .HasColumnType("TEXT"); + + b.Property("Requirements") + .HasColumnType("TEXT"); + + b.Property("Settings") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Scopes") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Descriptions") + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .HasColumnType("TEXT"); + + b.Property("DisplayNames") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("Resources") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", (string)null); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApplicationId") + .HasColumnType("TEXT"); + + b.Property("AuthorizationId") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("CreationDate") + .HasColumnType("TEXT"); + + b.Property("ExpirationDate") + .HasColumnType("TEXT"); + + b.Property("Payload") + .HasColumnType("TEXT"); + + b.Property("Properties") + .HasColumnType("TEXT"); + + b.Property("RedemptionDate") + .HasColumnType("TEXT"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasMaxLength(150) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", (string)null); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationActivationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_activations_projects_project_id"); + + b.HasOne("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", null) + .WithMany() + .HasForeignKey("InstallationId", "RepositoryId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_automation_activations_repository_grants_installation_id_repository_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.AutomationInvocationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.AutomationActivationRecord", null) + .WithMany() + .HasForeignKey("ActivationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_invocations_activations_activation_id"); + + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_automation_invocations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Decision", b => + { + b.HasOne("Agentweaver.Api.Memory.Decision", null) + .WithMany() + .HasForeignKey("SupersededById"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.DecisionInboxEntry", b => + { + b.HasOne("Agentweaver.Api.Memory.Decision", null) + .WithMany() + .HasForeignKey("DecisionId"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubAuthorizationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_github_authorizations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubInstallationRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_github_installations_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.GitHubRepositoryGrantRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.GitHubInstallationRecord", null) + .WithMany() + .HasForeignKey("InstallationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_github_repository_grants_installations_installation_id"); + + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_github_repository_grants_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectCopilotBindingRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_project_copilot_bindings_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.ProjectModelProviderCapabilityRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_marketplace_copilot_capabilities_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubCapabilitySnapshotRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("FK_run_github_capability_snapshots_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.RunGitHubIdentitySnapshotRecord", b => + { + b.HasOne("Agentweaver.Api.Memory.ProjectRecord", null) + .WithMany() + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_run_github_identity_snapshots_projects_project_id"); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.Subtask", b => + { + b.HasOne("Agentweaver.Api.Memory.WorkPlan", null) + .WithMany() + .HasForeignKey("WorkPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.SubtaskDependency", b => + { + b.HasOne("Agentweaver.Api.Memory.Subtask", null) + .WithMany() + .HasForeignKey("DependsOnSubtaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Agentweaver.Api.Memory.Subtask", null) + .WithMany() + .HasForeignKey("SubtaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Agentweaver.Api.Memory.WorkPlan", b => + { + b.HasOne("Agentweaver.Api.Memory.OutcomeSpec", null) + .WithMany() + .HasForeignKey("OutcomeSpecId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.cs b/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.cs new file mode 100644 index 000000000..3728368da --- /dev/null +++ b/apps/Agentweaver.Api/Migrations/20260904224040_AddOAuthConsentContinuationDecision.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Agentweaver.Api.Migrations +{ + /// + public partial class AddOAuthConsentContinuationDecision : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ContinuationDecision", + table: "OAuthAuthorizationTransactions", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ContinuationDecision", + table: "OAuthAuthorizationTransactions"); + } + } +} diff --git a/apps/Agentweaver.Api/Migrations/MemoryDbContextModelSnapshot.cs b/apps/Agentweaver.Api/Migrations/MemoryDbContextModelSnapshot.cs index 03aa5a1b6..31f639248 100644 --- a/apps/Agentweaver.Api/Migrations/MemoryDbContextModelSnapshot.cs +++ b/apps/Agentweaver.Api/Migrations/MemoryDbContextModelSnapshot.cs @@ -87,6 +87,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ConsumedAt") .HasColumnType("TEXT"); + b.Property("ContinuationDecision") + .HasColumnType("TEXT"); + b.Property("ExpiresAt") .HasColumnType("TEXT"); diff --git a/docs/mcp-oauth.md b/docs/mcp-oauth.md index d19dfdde3..3a6e7d1ce 100644 --- a/docs/mcp-oauth.md +++ b/docs/mcp-oauth.md @@ -22,6 +22,15 @@ PKCE S256 and explicit consent are required. The stable least-privilege scope is `mcp:invoke`; requesting additional approved scopes re-opens consent. Password, implicit, client-credentials, and device grants are unavailable. +The consent page keeps a strict Content Security Policy. Its form may redirect only to +the callback registered for that OAuth client: the exact loopback or HTTPS authority, or +the validated private-use scheme for a native client. If the Agentweaver browser session +expires while the consent page is open, the page shows a same-origin sign-in action +instead of forwarding the form submission through the upstream identity-provider chain. +Chromium does not accept literal IPv6 hosts in `form-action`; for a validated IPv6 +callback, Agentweaver therefore finishes consent through a short-lived, single-use +same-origin continuation that contains no callback, client state, code, or token. + Access tokens are signed JWTs with a ten-minute lifetime. Authorization codes and refresh tokens are opaque references persisted by OpenIddict. Code replay is rejected. Refresh-token replay atomically revokes all tokens in its authorization diff --git a/package-lock.json b/package-lock.json index 4803f8843..d85045671 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "agentweaver", - "version": "0.28.4", + "version": "0.28.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "agentweaver", - "version": "0.28.4", + "version": "0.28.5", "devDependencies": { "@changesets/cli": "^3.0.0", "@types/node": "^26.2.0", diff --git a/package.json b/package.json index ce0c9f8aa..71fe033fb 100644 --- a/package.json +++ b/package.json @@ -42,5 +42,5 @@ "@types/node": "^26.2.0", "playwright": "^1.62.1" }, - "version": "0.28.4" + "version": "0.28.5" } diff --git a/tests/Agentweaver.Tests/Auth/EndpointAuthorization.golden.txt b/tests/Agentweaver.Tests/Auth/EndpointAuthorization.golden.txt index 361faef2c..4b8a841d4 100644 --- a/tests/Agentweaver.Tests/Auth/EndpointAuthorization.golden.txt +++ b/tests/Agentweaver.Tests/Auth/EndpointAuthorization.golden.txt @@ -115,6 +115,7 @@ GET /auth/github/repo-app/installation/callback | ProtocolManaged GET /health | OperationalAnonymous GET /healthz/workspace | OperationalAnonymous GET /oauth/authorize | ProtocolManaged +GET /oauth/continue | ProtocolManaged GET /oauth/resume | ProtocolManaged GET /openapi/{documentName}.json | OperationalAnonymous GET /openapi/{documentName}.yaml | OperationalAnonymous diff --git a/tests/Agentweaver.Tests/Auth/OpenIddictAuthorizationServerTests.cs b/tests/Agentweaver.Tests/Auth/OpenIddictAuthorizationServerTests.cs index c51ea216a..b60c8844d 100644 --- a/tests/Agentweaver.Tests/Auth/OpenIddictAuthorizationServerTests.cs +++ b/tests/Agentweaver.Tests/Auth/OpenIddictAuthorizationServerTests.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using Agentweaver.Api.Auth; using Agentweaver.Api.Auth.OAuth; +using Agentweaver.Api.Endpoints; using Agentweaver.Api.Memory; using Agentweaver.Domain; using Agentweaver.Tests.Helpers; @@ -366,7 +367,228 @@ public async Task Authorization_ConsentPageShowsClientPermissionsAndSignedInIden var styleNonce = Regex.Match(html, "