@kernel-sig/console 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.env.example +50 -0
  2. package/LICENSE +127 -0
  3. package/Makefile +66 -0
  4. package/README.md +279 -0
  5. package/backend/.dockerignore +12 -0
  6. package/backend/Dockerfile +43 -0
  7. package/backend/alembic/env.py +75 -0
  8. package/backend/alembic/script.py.mako +25 -0
  9. package/backend/alembic/versions/0001_initial.py +88 -0
  10. package/backend/alembic/versions/350e2d9b6553_add_pr_comment_table.py +48 -0
  11. package/backend/alembic/versions/4f8342e727fe_sig_info_roster_fields_and_branch_.py +39 -0
  12. package/backend/alembic/versions/5a31ade90136_add_repository_credential_pull_request_.py +293 -0
  13. package/backend/alembic/versions/87a008142f17_add_classification_attention_and_ai_.py +436 -0
  14. package/backend/alembic/versions/a1c7d90e4b52_branch_belongs_to_a_repository.py +72 -0
  15. package/backend/alembic/versions/b41c9d7e5f28_extend_pr_kind_categories.py +43 -0
  16. package/backend/alembic/versions/c8e4f2a71b93_add_release_kind.py +40 -0
  17. package/backend/alembic/versions/d1275091dfdc_add_needs_detail_flag_to_pull_request.py +44 -0
  18. package/backend/alembic/versions/e5b27c9d3a41_downgrade_cve_without_ids.py +54 -0
  19. package/backend/alembic/versions/ee3159a4eff0_add_sig_meeting_member_and_release_.py +164 -0
  20. package/backend/alembic/versions/fcf3c186d63b_attention_rule_subscriptions.py +42 -0
  21. package/backend/alembic.ini +40 -0
  22. package/backend/app/__init__.py +0 -0
  23. package/backend/app/api/__init__.py +0 -0
  24. package/backend/app/api/deps.py +74 -0
  25. package/backend/app/api/v1/__init__.py +0 -0
  26. package/backend/app/api/v1/ai.py +386 -0
  27. package/backend/app/api/v1/analytics.py +48 -0
  28. package/backend/app/api/v1/attention.py +142 -0
  29. package/backend/app/api/v1/auth.py +106 -0
  30. package/backend/app/api/v1/classification.py +192 -0
  31. package/backend/app/api/v1/health.py +29 -0
  32. package/backend/app/api/v1/issues.py +143 -0
  33. package/backend/app/api/v1/pulls.py +388 -0
  34. package/backend/app/api/v1/repositories.py +273 -0
  35. package/backend/app/api/v1/router.py +32 -0
  36. package/backend/app/api/v1/sig.py +565 -0
  37. package/backend/app/api/v1/users.py +87 -0
  38. package/backend/app/api/v1/webhooks.py +135 -0
  39. package/backend/app/core/__init__.py +0 -0
  40. package/backend/app/core/config.py +72 -0
  41. package/backend/app/core/crypto.py +74 -0
  42. package/backend/app/core/db.py +79 -0
  43. package/backend/app/core/exceptions.py +60 -0
  44. package/backend/app/core/logging.py +69 -0
  45. package/backend/app/core/permissions.py +87 -0
  46. package/backend/app/core/queue.py +57 -0
  47. package/backend/app/core/security.py +69 -0
  48. package/backend/app/domain/__init__.py +0 -0
  49. package/backend/app/domain/attention.py +618 -0
  50. package/backend/app/domain/classification.py +689 -0
  51. package/backend/app/domain/meeting.py +408 -0
  52. package/backend/app/domain/release.py +163 -0
  53. package/backend/app/domain/review.py +416 -0
  54. package/backend/app/domain/sig.py +178 -0
  55. package/backend/app/domain/sig_info.py +201 -0
  56. package/backend/app/integrations/__init__.py +0 -0
  57. package/backend/app/integrations/atomgit/__init__.py +0 -0
  58. package/backend/app/integrations/atomgit/client.py +505 -0
  59. package/backend/app/integrations/atomgit/models.py +311 -0
  60. package/backend/app/integrations/llm/__init__.py +0 -0
  61. package/backend/app/integrations/llm/prompts.py +222 -0
  62. package/backend/app/integrations/llm/provider.py +326 -0
  63. package/backend/app/main.py +242 -0
  64. package/backend/app/middleware/__init__.py +0 -0
  65. package/backend/app/middleware/audit.py +129 -0
  66. package/backend/app/middleware/request_context.py +42 -0
  67. package/backend/app/models/__init__.py +112 -0
  68. package/backend/app/models/ai.py +214 -0
  69. package/backend/app/models/attention.py +131 -0
  70. package/backend/app/models/attention_settings.py +61 -0
  71. package/backend/app/models/audit.py +53 -0
  72. package/backend/app/models/base.py +35 -0
  73. package/backend/app/models/classification.py +144 -0
  74. package/backend/app/models/credential.py +56 -0
  75. package/backend/app/models/issue.py +132 -0
  76. package/backend/app/models/meeting.py +189 -0
  77. package/backend/app/models/pull_request.py +288 -0
  78. package/backend/app/models/repository.py +196 -0
  79. package/backend/app/models/sig.py +194 -0
  80. package/backend/app/models/user.py +44 -0
  81. package/backend/app/schemas/__init__.py +0 -0
  82. package/backend/app/schemas/ai.py +120 -0
  83. package/backend/app/schemas/attention.py +43 -0
  84. package/backend/app/schemas/auth.py +26 -0
  85. package/backend/app/schemas/classification.py +58 -0
  86. package/backend/app/schemas/common.py +43 -0
  87. package/backend/app/schemas/pull_request.py +214 -0
  88. package/backend/app/schemas/repository.py +94 -0
  89. package/backend/app/schemas/sig.py +246 -0
  90. package/backend/app/schemas/user.py +79 -0
  91. package/backend/app/services/__init__.py +0 -0
  92. package/backend/app/services/ai_service.py +569 -0
  93. package/backend/app/services/analytics_service.py +390 -0
  94. package/backend/app/services/attention_queue.py +451 -0
  95. package/backend/app/services/attention_service.py +432 -0
  96. package/backend/app/services/auth_service.py +74 -0
  97. package/backend/app/services/classification_service.py +658 -0
  98. package/backend/app/services/credential_service.py +105 -0
  99. package/backend/app/services/pull_query.py +273 -0
  100. package/backend/app/services/release_service.py +446 -0
  101. package/backend/app/services/repository_service.py +132 -0
  102. package/backend/app/services/sig_service.py +385 -0
  103. package/backend/app/services/sync_service.py +752 -0
  104. package/backend/app/services/user_service.py +68 -0
  105. package/backend/app/worker.py +389 -0
  106. package/backend/entrypoint.sh +10 -0
  107. package/backend/pyproject.toml +68 -0
  108. package/backend/tests/test_analysis_api.py +154 -0
  109. package/backend/tests/test_atomgit_client.py +360 -0
  110. package/backend/tests/test_atomgit_models.py +257 -0
  111. package/backend/tests/test_attention.py +291 -0
  112. package/backend/tests/test_audit_middleware.py +135 -0
  113. package/backend/tests/test_classification.py +498 -0
  114. package/backend/tests/test_config.py +41 -0
  115. package/backend/tests/test_crypto.py +68 -0
  116. package/backend/tests/test_exceptions.py +62 -0
  117. package/backend/tests/test_health.py +63 -0
  118. package/backend/tests/test_llm_provider.py +320 -0
  119. package/backend/tests/test_meeting_domain.py +169 -0
  120. package/backend/tests/test_permissions.py +69 -0
  121. package/backend/tests/test_pull_query_wiring.py +66 -0
  122. package/backend/tests/test_pull_schemas.py +82 -0
  123. package/backend/tests/test_release_domain.py +82 -0
  124. package/backend/tests/test_review_parser.py +301 -0
  125. package/backend/tests/test_schemas_user.py +97 -0
  126. package/backend/tests/test_security.py +92 -0
  127. package/cli/index.js +338 -0
  128. package/compose.yaml +105 -0
  129. package/frontend/.dockerignore +4 -0
  130. package/frontend/Dockerfile +27 -0
  131. package/frontend/index.html +14 -0
  132. package/frontend/nginx.conf +47 -0
  133. package/frontend/package-lock.json +5020 -0
  134. package/frontend/package.json +35 -0
  135. package/frontend/src/api/ai.ts +124 -0
  136. package/frontend/src/api/analytics.ts +66 -0
  137. package/frontend/src/api/attention.ts +181 -0
  138. package/frontend/src/api/auth.ts +74 -0
  139. package/frontend/src/api/classification.ts +170 -0
  140. package/frontend/src/api/issues.ts +90 -0
  141. package/frontend/src/api/pulls.ts +233 -0
  142. package/frontend/src/api/repositories.ts +95 -0
  143. package/frontend/src/api/sig.ts +232 -0
  144. package/frontend/src/app/antd-theme.ts +94 -0
  145. package/frontend/src/app/providers.tsx +59 -0
  146. package/frontend/src/app/router.tsx +411 -0
  147. package/frontend/src/app/search.ts +30 -0
  148. package/frontend/src/components/ClassificationBadge.tsx +58 -0
  149. package/frontend/src/components/GateBadge.tsx +13 -0
  150. package/frontend/src/components/SeverityBadge.tsx +20 -0
  151. package/frontend/src/components/layout/AppShell.tsx +16 -0
  152. package/frontend/src/components/layout/AuthLayout.tsx +32 -0
  153. package/frontend/src/components/layout/Sidebar.tsx +223 -0
  154. package/frontend/src/components/layout/TopBar.tsx +47 -0
  155. package/frontend/src/components/pulls/DiscussionTimeline.tsx +145 -0
  156. package/frontend/src/components/pulls/FacetRail.tsx +199 -0
  157. package/frontend/src/components/pulls/LabelChips.tsx +87 -0
  158. package/frontend/src/components/ui/alert.tsx +30 -0
  159. package/frontend/src/components/ui/badge.tsx +53 -0
  160. package/frontend/src/components/ui/button.tsx +51 -0
  161. package/frontend/src/components/ui/card.tsx +64 -0
  162. package/frontend/src/components/ui/chart-theme.ts +65 -0
  163. package/frontend/src/components/ui/data-table.tsx +39 -0
  164. package/frontend/src/components/ui/echart.tsx +70 -0
  165. package/frontend/src/components/ui/empty-state.tsx +22 -0
  166. package/frontend/src/components/ui/input.tsx +39 -0
  167. package/frontend/src/components/ui/lazy-chart.tsx +21 -0
  168. package/frontend/src/components/ui/skeleton.tsx +19 -0
  169. package/frontend/src/hooks/use-current-repository.ts +44 -0
  170. package/frontend/src/hooks/use-current-user.ts +38 -0
  171. package/frontend/src/lib/api-client.ts +93 -0
  172. package/frontend/src/lib/css-color.ts +60 -0
  173. package/frontend/src/lib/utils.ts +45 -0
  174. package/frontend/src/main.tsx +22 -0
  175. package/frontend/src/pages/attention/AttentionQueuePage.tsx +316 -0
  176. package/frontend/src/pages/attention/RuleSettingsPanel.tsx +177 -0
  177. package/frontend/src/pages/branches/BranchDetailPage.tsx +632 -0
  178. package/frontend/src/pages/branches/BranchListPage.tsx +308 -0
  179. package/frontend/src/pages/dashboard/DashboardPage.tsx +657 -0
  180. package/frontend/src/pages/issues/IssueListPage.tsx +284 -0
  181. package/frontend/src/pages/login/LoginPage.tsx +95 -0
  182. package/frontend/src/pages/meetings/MeetingDetailPage.tsx +403 -0
  183. package/frontend/src/pages/meetings/MeetingListPage.tsx +264 -0
  184. package/frontend/src/pages/members/MembersPage.tsx +534 -0
  185. package/frontend/src/pages/pulls/PullDetailPage.tsx +833 -0
  186. package/frontend/src/pages/pulls/PullListPage.tsx +399 -0
  187. package/frontend/src/pages/settings/AiSettingsPage.tsx +449 -0
  188. package/frontend/src/pages/settings/SettingsPage.tsx +321 -0
  189. package/frontend/src/pages/setup/SetupPage.tsx +144 -0
  190. package/frontend/src/styles/globals.css +147 -0
  191. package/frontend/tsconfig.json +22 -0
  192. package/frontend/vite.config.ts +57 -0
  193. package/package.json +48 -0
  194. package/scripts/e2e-auth-flow.py +131 -0
  195. package/scripts/e2e-pr-detail.py +128 -0
  196. package/scripts/e2e-verify.py +773 -0
  197. package/scripts/verify-ai-pipeline.py +616 -0
  198. package/scripts/verify-analysis-pipeline.py +303 -0
