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,610 @@
|
|
|
1
|
+
"""lf-0136 (Cycle 8 Phase 10) — valor medido do context pack (proxy).
|
|
2
|
+
|
|
3
|
+
**Não é avaliação de agente** — não existe LLM/agent loop no
|
|
4
|
+
repositório, então a qualidade-de-resposta é NOT RUN (declarado, não
|
|
5
|
+
simulado). O que se mede é um **proxy estrutural honesto**:
|
|
6
|
+
|
|
7
|
+
- ``no_atlas``: simulação determinística de "engenheiro com grep" —
|
|
8
|
+
busca literal iterativa sobre a árvore da fixture (1 query por
|
|
9
|
+
rodada, tokens = conteúdo dos arquivos abertos);
|
|
10
|
+
- ``pack_only``: pack real de ``DomainContextEngine.build_domain``
|
|
11
|
+
(budget default 4000) — ``tokens_payload`` estimado pelo próprio
|
|
12
|
+
engine vs fatos contidos;
|
|
13
|
+
- ``atlas_plus_search``: duas variantes reais — ``impact symbol``
|
|
14
|
+
(especialista, 1 query) e ``domains entities``+``lineage`` (montagem
|
|
15
|
+
por busca estrutural — as queries necessárias para juntar a mesma
|
|
16
|
+
informação sem o pack).
|
|
17
|
+
|
|
18
|
+
Fatos = os mesmos ``ImpactCandidate.qualified_name`` verificados em
|
|
19
|
+
lf-0135 — o "o que precisa constar na resposta" rotulado à mão. Um
|
|
20
|
+
fato é "answerable" por um braço quando uma de suas formas aparece no
|
|
21
|
+
conteúdo que o braço entrega (match com fronteira de token — `orders`
|
|
22
|
+
não casa dentro de `enriched_orders`/`staging.orders`).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
import sqlite3
|
|
30
|
+
from collections.abc import Mapping, Sequence
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from code_atlas.context.contracts import DomainPackRequest
|
|
36
|
+
from code_atlas.context.domain import DomainContextEngine
|
|
37
|
+
from code_atlas.context.tokenizer import HeuristicTokenizer
|
|
38
|
+
from code_atlas.eval.impact_truth import (
|
|
39
|
+
index_fixture,
|
|
40
|
+
run_cli,
|
|
41
|
+
run_impact,
|
|
42
|
+
)
|
|
43
|
+
from code_atlas.impact.domain_traversal import resolve_entity_refs
|
|
44
|
+
|
|
45
|
+
SCENARIO_SCHEMA = 1
|
|
46
|
+
RESULTS_SCHEMA = 1
|
|
47
|
+
|
|
48
|
+
PACK_BUDGET = 4000 # default do DomainPackRequest
|
|
49
|
+
NO_ATLAS_MAX_ROUNDS = 4 # o agente real desiste; cap honesto
|
|
50
|
+
_LINEAGE_DEPTH = 8 # teto real do `domains lineage` (MAX_DEPTH)
|
|
51
|
+
|
|
52
|
+
_DOMAIN_PREFIXES = ("sql:", "pyspark:", "aws-data:", "terraform:",
|
|
53
|
+
"cicd:", "api:")
|
|
54
|
+
|
|
55
|
+
def _edge_pattern(token: str) -> re.Pattern[str]:
|
|
56
|
+
"""Fronteira para DETECÇÃO DE FATO: o nome não pode estar colado
|
|
57
|
+
em identificador maior (``orders`` não casa em ``enriched_orders``
|
|
58
|
+
nem em ``staging.orders`` — a forma qualificada cobre esse caso).
|
|
59
|
+
``/`` não é bloqueada no lookbehind: ``x/orders`` termina com o
|
|
60
|
+
nome de verdade."""
|
|
61
|
+
return re.compile(
|
|
62
|
+
r"(?<![\w.\-])" + re.escape(token) + r"(?![\w])")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _display_of_row(row: Mapping[str, Any]) -> str:
|
|
66
|
+
"""``ns.qn[@env]`` — mesma forma de ``_display_of`` do traversal."""
|
|
67
|
+
head = ".".join(
|
|
68
|
+
p for p in (str(row["catalog"] or ""),
|
|
69
|
+
str(row["namespace"] or "")) if p)
|
|
70
|
+
qn = str(row["qualified_name"])
|
|
71
|
+
disp = f"{head}.{qn}" if head else qn
|
|
72
|
+
env = str(row["environment"] or "")
|
|
73
|
+
return f"{disp}@{env}" if env else disp
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_fact_forms(
|
|
77
|
+
conn: sqlite3.Connection, repository_id: int, fact: str,
|
|
78
|
+
) -> tuple[str, ...]:
|
|
79
|
+
"""Formas reais do fato — resolvidas em ``domain_entities`` quando
|
|
80
|
+
o fato é de domínio (o ``qualified_name`` de verdade, que é o que
|
|
81
|
+
aparece no código-fonte); fallback heurístico para símbolos."""
|
|
82
|
+
domain = ""
|
|
83
|
+
target = fact
|
|
84
|
+
for prefix in _DOMAIN_PREFIXES:
|
|
85
|
+
if fact.startswith(prefix):
|
|
86
|
+
domain = prefix[:-1]
|
|
87
|
+
target = fact[len(prefix):]
|
|
88
|
+
break
|
|
89
|
+
if not domain:
|
|
90
|
+
return fact_forms(fact)
|
|
91
|
+
for row in conn.execute(
|
|
92
|
+
"SELECT qualified_name, namespace, catalog, environment"
|
|
93
|
+
" FROM domain_entities"
|
|
94
|
+
" WHERE repository_id = ? AND domain = ?",
|
|
95
|
+
(repository_id, domain)).fetchall():
|
|
96
|
+
disp = _display_of_row(row)
|
|
97
|
+
if target == disp or target == str(row["qualified_name"]):
|
|
98
|
+
return _entity_forms(fact, target, disp,
|
|
99
|
+
str(row["qualified_name"]))
|
|
100
|
+
return fact_forms(fact)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _entity_forms(
|
|
104
|
+
fact: str, target: str, disp: str, qn: str,
|
|
105
|
+
) -> tuple[str, ...]:
|
|
106
|
+
"""Formas de match derivadas da entidade real — ``file#N`` rende
|
|
107
|
+
o caminho (o ``#N`` nunca existe como texto)."""
|
|
108
|
+
forms = {fact, target, disp, disp.split("@", 1)[0], qn,
|
|
109
|
+
qn.split("@", 1)[0]}
|
|
110
|
+
for base in (qn, disp.split("@", 1)[0]):
|
|
111
|
+
if "/" in base:
|
|
112
|
+
forms.add(base.rstrip("/").rsplit("/", 1)[-1])
|
|
113
|
+
if "." in base:
|
|
114
|
+
forms.add(base.rsplit(".", 1)[-1])
|
|
115
|
+
if "#" in base:
|
|
116
|
+
path, _, frag = base.partition("#")
|
|
117
|
+
if path:
|
|
118
|
+
forms.add(path)
|
|
119
|
+
forms.add(path.rsplit("/", 1)[-1])
|
|
120
|
+
if frag:
|
|
121
|
+
forms.add(frag.rsplit("/", 1)[-1])
|
|
122
|
+
return tuple(sorted(f for f in forms if f and not f.isdigit()))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class PackTask:
|
|
127
|
+
"""Tarefa rotulada — pergunta real + conjunto de fatos esperados."""
|
|
128
|
+
|
|
129
|
+
task_id: str
|
|
130
|
+
question: str
|
|
131
|
+
fixture: str
|
|
132
|
+
domains: tuple[str, ...]
|
|
133
|
+
cross_domain: bool
|
|
134
|
+
seed: str
|
|
135
|
+
seed_domain: str
|
|
136
|
+
grep_seeds: tuple[str, ...]
|
|
137
|
+
facts: tuple[str, ...]
|
|
138
|
+
notes: str = ""
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def load_tasks(path: Path) -> list[PackTask]:
|
|
142
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
143
|
+
if int(raw.get("schema", 0)) != SCENARIO_SCHEMA:
|
|
144
|
+
raise ValueError(f"pack-value tasks schema != {SCENARIO_SCHEMA}")
|
|
145
|
+
out: list[PackTask] = []
|
|
146
|
+
for t in raw["tasks"]:
|
|
147
|
+
out.append(PackTask(
|
|
148
|
+
task_id=str(t["id"]),
|
|
149
|
+
question=str(t["question"]),
|
|
150
|
+
fixture=str(t["fixture"]),
|
|
151
|
+
domains=tuple(str(d) for d in t["domains"]),
|
|
152
|
+
cross_domain=bool(t.get("cross_domain", False)),
|
|
153
|
+
seed=str(t["seed"]),
|
|
154
|
+
seed_domain=str(t.get("seed_domain", "")),
|
|
155
|
+
grep_seeds=tuple(str(g) for g in t.get("grep_seeds", ())),
|
|
156
|
+
facts=tuple(str(f) for f in t["facts"]),
|
|
157
|
+
notes=str(t.get("notes", "")),
|
|
158
|
+
))
|
|
159
|
+
return out
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def validate_task(repo_root: Path, task: PackTask) -> list[str]:
|
|
163
|
+
errs: list[str] = []
|
|
164
|
+
if not (repo_root / task.fixture).is_dir():
|
|
165
|
+
errs.append(f"fixture ausente: {task.fixture}")
|
|
166
|
+
if not task.seed or not task.facts or not task.grep_seeds:
|
|
167
|
+
errs.append("seed/facts/grep_seeds vazios")
|
|
168
|
+
if len(set(task.facts)) != len(task.facts):
|
|
169
|
+
errs.append("facts duplicados")
|
|
170
|
+
return errs
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# ---------------------------------------------------------------------
|
|
174
|
+
# match de fatos — formas + fronteira de token
|
|
175
|
+
# ---------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def fact_forms(name: str) -> tuple[str, ...]:
|
|
179
|
+
"""Formas casáveis de um fato (``sql:staging.orders`` →
|
|
180
|
+
{sql:staging.orders, staging.orders, orders}; ``a/b.py#f`` →
|
|
181
|
+
{a/b.py#f, b.py, f})."""
|
|
182
|
+
s = name
|
|
183
|
+
for prefix in _DOMAIN_PREFIXES:
|
|
184
|
+
if s.startswith(prefix):
|
|
185
|
+
s = s[len(prefix):]
|
|
186
|
+
break
|
|
187
|
+
forms: set[str] = {name, s}
|
|
188
|
+
core = s.split("@", 1)[0] # '@dev' env / '@L19' linha → cauda
|
|
189
|
+
forms.add(core)
|
|
190
|
+
if "#" in core:
|
|
191
|
+
path, _, frag = core.partition("#")
|
|
192
|
+
if path:
|
|
193
|
+
forms.add(path)
|
|
194
|
+
forms.add(path.rsplit("/", 1)[-1])
|
|
195
|
+
if frag:
|
|
196
|
+
forms.add(frag.rsplit("/", 1)[-1])
|
|
197
|
+
if "/" in core:
|
|
198
|
+
forms.add(core.rstrip("/").rsplit("/", 1)[-1])
|
|
199
|
+
elif "." in core:
|
|
200
|
+
forms.add(core.rsplit(".", 1)[-1])
|
|
201
|
+
return tuple(sorted(f for f in forms if f and not f.isdigit()))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def contains_token(text: str, token: str) -> bool:
|
|
205
|
+
"""Match com fronteira — ``token`` não pode estar grudado em
|
|
206
|
+
identificador maior (detecção de fato, não grep)."""
|
|
207
|
+
return _edge_pattern(token).search(text) is not None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def facts_in(
|
|
211
|
+
texts: Sequence[str], forms_map: Mapping[str, Sequence[str]],
|
|
212
|
+
) -> list[str]:
|
|
213
|
+
"""Fatos casados no conteúdo entregue por um braço."""
|
|
214
|
+
blob = "\n".join(texts)
|
|
215
|
+
return [f for f, forms in forms_map.items()
|
|
216
|
+
if any(contains_token(blob, form) for form in forms)]
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------
|
|
220
|
+
# braço A — no_atlas: simulação grep+read iterativa (determinística)
|
|
221
|
+
# ---------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _iter_fixture_files(fixture_dir: Path) -> dict[str, str]:
|
|
225
|
+
"""``relative_path → conteúdo`` de todo arquivo de texto da
|
|
226
|
+
fixture (inclui ``.github/``; binários ilegíveis são pulados)."""
|
|
227
|
+
corpus: dict[str, str] = {}
|
|
228
|
+
for p in sorted(fixture_dir.rglob("*")):
|
|
229
|
+
if not p.is_file():
|
|
230
|
+
continue
|
|
231
|
+
try:
|
|
232
|
+
corpus[str(p.relative_to(fixture_dir))] = \
|
|
233
|
+
p.read_text(encoding="utf-8")
|
|
234
|
+
except (UnicodeDecodeError, ValueError):
|
|
235
|
+
continue
|
|
236
|
+
return corpus
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def no_atlas(
|
|
240
|
+
fixture_dir: Path, grep_seeds: Sequence[str],
|
|
241
|
+
forms_map: Mapping[str, Sequence[str]],
|
|
242
|
+
*, max_rounds: int = NO_ATLAS_MAX_ROUNDS,
|
|
243
|
+
counter: HeuristicTokenizer | None = None,
|
|
244
|
+
) -> dict[str, Any]:
|
|
245
|
+
"""Braço "sem atlas": grep literal → lê arquivos que casam → nomes
|
|
246
|
+
encontrados viram próximas buscas. 1 query por rodada (tokens
|
|
247
|
+
empacotados num único ``grep -e a -e b`` — a busca é substring
|
|
248
|
+
crua, como o grep real); tokens = conteúdo dos arquivos abertos."""
|
|
249
|
+
tok = counter or HeuristicTokenizer()
|
|
250
|
+
corpus = _iter_fixture_files(fixture_dir)
|
|
251
|
+
read: dict[str, str] = {}
|
|
252
|
+
found: set[str] = set()
|
|
253
|
+
used: set[str] = set()
|
|
254
|
+
pending = sorted(set(grep_seeds))
|
|
255
|
+
rounds: list[dict[str, Any]] = []
|
|
256
|
+
queries = 0
|
|
257
|
+
for _ in range(max_rounds + 1):
|
|
258
|
+
if not pending:
|
|
259
|
+
break
|
|
260
|
+
queries += 1
|
|
261
|
+
used.update(pending)
|
|
262
|
+
# grep real é substring crua — sem fronteira de token.
|
|
263
|
+
hits = sorted(
|
|
264
|
+
path for path, text in corpus.items()
|
|
265
|
+
if path not in read and any(
|
|
266
|
+
q in text or q in path for q in pending))
|
|
267
|
+
for h in hits:
|
|
268
|
+
read[h] = corpus[h]
|
|
269
|
+
texts = [*read.values(), *read.keys()]
|
|
270
|
+
now = set(facts_in(texts, forms_map))
|
|
271
|
+
new = now - found
|
|
272
|
+
found |= new
|
|
273
|
+
rounds.append({
|
|
274
|
+
"query_tokens": list(pending),
|
|
275
|
+
"files_matched": len(hits),
|
|
276
|
+
"facts_found_total": len(found),
|
|
277
|
+
})
|
|
278
|
+
pending = sorted(
|
|
279
|
+
{tok for f in new
|
|
280
|
+
for tok in forms_map.get(f, ()) if len(tok) >= 4}
|
|
281
|
+
- used)
|
|
282
|
+
tokens = sum(tok.count(t) for t in read.values())
|
|
283
|
+
missing = [f for f in forms_map if f not in found]
|
|
284
|
+
return {
|
|
285
|
+
"queries": queries,
|
|
286
|
+
"files_read": len(read),
|
|
287
|
+
"files_total": len(corpus),
|
|
288
|
+
"tokens": tokens,
|
|
289
|
+
"facts_found": sorted(found),
|
|
290
|
+
"facts_missing": missing,
|
|
291
|
+
"facts_answerable": len(found),
|
|
292
|
+
"facts_expected": len(forms_map),
|
|
293
|
+
"rounds": rounds,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ---------------------------------------------------------------------
|
|
298
|
+
# braço B — pack-only: DomainContextEngine real
|
|
299
|
+
# ---------------------------------------------------------------------
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _dom_key(
|
|
303
|
+
conn: sqlite3.Connection, repository_id: int, task: PackTask,
|
|
304
|
+
) -> str | None:
|
|
305
|
+
"""``dom:<identity_key>`` da seed — resolvido na base real."""
|
|
306
|
+
rows, _how = resolve_entity_refs(conn, repository_id, task.seed)
|
|
307
|
+
for row in rows:
|
|
308
|
+
if str(row["domain"]) == task.seed_domain:
|
|
309
|
+
return str(row["identity_key"])
|
|
310
|
+
return str(rows[0]["identity_key"]) if rows else None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def pack_arm(
|
|
314
|
+
fixture: Path, task: PackTask, conn: sqlite3.Connection,
|
|
315
|
+
repository_id: int, forms_map: Mapping[str, Sequence[str]],
|
|
316
|
+
*, budget: int = PACK_BUDGET,
|
|
317
|
+
) -> dict[str, Any]:
|
|
318
|
+
"""Braço "pack-only": um ``build_domain`` real — payload estimado
|
|
319
|
+
pelo próprio engine vs fatos contidos no pack."""
|
|
320
|
+
dom_key = _dom_key(conn, repository_id, task)
|
|
321
|
+
req = DomainPackRequest(
|
|
322
|
+
seed=dom_key or task.seed,
|
|
323
|
+
repository_root=str(fixture),
|
|
324
|
+
budget_tokens=budget)
|
|
325
|
+
pack = DomainContextEngine(conn).build_domain(req)
|
|
326
|
+
section = pack.domain
|
|
327
|
+
text = json.dumps(
|
|
328
|
+
_pack_dict(pack), sort_keys=True, default=str)
|
|
329
|
+
found = facts_in([text], forms_map)
|
|
330
|
+
return {
|
|
331
|
+
"queries": 1,
|
|
332
|
+
"tokens": int(pack.manifest.tokens_payload),
|
|
333
|
+
"tokens_estimated": bool(pack.manifest.tokens_estimated),
|
|
334
|
+
"budget_tokens": budget,
|
|
335
|
+
"status": (str(section.status) if section is not None
|
|
336
|
+
else "absent"),
|
|
337
|
+
"neighbors_total": (
|
|
338
|
+
int(section.neighbors_total) if section is not None else 0),
|
|
339
|
+
"neighbors_omitted": (
|
|
340
|
+
int(section.neighbors_omitted)
|
|
341
|
+
if section is not None else 0),
|
|
342
|
+
"items_selected": int(pack.manifest.selected),
|
|
343
|
+
"items_omitted": int(pack.manifest.omitted),
|
|
344
|
+
"truncated": bool(pack.manifest.truncated),
|
|
345
|
+
"seed_identity_key": dom_key,
|
|
346
|
+
"facts_found": found,
|
|
347
|
+
"facts_missing": [f for f in task.facts if f not in found],
|
|
348
|
+
"facts_answerable": len(found),
|
|
349
|
+
"facts_expected": len(task.facts),
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _pack_dict(pack: Any) -> dict[str, Any]:
|
|
354
|
+
import dataclasses
|
|
355
|
+
|
|
356
|
+
def conv(o: Any) -> Any:
|
|
357
|
+
if dataclasses.is_dataclass(o) and not isinstance(o, type):
|
|
358
|
+
return {k: conv(v) for k, v in
|
|
359
|
+
dataclasses.asdict(o).items()}
|
|
360
|
+
if isinstance(o, (tuple, list)):
|
|
361
|
+
return [conv(v) for v in o]
|
|
362
|
+
return o
|
|
363
|
+
out = conv(pack)
|
|
364
|
+
return out if isinstance(out, dict) else {"pack": out}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ---------------------------------------------------------------------
|
|
368
|
+
# braço C — atlas+search: impact (especialista) + montagem por
|
|
369
|
+
# domains entities/lineage (busca estrutural genérica)
|
|
370
|
+
# ---------------------------------------------------------------------
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _seed_query_term(seed: str) -> str:
|
|
374
|
+
"""Termo de ``--q`` natural: basename do caminho (``x.yml#job`` →
|
|
375
|
+
``x.yml``) ou último segmento (``staging.orders`` → ``orders``)."""
|
|
376
|
+
s = seed
|
|
377
|
+
for prefix in _DOMAIN_PREFIXES:
|
|
378
|
+
if s.startswith(prefix):
|
|
379
|
+
s = s[len(prefix):]
|
|
380
|
+
break
|
|
381
|
+
core = s.split("@", 1)[0].split("#", 1)[0]
|
|
382
|
+
if "/" in core:
|
|
383
|
+
cand = core.rstrip("/").rsplit("/", 1)[-1]
|
|
384
|
+
if cand:
|
|
385
|
+
return cand
|
|
386
|
+
if "." in core:
|
|
387
|
+
return core.rsplit(".", 1)[-1]
|
|
388
|
+
return core
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def atlas_search_arm(
|
|
392
|
+
fixture: Path, task: PackTask, conn: sqlite3.Connection,
|
|
393
|
+
repository_id: int, forms_map: Mapping[str, Sequence[str]],
|
|
394
|
+
*, data_dir: Path, config_dir: Path,
|
|
395
|
+
counter: HeuristicTokenizer,
|
|
396
|
+
) -> dict[str, Any]:
|
|
397
|
+
"""Duas variantes medidas:
|
|
398
|
+
|
|
399
|
+
- ``impact``: ``impact symbol <seed> --format json`` — 1 query,
|
|
400
|
+
conjunto verificado completo;
|
|
401
|
+
- ``search``: ``domains entities --q <termo>`` + ``domains
|
|
402
|
+
lineage <dom_key> --direction both --depth 8`` — montagem por
|
|
403
|
+
busca estrutural (só percorre fluxo-de-dados — mede o gap que
|
|
404
|
+
``CONTAINS``/``REFERENCES``/pontes de símbolo cobrem).
|
|
405
|
+
"""
|
|
406
|
+
out: dict[str, Any] = {}
|
|
407
|
+
|
|
408
|
+
_run, payload = run_impact(
|
|
409
|
+
fixture, task.seed, data_dir=data_dir, config_dir=config_dir)
|
|
410
|
+
if payload is not None:
|
|
411
|
+
imp_text = json.dumps(payload, sort_keys=True, default=str)
|
|
412
|
+
found = facts_in([imp_text], forms_map)
|
|
413
|
+
out["impact"] = {
|
|
414
|
+
"queries": 1,
|
|
415
|
+
"tokens": counter.count(imp_text),
|
|
416
|
+
"candidates": len(payload.get("impacts") or ()),
|
|
417
|
+
"facts_found": found,
|
|
418
|
+
"facts_answerable": len(found),
|
|
419
|
+
}
|
|
420
|
+
else:
|
|
421
|
+
out["impact"] = {"queries": 1, "error": "sem JSON"}
|
|
422
|
+
|
|
423
|
+
queries = 1
|
|
424
|
+
texts: list[str] = []
|
|
425
|
+
run_e = run_cli(
|
|
426
|
+
("domains", "entities", "--q", _seed_query_term(task.seed),
|
|
427
|
+
"--root", str(fixture), "--json"),
|
|
428
|
+
data_dir=data_dir, config_dir=config_dir)
|
|
429
|
+
texts.append(run_e.stdout)
|
|
430
|
+
dom_key = _dom_key(conn, repository_id, task)
|
|
431
|
+
if dom_key is not None:
|
|
432
|
+
queries += 1
|
|
433
|
+
run_l = run_cli(
|
|
434
|
+
("domains", "lineage", dom_key,
|
|
435
|
+
"--direction", "both", "--depth", str(_LINEAGE_DEPTH),
|
|
436
|
+
"--root", str(fixture), "--json"),
|
|
437
|
+
data_dir=data_dir, config_dir=config_dir)
|
|
438
|
+
texts.append(run_l.stdout)
|
|
439
|
+
found = facts_in(texts, forms_map)
|
|
440
|
+
out["search"] = {
|
|
441
|
+
"queries": queries,
|
|
442
|
+
"tokens": sum(counter.count(t) for t in texts),
|
|
443
|
+
"facts_found": found,
|
|
444
|
+
"facts_answerable": len(found),
|
|
445
|
+
}
|
|
446
|
+
out["facts_expected"] = len(task.facts)
|
|
447
|
+
return out
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ---------------------------------------------------------------------
|
|
451
|
+
# avaliação
|
|
452
|
+
# ---------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def evaluate_task(
|
|
456
|
+
repo_root: Path, task: PackTask, conn: sqlite3.Connection,
|
|
457
|
+
repository_id: int, snapshot_id: int, *, data_dir: Path,
|
|
458
|
+
config_dir: Path,
|
|
459
|
+
) -> dict[str, Any]:
|
|
460
|
+
"""Mede os três braços de uma tarefa num repo já indexado."""
|
|
461
|
+
fixture = repo_root / task.fixture
|
|
462
|
+
counter = HeuristicTokenizer()
|
|
463
|
+
rep: dict[str, Any] = {
|
|
464
|
+
"id": task.task_id,
|
|
465
|
+
"question": task.question,
|
|
466
|
+
"fixture": task.fixture,
|
|
467
|
+
"seed": task.seed,
|
|
468
|
+
"facts_expected": len(task.facts),
|
|
469
|
+
"notes": task.notes,
|
|
470
|
+
}
|
|
471
|
+
# formas reais dos fatos — resolvidas em ``domain_entities`` deste
|
|
472
|
+
# repositório (o qualified_name de verdade é o que aparece no
|
|
473
|
+
# código fonte e nas saídas dos braços).
|
|
474
|
+
forms_map = {
|
|
475
|
+
f: resolve_fact_forms(conn, repository_id, f)
|
|
476
|
+
for f in task.facts}
|
|
477
|
+
rep["no_atlas"] = no_atlas(
|
|
478
|
+
fixture, task.grep_seeds, forms_map, counter=counter)
|
|
479
|
+
rep["pack_only"] = pack_arm(
|
|
480
|
+
fixture, task, conn, repository_id, forms_map)
|
|
481
|
+
rep["atlas_plus_search"] = atlas_search_arm(
|
|
482
|
+
fixture, task, conn, repository_id, forms_map,
|
|
483
|
+
data_dir=data_dir, config_dir=config_dir, counter=counter)
|
|
484
|
+
rep["target_snapshot_id"] = snapshot_id
|
|
485
|
+
return rep
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _indexed_slot(
|
|
489
|
+
repo_root: Path, task: PackTask, work_dir: Path, ordinal: int,
|
|
490
|
+
) -> dict[str, Any]:
|
|
491
|
+
"""Provisiona o workspace indexado da fixture (uma indexação real
|
|
492
|
+
por par fixture+adapters) — conn aberta na primeira demanda."""
|
|
493
|
+
ws = work_dir / f"ws-{ordinal}"
|
|
494
|
+
data_dir, config_dir = ws / "data", ws / "cfg"
|
|
495
|
+
data_dir.mkdir(parents=True)
|
|
496
|
+
config_dir.mkdir(parents=True)
|
|
497
|
+
run = index_fixture(
|
|
498
|
+
repo_root / task.fixture, task.domains, task.cross_domain,
|
|
499
|
+
data_dir=data_dir, config_dir=config_dir)
|
|
500
|
+
return {"data_dir": data_dir, "config_dir": config_dir,
|
|
501
|
+
"index_run": run, "conn": None,
|
|
502
|
+
"repo_id": -1, "snapshot_id": -1}
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _open_slot(repo_root: Path, task: PackTask,
|
|
506
|
+
slot: dict[str, Any]) -> None:
|
|
507
|
+
"""Abre conn + resolve repo/snapshot pela mesma superfície do
|
|
508
|
+
engine (``StructuralSearch``)."""
|
|
509
|
+
conn = sqlite3.connect(str(slot["data_dir"] / "atlas.db"))
|
|
510
|
+
conn.row_factory = sqlite3.Row
|
|
511
|
+
from code_atlas.config import resolve_root
|
|
512
|
+
from code_atlas.search.structural import StructuralSearch
|
|
513
|
+
|
|
514
|
+
search = StructuralSearch(conn)
|
|
515
|
+
repo_id = search.repo_for_root(
|
|
516
|
+
str(resolve_root(str(repo_root / task.fixture))))
|
|
517
|
+
snap = (search.resolve_snapshot(repo_id, None)[0]
|
|
518
|
+
if repo_id is not None else None)
|
|
519
|
+
slot["conn"] = conn
|
|
520
|
+
slot["repo_id"] = repo_id if repo_id is not None else -1
|
|
521
|
+
slot["snapshot_id"] = (
|
|
522
|
+
int(snap.id) if snap is not None and snap.id is not None
|
|
523
|
+
else -1)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _arm_totals(
|
|
527
|
+
results: Mapping[str, Any], arm: str, sub: str | None,
|
|
528
|
+
) -> tuple[int, int, int]:
|
|
529
|
+
"""``(queries, tokens, facts)`` agregados de um braço (``sub`` =
|
|
530
|
+
variante dentro de ``atlas_plus_search``)."""
|
|
531
|
+
queries = tokens = facts = 0
|
|
532
|
+
for t in results["tasks"].values():
|
|
533
|
+
if not isinstance(t, dict):
|
|
534
|
+
continue
|
|
535
|
+
block = t.get(arm)
|
|
536
|
+
if sub is not None:
|
|
537
|
+
block = block.get(sub) if isinstance(block, dict) else None
|
|
538
|
+
if not isinstance(block, dict):
|
|
539
|
+
continue
|
|
540
|
+
queries += int(block.get("queries") or 0)
|
|
541
|
+
tokens += int(block.get("tokens") or 0)
|
|
542
|
+
facts += int(block.get("facts_answerable") or 0)
|
|
543
|
+
return queries, tokens, facts
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
def _summarize(results: dict[str, Any]) -> dict[str, Any]:
|
|
547
|
+
"""Agregado honesto — totais por braço; nada de média inventada."""
|
|
548
|
+
facts_total = sum(
|
|
549
|
+
int(t["pack_only"]["facts_expected"])
|
|
550
|
+
for t in results["tasks"].values()
|
|
551
|
+
if isinstance(t, dict)
|
|
552
|
+
and isinstance(t.get("pack_only"), dict)
|
|
553
|
+
and "facts_expected" in t["pack_only"])
|
|
554
|
+
summary: dict[str, Any] = {"tasks": len(results["tasks"]),
|
|
555
|
+
"facts_total": facts_total}
|
|
556
|
+
for name, arm, sub in (
|
|
557
|
+
("no_atlas", "no_atlas", None),
|
|
558
|
+
("pack_only", "pack_only", None),
|
|
559
|
+
("atlas_impact", "atlas_plus_search", "impact"),
|
|
560
|
+
("atlas_search", "atlas_plus_search", "search")):
|
|
561
|
+
queries, tokens, facts = _arm_totals(results, arm, sub)
|
|
562
|
+
summary[name] = {
|
|
563
|
+
"queries": queries,
|
|
564
|
+
"tokens": tokens,
|
|
565
|
+
"facts_answerable": facts,
|
|
566
|
+
"fact_coverage": (facts / facts_total
|
|
567
|
+
if facts_total else None),
|
|
568
|
+
}
|
|
569
|
+
return summary
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def evaluate_all(
|
|
573
|
+
repo_root: Path, tasks: Sequence[PackTask], work_dir: Path,
|
|
574
|
+
) -> dict[str, Any]:
|
|
575
|
+
"""Indexa cada fixture uma vez (mesmo ``atlas.db`` por par
|
|
576
|
+
fixture+adapters) e mede todos os braços de cada tarefa."""
|
|
577
|
+
from code_atlas.env_override import patched_env
|
|
578
|
+
|
|
579
|
+
results: dict[str, Any] = {"schema": RESULTS_SCHEMA, "tasks": {}}
|
|
580
|
+
indexed: dict[tuple[str, tuple[str, ...], bool],
|
|
581
|
+
dict[str, Any]] = {}
|
|
582
|
+
for task in tasks:
|
|
583
|
+
key = (task.fixture, task.domains, task.cross_domain)
|
|
584
|
+
slot = indexed.get(key)
|
|
585
|
+
if slot is None:
|
|
586
|
+
slot = _indexed_slot(repo_root, task, work_dir,
|
|
587
|
+
len(indexed))
|
|
588
|
+
indexed[key] = slot
|
|
589
|
+
if slot["index_run"].exit_code != 0:
|
|
590
|
+
results["tasks"][task.task_id] = {
|
|
591
|
+
"id": task.task_id,
|
|
592
|
+
"error": f"index exit {slot['index_run'].exit_code}",
|
|
593
|
+
}
|
|
594
|
+
continue
|
|
595
|
+
if slot["conn"] is None:
|
|
596
|
+
_open_slot(repo_root, task, slot)
|
|
597
|
+
env = {"CODE_ATLAS_DATA_DIR": str(slot["data_dir"]),
|
|
598
|
+
"CODE_ATLAS_CONFIG_DIR": str(slot["config_dir"])}
|
|
599
|
+
with patched_env(env):
|
|
600
|
+
results["tasks"][task.task_id] = evaluate_task(
|
|
601
|
+
repo_root, task, slot["conn"],
|
|
602
|
+
int(slot["repo_id"]), int(slot["snapshot_id"]),
|
|
603
|
+
data_dir=slot["data_dir"],
|
|
604
|
+
config_dir=slot["config_dir"])
|
|
605
|
+
for slot in indexed.values():
|
|
606
|
+
if slot["conn"] is not None:
|
|
607
|
+
slot["conn"].close()
|
|
608
|
+
|
|
609
|
+
results["summary"] = _summarize(results)
|
|
610
|
+
return results
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Federação multi-repo (lf-0118, Cycle 7 Phase 10).
|
|
2
|
+
|
|
3
|
+
Workspaces nomeados agrupam >=2 repositórios já indexados; cada membro
|
|
4
|
+
pode pinar um snapshot explícito (o default é o READY mais recente —
|
|
5
|
+
registrado na view, nunca presumido "mesmo tempo"). A leitura federada
|
|
6
|
+
junta entidades de domínio por identidade lógica compartilhada e emite
|
|
7
|
+
arestas ``shared_entity`` **computadas** — nada é persistido além das
|
|
8
|
+
tabelas v10, nenhuma relação é inventada e o isolamento por snapshot
|
|
9
|
+
entre repositórios é preservado por construção.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from code_atlas.federation.federation import (
|
|
13
|
+
Federation,
|
|
14
|
+
normalize_lookup_key,
|
|
15
|
+
shared_key_of,
|
|
16
|
+
)
|
|
17
|
+
from code_atlas.federation.model import (
|
|
18
|
+
FEDERATION_RESOLVER_ID,
|
|
19
|
+
MAX_GRAPH_DEPTH,
|
|
20
|
+
MIN_FEDERATION_MEMBERS,
|
|
21
|
+
VIEW_ID_FORMAT,
|
|
22
|
+
EntityNode,
|
|
23
|
+
FederatedEdge,
|
|
24
|
+
FederatedGraph,
|
|
25
|
+
FederatedView,
|
|
26
|
+
FederationError,
|
|
27
|
+
FederationMember,
|
|
28
|
+
FederationWorkspace,
|
|
29
|
+
InvalidKeyError,
|
|
30
|
+
InvalidPinError,
|
|
31
|
+
MemberNotFoundError,
|
|
32
|
+
RepositoryNotFoundError,
|
|
33
|
+
SharedOccurrence,
|
|
34
|
+
SnapshotNotFoundError,
|
|
35
|
+
SnapshotNotReadyError,
|
|
36
|
+
ViewMember,
|
|
37
|
+
WorkspaceExistsError,
|
|
38
|
+
WorkspaceNotFoundError,
|
|
39
|
+
WorkspaceSizeError,
|
|
40
|
+
compute_view_id,
|
|
41
|
+
)
|
|
42
|
+
from code_atlas.federation.store import FederationStore
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"FEDERATION_RESOLVER_ID",
|
|
46
|
+
"MAX_GRAPH_DEPTH",
|
|
47
|
+
"MIN_FEDERATION_MEMBERS",
|
|
48
|
+
"VIEW_ID_FORMAT",
|
|
49
|
+
"EntityNode",
|
|
50
|
+
"FederatedEdge",
|
|
51
|
+
"FederatedGraph",
|
|
52
|
+
"FederatedView",
|
|
53
|
+
"Federation",
|
|
54
|
+
"FederationError",
|
|
55
|
+
"FederationMember",
|
|
56
|
+
"FederationStore",
|
|
57
|
+
"FederationWorkspace",
|
|
58
|
+
"InvalidKeyError",
|
|
59
|
+
"InvalidPinError",
|
|
60
|
+
"MemberNotFoundError",
|
|
61
|
+
"RepositoryNotFoundError",
|
|
62
|
+
"SharedOccurrence",
|
|
63
|
+
"SnapshotNotFoundError",
|
|
64
|
+
"SnapshotNotReadyError",
|
|
65
|
+
"ViewMember",
|
|
66
|
+
"WorkspaceExistsError",
|
|
67
|
+
"WorkspaceNotFoundError",
|
|
68
|
+
"WorkspaceSizeError",
|
|
69
|
+
"compute_view_id",
|
|
70
|
+
"normalize_lookup_key",
|
|
71
|
+
"shared_key_of",
|
|
72
|
+
]
|