diffbutler 0.5.0__py3-none-any.whl

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 (105) hide show
  1. diffbutler/__init__.py +1 -0
  2. diffbutler/__main__.py +6 -0
  3. diffbutler/config/__init__.py +1 -0
  4. diffbutler/config/default.toml +91 -0
  5. diffbutler/config/diagnostics.py +90 -0
  6. diffbutler/config/file.py +30 -0
  7. diffbutler/config/instance.py +55 -0
  8. diffbutler/config/settings.py +297 -0
  9. diffbutler/config/setup.py +92 -0
  10. diffbutler/config/transport.py +65 -0
  11. diffbutler/core/__init__.py +1 -0
  12. diffbutler/core/application.py +103 -0
  13. diffbutler/core/context.py +54 -0
  14. diffbutler/core/dashboard.py +393 -0
  15. diffbutler/core/doctor.py +200 -0
  16. diffbutler/core/lifecycle.py +70 -0
  17. diffbutler/core/managed.py +441 -0
  18. diffbutler/core/native.py +301 -0
  19. diffbutler/core/polling.py +118 -0
  20. diffbutler/core/process.py +64 -0
  21. diffbutler/core/repositories.py +260 -0
  22. diffbutler/core/reset.py +34 -0
  23. diffbutler/core/resources.py +50 -0
  24. diffbutler/core/review.py +331 -0
  25. diffbutler/core/setup.py +232 -0
  26. diffbutler/core/snapshot.py +70 -0
  27. diffbutler/core/sync.py +259 -0
  28. diffbutler/core/worker.py +205 -0
  29. diffbutler/core/workspaces.py +139 -0
  30. diffbutler/domain/__init__.py +1 -0
  31. diffbutler/domain/dashboard.py +63 -0
  32. diffbutler/domain/diff.py +285 -0
  33. diffbutler/domain/forge.py +123 -0
  34. diffbutler/domain/lifecycle.py +51 -0
  35. diffbutler/domain/polling.py +92 -0
  36. diffbutler/domain/process.py +29 -0
  37. diffbutler/domain/redaction.py +61 -0
  38. diffbutler/domain/resources.py +50 -0
  39. diffbutler/domain/review.py +167 -0
  40. diffbutler/domain/sync.py +196 -0
  41. diffbutler/forges/__init__.py +1 -0
  42. diffbutler/forges/base.py +39 -0
  43. diffbutler/forges/diagnostics.py +104 -0
  44. diffbutler/forges/github/__init__.py +1 -0
  45. diffbutler/forges/github/adapter.py +352 -0
  46. diffbutler/forges/github/auth.py +49 -0
  47. diffbutler/forges/github/http.py +326 -0
  48. diffbutler/forges/github/mapping.py +157 -0
  49. diffbutler/forges/github/setup.py +86 -0
  50. diffbutler/forges/registry.py +23 -0
  51. diffbutler/py.typed +0 -0
  52. diffbutler/reviewers/__init__.py +1 -0
  53. diffbutler/reviewers/base.py +101 -0
  54. diffbutler/reviewers/extraction.py +198 -0
  55. diffbutler/reviewers/native.py +238 -0
  56. diffbutler/reviewers/parsing.py +247 -0
  57. diffbutler/reviewers/presets.toml +12 -0
  58. diffbutler/reviewers/process.py +78 -0
  59. diffbutler/reviewers/prompt.py +148 -0
  60. diffbutler/reviewers/redaction.py +30 -0
  61. diffbutler/reviewers/resources.py +162 -0
  62. diffbutler/sources/__init__.py +1 -0
  63. diffbutler/sources/diagnostics.py +100 -0
  64. diffbutler/sources/fetch.py +285 -0
  65. diffbutler/sources/git.py +461 -0
  66. diffbutler/sources/remotes.py +82 -0
  67. diffbutler/store/__init__.py +1 -0
  68. diffbutler/store/base.py +88 -0
  69. diffbutler/store/dashboard.py +201 -0
  70. diffbutler/store/database.py +172 -0
  71. diffbutler/store/lifecycle.py +247 -0
  72. diffbutler/store/migrations/__init__.py +1 -0
  73. diffbutler/store/migrations/env.py +14 -0
  74. diffbutler/store/migrations/versions/0001_initial.py +147 -0
  75. diffbutler/store/migrations/versions/0002_monitoring.py +38 -0
  76. diffbutler/store/migrations/versions/0003_reviews.py +72 -0
  77. diffbutler/store/migrations/versions/0004_claims.py +22 -0
  78. diffbutler/store/migrations/versions/0005_results.py +27 -0
  79. diffbutler/store/migrations/versions/0006_anchors.py +17 -0
  80. diffbutler/store/migrations/versions/0007_lifecycle.py +36 -0
  81. diffbutler/store/migrations/versions/0008_processes.py +30 -0
  82. diffbutler/store/migrations/versions/0009_dashboard.py +43 -0
  83. diffbutler/store/migrations/versions/0010_run_reasons.py +17 -0
  84. diffbutler/store/migrations/versions/__init__.py +1 -0
  85. diffbutler/store/models.py +290 -0
  86. diffbutler/store/process.py +75 -0
  87. diffbutler/store/queue.py +289 -0
  88. diffbutler/store/review.py +583 -0
  89. diffbutler/ui/__init__.py +1 -0
  90. diffbutler/ui/cli/__init__.py +532 -0
  91. diffbutler/ui/cli/editor.py +33 -0
  92. diffbutler/ui/tui/__init__.py +1 -0
  93. diffbutler/ui/tui/_fake_agent.py +47 -0
  94. diffbutler/ui/tui/_foreground_exec.py +23 -0
  95. diffbutler/ui/tui/app.py +34 -0
  96. diffbutler/ui/tui/browse.py +119 -0
  97. diffbutler/ui/tui/browser.py +42 -0
  98. diffbutler/ui/tui/dashboard.py +926 -0
  99. diffbutler/ui/tui/foreground.py +103 -0
  100. diffbutler/ui/tui/handoff_probe.py +184 -0
  101. diffbutler-0.5.0.dist-info/METADATA +170 -0
  102. diffbutler-0.5.0.dist-info/RECORD +105 -0
  103. diffbutler-0.5.0.dist-info/WHEEL +4 -0
  104. diffbutler-0.5.0.dist-info/entry_points.txt +3 -0
  105. diffbutler-0.5.0.dist-info/licenses/LICENSE.txt +21 -0
