@taodigital/database 0.0.0-stage → 1.2.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 (173) hide show
  1. package/README.md +21 -2
  2. package/dist/index.d.ts +58 -0
  3. package/dist/index.js +209 -0
  4. package/package.json +29 -4
  5. package/prisma/migrations/0000_init/migration.sql +220 -0
  6. package/prisma/migrations/0001_tenant_isolation/migration.sql +43 -0
  7. package/prisma/migrations/0002_app_runtime_role/migration.sql +14 -0
  8. package/prisma/migrations/0003_skill_taxonomy/migration.sql +30 -0
  9. package/prisma/migrations/0004_extended_models/migration.sql +215 -0
  10. package/prisma/migrations/0005_enhanced_test_artifacts/migration.sql +24 -0
  11. package/prisma/migrations/0006_tenant_settings/migration.sql +28 -0
  12. package/prisma/migrations/0007_project_slug/migration.sql +11 -0
  13. package/prisma/migrations/0008_project_crawl_config/migration.sql +3 -0
  14. package/prisma/migrations/0009_tenant_slug/migration.sql +11 -0
  15. package/prisma/migrations/0010_user_password/migration.sql +3 -0
  16. package/prisma/migrations/0011_oauth_connections/migration.sql +78 -0
  17. package/prisma/migrations/0012_agent_webhooks/migration.sql +46 -0
  18. package/prisma/migrations/0013_project_artifacts/migration.sql +27 -0
  19. package/prisma/migrations/0014_knowledge_chunks/migration.sql +37 -0
  20. package/prisma/migrations/20260626022056_platform_job_migration/migration.sql +44 -0
  21. package/prisma/migrations/20260626053339_kag_schema_update/migration.sql +27 -0
  22. package/prisma/migrations/20260626053340_restore_bm25_index/migration.sql +13 -0
  23. package/prisma/migrations/20260626063704_token_optimisation/migration.sql +86 -0
  24. package/prisma/migrations/20260626080000_job_name/migration.sql +2 -0
  25. package/prisma/migrations/20260626090000_rls_missing_tables/migration.sql +39 -0
  26. package/prisma/migrations/20260626100000_token_budget/migration.sql +21 -0
  27. package/prisma/migrations/20260626110000_screen_groups/migration.sql +42 -0
  28. package/prisma/migrations/20260626120000_artifact_intelligence/migration.sql +5 -0
  29. package/prisma/migrations/20260626130000_notifications/migration.sql +34 -0
  30. package/prisma/migrations/20260627100000_element_viewport/migration.sql +2 -0
  31. package/prisma/migrations/20260627100000_screenshot_cloud_url/migration.sql +2 -0
  32. package/prisma/migrations/20260627110000_screenshot_index/migration.sql +2 -0
  33. package/prisma/migrations/20260627140000_artifact_storage_key/migration.sql +3 -0
  34. package/prisma/migrations/20260627150000_screen_lifecycle/migration.sql +5 -0
  35. package/prisma/migrations/20260627160000_native_app_support/migration.sql +41 -0
  36. package/prisma/migrations/20260627200000_rls_missing_tables/migration.sql +57 -0
  37. package/prisma/migrations/20260627210000_llmcalllog_tasktype/migration.sql +1 -0
  38. package/prisma/migrations/20260628100000_element_enrichment/migration.sql +7 -0
  39. package/prisma/migrations/20260628110000_element_hash/migration.sql +6 -0
  40. package/prisma/migrations/20260628120000_screen_diff/migration.sql +20 -0
  41. package/prisma/migrations/20260628130000_probe_observations/migration.sql +19 -0
  42. package/prisma/migrations/20260629100000_visual_baseline_masking/migration.sql +1 -0
  43. package/prisma/migrations/20260629110000_testcase_viewports/migration.sql +1 -0
  44. package/prisma/migrations/20260630100000_visual_baseline_element_snapshot/migration.sql +1 -0
  45. package/prisma/migrations/20260630110000_git_branched_baselines/migration.sql +11 -0
  46. package/prisma/migrations/20260630120000_testcase_viewport_constraint/migration.sql +1 -0
  47. package/prisma/migrations/20260630130000_test_case_runs/migration.sql +22 -0
  48. package/prisma/migrations/20260630140000_perf_env_comments/migration.sql +68 -0
  49. package/prisma/migrations/20260630150000_testcase_gen_at/migration.sql +2 -0
  50. package/prisma/migrations/20260630160000_rls_catchup_late_tables/migration.sql +41 -0
  51. package/prisma/migrations/20260701100000_skill_file_diff_approval/migration.sql +9 -0
  52. package/prisma/migrations/20260701200000_workspace_connector/migration.sql +20 -0
  53. package/prisma/migrations/20260701220000_screen_a11y_security/migration.sql +2 -0
  54. package/prisma/migrations/20260702100000_ci_threshold_root_cause/migration.sql +2 -0
  55. package/prisma/migrations/20260702120000_medium_priority_gaps/migration.sql +28 -0
  56. package/prisma/migrations/20260702140000_testcase_v2_technique/migration.sql +7 -0
  57. package/prisma/migrations/20260702150000_add_app_field/migration.sql +40 -0
  58. package/prisma/migrations/20260702160000_add_requirement_model/migration.sql +62 -0
  59. package/prisma/migrations/20260702180000_add_invitation_model/migration.sql +53 -0
  60. package/prisma/migrations/20260702190000_suite_hierarchy_artifact_trace/migration.sql +28 -0
  61. package/prisma/migrations/20260702200000_project_member_access/migration.sql +31 -0
  62. package/prisma/migrations/20260703000000_add_system_config/migration.sql +11 -0
  63. package/prisma/migrations/20260703000000_custom_roles/migration.sql +29 -0
  64. package/prisma/migrations/20260703100000_platform_admin/migration.sql +11 -0
  65. package/prisma/migrations/20260703110000_add_extension_models/migration.sql +106 -0
  66. package/prisma/migrations/20260703120000_project_restricted_column/migration.sql +1 -0
  67. package/prisma/migrations/20260703200000_system_config_category_index/migration.sql +1 -0
  68. package/prisma/migrations/20260703210000_workspace_layer/migration.sql +24 -0
  69. package/prisma/migrations/20260703230000_workspace_agent_tokens/migration.sql +2 -0
  70. package/prisma/migrations/20260704000000_requirement_rls_policy/migration.sql +35 -0
  71. package/prisma/migrations/20260704100000_fix_oauth_auditlog_cascade/migration.sql +9 -0
  72. package/prisma/migrations/20260704110000_skillfile_approval_status/migration.sql +2 -0
  73. package/prisma/migrations/20260704120000_scenario_suspect_workspace/migration.sql +35 -0
  74. package/prisma/migrations/20260704130000_testcase_traceto_suspect_indexes/migration.sql +5 -0
  75. package/prisma/migrations/20260704140000_workspace_rls/migration.sql +19 -0
  76. package/prisma/migrations/20260704150000_project_cascade_deletes/migration.sql +125 -0
  77. package/prisma/migrations/20260705100000_platform_job_worker_id/migration.sql +12 -0
  78. package/prisma/migrations/20260705110000_screen_discovery_method/migration.sql +7 -0
  79. package/prisma/migrations/20260705120000_webhook_delivery/migration.sql +32 -0
  80. package/prisma/migrations/20260705130000_tenant_feature_flags/migration.sql +23 -0
  81. package/prisma/migrations/20260705140000_auth_sessions/migration.sql +30 -0
  82. package/prisma/migrations/20260705150000_fix_rls_wrong_session_var/migration.sql +51 -0
  83. package/prisma/migrations/20260705160000_testcase_flaky_quarantine/migration.sql +14 -0
  84. package/prisma/migrations/20260705170000_testcaserun_browser/migration.sql +6 -0
  85. package/prisma/migrations/20260705180000_allow_custom_roles/migration.sql +22 -0
  86. package/prisma/migrations/20260706050000_screen_markdown_extraction/migration.sql +8 -0
  87. package/prisma/migrations/20260706090000_add_lighthouse_to_performance_snapshot/migration.sql +4 -0
  88. package/prisma/migrations/20260706100000_add_lighthouse_bp_and_job_index/migration.sql +5 -0
  89. package/prisma/migrations/20260706110000_add_visual_diff_ai_summary/migration.sql +2 -0
  90. package/prisma/migrations/20260707120000_script_bundles/migration.sql +25 -0
  91. package/prisma/migrations/20260707130000_requirement_source_section/migration.sql +4 -0
  92. package/prisma/migrations/20260707140000_structured_skill_metadata/migration.sql +8 -0
  93. package/prisma/migrations/20260707150000_crawler_platform_primitives/migration.sql +112 -0
  94. package/prisma/migrations/20260707160000_screen_group_versioning/migration.sql +9 -0
  95. package/prisma/migrations/20260707170000_add_mfa_fields/migration.sql +24 -0
  96. package/prisma/migrations/20260707180000_add_missing_tables/migration.sql +158 -0
  97. package/prisma/migrations/20260707190000_add_missing_columns/migration.sql +15 -0
  98. package/prisma/migrations/20260707200000_add_plan_sessions/migration.sql +1 -0
  99. package/prisma/migrations/20260708100000_missing_columns_comprehensive/migration.sql +15 -0
  100. package/prisma/migrations/20260708200000_testcase_dataset_column/migration.sql +16 -0
  101. package/prisma/migrations/20260709100000_add_filename_to_native_app_build/migration.sql +1 -0
  102. package/prisma/migrations/20260709200000_cloud_url_visual_artifacts/migration.sql +6 -0
  103. package/prisma/migrations/20260714200000_add_cached_scenarios_to_artifact/migration.sql +4 -0
  104. package/prisma/migrations/20260715200000_screen_markdown_content/migration.sql +2 -0
  105. package/prisma/migrations/20260716100000_github_repo_coverage_tables/migration.sql +114 -0
  106. package/prisma/migrations/20260716200000_github_pr_script_validation/migration.sql +48 -0
  107. package/prisma/migrations/20260716210000_s3_content_keys/migration.sql +20 -0
  108. package/prisma/migrations/20260717220000_ide_push_token/migration.sql +21 -0
  109. package/prisma/migrations/20260717230000_ide_push_script_ids/migration.sql +2 -0
  110. package/prisma/migrations/20260730040000_add_step_evidence_and_validation/migration.sql +93 -0
  111. package/prisma/migrations/20260803000000_add_tenantId_to_test_data_row/migration.sql +40 -0
  112. package/prisma/migrations/20260804000000_add_project_restricted/migration.sql +6 -0
  113. package/prisma/migrations/20260804000000_add_user_avatar_url/migration.sql +4 -0
  114. package/prisma/migrations/20260805000000_add_auditlog_ipaddress/migration.sql +2 -0
  115. package/prisma/migrations/20260807000000_add_test_data_classification/migration.sql +18 -0
  116. package/prisma/migrations/20260807000001_add_test_case_notes/migration.sql +5 -0
  117. package/prisma/migrations/20260807000002_add_visual_baseline_browser_structured_steps/migration.sql +11 -0
  118. package/prisma/migrations/20260807100000_add_tenant_plan_config/migration.sql +14 -0
  119. package/prisma/migrations/20260808000000_agent_token_missing_columns/migration.sql +22 -0
  120. package/prisma/migrations/20260808000001_visual_diff_image_and_threshold/migration.sql +8 -0
  121. package/prisma/migrations/20260808000002_ssoconfig_email_domains/migration.sql +7 -0
  122. package/prisma/migrations/20260808000003_project_status_description_updatedat/migration.sql +7 -0
  123. package/prisma/migrations/20260814000000_workspace_require_mfa/migration.sql +2 -0
  124. package/prisma/migrations/20260814000001_add_screenshot_dpr/migration.sql +2 -0
  125. package/prisma/migrations/20260818000000_add_content_hash_to_test_case/migration.sql +6 -0
  126. package/prisma/migrations/20260818000001_add_grounding_hash_to_test_case/migration.sql +3 -0
  127. package/prisma/migrations/20260819000000_coverage_mapping_cross_encoder_fields/migration.sql +3 -0
  128. package/prisma/migrations/20260820000000_add_locators_json_to_test_case/migration.sql +6 -0
  129. package/prisma/migrations/20260820000001_add_test_case_version/migration.sql +27 -0
  130. package/prisma/migrations/20260826000000_add_capture_step_screenshots/migration.sql +1 -0
  131. package/prisma/migrations/20260903000000_add_platform_job_dlq/migration.sql +10 -0
  132. package/prisma/migrations/20260903000002_add_versioning_tables/migration.sql +38 -0
  133. package/prisma/migrations/20260903000003_add_learning_models/migration.sql +71 -0
  134. package/prisma/migrations/20260903000004_add_workspace_agent_prompts/migration.sql +33 -0
  135. package/prisma/migrations/20260903000005_add_config_history/migration.sql +70 -0
  136. package/prisma/migrations/20260903000006_add_skill_file_config/migration.sql +24 -0
  137. package/prisma/migrations/20260904000000_add_agent_prompt_platform_table/migration.sql +23 -0
  138. package/prisma/migrations/20260904180424_rbac_role_configs_admin_roles/migration.sql +606 -0
  139. package/prisma/migrations/20260907000000_fix_testcase_timestamps/migration.sql +13 -0
  140. package/prisma/migrations/20260907000001_add_artifact_imported_suite_id/migration.sql +1 -0
  141. package/prisma/migrations/20260907000002_add_user_login_tracking/migration.sql +9 -0
  142. package/prisma/migrations/20260907000003_add_artifact_import_tracking/migration.sql +9 -0
  143. package/prisma/migrations/20260907000004_add_artifact_version_chain/migration.sql +18 -0
  144. package/prisma/migrations/20260908000000_add_project_vsa_enabled/migration.sql +2 -0
  145. package/prisma/migrations/20260908000001_rbac_migration_catchup/migration.sql +314 -0
  146. package/prisma/migrations/20260908000002_fix_role_configs_empty_permissions/migration.sql +22 -0
  147. package/prisma/migrations/20260908000003_sync_builtin_role_permissions/migration.sql +38 -0
  148. package/prisma/migrations/20260909000000_add_filepath_to_testscript/migration.sql +5 -0
  149. package/prisma/migrations/20260909000001_add_alignment_result_table/migration.sql +31 -0
  150. package/prisma/migrations/20260910000000_add_admin_mfa/migration.sql +35 -0
  151. package/prisma/migrations/20260910000000_add_dom_snapshot_cloud_url/migration.sql +2 -0
  152. package/prisma/migrations/20260910000001_add_requires_auth_to_screen/migration.sql +1 -0
  153. package/prisma/migrations/20260910000002_add_screen_requires_auth/migration.sql +2 -0
  154. package/prisma/migrations/20260910000003_add_admin_auth_sessions/migration.sql +17 -0
  155. package/prisma/migrations/20260916000001_add_admin_last_login/migration.sql +1 -0
  156. package/prisma/migrations/20260916000002_add_admin_failed_login/migration.sql +2 -0
  157. package/prisma/migrations/20260916000003_seed_system_tenant/migration.sql +6 -0
  158. package/prisma/migrations/20260918000000_fix_permissions_comprehensive/migration.sql +63 -0
  159. package/prisma/migrations/20260924000000_add_screen_role_access/migration.sql +2 -0
  160. package/prisma/migrations/20260928000000_add_navigation_graph/migration.sql +41 -0
  161. package/prisma/migrations/20260929000000_artifact_content_hash_suite_unique/migration.sql +25 -0
  162. package/prisma/migrations/20260929000000_navigation_graph_unique_constraint/migration.sql +5 -0
  163. package/prisma/migrations/20260929000001_add_membership_status/migration.sql +7 -0
  164. package/prisma/migrations/20260929000002_add_sso_config_scim_token_hash/migration.sql +8 -0
  165. package/prisma/migrations/20261005000001_jobs_workers_hardening/migration.sql +63 -0
  166. package/prisma/migrations/20261009000001_add_suite_category/migration.sql +2 -0
  167. package/prisma/migrations/20261009000002_add_testcase_approved_fields/migration.sql +3 -0
  168. package/prisma/migrations/20261012_auto_version_history/migration.sql +111 -0
  169. package/prisma/migrations/20261015000001_traceability_alignment_gaps/migration.sql +11 -0
  170. package/prisma/migrations/20261028000001_context_intelligence_p1_p7/migration.sql +76 -0
  171. package/prisma/migrations/migration_lock.toml +3 -0
  172. package/prisma/schema.prisma +2901 -0
  173. package/prisma/seed.ts +1262 -0