@@ -0,0 +1,88 @@
1
+ """初始化用户与审计日志表
2
+
3
+ Revision ID: 0001_initial
4
+ Revises:
5
+ Create Date: 2026-09-16
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+ from sqlalchemy.dialects import postgresql
13
+
14
+ revision: str = "0001_initial"
15
+ down_revision: str | None = None
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+ ROLE_VALUES = ("VIEWER", "REVIEWER", "COMMITTER", "MAINTAINER", "ADMIN")
20
+
21
+
22
+ def upgrade() -> None:
23
+ user_role = postgresql.ENUM(*ROLE_VALUES, name="user_role", create_type=False)
24
+ user_role.create(op.get_bind(), checkfirst=True)
25
+
26
+ audit_result = postgresql.ENUM("success", "failure", name="audit_result", create_type=False)
27
+ audit_result.create(op.get_bind(), checkfirst=True)
28
+
29
+ op.create_table(
30
+ "ksp_user",
31
+ sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
32
+ sa.Column("username", sa.String(64), nullable=False),
33
+ sa.Column("display_name", sa.String(128), nullable=False),
34
+ sa.Column("email", sa.String(255), nullable=False),
35
+ sa.Column("password_hash", sa.String(255), nullable=False),
36
+ sa.Column("role", user_role, nullable=False),
37
+ sa.Column("atomgit_login", sa.String(64), nullable=True),
38
+ sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.true()),
39
+ sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True),
40
+ sa.Column(
41
+ "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()
42
+ ),
43
+ sa.Column(
44
+ "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()
45
+ ),
46
+ )
47
+ op.create_unique_constraint("uq_ksp_user_email", "ksp_user", ["email"])
48
+ # 用户名用唯一索引即可,无需再叠加一份唯一约束
49
+ op.create_index("ix_ksp_user_username", "ksp_user", ["username"], unique=True)
50
+ op.create_index("ix_ksp_user_role", "ksp_user", ["role"])
51
+ op.create_index("ix_ksp_user_atomgit_login", "ksp_user", ["atomgit_login"])
52
+
53
+ op.create_table(
54
+ "ksp_audit_log",
55
+ sa.Column("id", sa.BigInteger(), autoincrement=True, primary_key=True),
56
+ sa.Column("actor_user_id", postgresql.UUID(as_uuid=True), nullable=True),
57
+ sa.Column("actor_username", sa.String(64), nullable=True),
58
+ sa.Column("actor_ip", postgresql.INET(), nullable=True),
59
+ sa.Column("action", sa.String(64), nullable=False),
60
+ sa.Column("target_type", sa.String(64), nullable=True),
61
+ sa.Column("target_id", sa.String(128), nullable=True),
62
+ sa.Column("before", postgresql.JSONB(), nullable=True),
63
+ sa.Column("after", postgresql.JSONB(), nullable=True),
64
+ sa.Column("result", audit_result, nullable=False),
65
+ sa.Column("error", sa.Text(), nullable=True),
66
+ sa.Column("request_id", sa.String(64), nullable=True),
67
+ sa.Column(
68
+ "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()
69
+ ),
70
+ sa.ForeignKeyConstraint(
71
+ ["actor_user_id"],
72
+ ["ksp_user.id"],
73
+ name="fk_ksp_audit_log_actor_user_id_ksp_user",
74
+ ondelete="SET NULL",
75
+ ),
76
+ )
77
+ op.create_index("ix_ksp_audit_log_action", "ksp_audit_log", ["action"])
78
+ op.create_index("ix_ksp_audit_log_target_type", "ksp_audit_log", ["target_type"])
79
+ op.create_index("ix_ksp_audit_log_request_id", "ksp_audit_log", ["request_id"])
80
+ op.create_index("ix_ksp_audit_log_created_at", "ksp_audit_log", ["created_at"])
81
+ op.create_index("ix_ksp_audit_log_actor_user_id", "ksp_audit_log", ["actor_user_id"])
82
+
83
+
84
+ def downgrade() -> None:
85
+ op.drop_table("ksp_audit_log")
86
+ op.drop_table("ksp_user")
87
+ op.execute("DROP TYPE IF EXISTS audit_result")
88
+ op.execute("DROP TYPE IF EXISTS user_role")
@@ -0,0 +1,48 @@
1
+ """add pr comment table
2
+
3
+ Revision ID: 350e2d9b6553
4
+ Revises: 87a008142f17
5
+ Create Date: 2026-09-16 14:45:30.354006
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+
13
+
14
+ revision: str = '350e2d9b6553'
15
+ down_revision: str | None = '87a008142f17'
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table('ksp_pr_comment',
23
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
24
+ sa.Column('pull_request_id', sa.UUID(), nullable=False),
25
+ sa.Column('comment_id', sa.String(length=64), nullable=False),
26
+ sa.Column('comment_type', sa.String(length=32), nullable=True),
27
+ sa.Column('discussion_id', sa.String(length=64), nullable=True),
28
+ sa.Column('author_login', sa.String(length=128), nullable=False),
29
+ sa.Column('author_avatar_url', sa.String(length=512), nullable=True),
30
+ sa.Column('body', sa.Text(), nullable=False),
31
+ sa.Column('atomgit_created_at', sa.DateTime(timezone=True), nullable=True),
32
+ sa.Column('atomgit_updated_at', sa.DateTime(timezone=True), nullable=True),
33
+ sa.Column('last_synced_at', sa.DateTime(timezone=True), nullable=True),
34
+ sa.ForeignKeyConstraint(['pull_request_id'], ['ksp_pull_request.id'], name=op.f('fk_ksp_pr_comment_pull_request_id_ksp_pull_request'), ondelete='CASCADE'),
35
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_pr_comment')),
36
+ sa.UniqueConstraint('pull_request_id', 'comment_id', name='uq_ksp_pr_comment_identity')
37
+ )
38
+ op.create_index('ix_ksp_pr_comment_pr_created', 'ksp_pr_comment', ['pull_request_id', 'atomgit_created_at'], unique=False)
39
+ op.create_index(op.f('ix_ksp_pr_comment_pull_request_id'), 'ksp_pr_comment', ['pull_request_id'], unique=False)
40
+ # ### end Alembic commands ###
41
+
42
+
43
+ def downgrade() -> None:
44
+ # ### commands auto generated by Alembic - please adjust! ###
45
+ op.drop_index(op.f('ix_ksp_pr_comment_pull_request_id'), table_name='ksp_pr_comment')
46
+ op.drop_index('ix_ksp_pr_comment_pr_created', table_name='ksp_pr_comment')
47
+ op.drop_table('ksp_pr_comment')
48
+ # ### end Alembic commands ###
@@ -0,0 +1,39 @@
1
+ """sig info roster fields and branch keepers
2
+
3
+ Revision ID: 4f8342e727fe
4
+ Revises: ee3159a4eff0
5
+ Create Date: 2026-09-16 17:27:18.132867
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+ from sqlalchemy.dialects import postgresql
13
+
14
+ revision: str = '4f8342e727fe'
15
+ down_revision: str | None = 'ee3159a4eff0'
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.add_column('ksp_release_branch', sa.Column('keepers', postgresql.ARRAY(sa.Text()), nullable=True))
23
+ op.create_index('ix_ksp_release_branch_keepers', 'ksp_release_branch', ['keepers'], unique=False, postgresql_using='gin')
24
+ op.add_column('ksp_sig_member', sa.Column('roles', postgresql.ARRAY(sa.Text()), nullable=True))
25
+ op.add_column('ksp_sig_member', sa.Column('repositories', postgresql.ARRAY(sa.Text()), nullable=True))
26
+ op.add_column('ksp_sig_member', sa.Column('admin_repositories', postgresql.ARRAY(sa.Text()), nullable=True))
27
+ op.add_column('ksp_sig_member', sa.Column('kept_branches', postgresql.ARRAY(sa.Text()), nullable=True))
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade() -> None:
32
+ # ### commands auto generated by Alembic - please adjust! ###
33
+ op.drop_column('ksp_sig_member', 'kept_branches')
34
+ op.drop_column('ksp_sig_member', 'admin_repositories')
35
+ op.drop_column('ksp_sig_member', 'repositories')
36
+ op.drop_column('ksp_sig_member', 'roles')
37
+ op.drop_index('ix_ksp_release_branch_keepers', table_name='ksp_release_branch', postgresql_using='gin')
38
+ op.drop_column('ksp_release_branch', 'keepers')
39
+ # ### end Alembic commands ###
@@ -0,0 +1,293 @@
1
+ """add repository credential pull_request issue and sync tables
2
+
3
+ Revision ID: 5a31ade90136
4
+ Revises: 0001_initial
5
+ Create Date: 2026-09-16 12:33:43.667375
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+ from sqlalchemy.dialects import postgresql
13
+
14
+ revision: str = '5a31ade90136'
15
+ down_revision: str | None = '0001_initial'
16
+ branch_labels: str | Sequence[str] | None = None
17
+ depends_on: str | Sequence[str] | None = None
18
+
19
+
20
+ def upgrade() -> None:
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table('ksp_credential',
23
+ sa.Column('id', sa.UUID(), nullable=False),
24
+ sa.Column('kind', sa.Enum('atomgit_token', 'llm_api_key', name='credential_kind'), nullable=False),
25
+ sa.Column('name', sa.String(length=128), nullable=False),
26
+ sa.Column('encrypted_value', sa.LargeBinary(), nullable=False),
27
+ sa.Column('fingerprint', sa.String(length=32), nullable=False),
28
+ sa.Column('meta', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
29
+ sa.Column('last_verified_at', sa.DateTime(timezone=True), nullable=True),
30
+ sa.Column('last_verify_error', sa.String(length=500), nullable=True),
31
+ sa.Column('created_by', sa.UUID(), nullable=True),
32
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
33
+ sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
34
+ sa.ForeignKeyConstraint(['created_by'], ['ksp_user.id'], name=op.f('fk_ksp_credential_created_by_ksp_user'), ondelete='SET NULL'),
35
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_credential')),
36
+ sa.UniqueConstraint('name', name=op.f('uq_ksp_credential_name'))
37
+ )
38
+ op.create_index(op.f('ix_ksp_credential_kind'), 'ksp_credential', ['kind'], unique=False)
39
+ op.create_table('ksp_repository',
40
+ sa.Column('id', sa.UUID(), nullable=False),
41
+ sa.Column('owner', sa.String(length=128), nullable=False),
42
+ sa.Column('name', sa.String(length=128), nullable=False),
43
+ sa.Column('display_name', sa.String(length=255), nullable=True),
44
+ sa.Column('description', sa.Text(), nullable=True),
45
+ sa.Column('api_base_url', sa.String(length=255), nullable=False),
46
+ sa.Column('credential_id', sa.UUID(), nullable=True),
47
+ sa.Column('default_branch', sa.String(length=128), nullable=True),
48
+ sa.Column('tracked_branches', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
49
+ sa.Column('sync_enabled', sa.Boolean(), nullable=False),
50
+ sa.Column('sync_interval_seconds', sa.Integer(), nullable=False),
51
+ sa.Column('backfill_days', sa.Integer(), nullable=False),
52
+ sa.Column('webhook_secret', sa.String(length=64), nullable=True),
53
+ sa.Column('last_sync_at', sa.DateTime(timezone=True), nullable=True),
54
+ sa.Column('last_sync_error', sa.Text(), nullable=True),
55
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
56
+ sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
57
+ sa.ForeignKeyConstraint(['credential_id'], ['ksp_credential.id'], name=op.f('fk_ksp_repository_credential_id_ksp_credential'), ondelete='SET NULL'),
58
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_repository')),
59
+ sa.UniqueConstraint('owner', 'name', name='uq_ksp_repository_owner_name'),
60
+ sa.UniqueConstraint('webhook_secret', name=op.f('uq_ksp_repository_webhook_secret'))
61
+ )
62
+ op.create_table('ksp_issue',
63
+ sa.Column('id', sa.UUID(), nullable=False),
64
+ sa.Column('repository_id', sa.UUID(), nullable=False),
65
+ sa.Column('number', sa.Integer(), nullable=False),
66
+ sa.Column('title', sa.Text(), nullable=False),
67
+ sa.Column('body', sa.Text(), nullable=True),
68
+ sa.Column('state', sa.String(length=32), nullable=False),
69
+ sa.Column('issue_type', sa.String(length=64), nullable=True),
70
+ sa.Column('issue_state', sa.String(length=64), nullable=True),
71
+ sa.Column('priority', sa.Integer(), nullable=True),
72
+ sa.Column('priority_label', sa.String(length=64), nullable=True),
73
+ sa.Column('author_login', sa.String(length=128), nullable=True),
74
+ sa.Column('assignee_login', sa.String(length=128), nullable=True),
75
+ sa.Column('labels', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
76
+ sa.Column('label_names', postgresql.ARRAY(sa.Text()), nullable=True),
77
+ sa.Column('comments_count', sa.Integer(), nullable=False),
78
+ sa.Column('atomgit_created_at', sa.DateTime(timezone=True), nullable=True),
79
+ sa.Column('atomgit_updated_at', sa.DateTime(timezone=True), nullable=True),
80
+ sa.Column('finished_at', sa.DateTime(timezone=True), nullable=True),
81
+ sa.Column('html_url', sa.String(length=512), nullable=True),
82
+ sa.Column('first_seen_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
83
+ sa.Column('last_synced_at', sa.DateTime(timezone=True), nullable=True),
84
+ sa.Column('content_hash', sa.String(length=64), nullable=True),
85
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
86
+ sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
87
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_issue_repository_id_ksp_repository'), ondelete='CASCADE'),
88
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_issue')),
89
+ sa.UniqueConstraint('repository_id', 'number', name='uq_ksp_issue_repo_number')
90
+ )
91
+ op.create_index(op.f('ix_ksp_issue_assignee_login'), 'ksp_issue', ['assignee_login'], unique=False)
92
+ op.create_index(op.f('ix_ksp_issue_author_login'), 'ksp_issue', ['author_login'], unique=False)
93
+ op.create_index(op.f('ix_ksp_issue_issue_type'), 'ksp_issue', ['issue_type'], unique=False)
94
+ op.create_index('ix_ksp_issue_label_names', 'ksp_issue', ['label_names'], unique=False, postgresql_using='gin')
95
+ op.create_index(op.f('ix_ksp_issue_repository_id'), 'ksp_issue', ['repository_id'], unique=False)
96
+ op.create_index(op.f('ix_ksp_issue_state'), 'ksp_issue', ['state'], unique=False)
97
+ op.create_table('ksp_pull_request',
98
+ sa.Column('id', sa.UUID(), nullable=False),
99
+ sa.Column('repository_id', sa.UUID(), nullable=False),
100
+ sa.Column('number', sa.Integer(), nullable=False),
101
+ sa.Column('title', sa.Text(), nullable=False),
102
+ sa.Column('body', sa.Text(), nullable=True),
103
+ sa.Column('state', sa.Enum('open', 'closed', 'merged', name='pull_state'), nullable=False),
104
+ sa.Column('draft', sa.Boolean(), nullable=False),
105
+ sa.Column('author_login', sa.String(length=128), nullable=True),
106
+ sa.Column('author_name', sa.String(length=255), nullable=True),
107
+ sa.Column('author_avatar_url', sa.String(length=512), nullable=True),
108
+ sa.Column('source_branch', sa.String(length=255), nullable=True),
109
+ sa.Column('target_branch', sa.String(length=255), nullable=True),
110
+ sa.Column('labels', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
111
+ sa.Column('label_names', postgresql.ARRAY(sa.Text()), nullable=True),
112
+ sa.Column('added_lines', sa.Integer(), nullable=False),
113
+ sa.Column('removed_lines', sa.Integer(), nullable=False),
114
+ sa.Column('changed_files', sa.Integer(), nullable=False),
115
+ sa.Column('comments_count', sa.Integer(), nullable=False),
116
+ sa.Column('mergeable', sa.Boolean(), nullable=True),
117
+ sa.Column('merge_conflict', sa.Boolean(), nullable=False),
118
+ sa.Column('merge_check_detail', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
119
+ sa.Column('atomgit_created_at', sa.DateTime(timezone=True), nullable=True),
120
+ sa.Column('atomgit_updated_at', sa.DateTime(timezone=True), nullable=True),
121
+ sa.Column('merged_at', sa.DateTime(timezone=True), nullable=True),
122
+ sa.Column('closed_at', sa.DateTime(timezone=True), nullable=True),
123
+ sa.Column('html_url', sa.String(length=512), nullable=True),
124
+ sa.Column('first_seen_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
125
+ sa.Column('last_synced_at', sa.DateTime(timezone=True), nullable=True),
126
+ sa.Column('detail_synced_at', sa.DateTime(timezone=True), nullable=True),
127
+ sa.Column('content_hash', sa.String(length=64), nullable=True),
128
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
129
+ sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
130
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_pull_request_repository_id_ksp_repository'), ondelete='CASCADE'),
131
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_pull_request')),
132
+ sa.UniqueConstraint('repository_id', 'number', name='uq_ksp_pull_request_repo_number')
133
+ )
134
+ op.create_index(op.f('ix_ksp_pull_request_author_login'), 'ksp_pull_request', ['author_login'], unique=False)
135
+ op.create_index(op.f('ix_ksp_pull_request_content_hash'), 'ksp_pull_request', ['content_hash'], unique=False)
136
+ op.create_index('ix_ksp_pull_request_label_names', 'ksp_pull_request', ['label_names'], unique=False, postgresql_using='gin')
137
+ op.create_index('ix_ksp_pull_request_repo_state_updated', 'ksp_pull_request', ['repository_id', 'state', 'updated_at'], unique=False)
138
+ op.create_index('ix_ksp_pull_request_repo_synced', 'ksp_pull_request', ['repository_id', 'detail_synced_at'], unique=False)
139
+ op.create_index(op.f('ix_ksp_pull_request_repository_id'), 'ksp_pull_request', ['repository_id'], unique=False)
140
+ op.create_index(op.f('ix_ksp_pull_request_state'), 'ksp_pull_request', ['state'], unique=False)
141
+ op.create_index(op.f('ix_ksp_pull_request_target_branch'), 'ksp_pull_request', ['target_branch'], unique=False)
142
+ op.create_table('ksp_sync_cursor',
143
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
144
+ sa.Column('repository_id', sa.UUID(), nullable=False),
145
+ sa.Column('resource', sa.Enum('pulls', 'issues', 'labels', name='sync_resource'), nullable=False),
146
+ sa.Column('last_synced_at', sa.DateTime(timezone=True), nullable=True),
147
+ sa.Column('last_seen_updated_at', sa.DateTime(timezone=True), nullable=True),
148
+ sa.Column('total_seen', sa.Integer(), nullable=False),
149
+ sa.Column('backoff_until', sa.DateTime(timezone=True), nullable=True),
150
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_sync_cursor_repository_id_ksp_repository'), ondelete='CASCADE'),
151
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_sync_cursor')),
152
+ sa.UniqueConstraint('repository_id', 'resource', name='uq_ksp_sync_cursor_repo_resource')
153
+ )
154
+ op.create_table('ksp_sync_run',
155
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
156
+ sa.Column('repository_id', sa.UUID(), nullable=False),
157
+ sa.Column('resource', sa.Enum('pulls', 'issues', 'labels', name='sync_resource'), nullable=False),
158
+ sa.Column('trigger', sa.Enum('scheduled', 'manual', 'webhook', name='sync_trigger'), nullable=False),
159
+ sa.Column('status', sa.Enum('running', 'succeeded', 'failed', 'partial', name='sync_status'), nullable=False),
160
+ sa.Column('started_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
161
+ sa.Column('finished_at', sa.DateTime(timezone=True), nullable=True),
162
+ sa.Column('api_calls', sa.Integer(), nullable=False),
163
+ sa.Column('items_fetched', sa.Integer(), nullable=False),
164
+ sa.Column('items_created', sa.Integer(), nullable=False),
165
+ sa.Column('items_updated', sa.Integer(), nullable=False),
166
+ sa.Column('items_failed', sa.Integer(), nullable=False),
167
+ sa.Column('error', sa.Text(), nullable=True),
168
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_sync_run_repository_id_ksp_repository'), ondelete='CASCADE'),
169
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_sync_run'))
170
+ )
171
+ op.create_index(op.f('ix_ksp_sync_run_repository_id'), 'ksp_sync_run', ['repository_id'], unique=False)
172
+ op.create_index(op.f('ix_ksp_sync_run_started_at'), 'ksp_sync_run', ['started_at'], unique=False)
173
+ op.create_table('ksp_webhook_delivery',
174
+ sa.Column('delivery_id', sa.String(length=64), nullable=False),
175
+ sa.Column('repository_id', sa.UUID(), nullable=True),
176
+ sa.Column('event', sa.String(length=64), nullable=False),
177
+ sa.Column('recognized', sa.Boolean(), nullable=False),
178
+ sa.Column('received_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
179
+ sa.Column('processed_at', sa.DateTime(timezone=True), nullable=True),
180
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_webhook_delivery_repository_id_ksp_repository'), ondelete='CASCADE'),
181
+ sa.PrimaryKeyConstraint('delivery_id', name=op.f('pk_ksp_webhook_delivery'))
182
+ )
183
+ op.create_index(op.f('ix_ksp_webhook_delivery_received_at'), 'ksp_webhook_delivery', ['received_at'], unique=False)
184
+ op.create_table('ksp_pr_commit',
185
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
186
+ sa.Column('pull_request_id', sa.UUID(), nullable=False),
187
+ sa.Column('sha', sa.String(length=64), nullable=False),
188
+ sa.Column('sequence', sa.Integer(), nullable=False),
189
+ sa.Column('subject', sa.Text(), nullable=False),
190
+ sa.Column('message', sa.Text(), nullable=False),
191
+ sa.Column('author_name', sa.String(length=255), nullable=True),
192
+ sa.Column('author_email', sa.String(length=255), nullable=True),
193
+ sa.Column('committed_at', sa.DateTime(timezone=True), nullable=True),
194
+ sa.Column('signed_off_bys', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
195
+ sa.ForeignKeyConstraint(['pull_request_id'], ['ksp_pull_request.id'], name=op.f('fk_ksp_pr_commit_pull_request_id_ksp_pull_request'), ondelete='CASCADE'),
196
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_pr_commit')),
197
+ sa.UniqueConstraint('pull_request_id', 'sha', name='uq_ksp_pr_commit_pr_sha')
198
+ )
199
+ op.create_index(op.f('ix_ksp_pr_commit_pull_request_id'), 'ksp_pr_commit', ['pull_request_id'], unique=False)
200
+ op.create_table('ksp_pr_file',
201
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
202
+ sa.Column('pull_request_id', sa.UUID(), nullable=False),
203
+ sa.Column('filename', sa.Text(), nullable=False),
204
+ sa.Column('status', sa.String(length=32), nullable=True),
205
+ sa.Column('additions', sa.Integer(), nullable=False),
206
+ sa.Column('deletions', sa.Integer(), nullable=False),
207
+ sa.Column('is_binary', sa.Boolean(), nullable=False),
208
+ sa.Column('too_large', sa.Boolean(), nullable=False),
209
+ sa.Column('diff', sa.Text(), nullable=True),
210
+ sa.ForeignKeyConstraint(['pull_request_id'], ['ksp_pull_request.id'], name=op.f('fk_ksp_pr_file_pull_request_id_ksp_pull_request'), ondelete='CASCADE'),
211
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_pr_file')),
212
+ sa.UniqueConstraint('pull_request_id', 'filename', name='uq_ksp_pr_file_pr_filename')
213
+ )
214
+ op.create_index(op.f('ix_ksp_pr_file_pull_request_id'), 'ksp_pr_file', ['pull_request_id'], unique=False)
215
+ op.create_table('ksp_pr_issue_link',
216
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
217
+ sa.Column('pull_request_id', sa.UUID(), nullable=False),
218
+ sa.Column('repository_id', sa.UUID(), nullable=False),
219
+ sa.Column('issue_number', sa.Integer(), nullable=False),
220
+ sa.Column('source', sa.Enum('pr_body', 'issue_body', 'atomgit_link', 'manual', name='issue_link_source'), nullable=False),
221
+ sa.Column('confidence', sa.Float(), nullable=False),
222
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
223
+ sa.ForeignKeyConstraint(['pull_request_id'], ['ksp_pull_request.id'], name=op.f('fk_ksp_pr_issue_link_pull_request_id_ksp_pull_request'), ondelete='CASCADE'),
224
+ sa.ForeignKeyConstraint(['repository_id'], ['ksp_repository.id'], name=op.f('fk_ksp_pr_issue_link_repository_id_ksp_repository'), ondelete='CASCADE'),
225
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_pr_issue_link')),
226
+ sa.UniqueConstraint('pull_request_id', 'issue_number', name='uq_ksp_pr_issue_link')
227
+ )
228
+ op.create_index(op.f('ix_ksp_pr_issue_link_issue_number'), 'ksp_pr_issue_link', ['issue_number'], unique=False)
229
+ op.create_index(op.f('ix_ksp_pr_issue_link_pull_request_id'), 'ksp_pr_issue_link', ['pull_request_id'], unique=False)
230
+ op.create_table('ksp_review_event',
231
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
232
+ sa.Column('pull_request_id', sa.UUID(), nullable=False),
233
+ sa.Column('event_type', sa.Enum('lgtm', 'approve', 'ack', 'reject', 'ci_pass', 'ci_fail', 'ci_running', 'cla_signed', 'cla_denied', 'close', 'reopen', 'retest', 'label_added', 'label_removed', name='review_event_type'), nullable=False),
234
+ sa.Column('actor_login', sa.String(length=128), nullable=False),
235
+ sa.Column('actor_user_id', sa.UUID(), nullable=True),
236
+ sa.Column('source', sa.Enum('label', 'comment', 'webhook', name='review_event_source'), nullable=False),
237
+ sa.Column('raw_payload', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
238
+ sa.Column('occurred_at', sa.DateTime(timezone=True), nullable=False),
239
+ sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
240
+ sa.ForeignKeyConstraint(['actor_user_id'], ['ksp_user.id'], name=op.f('fk_ksp_review_event_actor_user_id_ksp_user'), ondelete='SET NULL'),
241
+ sa.ForeignKeyConstraint(['pull_request_id'], ['ksp_pull_request.id'], name=op.f('fk_ksp_review_event_pull_request_id_ksp_pull_request'), ondelete='CASCADE'),
242
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_ksp_review_event')),
243
+ sa.UniqueConstraint('pull_request_id', 'event_type', 'actor_login', 'occurred_at', name='uq_ksp_review_event_identity')
244
+ )
245
+ op.create_index(op.f('ix_ksp_review_event_actor_login'), 'ksp_review_event', ['actor_login'], unique=False)
246
+ op.create_index('ix_ksp_review_event_actor_occurred', 'ksp_review_event', ['actor_login', 'occurred_at'], unique=False)
247
+ op.create_index(op.f('ix_ksp_review_event_event_type'), 'ksp_review_event', ['event_type'], unique=False)
248
+ op.create_index('ix_ksp_review_event_pr_occurred', 'ksp_review_event', ['pull_request_id', 'occurred_at'], unique=False)
249
+ op.create_index(op.f('ix_ksp_review_event_pull_request_id'), 'ksp_review_event', ['pull_request_id'], unique=False)
250
+ # ### end Alembic commands ###
251
+
252
+
253
+ def downgrade() -> None:
254
+ # ### commands auto generated by Alembic - please adjust! ###
255
+ op.drop_index(op.f('ix_ksp_review_event_pull_request_id'), table_name='ksp_review_event')
256
+ op.drop_index('ix_ksp_review_event_pr_occurred', table_name='ksp_review_event')
257
+ op.drop_index(op.f('ix_ksp_review_event_event_type'), table_name='ksp_review_event')
258
+ op.drop_index('ix_ksp_review_event_actor_occurred', table_name='ksp_review_event')
259
+ op.drop_index(op.f('ix_ksp_review_event_actor_login'), table_name='ksp_review_event')
260
+ op.drop_table('ksp_review_event')
261
+ op.drop_index(op.f('ix_ksp_pr_issue_link_pull_request_id'), table_name='ksp_pr_issue_link')
262
+ op.drop_index(op.f('ix_ksp_pr_issue_link_issue_number'), table_name='ksp_pr_issue_link')
263
+ op.drop_table('ksp_pr_issue_link')
264
+ op.drop_index(op.f('ix_ksp_pr_file_pull_request_id'), table_name='ksp_pr_file')
265
+ op.drop_table('ksp_pr_file')
266
+ op.drop_index(op.f('ix_ksp_pr_commit_pull_request_id'), table_name='ksp_pr_commit')
267
+ op.drop_table('ksp_pr_commit')
268
+ op.drop_index(op.f('ix_ksp_webhook_delivery_received_at'), table_name='ksp_webhook_delivery')
269
+ op.drop_table('ksp_webhook_delivery')
270
+ op.drop_index(op.f('ix_ksp_sync_run_started_at'), table_name='ksp_sync_run')
271
+ op.drop_index(op.f('ix_ksp_sync_run_repository_id'), table_name='ksp_sync_run')
272
+ op.drop_table('ksp_sync_run')
273
+ op.drop_table('ksp_sync_cursor')
274
+ op.drop_index(op.f('ix_ksp_pull_request_target_branch'), table_name='ksp_pull_request')
275
+ op.drop_index(op.f('ix_ksp_pull_request_state'), table_name='ksp_pull_request')
276
+ op.drop_index(op.f('ix_ksp_pull_request_repository_id'), table_name='ksp_pull_request')
277
+ op.drop_index('ix_ksp_pull_request_repo_synced', table_name='ksp_pull_request')
278
+ op.drop_index('ix_ksp_pull_request_repo_state_updated', table_name='ksp_pull_request')
279
+ op.drop_index('ix_ksp_pull_request_label_names', table_name='ksp_pull_request', postgresql_using='gin')
280
+ op.drop_index(op.f('ix_ksp_pull_request_content_hash'), table_name='ksp_pull_request')
281
+ op.drop_index(op.f('ix_ksp_pull_request_author_login'), table_name='ksp_pull_request')
282
+ op.drop_table('ksp_pull_request')
283
+ op.drop_index(op.f('ix_ksp_issue_state'), table_name='ksp_issue')
284
+ op.drop_index(op.f('ix_ksp_issue_repository_id'), table_name='ksp_issue')
285
+ op.drop_index('ix_ksp_issue_label_names', table_name='ksp_issue', postgresql_using='gin')
286
+ op.drop_index(op.f('ix_ksp_issue_issue_type'), table_name='ksp_issue')
287
+ op.drop_index(op.f('ix_ksp_issue_author_login'), table_name='ksp_issue')
288
+ op.drop_index(op.f('ix_ksp_issue_assignee_login'), table_name='ksp_issue')
289
+ op.drop_table('ksp_issue')
290
+ op.drop_table('ksp_repository')
291
+ op.drop_index(op.f('ix_ksp_credential_kind'), table_name='ksp_credential')
292
+ op.drop_table('ksp_credential')
293
+ # ### end Alembic commands ###