@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.
- package/README.md +21 -2
- package/dist/index.d.ts +58 -0
- package/dist/index.js +209 -0
- package/package.json +29 -4
- package/prisma/migrations/0000_init/migration.sql +220 -0
- package/prisma/migrations/0001_tenant_isolation/migration.sql +43 -0
- package/prisma/migrations/0002_app_runtime_role/migration.sql +14 -0
- package/prisma/migrations/0003_skill_taxonomy/migration.sql +30 -0
- package/prisma/migrations/0004_extended_models/migration.sql +215 -0
- package/prisma/migrations/0005_enhanced_test_artifacts/migration.sql +24 -0
- package/prisma/migrations/0006_tenant_settings/migration.sql +28 -0
- package/prisma/migrations/0007_project_slug/migration.sql +11 -0
- package/prisma/migrations/0008_project_crawl_config/migration.sql +3 -0
- package/prisma/migrations/0009_tenant_slug/migration.sql +11 -0
- package/prisma/migrations/0010_user_password/migration.sql +3 -0
- package/prisma/migrations/0011_oauth_connections/migration.sql +78 -0
- package/prisma/migrations/0012_agent_webhooks/migration.sql +46 -0
- package/prisma/migrations/0013_project_artifacts/migration.sql +27 -0
- package/prisma/migrations/0014_knowledge_chunks/migration.sql +37 -0
- package/prisma/migrations/20260626022056_platform_job_migration/migration.sql +44 -0
- package/prisma/migrations/20260626053339_kag_schema_update/migration.sql +27 -0
- package/prisma/migrations/20260626053340_restore_bm25_index/migration.sql +13 -0
- package/prisma/migrations/20260626063704_token_optimisation/migration.sql +86 -0
- package/prisma/migrations/20260626080000_job_name/migration.sql +2 -0
- package/prisma/migrations/20260626090000_rls_missing_tables/migration.sql +39 -0
- package/prisma/migrations/20260626100000_token_budget/migration.sql +21 -0
- package/prisma/migrations/20260626110000_screen_groups/migration.sql +42 -0
- package/prisma/migrations/20260626120000_artifact_intelligence/migration.sql +5 -0
- package/prisma/migrations/20260626130000_notifications/migration.sql +34 -0
- package/prisma/migrations/20260627100000_element_viewport/migration.sql +2 -0
- package/prisma/migrations/20260627100000_screenshot_cloud_url/migration.sql +2 -0
- package/prisma/migrations/20260627110000_screenshot_index/migration.sql +2 -0
- package/prisma/migrations/20260627140000_artifact_storage_key/migration.sql +3 -0
- package/prisma/migrations/20260627150000_screen_lifecycle/migration.sql +5 -0
- package/prisma/migrations/20260627160000_native_app_support/migration.sql +41 -0
- package/prisma/migrations/20260627200000_rls_missing_tables/migration.sql +57 -0
- package/prisma/migrations/20260627210000_llmcalllog_tasktype/migration.sql +1 -0
- package/prisma/migrations/20260628100000_element_enrichment/migration.sql +7 -0
- package/prisma/migrations/20260628110000_element_hash/migration.sql +6 -0
- package/prisma/migrations/20260628120000_screen_diff/migration.sql +20 -0
- package/prisma/migrations/20260628130000_probe_observations/migration.sql +19 -0
- package/prisma/migrations/20260629100000_visual_baseline_masking/migration.sql +1 -0
- package/prisma/migrations/20260629110000_testcase_viewports/migration.sql +1 -0
- package/prisma/migrations/20260630100000_visual_baseline_element_snapshot/migration.sql +1 -0
- package/prisma/migrations/20260630110000_git_branched_baselines/migration.sql +11 -0
- package/prisma/migrations/20260630120000_testcase_viewport_constraint/migration.sql +1 -0
- package/prisma/migrations/20260630130000_test_case_runs/migration.sql +22 -0
- package/prisma/migrations/20260630140000_perf_env_comments/migration.sql +68 -0
- package/prisma/migrations/20260630150000_testcase_gen_at/migration.sql +2 -0
- package/prisma/migrations/20260630160000_rls_catchup_late_tables/migration.sql +41 -0
- package/prisma/migrations/20260701100000_skill_file_diff_approval/migration.sql +9 -0
- package/prisma/migrations/20260701200000_workspace_connector/migration.sql +20 -0
- package/prisma/migrations/20260701220000_screen_a11y_security/migration.sql +2 -0
- package/prisma/migrations/20260702100000_ci_threshold_root_cause/migration.sql +2 -0
- package/prisma/migrations/20260702120000_medium_priority_gaps/migration.sql +28 -0
- package/prisma/migrations/20260702140000_testcase_v2_technique/migration.sql +7 -0
- package/prisma/migrations/20260702150000_add_app_field/migration.sql +40 -0
- package/prisma/migrations/20260702160000_add_requirement_model/migration.sql +62 -0
- package/prisma/migrations/20260702180000_add_invitation_model/migration.sql +53 -0
- package/prisma/migrations/20260702190000_suite_hierarchy_artifact_trace/migration.sql +28 -0
- package/prisma/migrations/20260702200000_project_member_access/migration.sql +31 -0
- package/prisma/migrations/20260703000000_add_system_config/migration.sql +11 -0
- package/prisma/migrations/20260703000000_custom_roles/migration.sql +29 -0
- package/prisma/migrations/20260703100000_platform_admin/migration.sql +11 -0
- package/prisma/migrations/20260703110000_add_extension_models/migration.sql +106 -0
- package/prisma/migrations/20260703120000_project_restricted_column/migration.sql +1 -0
- package/prisma/migrations/20260703200000_system_config_category_index/migration.sql +1 -0
- package/prisma/migrations/20260703210000_workspace_layer/migration.sql +24 -0
- package/prisma/migrations/20260703230000_workspace_agent_tokens/migration.sql +2 -0
- package/prisma/migrations/20260704000000_requirement_rls_policy/migration.sql +35 -0
- package/prisma/migrations/20260704100000_fix_oauth_auditlog_cascade/migration.sql +9 -0
- package/prisma/migrations/20260704110000_skillfile_approval_status/migration.sql +2 -0
- package/prisma/migrations/20260704120000_scenario_suspect_workspace/migration.sql +35 -0
- package/prisma/migrations/20260704130000_testcase_traceto_suspect_indexes/migration.sql +5 -0
- package/prisma/migrations/20260704140000_workspace_rls/migration.sql +19 -0
- package/prisma/migrations/20260704150000_project_cascade_deletes/migration.sql +125 -0
- package/prisma/migrations/20260705100000_platform_job_worker_id/migration.sql +12 -0
- package/prisma/migrations/20260705110000_screen_discovery_method/migration.sql +7 -0
- package/prisma/migrations/20260705120000_webhook_delivery/migration.sql +32 -0
- package/prisma/migrations/20260705130000_tenant_feature_flags/migration.sql +23 -0
- package/prisma/migrations/20260705140000_auth_sessions/migration.sql +30 -0
- package/prisma/migrations/20260705150000_fix_rls_wrong_session_var/migration.sql +51 -0
- package/prisma/migrations/20260705160000_testcase_flaky_quarantine/migration.sql +14 -0
- package/prisma/migrations/20260705170000_testcaserun_browser/migration.sql +6 -0
- package/prisma/migrations/20260705180000_allow_custom_roles/migration.sql +22 -0
- package/prisma/migrations/20260706050000_screen_markdown_extraction/migration.sql +8 -0
- package/prisma/migrations/20260706090000_add_lighthouse_to_performance_snapshot/migration.sql +4 -0
- package/prisma/migrations/20260706100000_add_lighthouse_bp_and_job_index/migration.sql +5 -0
- package/prisma/migrations/20260706110000_add_visual_diff_ai_summary/migration.sql +2 -0
- package/prisma/migrations/20260707120000_script_bundles/migration.sql +25 -0
- package/prisma/migrations/20260707130000_requirement_source_section/migration.sql +4 -0
- package/prisma/migrations/20260707140000_structured_skill_metadata/migration.sql +8 -0
- package/prisma/migrations/20260707150000_crawler_platform_primitives/migration.sql +112 -0
- package/prisma/migrations/20260707160000_screen_group_versioning/migration.sql +9 -0
- package/prisma/migrations/20260707170000_add_mfa_fields/migration.sql +24 -0
- package/prisma/migrations/20260707180000_add_missing_tables/migration.sql +158 -0
- package/prisma/migrations/20260707190000_add_missing_columns/migration.sql +15 -0
- package/prisma/migrations/20260707200000_add_plan_sessions/migration.sql +1 -0
- package/prisma/migrations/20260708100000_missing_columns_comprehensive/migration.sql +15 -0
- package/prisma/migrations/20260708200000_testcase_dataset_column/migration.sql +16 -0
- package/prisma/migrations/20260709100000_add_filename_to_native_app_build/migration.sql +1 -0
- package/prisma/migrations/20260709200000_cloud_url_visual_artifacts/migration.sql +6 -0
- package/prisma/migrations/20260714200000_add_cached_scenarios_to_artifact/migration.sql +4 -0
- package/prisma/migrations/20260715200000_screen_markdown_content/migration.sql +2 -0
- package/prisma/migrations/20260716100000_github_repo_coverage_tables/migration.sql +114 -0
- package/prisma/migrations/20260716200000_github_pr_script_validation/migration.sql +48 -0
- package/prisma/migrations/20260716210000_s3_content_keys/migration.sql +20 -0
- package/prisma/migrations/20260717220000_ide_push_token/migration.sql +21 -0
- package/prisma/migrations/20260717230000_ide_push_script_ids/migration.sql +2 -0
- package/prisma/migrations/20260730040000_add_step_evidence_and_validation/migration.sql +93 -0
- package/prisma/migrations/20260803000000_add_tenantId_to_test_data_row/migration.sql +40 -0
- package/prisma/migrations/20260804000000_add_project_restricted/migration.sql +6 -0
- package/prisma/migrations/20260804000000_add_user_avatar_url/migration.sql +4 -0
- package/prisma/migrations/20260805000000_add_auditlog_ipaddress/migration.sql +2 -0
- package/prisma/migrations/20260807000000_add_test_data_classification/migration.sql +18 -0
- package/prisma/migrations/20260807000001_add_test_case_notes/migration.sql +5 -0
- package/prisma/migrations/20260807000002_add_visual_baseline_browser_structured_steps/migration.sql +11 -0
- package/prisma/migrations/20260807100000_add_tenant_plan_config/migration.sql +14 -0
- package/prisma/migrations/20260808000000_agent_token_missing_columns/migration.sql +22 -0
- package/prisma/migrations/20260808000001_visual_diff_image_and_threshold/migration.sql +8 -0
- package/prisma/migrations/20260808000002_ssoconfig_email_domains/migration.sql +7 -0
- package/prisma/migrations/20260808000003_project_status_description_updatedat/migration.sql +7 -0
- package/prisma/migrations/20260814000000_workspace_require_mfa/migration.sql +2 -0
- package/prisma/migrations/20260814000001_add_screenshot_dpr/migration.sql +2 -0
- package/prisma/migrations/20260818000000_add_content_hash_to_test_case/migration.sql +6 -0
- package/prisma/migrations/20260818000001_add_grounding_hash_to_test_case/migration.sql +3 -0
- package/prisma/migrations/20260819000000_coverage_mapping_cross_encoder_fields/migration.sql +3 -0
- package/prisma/migrations/20260820000000_add_locators_json_to_test_case/migration.sql +6 -0
- package/prisma/migrations/20260820000001_add_test_case_version/migration.sql +27 -0
- package/prisma/migrations/20260826000000_add_capture_step_screenshots/migration.sql +1 -0
- package/prisma/migrations/20260903000000_add_platform_job_dlq/migration.sql +10 -0
- package/prisma/migrations/20260903000002_add_versioning_tables/migration.sql +38 -0
- package/prisma/migrations/20260903000003_add_learning_models/migration.sql +71 -0
- package/prisma/migrations/20260903000004_add_workspace_agent_prompts/migration.sql +33 -0
- package/prisma/migrations/20260903000005_add_config_history/migration.sql +70 -0
- package/prisma/migrations/20260903000006_add_skill_file_config/migration.sql +24 -0
- package/prisma/migrations/20260904000000_add_agent_prompt_platform_table/migration.sql +23 -0
- package/prisma/migrations/20260904180424_rbac_role_configs_admin_roles/migration.sql +606 -0
- package/prisma/migrations/20260907000000_fix_testcase_timestamps/migration.sql +13 -0
- package/prisma/migrations/20260907000001_add_artifact_imported_suite_id/migration.sql +1 -0
- package/prisma/migrations/20260907000002_add_user_login_tracking/migration.sql +9 -0
- package/prisma/migrations/20260907000003_add_artifact_import_tracking/migration.sql +9 -0
- package/prisma/migrations/20260907000004_add_artifact_version_chain/migration.sql +18 -0
- package/prisma/migrations/20260908000000_add_project_vsa_enabled/migration.sql +2 -0
- package/prisma/migrations/20260908000001_rbac_migration_catchup/migration.sql +314 -0
- package/prisma/migrations/20260908000002_fix_role_configs_empty_permissions/migration.sql +22 -0
- package/prisma/migrations/20260908000003_sync_builtin_role_permissions/migration.sql +38 -0
- package/prisma/migrations/20260909000000_add_filepath_to_testscript/migration.sql +5 -0
- package/prisma/migrations/20260909000001_add_alignment_result_table/migration.sql +31 -0
- package/prisma/migrations/20260910000000_add_admin_mfa/migration.sql +35 -0
- package/prisma/migrations/20260910000000_add_dom_snapshot_cloud_url/migration.sql +2 -0
- package/prisma/migrations/20260910000001_add_requires_auth_to_screen/migration.sql +1 -0
- package/prisma/migrations/20260910000002_add_screen_requires_auth/migration.sql +2 -0
- package/prisma/migrations/20260910000003_add_admin_auth_sessions/migration.sql +17 -0
- package/prisma/migrations/20260916000001_add_admin_last_login/migration.sql +1 -0
- package/prisma/migrations/20260916000002_add_admin_failed_login/migration.sql +2 -0
- package/prisma/migrations/20260916000003_seed_system_tenant/migration.sql +6 -0
- package/prisma/migrations/20260918000000_fix_permissions_comprehensive/migration.sql +63 -0
- package/prisma/migrations/20260924000000_add_screen_role_access/migration.sql +2 -0
- package/prisma/migrations/20260928000000_add_navigation_graph/migration.sql +41 -0
- package/prisma/migrations/20260929000000_artifact_content_hash_suite_unique/migration.sql +25 -0
- package/prisma/migrations/20260929000000_navigation_graph_unique_constraint/migration.sql +5 -0
- package/prisma/migrations/20260929000001_add_membership_status/migration.sql +7 -0
- package/prisma/migrations/20260929000002_add_sso_config_scim_token_hash/migration.sql +8 -0
- package/prisma/migrations/20261005000001_jobs_workers_hardening/migration.sql +63 -0
- package/prisma/migrations/20261009000001_add_suite_category/migration.sql +2 -0
- package/prisma/migrations/20261009000002_add_testcase_approved_fields/migration.sql +3 -0
- package/prisma/migrations/20261012_auto_version_history/migration.sql +111 -0
- package/prisma/migrations/20261015000001_traceability_alignment_gaps/migration.sql +11 -0
- package/prisma/migrations/20261028000001_context_intelligence_p1_p7/migration.sql +76 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +2901 -0
- package/prisma/seed.ts +1262 -0
|
@@ -0,0 +1,2901 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// THE DATA MODEL — "the sealed boxes"
|
|
3
|
+
// Tenant -> Workspace -> Project -> (Zeta Core, Screens, Test Suites...)
|
|
4
|
+
//
|
|
5
|
+
// Every record carries tenantId. Tenant isolation is ALSO enforced at the
|
|
6
|
+
// database layer via Row-Level Security (see migrations/0001_tenant_isolation).
|
|
7
|
+
// Never rely on application code alone to keep tenants apart.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
generator client {
|
|
11
|
+
provider = "prisma-client-js"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
datasource db {
|
|
15
|
+
provider = "postgresql"
|
|
16
|
+
url = env("DATABASE_URL")
|
|
17
|
+
directUrl = env("MIGRATION_DATABASE_URL")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---- Access layer -----------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
model Tenant {
|
|
23
|
+
id String @id @default(cuid())
|
|
24
|
+
name String
|
|
25
|
+
slug String @unique @default("")
|
|
26
|
+
// "POOL" = shared infra with row-level filtering. "SILO" = dedicated (Phase 5).
|
|
27
|
+
isolation String
|
|
28
|
+
region String
|
|
29
|
+
createdAt DateTime @default(now())
|
|
30
|
+
workspaces Workspace[]
|
|
31
|
+
projects Project[]
|
|
32
|
+
memberships Membership[]
|
|
33
|
+
tokenBudget TokenBudget?
|
|
34
|
+
notifications Notification[]
|
|
35
|
+
notificationPreferences NotificationPreference[]
|
|
36
|
+
invitations Invitation[]
|
|
37
|
+
customRoles CustomRole[]
|
|
38
|
+
auditLogs AuditLog[]
|
|
39
|
+
ssoConfig SsoConfig?
|
|
40
|
+
slackConnection SlackConnection?
|
|
41
|
+
teamsConnection TeamsConnection?
|
|
42
|
+
siloConfig TenantSiloConfig?
|
|
43
|
+
plan TenantPlan?
|
|
44
|
+
accessReviews AccessReview[]
|
|
45
|
+
workspaceAgentPrompts WorkspaceAgentPrompt[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
model Workspace {
|
|
49
|
+
id String @id @default(cuid())
|
|
50
|
+
tenantId String
|
|
51
|
+
name String
|
|
52
|
+
slug String
|
|
53
|
+
settings Json? // domain config: { vertical, requirementTypes, testTechniques }
|
|
54
|
+
requireMfa Boolean @default(false)
|
|
55
|
+
createdAt DateTime @default(now())
|
|
56
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
57
|
+
projects Project[]
|
|
58
|
+
|
|
59
|
+
@@unique([tenantId, slug])
|
|
60
|
+
@@index([tenantId])
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
model User {
|
|
64
|
+
id String @id @default(cuid())
|
|
65
|
+
email String @unique
|
|
66
|
+
name String?
|
|
67
|
+
avatarUrl String?
|
|
68
|
+
passwordHash String?
|
|
69
|
+
mfaEnabled Boolean @default(false)
|
|
70
|
+
mfaSecret_enc String? // AES-256-GCM encrypted TOTP secret
|
|
71
|
+
mfaVerifiedAt DateTime? // when user completed first TOTP verification
|
|
72
|
+
lastLoginAt DateTime? // updated on every successful login
|
|
73
|
+
failedLoginCount Int @default(0) // consecutive failures since last success; reset on login
|
|
74
|
+
lastFailedLoginAt DateTime? // timestamp of most recent failed attempt
|
|
75
|
+
createdAt DateTime @default(now())
|
|
76
|
+
memberships Membership[]
|
|
77
|
+
mfaRecoveryCodes MfaRecoveryCode[]
|
|
78
|
+
notifications Notification[]
|
|
79
|
+
notificationPreferences NotificationPreference[]
|
|
80
|
+
auditLogs AuditLog[]
|
|
81
|
+
stepValidations StepValidation[]
|
|
82
|
+
createdAgentTokens AgentToken[]
|
|
83
|
+
comments Comment[]
|
|
84
|
+
userContext UserContext?
|
|
85
|
+
learningJourney LearningJourney?
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Which user has which ROLE inside which tenant.
|
|
89
|
+
model Membership {
|
|
90
|
+
id String @id @default(cuid())
|
|
91
|
+
userId String
|
|
92
|
+
tenantId String
|
|
93
|
+
// OWNER | ADMIN | QA_LEAD | TESTER | DEVELOPER | VIEWER
|
|
94
|
+
role String
|
|
95
|
+
// ACTIVE | SUSPENDED — set to SUSPENDED by SCIM deprovisioning (soft
|
|
96
|
+
// suspend, role preserved for reactivation) or an admin. Enforced in
|
|
97
|
+
// resolveContext() on every authenticated request, not just at login.
|
|
98
|
+
status String @default("ACTIVE")
|
|
99
|
+
user User @relation(fields: [userId], references: [id])
|
|
100
|
+
tenant Tenant @relation(fields: [tenantId], references: [id])
|
|
101
|
+
|
|
102
|
+
@@unique([userId, tenantId])
|
|
103
|
+
@@index([tenantId])
|
|
104
|
+
@@index([userId])
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Custom roles created by the tenant
|
|
108
|
+
model CustomRole {
|
|
109
|
+
id String @id @default(cuid())
|
|
110
|
+
tenantId String
|
|
111
|
+
name String
|
|
112
|
+
description String?
|
|
113
|
+
permissions Json @default("[]") // e.g. ["project:read", "project:write", "settings:manage"]
|
|
114
|
+
createdAt DateTime @default(now())
|
|
115
|
+
updatedAt DateTime @default(now())
|
|
116
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
117
|
+
|
|
118
|
+
@@unique([tenantId, name])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Pending invites to a tenant
|
|
122
|
+
model Invitation {
|
|
123
|
+
id String @id @default(cuid())
|
|
124
|
+
tenantId String
|
|
125
|
+
email String
|
|
126
|
+
role String
|
|
127
|
+
tokenHash String @unique
|
|
128
|
+
invitedBy String?
|
|
129
|
+
status String @default("PENDING")
|
|
130
|
+
expiresAt DateTime
|
|
131
|
+
createdAt DateTime @default(now())
|
|
132
|
+
acceptedAt DateTime?
|
|
133
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
134
|
+
|
|
135
|
+
@@unique([tenantId, email])
|
|
136
|
+
@@index([tenantId, status])
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---- A Project = one app being tested --------------------------------------
|
|
140
|
+
|
|
141
|
+
model Project {
|
|
142
|
+
id String @id @default(cuid())
|
|
143
|
+
tenantId String
|
|
144
|
+
name String
|
|
145
|
+
slug String @default("")
|
|
146
|
+
appUrl String?
|
|
147
|
+
figmaUrl String?
|
|
148
|
+
appType String?
|
|
149
|
+
crawlConfig Json? // { username?, password_enc?, totpSecret_enc? }
|
|
150
|
+
ciFailureThreshold Int?
|
|
151
|
+
slaThreshold Int?
|
|
152
|
+
visualDiffThreshold Float?
|
|
153
|
+
// Set to true when the project's knowledge outgrows direct loading and
|
|
154
|
+
// needs vector search (RAG). Default false = direct loading (the simple way).
|
|
155
|
+
description String?
|
|
156
|
+
status String @default("ACTIVE")
|
|
157
|
+
oversized Boolean @default(false)
|
|
158
|
+
restricted Boolean @default(false)
|
|
159
|
+
vsaEnabled Boolean @default(false)
|
|
160
|
+
workspaceId String?
|
|
161
|
+
// Context Intelligence (P1-P8)
|
|
162
|
+
projectKind String @default("WEB")
|
|
163
|
+
contextSources Json @default("{}")
|
|
164
|
+
contextScore Int @default(0)
|
|
165
|
+
contextPhase String @default("PENDING_CONTEXT")
|
|
166
|
+
contextSourceRequirements Json @default("{}")
|
|
167
|
+
approvalGateSummary Json?
|
|
168
|
+
createdAt DateTime @default(now())
|
|
169
|
+
updatedAt DateTime @updatedAt
|
|
170
|
+
tenant Tenant @relation(fields: [tenantId], references: [id])
|
|
171
|
+
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: SetNull)
|
|
172
|
+
|
|
173
|
+
screens Screen[]
|
|
174
|
+
screenGroups ScreenGroup[]
|
|
175
|
+
flows Flow[]
|
|
176
|
+
skillFiles SkillFile[]
|
|
177
|
+
artifacts ProjectArtifact[]
|
|
178
|
+
testSuites TestSuite[]
|
|
179
|
+
scriptBundles ScriptBundle[]
|
|
180
|
+
connectors Connector[]
|
|
181
|
+
oauthConnections OAuthConnection[]
|
|
182
|
+
agentTokens AgentToken[]
|
|
183
|
+
webhooks Webhook[]
|
|
184
|
+
auditLogs AuditLog[]
|
|
185
|
+
changeLogs ChangeLog[]
|
|
186
|
+
platformJobs PlatformJob[]
|
|
187
|
+
sessions UserSession[]
|
|
188
|
+
testRuns TestRun[]
|
|
189
|
+
appFields AppField[]
|
|
190
|
+
nativeAppBuilds NativeAppBuild[]
|
|
191
|
+
projectEnvironments ProjectEnvironment[]
|
|
192
|
+
projectMembers ProjectMember[]
|
|
193
|
+
requirements Requirement[]
|
|
194
|
+
extensionCaptures ExtensionCapture[]
|
|
195
|
+
extensionErrors ExtensionError[]
|
|
196
|
+
screenTransitions ScreenTransition[]
|
|
197
|
+
crawlEvidence CrawlEvidence[]
|
|
198
|
+
actionRecipes ActionRecipe[]
|
|
199
|
+
apiEndpointObservations ApiEndpointObservation[]
|
|
200
|
+
testDataSets TestDataSet[]
|
|
201
|
+
a11yViolations ScreenA11yViolation[]
|
|
202
|
+
// Framework Modernization — Phase 2
|
|
203
|
+
githubRepoConnections GitHubRepoConnection[]
|
|
204
|
+
coverageSnapshots CoverageSnapshot[]
|
|
205
|
+
idePushTokens IdePushToken[]
|
|
206
|
+
skillFileConfigs SkillFileConfig[]
|
|
207
|
+
discoveredLinks DiscoveredLink[]
|
|
208
|
+
virtualScreens VirtualScreen[]
|
|
209
|
+
contextEnhancements ContextEnhancement[]
|
|
210
|
+
contextSourceApprovals ContextSourceApproval[]
|
|
211
|
+
crawlDiffs CrawlDiff[]
|
|
212
|
+
bugSignals BugSignal[]
|
|
213
|
+
requirementCoverages RequirementCoverage[]
|
|
214
|
+
figmaComponents FigmaComponent[]
|
|
215
|
+
extractedElements ExtractedElement[]
|
|
216
|
+
|
|
217
|
+
@@unique([tenantId, slug])
|
|
218
|
+
@@index([tenantId])
|
|
219
|
+
@@index([workspaceId])
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---- THE Zeta Core (a normal, structured, versioned knowledge store) -------
|
|
223
|
+
|
|
224
|
+
model ScreenGroup {
|
|
225
|
+
id String @id @default(cuid())
|
|
226
|
+
tenantId String
|
|
227
|
+
projectId String
|
|
228
|
+
name String
|
|
229
|
+
slug String
|
|
230
|
+
description String?
|
|
231
|
+
color String @default("#6FD0E8")
|
|
232
|
+
icon String @default("◈")
|
|
233
|
+
order Int @default(0)
|
|
234
|
+
crawlJobId String?
|
|
235
|
+
createdAt DateTime @default(now())
|
|
236
|
+
updatedAt DateTime @updatedAt
|
|
237
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
238
|
+
screens Screen[]
|
|
239
|
+
|
|
240
|
+
@@index([tenantId, projectId])
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
model Screen {
|
|
244
|
+
id String @id @default(cuid())
|
|
245
|
+
tenantId String
|
|
246
|
+
projectId String
|
|
247
|
+
name String
|
|
248
|
+
url String?
|
|
249
|
+
version Int @default(1)
|
|
250
|
+
groupId String?
|
|
251
|
+
group ScreenGroup? @relation(fields: [groupId], references: [id])
|
|
252
|
+
// How closely the live screen matches its Figma design (Phase 2). 0-100.
|
|
253
|
+
figmaMatch Int?
|
|
254
|
+
// Accessibility score 0-100 (Phase 3).
|
|
255
|
+
a11yScore Int?
|
|
256
|
+
lastSeenAt DateTime @default(now())
|
|
257
|
+
// Lifecycle fields — track screen presence across crawl runs
|
|
258
|
+
status String @default("ACTIVE") // ACTIVE | PENDING_REMOVAL
|
|
259
|
+
lastCrawlJobId String? // ID of the last PlatformJob that discovered this screen
|
|
260
|
+
removedAt DateTime?
|
|
261
|
+
removalReason String? // e.g. "Not found in crawl #N (was present in crawl #N-1)"
|
|
262
|
+
// How this screen was first discovered — set once at creation, never changed on
|
|
263
|
+
// re-crawl. Powers orphan-page detection: a screen with zero inbound KAG edges
|
|
264
|
+
// (Element.transitionsToScreenId) is only a meaningful "orphan" if it wasn't the
|
|
265
|
+
// crawl's own entry point.
|
|
266
|
+
// ENTRY_POINT | LINK_FOLLOW | SEED_URL | SKYVERN_AI | SITEMAP
|
|
267
|
+
discoveryMethod String @default("LINK_FOLLOW")
|
|
268
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
269
|
+
elements Element[]
|
|
270
|
+
screenshots Screenshot[]
|
|
271
|
+
baselines VisualBaseline[]
|
|
272
|
+
testCases TestCase[]
|
|
273
|
+
|
|
274
|
+
// Knowledge Graph (KAG): Elements on OTHER screens that transition to this screen
|
|
275
|
+
transitionedFrom Element[] @relation("ElementTransitions")
|
|
276
|
+
|
|
277
|
+
// Phase 2: element hash for stale-test detection
|
|
278
|
+
elementHash String?
|
|
279
|
+
elementHashChangedAt DateTime?
|
|
280
|
+
|
|
281
|
+
// Phase 3: perceptual screenshot diffs
|
|
282
|
+
screenDiffs ScreenDiff[]
|
|
283
|
+
|
|
284
|
+
// Phase 5: structured probe observations
|
|
285
|
+
probeObservations ProbeObservation[]
|
|
286
|
+
screenTransitionsFrom ScreenTransition[] @relation("ScreenTransitionFrom")
|
|
287
|
+
screenTransitionsTo ScreenTransition[] @relation("ScreenTransitionTo")
|
|
288
|
+
crawlEvidence CrawlEvidence[]
|
|
289
|
+
apiEndpointObservations ApiEndpointObservation[]
|
|
290
|
+
|
|
291
|
+
// Navigation graph: discovered link edges from/to this screen
|
|
292
|
+
discoveredLinksFrom DiscoveredLink[] @relation("DiscoveredLinkFromScreen")
|
|
293
|
+
discoveredLinksTo DiscoveredLink[] @relation("DiscoveredLinkToScreen")
|
|
294
|
+
|
|
295
|
+
domSnapshotPath String?
|
|
296
|
+
domSnapshotCloudUrl String?
|
|
297
|
+
// True when crawler detected this screen requires authentication but session was lost.
|
|
298
|
+
// DOM/elements captured for this screen may reflect an auth wall, not real content.
|
|
299
|
+
requiresAuth Boolean @default(false)
|
|
300
|
+
|
|
301
|
+
// Navigation graph metadata
|
|
302
|
+
depth Int? // BFS depth from entry point (0 = entry point)
|
|
303
|
+
parentUrl String? // URL of the screen that linked to this one first
|
|
304
|
+
|
|
305
|
+
// Role profiles that can access this screen — populated by multi-role crawl runs.
|
|
306
|
+
roleAccess String[] @default([])
|
|
307
|
+
|
|
308
|
+
// Markdown extraction (crawl4AI-style text output). "readability" = clean
|
|
309
|
+
// article content (nav/ads/chrome stripped); "full-page" = whole-page
|
|
310
|
+
// fallback for app UI screens Readability finds no article in.
|
|
311
|
+
markdownPath String?
|
|
312
|
+
markdownContent String?
|
|
313
|
+
markdownMethod String?
|
|
314
|
+
markdownWordCount Int?
|
|
315
|
+
// AI-generated one-sentence summary of what this screen does/contains
|
|
316
|
+
aiSummary String?
|
|
317
|
+
|
|
318
|
+
// Phase 4.4: axe-core accessibility violations
|
|
319
|
+
a11yViolations ScreenA11yViolation[]
|
|
320
|
+
virtualScreens VirtualScreen[]
|
|
321
|
+
figmaNodeId String?
|
|
322
|
+
figmaComponents FigmaComponent[]
|
|
323
|
+
crawlDiffsAsFrom CrawlDiff[] @relation("CrawlDiffFromScreen")
|
|
324
|
+
crawlDiffsAsTo CrawlDiff[] @relation("CrawlDiffToScreen")
|
|
325
|
+
|
|
326
|
+
@@index([tenantId, projectId])
|
|
327
|
+
@@index([tenantId, projectId, status])
|
|
328
|
+
@@index([tenantId, projectId, elementHashChangedAt])
|
|
329
|
+
@@index([status])
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Phase 4.4: Per-screen axe-core accessibility violations (WCAG 2.x)
|
|
333
|
+
model ScreenA11yViolation {
|
|
334
|
+
id String @id @default(cuid())
|
|
335
|
+
tenantId String
|
|
336
|
+
projectId String
|
|
337
|
+
screenId String
|
|
338
|
+
runId String?
|
|
339
|
+
impact String // critical | serious | moderate | minor
|
|
340
|
+
rule String // axe rule ID e.g. "color-contrast"
|
|
341
|
+
description String
|
|
342
|
+
nodeHtml String? // offending DOM snippet (truncated to 500 chars)
|
|
343
|
+
wcagTags Json @default("[]") // ["wcag2a", "wcag21aa"] etc
|
|
344
|
+
createdAt DateTime @default(now())
|
|
345
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
346
|
+
screen Screen @relation(fields: [screenId], references: [id], onDelete: Cascade)
|
|
347
|
+
|
|
348
|
+
@@index([tenantId, projectId])
|
|
349
|
+
@@index([screenId])
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Rich workflow graph edge discovered during crawl/probe/replay.
|
|
353
|
+
model ScreenTransition {
|
|
354
|
+
id String @id @default(cuid())
|
|
355
|
+
tenantId String
|
|
356
|
+
projectId String
|
|
357
|
+
fromScreenId String
|
|
358
|
+
toScreenId String?
|
|
359
|
+
action Json
|
|
360
|
+
confidence Float @default(0.5)
|
|
361
|
+
jobId String?
|
|
362
|
+
createdAt DateTime @default(now())
|
|
363
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
364
|
+
fromScreen Screen @relation("ScreenTransitionFrom", fields: [fromScreenId], references: [id], onDelete: Cascade)
|
|
365
|
+
toScreen Screen? @relation("ScreenTransitionTo", fields: [toScreenId], references: [id], onDelete: SetNull)
|
|
366
|
+
|
|
367
|
+
@@index([tenantId, projectId])
|
|
368
|
+
@@index([tenantId, projectId, jobId])
|
|
369
|
+
@@index([fromScreenId])
|
|
370
|
+
@@index([toScreenId])
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Durable evidence envelope emitted by the crawler for auditability and replay.
|
|
374
|
+
model CrawlEvidence {
|
|
375
|
+
id String @id @default(cuid())
|
|
376
|
+
tenantId String
|
|
377
|
+
projectId String
|
|
378
|
+
screenId String?
|
|
379
|
+
jobId String?
|
|
380
|
+
type String
|
|
381
|
+
payload Json
|
|
382
|
+
createdAt DateTime @default(now())
|
|
383
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
384
|
+
screen Screen? @relation(fields: [screenId], references: [id], onDelete: Cascade)
|
|
385
|
+
|
|
386
|
+
@@index([tenantId, projectId])
|
|
387
|
+
@@index([tenantId, projectId, jobId])
|
|
388
|
+
@@index([screenId])
|
|
389
|
+
@@index([type])
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Replay-first self-healing action recipe. Successful AI/manual actions become
|
|
393
|
+
// deterministic recipes before future crawls spend more model tokens.
|
|
394
|
+
model ActionRecipe {
|
|
395
|
+
id String @id @default(cuid())
|
|
396
|
+
tenantId String
|
|
397
|
+
projectId String
|
|
398
|
+
domain String
|
|
399
|
+
purpose String
|
|
400
|
+
fingerprint String
|
|
401
|
+
steps Json
|
|
402
|
+
successRate Float @default(0)
|
|
403
|
+
lastUsedAt DateTime?
|
|
404
|
+
createdAt DateTime @default(now())
|
|
405
|
+
updatedAt DateTime @updatedAt
|
|
406
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
407
|
+
|
|
408
|
+
@@unique([tenantId, projectId, purpose, fingerprint])
|
|
409
|
+
@@index([tenantId, projectId])
|
|
410
|
+
@@index([tenantId, projectId, domain])
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Represents a dynamic state (modal, panel, popup) within a base Screen, captured after an interaction
|
|
414
|
+
model VirtualScreen {
|
|
415
|
+
id String @id @default(cuid())
|
|
416
|
+
tenantId String
|
|
417
|
+
projectId String
|
|
418
|
+
parentScreenId String
|
|
419
|
+
stateId String // simHash of DOM after action
|
|
420
|
+
triggerAction String // e.g. "click Import Tests"
|
|
421
|
+
overlayType String // 'modal' | 'panel' | 'tooltip' | 'dropdown'
|
|
422
|
+
screenshotUrl String?
|
|
423
|
+
domSnapshotUrl String?
|
|
424
|
+
createdAt DateTime @default(now())
|
|
425
|
+
|
|
426
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
427
|
+
parentScreen Screen @relation(fields: [parentScreenId], references: [id], onDelete: Cascade)
|
|
428
|
+
|
|
429
|
+
@@unique([tenantId, projectId, parentScreenId, stateId])
|
|
430
|
+
@@index([tenantId, projectId])
|
|
431
|
+
@@index([parentScreenId])
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Network/API calls observed while rendering a screen. This connects UI screens
|
|
435
|
+
// to REST/GraphQL endpoints for API test generation and backend-state mapping.
|
|
436
|
+
model ApiEndpointObservation {
|
|
437
|
+
id String @id @default(cuid())
|
|
438
|
+
tenantId String
|
|
439
|
+
projectId String
|
|
440
|
+
screenId String?
|
|
441
|
+
method String
|
|
442
|
+
urlPattern String
|
|
443
|
+
statusCode Int?
|
|
444
|
+
requestKind String?
|
|
445
|
+
responseShape Json?
|
|
446
|
+
createdAt DateTime @default(now())
|
|
447
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
448
|
+
screen Screen? @relation(fields: [screenId], references: [id], onDelete: Cascade)
|
|
449
|
+
|
|
450
|
+
@@index([tenantId, projectId])
|
|
451
|
+
@@index([tenantId, projectId, method])
|
|
452
|
+
@@index([screenId])
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
model Element {
|
|
456
|
+
id String @id @default(cuid())
|
|
457
|
+
tenantId String
|
|
458
|
+
projectId String
|
|
459
|
+
// Nullable because extension-captured elements (context-menu/devtools/picker)
|
|
460
|
+
// don't correspond to a crawled Screen — only crawl-discovered elements set this.
|
|
461
|
+
screenId String?
|
|
462
|
+
// A stable, human-readable description, e.g. "Primary login submit button".
|
|
463
|
+
meaning String?
|
|
464
|
+
// The kind of thing it is: BUTTON | INPUT | LINK | MENU | CHECKBOX | ...
|
|
465
|
+
role String?
|
|
466
|
+
// What data this field expects, e.g. "valid_email", "us_phone", "date_ddmmyyyy".
|
|
467
|
+
expectedData String?
|
|
468
|
+
// Free-form notes learned over time, e.g. "rejects non-company.com domains".
|
|
469
|
+
notes String?
|
|
470
|
+
text String? // Inner text or placeholder
|
|
471
|
+
isInteractive Boolean? // True if it's an a/button/input, false if it's just layout
|
|
472
|
+
|
|
473
|
+
// Phase 1: enriched element metadata
|
|
474
|
+
boundingRect Json? // {x, y, width, height, top, left, right, bottom}
|
|
475
|
+
ariaState Json? // {expanded, checked, pressed, selected, invalid, required}
|
|
476
|
+
parentLandmark String? // "main" | "nav" | "aside" | "header" | "footer" | "modal"
|
|
477
|
+
viewport String @default("DESKTOP")
|
|
478
|
+
|
|
479
|
+
// Extension-capture fields (context-menu/devtools/picker sources — no crawled Screen).
|
|
480
|
+
selector String?
|
|
481
|
+
pageUrl String?
|
|
482
|
+
screenshotUrl String?
|
|
483
|
+
source String @default("crawl") // "crawl" | "context-menu" | "devtools" | "picker"
|
|
484
|
+
metadata Json?
|
|
485
|
+
|
|
486
|
+
// Knowledge Graph (KAG) Edge: If clicking this element leads to another screen.
|
|
487
|
+
transitionsToScreenId String?
|
|
488
|
+
transitionsToScreen Screen? @relation("ElementTransitions", fields: [transitionsToScreenId], references: [id])
|
|
489
|
+
|
|
490
|
+
screen Screen? @relation(fields: [screenId], references: [id])
|
|
491
|
+
fingerprint ElementFingerprint?
|
|
492
|
+
|
|
493
|
+
@@unique([tenantId, projectId, selector, pageUrl], name: "Element_ext_upsert_key")
|
|
494
|
+
@@index([tenantId, projectId])
|
|
495
|
+
@@index([tenantId, screenId])
|
|
496
|
+
@@index([tenantId, screenId, parentLandmark])
|
|
497
|
+
@@index([tenantId, screenId, viewport])
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
model Flow {
|
|
501
|
+
id String @id @default(cuid())
|
|
502
|
+
tenantId String
|
|
503
|
+
projectId String
|
|
504
|
+
name String
|
|
505
|
+
// Ordered list of screen ids that make up this user journey (JSON).
|
|
506
|
+
steps Json
|
|
507
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
508
|
+
|
|
509
|
+
@@index([tenantId, projectId])
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
model Screenshot {
|
|
513
|
+
id String @id @default(cuid())
|
|
514
|
+
tenantId String
|
|
515
|
+
projectId String
|
|
516
|
+
screenId String
|
|
517
|
+
// DESKTOP | MOBILE | TABLET
|
|
518
|
+
viewport String @default("DESKTOP")
|
|
519
|
+
storagePath String
|
|
520
|
+
cloudUrl String?
|
|
521
|
+
version Int @default(1)
|
|
522
|
+
capturedAt DateTime @default(now())
|
|
523
|
+
// FM-4: device pixel ratio captured at crawl time; null = unknown (pre-migration)
|
|
524
|
+
dpr Float?
|
|
525
|
+
screen Screen @relation(fields: [screenId], references: [id])
|
|
526
|
+
stepEvidence StepEvidence[]
|
|
527
|
+
|
|
528
|
+
@@index([tenantId, projectId])
|
|
529
|
+
@@index([tenantId, screenId])
|
|
530
|
+
@@index([screenId, version(sort: Desc)])
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// A reusable, per-project instruction package the AI loads before a task.
|
|
534
|
+
//
|
|
535
|
+
// TAXONOMY KINDS (Application Intelligence):
|
|
536
|
+
// PLATFORM — tech stack: React 18, JWT auth, REST API, mobile-responsive
|
|
537
|
+
// FUNCTION — domain: "e-commerce checkout", "healthcare patient portal"
|
|
538
|
+
// DESIGN — visual system: color tokens, typography, spacing, dark/light mode
|
|
539
|
+
// THEME — cross-cutting conventions: "errors = toast top-right", "spinner on submit"
|
|
540
|
+
// COMPONENT — reusable UI patterns: "DataTable paginates at 25 rows, sortable cols"
|
|
541
|
+
// ELEMENT — specific element behavior: "email validates on blur, error inline"
|
|
542
|
+
//
|
|
543
|
+
// LEGACY KINDS (task-specific):
|
|
544
|
+
// TEST_CASE | TEST_DATA | TEST_SCRIPT | CRAWL | SCREEN_UNDERSTANDING
|
|
545
|
+
//
|
|
546
|
+
// RETRIEVAL TIERS:
|
|
547
|
+
// PLATFORM + FUNCTION + DESIGN + THEME → always injected (global, project-wide)
|
|
548
|
+
// COMPONENT + ELEMENT → injected selectively (filtered by screenIds/tags)
|
|
549
|
+
model SkillFile {
|
|
550
|
+
id String @id @default(cuid())
|
|
551
|
+
tenantId String
|
|
552
|
+
projectId String
|
|
553
|
+
|
|
554
|
+
// Taxonomy kind (see above)
|
|
555
|
+
kind String
|
|
556
|
+
|
|
557
|
+
// PROJECT = per-project (default)
|
|
558
|
+
// WORKSPACE = shared template across all projects in this tenant
|
|
559
|
+
scope String @default("PROJECT")
|
|
560
|
+
|
|
561
|
+
// 1-3 line plain-English description — shown in UI and used for keyword search
|
|
562
|
+
summary String?
|
|
563
|
+
|
|
564
|
+
// Searchable tags for selective retrieval: ["login-form", "email-validation"]
|
|
565
|
+
tags Json @default("[]")
|
|
566
|
+
|
|
567
|
+
// For COMPONENT/ELEMENT: which screenIds this knowledge covers ([] = all screens)
|
|
568
|
+
screenIds Json @default("[]")
|
|
569
|
+
|
|
570
|
+
// The full markdown instruction content — grows with human edits over time
|
|
571
|
+
content String
|
|
572
|
+
|
|
573
|
+
// Machine-readable extraction alongside markdown. Generators should prefer
|
|
574
|
+
// this when present; markdown remains the human review surface.
|
|
575
|
+
structuredData Json @default("{}")
|
|
576
|
+
provenance Json @default("[]")
|
|
577
|
+
confidence Float?
|
|
578
|
+
|
|
579
|
+
// UNKNOWN | PASS | WARN | FAIL
|
|
580
|
+
validationStatus String @default("UNKNOWN")
|
|
581
|
+
validationSummary String?
|
|
582
|
+
|
|
583
|
+
// CURRENT | STALE | APPROVED_STALE
|
|
584
|
+
staleStatus String @default("CURRENT")
|
|
585
|
+
staleReason String?
|
|
586
|
+
|
|
587
|
+
generatorVersion String?
|
|
588
|
+
|
|
589
|
+
version Int @default(1)
|
|
590
|
+
approvedBy String?
|
|
591
|
+
// PENDING | APPROVED | NEEDS_REVIEW
|
|
592
|
+
approvalStatus String @default("PENDING")
|
|
593
|
+
|
|
594
|
+
// Diff / review fields — populated when AI regenerates content
|
|
595
|
+
priorContent String?
|
|
596
|
+
priorVersion Int?
|
|
597
|
+
priorGeneratedAt DateTime?
|
|
598
|
+
changesSummary String?
|
|
599
|
+
|
|
600
|
+
// AUTO (AI-generated) | MANUAL (human-written) | SEEDED (platform template)
|
|
601
|
+
source String @default("AUTO")
|
|
602
|
+
|
|
603
|
+
updatedAt DateTime @default(now())
|
|
604
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
605
|
+
|
|
606
|
+
// Token Tracking
|
|
607
|
+
lastGenPromptTokens Int?
|
|
608
|
+
lastGenCompletionTokens Int?
|
|
609
|
+
lastGenCostUsd Float?
|
|
610
|
+
lastGenModel String?
|
|
611
|
+
lastGenAt DateTime?
|
|
612
|
+
|
|
613
|
+
@@unique([projectId, kind, scope])
|
|
614
|
+
@@index([tenantId, projectId])
|
|
615
|
+
@@index([tenantId, kind])
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// ---- Per-project skill file kind configuration ----------------------------
|
|
619
|
+
// Platform admins can enable/disable generation for specific kinds,
|
|
620
|
+
// require manual approval, or set auto-gen schedules per project.
|
|
621
|
+
|
|
622
|
+
model SkillFileConfig {
|
|
623
|
+
id String @id @default(cuid())
|
|
624
|
+
projectId String
|
|
625
|
+
tenantId String
|
|
626
|
+
kind String // matches TAXONOMY_KINDS value
|
|
627
|
+
enabled Boolean @default(true)
|
|
628
|
+
requireApproval Boolean @default(false)
|
|
629
|
+
autoGenIntervalMins Int? // null = no scheduled auto-gen
|
|
630
|
+
generationStrategy String @default("AUTO") // AUTO | MANUAL | SEEDED
|
|
631
|
+
validationThresholds Json? // { minConfidence?: number, maxStaleAgeDays?: number }
|
|
632
|
+
adminNotes String? @db.Text
|
|
633
|
+
createdAt DateTime @default(now())
|
|
634
|
+
updatedAt DateTime @updatedAt
|
|
635
|
+
|
|
636
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
637
|
+
|
|
638
|
+
@@unique([projectId, kind])
|
|
639
|
+
@@index([tenantId, projectId])
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ---- User-uploaded knowledge artifacts ------------------------------------
|
|
643
|
+
|
|
644
|
+
model ProjectArtifact {
|
|
645
|
+
id String @id @default(cuid())
|
|
646
|
+
tenantId String
|
|
647
|
+
projectId String
|
|
648
|
+
name String // display name (user-editable)
|
|
649
|
+
originalName String // filename as uploaded
|
|
650
|
+
mimeType String
|
|
651
|
+
size Int // bytes of original file
|
|
652
|
+
content String @db.Text // converted markdown content
|
|
653
|
+
storageKey String? // object key in tenant S3/R2 (original file); null = not stored in cloud
|
|
654
|
+
// When tenant S3 is configured, extracted markdown is stored there too.
|
|
655
|
+
// Resolver: if contentKey is set, read from S3; otherwise use content field directly.
|
|
656
|
+
contentKey String?
|
|
657
|
+
kind String @default("DOCUMENT") // DOCUMENT | SPREADSHEET | TEST_CASES | SPEC | OTHER
|
|
658
|
+
uploadedBy String
|
|
659
|
+
uploadedAt DateTime @default(now())
|
|
660
|
+
summary String?
|
|
661
|
+
keywords String[] @default([])
|
|
662
|
+
docType String?
|
|
663
|
+
chunkCount Int @default(1)
|
|
664
|
+
cachedScenarios Json? // Cached extractScenarios result — array of ScenarioSummary objects
|
|
665
|
+
// Import lifecycle tracking
|
|
666
|
+
importStatus String? // null | 'IMPORTED' | 'FAILED'
|
|
667
|
+
importedAt DateTime?
|
|
668
|
+
importedTestCount Int?
|
|
669
|
+
importedSuiteId String? // suite the test cases were imported into; null if suite deleted
|
|
670
|
+
// Version chain — versionNum increments on re-upload; previousVersionId links to prior artifact
|
|
671
|
+
versionNum Int @default(1)
|
|
672
|
+
previousVersionId String?
|
|
673
|
+
contentHash String? // sha256 of original upload bytes; partial unique (projectId, contentHash) index in migration SQL
|
|
674
|
+
previousVersion ProjectArtifact? @relation("ArtifactVersionChain", fields: [previousVersionId], references: [id], onDelete: SetNull)
|
|
675
|
+
nextVersions ProjectArtifact[] @relation("ArtifactVersionChain")
|
|
676
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
677
|
+
testCases TestCase[]
|
|
678
|
+
|
|
679
|
+
@@index([tenantId, projectId])
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ---- Test artifacts --------------------------------------------------------
|
|
683
|
+
|
|
684
|
+
enum SuiteCategory {
|
|
685
|
+
REGRESSION
|
|
686
|
+
SMOKE
|
|
687
|
+
SANITY
|
|
688
|
+
API
|
|
689
|
+
SECURITY
|
|
690
|
+
INTEGRATION
|
|
691
|
+
E2E
|
|
692
|
+
CUSTOM
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
model TestSuite {
|
|
696
|
+
id String @id @default(cuid())
|
|
697
|
+
tenantId String
|
|
698
|
+
projectId String
|
|
699
|
+
name String
|
|
700
|
+
createdAt DateTime @default(now())
|
|
701
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
702
|
+
testCases TestCase[]
|
|
703
|
+
category SuiteCategory?
|
|
704
|
+
|
|
705
|
+
// Hierarchy — child suites are created per Section column on import
|
|
706
|
+
// Unique (tenantId, projectId, COALESCE(parentId,''), name) expression index lives in migration SQL.
|
|
707
|
+
parentId String?
|
|
708
|
+
parent TestSuite? @relation("SuiteHierarchy", fields: [parentId], references: [id], onDelete: Cascade)
|
|
709
|
+
children TestSuite[] @relation("SuiteHierarchy")
|
|
710
|
+
|
|
711
|
+
ownerUserId String?
|
|
712
|
+
|
|
713
|
+
// Token Tracking
|
|
714
|
+
genTotalTokens Int?
|
|
715
|
+
genTotalCostUsd Float?
|
|
716
|
+
genModel String?
|
|
717
|
+
genAt DateTime?
|
|
718
|
+
genScreenCount Int?
|
|
719
|
+
coverageReport Json?
|
|
720
|
+
|
|
721
|
+
@@index([tenantId, projectId])
|
|
722
|
+
@@index([parentId])
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
model TestCase {
|
|
726
|
+
id String @id @default(cuid())
|
|
727
|
+
tenantId String
|
|
728
|
+
projectId String
|
|
729
|
+
suiteId String
|
|
730
|
+
screenId String?
|
|
731
|
+
// Human-readable stable reference, e.g. "TC-LOGIN-014"
|
|
732
|
+
ref String?
|
|
733
|
+
title String
|
|
734
|
+
// POSITIVE | NEGATIVE | EDGE | ACCESSIBILITY | SECURITY
|
|
735
|
+
kind String
|
|
736
|
+
// CRITICAL | HIGH | MEDIUM | LOW
|
|
737
|
+
priority String @default("HIGH")
|
|
738
|
+
preconditions String?
|
|
739
|
+
steps Json
|
|
740
|
+
expectedResult String?
|
|
741
|
+
// The rendered Gherkin .feature text for this case
|
|
742
|
+
gherkin String?
|
|
743
|
+
// PENDING_REVIEW | APPROVED | RETIRED (human-in-the-loop)
|
|
744
|
+
status String @default("PENDING_REVIEW")
|
|
745
|
+
approvedBy String? // userId of reviewer who approved
|
|
746
|
+
approvedAt DateTime? // when APPROVED status was set
|
|
747
|
+
// AI_GENERATED | HUMAN_WRITTEN | IMPORTED
|
|
748
|
+
source String @default("AI_GENERATED")
|
|
749
|
+
// Which real element ids this test is grounded in (proves "no invention").
|
|
750
|
+
groundedOn Json
|
|
751
|
+
// Searchable tags e.g. ["@smoke", "@auth", "@regression"]
|
|
752
|
+
tags Json @default("[]")
|
|
753
|
+
// Links to external requirements/Figma/JIRA
|
|
754
|
+
caseRefs Json @default("[]")
|
|
755
|
+
// Viewport scopes for this test case. Empty = applies to all.
|
|
756
|
+
// Values: ["DESKTOP", "MOBILE_PORTRAIT", "TABLET"]
|
|
757
|
+
viewports Json @default("[]")
|
|
758
|
+
// Which runner viewport this test case is valid for. ALL = any runner.
|
|
759
|
+
// ALL | DESKTOP_ONLY | MOBILE_ONLY | TABLET_ONLY
|
|
760
|
+
viewportConstraint String @default("ALL")
|
|
761
|
+
screen Screen? @relation(fields: [screenId], references: [id])
|
|
762
|
+
suite TestSuite @relation(fields: [suiteId], references: [id])
|
|
763
|
+
testData TestData[]
|
|
764
|
+
scripts TestScript[]
|
|
765
|
+
humanReviews HumanReviewItem[]
|
|
766
|
+
results TestResult[]
|
|
767
|
+
testCaseRuns TestCaseRun[]
|
|
768
|
+
stepEvidence StepEvidence[]
|
|
769
|
+
stepValidations StepValidation[]
|
|
770
|
+
|
|
771
|
+
techniqueOfRecord String?
|
|
772
|
+
traceTo String?
|
|
773
|
+
// Additional requirement IDs this test also covers (many-to-many traceability).
|
|
774
|
+
// traceTo stays the primary link; this array holds secondary reqIds.
|
|
775
|
+
traceToExtra Json @default("[]")
|
|
776
|
+
testDataInline Json?
|
|
777
|
+
dataNote String? // LLM-generated guidance for the data-agent: field constraints, personas, boundary values
|
|
778
|
+
scriptNote String? // LLM-generated guidance for the script-agent: preconditions, dynamic extractions, parallelism
|
|
779
|
+
structuredSteps Json? // [{action, target?, data?}] — machine-readable step format for accurate data extraction
|
|
780
|
+
|
|
781
|
+
// Traceability — which artifact this test case was imported from
|
|
782
|
+
artifactId String?
|
|
783
|
+
artifact ProjectArtifact? @relation(fields: [artifactId], references: [id], onDelete: SetNull)
|
|
784
|
+
|
|
785
|
+
// Scenario traceability
|
|
786
|
+
scenarioId String?
|
|
787
|
+
scenario Scenario? @relation(fields: [scenarioId], references: [id], onDelete: SetNull)
|
|
788
|
+
// Suspect link — flagged when upstream requirement changes after this test was generated
|
|
789
|
+
suspect Boolean @default(false)
|
|
790
|
+
suspectReason String?
|
|
791
|
+
|
|
792
|
+
// Flakiness tracking — rolling stats over the last N TestCaseRun outcomes,
|
|
793
|
+
// recomputed by AppBrain.recordFlakinessOutcome() after each run. See
|
|
794
|
+
// docs/13-TEST-FLAKINESS-DETECTION.md "Future Work".
|
|
795
|
+
// 0.0-1.0 fraction of FAILED+FLAKY outcomes in the rolling window.
|
|
796
|
+
flakyScore Float @default(0)
|
|
797
|
+
// Current losing streak (consecutive FAILED/FLAKY runs), reset on a PASS.
|
|
798
|
+
consecutiveFlakes Int @default(0)
|
|
799
|
+
lastFlakyAt DateTime?
|
|
800
|
+
// Auto- or human-quarantined tests are blocked from running (POST .../run
|
|
801
|
+
// returns 409) unless the caller explicitly passes force:true. Never
|
|
802
|
+
// auto-cleared — requires an explicit PATCH .../quarantine to lift.
|
|
803
|
+
quarantined Boolean @default(false)
|
|
804
|
+
quarantinedAt DateTime?
|
|
805
|
+
quarantineReason String?
|
|
806
|
+
|
|
807
|
+
// Token Tracking
|
|
808
|
+
genPromptTokens Int?
|
|
809
|
+
genCompletionTokens Int?
|
|
810
|
+
genCostUsd Float?
|
|
811
|
+
genModel String?
|
|
812
|
+
|
|
813
|
+
// Data-driven execution — linked data set (Phase 4.3)
|
|
814
|
+
dataSetId String?
|
|
815
|
+
dataSet TestDataSet? @relation(fields: [dataSetId], references: [id], onDelete: SetNull)
|
|
816
|
+
|
|
817
|
+
// Framework Modernization — script coverage mapping (Phase 2)
|
|
818
|
+
coverageMapping CoverageMapping?
|
|
819
|
+
// Where a generated script should land in the connected repo (Phase 3)
|
|
820
|
+
repoTargetPath String?
|
|
821
|
+
|
|
822
|
+
// S3 content key pointers (DB→S3 pattern; doc 18).
|
|
823
|
+
// When set, the corresponding DB field is a stub and actual content is in tenant S3.
|
|
824
|
+
stepsKey String? // pointer for steps Json
|
|
825
|
+
gherkinKey String? // pointer for gherkin String
|
|
826
|
+
|
|
827
|
+
// SHA-256 of normalized(title+steps+expectedResult+preconditions) — used for content-hash dedup.
|
|
828
|
+
// Replaces title-only dedup so AI-paraphrased duplicates are caught cross-pass.
|
|
829
|
+
contentHash String?
|
|
830
|
+
// SHA-256 of sorted(groundedOn element IDs) + kind — structural dedup: same DOM flow + kind = same test.
|
|
831
|
+
groundingHash String?
|
|
832
|
+
|
|
833
|
+
// Extracted Playwright locator expressions — computed from Screen.elements (crawl path)
|
|
834
|
+
// or from on-demand page extraction (import path). Stored as PlaywrightLocator[] JSON.
|
|
835
|
+
// Shape: [{key, expression, elementType, confidence, source, description}]
|
|
836
|
+
locatorsJson Json?
|
|
837
|
+
// ISO timestamp of last locator extraction (used to show staleness in UI)
|
|
838
|
+
locatorsExtractedAt DateTime?
|
|
839
|
+
// Per-step screenshot/evidence trail from CAPTURE_STEPS jobs.
|
|
840
|
+
captureStepScreenshots Json?
|
|
841
|
+
|
|
842
|
+
versions TestCaseVersion[]
|
|
843
|
+
sourceEnhancementId String?
|
|
844
|
+
sourceEnhancement ContextEnhancement? @relation("TestCaseSourceEnhancement", fields: [sourceEnhancementId], references: [id], onDelete: SetNull)
|
|
845
|
+
contextQualityScore Int?
|
|
846
|
+
crawlDiffId String?
|
|
847
|
+
crawlDiff CrawlDiff? @relation("TestCaseCrawlDiff", fields: [crawlDiffId], references: [id], onDelete: SetNull)
|
|
848
|
+
bugSignalRefs BugSignal[] @relation("BugSignalAffectedTestCases")
|
|
849
|
+
|
|
850
|
+
createdAt DateTime @default(now())
|
|
851
|
+
updatedAt DateTime @updatedAt
|
|
852
|
+
|
|
853
|
+
@@index([tenantId, projectId])
|
|
854
|
+
@@index([tenantId, suiteId])
|
|
855
|
+
@@index([suiteId, contentHash])
|
|
856
|
+
@@index([suiteId, groundingHash])
|
|
857
|
+
@@index([screenId])
|
|
858
|
+
@@index([priority])
|
|
859
|
+
@@index([source])
|
|
860
|
+
@@index([artifactId])
|
|
861
|
+
@@index([scenarioId])
|
|
862
|
+
@@index([traceTo])
|
|
863
|
+
@@index([tenantId, projectId, suspect])
|
|
864
|
+
@@index([tenantId, projectId, quarantined])
|
|
865
|
+
@@index([dataSetId])
|
|
866
|
+
@@index([status, projectId])
|
|
867
|
+
@@index([sourceEnhancementId])
|
|
868
|
+
@@index([crawlDiffId])
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Immutable snapshot of a TestCase taken before each save (edit/review).
|
|
872
|
+
// versionNum increments per testCase starting at 1. The snapshot field holds
|
|
873
|
+
// the full TestCase row at time of capture for complete restore fidelity.
|
|
874
|
+
model TestCaseVersion {
|
|
875
|
+
id String @id @default(cuid())
|
|
876
|
+
testCaseId String
|
|
877
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id], onDelete: Cascade)
|
|
878
|
+
versionNum Int
|
|
879
|
+
title String
|
|
880
|
+
steps Json?
|
|
881
|
+
preconditions String?
|
|
882
|
+
expectedResult String?
|
|
883
|
+
kind String?
|
|
884
|
+
priority String?
|
|
885
|
+
snapshot Json // full TestCase fields at time of snapshot
|
|
886
|
+
createdAt DateTime @default(now())
|
|
887
|
+
createdBy String? // userId
|
|
888
|
+
tenantId String
|
|
889
|
+
|
|
890
|
+
@@unique([testCaseId, versionNum])
|
|
891
|
+
@@index([tenantId, testCaseId])
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
model TestData {
|
|
895
|
+
id String @id @default(cuid())
|
|
896
|
+
tenantId String
|
|
897
|
+
projectId String
|
|
898
|
+
testCaseId String
|
|
899
|
+
// VALID | INVALID | EDGE | SYNTHETIC
|
|
900
|
+
kind String
|
|
901
|
+
// SYNTHETIC | FALLBACK | PRODUCTION | STAGING | SANDBOX | DUMMY | IMPORTED
|
|
902
|
+
source String @default("SYNTHETIC")
|
|
903
|
+
payload Json
|
|
904
|
+
// When set, this row is a historical version superseded by a later regeneration
|
|
905
|
+
supersededAt DateTime?
|
|
906
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id])
|
|
907
|
+
|
|
908
|
+
@@index([tenantId, projectId])
|
|
909
|
+
@@index([tenantId, testCaseId])
|
|
910
|
+
@@index([tenantId, source])
|
|
911
|
+
@@index([tenantId, testCaseId, supersededAt])
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
model TestScript {
|
|
915
|
+
id String @id @default(cuid())
|
|
916
|
+
tenantId String
|
|
917
|
+
projectId String
|
|
918
|
+
testCaseId String
|
|
919
|
+
// The runnable, exportable code (e.g. Playwright).
|
|
920
|
+
language String @default("playwright-ts")
|
|
921
|
+
code String @db.Text
|
|
922
|
+
filePath String? // relative path in the repo, e.g. src/MySuite/locators/MyClass.locators.ts
|
|
923
|
+
// When tenant S3 is configured, code is stored there and this holds the object key.
|
|
924
|
+
// Resolver: if codeKey is set, read from S3; otherwise use code field directly.
|
|
925
|
+
codeKey String?
|
|
926
|
+
// PENDING | PASS | FAIL | SKIPPED
|
|
927
|
+
validationStatus String @default("PENDING")
|
|
928
|
+
validationErrors String? @db.Text
|
|
929
|
+
// Whether this script was pushed to a GitHub PR
|
|
930
|
+
pushedToPR Boolean @default(false)
|
|
931
|
+
pushedPRId String? // GitHubPR.id
|
|
932
|
+
scriptContextSnapshot Json?
|
|
933
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id])
|
|
934
|
+
|
|
935
|
+
@@unique([testCaseId, language])
|
|
936
|
+
@@index([tenantId, projectId])
|
|
937
|
+
@@index([tenantId, testCaseId])
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
model ScriptBundle {
|
|
941
|
+
id String @id @default(cuid())
|
|
942
|
+
tenantId String
|
|
943
|
+
projectId String
|
|
944
|
+
// SUITE | PACK
|
|
945
|
+
scopeType String
|
|
946
|
+
// TestSuite.id for suites, pack id such as regression/smoke/sanity for packs.
|
|
947
|
+
scopeId String
|
|
948
|
+
language String @default("playwright-ts")
|
|
949
|
+
files Json
|
|
950
|
+
basePage String?
|
|
951
|
+
caseCount Int @default(0)
|
|
952
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
953
|
+
createdAt DateTime @default(now())
|
|
954
|
+
updatedAt DateTime @updatedAt
|
|
955
|
+
|
|
956
|
+
@@unique([tenantId, projectId, scopeType, scopeId, language])
|
|
957
|
+
@@index([tenantId, projectId])
|
|
958
|
+
@@index([tenantId, projectId, scopeType, scopeId])
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Stores script alignment verification results — score, aligned flag, and issues
|
|
962
|
+
// from the AI verifier. Uses a proper dedicated table instead of abusing TestScript.
|
|
963
|
+
model AlignmentResult {
|
|
964
|
+
id String @id @default(cuid())
|
|
965
|
+
tenantId String
|
|
966
|
+
projectId String
|
|
967
|
+
testCaseId String
|
|
968
|
+
language String
|
|
969
|
+
score Int @default(0)
|
|
970
|
+
aligned Boolean @default(false)
|
|
971
|
+
issues Json @default("[]")
|
|
972
|
+
// Deterministic step coverage (matched steps ÷ total steps), independent of the LLM score.
|
|
973
|
+
stepCoveragePct Float?
|
|
974
|
+
// { totalSteps, coveredSteps, coveragePct, assertionCoverage, perStep: [...] }
|
|
975
|
+
stepCoverage Json?
|
|
976
|
+
verifiedAt DateTime @default(now())
|
|
977
|
+
createdAt DateTime @default(now())
|
|
978
|
+
updatedAt DateTime @updatedAt
|
|
979
|
+
|
|
980
|
+
@@unique([testCaseId, language])
|
|
981
|
+
@@index([tenantId, projectId])
|
|
982
|
+
@@index([tenantId, testCaseId])
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Snapshot table — captures old TestScript code before each regeneration.
|
|
986
|
+
// The TestScript table always holds the latest version; history lives here.
|
|
987
|
+
model TestScriptVersion {
|
|
988
|
+
id String @id @default(cuid())
|
|
989
|
+
tenantId String
|
|
990
|
+
testCaseId String
|
|
991
|
+
language String
|
|
992
|
+
code String @db.Text
|
|
993
|
+
versionNum Int
|
|
994
|
+
createdAt DateTime @default(now())
|
|
995
|
+
|
|
996
|
+
@@index([testCaseId, language])
|
|
997
|
+
@@index([tenantId, testCaseId])
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Snapshot table — captures SkillFile content (especially TEST_PLAN) before each update.
|
|
1001
|
+
model SkillFileVersion {
|
|
1002
|
+
id String @id @default(cuid())
|
|
1003
|
+
tenantId String
|
|
1004
|
+
projectId String
|
|
1005
|
+
skillFileId String
|
|
1006
|
+
kind String
|
|
1007
|
+
content String @db.Text
|
|
1008
|
+
versionNum Int
|
|
1009
|
+
createdAt DateTime @default(now())
|
|
1010
|
+
|
|
1011
|
+
@@index([skillFileId, versionNum])
|
|
1012
|
+
@@index([tenantId, projectId, kind])
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// ---- Workspace / Tenant Settings -------------------------------------------
|
|
1016
|
+
|
|
1017
|
+
// Workspace-level configuration per tenant — LLM provider, notifications, etc.
|
|
1018
|
+
// Sensitive fields (API keys) use the _enc suffix, encrypted with AES-256-GCM.
|
|
1019
|
+
model TenantSetting {
|
|
1020
|
+
id String @id @default(cuid())
|
|
1021
|
+
tenantId String
|
|
1022
|
+
// "LLM" | "NOTIFICATIONS" | "BRANDING"
|
|
1023
|
+
category String
|
|
1024
|
+
// JSON blob; sensitive fields end with _enc (same pattern as Connector)
|
|
1025
|
+
config Json @default("{}")
|
|
1026
|
+
dataRetentionDays Int @default(365)
|
|
1027
|
+
updatedAt DateTime @default(now())
|
|
1028
|
+
|
|
1029
|
+
@@unique([tenantId, category])
|
|
1030
|
+
@@index([tenantId])
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Per-tenant override of a platform feature-flag default. Absence of a row for
|
|
1034
|
+
// a given (tenantId, key) means "use the default" (see FEATURE_FLAG_DEFAULTS
|
|
1035
|
+
// in @detiq/core) — this table only ever stores explicit overrides, not every
|
|
1036
|
+
// flag's state for every tenant.
|
|
1037
|
+
model TenantFeatureFlag {
|
|
1038
|
+
id String @id @default(cuid())
|
|
1039
|
+
tenantId String
|
|
1040
|
+
key String
|
|
1041
|
+
enabled Boolean
|
|
1042
|
+
updatedAt DateTime @default(now())
|
|
1043
|
+
|
|
1044
|
+
@@unique([tenantId, key])
|
|
1045
|
+
@@index([tenantId])
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
model PlatformJob {
|
|
1049
|
+
id String @id @default(cuid())
|
|
1050
|
+
tenantId String
|
|
1051
|
+
projectId String
|
|
1052
|
+
type String @default("CRAWL") // CRAWL | GENERATE_TESTS | VISUAL_REGRESSION
|
|
1053
|
+
name String? // EventName_YYYY-MM-DD_timestamp for tracking
|
|
1054
|
+
// QUEUED | RUNNING | DONE | FAILED
|
|
1055
|
+
status String @default("QUEUED")
|
|
1056
|
+
config Json
|
|
1057
|
+
result Json?
|
|
1058
|
+
error String?
|
|
1059
|
+
failureType String? // FailureType enum: TRANSIENT | INFRASTRUCTURE | LOGICAL | FATAL
|
|
1060
|
+
progress Int @default(0)
|
|
1061
|
+
queuedAt DateTime @default(now())
|
|
1062
|
+
startedAt DateTime?
|
|
1063
|
+
finishedAt DateTime?
|
|
1064
|
+
// Identifies which worker process claimed this job (host:pid) — diagnostics +
|
|
1065
|
+
// lets the stale-job sweep tell a legitimately long-running job apart from
|
|
1066
|
+
// one whose worker died without updating status.
|
|
1067
|
+
workerId String?
|
|
1068
|
+
// Bumped on every progress update while RUNNING. The stale-job sweep compares
|
|
1069
|
+
// this (not startedAt) against its timeout, so a slow-but-alive job doesn't
|
|
1070
|
+
// get requeued and double-processed by another worker.
|
|
1071
|
+
heartbeatAt DateTime?
|
|
1072
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1073
|
+
|
|
1074
|
+
// Token Tracking
|
|
1075
|
+
totalPromptTokens Int?
|
|
1076
|
+
totalCompletionTokens Int?
|
|
1077
|
+
totalCacheReadTokens Int?
|
|
1078
|
+
totalEstimatedCostUsd Float?
|
|
1079
|
+
llmCallCount Int @default(0)
|
|
1080
|
+
|
|
1081
|
+
// Gap 30: Dead-letter queue / automatic retry
|
|
1082
|
+
retryCount Int @default(0)
|
|
1083
|
+
nextRetryAt DateTime?
|
|
1084
|
+
dlq Boolean @default(false)
|
|
1085
|
+
dlqReason String?
|
|
1086
|
+
|
|
1087
|
+
// Worker lease / fencing (GAP-03)
|
|
1088
|
+
leaseExpiresAt DateTime?
|
|
1089
|
+
fencingToken String?
|
|
1090
|
+
|
|
1091
|
+
// Durable checkpoint for CRAWL (GAP-04)
|
|
1092
|
+
checkpoint Json?
|
|
1093
|
+
checkpointAt DateTime?
|
|
1094
|
+
|
|
1095
|
+
// Parent-child relationship (GAP-06)
|
|
1096
|
+
parentJobId String?
|
|
1097
|
+
rootJobId String?
|
|
1098
|
+
parent PlatformJob? @relation("JobParent", fields: [parentJobId], references: [id])
|
|
1099
|
+
children PlatformJob[] @relation("JobParent")
|
|
1100
|
+
|
|
1101
|
+
// Current execution step as indexed column (GAP-09)
|
|
1102
|
+
currentStep String?
|
|
1103
|
+
|
|
1104
|
+
// Re-queue timestamp for accurate queue-wait time on retries (GAP-10/22)
|
|
1105
|
+
requeuedAt DateTime?
|
|
1106
|
+
|
|
1107
|
+
// DLQ lifecycle (GAP-13)
|
|
1108
|
+
dlqAt DateTime?
|
|
1109
|
+
dlqRedriveCount Int @default(0)
|
|
1110
|
+
|
|
1111
|
+
// User attribution (GAP-16)
|
|
1112
|
+
submittedBy String?
|
|
1113
|
+
|
|
1114
|
+
// Inline (API-in-process) vs Worker mode (GAP-21)
|
|
1115
|
+
inline Boolean @default(false)
|
|
1116
|
+
|
|
1117
|
+
// Priority — higher = sooner (GAP-23)
|
|
1118
|
+
priority Int @default(0)
|
|
1119
|
+
|
|
1120
|
+
// Attempts relation
|
|
1121
|
+
attempts JobAttempt[]
|
|
1122
|
+
|
|
1123
|
+
@@index([tenantId, projectId])
|
|
1124
|
+
@@index([tenantId, projectId, queuedAt])
|
|
1125
|
+
@@index([status])
|
|
1126
|
+
@@index([status, queuedAt])
|
|
1127
|
+
@@index([status, workerId])
|
|
1128
|
+
@@index([status, dlq, nextRetryAt])
|
|
1129
|
+
@@index([status, nextRetryAt])
|
|
1130
|
+
@@index([parentJobId])
|
|
1131
|
+
@@index([tenantId, status])
|
|
1132
|
+
@@index([priority, queuedAt])
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
model JobAttempt {
|
|
1136
|
+
id String @id @default(cuid())
|
|
1137
|
+
jobId String
|
|
1138
|
+
tenantId String
|
|
1139
|
+
attemptNumber Int
|
|
1140
|
+
workerId String?
|
|
1141
|
+
fencingToken String?
|
|
1142
|
+
startedAt DateTime @default(now())
|
|
1143
|
+
finishedAt DateTime?
|
|
1144
|
+
status String // RUNNING | DONE | FAILED | CANCELLED
|
|
1145
|
+
error String?
|
|
1146
|
+
progress Int @default(0)
|
|
1147
|
+
job PlatformJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
|
|
1148
|
+
|
|
1149
|
+
@@index([jobId])
|
|
1150
|
+
@@index([tenantId])
|
|
1151
|
+
@@index([jobId, attemptNumber])
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ---- Self-Healing Element Fingerprints -------------------------------------
|
|
1155
|
+
|
|
1156
|
+
model ElementFingerprint {
|
|
1157
|
+
id String @id @default(cuid())
|
|
1158
|
+
tenantId String
|
|
1159
|
+
projectId String
|
|
1160
|
+
elementId String @unique
|
|
1161
|
+
// Multiple selectors ranked by stability: [{type, value, confidence}]
|
|
1162
|
+
selectors Json
|
|
1163
|
+
lastHealedAt DateTime?
|
|
1164
|
+
healCount Int @default(0)
|
|
1165
|
+
element Element @relation(fields: [elementId], references: [id])
|
|
1166
|
+
|
|
1167
|
+
@@index([tenantId, projectId])
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
// ---- Session Recording (Record-and-Replay + Production Capture) ------------
|
|
1171
|
+
|
|
1172
|
+
model UserSession {
|
|
1173
|
+
id String @id @default(cuid())
|
|
1174
|
+
tenantId String
|
|
1175
|
+
projectId String
|
|
1176
|
+
// RECORDED | PRODUCTION
|
|
1177
|
+
source String
|
|
1178
|
+
startUrl String
|
|
1179
|
+
// Array of {type, target, value, timestamp, viewport}
|
|
1180
|
+
events Json
|
|
1181
|
+
duration Int?
|
|
1182
|
+
// PENDING | PROCESSING | CONVERTED | FAILED
|
|
1183
|
+
status String @default("PENDING")
|
|
1184
|
+
convertedSuiteId String?
|
|
1185
|
+
recordedAt DateTime @default(now())
|
|
1186
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1187
|
+
|
|
1188
|
+
@@index([tenantId, projectId])
|
|
1189
|
+
@@index([status])
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// ---- Visual Regression Testing ---------------------------------------------
|
|
1193
|
+
|
|
1194
|
+
model VisualBaseline {
|
|
1195
|
+
id String @id @default(cuid())
|
|
1196
|
+
tenantId String
|
|
1197
|
+
projectId String
|
|
1198
|
+
screenId String
|
|
1199
|
+
viewport String @default("DESKTOP")
|
|
1200
|
+
browser String @default("chromium")
|
|
1201
|
+
storagePath String
|
|
1202
|
+
cloudUrl String?
|
|
1203
|
+
approvedBy String?
|
|
1204
|
+
createdAt DateTime @default(now())
|
|
1205
|
+
gitBranch String @default("main")
|
|
1206
|
+
gitCommit String?
|
|
1207
|
+
// Masking zones — regions excluded from visual diff comparison.
|
|
1208
|
+
// Array of {x, y, width, height, label} rectangles (user-drawn exclusions for dynamic content).
|
|
1209
|
+
ignoredRegions Json @default("[]")
|
|
1210
|
+
// Snapshot of element bounding boxes at baseline creation time for layout drift detection.
|
|
1211
|
+
// Array of {meaning, role, boundingRect} objects.
|
|
1212
|
+
elementSnapshot Json?
|
|
1213
|
+
screen Screen @relation(fields: [screenId], references: [id])
|
|
1214
|
+
diffs VisualDiff[]
|
|
1215
|
+
|
|
1216
|
+
@@index([tenantId, projectId])
|
|
1217
|
+
@@unique([screenId, viewport, gitBranch, browser])
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
model VisualDiff {
|
|
1221
|
+
id String @id @default(cuid())
|
|
1222
|
+
tenantId String
|
|
1223
|
+
baselineId String
|
|
1224
|
+
newScreenshotPath String
|
|
1225
|
+
newScreenshotCloudUrl String?
|
|
1226
|
+
diffImageCloudUrl String?
|
|
1227
|
+
// 0-100; 0 = identical
|
|
1228
|
+
diffScore Float
|
|
1229
|
+
// NONE | MINOR | SIGNIFICANT | CRITICAL
|
|
1230
|
+
changeType String
|
|
1231
|
+
// [{description, location, severity}]
|
|
1232
|
+
changes Json?
|
|
1233
|
+
// APPROVE | REVIEW | FLAG | BLOCK
|
|
1234
|
+
recommendation String?
|
|
1235
|
+
bugReportDraft Json?
|
|
1236
|
+
// AI-generated one-sentence summary of what changed (or "No meaningful changes detected")
|
|
1237
|
+
aiSummary String?
|
|
1238
|
+
// PENDING | APPROVED | FLAGGED | BLOCKED
|
|
1239
|
+
status String @default("PENDING")
|
|
1240
|
+
reviewedBy String?
|
|
1241
|
+
bugSignalVisualDiffId String?
|
|
1242
|
+
bugSignalRef BugSignal? @relation("BugSignalVisualDiffs", fields: [bugSignalVisualDiffId], references: [id], onDelete: SetNull)
|
|
1243
|
+
createdAt DateTime @default(now())
|
|
1244
|
+
baseline VisualBaseline @relation(fields: [baselineId], references: [id])
|
|
1245
|
+
|
|
1246
|
+
@@index([tenantId, baselineId])
|
|
1247
|
+
@@index([status])
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// ---- Human + AI Hybrid Review Queue ----------------------------------------
|
|
1251
|
+
|
|
1252
|
+
model HumanReviewItem {
|
|
1253
|
+
id String @id @default(cuid())
|
|
1254
|
+
tenantId String
|
|
1255
|
+
projectId String
|
|
1256
|
+
testCaseId String
|
|
1257
|
+
reason String
|
|
1258
|
+
confidence Float
|
|
1259
|
+
context Json?
|
|
1260
|
+
// PENDING | IN_REVIEW | APPROVED | REJECTED
|
|
1261
|
+
status String @default("PENDING")
|
|
1262
|
+
assignedTo String?
|
|
1263
|
+
resolution String?
|
|
1264
|
+
reviewedAt DateTime?
|
|
1265
|
+
createdAt DateTime @default(now())
|
|
1266
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id])
|
|
1267
|
+
|
|
1268
|
+
@@index([tenantId, projectId])
|
|
1269
|
+
@@index([status])
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// ---- Per-step visual evidence — persisted AI+human element matching -----------
|
|
1273
|
+
// One row per (testCaseId, stepIndex). Populated on first evidence fetch and
|
|
1274
|
+
// can be corrected by a QA reviewer (source -> 'human_verified').
|
|
1275
|
+
// When the parent Screen is re-crawled and elementHash changes, source is set
|
|
1276
|
+
// to 'stale' so reviewers know to re-verify.
|
|
1277
|
+
|
|
1278
|
+
model StepEvidence {
|
|
1279
|
+
id String @id @default(cuid())
|
|
1280
|
+
tenantId String
|
|
1281
|
+
testCaseId String
|
|
1282
|
+
stepIndex Int // 0-based index matching TestCase.steps array
|
|
1283
|
+
stepText String // snapshot of step text when evidence was computed
|
|
1284
|
+
|
|
1285
|
+
// The screenshot that best captures the screen state for this step.
|
|
1286
|
+
// Null = use the Screen's latest screenshot (default).
|
|
1287
|
+
screenshotId String?
|
|
1288
|
+
screenshot Screenshot? @relation(fields: [screenshotId], references: [id], onDelete: SetNull)
|
|
1289
|
+
|
|
1290
|
+
// JSON array of Element.id strings that are relevant to this step
|
|
1291
|
+
elementIds Json @default("[]")
|
|
1292
|
+
|
|
1293
|
+
// Highest confidence score among matched elements (0.0-1.0)
|
|
1294
|
+
confidence Float @default(0)
|
|
1295
|
+
|
|
1296
|
+
// 'ai_heuristic' | 'ai_semantic' | 'human_verified' | 'stale'
|
|
1297
|
+
source String @default("ai_heuristic")
|
|
1298
|
+
|
|
1299
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id], onDelete: Cascade)
|
|
1300
|
+
createdAt DateTime @default(now())
|
|
1301
|
+
updatedAt DateTime @updatedAt
|
|
1302
|
+
|
|
1303
|
+
@@unique([testCaseId, stepIndex])
|
|
1304
|
+
@@index([tenantId, testCaseId])
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// ---- Manual QA step validation — human-in-the-loop per-step annotation -------
|
|
1308
|
+
// Inspired by TestRail step results. A QA reviewer marks each step
|
|
1309
|
+
// PASS / FAIL / SKIP / BLOCKED and optionally uploads screenshot evidence.
|
|
1310
|
+
|
|
1311
|
+
model StepValidation {
|
|
1312
|
+
id String @id @default(cuid())
|
|
1313
|
+
tenantId String
|
|
1314
|
+
testCaseId String
|
|
1315
|
+
stepIndex Int // 0-based index matching TestCase.steps array
|
|
1316
|
+
// PASS | FAIL | SKIP | BLOCKED
|
|
1317
|
+
result String
|
|
1318
|
+
comment String?
|
|
1319
|
+
// URL of an uploaded screenshot or other attachment (S3 cloudUrl)
|
|
1320
|
+
attachmentUrl String?
|
|
1321
|
+
// Optional: ties this validation to a specific TestCaseRun
|
|
1322
|
+
testCaseRunId String?
|
|
1323
|
+
testCaseRun TestCaseRun? @relation(fields: [testCaseRunId], references: [id], onDelete: SetNull)
|
|
1324
|
+
reviewerId String?
|
|
1325
|
+
reviewer User? @relation(fields: [reviewerId], references: [id], onDelete: SetNull)
|
|
1326
|
+
createdAt DateTime @default(now())
|
|
1327
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id], onDelete: Cascade)
|
|
1328
|
+
|
|
1329
|
+
@@index([tenantId, testCaseId])
|
|
1330
|
+
@@index([testCaseId, stepIndex])
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// ---- Per-Test-Case Execution Runs ------------------------------------------
|
|
1334
|
+
|
|
1335
|
+
model TestCaseRun {
|
|
1336
|
+
id String @id @default(cuid())
|
|
1337
|
+
testCaseId String
|
|
1338
|
+
tenantId String
|
|
1339
|
+
// PENDING | RUNNING | PASSED | FAILED | ERROR | FLAKY (maxRuns>1, outcomes diverged)
|
|
1340
|
+
status String @default("PENDING")
|
|
1341
|
+
// Array<ExecutionStep> generated by test-script-agent
|
|
1342
|
+
executionPlan Json?
|
|
1343
|
+
// Array<StepResult> from executor
|
|
1344
|
+
stepResults Json?
|
|
1345
|
+
totalDuration Int?
|
|
1346
|
+
// 0-indexed step number that failed
|
|
1347
|
+
errorStep Int?
|
|
1348
|
+
errorMessage String?
|
|
1349
|
+
screenshotPath String?
|
|
1350
|
+
viewport String @default("DESKTOP")
|
|
1351
|
+
// chromium | firefox | webkit — engine used for this run (executeTestPlan already
|
|
1352
|
+
// supports all 3; this just records/selects which one)
|
|
1353
|
+
browser String @default("chromium")
|
|
1354
|
+
createdAt DateTime @default(now())
|
|
1355
|
+
completedAt DateTime?
|
|
1356
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id], onDelete: Cascade)
|
|
1357
|
+
stepValidations StepValidation[]
|
|
1358
|
+
|
|
1359
|
+
@@index([tenantId, testCaseId])
|
|
1360
|
+
@@index([tenantId, status, createdAt])
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// ---- Test Run Tracking -----------------------------------------------------
|
|
1364
|
+
|
|
1365
|
+
model TestRun {
|
|
1366
|
+
id String @id @default(cuid())
|
|
1367
|
+
tenantId String
|
|
1368
|
+
projectId String
|
|
1369
|
+
suiteId String?
|
|
1370
|
+
// MANUAL | SCHEDULED | GIT_PUSH | POST_CRAWL | GITHUB_ACTION
|
|
1371
|
+
trigger String @default("MANUAL")
|
|
1372
|
+
// RUNNING | COMPLETED | FAILED | CANCELLED
|
|
1373
|
+
status String @default("RUNNING")
|
|
1374
|
+
// {browser, viewport, baseUrl, env, browsers?: string[], shards?: number}
|
|
1375
|
+
config Json?
|
|
1376
|
+
// {total, passed, failed, skipped, duration}
|
|
1377
|
+
summary Json?
|
|
1378
|
+
// Parallel shard config — null means single-shard (legacy) run
|
|
1379
|
+
totalShards Int?
|
|
1380
|
+
// CI/CD integration fields
|
|
1381
|
+
githubRepo String?
|
|
1382
|
+
githubSha String?
|
|
1383
|
+
githubRef String?
|
|
1384
|
+
triggeredBy String?
|
|
1385
|
+
startedAt DateTime @default(now())
|
|
1386
|
+
finishedAt DateTime?
|
|
1387
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1388
|
+
results TestResult[]
|
|
1389
|
+
shards TestRunShard[]
|
|
1390
|
+
|
|
1391
|
+
@@index([tenantId, projectId])
|
|
1392
|
+
@@index([status])
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// One row per parallel shard within a TestRun.
|
|
1396
|
+
// Each shard runs a subset of test cases, optionally on a different browser.
|
|
1397
|
+
model TestRunShard {
|
|
1398
|
+
id String @id @default(cuid())
|
|
1399
|
+
runId String
|
|
1400
|
+
tenantId String
|
|
1401
|
+
shardIndex Int // 0-based index within the run
|
|
1402
|
+
browser String @default("chromium") // chromium | firefox | webkit
|
|
1403
|
+
// PENDING | RUNNING | COMPLETED | FAILED
|
|
1404
|
+
status String @default("PENDING")
|
|
1405
|
+
// {total, passed, failed, skipped, duration}
|
|
1406
|
+
summary Json?
|
|
1407
|
+
startedAt DateTime?
|
|
1408
|
+
finishedAt DateTime?
|
|
1409
|
+
run TestRun @relation(fields: [runId], references: [id], onDelete: Cascade)
|
|
1410
|
+
|
|
1411
|
+
@@unique([runId, shardIndex, browser])
|
|
1412
|
+
@@index([runId])
|
|
1413
|
+
@@index([tenantId])
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
model TestResult {
|
|
1417
|
+
id String @id @default(cuid())
|
|
1418
|
+
tenantId String
|
|
1419
|
+
runId String
|
|
1420
|
+
testCaseId String
|
|
1421
|
+
// PASSED | FAILED | SKIPPED | ERROR
|
|
1422
|
+
status String
|
|
1423
|
+
duration Int?
|
|
1424
|
+
error String?
|
|
1425
|
+
screenshotPath String?
|
|
1426
|
+
logs Json?
|
|
1427
|
+
healedAt DateTime?
|
|
1428
|
+
// AI-generated root cause summary (Phase 4.1)
|
|
1429
|
+
failureAnalysis String?
|
|
1430
|
+
failureAnalysisAt DateTime?
|
|
1431
|
+
// Data-driven execution — which row produced this result (Phase 4.3)
|
|
1432
|
+
dataRowId String?
|
|
1433
|
+
dataRowIndex Int?
|
|
1434
|
+
bugSignalId String?
|
|
1435
|
+
bugSignalRef BugSignal? @relation("BugSignalTestResults", fields: [bugSignalId], references: [id], onDelete: SetNull)
|
|
1436
|
+
run TestRun @relation(fields: [runId], references: [id])
|
|
1437
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id])
|
|
1438
|
+
|
|
1439
|
+
@@index([tenantId, runId])
|
|
1440
|
+
@@index([tenantId, runId, status])
|
|
1441
|
+
@@index([testCaseId])
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// ---- Connectors, audit, change tracking ------------------------------------
|
|
1445
|
+
|
|
1446
|
+
model Connector {
|
|
1447
|
+
id String @id @default(cuid())
|
|
1448
|
+
tenantId String
|
|
1449
|
+
projectId String
|
|
1450
|
+
// GITHUB | JIRA | FIGMA | CONFLUENCE | PAGERDUTY | SERVICENOW | SALESFORCE
|
|
1451
|
+
kind String
|
|
1452
|
+
// A reference/handle to where credentials live in the secrets manager.
|
|
1453
|
+
// NEVER store raw secrets in the database.
|
|
1454
|
+
secretRef String
|
|
1455
|
+
config Json?
|
|
1456
|
+
createdAt DateTime @default(now())
|
|
1457
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1458
|
+
|
|
1459
|
+
@@index([tenantId, projectId])
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// OAuth 2.0 connections — replaces manual token entry in Connector.
|
|
1463
|
+
// All token fields use the _enc suffix and are AES-256-GCM encrypted at rest.
|
|
1464
|
+
// Never return raw token values in API responses.
|
|
1465
|
+
model OAuthConnection {
|
|
1466
|
+
id String @id @default(cuid())
|
|
1467
|
+
tenantId String
|
|
1468
|
+
projectId String
|
|
1469
|
+
|
|
1470
|
+
// GITHUB | JIRA | FIGMA
|
|
1471
|
+
provider String
|
|
1472
|
+
|
|
1473
|
+
// Encrypted access token (AES-256-GCM, base64).
|
|
1474
|
+
// Null for GitHub App (uses IAT generated on-the-fly from private key).
|
|
1475
|
+
accessToken_enc String?
|
|
1476
|
+
// Encrypted refresh token. Null for GitHub App.
|
|
1477
|
+
// Jira: rotating — always store new one issued on refresh.
|
|
1478
|
+
refreshToken_enc String?
|
|
1479
|
+
// When the access token expires. Null = does not expire (rare).
|
|
1480
|
+
tokenExpiresAt DateTime?
|
|
1481
|
+
// When the refresh token expires (Jira ~180 days unused; GitHub 6 months).
|
|
1482
|
+
refreshTokenExpiresAt DateTime?
|
|
1483
|
+
|
|
1484
|
+
// CONNECTED | NEEDS_REAUTH | DISCONNECTED
|
|
1485
|
+
// Only set to NEEDS_REAUTH on definitive auth errors (400/401 with invalid_grant).
|
|
1486
|
+
// Proactive refresh failures use backoff, not status change.
|
|
1487
|
+
status String @default("CONNECTED")
|
|
1488
|
+
|
|
1489
|
+
// Human-readable identity — who/what authorized this connection.
|
|
1490
|
+
// e.g. "Acme Corp (org)" for GitHub App, "user@company.atlassian.net" for Jira.
|
|
1491
|
+
connectedAs String?
|
|
1492
|
+
|
|
1493
|
+
// GitHub App: numeric installation ID (needed to generate IAT)
|
|
1494
|
+
installationId String?
|
|
1495
|
+
|
|
1496
|
+
// Jira Cloud: cloudId from /oauth/token/accessible-resources
|
|
1497
|
+
// Required to construct API base URL: api.atlassian.com/ex/jira/{cloudId}/...
|
|
1498
|
+
cloudId String?
|
|
1499
|
+
|
|
1500
|
+
// Granted OAuth scopes (space-separated string or JSON array as string)
|
|
1501
|
+
scopes String?
|
|
1502
|
+
|
|
1503
|
+
// Encrypted PAT — fallback for Jira Data Center / on-prem (no 3LO available)
|
|
1504
|
+
pat_enc String?
|
|
1505
|
+
// Jira Data Center base URL (e.g. https://jira.internal.company.com)
|
|
1506
|
+
dcBaseUrl String?
|
|
1507
|
+
|
|
1508
|
+
createdAt DateTime @default(now())
|
|
1509
|
+
updatedAt DateTime @updatedAt
|
|
1510
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1511
|
+
|
|
1512
|
+
// One active connection per provider per project
|
|
1513
|
+
@@unique([projectId, provider])
|
|
1514
|
+
@@index([tenantId, projectId])
|
|
1515
|
+
@@index([tenantId, provider])
|
|
1516
|
+
@@index([status])
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// A real, revocable login session — the row's id IS the JWT's `jti` claim.
|
|
1520
|
+
// checked on every request (see resolveContext in packages/api). Deliberately
|
|
1521
|
+
// distinct from UserSession (that model is unrelated: recorded browser test
|
|
1522
|
+
// sessions for replay/conversion, nothing to do with authentication).
|
|
1523
|
+
// Admin-impersonation tokens get one too (minted via mintSessionToken, same
|
|
1524
|
+
// as a real login) so "Stop Impersonating" can actually revoke it server-side
|
|
1525
|
+
// instead of only clearing the admin's local browser storage — the 30-minute
|
|
1526
|
+
// natural expiry alone isn't enough for a privileged-access session that's
|
|
1527
|
+
// meant to be endable on demand. Not created for the transient "root" token
|
|
1528
|
+
// issued when a login can't yet pick a single tenant (that token alone can't
|
|
1529
|
+
// reach any tenant-scoped route, so there is nothing meaningful to revoke).
|
|
1530
|
+
model AuthSession {
|
|
1531
|
+
id String @id // JWT jti claim — set from crypto.randomUUID() in mintSessionToken()
|
|
1532
|
+
userId String
|
|
1533
|
+
tenantId String
|
|
1534
|
+
userAgent String?
|
|
1535
|
+
ipAddress String?
|
|
1536
|
+
createdAt DateTime @default(now())
|
|
1537
|
+
lastSeenAt DateTime @default(now())
|
|
1538
|
+
revokedAt DateTime?
|
|
1539
|
+
|
|
1540
|
+
@@index([userId])
|
|
1541
|
+
@@index([tenantId])
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// One-time backup codes for TOTP MFA recovery (each usable exactly once)
|
|
1545
|
+
model MfaRecoveryCode {
|
|
1546
|
+
id String @id @default(cuid())
|
|
1547
|
+
userId String
|
|
1548
|
+
codeHash String // bcrypt hash of the plain recovery code
|
|
1549
|
+
usedAt DateTime?
|
|
1550
|
+
createdAt DateTime @default(now())
|
|
1551
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
1552
|
+
|
|
1553
|
+
@@index([userId])
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// SSO configuration per tenant (SAML 2.0 or OIDC)
|
|
1557
|
+
model SsoConfig {
|
|
1558
|
+
id String @id @default(cuid())
|
|
1559
|
+
tenantId String @unique
|
|
1560
|
+
provider String // "SAML" | "OIDC"
|
|
1561
|
+
// SAML fields
|
|
1562
|
+
entryPoint String? // IdP SSO URL
|
|
1563
|
+
issuer String? // IdP entity ID / OIDC issuer URL
|
|
1564
|
+
cert String? // IdP signing certificate (PEM)
|
|
1565
|
+
// OIDC fields
|
|
1566
|
+
clientId String?
|
|
1567
|
+
clientSecret_enc String? // AES-256-GCM encrypted
|
|
1568
|
+
// Common
|
|
1569
|
+
defaultRole String @default("TESTER")
|
|
1570
|
+
emailDomains String[] // permitted email domains for JIT provisioning; empty = allow all
|
|
1571
|
+
jitProvisioning Boolean @default(false) // auto-create users on first SSO login
|
|
1572
|
+
enabled Boolean @default(false)
|
|
1573
|
+
// When true, password login is rejected for members of this tenant who
|
|
1574
|
+
// have an active membership here — they must use SSO. Defaults to false
|
|
1575
|
+
// (opt-in) so configuring SSO doesn't silently lock out existing password
|
|
1576
|
+
// users. See docs/100-GAPS-RBAC-SECURITY-ENTERPRISE.md §1 #42 (SSO-02).
|
|
1577
|
+
ssoRequired Boolean @default(false)
|
|
1578
|
+
// SCIM 2.0 bearer token for this tenant, stored as a SHA-256 hash (same
|
|
1579
|
+
// pattern as AgentToken.tokenHash) — never the plaintext, which is shown
|
|
1580
|
+
// once at generation time. Nullable: SCIM is off until an admin generates
|
|
1581
|
+
// one. Previously this field didn't exist at all, so per-tenant SCIM could
|
|
1582
|
+
// never work — see docs/100-GAPS-RBAC-SECURITY-ENTERPRISE.md §3 NEW-1.
|
|
1583
|
+
scimTokenHash String? @unique
|
|
1584
|
+
createdAt DateTime @default(now())
|
|
1585
|
+
updatedAt DateTime @updatedAt
|
|
1586
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// Audit trail for compliance (SOC 2 / EU AI Act) — Phase 3 expands this.
|
|
1590
|
+
model AuditLog {
|
|
1591
|
+
id String @id @default(cuid())
|
|
1592
|
+
tenantId String
|
|
1593
|
+
projectId String?
|
|
1594
|
+
actorId String?
|
|
1595
|
+
action String
|
|
1596
|
+
detail Json?
|
|
1597
|
+
ipAddress String?
|
|
1598
|
+
createdAt DateTime @default(now())
|
|
1599
|
+
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
|
|
1600
|
+
user User? @relation(fields: [actorId], references: [id])
|
|
1601
|
+
tenant Tenant @relation(fields: [tenantId], references: [id])
|
|
1602
|
+
|
|
1603
|
+
@@index([tenantId, createdAt])
|
|
1604
|
+
@@index([tenantId, action])
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
model AccessReview {
|
|
1608
|
+
id String @id @default(cuid())
|
|
1609
|
+
tenantId String
|
|
1610
|
+
reviewedBy String
|
|
1611
|
+
memberCount Int
|
|
1612
|
+
removedCount Int @default(0)
|
|
1613
|
+
completedAt DateTime @default(now())
|
|
1614
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
1615
|
+
|
|
1616
|
+
@@index([tenantId])
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// Local agent pairing token — hashed, never stored plain.
|
|
1620
|
+
model AgentToken {
|
|
1621
|
+
id String @id @default(cuid())
|
|
1622
|
+
tenantId String
|
|
1623
|
+
projectId String?
|
|
1624
|
+
name String
|
|
1625
|
+
tokenHash String @unique
|
|
1626
|
+
lastSeenAt DateTime?
|
|
1627
|
+
revokedAt DateTime?
|
|
1628
|
+
createdAt DateTime @default(now())
|
|
1629
|
+
expiresAt DateTime?
|
|
1630
|
+
createdByUserId String?
|
|
1631
|
+
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
|
|
1632
|
+
createdByUser User? @relation(fields: [createdByUserId], references: [id])
|
|
1633
|
+
|
|
1634
|
+
@@index([tenantId, projectId])
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// Outbound webhook — ZeTa posts events to this URL on crawl/test changes.
|
|
1638
|
+
model Webhook {
|
|
1639
|
+
id String @id @default(cuid())
|
|
1640
|
+
tenantId String
|
|
1641
|
+
projectId String
|
|
1642
|
+
url String
|
|
1643
|
+
// HMAC secret, AES-256-GCM encrypted at application layer.
|
|
1644
|
+
secret_enc String
|
|
1645
|
+
// JSON array of event names, e.g. ["crawl.done","test.generated"]
|
|
1646
|
+
events Json @default("[]")
|
|
1647
|
+
active Boolean @default(true)
|
|
1648
|
+
createdAt DateTime @default(now())
|
|
1649
|
+
updatedAt DateTime @updatedAt
|
|
1650
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1651
|
+
deliveries WebhookDelivery[]
|
|
1652
|
+
|
|
1653
|
+
@@index([tenantId, projectId])
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// One row per delivery attempt (dispatch retries on failure, so a single
|
|
1657
|
+
// logical event can produce several rows sharing the same event/payload with
|
|
1658
|
+
// increasing `attempt`). Storing the payload lets a failed delivery be
|
|
1659
|
+
// manually redelivered later with the exact original body.
|
|
1660
|
+
model WebhookDelivery {
|
|
1661
|
+
id String @id @default(cuid())
|
|
1662
|
+
webhookId String
|
|
1663
|
+
tenantId String
|
|
1664
|
+
event String
|
|
1665
|
+
payload Json
|
|
1666
|
+
attempt Int @default(1)
|
|
1667
|
+
status String // SUCCESS | FAILED
|
|
1668
|
+
httpStatus Int?
|
|
1669
|
+
error String?
|
|
1670
|
+
createdAt DateTime @default(now())
|
|
1671
|
+
webhook Webhook @relation(fields: [webhookId], references: [id], onDelete: Cascade)
|
|
1672
|
+
|
|
1673
|
+
@@index([webhookId, createdAt])
|
|
1674
|
+
@@index([webhookId, status])
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Phase 3: Perceptual screenshot diff — crawl-to-crawl visual change detection
|
|
1678
|
+
model ScreenDiff {
|
|
1679
|
+
id String @id @default(cuid())
|
|
1680
|
+
tenantId String
|
|
1681
|
+
projectId String
|
|
1682
|
+
screenId String
|
|
1683
|
+
fromVersion Int
|
|
1684
|
+
toVersion Int
|
|
1685
|
+
// (hammingDistance / 64) * 100, rounded to 1 decimal. 0–100.
|
|
1686
|
+
pHashDistance Float
|
|
1687
|
+
// "IDENTICAL" | "MINOR" | "MODERATE" | "SIGNIFICANT"
|
|
1688
|
+
changeLevel String
|
|
1689
|
+
createdAt DateTime @default(now())
|
|
1690
|
+
screen Screen @relation(fields: [screenId], references: [id])
|
|
1691
|
+
|
|
1692
|
+
@@unique([screenId, fromVersion, toVersion])
|
|
1693
|
+
@@index([tenantId, projectId])
|
|
1694
|
+
@@index([tenantId, screenId])
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// Phase 5: Structured probe observations — typed facts from interactive probe
|
|
1698
|
+
model ProbeObservation {
|
|
1699
|
+
id String @id @default(cuid())
|
|
1700
|
+
tenantId String
|
|
1701
|
+
projectId String
|
|
1702
|
+
screenId String
|
|
1703
|
+
elementMeaning String
|
|
1704
|
+
// VALIDATION_ERROR | MODAL | SUCCESS_TOAST | CONDITIONAL_REVEAL | LOADING_STATE
|
|
1705
|
+
observationType String
|
|
1706
|
+
observedText String
|
|
1707
|
+
// "empty_submit" | "invalid_format" | "button_click:{label}" | "select_change:{label}:{value}"
|
|
1708
|
+
triggeredBy String
|
|
1709
|
+
observedAt DateTime @default(now())
|
|
1710
|
+
screen Screen @relation(fields: [screenId], references: [id])
|
|
1711
|
+
|
|
1712
|
+
@@index([tenantId, projectId])
|
|
1713
|
+
@@index([tenantId, screenId])
|
|
1714
|
+
@@index([tenantId, screenId, observationType])
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Records what changed between crawls (Differential Engine — Phase 6).
|
|
1718
|
+
model ChangeLog {
|
|
1719
|
+
id String @id @default(cuid())
|
|
1720
|
+
tenantId String
|
|
1721
|
+
projectId String
|
|
1722
|
+
// ADDED | REMOVED | MODIFIED
|
|
1723
|
+
changeType String
|
|
1724
|
+
summary String
|
|
1725
|
+
detail Json?
|
|
1726
|
+
createdAt DateTime @default(now())
|
|
1727
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1728
|
+
|
|
1729
|
+
@@index([tenantId, projectId])
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// Full-text search index — one row per heading-delimited section of a skill file or artifact.
|
|
1733
|
+
// The `tsv` tsvector column is GENERATED ALWAYS AS in Postgres (see migration 0014).
|
|
1734
|
+
// Prisma does not manage tsv — it is invisible to Prisma and updated automatically by Postgres.
|
|
1735
|
+
model KnowledgeChunk {
|
|
1736
|
+
id String @id @default(cuid())
|
|
1737
|
+
tenantId String
|
|
1738
|
+
projectId String? // null = global / research docs available to all projects
|
|
1739
|
+
sourceType String // SKILL_FILE | ARTIFACT | SCREEN
|
|
1740
|
+
sourceId String // ID of the parent SkillFile or ProjectArtifact
|
|
1741
|
+
imagePath String? // Base64 or local path for vision-grounded chunks
|
|
1742
|
+
heading String
|
|
1743
|
+
content String
|
|
1744
|
+
// tsv is a GENERATED ALWAYS AS STORED tsvector column managed entirely by Postgres.
|
|
1745
|
+
// Declared Unsupported so Prisma never tries to write it (and never drops it).
|
|
1746
|
+
tsv Unsupported("tsvector")?
|
|
1747
|
+
updatedAt DateTime @updatedAt
|
|
1748
|
+
|
|
1749
|
+
@@index([tenantId, projectId])
|
|
1750
|
+
@@index([sourceId])
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// ---- Token Optimisation & Ledger -------------------------------------------
|
|
1754
|
+
|
|
1755
|
+
model LLMCallLog {
|
|
1756
|
+
id String @id @default(cuid())
|
|
1757
|
+
tenantId String
|
|
1758
|
+
projectId String?
|
|
1759
|
+
|
|
1760
|
+
// What triggered this call
|
|
1761
|
+
jobId String? // FK to PlatformJob (if inside a job)
|
|
1762
|
+
agentType String // 'test_case' | 'skill_file' | 'flow' | 'nlp' | 'script' | 'data' | 'visual'
|
|
1763
|
+
label String // 'pass1_functional' | 'skill_file_TECH_STACK' | 'flow_journey'
|
|
1764
|
+
entityId String? // TestCase.id or SkillFile.id this call produced/contributed to
|
|
1765
|
+
entityType String? // 'test_case' | 'skill_file' | 'script' | 'test_data'
|
|
1766
|
+
|
|
1767
|
+
// Provider
|
|
1768
|
+
provider String
|
|
1769
|
+
model String
|
|
1770
|
+
|
|
1771
|
+
// Token counts
|
|
1772
|
+
promptTokens Int
|
|
1773
|
+
completionTokens Int
|
|
1774
|
+
cacheReadTokens Int @default(0)
|
|
1775
|
+
cacheWriteTokens Int @default(0)
|
|
1776
|
+
totalTokens Int
|
|
1777
|
+
|
|
1778
|
+
// Cost
|
|
1779
|
+
estimatedCostUsd Float @default(0)
|
|
1780
|
+
|
|
1781
|
+
// Response quality
|
|
1782
|
+
outputTokens Int? // alias completionTokens
|
|
1783
|
+
latencyMs Int?
|
|
1784
|
+
|
|
1785
|
+
taskType String?
|
|
1786
|
+
calledAt DateTime @default(now())
|
|
1787
|
+
|
|
1788
|
+
@@index([tenantId, projectId])
|
|
1789
|
+
@@index([tenantId, jobId])
|
|
1790
|
+
@@index([tenantId, agentType])
|
|
1791
|
+
@@index([entityId])
|
|
1792
|
+
@@index([calledAt])
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
model TokenBudget {
|
|
1796
|
+
id String @id @default(cuid())
|
|
1797
|
+
tenantId String @unique
|
|
1798
|
+
monthlyTokenCap Int?
|
|
1799
|
+
monthlyUsdCap Float?
|
|
1800
|
+
alertAtPercent Int @default(80)
|
|
1801
|
+
currentMonthTokens Int @default(0)
|
|
1802
|
+
currentMonthUsd Float @default(0)
|
|
1803
|
+
lastResetAt DateTime @default(now())
|
|
1804
|
+
updatedAt DateTime @updatedAt
|
|
1805
|
+
tenant Tenant @relation(fields: [tenantId], references: [id])
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
model Notification {
|
|
1809
|
+
id String @id @default(cuid())
|
|
1810
|
+
tenantId String
|
|
1811
|
+
userId String
|
|
1812
|
+
type String
|
|
1813
|
+
title String
|
|
1814
|
+
body String
|
|
1815
|
+
metadata Json?
|
|
1816
|
+
read Boolean @default(false)
|
|
1817
|
+
createdAt DateTime @default(now())
|
|
1818
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
1819
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
1820
|
+
@@index([tenantId, userId, read])
|
|
1821
|
+
@@index([tenantId, createdAt])
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
model NotificationPreference {
|
|
1825
|
+
id String @id @default(cuid())
|
|
1826
|
+
tenantId String
|
|
1827
|
+
userId String
|
|
1828
|
+
eventType String
|
|
1829
|
+
inApp Boolean @default(true)
|
|
1830
|
+
email Boolean @default(true)
|
|
1831
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
1832
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
1833
|
+
@@unique([userId, tenantId, eventType])
|
|
1834
|
+
@@index([tenantId, userId])
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// ─── Platform System Configuration ───────────────────────────────────────────
|
|
1838
|
+
// Key-value store for platform-wide settings managed via admin panel.
|
|
1839
|
+
// Replaces env vars for runtime-changeable config (SMTP, LLM, OAuth app creds).
|
|
1840
|
+
// Sensitive values are AES-256-GCM encrypted before storage.
|
|
1841
|
+
// Env vars remain as fallback if no DB row exists.
|
|
1842
|
+
|
|
1843
|
+
model SystemConfig {
|
|
1844
|
+
key String @id
|
|
1845
|
+
value String
|
|
1846
|
+
category String
|
|
1847
|
+
sensitive Boolean @default(false)
|
|
1848
|
+
updatedAt DateTime @updatedAt
|
|
1849
|
+
updatedByAdminId String?
|
|
1850
|
+
|
|
1851
|
+
@@index([category])
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// ---- App Fields (form field schema per project) ----------------------------
|
|
1855
|
+
|
|
1856
|
+
model AppField {
|
|
1857
|
+
id String @id @default(cuid())
|
|
1858
|
+
tenantId String
|
|
1859
|
+
projectId String
|
|
1860
|
+
fieldKey String
|
|
1861
|
+
label String
|
|
1862
|
+
elementRole String
|
|
1863
|
+
dataBearing Boolean @default(true)
|
|
1864
|
+
dataType String
|
|
1865
|
+
required Boolean @default(false)
|
|
1866
|
+
minLength Int?
|
|
1867
|
+
maxLength Int?
|
|
1868
|
+
minValue Float?
|
|
1869
|
+
maxValue Float?
|
|
1870
|
+
precision Int?
|
|
1871
|
+
regex String?
|
|
1872
|
+
enumValues Json?
|
|
1873
|
+
piiClass String @default("unknown")
|
|
1874
|
+
exampleValue String?
|
|
1875
|
+
relationGroup String?
|
|
1876
|
+
notes String?
|
|
1877
|
+
status String @default("unconfirmed")
|
|
1878
|
+
createdAt DateTime @default(now())
|
|
1879
|
+
updatedAt DateTime @updatedAt
|
|
1880
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1881
|
+
|
|
1882
|
+
@@unique([tenantId, projectId, fieldKey])
|
|
1883
|
+
@@index([tenantId, projectId])
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// ---- Workspace Connectors (third-party service credentials per tenant) ------
|
|
1887
|
+
|
|
1888
|
+
model WorkspaceConnector {
|
|
1889
|
+
id String @id @default(cuid())
|
|
1890
|
+
tenantId String
|
|
1891
|
+
kind String
|
|
1892
|
+
secretRef String @default("local-aes256")
|
|
1893
|
+
config Json?
|
|
1894
|
+
testOk Boolean @default(false)
|
|
1895
|
+
testedAt DateTime?
|
|
1896
|
+
createdAt DateTime @default(now())
|
|
1897
|
+
updatedAt DateTime @updatedAt
|
|
1898
|
+
|
|
1899
|
+
@@unique([tenantId, kind])
|
|
1900
|
+
@@index([tenantId])
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// ---- Shared Run Tokens (shareable signed links to test run results) ---------
|
|
1904
|
+
|
|
1905
|
+
model SharedRunToken {
|
|
1906
|
+
id String @id
|
|
1907
|
+
tenantId String
|
|
1908
|
+
runId String
|
|
1909
|
+
token String @unique
|
|
1910
|
+
expiresAt DateTime
|
|
1911
|
+
createdAt DateTime @default(now())
|
|
1912
|
+
|
|
1913
|
+
@@index([tenantId])
|
|
1914
|
+
@@index([token])
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// ---- Native App Builds -----------------------------------------------------
|
|
1918
|
+
|
|
1919
|
+
model NativeAppBuild {
|
|
1920
|
+
id String @id @default(cuid())
|
|
1921
|
+
tenantId String
|
|
1922
|
+
projectId String
|
|
1923
|
+
platform String
|
|
1924
|
+
fileFormat String
|
|
1925
|
+
fileName String?
|
|
1926
|
+
s3Path String
|
|
1927
|
+
packageName String?
|
|
1928
|
+
version String?
|
|
1929
|
+
uploadedAt DateTime @default(now())
|
|
1930
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1931
|
+
|
|
1932
|
+
@@index([tenantId, projectId])
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// ---- Performance Snapshots -------------------------------------------------
|
|
1936
|
+
|
|
1937
|
+
model PerformanceSnapshot {
|
|
1938
|
+
id String @id @default(cuid())
|
|
1939
|
+
tenantId String
|
|
1940
|
+
screenId String
|
|
1941
|
+
crawlJobId String
|
|
1942
|
+
lcp Float?
|
|
1943
|
+
fcp Float?
|
|
1944
|
+
cls Float?
|
|
1945
|
+
ttfb Float?
|
|
1946
|
+
viewport String @default("DESKTOP")
|
|
1947
|
+
lighthousePerf Float?
|
|
1948
|
+
lighthouseA11y Float?
|
|
1949
|
+
lighthouseSEO Float?
|
|
1950
|
+
lighthouseBP Float?
|
|
1951
|
+
capturedAt DateTime @default(now())
|
|
1952
|
+
|
|
1953
|
+
@@index([tenantId, screenId])
|
|
1954
|
+
@@index([tenantId, crawlJobId])
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// ---- Project Environments --------------------------------------------------
|
|
1958
|
+
|
|
1959
|
+
model ProjectEnvironment {
|
|
1960
|
+
id String @id @default(cuid())
|
|
1961
|
+
tenantId String
|
|
1962
|
+
projectId String
|
|
1963
|
+
name String
|
|
1964
|
+
baseUrl String
|
|
1965
|
+
credentials_enc String?
|
|
1966
|
+
isDefault Boolean @default(false)
|
|
1967
|
+
createdAt DateTime @default(now())
|
|
1968
|
+
updatedAt DateTime @updatedAt
|
|
1969
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1970
|
+
|
|
1971
|
+
@@index([tenantId, projectId])
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// ---- Project Members -------------------------------------------------------
|
|
1975
|
+
|
|
1976
|
+
model ProjectMember {
|
|
1977
|
+
id String @id @default(cuid())
|
|
1978
|
+
tenantId String
|
|
1979
|
+
projectId String
|
|
1980
|
+
userId String
|
|
1981
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
1982
|
+
|
|
1983
|
+
@@unique([projectId, userId])
|
|
1984
|
+
@@index([tenantId, userId])
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// ---- Comments --------------------------------------------------------------
|
|
1988
|
+
|
|
1989
|
+
model Comment {
|
|
1990
|
+
id String @id @default(cuid())
|
|
1991
|
+
tenantId String
|
|
1992
|
+
parentType String
|
|
1993
|
+
parentId String
|
|
1994
|
+
userId String?
|
|
1995
|
+
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
1996
|
+
body String
|
|
1997
|
+
createdAt DateTime @default(now())
|
|
1998
|
+
updatedAt DateTime @updatedAt
|
|
1999
|
+
|
|
2000
|
+
@@index([tenantId, parentType, parentId])
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// ---- Requirements ----------------------------------------------------------
|
|
2004
|
+
|
|
2005
|
+
model Requirement {
|
|
2006
|
+
id String @id @default(cuid())
|
|
2007
|
+
tenantId String
|
|
2008
|
+
projectId String
|
|
2009
|
+
reqId String
|
|
2010
|
+
title String
|
|
2011
|
+
actor String?
|
|
2012
|
+
type String @default("functional")
|
|
2013
|
+
priority String @default("HIGH")
|
|
2014
|
+
acceptanceCriteria Json @default("[]")
|
|
2015
|
+
businessRules Json @default("[]")
|
|
2016
|
+
relatedScreenHints Json @default("[]")
|
|
2017
|
+
sourceSection String?
|
|
2018
|
+
derived Boolean @default(false)
|
|
2019
|
+
status String @default("pending")
|
|
2020
|
+
source String @default("AI_GENERATED")
|
|
2021
|
+
// External tracker metadata (Jira): { url, issueType, status, epic, labels, components, links, subtasks }
|
|
2022
|
+
externalMeta Json?
|
|
2023
|
+
// Append-only history of content changes. Each entry: { version, changedAt, changedFields, snapshot }
|
|
2024
|
+
changeLog Json @default("[]")
|
|
2025
|
+
createdAt DateTime @default(now())
|
|
2026
|
+
updatedAt DateTime @updatedAt
|
|
2027
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2028
|
+
scenarios Scenario[]
|
|
2029
|
+
|
|
2030
|
+
@@unique([tenantId, projectId, reqId])
|
|
2031
|
+
@@index([tenantId, projectId])
|
|
2032
|
+
@@index([tenantId, projectId, priority])
|
|
2033
|
+
@@index([tenantId, projectId, status])
|
|
2034
|
+
@@index([tenantId, projectId, sourceSection])
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// ---- Scenarios --------------------------------------------------------------
|
|
2038
|
+
|
|
2039
|
+
model Scenario {
|
|
2040
|
+
id String @id @default(cuid())
|
|
2041
|
+
tenantId String
|
|
2042
|
+
projectId String
|
|
2043
|
+
requirementId String
|
|
2044
|
+
title String
|
|
2045
|
+
description String?
|
|
2046
|
+
gherkin String @db.Text
|
|
2047
|
+
status String @default("draft") // draft | approved | rejected
|
|
2048
|
+
version Int @default(1)
|
|
2049
|
+
source String @default("AI_GENERATED")
|
|
2050
|
+
createdAt DateTime @default(now())
|
|
2051
|
+
updatedAt DateTime @updatedAt
|
|
2052
|
+
|
|
2053
|
+
requirement Requirement @relation(fields: [requirementId], references: [id], onDelete: Cascade)
|
|
2054
|
+
testCases TestCase[]
|
|
2055
|
+
|
|
2056
|
+
@@index([tenantId, projectId])
|
|
2057
|
+
@@index([requirementId])
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// ---- Extension Captures ----------------------------------------------------
|
|
2061
|
+
|
|
2062
|
+
model ExtensionCapture {
|
|
2063
|
+
id String @id @default(cuid())
|
|
2064
|
+
tenantId String
|
|
2065
|
+
projectId String
|
|
2066
|
+
kind String
|
|
2067
|
+
s3Url String?
|
|
2068
|
+
entryCount Int @default(0)
|
|
2069
|
+
capturedAt DateTime @default(now())
|
|
2070
|
+
createdAt DateTime @default(now())
|
|
2071
|
+
metadata Json?
|
|
2072
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2073
|
+
interactions ExtensionInteraction[]
|
|
2074
|
+
|
|
2075
|
+
@@index([tenantId, projectId])
|
|
2076
|
+
@@index([tenantId, projectId, kind])
|
|
2077
|
+
@@index([tenantId, projectId, capturedAt])
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
model ExtensionInteraction {
|
|
2081
|
+
id String @id @default(cuid())
|
|
2082
|
+
tenantId String
|
|
2083
|
+
captureId String
|
|
2084
|
+
type String
|
|
2085
|
+
selector String?
|
|
2086
|
+
xpath String?
|
|
2087
|
+
tagName String?
|
|
2088
|
+
value String?
|
|
2089
|
+
url String?
|
|
2090
|
+
timestamp BigInt?
|
|
2091
|
+
metadata Json?
|
|
2092
|
+
capture ExtensionCapture @relation(fields: [captureId], references: [id], onDelete: Cascade)
|
|
2093
|
+
|
|
2094
|
+
@@index([tenantId, captureId])
|
|
2095
|
+
@@index([captureId])
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
model ExtensionError {
|
|
2099
|
+
id String @id @default(cuid())
|
|
2100
|
+
tenantId String
|
|
2101
|
+
projectId String
|
|
2102
|
+
pageUrl String?
|
|
2103
|
+
kind String
|
|
2104
|
+
level String @default("error")
|
|
2105
|
+
text String
|
|
2106
|
+
url String?
|
|
2107
|
+
lineNumber Int?
|
|
2108
|
+
columnNumber Int?
|
|
2109
|
+
stackTrace Json?
|
|
2110
|
+
source String?
|
|
2111
|
+
capturedAt DateTime @default(now())
|
|
2112
|
+
createdAt DateTime @default(now())
|
|
2113
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2114
|
+
|
|
2115
|
+
@@index([tenantId, projectId])
|
|
2116
|
+
@@index([tenantId, projectId, pageUrl])
|
|
2117
|
+
@@index([tenantId, projectId, kind])
|
|
2118
|
+
@@index([tenantId, projectId, level])
|
|
2119
|
+
@@index([tenantId, projectId, capturedAt])
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// ---- Billing / Plan Enforcement --------------------------------------------
|
|
2123
|
+
|
|
2124
|
+
model TenantPlanConfig {
|
|
2125
|
+
tier String @id
|
|
2126
|
+
maxProjects Int
|
|
2127
|
+
maxMembers Int
|
|
2128
|
+
maxSessions Int
|
|
2129
|
+
maxCrawlsPerMonth Int
|
|
2130
|
+
maxRunsPerMonth Int
|
|
2131
|
+
maxShards Int
|
|
2132
|
+
maxTokensPerMonth Int
|
|
2133
|
+
createdAt DateTime @default(now())
|
|
2134
|
+
updatedAt DateTime @updatedAt
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
model TenantPlan {
|
|
2138
|
+
tenantId String @id
|
|
2139
|
+
tier String @default("TRIAL") // TRIAL | STARTER | GROWTH | ENTERPRISE
|
|
2140
|
+
stripeCustomerId String?
|
|
2141
|
+
stripeSubscriptionId String?
|
|
2142
|
+
trialEndsAt DateTime?
|
|
2143
|
+
maxProjects Int
|
|
2144
|
+
maxMembers Int
|
|
2145
|
+
maxSessions Int
|
|
2146
|
+
maxCrawlsPerMonth Int
|
|
2147
|
+
maxRunsPerMonth Int
|
|
2148
|
+
maxShards Int
|
|
2149
|
+
maxTokensPerMonth Int
|
|
2150
|
+
currentCrawls Int @default(0)
|
|
2151
|
+
currentRuns Int @default(0)
|
|
2152
|
+
lastResetAt DateTime @default(now())
|
|
2153
|
+
updatedAt DateTime @updatedAt
|
|
2154
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// ---- Platform Admin --------------------------------------------------------
|
|
2158
|
+
|
|
2159
|
+
model AdminUser {
|
|
2160
|
+
id String @id @default(cuid())
|
|
2161
|
+
email String @unique
|
|
2162
|
+
passwordHash String
|
|
2163
|
+
name String?
|
|
2164
|
+
role String @default("SUPER_ADMIN") // SUPER_ADMIN | SUPPORT
|
|
2165
|
+
mfaEnabled Boolean @default(false)
|
|
2166
|
+
mfaSecret_enc String?
|
|
2167
|
+
mfaVerifiedAt DateTime?
|
|
2168
|
+
lastLoginAt DateTime? // updated on every successful login (F-009)
|
|
2169
|
+
failedLoginCount Int @default(0) // consecutive failures since last success; reset on login (F-010)
|
|
2170
|
+
lastFailedLoginAt DateTime? // timestamp of most recent failed attempt (F-010)
|
|
2171
|
+
createdAt DateTime @default(now())
|
|
2172
|
+
updatedAt DateTime @default(now())
|
|
2173
|
+
mfaRecoveryCodes AdminMfaRecoveryCode[]
|
|
2174
|
+
mfaChallenges AdminMfaChallenge[]
|
|
2175
|
+
authSessions AdminAuthSession[]
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
model AdminAuthSession {
|
|
2179
|
+
id String @id // JWT jti claim — set from crypto.randomUUID() in mintSessionToken()
|
|
2180
|
+
adminId String
|
|
2181
|
+
userAgent String?
|
|
2182
|
+
ipAddress String?
|
|
2183
|
+
createdAt DateTime @default(now())
|
|
2184
|
+
lastSeenAt DateTime @default(now())
|
|
2185
|
+
revokedAt DateTime?
|
|
2186
|
+
admin AdminUser @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
|
2187
|
+
|
|
2188
|
+
@@index([adminId])
|
|
2189
|
+
@@index([lastSeenAt])
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
model AdminMfaRecoveryCode {
|
|
2193
|
+
id String @id @default(cuid())
|
|
2194
|
+
adminId String
|
|
2195
|
+
codeHash String
|
|
2196
|
+
usedAt DateTime?
|
|
2197
|
+
createdAt DateTime @default(now())
|
|
2198
|
+
admin AdminUser @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
|
2199
|
+
|
|
2200
|
+
@@index([adminId])
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
model AdminMfaChallenge {
|
|
2204
|
+
id String @id
|
|
2205
|
+
adminId String
|
|
2206
|
+
purpose String
|
|
2207
|
+
expiresAt DateTime
|
|
2208
|
+
usedAt DateTime?
|
|
2209
|
+
createdAt DateTime @default(now())
|
|
2210
|
+
admin AdminUser @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
|
2211
|
+
|
|
2212
|
+
@@index([adminId, purpose, usedAt])
|
|
2213
|
+
@@index([expiresAt])
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// ---- DB-driven built-in role permissions -----------------------------------
|
|
2217
|
+
// Stores the permission sets for the 6 built-in roles (OWNER, ADMIN, etc.).
|
|
2218
|
+
// Seeded on first deploy; editable by platform super-admins at runtime.
|
|
2219
|
+
// The middleware reads from here (with a 60s in-memory cache) instead of
|
|
2220
|
+
// the hardcoded ROLE_PERMISSIONS map in @detiq/core.
|
|
2221
|
+
model RoleConfig {
|
|
2222
|
+
roleName String @id // 'OWNER' | 'ADMIN' | 'QA_LEAD' | 'TESTER' | 'DEVELOPER' | 'VIEWER'
|
|
2223
|
+
rank Int @unique // 0=VIEWER … 5=OWNER — used for privilege-escalation checks
|
|
2224
|
+
permissions String[] // array of Permission strings
|
|
2225
|
+
updatedAt DateTime @updatedAt
|
|
2226
|
+
|
|
2227
|
+
@@map("role_configs")
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// ---- Slack Integration (OAuth Bot token per workspace) ----------------------
|
|
2231
|
+
|
|
2232
|
+
model SlackConnection {
|
|
2233
|
+
id String @id @default(cuid())
|
|
2234
|
+
tenantId String @unique
|
|
2235
|
+
teamId String
|
|
2236
|
+
teamName String
|
|
2237
|
+
channelId String
|
|
2238
|
+
channelName String
|
|
2239
|
+
accessToken_enc String // AES-256-GCM encrypted Bot token
|
|
2240
|
+
installedBy String // userId of the member who ran /slack/install
|
|
2241
|
+
createdAt DateTime @default(now())
|
|
2242
|
+
updatedAt DateTime @updatedAt
|
|
2243
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// ---- Microsoft Teams Integration (Power Automate webhook) ------------------
|
|
2247
|
+
|
|
2248
|
+
model TeamsConnection {
|
|
2249
|
+
id String @id @default(cuid())
|
|
2250
|
+
tenantId String @unique
|
|
2251
|
+
webhookUrl String // Power Automate incoming webhook URL
|
|
2252
|
+
channelName String?
|
|
2253
|
+
installedBy String
|
|
2254
|
+
createdAt DateTime @default(now())
|
|
2255
|
+
updatedAt DateTime @updatedAt
|
|
2256
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// ---- SILO Isolation Config (Phase 3.2) -------------------------------------
|
|
2260
|
+
|
|
2261
|
+
model TenantSiloConfig {
|
|
2262
|
+
tenantId String @id
|
|
2263
|
+
connectionString_enc String
|
|
2264
|
+
encryptionKeyRef String @default("default")
|
|
2265
|
+
provisionedAt DateTime @default(now())
|
|
2266
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// ---- Data-Driven Test Execution (Phase 4.3) ---------------------------------
|
|
2270
|
+
|
|
2271
|
+
model TestDataSet {
|
|
2272
|
+
id String @id @default(cuid())
|
|
2273
|
+
tenantId String
|
|
2274
|
+
projectId String
|
|
2275
|
+
name String
|
|
2276
|
+
// SYNTHETIC | PRODUCTION | STAGING | SANDBOX | DUMMY | IMPORTED
|
|
2277
|
+
classification String @default("SYNTHETIC")
|
|
2278
|
+
// Free-form environment label e.g. "Production", "UAT", "QA-01"
|
|
2279
|
+
environment String?
|
|
2280
|
+
rows TestDataRow[]
|
|
2281
|
+
testCases TestCase[]
|
|
2282
|
+
createdAt DateTime @default(now())
|
|
2283
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2284
|
+
|
|
2285
|
+
@@index([tenantId, projectId])
|
|
2286
|
+
@@index([tenantId, classification])
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
model TestDataRow {
|
|
2290
|
+
id String @id @default(cuid())
|
|
2291
|
+
tenantId String
|
|
2292
|
+
dataSetId String
|
|
2293
|
+
rowIndex Int
|
|
2294
|
+
data Json // { fieldName: value } — e.g. { "username": "alice", "password": "pass1" }
|
|
2295
|
+
dataSet TestDataSet @relation(fields: [dataSetId], references: [id], onDelete: Cascade)
|
|
2296
|
+
|
|
2297
|
+
@@unique([dataSetId, rowIndex])
|
|
2298
|
+
@@index([tenantId])
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
// ---- Framework Modernization — Phase 2: GitHub Repo Integration -------------
|
|
2302
|
+
|
|
2303
|
+
// One row per connected GitHub automation repo per project.
|
|
2304
|
+
model GitHubRepoConnection {
|
|
2305
|
+
id String @id @default(cuid())
|
|
2306
|
+
tenantId String
|
|
2307
|
+
projectId String
|
|
2308
|
+
createdBy String // userId who connected the repo
|
|
2309
|
+
// "owner/repo"
|
|
2310
|
+
repoFullName String
|
|
2311
|
+
defaultBranch String @default("main")
|
|
2312
|
+
// AES-256-GCM: "<iv>:<authTag>:<ciphertext>"
|
|
2313
|
+
tokenEnc String
|
|
2314
|
+
// ACTIVE | NEEDS_REAUTH | ERROR | DISCONNECTED
|
|
2315
|
+
status String @default("ACTIVE")
|
|
2316
|
+
// PAT | GITHUB_APP | FINE_GRAINED_PAT
|
|
2317
|
+
authMethod String @default("PAT")
|
|
2318
|
+
// Detected from config files: playwright-ts | cypress-ts | pytest | junit | etc.
|
|
2319
|
+
detectedFramework String?
|
|
2320
|
+
configFilePath String? // e.g. "playwright.config.ts"
|
|
2321
|
+
testGlob String? // e.g. "**/*.spec.ts"
|
|
2322
|
+
// Regex to extract test IDs from test titles
|
|
2323
|
+
testIdPattern String @default("TC-[A-Z]+-\\d+")
|
|
2324
|
+
// JSON: extracted style conventions for aligned script generation (Phase 3)
|
|
2325
|
+
styleProfile Json?
|
|
2326
|
+
// Compressed JSON tree from last scan (stored as base64 gzip)
|
|
2327
|
+
treeSnapshot String? @db.Text
|
|
2328
|
+
// IDLE | SCANNING | DONE | FAILED
|
|
2329
|
+
scanStatus String @default("IDLE")
|
|
2330
|
+
scanError String?
|
|
2331
|
+
testFileCount Int @default(0)
|
|
2332
|
+
extractedIdCount Int @default(0)
|
|
2333
|
+
lastScannedAt DateTime?
|
|
2334
|
+
lastSyncAt DateTime?
|
|
2335
|
+
// JSON array of PageObjectSummary — populated during repo scan
|
|
2336
|
+
pageObjectInventory Json?
|
|
2337
|
+
// GitHub App installation ID (for GitHub App auth)
|
|
2338
|
+
installationId Int?
|
|
2339
|
+
createdAt DateTime @default(now())
|
|
2340
|
+
updatedAt DateTime @updatedAt
|
|
2341
|
+
|
|
2342
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2343
|
+
repoTestFiles RepoTestFile[]
|
|
2344
|
+
coverageMappings CoverageMapping[]
|
|
2345
|
+
gitHubPRs GitHubPR[]
|
|
2346
|
+
|
|
2347
|
+
@@index([tenantId, projectId])
|
|
2348
|
+
@@index([tenantId, status])
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// One row per test file discovered in the connected repo.
|
|
2352
|
+
model RepoTestFile {
|
|
2353
|
+
id String @id @default(cuid())
|
|
2354
|
+
tenantId String
|
|
2355
|
+
projectId String
|
|
2356
|
+
repoConnectionId String
|
|
2357
|
+
// Repo-root-relative path, forward slashes, no leading slash
|
|
2358
|
+
filePath String
|
|
2359
|
+
framework String // playwright-ts | cypress-ts | pytest | etc.
|
|
2360
|
+
// string[] of extracted test IDs / names
|
|
2361
|
+
extractedIds Json @default("[]")
|
|
2362
|
+
// First 2000 chars — used for style extraction
|
|
2363
|
+
rawSample String? @db.Text
|
|
2364
|
+
lastScannedAt DateTime @default(now())
|
|
2365
|
+
|
|
2366
|
+
repoConnection GitHubRepoConnection @relation(fields: [repoConnectionId], references: [id], onDelete: Cascade)
|
|
2367
|
+
|
|
2368
|
+
@@unique([repoConnectionId, filePath])
|
|
2369
|
+
@@index([tenantId, projectId])
|
|
2370
|
+
@@index([repoConnectionId])
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// One row per TestCase — tracks mapping to a repo script file.
|
|
2374
|
+
model CoverageMapping {
|
|
2375
|
+
id String @id @default(cuid())
|
|
2376
|
+
tenantId String
|
|
2377
|
+
projectId String
|
|
2378
|
+
testCaseId String @unique
|
|
2379
|
+
repoConnectionId String
|
|
2380
|
+
// MAPPED | UNMAPPED | AMBIGUOUS
|
|
2381
|
+
status String @default("UNMAPPED")
|
|
2382
|
+
matchedFilePath String? // relative path in repo
|
|
2383
|
+
scriptTestName String? // matched test name inside the file
|
|
2384
|
+
lineNumber Int?
|
|
2385
|
+
// 0.0–1.0
|
|
2386
|
+
confidence Float?
|
|
2387
|
+
requirementScore Float? // cross-encoder quality score: how well TC covers its linked requirement (0.0–1.0)
|
|
2388
|
+
suggestedReqId String? // for UNMAPPED TCs: best-match requirement ID from cross-encoder
|
|
2389
|
+
suggestedReqScore Float? // cross-encoder confidence for the suggestion (0.0–1.0)
|
|
2390
|
+
// EXACT | NORMALIZED | FUZZY | AI | MANUAL
|
|
2391
|
+
matchMethod String?
|
|
2392
|
+
manualOverride Boolean @default(false)
|
|
2393
|
+
confirmedBy String? // userId
|
|
2394
|
+
confirmedAt DateTime?
|
|
2395
|
+
createdAt DateTime @default(now())
|
|
2396
|
+
updatedAt DateTime @updatedAt
|
|
2397
|
+
|
|
2398
|
+
testCase TestCase @relation(fields: [testCaseId], references: [id], onDelete: Cascade)
|
|
2399
|
+
repoConnection GitHubRepoConnection @relation(fields: [repoConnectionId], references: [id], onDelete: Cascade)
|
|
2400
|
+
|
|
2401
|
+
@@index([tenantId, projectId])
|
|
2402
|
+
@@index([repoConnectionId])
|
|
2403
|
+
@@index([status, projectId])
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// Time-series snapshot of coverage % for the dashboard trend chart.
|
|
2407
|
+
model CoverageSnapshot {
|
|
2408
|
+
id String @id @default(cuid())
|
|
2409
|
+
tenantId String
|
|
2410
|
+
projectId String
|
|
2411
|
+
totalCases Int
|
|
2412
|
+
mappedCases Int
|
|
2413
|
+
unmappedCases Int
|
|
2414
|
+
ambiguousCases Int
|
|
2415
|
+
coveragePercent Float
|
|
2416
|
+
snapshotAt DateTime @default(now())
|
|
2417
|
+
|
|
2418
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2419
|
+
|
|
2420
|
+
@@index([tenantId, projectId, snapshotAt])
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// Tracks GitHub PRs created by ZeTa (one per automated push batch).
|
|
2424
|
+
model GitHubPR {
|
|
2425
|
+
id String @id @default(cuid())
|
|
2426
|
+
tenantId String
|
|
2427
|
+
projectId String
|
|
2428
|
+
repoConnectionId String
|
|
2429
|
+
prNumber Int
|
|
2430
|
+
prUrl String
|
|
2431
|
+
branch String
|
|
2432
|
+
headSha String
|
|
2433
|
+
// OPEN | MERGED | CLOSED
|
|
2434
|
+
status String @default("OPEN")
|
|
2435
|
+
// Which crawl job triggered this PR (optional)
|
|
2436
|
+
crawlJobId String?
|
|
2437
|
+
// How many scripts are in this PR
|
|
2438
|
+
scriptCount Int @default(0)
|
|
2439
|
+
// ZeTa-generated PR body text
|
|
2440
|
+
prBody String? @db.Text
|
|
2441
|
+
createdAt DateTime @default(now())
|
|
2442
|
+
updatedAt DateTime @updatedAt
|
|
2443
|
+
|
|
2444
|
+
repoConnection GitHubRepoConnection @relation(fields: [repoConnectionId], references: [id], onDelete: Cascade)
|
|
2445
|
+
|
|
2446
|
+
@@index([tenantId, projectId])
|
|
2447
|
+
@@index([repoConnectionId])
|
|
2448
|
+
@@index([tenantId, status])
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// ---- IDE Push Tokens (one-time, 60-second OTP for VS Code / JetBrains) ------
|
|
2452
|
+
// The web app mints a token, puts only its hash in DB, and hands the plain token
|
|
2453
|
+
// to the IDE extension via the vscode:// URI. The extension exchanges it here
|
|
2454
|
+
// (GET /ide-push/:token) which validates, marks as used (single-use), and returns
|
|
2455
|
+
// the script payload — without the long-lived AgentToken ever touching a URI.
|
|
2456
|
+
model IdePushToken {
|
|
2457
|
+
id String @id @default(cuid())
|
|
2458
|
+
tenantId String
|
|
2459
|
+
projectId String
|
|
2460
|
+
suiteId String?
|
|
2461
|
+
packId String? // for pack-based pushes (regression/smoke/sanity)
|
|
2462
|
+
scriptIds String? // JSON array of TestScript IDs for direct selection
|
|
2463
|
+
language String @default("playwright-ts")
|
|
2464
|
+
tokenHash String @unique
|
|
2465
|
+
expiresAt DateTime
|
|
2466
|
+
usedAt DateTime?
|
|
2467
|
+
createdAt DateTime @default(now())
|
|
2468
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2469
|
+
|
|
2470
|
+
@@index([tenantId, projectId])
|
|
2471
|
+
@@index([tokenHash])
|
|
2472
|
+
@@index([expiresAt])
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
// ---- LLM pricing table (replaces hardcoded PRICING constant in token-pricing.ts) ---------
|
|
2476
|
+
// Platform admin can update prices via UI without a code deploy.
|
|
2477
|
+
// modelKey format: "provider/model" e.g. "anthropic/claude-sonnet-4-6".
|
|
2478
|
+
model LlmPricing {
|
|
2479
|
+
id String @id @default(cuid())
|
|
2480
|
+
modelKey String @unique
|
|
2481
|
+
inputPer1M Float
|
|
2482
|
+
outputPer1M Float
|
|
2483
|
+
cacheReadPer1M Float @default(0)
|
|
2484
|
+
cacheWritePer1M Float @default(0)
|
|
2485
|
+
effectiveDate DateTime @default(now())
|
|
2486
|
+
createdAt DateTime @default(now())
|
|
2487
|
+
updatedAt DateTime @updatedAt
|
|
2488
|
+
|
|
2489
|
+
@@index([modelKey])
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
// ---- Database-seeded agent prompts (replaces hardcoded GLOBAL_RULES) ---------
|
|
2493
|
+
// Loaded by packages/core/src/agent-prompts.ts with 60s in-process cache.
|
|
2494
|
+
// agentKey: e.g. "test-case-global-rules", passKey: optional per-pass override key.
|
|
2495
|
+
model AgentPrompt {
|
|
2496
|
+
id String @id @default(cuid())
|
|
2497
|
+
name String
|
|
2498
|
+
agentKey String
|
|
2499
|
+
passKey String?
|
|
2500
|
+
body String @db.Text
|
|
2501
|
+
version Int @default(1)
|
|
2502
|
+
isActive Boolean @default(true)
|
|
2503
|
+
createdAt DateTime @default(now())
|
|
2504
|
+
updatedAt DateTime @updatedAt
|
|
2505
|
+
|
|
2506
|
+
@@unique([agentKey, passKey, isActive])
|
|
2507
|
+
@@index([agentKey, isActive])
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// Per-tenant override of platform-wide AgentPrompt.
|
|
2511
|
+
// Resolution order: WorkspaceAgentPrompt (tenant) → AgentPrompt (platform) → hardcoded fallback.
|
|
2512
|
+
model WorkspaceAgentPrompt {
|
|
2513
|
+
id String @id @default(cuid())
|
|
2514
|
+
tenantId String
|
|
2515
|
+
agentKey String
|
|
2516
|
+
passKey String?
|
|
2517
|
+
name String
|
|
2518
|
+
body String @db.Text
|
|
2519
|
+
isActive Boolean @default(true)
|
|
2520
|
+
createdAt DateTime @default(now())
|
|
2521
|
+
updatedAt DateTime @updatedAt
|
|
2522
|
+
updatedBy String?
|
|
2523
|
+
|
|
2524
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2525
|
+
|
|
2526
|
+
@@unique([tenantId, agentKey, passKey])
|
|
2527
|
+
@@index([tenantId, agentKey, isActive])
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// ---- Admin Config Change History -----------------------------------------
|
|
2531
|
+
// Immutable audit trail for every SystemConfig upsert and delete.
|
|
2532
|
+
// Sensitive field values stored as MASK ("••••••••") — never raw.
|
|
2533
|
+
|
|
2534
|
+
model SystemConfigHistory {
|
|
2535
|
+
id String @id @default(cuid())
|
|
2536
|
+
configKey String // e.g. "smtp.password"
|
|
2537
|
+
category String
|
|
2538
|
+
fieldKey String
|
|
2539
|
+
oldValue String? // null on first-ever create; MASK for sensitive
|
|
2540
|
+
newValue String? // null on delete; MASK for sensitive
|
|
2541
|
+
action String // "upsert" | "delete"
|
|
2542
|
+
sensitive Boolean @default(false)
|
|
2543
|
+
adminId String?
|
|
2544
|
+
adminEmail String?
|
|
2545
|
+
changedAt DateTime @default(now())
|
|
2546
|
+
|
|
2547
|
+
@@index([configKey, changedAt(sort: Desc)])
|
|
2548
|
+
@@index([category, changedAt(sort: Desc)])
|
|
2549
|
+
@@index([adminId])
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// ---- Platform Prompt Change History --------------------------------------
|
|
2553
|
+
// Captures every body edit to AgentPrompt platform records (version++ trigger).
|
|
2554
|
+
|
|
2555
|
+
model AgentPromptHistory {
|
|
2556
|
+
id String @id @default(cuid())
|
|
2557
|
+
agentPromptId String
|
|
2558
|
+
agentKey String
|
|
2559
|
+
passKey String?
|
|
2560
|
+
oldBody String @db.Text
|
|
2561
|
+
newBody String @db.Text
|
|
2562
|
+
version Int
|
|
2563
|
+
adminId String?
|
|
2564
|
+
adminEmail String?
|
|
2565
|
+
changedAt DateTime @default(now())
|
|
2566
|
+
|
|
2567
|
+
@@index([agentPromptId, changedAt(sort: Desc)])
|
|
2568
|
+
@@index([agentKey, changedAt(sort: Desc)])
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
// ---- Tenant Prompt Override History --------------------------------------
|
|
2572
|
+
// Tracks every upsert/delete of WorkspaceAgentPrompt per tenant.
|
|
2573
|
+
|
|
2574
|
+
model WorkspaceAgentPromptHistory {
|
|
2575
|
+
id String @id @default(cuid())
|
|
2576
|
+
workspaceAgentPromptId String? // null when record was deleted
|
|
2577
|
+
tenantId String
|
|
2578
|
+
agentKey String
|
|
2579
|
+
passKey String?
|
|
2580
|
+
oldBody String? @db.Text // null = first creation
|
|
2581
|
+
newBody String? @db.Text // null = deletion
|
|
2582
|
+
action String // "upsert" | "delete"
|
|
2583
|
+
updatedBy String?
|
|
2584
|
+
changedAt DateTime @default(now())
|
|
2585
|
+
|
|
2586
|
+
@@index([tenantId, agentKey, changedAt(sort: Desc)])
|
|
2587
|
+
@@index([workspaceAgentPromptId, changedAt(sort: Desc)])
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// ==========================================
|
|
2591
|
+
// Learning / Onboarding Models
|
|
2592
|
+
// ==========================================
|
|
2593
|
+
|
|
2594
|
+
model UserContext {
|
|
2595
|
+
id String @id @default(cuid())
|
|
2596
|
+
userId String @unique
|
|
2597
|
+
dailyTasks String[] @default([])
|
|
2598
|
+
roles String[] @default([])
|
|
2599
|
+
responsibilities String[] @default([])
|
|
2600
|
+
toolsUsed String[] @default([])
|
|
2601
|
+
lastExtractedAt DateTime?
|
|
2602
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
model LearningJourney {
|
|
2606
|
+
id String @id @default(cuid())
|
|
2607
|
+
userId String @unique
|
|
2608
|
+
tenantId String
|
|
2609
|
+
level Int @default(1)
|
|
2610
|
+
status String @default("ACTIVE")
|
|
2611
|
+
tasks LearningTask[]
|
|
2612
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
2613
|
+
|
|
2614
|
+
@@index([tenantId])
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2617
|
+
model LearningTask {
|
|
2618
|
+
id String @id @default(cuid())
|
|
2619
|
+
journeyId String
|
|
2620
|
+
title String
|
|
2621
|
+
description String @db.Text
|
|
2622
|
+
requiredArtifacts String[] @default([])
|
|
2623
|
+
status String @default("ASSIGNED")
|
|
2624
|
+
createdAt DateTime @default(now())
|
|
2625
|
+
submissions TaskSubmission[]
|
|
2626
|
+
journey LearningJourney @relation(fields: [journeyId], references: [id], onDelete: Cascade)
|
|
2627
|
+
|
|
2628
|
+
@@index([journeyId])
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
model TaskSubmission {
|
|
2632
|
+
id String @id @default(cuid())
|
|
2633
|
+
taskId String
|
|
2634
|
+
textContent String? @db.Text
|
|
2635
|
+
contentUrl String?
|
|
2636
|
+
submittedAt DateTime @default(now())
|
|
2637
|
+
task LearningTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
2638
|
+
|
|
2639
|
+
@@index([taskId])
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// Navigation Knowledge Graph: hyperlink edges discovered between screens during crawl.
|
|
2643
|
+
// linkType: NAV_LINK | CONTENT_LINK | FORM_ACTION | BUTTON_LINK | DATA_LINK
|
|
2644
|
+
// source: DOM | AI | INTERACTIVE | SITEMAP
|
|
2645
|
+
model DiscoveredLink {
|
|
2646
|
+
id String @id @default(cuid())
|
|
2647
|
+
tenantId String
|
|
2648
|
+
projectId String
|
|
2649
|
+
fromScreenId String
|
|
2650
|
+
toScreenId String?
|
|
2651
|
+
href String
|
|
2652
|
+
text String?
|
|
2653
|
+
landmark String? // nav | header | footer | main | sidebar | aside
|
|
2654
|
+
linkType String @default("NAV_LINK")
|
|
2655
|
+
source String @default("DOM")
|
|
2656
|
+
confidence Float @default(1.0)
|
|
2657
|
+
jobId String?
|
|
2658
|
+
createdAt DateTime @default(now())
|
|
2659
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2660
|
+
fromScreen Screen @relation("DiscoveredLinkFromScreen", fields: [fromScreenId], references: [id], onDelete: Cascade)
|
|
2661
|
+
toScreen Screen? @relation("DiscoveredLinkToScreen", fields: [toScreenId], references: [id], onDelete: SetNull)
|
|
2662
|
+
|
|
2663
|
+
@@unique([fromScreenId, href])
|
|
2664
|
+
@@index([tenantId, projectId])
|
|
2665
|
+
@@index([tenantId, projectId, jobId])
|
|
2666
|
+
@@index([toScreenId])
|
|
2667
|
+
@@index([href])
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// ---- API Test Run Persistence (GAP-003) ------------------------------------
|
|
2671
|
+
// Persists newman / agent API test run results beyond the 5-min in-memory TTL.
|
|
2672
|
+
|
|
2673
|
+
model ApiTestRunHistory {
|
|
2674
|
+
id String @id @default(cuid())
|
|
2675
|
+
tenantId String
|
|
2676
|
+
projectId String
|
|
2677
|
+
status String // queued | running | passed | failed | error
|
|
2678
|
+
summary Json?
|
|
2679
|
+
source String? // newman | agent
|
|
2680
|
+
createdAt DateTime @default(now())
|
|
2681
|
+
completedAt DateTime?
|
|
2682
|
+
requests ApiTestRunRequest[]
|
|
2683
|
+
|
|
2684
|
+
@@index([tenantId, projectId])
|
|
2685
|
+
@@index([tenantId, createdAt])
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
model ApiTestRunRequest {
|
|
2689
|
+
id String @id @default(cuid())
|
|
2690
|
+
runHistoryId String
|
|
2691
|
+
run ApiTestRunHistory @relation(fields: [runHistoryId], references: [id], onDelete: Cascade)
|
|
2692
|
+
requestName String
|
|
2693
|
+
status String // pending | running | passed | failed | skipped | error
|
|
2694
|
+
statusCode Int?
|
|
2695
|
+
durationMs Int?
|
|
2696
|
+
assertions Json?
|
|
2697
|
+
requestUrl String?
|
|
2698
|
+
error String?
|
|
2699
|
+
|
|
2700
|
+
@@index([runHistoryId])
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// ---- Custom Assertion Builder (GAP-009) ------------------------------------
|
|
2704
|
+
// Stores per-test-case custom assertions for the assertion builder UI.
|
|
2705
|
+
|
|
2706
|
+
model TestCaseAssertion {
|
|
2707
|
+
id String @id @default(cuid())
|
|
2708
|
+
testCaseId String
|
|
2709
|
+
field String
|
|
2710
|
+
operator String // eq | ne | contains | gt | lt | gte | lte | schema | regex | exists
|
|
2711
|
+
expectedValue String?
|
|
2712
|
+
createdAt DateTime @default(now())
|
|
2713
|
+
|
|
2714
|
+
@@index([testCaseId])
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
// ---- CSV/JSON Data-Driven Testing — Skill File Link (GAP-017) -------------
|
|
2718
|
+
// Links a test case to a TEST_DATA skill file for multi-row iteration via
|
|
2719
|
+
// Newman --iteration-data (CSV/JSON). Separate from TestDataSet (DB rows)
|
|
2720
|
+
// to support file-based data sources without DB import.
|
|
2721
|
+
|
|
2722
|
+
model TestCaseDataLink {
|
|
2723
|
+
id String @id @default(cuid())
|
|
2724
|
+
testCaseId String @unique
|
|
2725
|
+
skillFileId String // ID of the TEST_DATA skill file
|
|
2726
|
+
dataFormat String @default("json") // json | csv
|
|
2727
|
+
iterateAll Boolean @default(true)
|
|
2728
|
+
createdAt DateTime @default(now())
|
|
2729
|
+
|
|
2730
|
+
@@index([testCaseId])
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
// ---- Context Intelligence Models (P1-P7) ------------------------------------
|
|
2734
|
+
|
|
2735
|
+
model ContextEnhancement {
|
|
2736
|
+
id String @id @default(cuid())
|
|
2737
|
+
tenantId String
|
|
2738
|
+
projectId String
|
|
2739
|
+
sourceType String
|
|
2740
|
+
sourceRef String?
|
|
2741
|
+
crawlJobId String?
|
|
2742
|
+
status String @default("PENDING")
|
|
2743
|
+
proposedChanges Json @default("[]")
|
|
2744
|
+
appliedAt DateTime?
|
|
2745
|
+
appliedBy String?
|
|
2746
|
+
rejectedAt DateTime?
|
|
2747
|
+
rejectedBy String?
|
|
2748
|
+
approvalRequestedAt DateTime?
|
|
2749
|
+
approvalRequestedBy String?
|
|
2750
|
+
assignedReviewerId String?
|
|
2751
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2752
|
+
extractedElements ExtractedElement[]
|
|
2753
|
+
enrichedTestCases TestCase[] @relation("TestCaseSourceEnhancement")
|
|
2754
|
+
createdAt DateTime @default(now())
|
|
2755
|
+
updatedAt DateTime @updatedAt
|
|
2756
|
+
|
|
2757
|
+
@@index([tenantId, projectId])
|
|
2758
|
+
@@index([tenantId, projectId, status])
|
|
2759
|
+
@@index([tenantId, projectId, sourceType])
|
|
2760
|
+
@@index([sourceRef])
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
model ExtractedElement {
|
|
2764
|
+
id String @id @default(cuid())
|
|
2765
|
+
tenantId String
|
|
2766
|
+
projectId String
|
|
2767
|
+
sourceEnhancementId String
|
|
2768
|
+
figmaComponentId String?
|
|
2769
|
+
name String?
|
|
2770
|
+
role String?
|
|
2771
|
+
selector String?
|
|
2772
|
+
confidence Float @default(0.5)
|
|
2773
|
+
boundingBox Json?
|
|
2774
|
+
extractedFrom String
|
|
2775
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2776
|
+
sourceEnhancement ContextEnhancement @relation(fields: [sourceEnhancementId], references: [id], onDelete: Cascade)
|
|
2777
|
+
figmaComponent FigmaComponent? @relation(fields: [figmaComponentId], references: [id], onDelete: SetNull)
|
|
2778
|
+
createdAt DateTime @default(now())
|
|
2779
|
+
|
|
2780
|
+
@@index([tenantId, projectId])
|
|
2781
|
+
@@index([sourceEnhancementId])
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
model ContextSourceApproval {
|
|
2785
|
+
id String @id @default(cuid())
|
|
2786
|
+
tenantId String
|
|
2787
|
+
projectId String
|
|
2788
|
+
sourceType String
|
|
2789
|
+
approvedBy String?
|
|
2790
|
+
approvedAt DateTime?
|
|
2791
|
+
jobId String?
|
|
2792
|
+
diffCount Int @default(0)
|
|
2793
|
+
diffApprovedCount Int @default(0)
|
|
2794
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2795
|
+
createdAt DateTime @default(now())
|
|
2796
|
+
updatedAt DateTime @updatedAt
|
|
2797
|
+
|
|
2798
|
+
@@index([tenantId, projectId])
|
|
2799
|
+
@@index([tenantId, projectId, sourceType])
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
model FigmaComponent {
|
|
2803
|
+
id String @id @default(cuid())
|
|
2804
|
+
tenantId String
|
|
2805
|
+
projectId String
|
|
2806
|
+
screenId String?
|
|
2807
|
+
figmaNodeId String
|
|
2808
|
+
name String
|
|
2809
|
+
componentType String @default("COMPONENT")
|
|
2810
|
+
description String?
|
|
2811
|
+
interactions Json?
|
|
2812
|
+
destinationScreenId String?
|
|
2813
|
+
suggestedLocator String?
|
|
2814
|
+
suggestedTestId String?
|
|
2815
|
+
boundingBox Json?
|
|
2816
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2817
|
+
screen Screen? @relation(fields: [screenId], references: [id], onDelete: SetNull)
|
|
2818
|
+
extractedElements ExtractedElement[]
|
|
2819
|
+
createdAt DateTime @default(now())
|
|
2820
|
+
updatedAt DateTime @updatedAt
|
|
2821
|
+
|
|
2822
|
+
@@index([tenantId, projectId])
|
|
2823
|
+
@@index([screenId])
|
|
2824
|
+
@@index([figmaNodeId])
|
|
2825
|
+
@@unique([projectId, figmaNodeId])
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
model CrawlDiff {
|
|
2829
|
+
id String @id @default(cuid())
|
|
2830
|
+
tenantId String
|
|
2831
|
+
projectId String
|
|
2832
|
+
fromJobId String?
|
|
2833
|
+
toJobId String
|
|
2834
|
+
fromScreenId String?
|
|
2835
|
+
toScreenId String?
|
|
2836
|
+
diffType String
|
|
2837
|
+
severity String @default("MINOR")
|
|
2838
|
+
description String?
|
|
2839
|
+
beforeSnapshot Json?
|
|
2840
|
+
afterSnapshot Json?
|
|
2841
|
+
affectedTestCaseIds Json @default("[]")
|
|
2842
|
+
status String @default("PENDING")
|
|
2843
|
+
reviewedBy String?
|
|
2844
|
+
reviewedAt DateTime?
|
|
2845
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2846
|
+
fromScreen Screen? @relation("CrawlDiffFromScreen", fields: [fromScreenId], references: [id], onDelete: SetNull)
|
|
2847
|
+
toScreen Screen? @relation("CrawlDiffToScreen", fields: [toScreenId], references: [id], onDelete: SetNull)
|
|
2848
|
+
staleTestCases TestCase[] @relation("TestCaseCrawlDiff")
|
|
2849
|
+
createdAt DateTime @default(now())
|
|
2850
|
+
updatedAt DateTime @updatedAt
|
|
2851
|
+
|
|
2852
|
+
@@index([tenantId, projectId])
|
|
2853
|
+
@@index([tenantId, projectId, status])
|
|
2854
|
+
@@index([toJobId])
|
|
2855
|
+
@@index([fromJobId])
|
|
2856
|
+
@@index([severity, status])
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
model BugSignal {
|
|
2860
|
+
id String @id @default(cuid())
|
|
2861
|
+
tenantId String
|
|
2862
|
+
projectId String
|
|
2863
|
+
signalType String
|
|
2864
|
+
severity String @default("MEDIUM")
|
|
2865
|
+
title String
|
|
2866
|
+
description String?
|
|
2867
|
+
sourceRef Json @default("{}")
|
|
2868
|
+
affectedTestCaseIds Json @default("[]")
|
|
2869
|
+
affectedRequirementIds Json @default("[]")
|
|
2870
|
+
status String @default("OPEN")
|
|
2871
|
+
resolution String?
|
|
2872
|
+
assignedTo String?
|
|
2873
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2874
|
+
testResults TestResult[] @relation("BugSignalTestResults")
|
|
2875
|
+
visualDiffs VisualDiff[] @relation("BugSignalVisualDiffs")
|
|
2876
|
+
relatedTestCases TestCase[] @relation("BugSignalAffectedTestCases")
|
|
2877
|
+
createdAt DateTime @default(now())
|
|
2878
|
+
updatedAt DateTime @updatedAt
|
|
2879
|
+
|
|
2880
|
+
@@index([tenantId, projectId])
|
|
2881
|
+
@@index([tenantId, projectId, status])
|
|
2882
|
+
@@index([tenantId, projectId, signalType])
|
|
2883
|
+
@@index([tenantId, projectId, severity])
|
|
2884
|
+
@@index([assignedTo])
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
model RequirementCoverage {
|
|
2888
|
+
id String @id @default(cuid())
|
|
2889
|
+
tenantId String
|
|
2890
|
+
projectId String
|
|
2891
|
+
requirementId String
|
|
2892
|
+
testCaseIds Json @default("[]")
|
|
2893
|
+
coverageScore Float @default(0)
|
|
2894
|
+
uncoveredAcIds Json @default("[]")
|
|
2895
|
+
computedAt DateTime @default(now())
|
|
2896
|
+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
|
2897
|
+
|
|
2898
|
+
@@unique([projectId, requirementId])
|
|
2899
|
+
@@index([tenantId, projectId])
|
|
2900
|
+
@@index([tenantId, projectId, coverageScore])
|
|
2901
|
+
}
|