batho 0.1.4__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.
- batho/__init__.py +55 -0
- batho/bridge/__init__.py +42 -0
- batho/bridge/artifact_loader.py +214 -0
- batho/bridge/constants.py +79 -0
- batho/bridge/http_api.py +383 -0
- batho/bridge/mcp_server.py +159 -0
- batho/bridge/models.py +102 -0
- batho/bridge/registry_client.py +221 -0
- batho/bsg/__init__.py +31 -0
- batho/bsg/plugins/foundation/bsg_detection_cicd.yaml +87 -0
- batho/bsg/plugins/foundation/bsg_detection_cloud_providers.yaml +100 -0
- batho/bsg/plugins/foundation/bsg_detection_cpp.yaml +110 -0
- batho/bsg/plugins/foundation/bsg_detection_csharp.yaml +130 -0
- batho/bsg/plugins/foundation/bsg_detection_dart.yaml +89 -0
- batho/bsg/plugins/foundation/bsg_detection_elixir.yaml +92 -0
- batho/bsg/plugins/foundation/bsg_detection_foundation.yaml +268 -0
- batho/bsg/plugins/foundation/bsg_detection_kotlin.yaml +79 -0
- batho/bsg/plugins/foundation/bsg_detection_php.yaml +135 -0
- batho/bsg/plugins/foundation/bsg_detection_ruby.yaml +100 -0
- batho/bsg/plugins/foundation/bsg_detection_scala.yaml +93 -0
- batho/bsg/plugins/foundation/bsg_detection_swift.yaml +119 -0
- batho/bsg/plugins/foundation/bsg_detection_test_frameworks.yaml +130 -0
- batho/bsg/plugins/foundation/bsg_file_categorization.yaml +320 -0
- batho/bsg/plugins/foundation/bsg_framework_angular.yaml +40 -0
- batho/bsg/plugins/foundation/bsg_framework_django.yaml +44 -0
- batho/bsg/plugins/foundation/bsg_framework_flask.yaml +44 -0
- batho/bsg/plugins/foundation/bsg_framework_nodejs.yaml +378 -0
- batho/bsg/plugins/foundation/bsg_framework_other.yaml +278 -0
- batho/bsg/plugins/foundation/bsg_framework_python.yaml +249 -0
- batho/bsg/plugins/foundation/bsg_framework_react.yaml +92 -0
- batho/bsg/plugins/foundation/bsg_framework_vue.yaml +58 -0
- batho/bsg/plugins/foundation/bsg_graph_foundation.yaml +109 -0
- batho/bsg/plugins/foundation/bsg_token_optimization.yaml +184 -0
- batho/bsg/plugins/interceptors/bsg_api_contract_guardian.yaml +27 -0
- batho/bsg/plugins/interceptors/bsg_auth_boundary_shield.yaml +29 -0
- batho/bsg/plugins/interceptors/bsg_dependency_blast_radius.yaml +28 -0
- batho/bsg/plugins/interceptors/bsg_hardcoded_secret_catcher.yaml +30 -0
- batho/bsg/plugins/interceptors/bsg_iac_drift_sentinel.yaml +29 -0
- batho/bsg/plugins/interceptors/bsg_nplus1_query_catcher.yaml +29 -0
- batho/bsg/plugins/interceptors/bsg_resource_leak_preventer.yaml +27 -0
- batho/bsg/plugins/interceptors/bsg_schema_migration_enforcer.yaml +23 -0
- batho/bsg/plugins/interceptors/bsg_silent_failure_catcher.yaml +23 -0
- batho/bsg/plugins_cli.py +263 -0
- batho/bsg/rules.py +3268 -0
- batho/bsg/schemas/bsg-plugin-schema-v1.json +429 -0
- batho/bsg/testing.py +519 -0
- batho/cli/bridge.py +295 -0
- batho/cli/dashboard.py +280 -0
- batho/cloud_sync/__init__.py +11 -0
- batho/cloud_sync/client.py +358 -0
- batho/cloud_sync/config.py +55 -0
- batho/cloud_sync/uploader.py +195 -0
- batho/config.py +1021 -0
- batho/context/__init__.py +22 -0
- batho/context/bsg.py +8 -0
- batho/context/bsg_map.py +1835 -0
- batho/context/cache.py +473 -0
- batho/context/codegraph.py +1179 -0
- batho/context/extractor.py +1004 -0
- batho/context/graph_cache.py +99 -0
- batho/context/incremental.py +198 -0
- batho/context/languages/__init__.py +148 -0
- batho/context/languages/_common.py +271 -0
- batho/context/languages/bash.py +72 -0
- batho/context/languages/c.py +61 -0
- batho/context/languages/cpp.py +87 -0
- batho/context/languages/csharp.py +86 -0
- batho/context/languages/css.py +242 -0
- batho/context/languages/dart.py +84 -0
- batho/context/languages/detector.py +578 -0
- batho/context/languages/erlang.py +76 -0
- batho/context/languages/factory.py +500 -0
- batho/context/languages/go.py +67 -0
- batho/context/languages/hack.py +83 -0
- batho/context/languages/haskell.py +87 -0
- batho/context/languages/hcl.py +357 -0
- batho/context/languages/html.py +225 -0
- batho/context/languages/java.py +69 -0
- batho/context/languages/javascript.py +78 -0
- batho/context/languages/json.py +243 -0
- batho/context/languages/julia.py +83 -0
- batho/context/languages/kotlin.py +76 -0
- batho/context/languages/lua.py +70 -0
- batho/context/languages/markdown.py +357 -0
- batho/context/languages/objectivec.py +107 -0
- batho/context/languages/ocaml.py +87 -0
- batho/context/languages/perl.py +75 -0
- batho/context/languages/php.py +74 -0
- batho/context/languages/python.py +106 -0
- batho/context/languages/r.py +63 -0
- batho/context/languages/registry.py +635 -0
- batho/context/languages/ruby.py +67 -0
- batho/context/languages/rust.py +75 -0
- batho/context/languages/scala.py +99 -0
- batho/context/languages/swift.py +94 -0
- batho/context/languages/toml.py +262 -0
- batho/context/languages/typescript.py +88 -0
- batho/context/languages/verilog.py +100 -0
- batho/context/languages/yaml.py +285 -0
- batho/context/languages/zig.py +88 -0
- batho/context/mmap_storage.py +63 -0
- batho/context/pipeline.py +381 -0
- batho/context/query.py +313 -0
- batho/context/schema.py +227 -0
- batho/context/storage.py +1727 -0
- batho/context/symbol_index.py +124 -0
- batho/dashboard/DESIGN.md +8 -0
- batho/dashboard/README.md +51 -0
- batho/dashboard/assets/css/animations.css +35 -0
- batho/dashboard/assets/css/base.css +139 -0
- batho/dashboard/assets/css/components.css +326 -0
- batho/dashboard/assets/css/graph.css +267 -0
- batho/dashboard/assets/css/tokens.css +119 -0
- batho/dashboard/assets/fonts/inter-500.woff2 +11 -0
- batho/dashboard/assets/fonts/inter-500.woff2.LICENSE +2 -0
- batho/dashboard/assets/fonts/inter-600.woff2 +11 -0
- batho/dashboard/assets/fonts/space-grotesk-500.woff2 +11 -0
- batho/dashboard/assets/fonts/space-grotesk-500.woff2.LICENSE +2 -0
- batho/dashboard/assets/fonts/space-grotesk-700.woff2 +11 -0
- batho/dashboard/assets/js/ctn-loader.js +424 -0
- batho/dashboard/assets/js/cy-import.js +34 -0
- batho/dashboard/assets/js/cy-stylesheet.js +156 -0
- batho/dashboard/assets/js/format.js +60 -0
- batho/dashboard/assets/js/glob.js +94 -0
- batho/dashboard/assets/js/graph-stream.js +158 -0
- batho/dashboard/assets/js/main.js +175 -0
- batho/dashboard/assets/js/router.js +101 -0
- batho/dashboard/assets/js/store.js +53 -0
- batho/dashboard/index.html +88 -0
- batho/dashboard/pages/files.js +275 -0
- batho/dashboard/pages/hypergraph.js +564 -0
- batho/dashboard/pages/metrics.js +6 -0
- batho/dashboard/pages/overview.js +752 -0
- batho/dashboard/pages/relationships.js +192 -0
- batho/dashboard/pages/rules.js +6 -0
- batho/dashboard/pages/search.js +6 -0
- batho/dashboard/pages/snapshots.js +234 -0
- batho/dashboard/shared/components/audit-export.js +127 -0
- batho/dashboard/shared/components/chip-filter.js +67 -0
- batho/dashboard/shared/components/code-pane.js +47 -0
- batho/dashboard/shared/components/data-table.js +206 -0
- batho/dashboard/shared/components/drawer.js +134 -0
- batho/dashboard/shared/components/entity-chip.js +75 -0
- batho/dashboard/shared/components/header-bar.js +230 -0
- batho/dashboard/shared/components/kpi-row.js +28 -0
- batho/dashboard/shared/components/latency-bar.js +69 -0
- batho/dashboard/shared/components/side-rail.js +73 -0
- batho/dashboard/shared/components/staleness-gauge.js +69 -0
- batho/dashboard/shared/components/stat-tile.js +41 -0
- batho/dashboard/shared/components/tab-bar.js +70 -0
- batho/dashboard/vendor/cytoscape/LICENSE +19 -0
- batho/dashboard/vendor/cytoscape/VERSION.txt +1 -0
- batho/dashboard/vendor/cytoscape/cytoscape.min.js +32 -0
- batho/dashboard/vendor/marked/LICENSE +1 -0
- batho/dashboard/vendor/marked/VERSION.txt +1 -0
- batho/dashboard/vendor/marked/marked.min.js +6 -0
- batho/hooks/__init__.py +43 -0
- batho/hooks/bootstrap.py +18 -0
- batho/hooks/constants.py +97 -0
- batho/hooks/installer.py +159 -0
- batho/hooks/loader.py +63 -0
- batho/hooks/models.py +78 -0
- batho/hooks/planner.py +128 -0
- batho/hooks/runner.py +147 -0
- batho/synthesizer.py +249 -0
- batho/time_machine.py +1667 -0
- batho/utils/__init__.py +54 -0
- batho/utils/cli_output.py +165 -0
- batho/utils/dependencies.py +618 -0
- batho/utils/encoding.py +106 -0
- batho/utils/file_io.py +194 -0
- batho/utils/file_lock.py +294 -0
- batho/utils/hash.py +214 -0
- batho/utils/ignore.py +379 -0
- batho/utils/logging.py +160 -0
- batho/utils/memory_monitor.py +304 -0
- batho/utils/patch_errors.py +279 -0
- batho/utils/path_sanitizer.py +237 -0
- batho-0.1.4.dist-info/METADATA +1378 -0
- batho-0.1.4.dist-info/RECORD +184 -0
- batho-0.1.4.dist-info/WHEEL +4 -0
- batho-0.1.4.dist-info/entry_points.txt +2 -0
- batho-0.1.4.dist-info/licenses/LICENSE +201 -0
- batho_cli.py +3922 -0
batho/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Batho Core - Code analysis and indexing library.
|
|
3
|
+
|
|
4
|
+
This package provides the core functionality for analyzing code repositories,
|
|
5
|
+
building dependency graphs, and generating contextual information for LLMs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.3"
|
|
9
|
+
|
|
10
|
+
from batho.config import get_config_cached, reload_config
|
|
11
|
+
from batho.context.bsg_map import BSGMap
|
|
12
|
+
|
|
13
|
+
# Import and re-export public APIs from submodules
|
|
14
|
+
from batho.context.codegraph import CodeGraphIndexer, InMemoryGraph
|
|
15
|
+
from batho.context.incremental import get_changed_file_status_since
|
|
16
|
+
from batho.context.query import QueryService
|
|
17
|
+
from batho.time_machine import (
|
|
18
|
+
FileChange,
|
|
19
|
+
FileChangeSummary,
|
|
20
|
+
FileChangeTracker,
|
|
21
|
+
FileChangeType,
|
|
22
|
+
create_snapshot,
|
|
23
|
+
diff_snapshots,
|
|
24
|
+
incremental_patch,
|
|
25
|
+
list_snapshots,
|
|
26
|
+
load_snapshot,
|
|
27
|
+
)
|
|
28
|
+
from batho.utils.logging import get_logger
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
# Core indexing
|
|
32
|
+
"CodeGraphIndexer",
|
|
33
|
+
"InMemoryGraph",
|
|
34
|
+
# Query service
|
|
35
|
+
"QueryService",
|
|
36
|
+
# BSG rendering
|
|
37
|
+
"BSGMap",
|
|
38
|
+
# Incremental
|
|
39
|
+
"get_changed_file_status_since",
|
|
40
|
+
# Time machine
|
|
41
|
+
"create_snapshot",
|
|
42
|
+
"diff_snapshots",
|
|
43
|
+
"FileChange",
|
|
44
|
+
"FileChangeSummary",
|
|
45
|
+
"FileChangeTracker",
|
|
46
|
+
"FileChangeType",
|
|
47
|
+
"incremental_patch",
|
|
48
|
+
"list_snapshots",
|
|
49
|
+
"load_snapshot",
|
|
50
|
+
# Config
|
|
51
|
+
"get_config_cached",
|
|
52
|
+
"reload_config",
|
|
53
|
+
# Logging
|
|
54
|
+
"get_logger",
|
|
55
|
+
]
|
batho/bridge/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Batho bridge — access .ctn artifacts via registry, REST, and MCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from batho.bridge.artifact_loader import (
|
|
6
|
+
ArtifactContent,
|
|
7
|
+
ArtifactLoader,
|
|
8
|
+
ArtifactNotFoundError,
|
|
9
|
+
ArtifactParseError,
|
|
10
|
+
ChecksumMismatchError,
|
|
11
|
+
)
|
|
12
|
+
from batho.bridge.http_api import BridgeAPIHandler, create_bridge_server
|
|
13
|
+
from batho.bridge.mcp_server import create_mcp_server, run_mcp_sse, run_mcp_stdio
|
|
14
|
+
from batho.bridge.models import (
|
|
15
|
+
ArtifactRecord,
|
|
16
|
+
BridgeErrorResponse,
|
|
17
|
+
BridgeResponse,
|
|
18
|
+
IndexEntry,
|
|
19
|
+
RegistryStats,
|
|
20
|
+
)
|
|
21
|
+
from batho.bridge.registry_client import ArtifactRegistryBridge
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ArtifactContent",
|
|
27
|
+
"ArtifactLoader",
|
|
28
|
+
"ArtifactNotFoundError",
|
|
29
|
+
"ArtifactParseError",
|
|
30
|
+
"ArtifactRecord",
|
|
31
|
+
"ArtifactRegistryBridge",
|
|
32
|
+
"BridgeAPIHandler",
|
|
33
|
+
"BridgeErrorResponse",
|
|
34
|
+
"BridgeResponse",
|
|
35
|
+
"ChecksumMismatchError",
|
|
36
|
+
"IndexEntry",
|
|
37
|
+
"RegistryStats",
|
|
38
|
+
"create_bridge_server",
|
|
39
|
+
"create_mcp_server",
|
|
40
|
+
"run_mcp_sse",
|
|
41
|
+
"run_mcp_stdio",
|
|
42
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Artifact loader — resolves paths and loads JSON content from .ctn artifacts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from batho.bridge.constants import DEFAULT_PATH_PATTERNS, INDEX_SCOPED_TYPES
|
|
12
|
+
from batho.bridge.models import ArtifactRecord
|
|
13
|
+
from batho.bridge.registry_client import ArtifactRegistryBridge
|
|
14
|
+
from batho.utils.hash import compute_file_hash
|
|
15
|
+
from batho.utils.logging import get_logger
|
|
16
|
+
|
|
17
|
+
LOGGER = get_logger(__name__, component="bridge")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ArtifactNotFoundError(Exception):
|
|
21
|
+
"""Raised when an artifact cannot be resolved on disk."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, artifact_type: str, resolved_path: str) -> None:
|
|
24
|
+
super().__init__(f"Artifact not found: {artifact_type} at {resolved_path}")
|
|
25
|
+
self.artifact_type = artifact_type
|
|
26
|
+
self.resolved_path = resolved_path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ChecksumMismatchError(Exception):
|
|
30
|
+
"""Raised when the on-disk checksum does not match the registry."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, artifact_type: str, expected: str, actual: str) -> None:
|
|
33
|
+
super().__init__(
|
|
34
|
+
f"Checksum mismatch for {artifact_type}: expected {expected}, got {actual}"
|
|
35
|
+
)
|
|
36
|
+
self.artifact_type = artifact_type
|
|
37
|
+
self.expected = expected
|
|
38
|
+
self.actual = actual
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ArtifactParseError(Exception):
|
|
42
|
+
"""Raised when JSON parsing fails."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, artifact_type: str, path: str, message: str) -> None:
|
|
45
|
+
super().__init__(f"Parse error in {artifact_type} ({path}): {message}")
|
|
46
|
+
self.artifact_type = artifact_type
|
|
47
|
+
self.path = path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class ArtifactContent:
|
|
52
|
+
"""Loaded artifact with metadata."""
|
|
53
|
+
|
|
54
|
+
record: ArtifactRecord
|
|
55
|
+
data: dict[str, Any]
|
|
56
|
+
resolved_path: str
|
|
57
|
+
checksum_verified: bool
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ArtifactLoader:
|
|
61
|
+
"""Resolves artifact paths and loads JSON content with fallbacks."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, ctn_dir: Path) -> None:
|
|
64
|
+
self.ctn_dir = ctn_dir.resolve()
|
|
65
|
+
self._bridge = ArtifactRegistryBridge(self.ctn_dir)
|
|
66
|
+
|
|
67
|
+
def _resolve_index_id(self) -> str | None:
|
|
68
|
+
"""Return the current index_id from index.json, if any."""
|
|
69
|
+
latest = self._bridge.get_latest_index()
|
|
70
|
+
return latest.index_id if latest else None
|
|
71
|
+
|
|
72
|
+
def _resolve_path(self, artifact_type: str, index_id: str | None = None) -> Path | None:
|
|
73
|
+
"""Resolve the filesystem path for an artifact type.
|
|
74
|
+
|
|
75
|
+
Resolution order:
|
|
76
|
+
1. Registry lookup by type (most recent active artifact).
|
|
77
|
+
2. Default pattern using provided or current index_id.
|
|
78
|
+
3. index.json ``outputs`` map lookup.
|
|
79
|
+
"""
|
|
80
|
+
# 1. Registry lookup
|
|
81
|
+
candidates = self._bridge.get_artifacts_by_type(artifact_type, limit=1)
|
|
82
|
+
if candidates:
|
|
83
|
+
record = candidates[0]
|
|
84
|
+
phys = Path(record.physical_path)
|
|
85
|
+
if phys.exists():
|
|
86
|
+
return phys
|
|
87
|
+
logical = self.ctn_dir / record.logical_path
|
|
88
|
+
if logical.exists():
|
|
89
|
+
return logical
|
|
90
|
+
|
|
91
|
+
# 2. Default pattern
|
|
92
|
+
pattern = DEFAULT_PATH_PATTERNS.get(artifact_type)
|
|
93
|
+
if pattern:
|
|
94
|
+
if artifact_type in INDEX_SCOPED_TYPES:
|
|
95
|
+
idx = index_id or self._resolve_index_id()
|
|
96
|
+
if idx:
|
|
97
|
+
resolved = self.ctn_dir / pattern.format(index_id=idx)
|
|
98
|
+
if resolved.exists():
|
|
99
|
+
return resolved
|
|
100
|
+
else:
|
|
101
|
+
resolved = self.ctn_dir / pattern
|
|
102
|
+
if resolved.exists():
|
|
103
|
+
return resolved
|
|
104
|
+
|
|
105
|
+
# 3. index.json outputs map
|
|
106
|
+
latest = self._bridge.get_latest_index()
|
|
107
|
+
if latest and latest.outputs:
|
|
108
|
+
output_key = _artifact_type_to_output_key(artifact_type)
|
|
109
|
+
if output_key:
|
|
110
|
+
rel_path = latest.outputs.get(output_key)
|
|
111
|
+
if rel_path:
|
|
112
|
+
candidate = (self.ctn_dir.parent / rel_path).resolve()
|
|
113
|
+
if candidate.exists():
|
|
114
|
+
return candidate
|
|
115
|
+
# Try relative to ctn_dir itself
|
|
116
|
+
candidate2 = self.ctn_dir / rel_path
|
|
117
|
+
if candidate2.exists():
|
|
118
|
+
return candidate2
|
|
119
|
+
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def load_json(
|
|
123
|
+
self,
|
|
124
|
+
artifact_type: str,
|
|
125
|
+
*,
|
|
126
|
+
index_id: str | None = None,
|
|
127
|
+
verify_checksum: bool = True,
|
|
128
|
+
) -> dict[str, Any]:
|
|
129
|
+
"""Load and return the JSON content for an artifact type.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
ArtifactNotFoundError: if no file can be resolved.
|
|
133
|
+
ChecksumMismatchError: if checksum verification fails.
|
|
134
|
+
ArtifactParseError: if JSON parsing fails.
|
|
135
|
+
"""
|
|
136
|
+
path = self._resolve_path(artifact_type, index_id=index_id)
|
|
137
|
+
if path is None or not path.exists():
|
|
138
|
+
raise ArtifactNotFoundError(artifact_type, str(path) if path else "<unresolved>")
|
|
139
|
+
|
|
140
|
+
# Checksum verification
|
|
141
|
+
record: ArtifactRecord | None = None
|
|
142
|
+
candidates = self._bridge.get_artifacts_by_type(artifact_type, limit=1)
|
|
143
|
+
if candidates:
|
|
144
|
+
record = candidates[0]
|
|
145
|
+
|
|
146
|
+
checksum_verified = False
|
|
147
|
+
if verify_checksum and record and record.checksum:
|
|
148
|
+
actual = compute_file_hash(path) or ""
|
|
149
|
+
if actual and actual != record.checksum:
|
|
150
|
+
raise ChecksumMismatchError(artifact_type, record.checksum, actual)
|
|
151
|
+
checksum_verified = actual == record.checksum
|
|
152
|
+
|
|
153
|
+
# Load JSON
|
|
154
|
+
try:
|
|
155
|
+
raw = path.read_text(encoding="utf-8")
|
|
156
|
+
data: dict[str, Any] = json.loads(raw)
|
|
157
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
158
|
+
raise ArtifactParseError(artifact_type, str(path), str(exc))
|
|
159
|
+
|
|
160
|
+
if not isinstance(data, dict):
|
|
161
|
+
raise ArtifactParseError(artifact_type, str(path), "top-level value is not an object")
|
|
162
|
+
|
|
163
|
+
return data
|
|
164
|
+
|
|
165
|
+
def load_artifact(self, record: ArtifactRecord, *, verify_checksum: bool = True) -> ArtifactContent:
|
|
166
|
+
"""Load a specific artifact by its registry record."""
|
|
167
|
+
path = Path(record.physical_path)
|
|
168
|
+
if not path.exists():
|
|
169
|
+
path = self.ctn_dir / record.logical_path
|
|
170
|
+
if not path.exists():
|
|
171
|
+
raise ArtifactNotFoundError(record.artifact_type, str(record.physical_path))
|
|
172
|
+
|
|
173
|
+
checksum_verified = False
|
|
174
|
+
if verify_checksum and record.checksum:
|
|
175
|
+
actual = compute_file_hash(path) or ""
|
|
176
|
+
if actual and actual != record.checksum:
|
|
177
|
+
raise ChecksumMismatchError(record.artifact_type, record.checksum, actual)
|
|
178
|
+
checksum_verified = actual == record.checksum
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
raw = path.read_text(encoding="utf-8")
|
|
182
|
+
data = json.loads(raw)
|
|
183
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
184
|
+
raise ArtifactParseError(record.artifact_type, str(path), str(exc))
|
|
185
|
+
|
|
186
|
+
if not isinstance(data, dict):
|
|
187
|
+
raise ArtifactParseError(record.artifact_type, str(path), "top-level value is not an object")
|
|
188
|
+
|
|
189
|
+
return ArtifactContent(
|
|
190
|
+
record=record,
|
|
191
|
+
data=data,
|
|
192
|
+
resolved_path=str(path),
|
|
193
|
+
checksum_verified=checksum_verified,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _artifact_type_to_output_key(artifact_type: str) -> str | None:
|
|
198
|
+
"""Map artifact types to keys used in index.json ``outputs``."""
|
|
199
|
+
mapping = {
|
|
200
|
+
"graph_json": "graph_json",
|
|
201
|
+
"bsg_json": "bsg_json",
|
|
202
|
+
"context_overview_json": "overview_json",
|
|
203
|
+
"context_files_json": "files_json",
|
|
204
|
+
}
|
|
205
|
+
return mapping.get(artifact_type)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
__all__ = [
|
|
209
|
+
"ArtifactLoader",
|
|
210
|
+
"ArtifactContent",
|
|
211
|
+
"ArtifactNotFoundError",
|
|
212
|
+
"ChecksumMismatchError",
|
|
213
|
+
"ArtifactParseError",
|
|
214
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Artifact type constants and path mappings for the Batho bridge."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
# Artifact types that the bridge can load as JSON
|
|
6
|
+
KNOWN_ARTIFACT_TYPES: set[str] = {
|
|
7
|
+
"interception_stats_json",
|
|
8
|
+
"graph_json",
|
|
9
|
+
"bsg_json",
|
|
10
|
+
"context_overview_json",
|
|
11
|
+
"context_files_json",
|
|
12
|
+
"snapshot_json",
|
|
13
|
+
"index_metadata",
|
|
14
|
+
"metrics_json",
|
|
15
|
+
"file_hashes_json",
|
|
16
|
+
"patches_index",
|
|
17
|
+
"patch_detail",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Default logical path patterns relative to .ctn/ for each artifact type.
|
|
21
|
+
# The {index_id} placeholder is resolved from the current index.
|
|
22
|
+
DEFAULT_PATH_PATTERNS: dict[str, str] = {
|
|
23
|
+
"interception_stats_json": "local/metrics/interception_stats.json",
|
|
24
|
+
"graph_json": "{index_id}/graph.json",
|
|
25
|
+
"bsg_json": "{index_id}/bsg.json",
|
|
26
|
+
"context_overview_json": "{index_id}/context/json/overview.json",
|
|
27
|
+
"context_files_json": "{index_id}/context/json/files.json",
|
|
28
|
+
"snapshot_json": "snapshots/{name}.json",
|
|
29
|
+
"index_metadata": "index.json",
|
|
30
|
+
"metrics_json": "local/metrics/metrics.json",
|
|
31
|
+
"file_hashes_json": "local/state/file_hashes.json",
|
|
32
|
+
"patches_index": "patches/index.json",
|
|
33
|
+
"patch_detail": "patches/patch_{operation_id}.json",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Artifact types that require an index_id for path resolution
|
|
37
|
+
INDEX_SCOPED_TYPES: set[str] = {
|
|
38
|
+
"graph_json",
|
|
39
|
+
"bsg_json",
|
|
40
|
+
"context_overview_json",
|
|
41
|
+
"context_files_json",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Registry column names exposed by the bridge API
|
|
45
|
+
ARTIFACT_RECORD_FIELDS: tuple[str, ...] = (
|
|
46
|
+
"artifact_id",
|
|
47
|
+
"content_id",
|
|
48
|
+
"artifact_type",
|
|
49
|
+
"logical_path",
|
|
50
|
+
"physical_path",
|
|
51
|
+
"checksum",
|
|
52
|
+
"size_bytes",
|
|
53
|
+
"schema_version",
|
|
54
|
+
"producer",
|
|
55
|
+
"run_id",
|
|
56
|
+
"sync_status",
|
|
57
|
+
"cloud_content_id",
|
|
58
|
+
"last_sync_at",
|
|
59
|
+
"sync_error",
|
|
60
|
+
"retry_count",
|
|
61
|
+
"retention_class",
|
|
62
|
+
"metadata",
|
|
63
|
+
"created_at",
|
|
64
|
+
"updated_at",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
DEFAULT_BRIDGE_HTTP_PORT = 8766
|
|
68
|
+
DEFAULT_MCP_SSE_PORT = 8767
|
|
69
|
+
DEFAULT_PAGE_LIMIT = 200
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
"KNOWN_ARTIFACT_TYPES",
|
|
73
|
+
"DEFAULT_PATH_PATTERNS",
|
|
74
|
+
"INDEX_SCOPED_TYPES",
|
|
75
|
+
"ARTIFACT_RECORD_FIELDS",
|
|
76
|
+
"DEFAULT_BRIDGE_HTTP_PORT",
|
|
77
|
+
"DEFAULT_MCP_SSE_PORT",
|
|
78
|
+
"DEFAULT_PAGE_LIMIT",
|
|
79
|
+
]
|