@@ -0,0 +1,314 @@
1
+ -- Catch-up migration for failed 20260904180424_rbac_role_configs_admin_roles.
2
+ -- That migration had a bug (KnowledgeChunk.tsv DROP DEFAULT on a generated column)
3
+ -- which caused a PostgreSQL error and rolled back the entire migration, but Prisma
4
+ -- marked it as "applied". This migration re-applies everything idempotently.
5
+ -- All statements use IF NOT EXISTS / IF EXISTS / DO $$ blocks for safety.
6
+
7
+ -- ============================================================
8
+ -- 1. COLUMN ADDITIONS (critical — missing columns cause 500s)
9
+ -- ============================================================
10
+
11
+ -- AdminUser.role (RBAC)
12
+ ALTER TABLE "AdminUser" ADD COLUMN IF NOT EXISTS "role" TEXT NOT NULL DEFAULT 'SUPER_ADMIN';
13
+
14
+ -- Project.vsaEnabled (schema drift — was added to schema.prisma without a migration)
15
+ ALTER TABLE "Project" ADD COLUMN IF NOT EXISTS "vsaEnabled" BOOLEAN NOT NULL DEFAULT false;
16
+
17
+ -- TestCase timestamps (were added in the RBAC migration, required NOT NULL fields)
18
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
19
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
20
+
21
+ -- ============================================================
22
+ -- 2. COLUMN DROPS (remove deprecated columns; IF EXISTS = safe)
23
+ -- ============================================================
24
+
25
+ ALTER TABLE "TestCase" DROP COLUMN IF EXISTS "genAt";
26
+ ALTER TABLE "TestResult" DROP COLUMN IF EXISTS "dataRow";
27
+ ALTER TABLE "TestResult" DROP COLUMN IF EXISTS "networkTimings";
28
+ ALTER TABLE "TestResult" DROP COLUMN IF EXISTS "rootCauseSummary";
29
+ ALTER TABLE "VisualBaseline" DROP COLUMN IF EXISTS "domSnapshotPath";
30
+ ALTER TABLE "Screen" DROP COLUMN IF EXISTS "a11yViolations";
31
+ ALTER TABLE "Screen" DROP COLUMN IF EXISTS "securityHeaders";
32
+ ALTER TABLE "PerformanceSnapshot" DROP COLUMN IF EXISTS "lighthouseReport";
33
+ ALTER TABLE "PlatformJob" DROP COLUMN IF EXISTS "appiumSessionId";
34
+ ALTER TABLE "PlatformJob" DROP COLUMN IF EXISTS "deviceFarm";
35
+ ALTER TABLE "PlatformJob" DROP COLUMN IF EXISTS "deviceModel";
36
+ ALTER TABLE "PlatformJob" DROP COLUMN IF EXISTS "osVersion";
37
+
38
+ -- ============================================================
39
+ -- 3. COLUMN TYPE / DEFAULT CHANGES (idempotent or no-op safe)
40
+ -- ============================================================
41
+
42
+ -- AppField.piiClass default
43
+ DO $$ BEGIN
44
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='AppField' AND column_name='piiClass') THEN
45
+ ALTER TABLE "AppField" ALTER COLUMN "piiClass" SET DEFAULT 'unknown';
46
+ END IF;
47
+ END $$;
48
+
49
+ -- Comment.userId — allow null (drop not null constraint)
50
+ DO $$ BEGIN
51
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='Comment' AND column_name='userId' AND is_nullable='NO') THEN
52
+ ALTER TABLE "Comment" ALTER COLUMN "userId" DROP NOT NULL;
53
+ END IF;
54
+ END $$;
55
+
56
+ -- GitHubRepoConnection.testIdPattern default
57
+ DO $$ BEGIN
58
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='GitHubRepoConnection' AND column_name='testIdPattern') THEN
59
+ ALTER TABLE "GitHubRepoConnection" ALTER COLUMN "testIdPattern" SET DEFAULT 'TC-[A-Z]+-\d+';
60
+ END IF;
61
+ END $$;
62
+
63
+ -- KnowledgeChunk.tsv — if still a generated column, drop expression so it can be reattached
64
+ -- (Use DO block so it's a no-op if already a plain column)
65
+ DO $$ BEGIN
66
+ IF EXISTS (
67
+ SELECT 1 FROM information_schema.columns
68
+ WHERE table_name = 'KnowledgeChunk' AND column_name = 'tsv'
69
+ AND is_generated = 'ALWAYS'
70
+ ) THEN
71
+ ALTER TABLE "KnowledgeChunk" ALTER COLUMN "tsv" DROP EXPRESSION;
72
+ END IF;
73
+ END $$;
74
+
75
+ -- PerformanceSnapshot: change lighthouse columns from numeric to double precision
76
+ DO $$ BEGIN
77
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='PerformanceSnapshot' AND column_name='lighthousePerf') THEN
78
+ ALTER TABLE "PerformanceSnapshot"
79
+ ALTER COLUMN "lighthousePerf" SET DATA TYPE DOUBLE PRECISION,
80
+ ALTER COLUMN "lighthouseA11y" SET DATA TYPE DOUBLE PRECISION,
81
+ ALTER COLUMN "lighthouseSEO" SET DATA TYPE DOUBLE PRECISION,
82
+ ALTER COLUMN "lighthouseBP" SET DATA TYPE DOUBLE PRECISION;
83
+ END IF;
84
+ END $$;
85
+
86
+ -- SharedRunToken: standardize timestamp types
87
+ DO $$ BEGIN
88
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='SharedRunToken' AND column_name='expiresAt') THEN
89
+ ALTER TABLE "SharedRunToken"
90
+ ALTER COLUMN "expiresAt" SET DATA TYPE TIMESTAMP(3),
91
+ ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMP(3);
92
+ END IF;
93
+ END $$;
94
+
95
+ -- SsoConfig: set jitProvisioning default to false
96
+ DO $$ BEGIN
97
+ IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='SsoConfig' AND column_name='jitProvisioning') THEN
98
+ ALTER TABLE "SsoConfig" ALTER COLUMN "jitProvisioning" SET DEFAULT false;
99
+ END IF;
100
+ END $$;
101
+
102
+ -- ============================================================
103
+ -- 4. TABLES (audit history tables + role_configs + LlmPricing)
104
+ -- ============================================================
105
+
106
+ -- SystemConfigHistory / AgentPromptHistory / WorkspaceAgentPromptHistory
107
+ -- These were added in 20260903000005_add_config_history. If that migration
108
+ -- silently failed and was marked applied by Prisma, recreate idempotently.
109
+ CREATE TABLE IF NOT EXISTS "SystemConfigHistory" (
110
+ "id" TEXT NOT NULL,
111
+ "configKey" TEXT NOT NULL,
112
+ "category" TEXT NOT NULL,
113
+ "fieldKey" TEXT NOT NULL,
114
+ "oldValue" TEXT,
115
+ "newValue" TEXT,
116
+ "action" TEXT NOT NULL,
117
+ "sensitive" BOOLEAN NOT NULL DEFAULT false,
118
+ "adminId" TEXT,
119
+ "adminEmail" TEXT,
120
+ "changedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
121
+ CONSTRAINT "SystemConfigHistory_pkey" PRIMARY KEY ("id")
122
+ );
123
+ CREATE INDEX IF NOT EXISTS "SystemConfigHistory_configKey_changedAt_idx" ON "SystemConfigHistory"("configKey", "changedAt" DESC);
124
+ CREATE INDEX IF NOT EXISTS "SystemConfigHistory_category_changedAt_idx" ON "SystemConfigHistory"("category", "changedAt" DESC);
125
+ CREATE INDEX IF NOT EXISTS "SystemConfigHistory_adminId_idx" ON "SystemConfigHistory"("adminId");
126
+
127
+ CREATE TABLE IF NOT EXISTS "AgentPromptHistory" (
128
+ "id" TEXT NOT NULL,
129
+ "agentPromptId" TEXT NOT NULL,
130
+ "agentKey" TEXT NOT NULL,
131
+ "passKey" TEXT,
132
+ "oldBody" TEXT NOT NULL,
133
+ "newBody" TEXT NOT NULL,
134
+ "version" INTEGER NOT NULL,
135
+ "adminId" TEXT,
136
+ "adminEmail" TEXT,
137
+ "changedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
138
+ CONSTRAINT "AgentPromptHistory_pkey" PRIMARY KEY ("id")
139
+ );
140
+ CREATE INDEX IF NOT EXISTS "AgentPromptHistory_agentPromptId_changedAt_idx" ON "AgentPromptHistory"("agentPromptId", "changedAt" DESC);
141
+ CREATE INDEX IF NOT EXISTS "AgentPromptHistory_agentKey_changedAt_idx" ON "AgentPromptHistory"("agentKey", "changedAt" DESC);
142
+
143
+ CREATE TABLE IF NOT EXISTS "WorkspaceAgentPromptHistory" (
144
+ "id" TEXT NOT NULL,
145
+ "workspaceAgentPromptId" TEXT,
146
+ "tenantId" TEXT NOT NULL,
147
+ "agentKey" TEXT NOT NULL,
148
+ "passKey" TEXT,
149
+ "oldBody" TEXT,
150
+ "newBody" TEXT,
151
+ "action" TEXT NOT NULL,
152
+ "updatedBy" TEXT,
153
+ "changedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
154
+ CONSTRAINT "WorkspaceAgentPromptHistory_pkey" PRIMARY KEY ("id")
155
+ );
156
+ CREATE INDEX IF NOT EXISTS "WorkspaceAgentPromptHistory_tenantId_agentKey_changedAt_idx" ON "WorkspaceAgentPromptHistory"("tenantId", "agentKey", "changedAt" DESC);
157
+ CREATE INDEX IF NOT EXISTS "WorkspaceAgentPromptHistory_workspaceAgentPromptId_changedAt_idx" ON "WorkspaceAgentPromptHistory"("workspaceAgentPromptId", "changedAt" DESC);
158
+
159
+ -- role_configs: create with correct schema.
160
+ -- Drop and recreate if table is missing OR has wrong columns (displayName/description
161
+ -- were added by an earlier broken manual attempt on prod).
162
+ DO $$ BEGIN
163
+ IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'role_configs')
164
+ OR EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'role_configs' AND column_name = 'displayName')
165
+ OR EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'role_configs' AND column_name = 'description') THEN
166
+ DROP TABLE IF EXISTS "role_configs";
167
+ CREATE TABLE "role_configs" (
168
+ "roleName" TEXT NOT NULL,
169
+ "rank" INTEGER NOT NULL,
170
+ "permissions" TEXT[],
171
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
172
+ CONSTRAINT "role_configs_pkey" PRIMARY KEY ("roleName")
173
+ );
174
+ END IF;
175
+ END $$;
176
+
177
+ CREATE TABLE IF NOT EXISTS "LlmPricing" (
178
+ "id" TEXT NOT NULL,
179
+ "modelKey" TEXT NOT NULL,
180
+ "inputPer1M" DOUBLE PRECISION NOT NULL,
181
+ "outputPer1M" DOUBLE PRECISION NOT NULL,
182
+ "cacheReadPer1M" DOUBLE PRECISION NOT NULL DEFAULT 0,
183
+ "cacheWritePer1M" DOUBLE PRECISION NOT NULL DEFAULT 0,
184
+ "effectiveDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
185
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
186
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT NOW(),
187
+ CONSTRAINT "LlmPricing_pkey" PRIMARY KEY ("id")
188
+ );
189
+
190
+ -- ============================================================
191
+ -- 5. INDEXES
192
+ -- ============================================================
193
+
194
+ DROP INDEX IF EXISTS "KnowledgeChunk_tsv_idx";
195
+ DROP INDEX IF EXISTS "PlatformJob_status_heartbeatAt_idx";
196
+ DROP INDEX IF EXISTS "Screen_tenantId_projectId_discoveryMethod_idx";
197
+ DROP INDEX IF EXISTS "ScreenGroup_tenantId_projectId_crawlJobId_idx";
198
+
199
+ CREATE UNIQUE INDEX IF NOT EXISTS "role_configs_rank_key" ON "role_configs"("rank");
200
+ CREATE UNIQUE INDEX IF NOT EXISTS "LlmPricing_modelKey_key" ON "LlmPricing"("modelKey");
201
+ CREATE INDEX IF NOT EXISTS "LlmPricing_modelKey_idx" ON "LlmPricing"("modelKey");
202
+ CREATE INDEX IF NOT EXISTS "AuditLog_tenantId_action_idx" ON "AuditLog"("tenantId", "action");
203
+ CREATE INDEX IF NOT EXISTS "Membership_tenantId_idx" ON "Membership"("tenantId");
204
+ CREATE INDEX IF NOT EXISTS "Membership_userId_idx" ON "Membership"("userId");
205
+ CREATE INDEX IF NOT EXISTS "MfaRecoveryCode_userId_idx" ON "MfaRecoveryCode"("userId");
206
+ CREATE INDEX IF NOT EXISTS "PlatformJob_tenantId_projectId_queuedAt_idx" ON "PlatformJob"("tenantId", "projectId", "queuedAt");
207
+ CREATE INDEX IF NOT EXISTS "PlatformJob_status_workerId_idx" ON "PlatformJob"("status", "workerId");
208
+ CREATE INDEX IF NOT EXISTS "Screen_status_idx" ON "Screen"("status");
209
+ CREATE INDEX IF NOT EXISTS "TestCase_status_projectId_idx" ON "TestCase"("status", "projectId");
210
+ CREATE INDEX IF NOT EXISTS "TestCaseRun_tenantId_status_createdAt_idx" ON "TestCaseRun"("tenantId", "status", "createdAt");
211
+ CREATE INDEX IF NOT EXISTS "TestResult_tenantId_runId_status_idx" ON "TestResult"("tenantId", "runId", "status");
212
+ CREATE INDEX IF NOT EXISTS "WebhookDelivery_webhookId_status_idx" ON "WebhookDelivery"("webhookId", "status");
213
+
214
+ -- ============================================================
215
+ -- 6. FOREIGN KEYS (all guarded with IF NOT EXISTS + orphan cleanup)
216
+ -- ============================================================
217
+
218
+ -- Clean up orphaned AuditLog.actorId before adding FK
219
+ UPDATE "AuditLog" SET "actorId" = NULL
220
+ WHERE "actorId" IS NOT NULL
221
+ AND "actorId" NOT IN (SELECT id FROM "User");
222
+
223
+ DO $$ BEGIN
224
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'AuditLog_actorId_fkey') THEN
225
+ ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_actorId_fkey"
226
+ FOREIGN KEY ("actorId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
227
+ END IF;
228
+ END $$;
229
+ DO $$ BEGIN
230
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'AuditLog_tenantId_fkey') THEN
231
+ ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_tenantId_fkey"
232
+ FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
233
+ END IF;
234
+ END $$;
235
+ DO $$ BEGIN
236
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'AccessReview_tenantId_fkey') THEN
237
+ ALTER TABLE "AccessReview" ADD CONSTRAINT "AccessReview_tenantId_fkey"
238
+ FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
239
+ END IF;
240
+ END $$;
241
+ DO $$ BEGIN
242
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'Screen_projectId_fkey') THEN
243
+ ALTER TABLE "Screen" ADD CONSTRAINT "Screen_projectId_fkey"
244
+ FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
245
+ END IF;
246
+ END $$;
247
+ DO $$ BEGIN
248
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenA11yViolation_projectId_fkey') THEN
249
+ ALTER TABLE "ScreenA11yViolation" ADD CONSTRAINT "ScreenA11yViolation_projectId_fkey"
250
+ FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
251
+ END IF;
252
+ END $$;
253
+ DO $$ BEGIN
254
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenA11yViolation_screenId_fkey') THEN
255
+ ALTER TABLE "ScreenA11yViolation" ADD CONSTRAINT "ScreenA11yViolation_screenId_fkey"
256
+ FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE CASCADE ON UPDATE CASCADE;
257
+ END IF;
258
+ END $$;
259
+ DO $$ BEGIN
260
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'Element_screenId_fkey') THEN
261
+ ALTER TABLE "Element" ADD CONSTRAINT "Element_screenId_fkey"
262
+ FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE SET NULL ON UPDATE CASCADE;
263
+ END IF;
264
+ END $$;
265
+ DO $$ BEGIN
266
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestSuite_projectId_fkey') THEN
267
+ ALTER TABLE "TestSuite" ADD CONSTRAINT "TestSuite_projectId_fkey"
268
+ FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
269
+ END IF;
270
+ END $$;
271
+ DO $$ BEGIN
272
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestCase_dataSetId_fkey') THEN
273
+ ALTER TABLE "TestCase" ADD CONSTRAINT "TestCase_dataSetId_fkey"
274
+ FOREIGN KEY ("dataSetId") REFERENCES "TestDataSet"("id") ON DELETE SET NULL ON UPDATE CASCADE;
275
+ END IF;
276
+ END $$;
277
+ DO $$ BEGIN
278
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestRunShard_runId_fkey') THEN
279
+ ALTER TABLE "TestRunShard" ADD CONSTRAINT "TestRunShard_runId_fkey"
280
+ FOREIGN KEY ("runId") REFERENCES "TestRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
281
+ END IF;
282
+ END $$;
283
+ DO $$ BEGIN
284
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'SsoConfig_tenantId_fkey') THEN
285
+ ALTER TABLE "SsoConfig" ADD CONSTRAINT "SsoConfig_tenantId_fkey"
286
+ FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
287
+ END IF;
288
+ END $$;
289
+ DO $$ BEGIN
290
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenDiff_screenId_fkey') THEN
291
+ ALTER TABLE "ScreenDiff" ADD CONSTRAINT "ScreenDiff_screenId_fkey"
292
+ FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
293
+ END IF;
294
+ END $$;
295
+ DO $$ BEGIN
296
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ProbeObservation_screenId_fkey') THEN
297
+ ALTER TABLE "ProbeObservation" ADD CONSTRAINT "ProbeObservation_screenId_fkey"
298
+ FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
299
+ END IF;
300
+ END $$;
301
+
302
+ -- ============================================================
303
+ -- 7. SEED DATA — role_configs default permissions
304
+ -- ON CONFLICT DO NOTHING so admin customizations are preserved.
305
+ -- ============================================================
306
+
307
+ INSERT INTO "role_configs" ("roleName", "rank", "permissions", "updatedAt") VALUES
308
+ ('VIEWER', 0, ARRAY['project:read','connector:read','audit:read','analytics:read'], NOW()),
309
+ ('DEVELOPER', 1, ARRAY['project:read','crawl:run','test:generate','test:run','run:execute','connector:read','extension:write','mcp:access'], NOW()),
310
+ ('TESTER', 2, ARRAY['project:read','crawl:run','test:generate','test:review','test:run','run:execute','run:cancel','run:share','connector:read','extension:write','analytics:read','report:send','coverage:export'], NOW()),
311
+ ('QA_LEAD', 3, ARRAY['project:create','project:read','project:write','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','visual:manage','skill-file:approve','member:invite','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','mcp:access'], NOW()),
312
+ ('ADMIN', 4, ARRAY['project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','mcp:access','workspace:session-manage'], NOW()),
313
+ ('OWNER', 5, ARRAY['workspace:manage','project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','billing:manage','mcp:access','workspace:session-manage'], NOW())
314
+ ON CONFLICT ("roleName") DO NOTHING;
@@ -0,0 +1,22 @@
1
+ -- Fix role_configs rows that have empty permissions arrays (from the broken initial seed).
2
+ -- The catchup migration (20260908000001) used ON CONFLICT DO NOTHING, leaving existing
3
+ -- rows with permissions = '{}' untouched. This migration updates them to canonical defaults.
4
+ -- Rows with non-empty permissions are left alone (preserves admin customizations).
5
+
6
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','connector:read','audit:read','analytics:read'], "updatedAt" = NOW()
7
+ WHERE "roleName" = 'VIEWER' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
8
+
9
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','crawl:run','test:generate','test:run','run:execute','connector:read','extension:write','mcp:access'], "updatedAt" = NOW()
10
+ WHERE "roleName" = 'DEVELOPER' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
11
+
12
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','crawl:run','test:generate','test:review','test:run','run:execute','run:cancel','run:share','connector:read','extension:write','analytics:read','report:send','coverage:export'], "updatedAt" = NOW()
13
+ WHERE "roleName" = 'TESTER' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
14
+
15
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:create','project:read','project:write','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','visual:manage','skill-file:approve','member:invite','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','mcp:access'], "updatedAt" = NOW()
16
+ WHERE "roleName" = 'QA_LEAD' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
17
+
18
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','mcp:access','workspace:session-manage'], "updatedAt" = NOW()
19
+ WHERE "roleName" = 'ADMIN' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
20
+
21
+ UPDATE "role_configs" SET "permissions" = ARRAY['workspace:manage','project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','billing:manage','mcp:access','workspace:session-manage'], "updatedAt" = NOW()
22
+ WHERE "roleName" = 'OWNER' AND (array_length("permissions", 1) IS NULL OR array_length("permissions", 1) = 0);
@@ -0,0 +1,38 @@
1
+ -- Unconditionally sync all built-in role_configs to canonical permission sets.
2
+ -- Previous migration (20260908000002) only fixed EMPTY arrays; rows that had
3
+ -- a non-empty but STALE permissions array (missing permissions added to the
4
+ -- codebase after the row was created) were left untouched, causing 403s for
5
+ -- permissions like artifact:delete that are defined in ROLE_PERMISSIONS but
6
+ -- absent from the DB row.
7
+ --
8
+ -- Built-in roles (OWNER/ADMIN/QA_LEAD/TESTER/DEVELOPER/VIEWER) are always
9
+ -- kept in sync with the canonical list. Only custom roles are admin-managed.
10
+
11
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','connector:read','audit:read','analytics:read'], "updatedAt" = NOW()
12
+ WHERE "roleName" = 'VIEWER';
13
+
14
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','crawl:run','test:generate','test:review','test:run','run:execute','run:cancel','run:share','connector:read','extension:write','analytics:read','report:send','coverage:export'], "updatedAt" = NOW()
15
+ WHERE "roleName" = 'TESTER';
16
+
17
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:read','crawl:run','test:generate','test:run','run:execute','connector:read','extension:write','mcp:access'], "updatedAt" = NOW()
18
+ WHERE "roleName" = 'DEVELOPER';
19
+
20
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:create','project:read','project:write','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','visual:manage','skill-file:approve','member:invite','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','mcp:access'], "updatedAt" = NOW()
21
+ WHERE "roleName" = 'QA_LEAD';
22
+
23
+ UPDATE "role_configs" SET "permissions" = ARRAY['project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','mcp:access','workspace:session-manage'], "updatedAt" = NOW()
24
+ WHERE "roleName" = 'ADMIN';
25
+
26
+ UPDATE "role_configs" SET "permissions" = ARRAY['workspace:manage','project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','billing:manage','mcp:access','workspace:session-manage'], "updatedAt" = NOW()
27
+ WHERE "roleName" = 'OWNER';
28
+
29
+ -- Insert any missing built-in roles (in case the initial seed never ran)
30
+ INSERT INTO "role_configs" ("roleName", "rank", "permissions", "updatedAt")
31
+ VALUES
32
+ ('VIEWER', 1, ARRAY['project:read','connector:read','audit:read','analytics:read'], NOW()),
33
+ ('TESTER', 2, ARRAY['project:read','crawl:run','test:generate','test:review','test:run','run:execute','run:cancel','run:share','connector:read','extension:write','analytics:read','report:send','coverage:export'], NOW()),
34
+ ('DEVELOPER', 2, ARRAY['project:read','crawl:run','test:generate','test:run','run:execute','connector:read','extension:write','mcp:access'], NOW()),
35
+ ('QA_LEAD', 3, ARRAY['project:create','project:read','project:write','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','visual:manage','skill-file:approve','member:invite','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','mcp:access'], NOW()),
36
+ ('ADMIN', 4, ARRAY['project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','mcp:access','workspace:session-manage'], NOW()),
37
+ ('OWNER', 5, ARRAY['workspace:manage','project:create','project:read','project:write','project:delete','crawl:run','test:generate','test:review','test:run','test:delete','run:execute','run:cancel','run:schedule','run:share','connector:manage','connector:read','audit:read','visual:manage','skill-file:approve','member:invite','member:manage','extension:write','analytics:read','report:send','coverage:export','coverage:remap','performance:alert','artifact:delete','import:manage','token-usage:read','billing:read','billing:manage','mcp:access','workspace:session-manage'], NOW())
38
+ ON CONFLICT ("roleName") DO NOTHING;
@@ -0,0 +1,5 @@
1
+ -- Add filePath column to TestScript.
2
+ -- This column was added to schema.prisma in commit 2ee9eed9 without a corresponding
3
+ -- migration, causing Prisma to SELECT a non-existent column → 500 on any query
4
+ -- that includes TestScript (e.g. listTestCases with scripts: true).
5
+ ALTER TABLE "TestScript" ADD COLUMN IF NOT EXISTS "filePath" TEXT;
@@ -0,0 +1,31 @@
1
+ -- CreateTable: AlignmentResult
2
+ -- Dedicated table for AI alignment verification results.
3
+ -- Replaces the previous hack of storing JSON blobs in TestScript rows
4
+ -- with a synthetic language suffix (e.g. 'playwright-ts:alignment').
5
+
6
+ CREATE TABLE "AlignmentResult" (
7
+ "id" TEXT NOT NULL,
8
+ "tenantId" TEXT NOT NULL,
9
+ "projectId" TEXT NOT NULL,
10
+ "testCaseId" TEXT NOT NULL,
11
+ "language" TEXT NOT NULL,
12
+ "score" INTEGER NOT NULL DEFAULT 0,
13
+ "aligned" BOOLEAN NOT NULL DEFAULT false,
14
+ "issues" JSONB NOT NULL DEFAULT '[]',
15
+ "verifiedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
18
+
19
+ CONSTRAINT "AlignmentResult_pkey" PRIMARY KEY ("id")
20
+ );
21
+
22
+ CREATE UNIQUE INDEX "AlignmentResult_testCaseId_language_key" ON "AlignmentResult"("testCaseId", "language");
23
+ CREATE INDEX "AlignmentResult_tenantId_projectId_idx" ON "AlignmentResult"("tenantId", "projectId");
24
+ CREATE INDEX "AlignmentResult_tenantId_testCaseId_idx" ON "AlignmentResult"("tenantId", "testCaseId");
25
+
26
+ -- Apply Row-Level Security (same pattern as all other tenant tables).
27
+ ALTER TABLE "AlignmentResult" ENABLE ROW LEVEL SECURITY;
28
+ ALTER TABLE "AlignmentResult" FORCE ROW LEVEL SECURITY;
29
+ CREATE POLICY tenant_isolation ON "AlignmentResult"
30
+ USING ("tenantId" = current_setting('app.current_tenant', true))
31
+ WITH CHECK ("tenantId" = current_setting('app.current_tenant', true));
@@ -0,0 +1,35 @@
1
+ ALTER TABLE "AdminUser"
2
+ ADD COLUMN "mfaEnabled" BOOLEAN NOT NULL DEFAULT false,
3
+ ADD COLUMN "mfaSecret_enc" TEXT,
4
+ ADD COLUMN "mfaVerifiedAt" TIMESTAMP(3);
5
+
6
+ CREATE TABLE "AdminMfaRecoveryCode" (
7
+ "id" TEXT NOT NULL,
8
+ "adminId" TEXT NOT NULL,
9
+ "codeHash" TEXT NOT NULL,
10
+ "usedAt" TIMESTAMP(3),
11
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ CONSTRAINT "AdminMfaRecoveryCode_pkey" PRIMARY KEY ("id")
13
+ );
14
+
15
+ CREATE TABLE "AdminMfaChallenge" (
16
+ "id" TEXT NOT NULL,
17
+ "adminId" TEXT NOT NULL,
18
+ "purpose" TEXT NOT NULL,
19
+ "expiresAt" TIMESTAMP(3) NOT NULL,
20
+ "usedAt" TIMESTAMP(3),
21
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
22
+ CONSTRAINT "AdminMfaChallenge_pkey" PRIMARY KEY ("id")
23
+ );
24
+
25
+ CREATE INDEX "AdminMfaRecoveryCode_adminId_idx" ON "AdminMfaRecoveryCode"("adminId");
26
+ CREATE INDEX "AdminMfaChallenge_adminId_purpose_usedAt_idx" ON "AdminMfaChallenge"("adminId", "purpose", "usedAt");
27
+ CREATE INDEX "AdminMfaChallenge_expiresAt_idx" ON "AdminMfaChallenge"("expiresAt");
28
+
29
+ ALTER TABLE "AdminMfaRecoveryCode"
30
+ ADD CONSTRAINT "AdminMfaRecoveryCode_adminId_fkey"
31
+ FOREIGN KEY ("adminId") REFERENCES "AdminUser"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32
+
33
+ ALTER TABLE "AdminMfaChallenge"
34
+ ADD CONSTRAINT "AdminMfaChallenge_adminId_fkey"
35
+ FOREIGN KEY ("adminId") REFERENCES "AdminUser"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,2 @@
1
+ -- AlterTable
2
+ ALTER TABLE "Screen" ADD COLUMN "domSnapshotCloudUrl" TEXT;
@@ -0,0 +1 @@
1
+ ALTER TABLE "Screen" ADD COLUMN "requiresAuth" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "Screen"
2
+ ADD COLUMN IF NOT EXISTS "requiresAuth" BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,17 @@
1
+ CREATE TABLE "AdminAuthSession" (
2
+ "id" TEXT NOT NULL,
3
+ "adminId" TEXT NOT NULL,
4
+ "userAgent" TEXT,
5
+ "ipAddress" TEXT,
6
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
+ "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+ "revokedAt" TIMESTAMP(3),
9
+ CONSTRAINT "AdminAuthSession_pkey" PRIMARY KEY ("id")
10
+ );
11
+
12
+ CREATE INDEX "AdminAuthSession_adminId_idx" ON "AdminAuthSession"("adminId");
13
+ CREATE INDEX "AdminAuthSession_lastSeenAt_idx" ON "AdminAuthSession"("lastSeenAt");
14
+
15
+ ALTER TABLE "AdminAuthSession"
16
+ ADD CONSTRAINT "AdminAuthSession_adminId_fkey"
17
+ FOREIGN KEY ("adminId") REFERENCES "AdminUser"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1 @@
1
+ ALTER TABLE "AdminUser" ADD COLUMN "lastLoginAt" TIMESTAMP(3);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "AdminUser" ADD COLUMN "failedLoginCount" INTEGER NOT NULL DEFAULT 0;
2
+ ALTER TABLE "AdminUser" ADD COLUMN "lastFailedLoginAt" TIMESTAMP(3);
@@ -0,0 +1,6 @@
1
+ -- Sentinel tenant for platform-admin audit events that have no tenant context
2
+ -- (e.g. admin.login / admin.login.failed). AuditLog.tenantId is NOT NULL with
3
+ -- an enforced FK, so a real Tenant row is required. Idempotent.
4
+ INSERT INTO "Tenant" (id, name, slug, isolation, region)
5
+ VALUES ('system', 'Platform (System)', 'system', 'POOL', 'GLOBAL')
6
+ ON CONFLICT (id) DO NOTHING;
@@ -0,0 +1,63 @@
1
+ -- ============================================================================
2
+ -- Comprehensive permissions fix
3
+ --
4
+ -- Gaps fixed:
5
+ -- 1. ALTER DEFAULT PRIVILEGES was missing FOR ROLE zeta_admin — tables created
6
+ -- by zeta_admin in migrations after 0002 did not auto-grant to qip_app.
7
+ -- This caused 42501 on Notification, ScreenGroup, WebhookDelivery, etc.
8
+ --
9
+ -- 2. Eight tables have ENABLE ROW LEVEL SECURITY but not FORCE ROW LEVEL
10
+ -- SECURITY, allowing the table owner (zeta_admin / migration role) to
11
+ -- bypass RLS silently. Consistent with all other tenant-scoped tables.
12
+ --
13
+ -- 3. Re-grants all existing tables and sequences to qip_app (catchup for
14
+ -- any table created after the initial 0002 grant ran without default privs).
15
+ -- ============================================================================
16
+
17
+ -- ---- 1. Fix DEFAULT PRIVILEGES so future tables auto-grant to qip_app ------
18
+ -- Without FOR ROLE the grant only applies to the role that ran migration 0002,
19
+ -- not to zeta_admin which creates all schema objects in production migrations.
20
+ DO $$
21
+ BEGIN
22
+ -- Set default privileges for zeta_admin-created objects going forward.
23
+ -- Idempotent: repeated runs are harmless.
24
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'zeta_admin') THEN
25
+ EXECUTE $q$ALTER DEFAULT PRIVILEGES FOR ROLE zeta_admin IN SCHEMA public
26
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO qip_app$q$;
27
+ EXECUTE $q$ALTER DEFAULT PRIVILEGES FOR ROLE zeta_admin IN SCHEMA public
28
+ GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO qip_app$q$;
29
+ END IF;
30
+ END $$;
31
+
32
+ -- ---- 2. Catchup grant: all tables and sequences that exist right now --------
33
+ GRANT USAGE ON SCHEMA public TO qip_app;
34
+ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO qip_app;
35
+ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO qip_app;
36
+
37
+ -- ---- 3. Add FORCE ROW LEVEL SECURITY to tables missing it ------------------
38
+ -- These tables have RLS enabled but not FORCE, letting the owner bypass policies.
39
+ DO $$
40
+ DECLARE
41
+ t text;
42
+ tables text[] := ARRAY[
43
+ 'TenantSetting',
44
+ 'AgentToken',
45
+ 'Webhook',
46
+ 'ProjectArtifact',
47
+ 'KnowledgeChunk',
48
+ 'Invitation',
49
+ 'ProjectMember',
50
+ 'CustomRole'
51
+ ];
52
+ BEGIN
53
+ FOREACH t IN ARRAY tables LOOP
54
+ -- Only apply if table exists and RLS is already enabled
55
+ IF EXISTS (
56
+ SELECT 1 FROM pg_class c
57
+ JOIN pg_namespace n ON n.oid = c.relnamespace
58
+ WHERE c.relname = t AND n.nspname = 'public' AND c.relrowsecurity = true
59
+ ) THEN
60
+ EXECUTE format('ALTER TABLE "%s" FORCE ROW LEVEL SECURITY;', t);
61
+ END IF;
62
+ END LOOP;
63
+ END $$;
@@ -0,0 +1,2 @@
1
+ -- Add roleAccess column to Screen for multi-role crawl tracking
2
+ ALTER TABLE "Screen" ADD COLUMN IF NOT EXISTS "roleAccess" TEXT[] NOT NULL DEFAULT '{}';
@@ -0,0 +1,41 @@
1
+ -- Navigation Knowledge Graph: Phase 1 schema additions
2
+
3
+ -- Screen: add crawl depth and parent URL for graph traversal
4
+ ALTER TABLE "Screen" ADD COLUMN IF NOT EXISTS "depth" INTEGER;
5
+ ALTER TABLE "Screen" ADD COLUMN IF NOT EXISTS "parentUrl" TEXT;
6
+
7
+ -- DiscoveredLink: hyperlink edges between screens discovered during crawl
8
+ CREATE TABLE IF NOT EXISTS "DiscoveredLink" (
9
+ "id" TEXT NOT NULL,
10
+ "tenantId" TEXT NOT NULL,
11
+ "projectId" TEXT NOT NULL,
12
+ "fromScreenId" TEXT NOT NULL,
13
+ "toScreenId" TEXT,
14
+ "href" TEXT NOT NULL,
15
+ "text" TEXT,
16
+ "landmark" TEXT,
17
+ "linkType" TEXT NOT NULL DEFAULT 'NAV_LINK',
18
+ "source" TEXT NOT NULL DEFAULT 'DOM',
19
+ "confidence" DOUBLE PRECISION NOT NULL DEFAULT 1.0,
20
+ "jobId" TEXT,
21
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
22
+
23
+ CONSTRAINT "DiscoveredLink_pkey" PRIMARY KEY ("id")
24
+ );
25
+
26
+ -- Foreign keys
27
+ ALTER TABLE "DiscoveredLink" ADD CONSTRAINT "DiscoveredLink_projectId_fkey"
28
+ FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
29
+
30
+ ALTER TABLE "DiscoveredLink" ADD CONSTRAINT "DiscoveredLink_fromScreenId_fkey"
31
+ FOREIGN KEY ("fromScreenId") REFERENCES "Screen"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32
+
33
+ ALTER TABLE "DiscoveredLink" ADD CONSTRAINT "DiscoveredLink_toScreenId_fkey"
34
+ FOREIGN KEY ("toScreenId") REFERENCES "Screen"("id") ON DELETE SET NULL ON UPDATE CASCADE;
35
+
36
+ -- Indexes
37
+ CREATE INDEX IF NOT EXISTS "DiscoveredLink_tenantId_projectId_idx" ON "DiscoveredLink"("tenantId", "projectId");
38
+ CREATE INDEX IF NOT EXISTS "DiscoveredLink_tenantId_projectId_jobId_idx" ON "DiscoveredLink"("tenantId", "projectId", "jobId");
39
+ CREATE INDEX IF NOT EXISTS "DiscoveredLink_fromScreenId_idx" ON "DiscoveredLink"("fromScreenId");
40
+ CREATE INDEX IF NOT EXISTS "DiscoveredLink_toScreenId_idx" ON "DiscoveredLink"("toScreenId");
41
+ CREATE INDEX IF NOT EXISTS "DiscoveredLink_href_idx" ON "DiscoveredLink"("href");