code-atlas-forge 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_atlas/__init__.py +6 -0
- code_atlas/__main__.py +6 -0
- code_atlas/api/__init__.py +5 -0
- code_atlas/api/handlers.py +1514 -0
- code_atlas/api/server.py +394 -0
- code_atlas/cli.py +2084 -0
- code_atlas/config.py +149 -0
- code_atlas/context/__init__.py +66 -0
- code_atlas/context/contracts.py +277 -0
- code_atlas/context/domain.py +760 -0
- code_atlas/context/engine.py +442 -0
- code_atlas/context/expand.py +133 -0
- code_atlas/context/renderers.py +71 -0
- code_atlas/context/tokenizer.py +37 -0
- code_atlas/contracts/__init__.py +38 -0
- code_atlas/contracts/enums.py +67 -0
- code_atlas/contracts/models.py +175 -0
- code_atlas/contracts/ports.py +70 -0
- code_atlas/discovery/__init__.py +25 -0
- code_atlas/discovery/gitignore.py +119 -0
- code_atlas/discovery/identity.py +52 -0
- code_atlas/discovery/models.py +204 -0
- code_atlas/discovery/scanner.py +495 -0
- code_atlas/domains/__init__.py +107 -0
- code_atlas/domains/api/__init__.py +28 -0
- code_atlas/domains/api/adapter.py +405 -0
- code_atlas/domains/api/common.py +308 -0
- code_atlas/domains/api/consumers.py +224 -0
- code_atlas/domains/api/emit.py +231 -0
- code_atlas/domains/api/openapi.py +398 -0
- code_atlas/domains/api/routes.py +582 -0
- code_atlas/domains/api/yaml_subset.py +683 -0
- code_atlas/domains/aws/__init__.py +28 -0
- code_atlas/domains/aws/adapter.py +382 -0
- code_atlas/domains/aws/common.py +261 -0
- code_atlas/domains/aws/configs.py +1164 -0
- code_atlas/domains/aws/emit.py +411 -0
- code_atlas/domains/aws/pyfiles.py +2220 -0
- code_atlas/domains/aws/sqlbridge.py +448 -0
- code_atlas/domains/cicd/__init__.py +50 -0
- code_atlas/domains/cicd/adapter.py +762 -0
- code_atlas/domains/cicd/classify.py +136 -0
- code_atlas/domains/cicd/workflow.py +504 -0
- code_atlas/domains/cicd/yamlmini.py +946 -0
- code_atlas/domains/cli_surface.py +739 -0
- code_atlas/domains/contracts.py +274 -0
- code_atlas/domains/crossresolve.py +1208 -0
- code_atlas/domains/evidence.py +57 -0
- code_atlas/domains/identity.py +149 -0
- code_atlas/domains/model.py +202 -0
- code_atlas/domains/registry.py +250 -0
- code_atlas/domains/resolution.py +65 -0
- code_atlas/domains/spark/__init__.py +31 -0
- code_atlas/domains/spark/adapter.py +146 -0
- code_atlas/domains/spark/emit.py +344 -0
- code_atlas/domains/spark/extract.py +108 -0
- code_atlas/domains/spark/sqllit.py +192 -0
- code_atlas/domains/spark/walk.py +1943 -0
- code_atlas/domains/sql/__init__.py +30 -0
- code_atlas/domains/sql/adapter.py +382 -0
- code_atlas/domains/sql/extract.py +1271 -0
- code_atlas/domains/sql/nodes.py +264 -0
- code_atlas/domains/sql/parser.py +1510 -0
- code_atlas/domains/sql/tokens.py +318 -0
- code_atlas/domains/summarize.py +126 -0
- code_atlas/domains/terraform/__init__.py +20 -0
- code_atlas/domains/terraform/adapter.py +127 -0
- code_atlas/domains/terraform/catalog.py +43 -0
- code_atlas/domains/terraform/extract.py +1277 -0
- code_atlas/domains/terraform/hcl.py +538 -0
- code_atlas/domains/terraform/refs.py +181 -0
- code_atlas/domains/terraform/tfjson.py +163 -0
- code_atlas/domains/terraform/tfvars.py +98 -0
- code_atlas/domains/validation.py +120 -0
- code_atlas/env_override.py +27 -0
- code_atlas/eval/__init__.py +26 -0
- code_atlas/eval/domain_eval.py +681 -0
- code_atlas/eval/ground_truth.py +688 -0
- code_atlas/eval/impact_truth.py +758 -0
- code_atlas/eval/metrics.py +178 -0
- code_atlas/eval/pack_value.py +610 -0
- code_atlas/federation/__init__.py +72 -0
- code_atlas/federation/federation.py +676 -0
- code_atlas/federation/model.py +355 -0
- code_atlas/federation/store.py +194 -0
- code_atlas/gitbin.py +48 -0
- code_atlas/graph/__init__.py +20 -0
- code_atlas/graph/builder.py +378 -0
- code_atlas/graph/identity.py +54 -0
- code_atlas/graph/queries.py +47 -0
- code_atlas/graph/validation.py +92 -0
- code_atlas/impact/__init__.py +37 -0
- code_atlas/impact/blast_radius.py +171 -0
- code_atlas/impact/contract_analyzer.py +104 -0
- code_atlas/impact/contracts.py +315 -0
- code_atlas/impact/diff_reader.py +142 -0
- code_atlas/impact/domain_traversal.py +768 -0
- code_atlas/impact/engine.py +392 -0
- code_atlas/impact/packs.py +128 -0
- code_atlas/impact/report.py +136 -0
- code_atlas/impact/revision_binding.py +220 -0
- code_atlas/impact/risk_classifier.py +83 -0
- code_atlas/impact/snapshot_resolver.py +136 -0
- code_atlas/impact/symbol_diff.py +210 -0
- code_atlas/impact/test_selector.py +166 -0
- code_atlas/indexing/__init__.py +5 -0
- code_atlas/indexing/cache.py +396 -0
- code_atlas/indexing/change_detector.py +278 -0
- code_atlas/indexing/coordinator.py +1032 -0
- code_atlas/indexing/dependency_planner.py +268 -0
- code_atlas/indexing/persist.py +436 -0
- code_atlas/indexing/reuse.py +85 -0
- code_atlas/install/__init__.py +7 -0
- code_atlas/install/cli.py +168 -0
- code_atlas/install/lifecycle.py +286 -0
- code_atlas/mcp.py +674 -0
- code_atlas/observability/__init__.py +19 -0
- code_atlas/observability/logging.py +199 -0
- code_atlas/parsers/__init__.py +43 -0
- code_atlas/parsers/contracts.py +209 -0
- code_atlas/parsers/engine.py +125 -0
- code_atlas/parsers/languages/__init__.py +1 -0
- code_atlas/parsers/languages/python.py +391 -0
- code_atlas/parsers/languages/tsx.py +28 -0
- code_atlas/parsers/languages/typescript.py +909 -0
- code_atlas/parsers/registry.py +51 -0
- code_atlas/parsers/tree_sitter_runtime.py +118 -0
- code_atlas/resolvers/__init__.py +33 -0
- code_atlas/resolvers/bootstrap.py +246 -0
- code_atlas/resolvers/capabilities.py +33 -0
- code_atlas/resolvers/contracts.py +133 -0
- code_atlas/resolvers/coordinator.py +96 -0
- code_atlas/resolvers/enrichment.py +318 -0
- code_atlas/resolvers/lsp/__init__.py +26 -0
- code_atlas/resolvers/lsp/adapter.py +256 -0
- code_atlas/resolvers/lsp/lifecycle.py +251 -0
- code_atlas/resolvers/lsp/spawn.py +92 -0
- code_atlas/resolvers/lsp/transport.py +240 -0
- code_atlas/resolvers/registry.py +33 -0
- code_atlas/resolvers/scip/__init__.py +49 -0
- code_atlas/resolvers/scip/decoder.py +216 -0
- code_atlas/resolvers/scip/importer.py +248 -0
- code_atlas/resolvers/scip/loader.py +90 -0
- code_atlas/resolvers/scip/mapper.py +271 -0
- code_atlas/resolvers/scip/provenance.py +211 -0
- code_atlas/resolvers/scip/resolver.py +289 -0
- code_atlas/resolvers/scip/validation.py +101 -0
- code_atlas/resolvers/syntactic/__init__.py +8 -0
- code_atlas/resolvers/syntactic/resolver.py +254 -0
- code_atlas/search/__init__.py +32 -0
- code_atlas/search/contracts.py +176 -0
- code_atlas/search/domain.py +561 -0
- code_atlas/search/engine.py +530 -0
- code_atlas/search/federated.py +276 -0
- code_atlas/search/graph.py +282 -0
- code_atlas/search/hybrid.py +151 -0
- code_atlas/search/intent.py +48 -0
- code_atlas/search/lexical.py +176 -0
- code_atlas/search/refs.py +359 -0
- code_atlas/search/structural.py +505 -0
- code_atlas/search/tokenizer.py +43 -0
- code_atlas/search/vector/__init__.py +35 -0
- code_atlas/search/vector/contracts.py +76 -0
- code_atlas/search/vector/embeddings.py +96 -0
- code_atlas/search/vector/index.py +169 -0
- code_atlas/search/vector/provider.py +50 -0
- code_atlas/storage/__init__.py +14 -0
- code_atlas/storage/db.py +47 -0
- code_atlas/storage/migrations.py +552 -0
- code_atlas/storage/store.py +1045 -0
- code_atlas/ui/__init__.py +29 -0
- code_atlas/ui/i18n.py +143 -0
- code_atlas/ui/kit.py +689 -0
- code_atlas/ui/wizard.py +317 -0
- code_atlas_forge-1.1.0.dist-info/METADATA +246 -0
- code_atlas_forge-1.1.0.dist-info/RECORD +181 -0
- code_atlas_forge-1.1.0.dist-info/WHEEL +5 -0
- code_atlas_forge-1.1.0.dist-info/entry_points.txt +2 -0
- code_atlas_forge-1.1.0.dist-info/licenses/LICENSE +202 -0
- code_atlas_forge-1.1.0.dist-info/licenses/NOTICE +17 -0
- code_atlas_forge-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
"""Runner de migrações versionadas (Blueprint §6).
|
|
2
|
+
|
|
3
|
+
Cada migração é ``(version, name, sql)``; aplicação é sequencial,
|
|
4
|
+
transacional e idempotente (versões aplicadas são puladas). O checksum do
|
|
5
|
+
SQL aplicado é gravado em ``schema_migrations`` e validado em execuções
|
|
6
|
+
posteriores — drift levanta ``MigrationError``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import sqlite3
|
|
13
|
+
from collections.abc import Iterator
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MigrationError(Exception):
|
|
18
|
+
"""Migração inválida ou drift de schema detectado."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class Migration:
|
|
23
|
+
version: int
|
|
24
|
+
name: str
|
|
25
|
+
sql: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_SCHEMA_V1 = """
|
|
29
|
+
CREATE TABLE repositories (
|
|
30
|
+
id INTEGER PRIMARY KEY,
|
|
31
|
+
origin TEXT NOT NULL DEFAULT '',
|
|
32
|
+
root_handle TEXT NOT NULL,
|
|
33
|
+
git_provider TEXT NOT NULL DEFAULT '',
|
|
34
|
+
default_branch TEXT NOT NULL DEFAULT '',
|
|
35
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE snapshots (
|
|
39
|
+
id INTEGER PRIMARY KEY,
|
|
40
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
41
|
+
git_ref TEXT NOT NULL DEFAULT '',
|
|
42
|
+
commit_sha TEXT,
|
|
43
|
+
worktree_digest TEXT NOT NULL,
|
|
44
|
+
analyzer_versions TEXT NOT NULL DEFAULT '{}',
|
|
45
|
+
parent_snapshot_id INTEGER REFERENCES snapshots(id),
|
|
46
|
+
status TEXT NOT NULL CHECK (status IN ('BUILDING','READY','FAILED','STALE')),
|
|
47
|
+
error TEXT,
|
|
48
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE TABLE source_files (
|
|
52
|
+
id INTEGER PRIMARY KEY,
|
|
53
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
54
|
+
relative_path TEXT NOT NULL,
|
|
55
|
+
language TEXT NOT NULL,
|
|
56
|
+
sha256 TEXT,
|
|
57
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
58
|
+
ignored INTEGER NOT NULL DEFAULT 0,
|
|
59
|
+
UNIQUE (repository_id, relative_path)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
CREATE TABLE file_inventory (
|
|
63
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
64
|
+
source_file_id INTEGER NOT NULL REFERENCES source_files(id),
|
|
65
|
+
sha256 TEXT,
|
|
66
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
67
|
+
ignored INTEGER NOT NULL DEFAULT 0,
|
|
68
|
+
PRIMARY KEY (snapshot_id, source_file_id)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE symbols (
|
|
72
|
+
id INTEGER PRIMARY KEY,
|
|
73
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
74
|
+
language TEXT NOT NULL,
|
|
75
|
+
qualified_name TEXT NOT NULL,
|
|
76
|
+
kind TEXT NOT NULL,
|
|
77
|
+
UNIQUE (repository_id, language, qualified_name, kind)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE symbol_occurrences (
|
|
81
|
+
id INTEGER PRIMARY KEY,
|
|
82
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
83
|
+
logical_symbol_id INTEGER NOT NULL REFERENCES symbols(id),
|
|
84
|
+
source_file_id INTEGER NOT NULL REFERENCES source_files(id),
|
|
85
|
+
span_start INTEGER,
|
|
86
|
+
span_end INTEGER,
|
|
87
|
+
line INTEGER,
|
|
88
|
+
col INTEGER,
|
|
89
|
+
content_hash TEXT,
|
|
90
|
+
parse_status TEXT NOT NULL DEFAULT 'parsed'
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE TABLE relations (
|
|
94
|
+
id INTEGER PRIMARY KEY,
|
|
95
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
96
|
+
from_id INTEGER NOT NULL,
|
|
97
|
+
to_id INTEGER,
|
|
98
|
+
to_unresolved TEXT,
|
|
99
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
100
|
+
'CONTAINS','DEFINES','IMPORTS','CALLS','IMPLEMENTS',
|
|
101
|
+
'EXTENDS','REFERENCES','CONFIGURES','TESTS')),
|
|
102
|
+
resolution_status TEXT NOT NULL CHECK (resolution_status IN (
|
|
103
|
+
'resolved','ambiguous','unresolved','external')),
|
|
104
|
+
confidence_class TEXT NOT NULL CHECK (confidence_class IN (
|
|
105
|
+
'resolved','syntactic','heuristic','inferred')),
|
|
106
|
+
resolver TEXT NOT NULL,
|
|
107
|
+
evidence_ids TEXT NOT NULL DEFAULT '[]'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE TABLE evidence (
|
|
111
|
+
id INTEGER PRIMARY KEY,
|
|
112
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
113
|
+
source_file_id INTEGER NOT NULL REFERENCES source_files(id),
|
|
114
|
+
path TEXT NOT NULL,
|
|
115
|
+
span TEXT,
|
|
116
|
+
content_hash TEXT NOT NULL,
|
|
117
|
+
extractor TEXT NOT NULL,
|
|
118
|
+
extractor_version TEXT NOT NULL,
|
|
119
|
+
notes TEXT NOT NULL DEFAULT ''
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
CREATE TABLE index_jobs (
|
|
123
|
+
id INTEGER PRIMARY KEY,
|
|
124
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
125
|
+
snapshot_id INTEGER REFERENCES snapshots(id),
|
|
126
|
+
status TEXT NOT NULL CHECK (status IN ('running','success','failed','noop')),
|
|
127
|
+
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
128
|
+
finished_at TEXT,
|
|
129
|
+
counts TEXT NOT NULL DEFAULT '{}',
|
|
130
|
+
failures TEXT NOT NULL DEFAULT '[]',
|
|
131
|
+
manifest_hash TEXT
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
CREATE INDEX idx_snapshots_repo_status ON snapshots (repository_id, status);
|
|
135
|
+
CREATE INDEX idx_file_inventory_snapshot ON file_inventory (snapshot_id);
|
|
136
|
+
CREATE INDEX idx_source_files_repo_path ON source_files (repository_id, relative_path);
|
|
137
|
+
CREATE INDEX idx_occurrences_snap_file ON symbol_occurrences (snapshot_id, source_file_id);
|
|
138
|
+
CREATE INDEX idx_relations_from ON relations (from_id, type);
|
|
139
|
+
CREATE INDEX idx_relations_to ON relations (to_id, type);
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
_SCHEMA_V2_FTS = """
|
|
143
|
+
CREATE VIRTUAL TABLE fts_symbols USING fts5(
|
|
144
|
+
qualified_name, language, kind, content=''
|
|
145
|
+
);
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
_SCHEMA_V3_INDEXES = """
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_relations_snapshot ON relations (snapshot_id, type);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_occurrences_snapshot_symbol
|
|
151
|
+
ON symbol_occurrences (snapshot_id, logical_symbol_id);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_snapshot ON evidence (snapshot_id);
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
_SCHEMA_V4_CACHE = """
|
|
156
|
+
CREATE TABLE parse_result_cache (
|
|
157
|
+
id INTEGER PRIMARY KEY,
|
|
158
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
159
|
+
path TEXT NOT NULL,
|
|
160
|
+
content_sha256 TEXT NOT NULL,
|
|
161
|
+
language TEXT NOT NULL,
|
|
162
|
+
parser_version TEXT NOT NULL,
|
|
163
|
+
grammar_version TEXT NOT NULL,
|
|
164
|
+
extractor_version TEXT NOT NULL,
|
|
165
|
+
resolver_version TEXT NOT NULL,
|
|
166
|
+
builder_version TEXT NOT NULL,
|
|
167
|
+
config_digest TEXT NOT NULL,
|
|
168
|
+
payload TEXT NOT NULL,
|
|
169
|
+
payload_sha256 TEXT NOT NULL,
|
|
170
|
+
last_used_snapshot_id INTEGER,
|
|
171
|
+
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
172
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
173
|
+
UNIQUE (repository_id, path, content_sha256, language,
|
|
174
|
+
parser_version, grammar_version, extractor_version,
|
|
175
|
+
resolver_version, builder_version, config_digest)
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX idx_cache_repo_path ON parse_result_cache (repository_id, path);
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
_SCHEMA_V5_IDENTITY = """
|
|
181
|
+
ALTER TABLE repositories ADD COLUMN identity_key TEXT NOT NULL DEFAULT '';
|
|
182
|
+
UPDATE repositories SET identity_key = 'path:' || root_handle
|
|
183
|
+
WHERE identity_key = '';
|
|
184
|
+
CREATE UNIQUE INDEX uq_repositories_identity ON repositories (identity_key);
|
|
185
|
+
ALTER TABLE snapshots ADD COLUMN dirty INTEGER NOT NULL DEFAULT 0;
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
_SCHEMA_V6_EVIDENCE = """
|
|
189
|
+
ALTER TABLE evidence ADD COLUMN resolver_id TEXT NOT NULL DEFAULT '';
|
|
190
|
+
ALTER TABLE evidence ADD COLUMN resolver_version TEXT NOT NULL DEFAULT '';
|
|
191
|
+
ALTER TABLE evidence ADD COLUMN evidence_tier INTEGER NOT NULL DEFAULT 0;
|
|
192
|
+
ALTER TABLE evidence ADD COLUMN index_hash TEXT NOT NULL DEFAULT '';
|
|
193
|
+
ALTER TABLE evidence ADD COLUMN validation_status TEXT NOT NULL DEFAULT '';
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
_SCHEMA_V7_FTS_OCC = """
|
|
197
|
+
CREATE VIRTUAL TABLE fts_occurrences USING fts5(
|
|
198
|
+
snapshot_id UNINDEXED,
|
|
199
|
+
name,
|
|
200
|
+
terms,
|
|
201
|
+
signature,
|
|
202
|
+
docstring,
|
|
203
|
+
path,
|
|
204
|
+
language UNINDEXED,
|
|
205
|
+
kind UNINDEXED
|
|
206
|
+
);
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
_SCHEMA_V8_VECTORS = """
|
|
210
|
+
CREATE TABLE vector_embeddings (
|
|
211
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
212
|
+
occurrence_id INTEGER NOT NULL REFERENCES symbol_occurrences(id) ON DELETE CASCADE,
|
|
213
|
+
symbol_key TEXT NOT NULL,
|
|
214
|
+
model_id TEXT NOT NULL,
|
|
215
|
+
model_version TEXT NOT NULL,
|
|
216
|
+
dim INTEGER NOT NULL,
|
|
217
|
+
content_hash TEXT NOT NULL,
|
|
218
|
+
vector BLOB NOT NULL,
|
|
219
|
+
PRIMARY KEY (snapshot_id, occurrence_id, model_id)
|
|
220
|
+
);
|
|
221
|
+
CREATE INDEX idx_vector_embeddings_snap ON vector_embeddings(snapshot_id, model_id);
|
|
222
|
+
CREATE TABLE embedding_cache (
|
|
223
|
+
model_id TEXT NOT NULL,
|
|
224
|
+
model_version TEXT NOT NULL,
|
|
225
|
+
content_hash TEXT NOT NULL,
|
|
226
|
+
dim INTEGER NOT NULL,
|
|
227
|
+
vector BLOB NOT NULL,
|
|
228
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
229
|
+
PRIMARY KEY (model_id, model_version, content_hash)
|
|
230
|
+
);
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
_SCHEMA_V9_DOMAINS = """
|
|
235
|
+
-- Federação de domínios (lf-0110 / ADR-0010): tabelas dedicadas porque
|
|
236
|
+
-- (a) relations.type tem CHECK que não aceita o vocabulário de domínio e
|
|
237
|
+
-- SQLite não altera CHECK sem rebuild; (b) a identidade de entidades de
|
|
238
|
+
-- domínio inclui catalog/namespace/environment, inexpressável no UNIQUE
|
|
239
|
+
-- de symbols (repo, language, qualified_name, kind). Aditiva: bancos
|
|
240
|
+
-- antigos abrem e ganham as três tabelas vazias.
|
|
241
|
+
|
|
242
|
+
CREATE TABLE domain_entities (
|
|
243
|
+
id INTEGER PRIMARY KEY,
|
|
244
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
245
|
+
domain TEXT NOT NULL,
|
|
246
|
+
kind TEXT NOT NULL,
|
|
247
|
+
identity_key TEXT NOT NULL,
|
|
248
|
+
qualified_name TEXT NOT NULL,
|
|
249
|
+
name TEXT NOT NULL DEFAULT '',
|
|
250
|
+
catalog TEXT NOT NULL DEFAULT '',
|
|
251
|
+
namespace TEXT NOT NULL DEFAULT '',
|
|
252
|
+
environment TEXT NOT NULL DEFAULT '',
|
|
253
|
+
attributes TEXT NOT NULL DEFAULT '{}',
|
|
254
|
+
UNIQUE (repository_id, identity_key)
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
CREATE TABLE domain_entity_occurrences (
|
|
258
|
+
id INTEGER PRIMARY KEY,
|
|
259
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
260
|
+
entity_id INTEGER NOT NULL REFERENCES domain_entities(id),
|
|
261
|
+
source_file_id INTEGER REFERENCES source_files(id),
|
|
262
|
+
span_start INTEGER,
|
|
263
|
+
span_end INTEGER,
|
|
264
|
+
line INTEGER,
|
|
265
|
+
col INTEGER,
|
|
266
|
+
content_hash TEXT,
|
|
267
|
+
attributes TEXT NOT NULL DEFAULT '{}',
|
|
268
|
+
extraction_status TEXT NOT NULL DEFAULT 'extracted'
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
CREATE TABLE domain_relations (
|
|
272
|
+
id INTEGER PRIMARY KEY,
|
|
273
|
+
snapshot_id INTEGER NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE,
|
|
274
|
+
from_entity_occ_id INTEGER REFERENCES domain_entity_occurrences(id),
|
|
275
|
+
from_symbol_occ_id INTEGER REFERENCES symbol_occurrences(id),
|
|
276
|
+
to_entity_occ_id INTEGER REFERENCES domain_entity_occurrences(id),
|
|
277
|
+
to_symbol_occ_id INTEGER REFERENCES symbol_occurrences(id),
|
|
278
|
+
to_unresolved TEXT,
|
|
279
|
+
type TEXT NOT NULL CHECK (type IN (
|
|
280
|
+
'CONTAINS','DEFINES','IMPORTS','CALLS','IMPLEMENTS',
|
|
281
|
+
'EXTENDS','REFERENCES','CONFIGURES','TESTS',
|
|
282
|
+
'READS_FROM','WRITES_TO','TRANSFORMS','DERIVES_FROM',
|
|
283
|
+
'DEPENDS_ON','CALLS_ENDPOINT','IMPLEMENTS_CONTRACT',
|
|
284
|
+
'PROVISIONS','USES_RESOURCE','ASSUMES_ROLE','TRIGGERS','DEPLOYS')),
|
|
285
|
+
resolution_status TEXT NOT NULL CHECK (resolution_status IN (
|
|
286
|
+
'resolved','ambiguous','unresolved','external')),
|
|
287
|
+
confidence_class TEXT NOT NULL CHECK (confidence_class IN (
|
|
288
|
+
'resolved','syntactic','heuristic','inferred')),
|
|
289
|
+
resolver TEXT NOT NULL,
|
|
290
|
+
evidence_ids TEXT NOT NULL DEFAULT '[]',
|
|
291
|
+
CHECK (from_entity_occ_id IS NOT NULL OR from_symbol_occ_id IS NOT NULL),
|
|
292
|
+
CHECK (to_entity_occ_id IS NOT NULL OR to_symbol_occ_id IS NOT NULL
|
|
293
|
+
OR to_unresolved IS NOT NULL)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
CREATE INDEX idx_domain_entities_repo
|
|
297
|
+
ON domain_entities (repository_id, domain, kind);
|
|
298
|
+
CREATE INDEX idx_domain_occ_snapshot
|
|
299
|
+
ON domain_entity_occurrences (snapshot_id, entity_id);
|
|
300
|
+
CREATE INDEX idx_domain_occ_entity
|
|
301
|
+
ON domain_entity_occurrences (entity_id);
|
|
302
|
+
CREATE INDEX idx_domain_relations_from
|
|
303
|
+
ON domain_relations (from_entity_occ_id, type);
|
|
304
|
+
CREATE INDEX idx_domain_relations_to
|
|
305
|
+
ON domain_relations (to_entity_occ_id, type);
|
|
306
|
+
CREATE INDEX idx_domain_relations_snap
|
|
307
|
+
ON domain_relations (snapshot_id, type);
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
_SCHEMA_V10_FEDERATION = """
|
|
312
|
+
-- Workspaces federados (lf-0118 / Cycle 7 Phase 10): agrupa >=2
|
|
313
|
+
-- repositórios já indexados numa "view" multi-repo. Estritamente
|
|
314
|
+
-- aditiva — bancos antigos abrem e ganham as duas tabelas vazias.
|
|
315
|
+
--
|
|
316
|
+
-- ``federation_members.pinned_snapshot_id`` é NULLABLE e carrega
|
|
317
|
+
-- ``ON DELETE SET NULL``: quando o GC de retenção remove um snapshot
|
|
318
|
+
-- STALE que estava pinado, o pin é limpo e o membro volta a seguir o
|
|
319
|
+
-- READY mais recente — nunca FK dangling nem ponteiro para dados
|
|
320
|
+
-- removidos. ``repository_id`` não tem cascade: repositórios não são
|
|
321
|
+
-- removidos por nenhum caminho hoje (só snapshots viram GC).
|
|
322
|
+
|
|
323
|
+
CREATE TABLE federation_workspaces (
|
|
324
|
+
id INTEGER PRIMARY KEY,
|
|
325
|
+
name TEXT NOT NULL UNIQUE,
|
|
326
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
CREATE TABLE federation_members (
|
|
330
|
+
id INTEGER PRIMARY KEY,
|
|
331
|
+
workspace_id INTEGER NOT NULL
|
|
332
|
+
REFERENCES federation_workspaces(id) ON DELETE CASCADE,
|
|
333
|
+
repository_id INTEGER NOT NULL REFERENCES repositories(id),
|
|
334
|
+
pinned_snapshot_id INTEGER REFERENCES snapshots(id) ON DELETE SET NULL,
|
|
335
|
+
added_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
|
|
336
|
+
UNIQUE (workspace_id, repository_id)
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
CREATE INDEX idx_federation_members_ws
|
|
340
|
+
ON federation_members (workspace_id);
|
|
341
|
+
CREATE INDEX idx_federation_members_repo
|
|
342
|
+
ON federation_members (repository_id);
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
_SCHEMA_V11_DOMAIN_REL_INDEXES = """
|
|
347
|
+
-- lf-0132 (Cycle 8 Phase 6): índices compostos nos quatro endpoints de
|
|
348
|
+
-- ``domain_relations``. O ego (``from_/to_entity_occ_id IN (...)``) e o
|
|
349
|
+
-- BFS de linhagem filtram por endpoint dentro do snapshot; os índices
|
|
350
|
+
-- single-column de v9 lideram pela coluna de endpoint e não cobrem o
|
|
351
|
+
-- predicado ``snapshot_id = ?``, então o planner preferia o range-scan
|
|
352
|
+
-- de ``idx_domain_relations_snap`` (~O(snapshot)) mesmo para INs
|
|
353
|
+
-- pequenos. ``(snapshot_id, endpoint)`` dá probes diretos nos INs de um
|
|
354
|
+
-- termo e habilita MULTI-INDEX OR nas egos **quando há estatísticas**.
|
|
355
|
+
--
|
|
356
|
+
-- ``ANALYZE`` escopo-domínio popula ``sqlite_stat1`` em bancos já
|
|
357
|
+
-- povoados — sem stats o planner mantém o scan de snapshot até para os
|
|
358
|
+
-- INs literais do ego e os probes PK de ``deo.id IN (json_each)`` na
|
|
359
|
+
-- hidratação de endpoints. ANALYZE só escreve a tabela de sistema
|
|
360
|
+
-- ``sqlite_stat1``: nenhuma tabela de aplicação muda de forma. O
|
|
361
|
+
-- refresh contínuo fica em ``Store.publish_snapshot`` (mesma tx da
|
|
362
|
+
-- publicação — stats commitam com os dados que descrevem).
|
|
363
|
+
--
|
|
364
|
+
-- Estritamente aditiva: bancos antigos abrem e ganham os quatro
|
|
365
|
+
-- índices + stats; nada é dropado (os índices single-column de v9
|
|
366
|
+
-- permanecem — ``(endpoint, type)`` cobre formas diferentes).
|
|
367
|
+
|
|
368
|
+
CREATE INDEX IF NOT EXISTS idx_domain_relations_snap_from_ent
|
|
369
|
+
ON domain_relations (snapshot_id, from_entity_occ_id);
|
|
370
|
+
CREATE INDEX IF NOT EXISTS idx_domain_relations_snap_to_ent
|
|
371
|
+
ON domain_relations (snapshot_id, to_entity_occ_id);
|
|
372
|
+
CREATE INDEX IF NOT EXISTS idx_domain_relations_snap_from_sym
|
|
373
|
+
ON domain_relations (snapshot_id, from_symbol_occ_id);
|
|
374
|
+
CREATE INDEX IF NOT EXISTS idx_domain_relations_snap_to_sym
|
|
375
|
+
ON domain_relations (snapshot_id, to_symbol_occ_id);
|
|
376
|
+
|
|
377
|
+
ANALYZE domain_relations;
|
|
378
|
+
ANALYZE domain_entity_occurrences;
|
|
379
|
+
ANALYZE domain_entities;
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
_SCHEMA_V12_DOMAIN_FTS = """
|
|
384
|
+
-- lf-0133 (Cycle 8 Phase 7): FTS5 dedicado a entidades de domínio.
|
|
385
|
+
--
|
|
386
|
+
-- Standalone, mesmo desenho de ``fts_occurrences`` (v7): rowid = id da
|
|
387
|
+
-- ``domain_entity_occurrences`` — join direto para âncora snapshot/
|
|
388
|
+
-- entidade. ``terms`` carrega identificadores pré-tokenizados
|
|
389
|
+
-- (``search/tokenizer.index_terms``) — snake_case/camelCase/dotted
|
|
390
|
+
-- recuperáveis por fragmento, enquanto ``name``/``qualified_name``
|
|
391
|
+
-- guardam a forma exata. ``snapshot_id``/``entity_id``/``domain``/
|
|
392
|
+
-- ``kind`` são UNINDEXED: filtros de escopo, nunca alvos de MATCH.
|
|
393
|
+
--
|
|
394
|
+
-- ``content=`` externo não serve: as colunas de match moram em
|
|
395
|
+
-- ``domain_entities``/``source_files``, e cascades de FK não disparam
|
|
396
|
+
-- triggers em SQLite — a limpeza é manual em
|
|
397
|
+
-- ``Store.gc_stale_snapshots`` (mesmo mecanismo de fts_occurrences).
|
|
398
|
+
--
|
|
399
|
+
-- Migração separada da v11 por duas razões: (a) FTS5 é opcional de
|
|
400
|
+
-- build — um gate na v11 também removeria os índices dos builds sem
|
|
401
|
+
-- módulo; (b) o checksum por-versão exige SQL determinístico — variantes
|
|
402
|
+
-- condicionais na mesma versão gerariam drift entre builds. Bancos sem
|
|
403
|
+
-- FTS5 ficam na v11 com índices e LIKE bounded intactos.
|
|
404
|
+
--
|
|
405
|
+
-- Populada em ``persist_domain_result``: somente snapshots **novos**
|
|
406
|
+
-- ganham rows — snapshots pré-v12 seguem no LIKE honesto do
|
|
407
|
+
-- ``DomainEntitySearch`` (fallback declarado). Backfill in-place por
|
|
408
|
+
-- snapshot existe em ``Store.rebuild_domain_fts``; um ``code-atlas
|
|
409
|
+
-- reindex`` de superfície é o equivalente operacional (nova snapshot
|
|
410
|
+
-- já sai coberta pelo persist).
|
|
411
|
+
|
|
412
|
+
CREATE VIRTUAL TABLE domain_fts USING fts5(
|
|
413
|
+
snapshot_id UNINDEXED,
|
|
414
|
+
entity_id UNINDEXED,
|
|
415
|
+
name,
|
|
416
|
+
qualified_name,
|
|
417
|
+
terms,
|
|
418
|
+
path,
|
|
419
|
+
domain UNINDEXED,
|
|
420
|
+
kind UNINDEXED
|
|
421
|
+
);
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def migrations(*, fts5: bool) -> list[Migration]:
|
|
426
|
+
"""Lista de migrações; v2/v7/v12 (FTS5) só existem quando a build suporta."""
|
|
427
|
+
m = [Migration(1, "core schema (blueprint §6)", _SCHEMA_V1)]
|
|
428
|
+
if fts5:
|
|
429
|
+
m.append(Migration(2, "fts_symbols virtual table", _SCHEMA_V2_FTS))
|
|
430
|
+
m.append(Migration(3, "snapshot-scoped indexes", _SCHEMA_V3_INDEXES))
|
|
431
|
+
m.append(Migration(4, "parse result cache (lf-0020)", _SCHEMA_V4_CACHE))
|
|
432
|
+
m.append(Migration(5, "logical repo identity + dirty flag (lf-0021)", _SCHEMA_V5_IDENTITY))
|
|
433
|
+
m.append(Migration(6, "evidence resolver provenance (lf-0035)", _SCHEMA_V6_EVIDENCE))
|
|
434
|
+
if fts5:
|
|
435
|
+
m.append(Migration(
|
|
436
|
+
7, "fts_occurrences snapshot-scoped lexical index (lf-0036)",
|
|
437
|
+
_SCHEMA_V7_FTS_OCC,
|
|
438
|
+
))
|
|
439
|
+
m.append(Migration(
|
|
440
|
+
8, "vector embeddings + content cache (lf-0039)",
|
|
441
|
+
_SCHEMA_V8_VECTORS,
|
|
442
|
+
))
|
|
443
|
+
m.append(Migration(
|
|
444
|
+
9, "domain entities + relations (lf-0110)",
|
|
445
|
+
_SCHEMA_V9_DOMAINS,
|
|
446
|
+
))
|
|
447
|
+
m.append(Migration(
|
|
448
|
+
10, "federation workspaces + members (lf-0118)",
|
|
449
|
+
_SCHEMA_V10_FEDERATION,
|
|
450
|
+
))
|
|
451
|
+
m.append(Migration(
|
|
452
|
+
11, "domain_relations endpoint indexes + stats (lf-0132)",
|
|
453
|
+
_SCHEMA_V11_DOMAIN_REL_INDEXES,
|
|
454
|
+
))
|
|
455
|
+
if fts5:
|
|
456
|
+
m.append(Migration(
|
|
457
|
+
12, "domain_fts dedicated FTS5 (lf-0133)",
|
|
458
|
+
_SCHEMA_V12_DOMAIN_FTS,
|
|
459
|
+
))
|
|
460
|
+
return m
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _checksum(sql: str) -> str:
|
|
464
|
+
return hashlib.sha256(sql.encode("utf-8")).hexdigest()
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _split_statements(sql: str) -> Iterator[str]:
|
|
468
|
+
"""Divide o script em statements completos via sqlite3.complete_statement.
|
|
469
|
+
|
|
470
|
+
Necessário porque ``executescript()`` faz COMMIT implícito antes de
|
|
471
|
+
rodar — sairia da transação do ``with conn:`` e deixaria DDL parcial
|
|
472
|
+
aplicada sem registro em ``schema_migrations`` (Phase 0 / P1.3).
|
|
473
|
+
"""
|
|
474
|
+
buf: list[str] = []
|
|
475
|
+
for line in sql.splitlines(keepends=True):
|
|
476
|
+
buf.append(line)
|
|
477
|
+
chunk = "".join(buf)
|
|
478
|
+
if sqlite3.complete_statement(chunk):
|
|
479
|
+
stmt = chunk.strip()
|
|
480
|
+
if stmt:
|
|
481
|
+
yield stmt
|
|
482
|
+
buf.clear()
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def migrate(conn: sqlite3.Connection, *, fts5: bool | None = None) -> int:
|
|
486
|
+
"""Aplica migrações pendentes; retorna o schema_version resultante."""
|
|
487
|
+
if fts5 is None:
|
|
488
|
+
from code_atlas.storage.db import fts5_available
|
|
489
|
+
|
|
490
|
+
fts5 = fts5_available(conn)
|
|
491
|
+
conn.execute(
|
|
492
|
+
"""
|
|
493
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
494
|
+
version INTEGER PRIMARY KEY,
|
|
495
|
+
name TEXT NOT NULL,
|
|
496
|
+
checksum TEXT NOT NULL,
|
|
497
|
+
applied_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
498
|
+
)
|
|
499
|
+
"""
|
|
500
|
+
)
|
|
501
|
+
applied = {
|
|
502
|
+
row["version"]: row["checksum"]
|
|
503
|
+
for row in conn.execute("SELECT version, checksum FROM schema_migrations")
|
|
504
|
+
}
|
|
505
|
+
# lf-0155 fail-safe: ``schema_migrations`` com versão fora do
|
|
506
|
+
# catálogo completo = banco de build mais nova — migrar seria
|
|
507
|
+
# silencioso e inseguro; recusa honesta, nunca segue adiante.
|
|
508
|
+
# O catálogo fts5=True é o superset (v2/v7/v12 existem sempre) —
|
|
509
|
+
# um banco v12 num build sem FTS5 é caminho projetado (fallback
|
|
510
|
+
# LIKE), não versão desconhecida.
|
|
511
|
+
catalog = migrations(fts5=True)
|
|
512
|
+
by_version = {m.version: m for m in catalog}
|
|
513
|
+
unknown = sorted(v for v in applied if v not in by_version)
|
|
514
|
+
if unknown:
|
|
515
|
+
raise MigrationError(
|
|
516
|
+
f"schema_migrations has unknown versions {unknown} — "
|
|
517
|
+
"database was written by a newer build; upgrade code-atlas")
|
|
518
|
+
applicable = {m.version for m in migrations(fts5=fts5)}
|
|
519
|
+
for mig in catalog:
|
|
520
|
+
digest = _checksum(mig.sql)
|
|
521
|
+
if mig.version in applied:
|
|
522
|
+
if applied[mig.version] != digest:
|
|
523
|
+
raise MigrationError(
|
|
524
|
+
f"migration v{mig.version} checksum drift "
|
|
525
|
+
f"(applied {applied[mig.version][:12]} != current {digest[:12]})"
|
|
526
|
+
)
|
|
527
|
+
continue
|
|
528
|
+
if mig.version not in applicable:
|
|
529
|
+
continue # migração FTS5-gated — não aplicável nesta build
|
|
530
|
+
try:
|
|
531
|
+
with conn:
|
|
532
|
+
# sqlite3 legacy só inicia transação implícita em DML —
|
|
533
|
+
# BEGIN explícito garante que DDL também seja atômica.
|
|
534
|
+
conn.execute("BEGIN")
|
|
535
|
+
for stmt in _split_statements(mig.sql):
|
|
536
|
+
conn.execute(stmt)
|
|
537
|
+
conn.execute(
|
|
538
|
+
"INSERT INTO schema_migrations (version, name, checksum) VALUES (?, ?, ?)",
|
|
539
|
+
(mig.version, mig.name, digest),
|
|
540
|
+
)
|
|
541
|
+
except sqlite3.Error as exc:
|
|
542
|
+
raise MigrationError(f"migration v{mig.version} failed: {exc}") from exc
|
|
543
|
+
return schema_version(conn)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def schema_version(conn: sqlite3.Connection) -> int:
|
|
547
|
+
"""Maior versão aplicada; 0 se nenhuma."""
|
|
548
|
+
try:
|
|
549
|
+
row = conn.execute("SELECT MAX(version) AS v FROM schema_migrations").fetchone()
|
|
550
|
+
except sqlite3.Error:
|
|
551
|
+
return 0
|
|
552
|
+
return int(row["v"] or 0)
|