@@ -0,0 +1,147 @@
1
+ """Initial local identity and review-cycle ledger."""
2
+
3
+ from uuid import UUID
4
+
5
+ import sqlalchemy as sa
6
+ from alembic import op
7
+
8
+ revision = "0001"
9
+ down_revision = None
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+
14
+ def upgrade() -> None:
15
+ op.create_table(
16
+ "account",
17
+ sa.Column("id", sa.Uuid(), nullable=False),
18
+ sa.Column("forge", sa.String(length=32), nullable=False),
19
+ sa.Column("login", sa.String(length=255), nullable=False),
20
+ sa.Column("display_name", sa.String(length=255), nullable=False),
21
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_account")),
22
+ )
23
+ op.create_table(
24
+ "repository",
25
+ sa.Column("id", sa.Uuid(), nullable=False),
26
+ sa.Column("forge", sa.String(length=255), nullable=False),
27
+ sa.Column("namespace", sa.String(length=255), nullable=False),
28
+ sa.Column("name", sa.String(length=255), nullable=False),
29
+ sa.Column("default_branch", sa.String(length=255), nullable=False),
30
+ sa.Column("config_json", sa.JSON(), nullable=False),
31
+ sa.Column("enabled", sa.Boolean(), nullable=False),
32
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_repository")),
33
+ sa.UniqueConstraint("forge", "namespace", "name", name=op.f("uq_repository_forge")),
34
+ )
35
+ op.create_table(
36
+ "pull_request",
37
+ sa.Column("id", sa.Uuid(), nullable=False),
38
+ sa.Column("repository_id", sa.Uuid(), nullable=False),
39
+ sa.Column("number", sa.Integer(), nullable=False),
40
+ sa.Column("title", sa.String(length=1024), nullable=False),
41
+ sa.Column("author", sa.String(length=255), nullable=False),
42
+ sa.Column("head_sha", sa.String(length=64), nullable=False),
43
+ sa.Column("base_sha", sa.String(length=64), nullable=False),
44
+ sa.Column("head_ref", sa.String(length=255), nullable=False),
45
+ sa.Column("base_ref", sa.String(length=255), nullable=False),
46
+ sa.Column("is_draft", sa.Boolean(), nullable=False),
47
+ sa.Column("url", sa.String(length=2048), nullable=False),
48
+ sa.Column("state", sa.String(length=32), nullable=False),
49
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
50
+ sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
51
+ sa.Column("fetched_at", sa.DateTime(timezone=True), nullable=False),
52
+ sa.Column("extra_json", sa.JSON(), nullable=False),
53
+ sa.ForeignKeyConstraint(
54
+ ["repository_id"],
55
+ ["repository.id"],
56
+ name=op.f("fk_pull_request_repository_id_repository"),
57
+ ),
58
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_pull_request")),
59
+ sa.UniqueConstraint("repository_id", "number", name=op.f("uq_pull_request_repository_id")),
60
+ )
61
+ op.create_table(
62
+ "review_task",
63
+ sa.Column("id", sa.Uuid(), nullable=False),
64
+ sa.Column("pull_request_id", sa.Uuid(), nullable=False),
65
+ sa.Column("account_id", sa.Uuid(), nullable=False),
66
+ sa.Column("cycle_number", sa.Integer(), nullable=False),
67
+ sa.Column("state", sa.String(length=32), nullable=False),
68
+ sa.Column("snoozed_until", sa.DateTime(timezone=True), nullable=True),
69
+ sa.Column("priority", sa.Integer(), nullable=False),
70
+ sa.Column("notes", sa.String(length=16384), nullable=True),
71
+ sa.Column("first_seen_at", sa.DateTime(timezone=True), nullable=False),
72
+ sa.Column("opened_at", sa.DateTime(timezone=True), nullable=True),
73
+ sa.Column("posted_at", sa.DateTime(timezone=True), nullable=True),
74
+ sa.Column("updated_at", sa.DateTime(timezone=True), nullable=False),
75
+ sa.Column("following", sa.Boolean(), nullable=False),
76
+ sa.Column("request_sources_json", sa.JSON(), nullable=False),
77
+ sa.Column("last_request_event", sa.String(length=255), nullable=True),
78
+ sa.Column("last_activity_cursor", sa.String(length=1024), nullable=True),
79
+ sa.Column("last_reviewed_sha", sa.String(length=64), nullable=True),
80
+ sa.Column("approved_sha", sa.String(length=64), nullable=True),
81
+ sa.Column("completion_reason", sa.String(length=64), nullable=True),
82
+ sa.Column("completion_source", sa.String(length=64), nullable=True),
83
+ sa.Column("completion_event_watermark", sa.String(length=1024), nullable=True),
84
+ sa.Column("action_version", sa.Integer(), nullable=False),
85
+ sa.Column("state_source", sa.String(length=64), nullable=True),
86
+ sa.Column("state_event_watermark", sa.String(length=1024), nullable=True),
87
+ sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
88
+ sa.CheckConstraint("cycle_number > 0", name=op.f("ck_review_task_positive_cycle")),
89
+ sa.ForeignKeyConstraint(
90
+ ["account_id"], ["account.id"], name=op.f("fk_review_task_account_id_account")
91
+ ),
92
+ sa.ForeignKeyConstraint(
93
+ ["pull_request_id"],
94
+ ["pull_request.id"],
95
+ name=op.f("fk_review_task_pull_request_id_pull_request"),
96
+ ),
97
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_review_task")),
98
+ sa.UniqueConstraint(
99
+ "pull_request_id",
100
+ "account_id",
101
+ "cycle_number",
102
+ name=op.f("uq_review_task_pull_request_id"),
103
+ ),
104
+ )
105
+ op.create_table(
106
+ "cleanup_job",
107
+ sa.Column("id", sa.Uuid(), nullable=False),
108
+ sa.Column("review_task_id", sa.Uuid(), nullable=False),
109
+ sa.Column("status", sa.String(length=32), nullable=False),
110
+ sa.Column("attempts", sa.Integer(), nullable=False),
111
+ sa.Column("next_retry_at", sa.DateTime(timezone=True), nullable=True),
112
+ sa.Column("last_error", sa.String(length=4096), nullable=True),
113
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
114
+ sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
115
+ sa.ForeignKeyConstraint(
116
+ ["review_task_id"],
117
+ ["review_task.id"],
118
+ name=op.f("fk_cleanup_job_review_task_id_review_task"),
119
+ ),
120
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_cleanup_job")),
121
+ sa.UniqueConstraint("review_task_id", name=op.f("uq_cleanup_job_review_task_id")),
122
+ )
123
+ op.bulk_insert(
124
+ sa.table(
125
+ "account",
126
+ sa.column("id", sa.Uuid()),
127
+ sa.column("forge", sa.String()),
128
+ sa.column("login", sa.String()),
129
+ sa.column("display_name", sa.String()),
130
+ ),
131
+ [
132
+ {
133
+ "id": UUID("00000000-0000-0000-0000-000000000001"),
134
+ "forge": "local",
135
+ "login": "local",
136
+ "display_name": "Local account",
137
+ }
138
+ ],
139
+ )
140
+
141
+
142
+ def downgrade() -> None:
143
+ op.drop_table("cleanup_job")
144
+ op.drop_table("review_task")
145
+ op.drop_table("pull_request")
146
+ op.drop_table("repository")
147
+ op.drop_table("account")
@@ -0,0 +1,38 @@
1
+ """Account-scoped monitoring and persistent activity watermarks."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0002"
7
+ down_revision = "0001"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.add_column("review_task", sa.Column("completed_head_sha", sa.String(64), nullable=True))
14
+ op.create_table(
15
+ "forge_account",
16
+ sa.Column("account_id", sa.Uuid(), sa.ForeignKey("account.id"), primary_key=True),
17
+ sa.Column("forge", sa.String(255), nullable=False),
18
+ sa.Column("login", sa.String(255), nullable=False),
19
+ sa.Column("version", sa.Integer(), nullable=False),
20
+ sa.UniqueConstraint("forge", "login", name="uq_forge_account_forge"),
21
+ )
22
+ op.create_table(
23
+ "pr_monitor",
24
+ sa.Column("account_id", sa.Uuid(), sa.ForeignKey("account.id"), primary_key=True),
25
+ sa.Column("pull_request_id", sa.Uuid(), sa.ForeignKey("pull_request.id"), primary_key=True),
26
+ sa.Column("task_id", sa.Uuid(), sa.ForeignKey("review_task.id"), nullable=False),
27
+ sa.Column("following", sa.Boolean(), nullable=False),
28
+ sa.Column("sources", sa.JSON(), nullable=False),
29
+ sa.Column("snapshot_json", sa.JSON(), nullable=False),
30
+ sa.Column("request_key", sa.String(1024), nullable=False),
31
+ sa.Column("review_key", sa.String(1024), nullable=False),
32
+ )
33
+
34
+
35
+ def downgrade() -> None:
36
+ op.drop_table("pr_monitor")
37
+ op.drop_table("forge_account")
38
+ op.drop_column("review_task", "completed_head_sha")
@@ -0,0 +1,72 @@
1
+ """Durable conversation identity and analysis attempts; no automatic dispatch."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0003"
7
+ down_revision = "0002"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.create_table(
14
+ "review_session",
15
+ sa.Column("id", sa.Uuid(), primary_key=True),
16
+ sa.Column("review_task_id", sa.Uuid(), sa.ForeignKey("review_task.id"), nullable=False),
17
+ sa.Column("reviewer_id", sa.String(255), nullable=False),
18
+ sa.Column("generation", sa.Integer(), nullable=False),
19
+ sa.Column("display_name", sa.String(1024), nullable=False),
20
+ sa.Column("backend_session_id", sa.String(1024)),
21
+ sa.Column("repository_path", sa.String(4096)),
22
+ sa.Column("status", sa.String(32), nullable=False),
23
+ sa.Column("transcript_ref", sa.String(4096)),
24
+ sa.Column("resource_manifest_json", sa.JSON(), nullable=False),
25
+ sa.Column("cleanup_status", sa.String(32), nullable=False),
26
+ sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
27
+ sa.Column("resumed_at", sa.DateTime(timezone=True)),
28
+ sa.Column("cleaned_at", sa.DateTime(timezone=True)),
29
+ sa.UniqueConstraint("review_task_id", "reviewer_id", "generation"),
30
+ sa.UniqueConstraint("id", "review_task_id", "reviewer_id"),
31
+ sa.CheckConstraint("generation > 0", name="positive_generation"),
32
+ sa.CheckConstraint("status IN ('ready', 'suspended', 'cleaned')", name="session_status"),
33
+ )
34
+ op.create_table(
35
+ "ai_run",
36
+ sa.Column("id", sa.Uuid(), primary_key=True),
37
+ sa.Column("review_task_id", sa.Uuid(), sa.ForeignKey("review_task.id"), nullable=False),
38
+ sa.Column("review_session_id", sa.Uuid(), nullable=False),
39
+ sa.Column("reviewer_id", sa.String(255), nullable=False),
40
+ sa.Column("prompt_version", sa.String(255), nullable=False),
41
+ sa.Column("config_hash", sa.String(64), nullable=False),
42
+ sa.Column("head_sha", sa.String(64), nullable=False),
43
+ sa.Column("base_sha", sa.String(64), nullable=False),
44
+ sa.Column("analysis_key", sa.String(64), nullable=False),
45
+ sa.Column("attempt", sa.Integer(), nullable=False),
46
+ sa.Column("status", sa.String(32), nullable=False),
47
+ sa.Column("parse_status", sa.String(32), nullable=False),
48
+ sa.Column("requested_at", sa.DateTime(timezone=True), nullable=False),
49
+ sa.Column("started_at", sa.DateTime(timezone=True)),
50
+ sa.Column("finished_at", sa.DateTime(timezone=True)),
51
+ sa.Column("exit_code", sa.Integer()),
52
+ sa.Column("stdout_blob", sa.LargeBinary()),
53
+ sa.Column("stderr_blob", sa.LargeBinary()),
54
+ sa.UniqueConstraint("analysis_key", "attempt"),
55
+ sa.ForeignKeyConstraint(
56
+ ["review_session_id", "review_task_id", "reviewer_id"],
57
+ ["review_session.id", "review_session.review_task_id", "review_session.reviewer_id"],
58
+ ),
59
+ sa.CheckConstraint("attempt > 0", name="positive_attempt"),
60
+ sa.CheckConstraint(
61
+ "status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled', 'stale')",
62
+ name="run_status",
63
+ ),
64
+ sa.CheckConstraint(
65
+ "parse_status IN ('pending', 'succeeded', 'failed')", name="parse_status"
66
+ ),
67
+ )
68
+
69
+
70
+ def downgrade() -> None:
71
+ op.drop_table("ai_run")
72
+ op.drop_table("review_session")
@@ -0,0 +1,22 @@
1
+ """Durable exclusive worker ownership, separate from analysis validity."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0004"
7
+ down_revision = "0003"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.create_table(
14
+ "run_claim",
15
+ sa.Column("token", sa.Uuid(), primary_key=True),
16
+ sa.Column("run_id", sa.Uuid(), sa.ForeignKey("ai_run.id"), nullable=False),
17
+ sa.UniqueConstraint("run_id"),
18
+ )
19
+
20
+
21
+ def downgrade() -> None:
22
+ op.drop_table("run_claim")
@@ -0,0 +1,27 @@
1
+ """Retain parsed analysis independently of cleanup-eligible process output."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0005"
7
+ down_revision = "0004"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.create_table(
14
+ "review_result",
15
+ sa.Column("run_id", sa.Uuid(), sa.ForeignKey("ai_run.id"), primary_key=True),
16
+ sa.Column("document_json", sa.Text(), nullable=True),
17
+ sa.Column("parse_tier", sa.String(32), nullable=True),
18
+ sa.Column("error", sa.String(255), nullable=True),
19
+ sa.Column("notes", sa.JSON(), nullable=False),
20
+ sa.Column("selected_indices", sa.JSON(), nullable=False),
21
+ sa.Column("min_confidence", sa.Float(), nullable=False),
22
+ sa.Column("findings_threshold", sa.String(32), nullable=False),
23
+ )
24
+
25
+
26
+ def downgrade() -> None:
27
+ op.drop_table("review_result")
@@ -0,0 +1,17 @@
1
+ """Retain original and validated anchors separately from model findings."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0006"
7
+ down_revision = "0005"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.add_column("review_result", sa.Column("anchors_json", sa.Text(), nullable=True))
14
+
15
+
16
+ def downgrade() -> None:
17
+ op.drop_column("review_result", "anchors_json")
@@ -0,0 +1,36 @@
1
+ """Durable foreground ownership and exclusive cleanup claims."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0007"
7
+ down_revision = "0006"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.create_table(
14
+ "foreground_lease",
15
+ sa.Column("token", sa.Uuid(), primary_key=True),
16
+ sa.Column(
17
+ "session_id", sa.Uuid(), sa.ForeignKey("review_session.id"), nullable=False, unique=True
18
+ ),
19
+ sa.Column("backend_session_id", sa.String(1024), nullable=False),
20
+ sa.Column("head_sha", sa.String(128), nullable=False),
21
+ sa.Column("task_version", sa.Integer(), nullable=False),
22
+ sa.Column("acquired_at", sa.DateTime(timezone=True), nullable=False),
23
+ )
24
+ op.create_table(
25
+ "cleanup_claim",
26
+ sa.Column("token", sa.Uuid(), primary_key=True),
27
+ sa.Column(
28
+ "job_id", sa.Uuid(), sa.ForeignKey("cleanup_job.id"), nullable=False, unique=True
29
+ ),
30
+ sa.Column("acquired_at", sa.DateTime(timezone=True), nullable=False),
31
+ )
32
+
33
+
34
+ def downgrade() -> None:
35
+ op.drop_table("cleanup_claim")
36
+ op.drop_table("foreground_lease")
@@ -0,0 +1,30 @@
1
+ """Durable pre-launch identity for conservative restart recovery."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0008"
7
+ down_revision = "0007"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.create_table(
14
+ "process_launch",
15
+ sa.Column("token", sa.Uuid(), primary_key=True),
16
+ sa.Column("kind", sa.String(32), nullable=False),
17
+ sa.Column("host_id", sa.Uuid(), nullable=False),
18
+ sa.Column("boot_id", sa.Uuid(), nullable=False),
19
+ sa.Column("launcher_pid", sa.Integer(), nullable=True),
20
+ sa.CheckConstraint(
21
+ "kind IN ('background', 'foreground', 'cleanup')", name="operation_kind"
22
+ ),
23
+ sa.CheckConstraint(
24
+ "launcher_pid IS NULL OR launcher_pid > 0", name="positive_launcher_pid"
25
+ ),
26
+ )
27
+
28
+
29
+ def downgrade() -> None:
30
+ op.drop_table("process_launch")
@@ -0,0 +1,43 @@
1
+ """Retained forge receipts, cycle snapshots, and dashboard preferences."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0009"
7
+ down_revision = "0008"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.add_column("review_task", sa.Column("snapshot_json", sa.JSON(), nullable=True))
14
+ op.add_column("pr_monitor", sa.Column("activity_json", sa.Text(), nullable=True))
15
+ op.add_column(
16
+ "pr_monitor",
17
+ sa.Column("refresh_pending", sa.Boolean(), nullable=False, server_default=sa.false()),
18
+ )
19
+ op.add_column(
20
+ "pr_monitor", sa.Column("notice", sa.String(255), nullable=False, server_default="")
21
+ )
22
+ op.create_table(
23
+ "observed_activity",
24
+ sa.Column("account_id", sa.Uuid(), sa.ForeignKey("account.id"), primary_key=True),
25
+ sa.Column("pull_request_id", sa.Uuid(), sa.ForeignKey("pull_request.id"), primary_key=True),
26
+ sa.Column("event_id", sa.String(255), primary_key=True),
27
+ sa.Column("review_task_id", sa.Uuid(), sa.ForeignKey("review_task.id"), nullable=False),
28
+ sa.Column("payload_json", sa.Text(), nullable=False),
29
+ )
30
+ op.create_table(
31
+ "ui_preference",
32
+ sa.Column("key", sa.String(255), primary_key=True),
33
+ sa.Column("value", sa.Text(), nullable=False),
34
+ )
35
+
36
+
37
+ def downgrade() -> None:
38
+ op.drop_table("ui_preference")
39
+ op.drop_table("observed_activity")
40
+ op.drop_column("pr_monitor", "notice")
41
+ op.drop_column("pr_monitor", "refresh_pending")
42
+ op.drop_column("pr_monitor", "activity_json")
43
+ op.drop_column("review_task", "snapshot_json")
@@ -0,0 +1,17 @@
1
+ """Keep review failure and invalidation reasons across refreshes and cleanup."""
2
+
3
+ import sqlalchemy as sa
4
+ from alembic import op
5
+
6
+ revision = "0010"
7
+ down_revision = "0009"
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade() -> None:
13
+ op.add_column("ai_run", sa.Column("status_reason", sa.Text(), nullable=True))
14
+
15
+
16
+ def downgrade() -> None:
17
+ op.drop_column("ai_run", "status_reason")
@@ -0,0 +1 @@
1
+ """Immutable migration revisions."""