@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,32 @@
1
+ -- Webhook dispatch was fire-and-forget with zero persisted history: a failed
2
+ -- delivery left no trace anywhere except a best-effort server log line. This
3
+ -- table records one row per delivery attempt (dispatch retries on failure,
4
+ -- so a single logical event can produce several rows sharing the same
5
+ -- event/payload with increasing `attempt`), and stores the payload so a
6
+ -- failed delivery can be manually redelivered later with the exact original
7
+ -- body rather than an approximation.
8
+
9
+ CREATE TABLE "WebhookDelivery" (
10
+ "id" TEXT NOT NULL,
11
+ "webhookId" TEXT NOT NULL,
12
+ "tenantId" TEXT NOT NULL,
13
+ "event" TEXT NOT NULL,
14
+ "payload" JSONB NOT NULL,
15
+ "attempt" INTEGER NOT NULL DEFAULT 1,
16
+ "status" TEXT NOT NULL,
17
+ "httpStatus" INTEGER,
18
+ "error" TEXT,
19
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
+
21
+ CONSTRAINT "WebhookDelivery_pkey" PRIMARY KEY ("id"),
22
+ CONSTRAINT "WebhookDelivery_webhookId_fkey" FOREIGN KEY ("webhookId") REFERENCES "Webhook"("id") ON DELETE CASCADE ON UPDATE CASCADE
23
+ );
24
+
25
+ CREATE INDEX "WebhookDelivery_webhookId_createdAt_idx" ON "WebhookDelivery"("webhookId", "createdAt");
26
+
27
+ ALTER TABLE "WebhookDelivery" ENABLE ROW LEVEL SECURITY;
28
+ ALTER TABLE "WebhookDelivery" FORCE ROW LEVEL SECURITY;
29
+
30
+ CREATE POLICY tenant_isolation ON "WebhookDelivery"
31
+ USING ("tenantId" = current_setting('app.current_tenant', true))
32
+ WITH CHECK ("tenantId" = current_setting('app.current_tenant', true));
@@ -0,0 +1,23 @@
1
+ -- Per-tenant feature-flag overrides. Absence of a row for a given
2
+ -- (tenantId, key) means "use the platform default" (FEATURE_FLAG_DEFAULTS in
3
+ -- @detiq/core) — this table only stores explicit deviations from that default.
4
+
5
+ CREATE TABLE "TenantFeatureFlag" (
6
+ "id" TEXT NOT NULL,
7
+ "tenantId" TEXT NOT NULL,
8
+ "key" TEXT NOT NULL,
9
+ "enabled" BOOLEAN NOT NULL,
10
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+
12
+ CONSTRAINT "TenantFeatureFlag_pkey" PRIMARY KEY ("id"),
13
+ CONSTRAINT "TenantFeatureFlag_tenantId_key_key" UNIQUE ("tenantId", "key")
14
+ );
15
+
16
+ CREATE INDEX "TenantFeatureFlag_tenantId_idx" ON "TenantFeatureFlag"("tenantId");
17
+
18
+ ALTER TABLE "TenantFeatureFlag" ENABLE ROW LEVEL SECURITY;
19
+ ALTER TABLE "TenantFeatureFlag" FORCE ROW LEVEL SECURITY;
20
+
21
+ CREATE POLICY tenant_isolation ON "TenantFeatureFlag"
22
+ USING ("tenantId" = current_setting('app.current_tenant', true))
23
+ WITH CHECK ("tenantId" = current_setting('app.current_tenant', true));
@@ -0,0 +1,30 @@
1
+ -- Real, revocable login sessions. Auth was pure stateless JWT with no way to
2
+ -- see who was logged in or kill a compromised/lost-device session short of
3
+ -- rotating JWT_SECRET (which logs out every user on the platform). The row's
4
+ -- id IS the JWT's `jti` claim — resolveContext looks this up (via prismaAdmin,
5
+ -- same reasoning as AgentToken: tenant context isn't established yet at the
6
+ -- point this lookup happens) and rejects the request if it's missing or
7
+ -- revoked.
8
+
9
+ CREATE TABLE "AuthSession" (
10
+ "id" TEXT NOT NULL,
11
+ "userId" TEXT NOT NULL,
12
+ "tenantId" TEXT NOT NULL,
13
+ "userAgent" TEXT,
14
+ "ipAddress" TEXT,
15
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
17
+ "revokedAt" TIMESTAMP(3),
18
+
19
+ CONSTRAINT "AuthSession_pkey" PRIMARY KEY ("id")
20
+ );
21
+
22
+ CREATE INDEX "AuthSession_userId_idx" ON "AuthSession"("userId");
23
+ CREATE INDEX "AuthSession_tenantId_idx" ON "AuthSession"("tenantId");
24
+
25
+ ALTER TABLE "AuthSession" ENABLE ROW LEVEL SECURITY;
26
+ ALTER TABLE "AuthSession" FORCE ROW LEVEL SECURITY;
27
+
28
+ CREATE POLICY tenant_isolation ON "AuthSession"
29
+ USING ("tenantId" = current_setting('app.current_tenant', true))
30
+ WITH CHECK ("tenantId" = current_setting('app.current_tenant', true));
@@ -0,0 +1,51 @@
1
+ -- ============================================================================
2
+ -- FIX: three earlier migrations (0011_oauth_connections, 20260626110000_
3
+ -- screen_groups, 20260630140000_perf_env_comments) wrote RLS policies that
4
+ -- check current_setting('app.tenant_id', true) — but withTenant() in
5
+ -- @detiq/database has only ever set 'app.current_tenant' (every other policy
6
+ -- in this schema uses that name). 'app.tenant_id' is never set by anything,
7
+ -- so current_setting(...) always returns NULL there, and NULL = "tenantId"
8
+ -- is always NULL (falsy). Net effect since the day each of these tables was
9
+ -- created: every tenant-scoped SELECT/UPDATE/DELETE against them silently
10
+ -- returns/affects zero rows, and every INSERT fails with "new row violates
11
+ -- row-level security policy" (Postgres falls back to the USING expression
12
+ -- as the implicit WITH CHECK when none is given) — found by live-testing
13
+ -- the newly-registered environments.ts routes against a real database,
14
+ -- not by reading the code.
15
+ --
16
+ -- Affected tables: OAuthConnection, ScreenGroup, PerformanceSnapshot,
17
+ -- ProjectEnvironment, Comment.
18
+ --
19
+ -- Idempotent: DROP POLICY IF EXISTS + guarded CREATE, safe to run on a
20
+ -- database where a previous partial attempt already fixed some of these.
21
+ -- ============================================================================
22
+
23
+ DO $$
24
+ DECLARE
25
+ fix record;
26
+ BEGIN
27
+ FOR fix IN
28
+ SELECT * FROM (VALUES
29
+ ('OAuthConnection', 'tenant_isolation_oauth_connections'),
30
+ ('ScreenGroup', 'tenant_isolation'),
31
+ ('PerformanceSnapshot','tenant_isolation_perf'),
32
+ ('ProjectEnvironment', 'tenant_isolation_env'),
33
+ ('Comment', 'tenant_isolation_comment')
34
+ ) AS t(table_name, policy_name)
35
+ LOOP
36
+ IF to_regclass(format('"%s"', fix.table_name)) IS NULL THEN
37
+ CONTINUE; -- defensive; all five are created by earlier migrations that must have already run
38
+ END IF;
39
+
40
+ EXECUTE format('DROP POLICY IF EXISTS "%s" ON "%s";', fix.policy_name, fix.table_name);
41
+ EXECUTE format($f$
42
+ CREATE POLICY "%s" ON "%s"
43
+ USING ("tenantId" = current_setting('app.current_tenant', true))
44
+ WITH CHECK ("tenantId" = current_setting('app.current_tenant', true));
45
+ $f$, fix.policy_name, fix.table_name);
46
+
47
+ -- FORCE wasn't set by any of the three original migrations either — add
48
+ -- it for consistency with every RLS table created later this session.
49
+ EXECUTE format('ALTER TABLE "%s" FORCE ROW LEVEL SECURITY;', fix.table_name);
50
+ END LOOP;
51
+ END $$;
@@ -0,0 +1,14 @@
1
+ -- Flaky-test rolling score + quarantine (docs/13-TEST-FLAKINESS-DETECTION.md
2
+ -- "Future Work"). Rolling stats are recomputed by
3
+ -- AppBrain.recordFlakinessOutcome() after each TestCaseRun; quarantine blocks
4
+ -- POST /test-cases/:id/run (409) unless force:true is passed, and never
5
+ -- self-clears — lifting it always goes through PATCH /test-cases/:id/quarantine.
6
+
7
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "flakyScore" DOUBLE PRECISION NOT NULL DEFAULT 0;
8
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "consecutiveFlakes" INTEGER NOT NULL DEFAULT 0;
9
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "lastFlakyAt" TIMESTAMP(3);
10
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "quarantined" BOOLEAN NOT NULL DEFAULT false;
11
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "quarantinedAt" TIMESTAMP(3);
12
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "quarantineReason" TEXT;
13
+
14
+ CREATE INDEX IF NOT EXISTS "TestCase_tenantId_projectId_quarantined_idx" ON "TestCase"("tenantId", "projectId", "quarantined");
@@ -0,0 +1,6 @@
1
+ -- Records which Playwright engine (chromium|firefox|webkit) a TestCaseRun
2
+ -- used. executeTestPlan() has always supported all 3 engines via its
3
+ -- `browser` option; nothing ever threaded a caller-chosen value into it or
4
+ -- persisted the choice — see docs/16-ENTERPRISE-FEATURE-GAPS.md section 9.
5
+
6
+ ALTER TABLE "TestCaseRun" ADD COLUMN IF NOT EXISTS "browser" TEXT NOT NULL DEFAULT 'chromium';
@@ -0,0 +1,22 @@
1
+ -- Migration: allow_custom_roles
2
+ --
3
+ -- 20260702180000_add_invitation_model added a NOT VALID CHECK constraint
4
+ -- pinning Membership.role and Invitation.role to the 6 built-in role names.
5
+ -- 20260703000000_custom_roles then introduced the CustomRole model and a full
6
+ -- CRUD API + UI for tenant-defined roles, but never revisited these
7
+ -- constraints — so every attempt to assign a custom role (by id or name) has
8
+ -- been rejected at the database layer with a check-constraint violation,
9
+ -- regardless of what the application layer validates. This made the entire
10
+ -- custom-role feature unusable end-to-end since the day it was built: a
11
+ -- tenant could create a custom role and see it in every dropdown, but never
12
+ -- actually assign it to anyone.
13
+ --
14
+ -- Custom role values are per-tenant CustomRole.id/name — arbitrary strings a
15
+ -- CHECK constraint can't express (Postgres CHECK can't reference another
16
+ -- table). Dropping these two constraints brings the database in line with
17
+ -- what schema.prisma already declares (Membership.role / Invitation.role are
18
+ -- plain, unconstrained String columns) and moves role-value validation to
19
+ -- where it can actually see CustomRole rows: the application layer
20
+ -- (isAssignableRole() in packages/api/src/routes/workspace.ts).
21
+ ALTER TABLE "Membership" DROP CONSTRAINT IF EXISTS "Membership_role_check";
22
+ ALTER TABLE "Invitation" DROP CONSTRAINT IF EXISTS "Invitation_role_check";
@@ -0,0 +1,8 @@
1
+ -- Markdown/text extraction from crawled pages (crawl4AI gap-closure #2 of 5).
2
+ -- Mirrors the existing domSnapshotPath pattern: the extracted markdown is
3
+ -- written to a file alongside the DOM snapshot, and this column just points
4
+ -- at it. markdownMethod records whether Readability found an article
5
+ -- ("readability") or the whole-page fallback was used ("full-page").
6
+ ALTER TABLE "Screen" ADD COLUMN "markdownPath" TEXT;
7
+ ALTER TABLE "Screen" ADD COLUMN "markdownMethod" TEXT;
8
+ ALTER TABLE "Screen" ADD COLUMN "markdownWordCount" INTEGER;
@@ -0,0 +1,4 @@
1
+ -- AlterTable (columns already added in 20260702120000_medium_priority_gaps — no-op)
2
+ ALTER TABLE "PerformanceSnapshot" ADD COLUMN IF NOT EXISTS "lighthouseA11y" DOUBLE PRECISION,
3
+ ADD COLUMN IF NOT EXISTS "lighthousePerf" DOUBLE PRECISION,
4
+ ADD COLUMN IF NOT EXISTS "lighthouseSEO" DOUBLE PRECISION;
@@ -0,0 +1,5 @@
1
+ -- lighthouseBP already added by 20260702120000_medium_priority_gaps — no-op
2
+ ALTER TABLE "PerformanceSnapshot" ADD COLUMN IF NOT EXISTS "lighthouseBP" DOUBLE PRECISION;
3
+
4
+ -- Add composite index for fast job listing sorted by queuedAt
5
+ CREATE INDEX IF NOT EXISTS "PlatformJob_tenantId_projectId_queuedAt_idx" ON "PlatformJob"("tenantId", "projectId", "queuedAt" DESC);
@@ -0,0 +1,2 @@
1
+ ALTER TABLE "VisualDiff" ADD COLUMN "aiSummary" TEXT;
2
+ ALTER TABLE "Screen" ADD COLUMN "aiSummary" TEXT;
@@ -0,0 +1,25 @@
1
+ CREATE TABLE IF NOT EXISTS "ScriptBundle" (
2
+ "id" TEXT NOT NULL,
3
+ "tenantId" TEXT NOT NULL,
4
+ "projectId" TEXT NOT NULL,
5
+ "scopeType" TEXT NOT NULL,
6
+ "scopeId" TEXT NOT NULL,
7
+ "language" TEXT NOT NULL DEFAULT 'playwright-ts',
8
+ "files" JSONB NOT NULL,
9
+ "basePage" TEXT,
10
+ "caseCount" INTEGER NOT NULL DEFAULT 0,
11
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ "updatedAt" TIMESTAMP(3) NOT NULL,
13
+
14
+ CONSTRAINT "ScriptBundle_pkey" PRIMARY KEY ("id"),
15
+ CONSTRAINT "ScriptBundle_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE
16
+ );
17
+
18
+ CREATE UNIQUE INDEX IF NOT EXISTS "ScriptBundle_tenantId_projectId_scopeType_scopeId_language_key"
19
+ ON "ScriptBundle"("tenantId", "projectId", "scopeType", "scopeId", "language");
20
+
21
+ CREATE INDEX IF NOT EXISTS "ScriptBundle_tenantId_projectId_idx"
22
+ ON "ScriptBundle"("tenantId", "projectId");
23
+
24
+ CREATE INDEX IF NOT EXISTS "ScriptBundle_tenantId_projectId_scopeType_scopeId_idx"
25
+ ON "ScriptBundle"("tenantId", "projectId", "scopeType", "scopeId");
@@ -0,0 +1,4 @@
1
+ ALTER TABLE "Requirement" ADD COLUMN "sourceSection" TEXT;
2
+
3
+ CREATE INDEX "Requirement_tenantId_projectId_sourceSection_idx"
4
+ ON "Requirement"("tenantId", "projectId", "sourceSection");
@@ -0,0 +1,8 @@
1
+ ALTER TABLE "SkillFile" ADD COLUMN "structuredData" JSONB NOT NULL DEFAULT '{}';
2
+ ALTER TABLE "SkillFile" ADD COLUMN "provenance" JSONB NOT NULL DEFAULT '[]';
3
+ ALTER TABLE "SkillFile" ADD COLUMN "confidence" DOUBLE PRECISION;
4
+ ALTER TABLE "SkillFile" ADD COLUMN "validationStatus" TEXT NOT NULL DEFAULT 'UNKNOWN';
5
+ ALTER TABLE "SkillFile" ADD COLUMN "validationSummary" TEXT;
6
+ ALTER TABLE "SkillFile" ADD COLUMN "staleStatus" TEXT NOT NULL DEFAULT 'CURRENT';
7
+ ALTER TABLE "SkillFile" ADD COLUMN "staleReason" TEXT;
8
+ ALTER TABLE "SkillFile" ADD COLUMN "generatorVersion" TEXT;
@@ -0,0 +1,112 @@
1
+ CREATE TABLE IF NOT EXISTS "ScreenTransition" (
2
+ "id" TEXT NOT NULL,
3
+ "tenantId" TEXT NOT NULL,
4
+ "projectId" TEXT NOT NULL,
5
+ "fromScreenId" TEXT NOT NULL,
6
+ "toScreenId" TEXT,
7
+ "action" JSONB NOT NULL,
8
+ "confidence" DOUBLE PRECISION NOT NULL DEFAULT 0.5,
9
+ "jobId" TEXT,
10
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+ CONSTRAINT "ScreenTransition_pkey" PRIMARY KEY ("id")
12
+ );
13
+
14
+ CREATE TABLE IF NOT EXISTS "CrawlEvidence" (
15
+ "id" TEXT NOT NULL,
16
+ "tenantId" TEXT NOT NULL,
17
+ "projectId" TEXT NOT NULL,
18
+ "screenId" TEXT,
19
+ "jobId" TEXT,
20
+ "type" TEXT NOT NULL,
21
+ "payload" JSONB NOT NULL,
22
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23
+ CONSTRAINT "CrawlEvidence_pkey" PRIMARY KEY ("id")
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS "ActionRecipe" (
27
+ "id" TEXT NOT NULL,
28
+ "tenantId" TEXT NOT NULL,
29
+ "projectId" TEXT NOT NULL,
30
+ "domain" TEXT NOT NULL,
31
+ "purpose" TEXT NOT NULL,
32
+ "fingerprint" TEXT NOT NULL,
33
+ "steps" JSONB NOT NULL,
34
+ "successRate" DOUBLE PRECISION NOT NULL DEFAULT 0,
35
+ "lastUsedAt" TIMESTAMP(3),
36
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
38
+ CONSTRAINT "ActionRecipe_pkey" PRIMARY KEY ("id")
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS "ApiEndpointObservation" (
42
+ "id" TEXT NOT NULL,
43
+ "tenantId" TEXT NOT NULL,
44
+ "projectId" TEXT NOT NULL,
45
+ "screenId" TEXT,
46
+ "method" TEXT NOT NULL,
47
+ "urlPattern" TEXT NOT NULL,
48
+ "statusCode" INTEGER,
49
+ "requestKind" TEXT,
50
+ "responseShape" JSONB,
51
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
52
+ CONSTRAINT "ApiEndpointObservation_pkey" PRIMARY KEY ("id")
53
+ );
54
+
55
+ DO $$
56
+ BEGIN
57
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenTransition_projectId_fkey') THEN
58
+ ALTER TABLE "ScreenTransition" ADD CONSTRAINT "ScreenTransition_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
59
+ END IF;
60
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenTransition_fromScreenId_fkey') THEN
61
+ ALTER TABLE "ScreenTransition" ADD CONSTRAINT "ScreenTransition_fromScreenId_fkey" FOREIGN KEY ("fromScreenId") REFERENCES "Screen"("id") ON DELETE CASCADE ON UPDATE CASCADE;
62
+ END IF;
63
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenTransition_toScreenId_fkey') THEN
64
+ ALTER TABLE "ScreenTransition" ADD CONSTRAINT "ScreenTransition_toScreenId_fkey" FOREIGN KEY ("toScreenId") REFERENCES "Screen"("id") ON DELETE SET NULL ON UPDATE CASCADE;
65
+ END IF;
66
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CrawlEvidence_projectId_fkey') THEN
67
+ ALTER TABLE "CrawlEvidence" ADD CONSTRAINT "CrawlEvidence_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
68
+ END IF;
69
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'CrawlEvidence_screenId_fkey') THEN
70
+ ALTER TABLE "CrawlEvidence" ADD CONSTRAINT "CrawlEvidence_screenId_fkey" FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE CASCADE ON UPDATE CASCADE;
71
+ END IF;
72
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ActionRecipe_projectId_fkey') THEN
73
+ ALTER TABLE "ActionRecipe" ADD CONSTRAINT "ActionRecipe_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74
+ END IF;
75
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ApiEndpointObservation_projectId_fkey') THEN
76
+ ALTER TABLE "ApiEndpointObservation" ADD CONSTRAINT "ApiEndpointObservation_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
77
+ END IF;
78
+ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ApiEndpointObservation_screenId_fkey') THEN
79
+ ALTER TABLE "ApiEndpointObservation" ADD CONSTRAINT "ApiEndpointObservation_screenId_fkey" FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE CASCADE ON UPDATE CASCADE;
80
+ END IF;
81
+ END $$;
82
+
83
+ CREATE UNIQUE INDEX IF NOT EXISTS "ActionRecipe_tenantId_projectId_purpose_fingerprint_key" ON "ActionRecipe"("tenantId", "projectId", "purpose", "fingerprint");
84
+ CREATE INDEX IF NOT EXISTS "ScreenTransition_tenantId_projectId_idx" ON "ScreenTransition"("tenantId", "projectId");
85
+ CREATE INDEX IF NOT EXISTS "ScreenTransition_tenantId_projectId_jobId_idx" ON "ScreenTransition"("tenantId", "projectId", "jobId");
86
+ CREATE INDEX IF NOT EXISTS "ScreenTransition_fromScreenId_idx" ON "ScreenTransition"("fromScreenId");
87
+ CREATE INDEX IF NOT EXISTS "ScreenTransition_toScreenId_idx" ON "ScreenTransition"("toScreenId");
88
+ CREATE INDEX IF NOT EXISTS "CrawlEvidence_tenantId_projectId_idx" ON "CrawlEvidence"("tenantId", "projectId");
89
+ CREATE INDEX IF NOT EXISTS "CrawlEvidence_tenantId_projectId_jobId_idx" ON "CrawlEvidence"("tenantId", "projectId", "jobId");
90
+ CREATE INDEX IF NOT EXISTS "CrawlEvidence_screenId_idx" ON "CrawlEvidence"("screenId");
91
+ CREATE INDEX IF NOT EXISTS "CrawlEvidence_type_idx" ON "CrawlEvidence"("type");
92
+ CREATE INDEX IF NOT EXISTS "ActionRecipe_tenantId_projectId_idx" ON "ActionRecipe"("tenantId", "projectId");
93
+ CREATE INDEX IF NOT EXISTS "ActionRecipe_tenantId_projectId_domain_idx" ON "ActionRecipe"("tenantId", "projectId", "domain");
94
+ CREATE INDEX IF NOT EXISTS "ApiEndpointObservation_tenantId_projectId_idx" ON "ApiEndpointObservation"("tenantId", "projectId");
95
+ CREATE INDEX IF NOT EXISTS "ApiEndpointObservation_tenantId_projectId_method_idx" ON "ApiEndpointObservation"("tenantId", "projectId", "method");
96
+ CREATE INDEX IF NOT EXISTS "ApiEndpointObservation_screenId_idx" ON "ApiEndpointObservation"("screenId");
97
+
98
+ DO $$
99
+ DECLARE
100
+ t text;
101
+ BEGIN
102
+ FOREACH t IN ARRAY ARRAY['ScreenTransition', 'CrawlEvidence', 'ActionRecipe', 'ApiEndpointObservation']
103
+ LOOP
104
+ EXECUTE format('ALTER TABLE "%s" ENABLE ROW LEVEL SECURITY;', t);
105
+ EXECUTE format('ALTER TABLE "%s" FORCE ROW LEVEL SECURITY;', t);
106
+ EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON "%s";', t);
107
+ EXECUTE format(
108
+ 'CREATE POLICY tenant_isolation ON "%s" USING ("tenantId" = current_setting(''app.current_tenant'', true)) WITH CHECK ("tenantId" = current_setting(''app.current_tenant'', true));',
109
+ t
110
+ );
111
+ END LOOP;
112
+ END $$;
@@ -0,0 +1,9 @@
1
+ -- Add crawlJobId, createdAt, updatedAt to ScreenGroup for version tracking.
2
+ -- This lets the UI show when groups were last regenerated and by which crawl job.
3
+ ALTER TABLE "ScreenGroup"
4
+ ADD COLUMN IF NOT EXISTS "crawlJobId" TEXT,
5
+ ADD COLUMN IF NOT EXISTS "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
6
+ ADD COLUMN IF NOT EXISTS "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
7
+
8
+ CREATE INDEX IF NOT EXISTS "ScreenGroup_tenantId_projectId_crawlJobId_idx"
9
+ ON "ScreenGroup"("tenantId", "projectId", "crawlJobId");
@@ -0,0 +1,24 @@
1
+ -- Add MFA fields to User
2
+ ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "mfaEnabled" BOOLEAN NOT NULL DEFAULT false;
3
+ ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "mfaSecret_enc" TEXT;
4
+ ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "mfaVerifiedAt" TIMESTAMP(3);
5
+
6
+ -- Create MfaRecoveryCode table
7
+ CREATE TABLE IF NOT EXISTS "MfaRecoveryCode" (
8
+ "id" TEXT NOT NULL,
9
+ "userId" TEXT NOT NULL,
10
+ "codeHash" TEXT NOT NULL,
11
+ "usedAt" TIMESTAMP(3),
12
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
13
+ CONSTRAINT "MfaRecoveryCode_pkey" PRIMARY KEY ("id")
14
+ );
15
+
16
+ -- Foreign key
17
+ DO $$ BEGIN
18
+ IF NOT EXISTS (
19
+ SELECT 1 FROM pg_constraint WHERE conname = 'MfaRecoveryCode_userId_fkey'
20
+ ) THEN
21
+ ALTER TABLE "MfaRecoveryCode" ADD CONSTRAINT "MfaRecoveryCode_userId_fkey"
22
+ FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
23
+ END IF;
24
+ END $$;
@@ -0,0 +1,158 @@
1
+ -- ScreenA11yViolation
2
+ CREATE TABLE IF NOT EXISTS "ScreenA11yViolation" (
3
+ "id" TEXT NOT NULL,
4
+ "tenantId" TEXT NOT NULL,
5
+ "projectId" TEXT NOT NULL,
6
+ "screenId" TEXT NOT NULL,
7
+ "runId" TEXT,
8
+ "impact" TEXT NOT NULL,
9
+ "rule" TEXT NOT NULL,
10
+ "description" TEXT NOT NULL,
11
+ "nodeHtml" TEXT,
12
+ "wcagTags" JSONB NOT NULL DEFAULT '[]',
13
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
14
+ CONSTRAINT "ScreenA11yViolation_pkey" PRIMARY KEY ("id")
15
+ );
16
+ CREATE INDEX IF NOT EXISTS "ScreenA11yViolation_tenantId_projectId_idx" ON "ScreenA11yViolation"("tenantId", "projectId");
17
+ CREATE INDEX IF NOT EXISTS "ScreenA11yViolation_screenId_idx" ON "ScreenA11yViolation"("screenId");
18
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenA11yViolation_projectId_fkey') THEN ALTER TABLE "ScreenA11yViolation" ADD CONSTRAINT "ScreenA11yViolation_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE; END IF; END $$;
19
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'ScreenA11yViolation_screenId_fkey') THEN ALTER TABLE "ScreenA11yViolation" ADD CONSTRAINT "ScreenA11yViolation_screenId_fkey" FOREIGN KEY ("screenId") REFERENCES "Screen"("id") ON DELETE CASCADE; END IF; END $$;
20
+
21
+ -- TestRunShard
22
+ CREATE TABLE IF NOT EXISTS "TestRunShard" (
23
+ "id" TEXT NOT NULL,
24
+ "runId" TEXT NOT NULL,
25
+ "tenantId" TEXT NOT NULL,
26
+ "shardIndex" INTEGER NOT NULL,
27
+ "browser" TEXT NOT NULL DEFAULT 'chromium',
28
+ "status" TEXT NOT NULL DEFAULT 'PENDING',
29
+ "summary" JSONB,
30
+ "startedAt" TIMESTAMP(3),
31
+ "finishedAt" TIMESTAMP(3),
32
+ CONSTRAINT "TestRunShard_pkey" PRIMARY KEY ("id"),
33
+ CONSTRAINT "TestRunShard_runId_shardIndex_browser_key" UNIQUE ("runId", "shardIndex", "browser")
34
+ );
35
+ CREATE INDEX IF NOT EXISTS "TestRunShard_runId_idx" ON "TestRunShard"("runId");
36
+ CREATE INDEX IF NOT EXISTS "TestRunShard_tenantId_idx" ON "TestRunShard"("tenantId");
37
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestRunShard_runId_fkey') THEN ALTER TABLE "TestRunShard" ADD CONSTRAINT "TestRunShard_runId_fkey" FOREIGN KEY ("runId") REFERENCES "TestRun"("id") ON DELETE CASCADE; END IF; END $$;
38
+
39
+ -- SsoConfig
40
+ CREATE TABLE IF NOT EXISTS "SsoConfig" (
41
+ "id" TEXT NOT NULL,
42
+ "tenantId" TEXT NOT NULL,
43
+ "provider" TEXT NOT NULL,
44
+ "entryPoint" TEXT,
45
+ "issuer" TEXT,
46
+ "cert" TEXT,
47
+ "clientId" TEXT,
48
+ "clientSecret_enc" TEXT,
49
+ "defaultRole" TEXT NOT NULL DEFAULT 'TESTER',
50
+ "jitProvisioning" BOOLEAN NOT NULL DEFAULT true,
51
+ "enabled" BOOLEAN NOT NULL DEFAULT false,
52
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
53
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
54
+ CONSTRAINT "SsoConfig_pkey" PRIMARY KEY ("id"),
55
+ CONSTRAINT "SsoConfig_tenantId_key" UNIQUE ("tenantId")
56
+ );
57
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'SsoConfig_tenantId_fkey') THEN ALTER TABLE "SsoConfig" ADD CONSTRAINT "SsoConfig_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
58
+
59
+ -- AccessReview
60
+ CREATE TABLE IF NOT EXISTS "AccessReview" (
61
+ "id" TEXT NOT NULL,
62
+ "tenantId" TEXT NOT NULL,
63
+ "reviewedBy" TEXT NOT NULL,
64
+ "memberCount" INTEGER NOT NULL,
65
+ "removedCount" INTEGER NOT NULL DEFAULT 0,
66
+ "completedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
67
+ CONSTRAINT "AccessReview_pkey" PRIMARY KEY ("id")
68
+ );
69
+ CREATE INDEX IF NOT EXISTS "AccessReview_tenantId_idx" ON "AccessReview"("tenantId");
70
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'AccessReview_tenantId_fkey') THEN ALTER TABLE "AccessReview" ADD CONSTRAINT "AccessReview_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
71
+
72
+ -- TenantPlan
73
+ CREATE TABLE IF NOT EXISTS "TenantPlan" (
74
+ "tenantId" TEXT NOT NULL,
75
+ "tier" TEXT NOT NULL DEFAULT 'TRIAL',
76
+ "stripeCustomerId" TEXT,
77
+ "stripeSubscriptionId" TEXT,
78
+ "trialEndsAt" TIMESTAMP(3),
79
+ "maxProjects" INTEGER NOT NULL DEFAULT 3,
80
+ "maxMembers" INTEGER NOT NULL DEFAULT 5,
81
+ "maxCrawlsPerMonth" INTEGER NOT NULL DEFAULT 10,
82
+ "maxRunsPerMonth" INTEGER NOT NULL DEFAULT 50,
83
+ "maxShards" INTEGER NOT NULL DEFAULT 1,
84
+ "maxTokensPerMonth" INTEGER NOT NULL DEFAULT 500000,
85
+ "currentCrawls" INTEGER NOT NULL DEFAULT 0,
86
+ "currentRuns" INTEGER NOT NULL DEFAULT 0,
87
+ "lastResetAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
88
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
89
+ CONSTRAINT "TenantPlan_pkey" PRIMARY KEY ("tenantId")
90
+ );
91
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TenantPlan_tenantId_fkey') THEN ALTER TABLE "TenantPlan" ADD CONSTRAINT "TenantPlan_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
92
+
93
+ -- SlackConnection
94
+ CREATE TABLE IF NOT EXISTS "SlackConnection" (
95
+ "id" TEXT NOT NULL,
96
+ "tenantId" TEXT NOT NULL,
97
+ "teamId" TEXT NOT NULL,
98
+ "teamName" TEXT NOT NULL,
99
+ "channelId" TEXT NOT NULL,
100
+ "channelName" TEXT NOT NULL,
101
+ "accessToken_enc" TEXT NOT NULL,
102
+ "installedBy" TEXT NOT NULL,
103
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
104
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
105
+ CONSTRAINT "SlackConnection_pkey" PRIMARY KEY ("id"),
106
+ CONSTRAINT "SlackConnection_tenantId_key" UNIQUE ("tenantId")
107
+ );
108
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'SlackConnection_tenantId_fkey') THEN ALTER TABLE "SlackConnection" ADD CONSTRAINT "SlackConnection_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
109
+
110
+ -- TeamsConnection
111
+ CREATE TABLE IF NOT EXISTS "TeamsConnection" (
112
+ "id" TEXT NOT NULL,
113
+ "tenantId" TEXT NOT NULL,
114
+ "webhookUrl" TEXT NOT NULL,
115
+ "channelName" TEXT,
116
+ "installedBy" TEXT NOT NULL,
117
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
118
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
119
+ CONSTRAINT "TeamsConnection_pkey" PRIMARY KEY ("id"),
120
+ CONSTRAINT "TeamsConnection_tenantId_key" UNIQUE ("tenantId")
121
+ );
122
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TeamsConnection_tenantId_fkey') THEN ALTER TABLE "TeamsConnection" ADD CONSTRAINT "TeamsConnection_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
123
+
124
+ -- TenantSiloConfig
125
+ CREATE TABLE IF NOT EXISTS "TenantSiloConfig" (
126
+ "tenantId" TEXT NOT NULL,
127
+ "connectionString_enc" TEXT NOT NULL,
128
+ "encryptionKeyRef" TEXT NOT NULL DEFAULT 'default',
129
+ "provisionedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
130
+ CONSTRAINT "TenantSiloConfig_pkey" PRIMARY KEY ("tenantId")
131
+ );
132
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TenantSiloConfig_tenantId_fkey') THEN ALTER TABLE "TenantSiloConfig" ADD CONSTRAINT "TenantSiloConfig_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE; END IF; END $$;
133
+
134
+ -- TestDataSet
135
+ CREATE TABLE IF NOT EXISTS "TestDataSet" (
136
+ "id" TEXT NOT NULL,
137
+ "tenantId" TEXT NOT NULL,
138
+ "projectId" TEXT NOT NULL,
139
+ "name" TEXT NOT NULL,
140
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
141
+ CONSTRAINT "TestDataSet_pkey" PRIMARY KEY ("id")
142
+ );
143
+ CREATE INDEX IF NOT EXISTS "TestDataSet_tenantId_projectId_idx" ON "TestDataSet"("tenantId", "projectId");
144
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestDataSet_projectId_fkey') THEN ALTER TABLE "TestDataSet" ADD CONSTRAINT "TestDataSet_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE; END IF; END $$;
145
+
146
+ -- TestDataRow
147
+ CREATE TABLE IF NOT EXISTS "TestDataRow" (
148
+ "id" TEXT NOT NULL,
149
+ "dataSetId" TEXT NOT NULL,
150
+ "rowIndex" INTEGER NOT NULL,
151
+ "data" JSONB NOT NULL,
152
+ CONSTRAINT "TestDataRow_pkey" PRIMARY KEY ("id"),
153
+ CONSTRAINT "TestDataRow_dataSetId_rowIndex_key" UNIQUE ("dataSetId", "rowIndex")
154
+ );
155
+ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestDataRow_dataSetId_fkey') THEN ALTER TABLE "TestDataRow" ADD CONSTRAINT "TestDataRow_dataSetId_fkey" FOREIGN KEY ("dataSetId") REFERENCES "TestDataSet"("id") ON DELETE CASCADE; END IF; END $$;
156
+
157
+ -- TestCase → TestDataSet FK (if column exists but FK missing)
158
+ DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='TestCase' AND column_name='dataSetId') AND NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'TestCase_dataSetId_fkey') THEN ALTER TABLE "TestCase" ADD CONSTRAINT "TestCase_dataSetId_fkey" FOREIGN KEY ("dataSetId") REFERENCES "TestDataSet"("id") ON DELETE SET NULL; END IF; END $$;
@@ -0,0 +1,15 @@
1
+ -- TenantSetting: add missing columns
2
+ ALTER TABLE "TenantSetting" ADD COLUMN IF NOT EXISTS "dataRetentionDays" INTEGER NOT NULL DEFAULT 365;
3
+ ALTER TABLE "TenantSetting" ADD COLUMN IF NOT EXISTS "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
4
+
5
+ -- TenantFeatureFlag: create if missing
6
+ CREATE TABLE IF NOT EXISTS "TenantFeatureFlag" (
7
+ "id" TEXT NOT NULL,
8
+ "tenantId" TEXT NOT NULL,
9
+ "key" TEXT NOT NULL,
10
+ "enabled" BOOLEAN NOT NULL,
11
+ "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ CONSTRAINT "TenantFeatureFlag_pkey" PRIMARY KEY ("id"),
13
+ CONSTRAINT "TenantFeatureFlag_tenantId_key_key" UNIQUE ("tenantId", "key")
14
+ );
15
+ CREATE INDEX IF NOT EXISTS "TenantFeatureFlag_tenantId_idx" ON "TenantFeatureFlag"("tenantId");
@@ -0,0 +1 @@
1
+ ALTER TABLE "TenantPlan" ADD COLUMN IF NOT EXISTS "maxSessions" INTEGER NOT NULL DEFAULT 2;
@@ -0,0 +1,15 @@
1
+ -- Comprehensive fix: add all columns present in schema.prisma but missing from DB.
2
+ -- All statements use IF NOT EXISTS / idempotent patterns — safe to re-run.
3
+
4
+ -- ── TestRun: parallel shard + CI/CD fields ────────────────────────────────────
5
+ ALTER TABLE "TestRun" ADD COLUMN IF NOT EXISTS "totalShards" INTEGER;
6
+ ALTER TABLE "TestRun" ADD COLUMN IF NOT EXISTS "githubRepo" TEXT;
7
+ ALTER TABLE "TestRun" ADD COLUMN IF NOT EXISTS "githubSha" TEXT;
8
+ ALTER TABLE "TestRun" ADD COLUMN IF NOT EXISTS "githubRef" TEXT;
9
+ ALTER TABLE "TestRun" ADD COLUMN IF NOT EXISTS "triggeredBy" TEXT;
10
+
11
+ -- ── TestResult: failure analysis + data-driven row tracking ──────────────────
12
+ ALTER TABLE "TestResult" ADD COLUMN IF NOT EXISTS "failureAnalysis" TEXT;
13
+ ALTER TABLE "TestResult" ADD COLUMN IF NOT EXISTS "failureAnalysisAt" TIMESTAMP(3);
14
+ ALTER TABLE "TestResult" ADD COLUMN IF NOT EXISTS "dataRowId" TEXT;
15
+ ALTER TABLE "TestResult" ADD COLUMN IF NOT EXISTS "dataRowIndex" INTEGER;
@@ -0,0 +1,16 @@
1
+ -- Add dataSetId column to TestCase (data-driven execution link).
2
+ -- Was missing from previous migrations despite being in schema.prisma.
3
+
4
+ ALTER TABLE "TestCase" ADD COLUMN IF NOT EXISTS "dataSetId" TEXT;
5
+
6
+ CREATE INDEX IF NOT EXISTS "TestCase_dataSetId_idx" ON "TestCase"("dataSetId");
7
+
8
+ DO $$ BEGIN
9
+ IF NOT EXISTS (
10
+ SELECT 1 FROM pg_constraint WHERE conname = 'TestCase_dataSetId_fkey'
11
+ ) THEN
12
+ ALTER TABLE "TestCase"
13
+ ADD CONSTRAINT "TestCase_dataSetId_fkey"
14
+ FOREIGN KEY ("dataSetId") REFERENCES "TestDataSet"("id") ON DELETE SET NULL;
15
+ END IF;
16
+ END $$;
@@ -0,0 +1 @@
1
+ ALTER TABLE "NativeAppBuild" ADD COLUMN IF NOT EXISTS "fileName" TEXT;
@@ -0,0 +1,6 @@
1
+ -- Add cloud storage URL fields to visual artifacts
2
+ -- VisualBaseline: cloudUrl stores public URL or S3 key for presigned URL generation
3
+ ALTER TABLE "VisualBaseline" ADD COLUMN IF NOT EXISTS "cloudUrl" TEXT;
4
+
5
+ -- VisualDiff: newScreenshotCloudUrl for the comparison screenshot
6
+ ALTER TABLE "VisualDiff" ADD COLUMN IF NOT EXISTS "newScreenshotCloudUrl" TEXT;