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,448 @@
|
|
|
1
|
+
"""Ponte ``domains.sql`` → ``domains.aws`` (lf-0113, Cycle 7 Phase 5).
|
|
2
|
+
|
|
3
|
+
Reuso **como biblioteca** — nunca reemissão sob o domínio ``sql``:
|
|
4
|
+
|
|
5
|
+
- ``parse_file`` + ``extract_parsed`` produzem fatos de linhagem sobre
|
|
6
|
+
texto SQL (arquivo ``.sql`` Athena inteiro ou literal ``spark.sql``);
|
|
7
|
+
- cada identidade emitida é re-marcada para ``domain=aws-data``
|
|
8
|
+
preservando ``catalog``/``namespace``/``qualified_name`` — o
|
|
9
|
+
produtor do fato é o adapter aws (o contrato de validação exige
|
|
10
|
+
``identity.domain == adapter.domain_id``); a proveniência do parser
|
|
11
|
+
fica em ``evidence.notes`` (``via=domain:sql``);
|
|
12
|
+
- literais embutidos em ``.py`` carregam spans relativos ao literal —
|
|
13
|
+
``span_remap`` desloca para offsets reais do arquivo quando o corpo
|
|
14
|
+
do literal é contíguo no fonte; quando não é (escapes/concat),
|
|
15
|
+
todos os spans caem para o span do literal (honesto, nunca errado);
|
|
16
|
+
- ``USE <catalog>.<db>;`` sintético só é prefixado quando o contexto
|
|
17
|
+
foi **declarado no artefato** (ex.: ``QueryExecutionContext`` de
|
|
18
|
+
``start_query_execution``) — contexto inventado nunca entra;
|
|
19
|
+
- literais ``s3://`` dentro do SQL (``LOCATION``, ``OutputLocation``)
|
|
20
|
+
viram entidades ``data_table`` S3 + ``REFERENCES`` a partir da
|
|
21
|
+
tabela-alvo do statement (ou do ``data_transformation`` do
|
|
22
|
+
statement quando não há alvo nomeado).
|
|
23
|
+
|
|
24
|
+
Resolução cross-domain com o domínio ``sql`` é trabalho de lf-0117 —
|
|
25
|
+
aqui só se re-marca o que o parser provou no texto.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import re
|
|
31
|
+
from collections.abc import Callable
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
|
|
34
|
+
from code_atlas.contracts.enums import ConfidenceClass
|
|
35
|
+
from code_atlas.domains.aws.common import (
|
|
36
|
+
ICEBERG_META,
|
|
37
|
+
byte_span,
|
|
38
|
+
parse_s3_uri,
|
|
39
|
+
s3_uri,
|
|
40
|
+
)
|
|
41
|
+
from code_atlas.domains.aws.emit import ADAPTER_DOMAIN, RESOLVER_ID, Sink
|
|
42
|
+
from code_atlas.domains.contracts import ExtractedDomainEntity
|
|
43
|
+
from code_atlas.domains.identity import DomainIdentity
|
|
44
|
+
from code_atlas.domains.model import DomainEntityKind
|
|
45
|
+
from code_atlas.domains.resolution import DomainResolution
|
|
46
|
+
from code_atlas.domains.sql.extract import FileExtraction, extract_parsed
|
|
47
|
+
from code_atlas.domains.sql.nodes import SCreateTable, SInsert, Statement
|
|
48
|
+
from code_atlas.domains.sql.parser import parse_file
|
|
49
|
+
from code_atlas.parsers.contracts import SourceSpan
|
|
50
|
+
|
|
51
|
+
_S3_LIT_RE = re.compile(r"""['"](s3://[^'"]+)['"]""")
|
|
52
|
+
|
|
53
|
+
SpanRemap = Callable[[SourceSpan | None], SourceSpan | None]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class SqlFragment:
|
|
58
|
+
"""Resultado re-marcado de um fragmento SQL."""
|
|
59
|
+
|
|
60
|
+
transform_idents: tuple[DomainIdentity, ...]
|
|
61
|
+
"""``data_transformation`` emitidas (âncoras do fragmento)."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _map_ident(transform_tag: str, path: str, ident: DomainIdentity) -> DomainIdentity:
|
|
65
|
+
"""Re-marca identidade ``sql`` → ``aws-data`` (mesmos segmentos).
|
|
66
|
+
|
|
67
|
+
``transform_tag`` (ex.: ``L42``) desambigua literais embutidos no
|
|
68
|
+
mesmo arquivo: ``path#3`` vira ``path@L42#3``.
|
|
69
|
+
"""
|
|
70
|
+
qname = ident.qualified_name
|
|
71
|
+
if transform_tag and qname.startswith(f"{path}#"):
|
|
72
|
+
qname = f"{path}@{transform_tag}#" + qname[len(path) + 1 :]
|
|
73
|
+
return DomainIdentity(
|
|
74
|
+
domain=ADAPTER_DOMAIN,
|
|
75
|
+
repository=ident.repository,
|
|
76
|
+
qualified_name=qname,
|
|
77
|
+
root=ident.root,
|
|
78
|
+
catalog=ident.catalog,
|
|
79
|
+
namespace=ident.namespace,
|
|
80
|
+
environment=ident.environment,
|
|
81
|
+
snapshot=ident.snapshot,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _table_ident(
|
|
86
|
+
sink: Sink, parts: tuple[str, ...], ctx_catalog: str, ctx_schema: str
|
|
87
|
+
) -> DomainIdentity:
|
|
88
|
+
"""Mesma decomposição de ``_FileCtx.table_ident`` do adapter sql."""
|
|
89
|
+
if len(parts) >= 3:
|
|
90
|
+
catalog, namespace, name = (parts[0], ".".join(parts[1:-1]), parts[-1])
|
|
91
|
+
elif len(parts) == 2:
|
|
92
|
+
catalog, namespace, name = ctx_catalog, parts[0], parts[1]
|
|
93
|
+
else:
|
|
94
|
+
catalog, namespace, name = ctx_catalog, ctx_schema, parts[0]
|
|
95
|
+
return sink.ident(name, catalog=catalog, namespace=namespace)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _transform_ident(sink: Sink, path: str, stmt_i: int, transform_tag: str) -> DomainIdentity:
|
|
99
|
+
qname = f"{path}@{transform_tag}#{stmt_i}" if transform_tag else f"{path}#{stmt_i}"
|
|
100
|
+
return sink.ident(qname)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _char_to_byte(text: str, char_pos: int) -> int:
|
|
104
|
+
return len(text[:char_pos].encode("utf-8"))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _span_in_text(text: str, char_start: int, char_end: int) -> SourceSpan:
|
|
108
|
+
"""Span byte-accurate dentro do texto SQL (linha/col próprias)."""
|
|
109
|
+
starts = [0]
|
|
110
|
+
off = 0
|
|
111
|
+
for ch in text:
|
|
112
|
+
off += len(ch.encode("utf-8"))
|
|
113
|
+
if ch == "\n":
|
|
114
|
+
starts.append(off)
|
|
115
|
+
b0 = _char_to_byte(text, char_start)
|
|
116
|
+
b1 = _char_to_byte(text, char_end)
|
|
117
|
+
return byte_span(tuple(starts), b0, b1)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def s3_entity(
|
|
121
|
+
sink: Sink,
|
|
122
|
+
uri: str,
|
|
123
|
+
*,
|
|
124
|
+
path: str,
|
|
125
|
+
sha: str,
|
|
126
|
+
span: SourceSpan | None,
|
|
127
|
+
evi: int,
|
|
128
|
+
) -> DomainIdentity | None:
|
|
129
|
+
"""Entidade ``data_table`` para uma URI ``s3://`` literal estática.
|
|
130
|
+
|
|
131
|
+
``None`` quando ``uri`` é template/dinâmica ou malformada — o
|
|
132
|
+
caller decide entre diagnóstico e ``to_unresolved``.
|
|
133
|
+
"""
|
|
134
|
+
parsed = parse_s3_uri(uri)
|
|
135
|
+
if parsed is None:
|
|
136
|
+
return None
|
|
137
|
+
bucket, key = parsed
|
|
138
|
+
uri_norm = s3_uri(bucket, key)
|
|
139
|
+
return sink.entity(
|
|
140
|
+
kind=DomainEntityKind.DATA_TABLE,
|
|
141
|
+
name=uri_norm,
|
|
142
|
+
qualified_name=uri_norm,
|
|
143
|
+
path=path,
|
|
144
|
+
span=span,
|
|
145
|
+
content_hash=sha,
|
|
146
|
+
attributes={
|
|
147
|
+
"storage": "s3-path",
|
|
148
|
+
"uri": uri_norm,
|
|
149
|
+
"bucket": bucket,
|
|
150
|
+
},
|
|
151
|
+
evidence=(evi,),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _emit_s3_literals(
|
|
156
|
+
sink: Sink,
|
|
157
|
+
*,
|
|
158
|
+
sql_text: str,
|
|
159
|
+
statements: list[Statement],
|
|
160
|
+
path: str,
|
|
161
|
+
sha: str,
|
|
162
|
+
transform_tag: str,
|
|
163
|
+
span_remap: SpanRemap,
|
|
164
|
+
ctx_catalog: str,
|
|
165
|
+
ctx_schema: str,
|
|
166
|
+
fallback_anchor: DomainIdentity | None,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Literais ``s3://`` no SQL → entidade + ``REFERENCES`` da âncora.
|
|
169
|
+
|
|
170
|
+
Âncora = tabela-alvo do statement que contém o literal quando ela
|
|
171
|
+
foi emitida; senão a transformação ``path#i``; senão
|
|
172
|
+
``fallback_anchor`` (ex.: job dono do literal embutido).
|
|
173
|
+
"""
|
|
174
|
+
for m in _S3_LIT_RE.finditer(sql_text):
|
|
175
|
+
uri = m.group(1)
|
|
176
|
+
bpos = _char_to_byte(sql_text, m.start())
|
|
177
|
+
anchor = fallback_anchor
|
|
178
|
+
for i, st in enumerate(statements):
|
|
179
|
+
sp = st.span
|
|
180
|
+
if sp is not None and sp.start_byte <= bpos <= sp.end_byte:
|
|
181
|
+
anchor = _stmt_anchor(
|
|
182
|
+
sink,
|
|
183
|
+
i,
|
|
184
|
+
st,
|
|
185
|
+
path,
|
|
186
|
+
transform_tag,
|
|
187
|
+
ctx_catalog,
|
|
188
|
+
ctx_schema,
|
|
189
|
+
fallback_anchor,
|
|
190
|
+
)
|
|
191
|
+
break
|
|
192
|
+
if anchor is None:
|
|
193
|
+
continue
|
|
194
|
+
fspan = span_remap(_span_in_text(sql_text, m.start(1), m.end(1)))
|
|
195
|
+
evi = sink.evi(path, sha, fspan, notes="s3-literal")
|
|
196
|
+
ident = s3_entity(sink, uri, path=path, sha=sha, span=fspan, evi=evi)
|
|
197
|
+
if ident is None:
|
|
198
|
+
sink.diag(
|
|
199
|
+
"dynamic-s3-uri",
|
|
200
|
+
"warning",
|
|
201
|
+
"s3 URI is a template/dynamic — never an entity",
|
|
202
|
+
path,
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
sink.rel(
|
|
206
|
+
"REFERENCES",
|
|
207
|
+
path,
|
|
208
|
+
anchor,
|
|
209
|
+
to_identity=ident,
|
|
210
|
+
span=fspan,
|
|
211
|
+
evidence=(evi,),
|
|
212
|
+
)
|
|
213
|
+
sink.bump("s3_paths")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _stmt_anchor(
|
|
217
|
+
sink: Sink,
|
|
218
|
+
stmt_i: int,
|
|
219
|
+
st: Statement,
|
|
220
|
+
path: str,
|
|
221
|
+
transform_tag: str,
|
|
222
|
+
ctx_catalog: str,
|
|
223
|
+
ctx_schema: str,
|
|
224
|
+
fallback: DomainIdentity | None,
|
|
225
|
+
) -> DomainIdentity | None:
|
|
226
|
+
"""Tabela declarada/alvo do statement, senão a transformação ``#i``."""
|
|
227
|
+
target_parts: tuple[str, ...] | None = None
|
|
228
|
+
if isinstance(st, SCreateTable) and st.name is not None:
|
|
229
|
+
target_parts = st.name.parts
|
|
230
|
+
elif isinstance(st, SInsert) and st.target is not None:
|
|
231
|
+
target_parts = st.target.parts
|
|
232
|
+
if target_parts is not None:
|
|
233
|
+
ident = _table_ident(sink, target_parts, ctx_catalog, ctx_schema)
|
|
234
|
+
if ident.logical_key() in sink.entities:
|
|
235
|
+
return ident
|
|
236
|
+
tident = _transform_ident(sink, path, stmt_i, transform_tag)
|
|
237
|
+
if tident.logical_key() in sink.entities:
|
|
238
|
+
return tident
|
|
239
|
+
return fallback
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _map_res(
|
|
243
|
+
transform_tag: str, path: str, res: DomainResolution | None
|
|
244
|
+
) -> DomainResolution | None:
|
|
245
|
+
"""Re-marca uma ``DomainResolution`` sql → aws-data."""
|
|
246
|
+
if res is None:
|
|
247
|
+
return None
|
|
248
|
+
return DomainResolution(
|
|
249
|
+
status=res.status,
|
|
250
|
+
confidence_class=res.confidence_class,
|
|
251
|
+
resolver=RESOLVER_ID,
|
|
252
|
+
target=(_map_ident(transform_tag, path, res.target) if res.target is not None else None),
|
|
253
|
+
candidates=tuple(_map_ident(transform_tag, path, c) for c in res.candidates),
|
|
254
|
+
unresolved_text=res.unresolved_text,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _entity_kind(sink: Sink, e: ExtractedDomainEntity) -> tuple[DomainEntityKind, dict[str, str]]:
|
|
259
|
+
"""Promoção ``data_table`` → ``iceberg_table`` quando provada."""
|
|
260
|
+
kind = e.kind
|
|
261
|
+
extra: dict[str, str] = {}
|
|
262
|
+
if kind is not DomainEntityKind.DATA_TABLE:
|
|
263
|
+
return kind, extra
|
|
264
|
+
# meta-tabela ``…t.snapshots`` ou tabela sob catálogo iceberg
|
|
265
|
+
if e.name.lower() in ICEBERG_META and e.identity.namespace:
|
|
266
|
+
return DomainEntityKind.ICEBERG_TABLE, {"iceberg_meta_table": e.name.lower()}
|
|
267
|
+
cat_ent = sink.catalogs.get(e.identity.catalog)
|
|
268
|
+
prev = sink.entities.get(cat_ent.logical_key()) if cat_ent is not None else None
|
|
269
|
+
if prev is not None and prev.attributes.get("engine") == "iceberg":
|
|
270
|
+
return DomainEntityKind.ICEBERG_TABLE, extra
|
|
271
|
+
return kind, extra
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _merge_sql_entities(
|
|
275
|
+
sink: Sink,
|
|
276
|
+
fx: FileExtraction,
|
|
277
|
+
*,
|
|
278
|
+
transform_tag: str,
|
|
279
|
+
path: str,
|
|
280
|
+
remap: SpanRemap,
|
|
281
|
+
ev_map: dict[int, int],
|
|
282
|
+
) -> list[DomainIdentity]:
|
|
283
|
+
"""Re-marca entidades sql → aws-data; retorna transformações."""
|
|
284
|
+
transforms: list[DomainIdentity] = []
|
|
285
|
+
for e in fx.entities:
|
|
286
|
+
ident = _map_ident(transform_tag, path, e.identity)
|
|
287
|
+
kind, attrs_extra = _entity_kind(sink, e)
|
|
288
|
+
if kind is DomainEntityKind.DATA_TRANSFORMATION:
|
|
289
|
+
transforms.append(ident)
|
|
290
|
+
# atributos que citam identidades (ex.: ``table: sql:db.t``)
|
|
291
|
+
# re-marcados para o domínio produtor — mesmo fato, novo dono
|
|
292
|
+
attrs = {
|
|
293
|
+
k: (
|
|
294
|
+
f"{ADAPTER_DOMAIN}:{v[len('sql') + 1 :]}"
|
|
295
|
+
if isinstance(v, str) and v.startswith("sql:")
|
|
296
|
+
else v
|
|
297
|
+
)
|
|
298
|
+
for k, v in e.attributes.items()
|
|
299
|
+
}
|
|
300
|
+
attrs.update(attrs_extra)
|
|
301
|
+
sink.entity_for(
|
|
302
|
+
ident,
|
|
303
|
+
kind=kind,
|
|
304
|
+
name=e.name,
|
|
305
|
+
path=e.path,
|
|
306
|
+
span=remap(e.span),
|
|
307
|
+
content_hash=e.content_hash,
|
|
308
|
+
attributes=attrs,
|
|
309
|
+
evidence=tuple(ev_map[i] for i in e.evidence if i in ev_map),
|
|
310
|
+
)
|
|
311
|
+
return transforms
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _merge_sql_relations(
|
|
315
|
+
sink: Sink,
|
|
316
|
+
fx: FileExtraction,
|
|
317
|
+
*,
|
|
318
|
+
transform_tag: str,
|
|
319
|
+
path: str,
|
|
320
|
+
remap: SpanRemap,
|
|
321
|
+
ev_map: dict[int, int],
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Re-marca relações sql → aws-data (mesmo vocabulário fechado)."""
|
|
324
|
+
for r in fx.relations:
|
|
325
|
+
if r.from_identity is None:
|
|
326
|
+
# o extractor sql nunca emite from_symbol; se ocorrer,
|
|
327
|
+
# registra-se e segue — nunca crash.
|
|
328
|
+
sink.diag(
|
|
329
|
+
"unsupported-relation",
|
|
330
|
+
"warning",
|
|
331
|
+
f"sql relation with from_symbol={r.from_symbol!r} skipped — no identity endpoint",
|
|
332
|
+
path,
|
|
333
|
+
)
|
|
334
|
+
continue
|
|
335
|
+
res = _map_res(transform_tag, path, r.resolution)
|
|
336
|
+
sink.rel(
|
|
337
|
+
r.relation_type,
|
|
338
|
+
r.path,
|
|
339
|
+
_map_ident(transform_tag, path, r.from_identity),
|
|
340
|
+
to_identity=(
|
|
341
|
+
_map_ident(transform_tag, path, r.to_identity)
|
|
342
|
+
if r.to_identity is not None
|
|
343
|
+
else None
|
|
344
|
+
),
|
|
345
|
+
to_unresolved=r.to_unresolved or (res.unresolved_text if res is not None else ""),
|
|
346
|
+
status=(res.status if res is not None else None),
|
|
347
|
+
confidence=(res.confidence_class if res is not None else ConfidenceClass.SYNTACTIC),
|
|
348
|
+
span=remap(r.span),
|
|
349
|
+
evidence=tuple(ev_map[i] for i in r.evidence if i in ev_map),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def extract_sql_fragment(
|
|
354
|
+
sink: Sink,
|
|
355
|
+
*,
|
|
356
|
+
sql_text: str,
|
|
357
|
+
path: str,
|
|
358
|
+
sha: str,
|
|
359
|
+
transform_tag: str = "",
|
|
360
|
+
span_remap: SpanRemap | None = None,
|
|
361
|
+
use_catalog: str = "",
|
|
362
|
+
use_schema: str = "",
|
|
363
|
+
fallback_anchor: DomainIdentity | None = None,
|
|
364
|
+
) -> SqlFragment:
|
|
365
|
+
"""Parseia + extrai + re-marca um fragmento SQL no ``Sink``.
|
|
366
|
+
|
|
367
|
+
``use_catalog``/``use_schema`` entram via ``USE`` sintético —
|
|
368
|
+
somente quando declarados no artefato. ``span_remap`` desloca spans
|
|
369
|
+
relativos ao fragmento para spans reais do arquivo.
|
|
370
|
+
"""
|
|
371
|
+
remap = span_remap or (lambda s: s)
|
|
372
|
+
prefix = ""
|
|
373
|
+
if use_catalog or use_schema:
|
|
374
|
+
use_name = ".".join(p for p in (use_catalog, use_schema) if p)
|
|
375
|
+
if use_name:
|
|
376
|
+
prefix = f"USE {use_name};\n"
|
|
377
|
+
full_text = prefix + sql_text
|
|
378
|
+
parsed = parse_file(full_text)
|
|
379
|
+
fx = extract_parsed(
|
|
380
|
+
parsed,
|
|
381
|
+
repository=sink.repository,
|
|
382
|
+
root=sink.root,
|
|
383
|
+
environment=sink.environment,
|
|
384
|
+
path=path,
|
|
385
|
+
sha=sha,
|
|
386
|
+
adapter_version=sink.version,
|
|
387
|
+
)
|
|
388
|
+
# merge no sink — entidades/relações re-marcadas para aws-data
|
|
389
|
+
ev_map: dict[int, int] = {}
|
|
390
|
+
for i, ev in enumerate(fx.evidence):
|
|
391
|
+
notes = f"{ev.notes};via=domain:sql" if ev.notes else ("via=domain:sql")
|
|
392
|
+
ev_map[i] = sink.evi(ev.path, ev.content_hash, remap(ev.span), notes=notes)
|
|
393
|
+
transforms = _merge_sql_entities(
|
|
394
|
+
sink, fx, transform_tag=transform_tag, path=path, remap=remap, ev_map=ev_map
|
|
395
|
+
)
|
|
396
|
+
_merge_sql_relations(
|
|
397
|
+
sink, fx, transform_tag=transform_tag, path=path, remap=remap, ev_map=ev_map
|
|
398
|
+
)
|
|
399
|
+
for d in fx.diagnostics:
|
|
400
|
+
sink.diag(d.code, d.severity, d.message, path=d.path or path)
|
|
401
|
+
for k, v in fx.stats.items():
|
|
402
|
+
sink.bump(f"sql_{k}", v)
|
|
403
|
+
|
|
404
|
+
_emit_s3_literals(
|
|
405
|
+
sink,
|
|
406
|
+
sql_text=full_text,
|
|
407
|
+
statements=parsed.statements,
|
|
408
|
+
path=path,
|
|
409
|
+
sha=sha,
|
|
410
|
+
transform_tag=transform_tag,
|
|
411
|
+
span_remap=remap,
|
|
412
|
+
ctx_catalog=use_catalog,
|
|
413
|
+
ctx_schema=use_schema,
|
|
414
|
+
fallback_anchor=fallback_anchor,
|
|
415
|
+
)
|
|
416
|
+
sink.bump("sql_fragments")
|
|
417
|
+
return SqlFragment(transform_idents=tuple(transforms))
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def literal_span_remap(
|
|
421
|
+
*,
|
|
422
|
+
file_text: str,
|
|
423
|
+
line_starts: tuple[int, ...],
|
|
424
|
+
body_char_start: int,
|
|
425
|
+
contiguous: bool,
|
|
426
|
+
fallback: SourceSpan | None,
|
|
427
|
+
) -> SpanRemap:
|
|
428
|
+
"""Remap de spans de literal embutido → offsets reais do arquivo.
|
|
429
|
+
|
|
430
|
+
``contiguous=True`` quando o corpo decodificado do literal é uma
|
|
431
|
+
fatia literal do fonte (sem escapes) — então spans deslocam por
|
|
432
|
+
``body_char_start``; senão todo span cai para ``fallback`` (o span
|
|
433
|
+
do literal) — nunca um offset errado.
|
|
434
|
+
"""
|
|
435
|
+
body_byte_start = _char_to_byte(file_text, body_char_start)
|
|
436
|
+
|
|
437
|
+
def _remap(span: SourceSpan | None) -> SourceSpan | None:
|
|
438
|
+
if span is None:
|
|
439
|
+
return None
|
|
440
|
+
if not contiguous:
|
|
441
|
+
return fallback
|
|
442
|
+
return byte_span(
|
|
443
|
+
line_starts,
|
|
444
|
+
body_byte_start + span.start_byte,
|
|
445
|
+
body_byte_start + span.end_byte,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
return _remap
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Domínio CI/CD (lf-0116, Cycle 7 Phase 8).
|
|
2
|
+
|
|
3
|
+
GitHub Actions é a plataforma primária; demais plataformas (GitLab CI,
|
|
4
|
+
Azure Pipelines, Jenkins, CircleCI) são declared-optional — ver
|
|
5
|
+
``OPTIONAL_PLATFORMS``. O parser YAML é um subconjunto stdlib-only
|
|
6
|
+
(``yamlmini``) que reporta diagnósticos para construções fora do
|
|
7
|
+
suporte em vez de fabricar valores.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from code_atlas.domains.cicd.adapter import (
|
|
11
|
+
ADAPTER_VERSION,
|
|
12
|
+
DOMAIN_ID,
|
|
13
|
+
OPTIONAL_PLATFORMS,
|
|
14
|
+
PLATFORM,
|
|
15
|
+
GitHubActionsAdapter,
|
|
16
|
+
)
|
|
17
|
+
from code_atlas.domains.cicd.classify import classify_step
|
|
18
|
+
from code_atlas.domains.cicd.workflow import (
|
|
19
|
+
JobObs,
|
|
20
|
+
StepObs,
|
|
21
|
+
TriggerObs,
|
|
22
|
+
WorkflowObs,
|
|
23
|
+
parse_workflow,
|
|
24
|
+
sanitize,
|
|
25
|
+
)
|
|
26
|
+
from code_atlas.domains.cicd.yamlmini import (
|
|
27
|
+
MiniYamlResult,
|
|
28
|
+
YamlDiagnostic,
|
|
29
|
+
YNode,
|
|
30
|
+
parse,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ADAPTER_VERSION",
|
|
35
|
+
"DOMAIN_ID",
|
|
36
|
+
"OPTIONAL_PLATFORMS",
|
|
37
|
+
"PLATFORM",
|
|
38
|
+
"GitHubActionsAdapter",
|
|
39
|
+
"JobObs",
|
|
40
|
+
"MiniYamlResult",
|
|
41
|
+
"StepObs",
|
|
42
|
+
"TriggerObs",
|
|
43
|
+
"WorkflowObs",
|
|
44
|
+
"YNode",
|
|
45
|
+
"YamlDiagnostic",
|
|
46
|
+
"classify_step",
|
|
47
|
+
"parse",
|
|
48
|
+
"parse_workflow",
|
|
49
|
+
"sanitize",
|
|
50
|
+
]
|