gaard-api 0.2.13__tar.gz → 0.2.16__tar.gz
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.
- {gaard_api-0.2.13/src/gaard_api.egg-info → gaard_api-0.2.16}/PKG-INFO +1 -1
- {gaard_api-0.2.13 → gaard_api-0.2.16}/pyproject.toml +1 -1
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/database_initial.sql +66 -0
- gaard_api-0.2.16/src/gaard_api/admin/database_updates.sql +73 -0
- gaard_api-0.2.16/src/gaard_api/admin/metadata.py +53 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/migration_runner.py +38 -4
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/models.py +147 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/services.py +16 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/main.js +38 -26
- gaard_api-0.2.16/src/gaard_api/analysis_findings.py +544 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/admin.py +165 -60
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/analysis.py +784 -21
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/dashboards.py +348 -87
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/query.py +34 -20
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/core/settings.py +6 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/main.py +1 -1
- {gaard_api-0.2.13 → gaard_api-0.2.16/src/gaard_api.egg-info}/PKG-INFO +1 -1
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api.egg-info/SOURCES.txt +2 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_admin_api.py +893 -11
- gaard_api-0.2.16/tests/test_analysis_api.py +1120 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_database_migrations.py +142 -5
- gaard_api-0.2.13/src/gaard_api/admin/database_updates.sql +0 -10
- gaard_api-0.2.13/tests/test_analysis_api.py +0 -586
- {gaard_api-0.2.13 → gaard_api-0.2.16}/MANIFEST.in +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/README.md +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/setup.cfg +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/database.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/database_legacy_updates.sql +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/defaults.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/prompt_runtime.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin/security.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/getgaard.svg +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/identity.js +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/styles.css +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack-all.js +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/assets/vendor/gridstack/gridstack.min.css +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/index.html +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/admin-web/package.json +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/prompts.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api/v1/schema.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/api_registry.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/auth_dependencies.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/auth_hooks.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/cli.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/cli_commands.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/conversations.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/core/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/core/error_handlers.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/core/schema_cache.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/example_data/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/example_data/medical_poc/__init__.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/example_data/medical_poc/schema.sql +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/example_data/medical_poc/seed.sql +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/example_database.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/extension_services.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/extensions.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/license.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/package_updates.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/py.typed +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/query_hooks.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/server_cli.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/siem.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api/tls_http.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api.egg-info/dependency_links.txt +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api.egg-info/entry_points.txt +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api.egg-info/requires.txt +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/src/gaard_api.egg-info/top_level.txt +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_api_cli.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_api_registry.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_conversation_api.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_error_handlers.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_license_api.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_license_service.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_prompt_runtime.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_query_error_messages.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_server_cli.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_settings.py +0 -0
- {gaard_api-0.2.13 → gaard_api-0.2.16}/tests/test_tls_http.py +0 -0
|
@@ -7,6 +7,10 @@ CREATE TABLE IF NOT EXISTS admin_sessions (id INTEGER NOT NULL, token_hash VARCH
|
|
|
7
7
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_admin_sessions_token_hash ON admin_sessions (token_hash);
|
|
8
8
|
CREATE TABLE IF NOT EXISTS admin_settings ("key" VARCHAR(255) NOT NULL, value TEXT NOT NULL, updated_at DATETIME NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY ("key"));
|
|
9
9
|
CREATE TABLE IF NOT EXISTS admin_users (id INTEGER NOT NULL, username VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL, auth_provider VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL, is_system_admin BOOLEAN NOT NULL, enterprise_access BOOLEAN NOT NULL, password_hash TEXT NOT NULL, must_change_password BOOLEAN NOT NULL, is_provisioned BOOLEAN NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_admin_users_auth_provider_username UNIQUE (auth_provider, username));
|
|
10
|
+
CREATE TABLE IF NOT EXISTS analysis_findings (id INTEGER NOT NULL, finding_id VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, connector_id INTEGER NOT NULL, business_logic_suggestion_id INTEGER, statement TEXT NOT NULL, finding_type VARCHAR(100) NOT NULL, confidence FLOAT NOT NULL, critique TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, status VARCHAR(50) NOT NULL, evidence_state VARCHAR(50) NOT NULL, decision VARCHAR(50) NOT NULL, decision_confidence FLOAT, verdict TEXT NOT NULL, decision_scope_json TEXT NOT NULL, decision_evidence_refs_json TEXT NOT NULL, decided_by VARCHAR(255) NOT NULL, contract_version VARCHAR(20) NOT NULL, decisions_json TEXT NOT NULL, evidence_updates_json TEXT NOT NULL, used_in_steps_json TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
|
|
11
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_findings_finding_id ON analysis_findings (finding_id);
|
|
12
|
+
CREATE TABLE IF NOT EXISTS analysis_finding_decisions (id INTEGER NOT NULL, decision_id VARCHAR(64) NOT NULL, idempotency_key VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, finding_id VARCHAR(64) NOT NULL, radar_run_id VARCHAR(255) NOT NULL, decision VARCHAR(50) NOT NULL, confidence FLOAT NOT NULL, verdict TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, is_current BOOLEAN NOT NULL, active BOOLEAN NOT NULL, contract_version VARCHAR(20) NOT NULL, actor_id VARCHAR(255) NOT NULL, actor_type VARCHAR(50) NOT NULL, actor_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_analysis_finding_decisions_idempotency_key UNIQUE (idempotency_key));
|
|
13
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision_id ON analysis_finding_decisions (decision_id);
|
|
10
14
|
CREATE TABLE IF NOT EXISTS analysis_sessions (id INTEGER NOT NULL, session_id VARCHAR(64) NOT NULL, status VARCHAR(50) NOT NULL, user_id VARCHAR(255) NOT NULL, datasource_id VARCHAR(255) NOT NULL, question TEXT NOT NULL, context_json TEXT NOT NULL, events_json TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
|
|
11
15
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_sessions_session_id ON analysis_sessions (session_id);
|
|
12
16
|
CREATE TABLE IF NOT EXISTS business_knowledge_claims (id INTEGER NOT NULL, connector_id INTEGER NOT NULL, knowledge_type VARCHAR(100) NOT NULL, status VARCHAR(50) NOT NULL, claim_text TEXT NOT NULL, subject_json TEXT NOT NULL, evidence_json TEXT NOT NULL, confidence FLOAT NOT NULL, source VARCHAR(100) NOT NULL, request_id VARCHAR(255) NOT NULL, audit_reference VARCHAR(255) NOT NULL, requires_approval BOOLEAN NOT NULL, created_at DATETIME NOT NULL, last_verified_at DATETIME, updated_at DATETIME NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id));
|
|
@@ -16,12 +20,15 @@ CREATE UNIQUE INDEX IF NOT EXISTS ix_conversation_turns_turn_id ON conversation_
|
|
|
16
20
|
CREATE TABLE IF NOT EXISTS conversations (id INTEGER NOT NULL, conversation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL, datasource_id VARCHAR(255) NOT NULL, datasource_ids_json TEXT NOT NULL, title VARCHAR(255) NOT NULL, summary_json TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
|
|
17
21
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_conversations_conversation_id ON conversations (conversation_id);
|
|
18
22
|
CREATE TABLE IF NOT EXISTS dashboard_user_states (owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, active_dashboard_id VARCHAR(64) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (owner_user_id));
|
|
23
|
+
CREATE TABLE IF NOT EXISTS dashboard_shares (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
|
|
19
24
|
CREATE TABLE IF NOT EXISTS dashboard_widgets (id INTEGER NOT NULL, widget_id VARCHAR(64) NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, metric_widget_key VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, visualization_type VARCHAR(50) NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, w INTEGER NOT NULL, h INTEGER NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_widgets_widget_id UNIQUE (widget_id));
|
|
20
25
|
CREATE TABLE IF NOT EXISTS dashboards (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
|
|
21
26
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_dashboards_dashboard_id ON dashboards (dashboard_id);
|
|
22
27
|
CREATE TABLE IF NOT EXISTS data_query_audit_logs (id INTEGER NOT NULL, occurred_at DATETIME NOT NULL, type VARCHAR(50) NOT NULL, user_id VARCHAR(255) NOT NULL, datasource_id VARCHAR(255) NOT NULL, question TEXT NOT NULL, answer TEXT NOT NULL, sql TEXT NOT NULL, llm_sql_language VARCHAR(50) NOT NULL, output_classification VARCHAR(50) NOT NULL, metadata_json TEXT NOT NULL, PRIMARY KEY (id), CONSTRAINT data_query_audit_type CHECK (type IN ('info', 'sql_error', 'access_error')), CONSTRAINT output_classification CHECK (output_classification IN ('personal_data', 'sensitive_data', 'technical_data', 'neutral_data', 'unknown')));
|
|
23
28
|
CREATE TABLE IF NOT EXISTS database_migration_tags (tag VARCHAR(255) NOT NULL, applied_at DATETIME NOT NULL, PRIMARY KEY (tag));
|
|
24
29
|
CREATE TABLE IF NOT EXISTS datasource_connectors (id INTEGER NOT NULL, connector_key VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, database_type VARCHAR(50) NOT NULL, database_url TEXT NOT NULL, sql_dialect VARCHAR(50) NOT NULL, active BOOLEAN NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_datasource_connectors_key UNIQUE (connector_key));
|
|
30
|
+
UPDATE datasource_connectors SET database_type = 'duckdb-file' WHERE database_type = 'duckdb-excel';
|
|
31
|
+
UPDATE datasource_connectors SET database_url = REPLACE(database_url, 'duckdb-excel://', 'duckdb-file://') WHERE database_url LIKE 'duckdb-excel://%';
|
|
25
32
|
CREATE TABLE IF NOT EXISTS datasource_schema_caches (connector_id INTEGER NOT NULL, schema_json TEXT NOT NULL, table_settings_json TEXT NOT NULL, formatted_schema TEXT NOT NULL, introspected_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (connector_id));
|
|
26
33
|
CREATE TABLE IF NOT EXISTS overview_widgets (id INTEGER NOT NULL, widget_key VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, widget_type VARCHAR(50) NOT NULL, datasource_key VARCHAR(255) NOT NULL, question TEXT NOT NULL, sql TEXT NOT NULL, result_mode VARCHAR(50) NOT NULL, position INTEGER NOT NULL, grid_width INTEGER NOT NULL, grid_height INTEGER NOT NULL, active BOOLEAN NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_overview_widgets_key UNIQUE (widget_key));
|
|
27
34
|
CREATE TABLE IF NOT EXISTS prompt_templates (id INTEGER NOT NULL, prompt_key VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, system_prompt TEXT NOT NULL, user_prompt_template TEXT NOT NULL, version INTEGER NOT NULL, active BOOLEAN NOT NULL, updated_by VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_prompt_templates_key UNIQUE (prompt_key));
|
|
@@ -40,6 +47,21 @@ CREATE INDEX IF NOT EXISTS ix_admin_sessions_username ON admin_sessions (usernam
|
|
|
40
47
|
CREATE INDEX IF NOT EXISTS ix_admin_users_auth_provider ON admin_users (auth_provider);
|
|
41
48
|
CREATE INDEX IF NOT EXISTS ix_admin_users_role ON admin_users (role);
|
|
42
49
|
CREATE INDEX IF NOT EXISTS ix_admin_users_username ON admin_users (username);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_connector_id ON analysis_findings (connector_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_decision ON analysis_findings (decision);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_evidence_state ON analysis_findings (evidence_state);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_finding_type ON analysis_findings (finding_type);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_investigation_id ON analysis_findings (investigation_id);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_owner_user_id ON analysis_findings (owner_user_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_status ON analysis_findings (status);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_active ON analysis_finding_decisions (active);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_id ON analysis_finding_decisions (actor_id);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_type ON analysis_finding_decisions (actor_type);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision ON analysis_finding_decisions (decision);
|
|
61
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_finding_id ON analysis_finding_decisions (finding_id);
|
|
62
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_investigation_id ON analysis_finding_decisions (investigation_id);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_is_current ON analysis_finding_decisions (is_current);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_radar_run_id ON analysis_finding_decisions (radar_run_id);
|
|
43
65
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_datasource_id ON analysis_sessions (datasource_id);
|
|
44
66
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_status ON analysis_sessions (status);
|
|
45
67
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_user_id ON analysis_sessions (user_id);
|
|
@@ -63,6 +85,12 @@ CREATE INDEX IF NOT EXISTS ix_conversations_owner_username ON conversations (own
|
|
|
63
85
|
CREATE INDEX IF NOT EXISTS ix_conversations_status ON conversations (status);
|
|
64
86
|
CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_active_dashboard_id ON dashboard_user_states (active_dashboard_id);
|
|
65
87
|
CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_owner_username ON dashboard_user_states (owner_username);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
|
|
66
94
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_dashboard_id ON dashboard_widgets (dashboard_id);
|
|
67
95
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_metric_widget_key ON dashboard_widgets (metric_widget_key);
|
|
68
96
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_owner_user_id ON dashboard_widgets (owner_user_id);
|
|
@@ -81,6 +109,11 @@ CREATE INDEX IF NOT EXISTS ix_user_datasource_selections_owner_username ON user_
|
|
|
81
109
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_owner_user_id ON user_saved_metrics (owner_user_id);
|
|
82
110
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_owner_username ON user_saved_metrics (owner_username);
|
|
83
111
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_widget_key ON user_saved_metrics (widget_key);
|
|
112
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_imports (id VARCHAR(36) NOT NULL, source_directory VARCHAR(2048) NOT NULL, status VARCHAR(16) NOT NULL, database_url VARCHAR(2048), storage_key VARCHAR(255) NOT NULL, preview_key VARCHAR(255), options_json TEXT NOT NULL, created_at DATETIME NOT NULL, started_at DATETIME, completed_at DATETIME, error_message TEXT, PRIMARY KEY (id), UNIQUE (storage_key));
|
|
113
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_imports_status ON duckdb_file_imports(status);
|
|
114
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_materializations (datasource_key VARCHAR(255) NOT NULL, materialization_name VARCHAR(255) NOT NULL, import_id VARCHAR(36) NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (datasource_key), UNIQUE (materialization_name), UNIQUE (import_id), FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
115
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_relations (id INTEGER NOT NULL, import_id VARCHAR(36) NOT NULL, source_file VARCHAR(1024) NOT NULL, source_format VARCHAR(32) NOT NULL, source_member VARCHAR(1024), adapter_key VARCHAR(64) NOT NULL, relation_name VARCHAR(255) NOT NULL, row_count INTEGER NOT NULL, column_count INTEGER NOT NULL, source_size_bytes INTEGER NOT NULL, imported_at DATETIME NOT NULL, PRIMARY KEY (id), FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
116
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_relations_import_id ON duckdb_file_relations(import_id);
|
|
84
117
|
|
|
85
118
|
-- dialect: postgresql
|
|
86
119
|
CREATE TABLE IF NOT EXISTS admin_audit_logs (id SERIAL NOT NULL, occurred_at TIMESTAMP WITH TIME ZONE NOT NULL, actor VARCHAR(255) NOT NULL, action VARCHAR(255) NOT NULL, resource_type VARCHAR(255) NOT NULL, resource_id VARCHAR(255) NOT NULL, details_json TEXT NOT NULL, PRIMARY KEY (id));
|
|
@@ -88,6 +121,10 @@ CREATE TABLE IF NOT EXISTS admin_sessions (id SERIAL NOT NULL, token_hash VARCHA
|
|
|
88
121
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_admin_sessions_token_hash ON admin_sessions (token_hash);
|
|
89
122
|
CREATE TABLE IF NOT EXISTS admin_settings ("key" VARCHAR(255) NOT NULL, value TEXT NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY ("key"));
|
|
90
123
|
CREATE TABLE IF NOT EXISTS admin_users (id SERIAL NOT NULL, username VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL, auth_provider VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL, is_system_admin BOOLEAN NOT NULL, enterprise_access BOOLEAN NOT NULL, password_hash TEXT NOT NULL, must_change_password BOOLEAN NOT NULL, is_provisioned BOOLEAN NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_admin_users_auth_provider_username UNIQUE (auth_provider, username));
|
|
124
|
+
CREATE TABLE IF NOT EXISTS analysis_findings (id SERIAL NOT NULL, finding_id VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, connector_id INTEGER NOT NULL, business_logic_suggestion_id INTEGER, statement TEXT NOT NULL, finding_type VARCHAR(100) NOT NULL, confidence FLOAT NOT NULL, critique TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, status VARCHAR(50) NOT NULL, evidence_state VARCHAR(50) NOT NULL, decision VARCHAR(50) NOT NULL, decision_confidence FLOAT, verdict TEXT NOT NULL, decision_scope_json TEXT NOT NULL, decision_evidence_refs_json TEXT NOT NULL, decided_by VARCHAR(255) NOT NULL, contract_version VARCHAR(20) NOT NULL, decisions_json TEXT NOT NULL, evidence_updates_json TEXT NOT NULL, used_in_steps_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
|
|
125
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_findings_finding_id ON analysis_findings (finding_id);
|
|
126
|
+
CREATE TABLE IF NOT EXISTS analysis_finding_decisions (id SERIAL NOT NULL, decision_id VARCHAR(64) NOT NULL, idempotency_key VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, finding_id VARCHAR(64) NOT NULL, radar_run_id VARCHAR(255) NOT NULL, decision VARCHAR(50) NOT NULL, confidence FLOAT NOT NULL, verdict TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, is_current BOOLEAN NOT NULL, active BOOLEAN NOT NULL, contract_version VARCHAR(20) NOT NULL, actor_id VARCHAR(255) NOT NULL, actor_type VARCHAR(50) NOT NULL, actor_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_analysis_finding_decisions_idempotency_key UNIQUE (idempotency_key));
|
|
127
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision_id ON analysis_finding_decisions (decision_id);
|
|
91
128
|
CREATE TABLE IF NOT EXISTS analysis_sessions (id SERIAL NOT NULL, session_id VARCHAR(64) NOT NULL, status VARCHAR(50) NOT NULL, user_id VARCHAR(255) NOT NULL, datasource_id VARCHAR(255) NOT NULL, question TEXT NOT NULL, context_json TEXT NOT NULL, events_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
|
|
92
129
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_sessions_session_id ON analysis_sessions (session_id);
|
|
93
130
|
CREATE TABLE IF NOT EXISTS business_knowledge_claims (id SERIAL NOT NULL, connector_id INTEGER NOT NULL, knowledge_type VARCHAR(100) NOT NULL, status VARCHAR(50) NOT NULL, claim_text TEXT NOT NULL, subject_json TEXT NOT NULL, evidence_json TEXT NOT NULL, confidence FLOAT NOT NULL, source VARCHAR(100) NOT NULL, request_id VARCHAR(255) NOT NULL, audit_reference VARCHAR(255) NOT NULL, requires_approval BOOLEAN NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, last_verified_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id));
|
|
@@ -97,12 +134,15 @@ CREATE UNIQUE INDEX IF NOT EXISTS ix_conversation_turns_turn_id ON conversation_
|
|
|
97
134
|
CREATE TABLE IF NOT EXISTS conversations (id SERIAL NOT NULL, conversation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL, datasource_id VARCHAR(255) NOT NULL, datasource_ids_json TEXT NOT NULL, title VARCHAR(255) NOT NULL, summary_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
|
|
98
135
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_conversations_conversation_id ON conversations (conversation_id);
|
|
99
136
|
CREATE TABLE IF NOT EXISTS dashboard_user_states (owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, active_dashboard_id VARCHAR(64) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (owner_user_id));
|
|
137
|
+
CREATE TABLE IF NOT EXISTS dashboard_shares (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
|
|
100
138
|
CREATE TABLE IF NOT EXISTS dashboard_widgets (id SERIAL NOT NULL, widget_id VARCHAR(64) NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, metric_widget_key VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, visualization_type VARCHAR(50) NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, w INTEGER NOT NULL, h INTEGER NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_widgets_widget_id UNIQUE (widget_id));
|
|
101
139
|
CREATE TABLE IF NOT EXISTS dashboards (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, owner_username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
|
|
102
140
|
CREATE UNIQUE INDEX IF NOT EXISTS ix_dashboards_dashboard_id ON dashboards (dashboard_id);
|
|
103
141
|
CREATE TABLE IF NOT EXISTS data_query_audit_logs (id SERIAL NOT NULL, occurred_at TIMESTAMP WITH TIME ZONE NOT NULL, type VARCHAR(50) NOT NULL, user_id VARCHAR(255) NOT NULL, datasource_id VARCHAR(255) NOT NULL, question TEXT NOT NULL, answer TEXT NOT NULL, sql TEXT NOT NULL, llm_sql_language VARCHAR(50) NOT NULL, output_classification VARCHAR(50) NOT NULL, metadata_json TEXT NOT NULL, PRIMARY KEY (id), CONSTRAINT data_query_audit_type CHECK (type IN ('info', 'sql_error', 'access_error')), CONSTRAINT output_classification CHECK (output_classification IN ('personal_data', 'sensitive_data', 'technical_data', 'neutral_data', 'unknown')));
|
|
104
142
|
CREATE TABLE IF NOT EXISTS database_migration_tags (tag VARCHAR(255) NOT NULL, applied_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (tag));
|
|
105
143
|
CREATE TABLE IF NOT EXISTS datasource_connectors (id SERIAL NOT NULL, connector_key VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, database_type VARCHAR(50) NOT NULL, database_url TEXT NOT NULL, sql_dialect VARCHAR(50) NOT NULL, active BOOLEAN NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_datasource_connectors_key UNIQUE (connector_key));
|
|
144
|
+
UPDATE datasource_connectors SET database_type = 'duckdb-file' WHERE database_type = 'duckdb-excel';
|
|
145
|
+
UPDATE datasource_connectors SET database_url = REPLACE(database_url, 'duckdb-excel://', 'duckdb-file://') WHERE database_url LIKE 'duckdb-excel://%';
|
|
106
146
|
CREATE TABLE IF NOT EXISTS datasource_schema_caches (connector_id INTEGER NOT NULL, schema_json TEXT NOT NULL, table_settings_json TEXT NOT NULL, formatted_schema TEXT NOT NULL, introspected_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (connector_id));
|
|
107
147
|
CREATE TABLE IF NOT EXISTS overview_widgets (id SERIAL NOT NULL, widget_key VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, widget_type VARCHAR(50) NOT NULL, datasource_key VARCHAR(255) NOT NULL, question TEXT NOT NULL, sql TEXT NOT NULL, result_mode VARCHAR(50) NOT NULL, position INTEGER NOT NULL, grid_width INTEGER NOT NULL, grid_height INTEGER NOT NULL, active BOOLEAN NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_by VARCHAR(255) NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_overview_widgets_key UNIQUE (widget_key));
|
|
108
148
|
CREATE TABLE IF NOT EXISTS prompt_templates (id SERIAL NOT NULL, prompt_key VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, description TEXT NOT NULL, system_prompt TEXT NOT NULL, user_prompt_template TEXT NOT NULL, version INTEGER NOT NULL, active BOOLEAN NOT NULL, updated_by VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_prompt_templates_key UNIQUE (prompt_key));
|
|
@@ -121,6 +161,21 @@ CREATE INDEX IF NOT EXISTS ix_admin_sessions_username ON admin_sessions (usernam
|
|
|
121
161
|
CREATE INDEX IF NOT EXISTS ix_admin_users_auth_provider ON admin_users (auth_provider);
|
|
122
162
|
CREATE INDEX IF NOT EXISTS ix_admin_users_role ON admin_users (role);
|
|
123
163
|
CREATE INDEX IF NOT EXISTS ix_admin_users_username ON admin_users (username);
|
|
164
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_connector_id ON analysis_findings (connector_id);
|
|
165
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_decision ON analysis_findings (decision);
|
|
166
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_evidence_state ON analysis_findings (evidence_state);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_finding_type ON analysis_findings (finding_type);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_investigation_id ON analysis_findings (investigation_id);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_owner_user_id ON analysis_findings (owner_user_id);
|
|
170
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_status ON analysis_findings (status);
|
|
171
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_active ON analysis_finding_decisions (active);
|
|
172
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_id ON analysis_finding_decisions (actor_id);
|
|
173
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_type ON analysis_finding_decisions (actor_type);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision ON analysis_finding_decisions (decision);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_finding_id ON analysis_finding_decisions (finding_id);
|
|
176
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_investigation_id ON analysis_finding_decisions (investigation_id);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_is_current ON analysis_finding_decisions (is_current);
|
|
178
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_radar_run_id ON analysis_finding_decisions (radar_run_id);
|
|
124
179
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_datasource_id ON analysis_sessions (datasource_id);
|
|
125
180
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_status ON analysis_sessions (status);
|
|
126
181
|
CREATE INDEX IF NOT EXISTS ix_analysis_sessions_user_id ON analysis_sessions (user_id);
|
|
@@ -144,6 +199,12 @@ CREATE INDEX IF NOT EXISTS ix_conversations_owner_username ON conversations (own
|
|
|
144
199
|
CREATE INDEX IF NOT EXISTS ix_conversations_status ON conversations (status);
|
|
145
200
|
CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_active_dashboard_id ON dashboard_user_states (active_dashboard_id);
|
|
146
201
|
CREATE INDEX IF NOT EXISTS ix_dashboard_user_states_owner_username ON dashboard_user_states (owner_username);
|
|
202
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
|
|
203
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
|
|
204
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
|
|
205
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
|
|
206
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
|
|
207
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
|
|
147
208
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_dashboard_id ON dashboard_widgets (dashboard_id);
|
|
148
209
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_metric_widget_key ON dashboard_widgets (metric_widget_key);
|
|
149
210
|
CREATE INDEX IF NOT EXISTS ix_dashboard_widgets_owner_user_id ON dashboard_widgets (owner_user_id);
|
|
@@ -162,3 +223,8 @@ CREATE INDEX IF NOT EXISTS ix_user_datasource_selections_owner_username ON user_
|
|
|
162
223
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_owner_user_id ON user_saved_metrics (owner_user_id);
|
|
163
224
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_owner_username ON user_saved_metrics (owner_username);
|
|
164
225
|
CREATE INDEX IF NOT EXISTS ix_user_saved_metrics_widget_key ON user_saved_metrics (widget_key);
|
|
226
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_imports (id VARCHAR(36) NOT NULL, source_directory VARCHAR(2048) NOT NULL, status VARCHAR(16) NOT NULL, database_url VARCHAR(2048), storage_key VARCHAR(255) NOT NULL, preview_key VARCHAR(255), options_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE, error_message TEXT, PRIMARY KEY (id), UNIQUE (storage_key));
|
|
227
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_imports_status ON duckdb_file_imports(status);
|
|
228
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_materializations (datasource_key VARCHAR(255) NOT NULL, materialization_name VARCHAR(255) NOT NULL, import_id VARCHAR(36) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (datasource_key), UNIQUE (materialization_name), UNIQUE (import_id), FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
229
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_relations (id SERIAL PRIMARY KEY, import_id VARCHAR(36) NOT NULL, source_file VARCHAR(1024) NOT NULL, source_format VARCHAR(32) NOT NULL, source_member VARCHAR(1024), adapter_key VARCHAR(64) NOT NULL, relation_name VARCHAR(255) NOT NULL, row_count INTEGER NOT NULL, column_count INTEGER NOT NULL, source_size_bytes INTEGER NOT NULL, imported_at TIMESTAMP WITH TIME ZONE NOT NULL, FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
230
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_relations_import_id ON duckdb_file_relations(import_id);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- Append new tagged groups only at the end of this file. Never edit, reorder, or reuse a tag.
|
|
2
|
+
-- Every non-comment line is one SQL command and must end with a semicolon.
|
|
3
|
+
|
|
4
|
+
-- tag: 2026-07-31.identity-ids-use-admin-user-id.v1
|
|
5
|
+
-- table: identity_privilege_datasource_permissions
|
|
6
|
+
DELETE FROM identity_privilege_datasource_permissions AS legacy WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE legacy.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) AND EXISTS (SELECT 1 FROM identity_privilege_datasource_permissions AS duplicate WHERE duplicate.connector_id = legacy.connector_id AND duplicate.id <> legacy.id AND (duplicate.identity_id = CAST(mapped_user.id AS VARCHAR(255)) OR (duplicate.id < legacy.id AND duplicate.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username)))));
|
|
7
|
+
UPDATE identity_privilege_datasource_permissions AS permission SET identity_id = (SELECT CAST(mapped_user.id AS VARCHAR(255)) FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) ORDER BY mapped_user.id LIMIT 1) WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username));
|
|
8
|
+
-- table: identity_privilege_table_permissions
|
|
9
|
+
DELETE FROM identity_privilege_table_permissions AS legacy WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE legacy.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) AND EXISTS (SELECT 1 FROM identity_privilege_table_permissions AS duplicate WHERE duplicate.connector_id = legacy.connector_id AND duplicate.table_name = legacy.table_name AND duplicate.id <> legacy.id AND (duplicate.identity_id = CAST(mapped_user.id AS VARCHAR(255)) OR (duplicate.id < legacy.id AND duplicate.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username)))));
|
|
10
|
+
UPDATE identity_privilege_table_permissions AS permission SET identity_id = (SELECT CAST(mapped_user.id AS VARCHAR(255)) FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username) ORDER BY mapped_user.id LIMIT 1) WHERE EXISTS (SELECT 1 FROM admin_users AS mapped_user WHERE permission.identity_id IN ('local:' || CAST(mapped_user.id AS VARCHAR(255)), mapped_user.auth_provider || ':' || mapped_user.username));
|
|
11
|
+
|
|
12
|
+
-- tag: 2026-08-01.dashboard-sharing.v1
|
|
13
|
+
-- dialect: sqlite
|
|
14
|
+
CREATE TABLE IF NOT EXISTS dashboard_shares (id INTEGER NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
|
|
15
|
+
-- dialect: postgresql
|
|
16
|
+
CREATE TABLE IF NOT EXISTS dashboard_shares (id SERIAL NOT NULL, dashboard_id VARCHAR(64) NOT NULL, target_user_id VARCHAR(255) NOT NULL, target_username VARCHAR(255) NOT NULL, access_level VARCHAR(20) NOT NULL, created_by_user_id VARCHAR(255) NOT NULL, created_by_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_dashboard_shares_dashboard_target UNIQUE (dashboard_id, target_user_id));
|
|
17
|
+
-- dialect: all
|
|
18
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_access_level ON dashboard_shares (access_level);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_user_id ON dashboard_shares (created_by_user_id);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_created_by_username ON dashboard_shares (created_by_username);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_dashboard_id ON dashboard_shares (dashboard_id);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_user_id ON dashboard_shares (target_user_id);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS ix_dashboard_shares_target_username ON dashboard_shares (target_username);
|
|
24
|
+
|
|
25
|
+
-- tag: 2026-08-12.duckdb-file-connector.initial.v1
|
|
26
|
+
-- dialect: sqlite
|
|
27
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_imports (id VARCHAR(36) NOT NULL, mode VARCHAR(16) NOT NULL, original_filename VARCHAR(1024) NOT NULL, status VARCHAR(16) NOT NULL, database_url VARCHAR(2048), storage_key VARCHAR(255) NOT NULL, options_json TEXT NOT NULL, created_at DATETIME NOT NULL, started_at DATETIME, completed_at DATETIME, error_message TEXT, PRIMARY KEY (id), UNIQUE (storage_key));
|
|
28
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_imports_status ON duckdb_file_imports(status);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_relations (id INTEGER NOT NULL, import_id VARCHAR(36) NOT NULL, source_file VARCHAR(1024) NOT NULL, source_format VARCHAR(32) NOT NULL, source_member VARCHAR(1024), adapter_key VARCHAR(64) NOT NULL, relation_name VARCHAR(255) NOT NULL, row_count INTEGER NOT NULL, column_count INTEGER NOT NULL, source_size_bytes INTEGER NOT NULL, imported_at DATETIME NOT NULL, PRIMARY KEY (id), FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_relations_import_id ON duckdb_file_relations(import_id);
|
|
31
|
+
-- dialect: postgresql
|
|
32
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_imports (id VARCHAR(36) NOT NULL, mode VARCHAR(16) NOT NULL, original_filename VARCHAR(1024) NOT NULL, status VARCHAR(16) NOT NULL, database_url VARCHAR(2048), storage_key VARCHAR(255) NOT NULL, options_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE, error_message TEXT, PRIMARY KEY (id), UNIQUE (storage_key));
|
|
33
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_imports_status ON duckdb_file_imports(status);
|
|
34
|
+
CREATE TABLE IF NOT EXISTS duckdb_file_relations (id SERIAL PRIMARY KEY, import_id VARCHAR(36) NOT NULL, source_file VARCHAR(1024) NOT NULL, source_format VARCHAR(32) NOT NULL, source_member VARCHAR(1024), adapter_key VARCHAR(64) NOT NULL, relation_name VARCHAR(255) NOT NULL, row_count INTEGER NOT NULL, column_count INTEGER NOT NULL, source_size_bytes INTEGER NOT NULL, imported_at TIMESTAMP WITH TIME ZONE NOT NULL, FOREIGN KEY(import_id) REFERENCES duckdb_file_imports(id) ON DELETE CASCADE);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS ix_duckdb_file_relations_import_id ON duckdb_file_relations(import_id);
|
|
36
|
+
|
|
37
|
+
-- tag: 2026-08-14.duckdb-file-connector.migrate-duckdb-excel.v1
|
|
38
|
+
-- dialect: all
|
|
39
|
+
-- table: datasource_connectors
|
|
40
|
+
UPDATE datasource_connectors SET database_type = 'duckdb-file' WHERE database_type = 'duckdb-excel';
|
|
41
|
+
-- table: datasource_connectors
|
|
42
|
+
UPDATE datasource_connectors SET database_url = REPLACE(database_url, 'duckdb-excel://', 'duckdb-file://') WHERE database_url LIKE 'duckdb-excel://%';
|
|
43
|
+
|
|
44
|
+
-- tag: 2026-09-06.analysis-findings.v1
|
|
45
|
+
-- dialect: sqlite
|
|
46
|
+
CREATE TABLE IF NOT EXISTS analysis_findings (id INTEGER NOT NULL, finding_id VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, connector_id INTEGER NOT NULL, business_logic_suggestion_id INTEGER, statement TEXT NOT NULL, finding_type VARCHAR(100) NOT NULL, confidence FLOAT NOT NULL, critique TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, status VARCHAR(50) NOT NULL, evidence_state VARCHAR(50) NOT NULL, decision VARCHAR(50) NOT NULL, decision_confidence FLOAT, verdict TEXT NOT NULL, decision_scope_json TEXT NOT NULL, decision_evidence_refs_json TEXT NOT NULL, decided_by VARCHAR(255) NOT NULL, contract_version VARCHAR(20) NOT NULL, decisions_json TEXT NOT NULL, evidence_updates_json TEXT NOT NULL, used_in_steps_json TEXT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id));
|
|
47
|
+
-- dialect: postgresql
|
|
48
|
+
CREATE TABLE IF NOT EXISTS analysis_findings (id SERIAL NOT NULL, finding_id VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, owner_user_id VARCHAR(255) NOT NULL, connector_id INTEGER NOT NULL, business_logic_suggestion_id INTEGER, statement TEXT NOT NULL, finding_type VARCHAR(100) NOT NULL, confidence FLOAT NOT NULL, critique TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, status VARCHAR(50) NOT NULL, evidence_state VARCHAR(50) NOT NULL, decision VARCHAR(50) NOT NULL, decision_confidence FLOAT, verdict TEXT NOT NULL, decision_scope_json TEXT NOT NULL, decision_evidence_refs_json TEXT NOT NULL, decided_by VARCHAR(255) NOT NULL, contract_version VARCHAR(20) NOT NULL, decisions_json TEXT NOT NULL, evidence_updates_json TEXT NOT NULL, used_in_steps_json TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id));
|
|
49
|
+
-- dialect: all
|
|
50
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_findings_finding_id ON analysis_findings (finding_id);
|
|
51
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_connector_id ON analysis_findings (connector_id);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_decision ON analysis_findings (decision);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_evidence_state ON analysis_findings (evidence_state);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_finding_type ON analysis_findings (finding_type);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_investigation_id ON analysis_findings (investigation_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_owner_user_id ON analysis_findings (owner_user_id);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_findings_status ON analysis_findings (status);
|
|
58
|
+
|
|
59
|
+
-- tag: 2026-09-06.analysis-finding-decisions.v1
|
|
60
|
+
-- dialect: sqlite
|
|
61
|
+
CREATE TABLE IF NOT EXISTS analysis_finding_decisions (id INTEGER NOT NULL, decision_id VARCHAR(64) NOT NULL, idempotency_key VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, finding_id VARCHAR(64) NOT NULL, radar_run_id VARCHAR(255) NOT NULL, decision VARCHAR(50) NOT NULL, confidence FLOAT NOT NULL, verdict TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, is_current BOOLEAN NOT NULL, active BOOLEAN NOT NULL, contract_version VARCHAR(20) NOT NULL, actor_id VARCHAR(255) NOT NULL, actor_type VARCHAR(50) NOT NULL, actor_username VARCHAR(255) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_analysis_finding_decisions_idempotency_key UNIQUE (idempotency_key));
|
|
62
|
+
-- dialect: postgresql
|
|
63
|
+
CREATE TABLE IF NOT EXISTS analysis_finding_decisions (id SERIAL NOT NULL, decision_id VARCHAR(64) NOT NULL, idempotency_key VARCHAR(64) NOT NULL, investigation_id VARCHAR(64) NOT NULL, finding_id VARCHAR(64) NOT NULL, radar_run_id VARCHAR(255) NOT NULL, decision VARCHAR(50) NOT NULL, confidence FLOAT NOT NULL, verdict TEXT NOT NULL, scope_json TEXT NOT NULL, evidence_refs_json TEXT NOT NULL, is_current BOOLEAN NOT NULL, active BOOLEAN NOT NULL, contract_version VARCHAR(20) NOT NULL, actor_id VARCHAR(255) NOT NULL, actor_type VARCHAR(50) NOT NULL, actor_username VARCHAR(255) NOT NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL, updated_at TIMESTAMP WITH TIME ZONE NOT NULL, PRIMARY KEY (id), CONSTRAINT uq_analysis_finding_decisions_idempotency_key UNIQUE (idempotency_key));
|
|
64
|
+
-- dialect: all
|
|
65
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision_id ON analysis_finding_decisions (decision_id);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_active ON analysis_finding_decisions (active);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_id ON analysis_finding_decisions (actor_id);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_actor_type ON analysis_finding_decisions (actor_type);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_decision ON analysis_finding_decisions (decision);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_finding_id ON analysis_finding_decisions (finding_id);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_investigation_id ON analysis_finding_decisions (investigation_id);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_is_current ON analysis_finding_decisions (is_current);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS ix_analysis_finding_decisions_radar_run_id ON analysis_finding_decisions (radar_run_id);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Public metadata-store API for trusted GAARD extensions.
|
|
2
|
+
|
|
3
|
+
Extensions should import metadata sessions and the shared declarative base from
|
|
4
|
+
this module instead of depending on the admin database implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Iterator
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
from gaard_api.admin.database import create_session
|
|
13
|
+
from gaard_api.admin.models import (
|
|
14
|
+
Base,
|
|
15
|
+
DatasourceConnector,
|
|
16
|
+
DuckDBFileImport,
|
|
17
|
+
DuckDBFileMaterialization,
|
|
18
|
+
DuckDBFileRelation,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
MetadataModelBase = Base
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_metadata_session() -> Session:
|
|
25
|
+
"""Return a session connected to the configured GAARD metadata store."""
|
|
26
|
+
|
|
27
|
+
return create_session()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@contextmanager
|
|
31
|
+
def metadata_session() -> Iterator[Session]:
|
|
32
|
+
"""Open a transactional metadata session and roll back on failure."""
|
|
33
|
+
|
|
34
|
+
session = create_metadata_session()
|
|
35
|
+
try:
|
|
36
|
+
yield session
|
|
37
|
+
session.commit()
|
|
38
|
+
except BaseException:
|
|
39
|
+
session.rollback()
|
|
40
|
+
raise
|
|
41
|
+
finally:
|
|
42
|
+
session.close()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"DatasourceConnector",
|
|
47
|
+
"DuckDBFileImport",
|
|
48
|
+
"DuckDBFileMaterialization",
|
|
49
|
+
"DuckDBFileRelation",
|
|
50
|
+
"MetadataModelBase",
|
|
51
|
+
"create_metadata_session",
|
|
52
|
+
"metadata_session",
|
|
53
|
+
]
|
|
@@ -28,6 +28,8 @@ class SqlUpdateCommand:
|
|
|
28
28
|
sql: str
|
|
29
29
|
dialect: str | None = None
|
|
30
30
|
required_table: str | None = None
|
|
31
|
+
required_columns: tuple[str, ...] = ()
|
|
32
|
+
missing_columns: tuple[str, ...] = ()
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
@dataclass(frozen=True, slots=True)
|
|
@@ -52,6 +54,8 @@ def parse_sql_updates(contents: str) -> tuple[TaggedSqlUpdate, ...]:
|
|
|
52
54
|
current_tag: str | None = None
|
|
53
55
|
current_dialect: str | None = None
|
|
54
56
|
current_table: str | None = None
|
|
57
|
+
required_columns: list[str] = []
|
|
58
|
+
missing_columns: list[str] = []
|
|
55
59
|
current_commands: list[SqlUpdateCommand] = []
|
|
56
60
|
|
|
57
61
|
def finish_current_update() -> None:
|
|
@@ -94,6 +98,24 @@ def parse_sql_updates(contents: str) -> tuple[TaggedSqlUpdate, ...]:
|
|
|
94
98
|
current_table = None if table_name.lower() == "all" else table_name
|
|
95
99
|
continue
|
|
96
100
|
|
|
101
|
+
column_exists_match = _COLUMN_EXISTS_PATTERN.fullmatch(line)
|
|
102
|
+
if column_exists_match:
|
|
103
|
+
if current_tag is None or current_table is None:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Column condition without a tagged table on line {line_number}."
|
|
106
|
+
)
|
|
107
|
+
required_columns.append(column_exists_match.group(1))
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
column_missing_match = _COLUMN_MISSING_PATTERN.fullmatch(line)
|
|
111
|
+
if column_missing_match:
|
|
112
|
+
if current_tag is None or current_table is None:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Column condition without a tagged table on line {line_number}."
|
|
115
|
+
)
|
|
116
|
+
missing_columns.append(column_missing_match.group(1))
|
|
117
|
+
continue
|
|
118
|
+
|
|
97
119
|
if line.startswith("--"):
|
|
98
120
|
continue
|
|
99
121
|
if current_tag is None:
|
|
@@ -110,8 +132,12 @@ def parse_sql_updates(contents: str) -> tuple[TaggedSqlUpdate, ...]:
|
|
|
110
132
|
sql=sql,
|
|
111
133
|
dialect=current_dialect,
|
|
112
134
|
required_table=current_table,
|
|
135
|
+
required_columns=tuple(required_columns),
|
|
136
|
+
missing_columns=tuple(missing_columns),
|
|
113
137
|
)
|
|
114
138
|
)
|
|
139
|
+
required_columns = []
|
|
140
|
+
missing_columns = []
|
|
115
141
|
|
|
116
142
|
finish_current_update()
|
|
117
143
|
_validate_unique_tags(updates)
|
|
@@ -306,10 +332,18 @@ def _execute_update(connection: Connection, update: TaggedSqlUpdate) -> None:
|
|
|
306
332
|
for command in update.commands:
|
|
307
333
|
if command.dialect is not None and command.dialect != dialect:
|
|
308
334
|
continue
|
|
309
|
-
if command.required_table is not None
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
335
|
+
if command.required_table is not None:
|
|
336
|
+
inspector = inspect(connection)
|
|
337
|
+
if not inspector.has_table(command.required_table):
|
|
338
|
+
continue
|
|
339
|
+
column_names = {
|
|
340
|
+
str(column["name"])
|
|
341
|
+
for column in inspector.get_columns(command.required_table)
|
|
342
|
+
}
|
|
343
|
+
if not all(column in column_names for column in command.required_columns):
|
|
344
|
+
continue
|
|
345
|
+
if not all(column not in column_names for column in command.missing_columns):
|
|
346
|
+
continue
|
|
313
347
|
connection.exec_driver_sql(command.sql)
|
|
314
348
|
|
|
315
349
|
|
|
@@ -35,6 +35,55 @@ class DatabaseMigrationTag(Base):
|
|
|
35
35
|
applied_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
class DuckDBFileImport(Base):
|
|
39
|
+
"""One extension-managed directory materialization."""
|
|
40
|
+
|
|
41
|
+
__tablename__ = "duckdb_file_imports"
|
|
42
|
+
|
|
43
|
+
id: Mapped[str] = mapped_column(String(36), primary_key=True)
|
|
44
|
+
source_directory: Mapped[str] = mapped_column(String(2048))
|
|
45
|
+
status: Mapped[str] = mapped_column(String(16), index=True)
|
|
46
|
+
database_url: Mapped[str | None] = mapped_column(String(2048), nullable=True)
|
|
47
|
+
storage_key: Mapped[str] = mapped_column(String(255), unique=True)
|
|
48
|
+
preview_key: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
49
|
+
options_json: Mapped[str] = mapped_column(Text, default="{}")
|
|
50
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
51
|
+
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
52
|
+
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
53
|
+
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DuckDBFileMaterialization(Base):
|
|
57
|
+
"""A saved datasource key mapped to one durable DuckDB file."""
|
|
58
|
+
|
|
59
|
+
__tablename__ = "duckdb_file_materializations"
|
|
60
|
+
|
|
61
|
+
datasource_key: Mapped[str] = mapped_column(String(255), primary_key=True)
|
|
62
|
+
materialization_name: Mapped[str] = mapped_column(String(255), unique=True)
|
|
63
|
+
import_id: Mapped[str] = mapped_column(
|
|
64
|
+
String(36), ForeignKey("duckdb_file_imports.id", ondelete="CASCADE"), unique=True
|
|
65
|
+
)
|
|
66
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DuckDBFileRelation(Base):
|
|
70
|
+
__tablename__ = "duckdb_file_relations"
|
|
71
|
+
|
|
72
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
73
|
+
import_id: Mapped[str] = mapped_column(
|
|
74
|
+
String(36), ForeignKey("duckdb_file_imports.id", ondelete="CASCADE"), index=True
|
|
75
|
+
)
|
|
76
|
+
source_file: Mapped[str] = mapped_column(String(1024))
|
|
77
|
+
source_format: Mapped[str] = mapped_column(String(32))
|
|
78
|
+
source_member: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
|
79
|
+
adapter_key: Mapped[str] = mapped_column(String(64))
|
|
80
|
+
relation_name: Mapped[str] = mapped_column(String(255))
|
|
81
|
+
row_count: Mapped[int] = mapped_column(Integer)
|
|
82
|
+
column_count: Mapped[int] = mapped_column(Integer)
|
|
83
|
+
source_size_bytes: Mapped[int] = mapped_column(Integer)
|
|
84
|
+
imported_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
85
|
+
|
|
86
|
+
|
|
38
87
|
class AdminUser(Base):
|
|
39
88
|
__tablename__ = "admin_users"
|
|
40
89
|
__table_args__ = (
|
|
@@ -209,6 +258,31 @@ class DashboardUserState(Base):
|
|
|
209
258
|
)
|
|
210
259
|
|
|
211
260
|
|
|
261
|
+
class DashboardShare(Base):
|
|
262
|
+
__tablename__ = "dashboard_shares"
|
|
263
|
+
__table_args__ = (
|
|
264
|
+
UniqueConstraint(
|
|
265
|
+
"dashboard_id",
|
|
266
|
+
"target_user_id",
|
|
267
|
+
name="uq_dashboard_shares_dashboard_target",
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
272
|
+
dashboard_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
273
|
+
target_user_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
274
|
+
target_username: Mapped[str] = mapped_column(String(255), index=True, default="")
|
|
275
|
+
access_level: Mapped[str] = mapped_column(String(20), index=True)
|
|
276
|
+
created_by_user_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
277
|
+
created_by_username: Mapped[str] = mapped_column(String(255), index=True, default="")
|
|
278
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
279
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
280
|
+
DateTime(timezone=True),
|
|
281
|
+
default=utc_now,
|
|
282
|
+
onupdate=utc_now,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
212
286
|
class UserDatasourceSelection(Base):
|
|
213
287
|
"""Client datasource selections, scoped to one authenticated user."""
|
|
214
288
|
|
|
@@ -433,6 +507,79 @@ class BusinessKnowledgeClaim(Base):
|
|
|
433
507
|
updated_by: Mapped[str] = mapped_column(String(255), default="system")
|
|
434
508
|
|
|
435
509
|
|
|
510
|
+
class AnalysisFinding(Base):
|
|
511
|
+
"""Investigation-scoped semantic knowledge and its external review audit."""
|
|
512
|
+
|
|
513
|
+
__tablename__ = "analysis_findings"
|
|
514
|
+
|
|
515
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
516
|
+
finding_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
517
|
+
investigation_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
518
|
+
owner_user_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
519
|
+
connector_id: Mapped[int] = mapped_column(Integer, index=True)
|
|
520
|
+
business_logic_suggestion_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
521
|
+
statement: Mapped[str] = mapped_column(Text)
|
|
522
|
+
finding_type: Mapped[str] = mapped_column(String(100), index=True)
|
|
523
|
+
confidence: Mapped[float] = mapped_column(Float, default=0.0)
|
|
524
|
+
critique: Mapped[str] = mapped_column(Text, default="")
|
|
525
|
+
scope_json: Mapped[str] = mapped_column(Text, default="{}")
|
|
526
|
+
evidence_refs_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
527
|
+
status: Mapped[str] = mapped_column(String(50), index=True, default="pending")
|
|
528
|
+
evidence_state: Mapped[str] = mapped_column(String(50), index=True, default="unreviewed")
|
|
529
|
+
decision: Mapped[str] = mapped_column(String(50), index=True, default="")
|
|
530
|
+
decision_confidence: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
531
|
+
verdict: Mapped[str] = mapped_column(Text, default="")
|
|
532
|
+
decision_scope_json: Mapped[str] = mapped_column(Text, default="{}")
|
|
533
|
+
decision_evidence_refs_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
534
|
+
decided_by: Mapped[str] = mapped_column(String(255), default="")
|
|
535
|
+
contract_version: Mapped[str] = mapped_column(String(20), default="1.0")
|
|
536
|
+
decisions_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
537
|
+
evidence_updates_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
538
|
+
used_in_steps_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
539
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
540
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
541
|
+
DateTime(timezone=True),
|
|
542
|
+
default=utc_now,
|
|
543
|
+
onupdate=utc_now,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class AnalysisFindingDecision(Base):
|
|
548
|
+
"""One durable external decision about an investigation-scoped finding."""
|
|
549
|
+
|
|
550
|
+
__tablename__ = "analysis_finding_decisions"
|
|
551
|
+
__table_args__ = (
|
|
552
|
+
UniqueConstraint(
|
|
553
|
+
"idempotency_key",
|
|
554
|
+
name="uq_analysis_finding_decisions_idempotency_key",
|
|
555
|
+
),
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
559
|
+
decision_id: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
560
|
+
idempotency_key: Mapped[str] = mapped_column(String(64))
|
|
561
|
+
investigation_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
562
|
+
finding_id: Mapped[str] = mapped_column(String(64), index=True)
|
|
563
|
+
radar_run_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
564
|
+
decision: Mapped[str] = mapped_column(String(50), index=True)
|
|
565
|
+
confidence: Mapped[float] = mapped_column(Float)
|
|
566
|
+
verdict: Mapped[str] = mapped_column(Text)
|
|
567
|
+
scope_json: Mapped[str] = mapped_column(Text, default="{}")
|
|
568
|
+
evidence_refs_json: Mapped[str] = mapped_column(Text, default="[]")
|
|
569
|
+
is_current: Mapped[bool] = mapped_column(Boolean, index=True, default=True)
|
|
570
|
+
active: Mapped[bool] = mapped_column(Boolean, index=True, default=False)
|
|
571
|
+
contract_version: Mapped[str] = mapped_column(String(20), default="1.0")
|
|
572
|
+
actor_id: Mapped[str] = mapped_column(String(255), index=True)
|
|
573
|
+
actor_type: Mapped[str] = mapped_column(String(50), index=True, default="radar")
|
|
574
|
+
actor_username: Mapped[str] = mapped_column(String(255), default="")
|
|
575
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now)
|
|
576
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
577
|
+
DateTime(timezone=True),
|
|
578
|
+
default=utc_now,
|
|
579
|
+
onupdate=utc_now,
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
|
|
436
583
|
class AnalysisSessionRecord(Base):
|
|
437
584
|
__tablename__ = "analysis_sessions"
|
|
438
585
|
|
|
@@ -2381,6 +2381,14 @@ def get_datasource_schema_context_safe() -> (
|
|
|
2381
2381
|
if connector is None:
|
|
2382
2382
|
return None
|
|
2383
2383
|
|
|
2384
|
+
definition = get_connector_registry().get(connector.database_type)
|
|
2385
|
+
if definition.datasource_url_finalizer is not None:
|
|
2386
|
+
connector.database_url = definition.datasource_url_finalizer(
|
|
2387
|
+
session, connector.connector_key, connector.database_url
|
|
2388
|
+
)
|
|
2389
|
+
definition.validate_database_url(connector.database_url)
|
|
2390
|
+
session.commit()
|
|
2391
|
+
|
|
2384
2392
|
cache = get_datasource_schema_cache(session, connector.id)
|
|
2385
2393
|
|
|
2386
2394
|
if cache is None:
|
|
@@ -2422,6 +2430,14 @@ def get_datasource_schema_contexts_safe(
|
|
|
2422
2430
|
|
|
2423
2431
|
contexts: list[tuple[DatasourceConnector, DatasourceSchemaCache]] = []
|
|
2424
2432
|
for connector in connectors:
|
|
2433
|
+
definition = get_connector_registry().get(connector.database_type)
|
|
2434
|
+
if definition.datasource_url_finalizer is not None:
|
|
2435
|
+
connector.database_url = definition.datasource_url_finalizer(
|
|
2436
|
+
session, connector.connector_key, connector.database_url
|
|
2437
|
+
)
|
|
2438
|
+
definition.validate_database_url(connector.database_url)
|
|
2439
|
+
session.commit()
|
|
2440
|
+
|
|
2425
2441
|
cache = get_datasource_schema_cache(session, connector.id)
|
|
2426
2442
|
|
|
2427
2443
|
if cache is None:
|