attocode-code-intel 0.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.
- attocode_code_intel-0.1.0.dist-info/METADATA +221 -0
- attocode_code_intel-0.1.0.dist-info/RECORD +345 -0
- attocode_code_intel-0.1.0.dist-info/WHEEL +4 -0
- attocode_code_intel-0.1.0.dist-info/entry_points.txt +2 -0
- attocode_code_intel-0.1.0.dist-info/licenses/LICENSE +21 -0
- attocode_intel/GUIDELINES.md +432 -0
- attocode_intel/__init__.py +52 -0
- attocode_intel/_internal/__init__.py +1 -0
- attocode_intel/_internal/errors/__init__.py +126 -0
- attocode_intel/_internal/integrations/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/__init__.py +1 -0
- attocode_intel/_internal/integrations/context/architecture_drift.py +354 -0
- attocode_intel/_internal/integrations/context/ast_chunker.py +202 -0
- attocode_intel/_internal/integrations/context/ast_service.py +1483 -0
- attocode_intel/_internal/integrations/context/blob_oid.py +423 -0
- attocode_intel/_internal/integrations/context/cache_manifest.py +221 -0
- attocode_intel/_internal/integrations/context/cas.py +466 -0
- attocode_intel/_internal/integrations/context/code_analyzer.py +528 -0
- attocode_intel/_internal/integrations/context/codebase_ast.py +1461 -0
- attocode_intel/_internal/integrations/context/codebase_context.py +1736 -0
- attocode_intel/_internal/integrations/context/cross_mode.py +263 -0
- attocode_intel/_internal/integrations/context/cross_references.py +616 -0
- attocode_intel/_internal/integrations/context/embedding_rotation.py +597 -0
- attocode_intel/_internal/integrations/context/embeddings.py +282 -0
- attocode_intel/_internal/integrations/context/frecency.py +427 -0
- attocode_intel/_internal/integrations/context/fuzzy.py +320 -0
- attocode_intel/_internal/integrations/context/hierarchical_explorer.py +331 -0
- attocode_intel/_internal/integrations/context/hydration.py +90 -0
- attocode_intel/_internal/integrations/context/index_repair.py +87 -0
- attocode_intel/_internal/integrations/context/index_store.py +586 -0
- attocode_intel/_internal/integrations/context/memory_store.py +602 -0
- attocode_intel/_internal/integrations/context/module_resolution.py +52 -0
- attocode_intel/_internal/integrations/context/query_constraints.py +361 -0
- attocode_intel/_internal/integrations/context/query_history.py +417 -0
- attocode_intel/_internal/integrations/context/reranker.py +351 -0
- attocode_intel/_internal/integrations/context/semantic_search.py +2931 -0
- attocode_intel/_internal/integrations/context/syntax_evidence.py +94 -0
- attocode_intel/_internal/integrations/context/systemone_ranker.py +245 -0
- attocode_intel/_internal/integrations/context/temporal_coupling.py +411 -0
- attocode_intel/_internal/integrations/context/trigram_index.py +820 -0
- attocode_intel/_internal/integrations/context/trigram_regex.py +171 -0
- attocode_intel/_internal/integrations/context/ts_parser.py +2199 -0
- attocode_intel/_internal/integrations/context/vector_store.py +795 -0
- attocode_intel/_internal/integrations/feature_flags/__init__.py +357 -0
- attocode_intel/_internal/integrations/lsp/__init__.py +1 -0
- attocode_intel/_internal/integrations/lsp/client.py +1140 -0
- attocode_intel/_internal/integrations/mcp/__init__.py +1 -0
- attocode_intel/_internal/integrations/mcp/client.py +253 -0
- attocode_intel/_internal/integrations/security/__init__.py +1 -0
- attocode_intel/_internal/integrations/security/custom_rules.py +176 -0
- attocode_intel/_internal/integrations/security/dataflow.py +522 -0
- attocode_intel/_internal/integrations/security/dependency_audit.py +303 -0
- attocode_intel/_internal/integrations/security/matcher.py +44 -0
- attocode_intel/_internal/integrations/security/patterns.py +724 -0
- attocode_intel/_internal/integrations/security/scanner.py +351 -0
- attocode_intel/_internal/integrations/utilities/__init__.py +1 -0
- attocode_intel/_internal/integrations/utilities/ignore.py +64 -0
- attocode_intel/_internal/integrations/utilities/token_estimate.py +51 -0
- attocode_intel/_internal/tricks/__init__.py +1 -0
- attocode_intel/_internal/tricks/recursive_context.py +357 -0
- attocode_intel/_internal/types/__init__.py +1 -0
- attocode_intel/_internal/types/events.py +289 -0
- attocode_intel/_internal/types/severity.py +15 -0
- attocode_intel/_shared.py +341 -0
- attocode_intel/analysis_coverage.py +56 -0
- attocode_intel/api/__init__.py +1 -0
- attocode_intel/api/access.py +32 -0
- attocode_intel/api/app.py +247 -0
- attocode_intel/api/auth/__init__.py +128 -0
- attocode_intel/api/auth/api_keys.py +61 -0
- attocode_intel/api/auth/context.py +34 -0
- attocode_intel/api/auth/jwt.py +66 -0
- attocode_intel/api/auth/oauth.py +146 -0
- attocode_intel/api/auth/passwords.py +15 -0
- attocode_intel/api/deps.py +351 -0
- attocode_intel/api/knowledge_bridge.py +13 -0
- attocode_intel/api/mcp_transport.py +51 -0
- attocode_intel/api/middleware.py +411 -0
- attocode_intel/api/models.py +698 -0
- attocode_intel/api/providers/__init__.py +1 -0
- attocode_intel/api/providers/db_provider.py +1106 -0
- attocode_intel/api/providers/local_provider.py +476 -0
- attocode_intel/api/providers/remote_provider.py +740 -0
- attocode_intel/api/repository_source.py +21 -0
- attocode_intel/api/routes/__init__.py +1 -0
- attocode_intel/api/routes/_pin_helper.py +158 -0
- attocode_intel/api/routes/activity.py +148 -0
- attocode_intel/api/routes/adr.py +102 -0
- attocode_intel/api/routes/analysis.py +515 -0
- attocode_intel/api/routes/api_keys.py +170 -0
- attocode_intel/api/routes/auth.py +428 -0
- attocode_intel/api/routes/branches.py +262 -0
- attocode_intel/api/routes/cross_repo_search.py +198 -0
- attocode_intel/api/routes/embeddings.py +386 -0
- attocode_intel/api/routes/files.py +132 -0
- attocode_intel/api/routes/files_v2.py +388 -0
- attocode_intel/api/routes/gc.py +322 -0
- attocode_intel/api/routes/git_v2.py +433 -0
- attocode_intel/api/routes/graph.py +174 -0
- attocode_intel/api/routes/graph_viz.py +160 -0
- attocode_intel/api/routes/health.py +62 -0
- attocode_intel/api/routes/history.py +175 -0
- attocode_intel/api/routes/jobs.py +84 -0
- attocode_intel/api/routes/learning.py +205 -0
- attocode_intel/api/routes/lsp.py +272 -0
- attocode_intel/api/routes/notify.py +623 -0
- attocode_intel/api/routes/operations.py +27 -0
- attocode_intel/api/routes/orgs.py +924 -0
- attocode_intel/api/routes/preferences.py +100 -0
- attocode_intel/api/routes/presence.py +42 -0
- attocode_intel/api/routes/projects.py +116 -0
- attocode_intel/api/routes/repos.py +47 -0
- attocode_intel/api/routes/search.py +180 -0
- attocode_intel/api/routes/snapshots.py +614 -0
- attocode_intel/api/routes/webhooks.py +313 -0
- attocode_intel/api/routes/websocket.py +116 -0
- attocode_intel/api/static/graph.html +239 -0
- attocode_intel/api/utils.py +96 -0
- attocode_intel/artifacts/__init__.py +45 -0
- attocode_intel/artifacts/hashing.py +230 -0
- attocode_intel/artifacts/provenance.py +190 -0
- attocode_intel/artifacts/retrieval_pin.py +159 -0
- attocode_intel/audit.py +59 -0
- attocode_intel/bug_finder.py +206 -0
- attocode_intel/bundle.py +80 -0
- attocode_intel/catalog.py +242 -0
- attocode_intel/change_review.py +75 -0
- attocode_intel/cli.py +3062 -0
- attocode_intel/community.py +63 -0
- attocode_intel/confidence/__init__.py +144 -0
- attocode_intel/confidence/jev.py +234 -0
- attocode_intel/confidence/llm.py +330 -0
- attocode_intel/confidence/redact.py +44 -0
- attocode_intel/confidence/settings.py +74 -0
- attocode_intel/config.py +256 -0
- attocode_intel/crypto.py +31 -0
- attocode_intel/db/__init__.py +7 -0
- attocode_intel/db/base.py +33 -0
- attocode_intel/db/engine.py +61 -0
- attocode_intel/db/models.py +529 -0
- attocode_intel/entrypoint.py +185 -0
- attocode_intel/focused_evidence.py +130 -0
- attocode_intel/freshness.py +154 -0
- attocode_intel/gateway.py +734 -0
- attocode_intel/git/__init__.py +7 -0
- attocode_intel/git/credentials.py +75 -0
- attocode_intel/git/manager.py +493 -0
- attocode_intel/git/models.py +96 -0
- attocode_intel/git/storage.py +98 -0
- attocode_intel/graph_query_parser.py +761 -0
- attocode_intel/helpers.py +712 -0
- attocode_intel/indexing/__init__.py +29 -0
- attocode_intel/indexing/debouncer.py +136 -0
- attocode_intel/indexing/delta_indexer.py +151 -0
- attocode_intel/indexing/full_indexer.py +323 -0
- attocode_intel/indexing/incremental.py +240 -0
- attocode_intel/indexing/parser.py +403 -0
- attocode_intel/indexing/summarizer.py +264 -0
- attocode_intel/installer.py +1896 -0
- attocode_intel/knowledge.py +269 -0
- attocode_intel/local_knowledge.py +77 -0
- attocode_intel/migrations/alembic.ini +36 -0
- attocode_intel/migrations/env.py +71 -0
- attocode_intel/migrations/script.py.mako +27 -0
- attocode_intel/migrations/versions/001_initial_schema.py +219 -0
- attocode_intel/migrations/versions/002_branch_version_and_embeddings.py +61 -0
- attocode_intel/migrations/versions/003_embedding_uniqueness_and_cascade.py +51 -0
- attocode_intel/migrations/versions/004_audit_events.py +38 -0
- attocode_intel/migrations/versions/005_user_preferences.py +30 -0
- attocode_intel/migrations/versions/006_google_oauth.py +27 -0
- attocode_intel/migrations/versions/007_pgvector_embeddings.py +28 -0
- attocode_intel/migrations/versions/008_symbol_references.py +37 -0
- attocode_intel/migrations/versions/009_webhook_secret_encryption.py +25 -0
- attocode_intel/migrations/versions/010_add_commits_table.py +40 -0
- attocode_intel/migrations/versions/011_commit_changed_files.py +25 -0
- attocode_intel/migrations/versions/012_branch_merged_at.py +25 -0
- attocode_intel/migrations/versions/013_revoked_tokens.py +32 -0
- attocode_intel/migrations/versions/014_blame_hunks.py +40 -0
- attocode_intel/migrations/versions/015_commit_file_stats.py +40 -0
- attocode_intel/migrations/versions/016_embedding_provenance_columns.py +179 -0
- attocode_intel/migrations/versions/017_provenance_table.py +102 -0
- attocode_intel/migrations/versions/018_repo_snapshots.py +178 -0
- attocode_intel/migrations/versions/019_symref_caller.py +42 -0
- attocode_intel/migrations/versions/020_shared_knowledge.py +29 -0
- attocode_intel/onboarding.py +258 -0
- attocode_intel/output.py +178 -0
- attocode_intel/precision.py +133 -0
- attocode_intel/presence.py +160 -0
- attocode_intel/probe.py +110 -0
- attocode_intel/project_dir.py +97 -0
- attocode_intel/pubsub.py +113 -0
- attocode_intel/query_ranking.py +242 -0
- attocode_intel/readiness.py +972 -0
- attocode_intel/redis.py +51 -0
- attocode_intel/reference_pages.py +60 -0
- attocode_intel/remote.py +131 -0
- attocode_intel/repo_ranker.py +246 -0
- attocode_intel/request_context.py +82 -0
- attocode_intel/rules/__init__.py +28 -0
- attocode_intel/rules/ci.py +320 -0
- attocode_intel/rules/combinators.py +293 -0
- attocode_intel/rules/enricher.py +127 -0
- attocode_intel/rules/evolution.py +452 -0
- attocode_intel/rules/executor.py +531 -0
- attocode_intel/rules/filters/__init__.py +5 -0
- attocode_intel/rules/filters/pipeline.py +151 -0
- attocode_intel/rules/formatter.py +198 -0
- attocode_intel/rules/hygiene.py +242 -0
- attocode_intel/rules/importers/__init__.py +1 -0
- attocode_intel/rules/importers/semgrep.py +382 -0
- attocode_intel/rules/loader.py +292 -0
- attocode_intel/rules/marketplace.py +375 -0
- attocode_intel/rules/metavar.py +181 -0
- attocode_intel/rules/model.py +317 -0
- attocode_intel/rules/packs/__init__.py +1 -0
- attocode_intel/rules/packs/community/_license_templates/Apache-2.0.txt +176 -0
- attocode_intel/rules/packs/community/_license_templates/MIT.txt +21 -0
- attocode_intel/rules/packs/community/bandit-python/LICENSE +176 -0
- attocode_intel/rules/packs/community/bandit-python/NOTICE +13 -0
- attocode_intel/rules/packs/community/bandit-python/PORTING.md +23 -0
- attocode_intel/rules/packs/community/bandit-python/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B105-hardcoded-password.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B303-weak-md5.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B304-weak-des.yaml +20 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B307-use-of-eval.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B324-weak-sha1.yaml +21 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B501-no-cert-validation.yaml +23 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B602-shell-true.yaml +22 -0
- attocode_intel/rules/packs/community/bandit-python/rules/B608-sqli.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/LICENSE +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/NOTICE +13 -0
- attocode_intel/rules/packs/community/eslint-typescript/PORTING.md +23 -0
- attocode_intel/rules/packs/community/eslint-typescript/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/eqeqeq.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-eval.yaml +22 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-implied-eval.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-new-func.yaml +21 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-throw-literal.yaml +19 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-var.yaml +20 -0
- attocode_intel/rules/packs/community/eslint-typescript/rules/no-with.yaml +18 -0
- attocode_intel/rules/packs/community/gosec-go/LICENSE +176 -0
- attocode_intel/rules/packs/community/gosec-go/NOTICE +13 -0
- attocode_intel/rules/packs/community/gosec-go/PORTING.md +23 -0
- attocode_intel/rules/packs/community/gosec-go/manifest.yaml +14 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G101-hardcoded-credentials.yaml +22 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G102-bind-all-interfaces.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G103-unsafe-block.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G201-sql-format.yaml +21 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G304-file-inclusion.yaml +23 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G401-weak-cipher.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G402-tls-min-version.yaml +20 -0
- attocode_intel/rules/packs/community/gosec-go/rules/G501-md5-import.yaml +21 -0
- attocode_intel/rules/packs/examples/cpp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/cpp/rules/security.yaml +90 -0
- attocode_intel/rules/packs/examples/cpp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/csharp/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/csharp/rules/security.yaml +69 -0
- attocode_intel/rules/packs/examples/csharp/rules/structural.yaml +42 -0
- attocode_intel/rules/packs/examples/go/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/go/rules/performance.yaml +136 -0
- attocode_intel/rules/packs/examples/go/rules/security.yaml +48 -0
- attocode_intel/rules/packs/examples/go/rules/structural.yaml +58 -0
- attocode_intel/rules/packs/examples/go/taint/sanitizers.yaml +24 -0
- attocode_intel/rules/packs/examples/go/taint/sinks.yaml +51 -0
- attocode_intel/rules/packs/examples/go/taint/sources.yaml +26 -0
- attocode_intel/rules/packs/examples/java/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/java/rules/correctness.yaml +55 -0
- attocode_intel/rules/packs/examples/java/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/examples/java/taint/sinks.yaml +37 -0
- attocode_intel/rules/packs/examples/java/taint/sources.yaml +21 -0
- attocode_intel/rules/packs/examples/kotlin/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/kotlin/rules/correctness.yaml +50 -0
- attocode_intel/rules/packs/examples/kotlin/rules/structural.yaml +38 -0
- attocode_intel/rules/packs/examples/php/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/php/rules/security.yaml +66 -0
- attocode_intel/rules/packs/examples/php/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/python/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/python/rules/performance.yaml +90 -0
- attocode_intel/rules/packs/examples/python/rules/structural.yaml +55 -0
- attocode_intel/rules/packs/examples/ruby/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/ruby/rules/security.yaml +73 -0
- attocode_intel/rules/packs/examples/ruby/rules/structural.yaml +41 -0
- attocode_intel/rules/packs/examples/rust/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/rust/rules/correctness.yaml +51 -0
- attocode_intel/rules/packs/examples/rust/rules/structural.yaml +49 -0
- attocode_intel/rules/packs/examples/typescript/manifest.yaml +4 -0
- attocode_intel/rules/packs/examples/typescript/rules/performance.yaml +70 -0
- attocode_intel/rules/packs/examples/typescript/rules/structural.yaml +48 -0
- attocode_intel/rules/packs/pack_loader.py +244 -0
- attocode_intel/rules/plugins/__init__.py +1 -0
- attocode_intel/rules/plugins/plugin_loader.py +132 -0
- attocode_intel/rules/profiling.py +332 -0
- attocode_intel/rules/registry.py +213 -0
- attocode_intel/rules/sarif.py +187 -0
- attocode_intel/rules/synthesis.py +420 -0
- attocode_intel/rules/testing.py +326 -0
- attocode_intel/server.py +734 -0
- attocode_intel/service.py +3416 -0
- attocode_intel/sharing.py +57 -0
- attocode_intel/storage/__init__.py +6 -0
- attocode_intel/storage/branch_overlay.py +352 -0
- attocode_intel/storage/content_store.py +220 -0
- attocode_intel/storage/dependency_store.py +143 -0
- attocode_intel/storage/diff_engine.py +187 -0
- attocode_intel/storage/embedding_store.py +594 -0
- attocode_intel/storage/provenance_store.py +137 -0
- attocode_intel/storage/security_scanner_db.py +164 -0
- attocode_intel/storage/symbol_store.py +160 -0
- attocode_intel/structural_evidence.py +144 -0
- attocode_intel/symbol_inspection.py +141 -0
- attocode_intel/symbol_links.py +167 -0
- attocode_intel/telemetry.py +42 -0
- attocode_intel/test_ranking.py +73 -0
- attocode_intel/testing/__init__.py +43 -0
- attocode_intel/testing/fixtures.py +122 -0
- attocode_intel/testing/helpers.py +75 -0
- attocode_intel/testing/mocks.py +99 -0
- attocode_intel/tools/__init__.py +2 -0
- attocode_intel/tools/adr_tools.py +628 -0
- attocode_intel/tools/analysis_tools.py +382 -0
- attocode_intel/tools/composite_tools.py +695 -0
- attocode_intel/tools/cross_mode_tools.py +173 -0
- attocode_intel/tools/dead_code_tools.py +582 -0
- attocode_intel/tools/distill_tools.py +353 -0
- attocode_intel/tools/frecency_tools.py +220 -0
- attocode_intel/tools/fuzzy_tools.py +255 -0
- attocode_intel/tools/history_tools.py +486 -0
- attocode_intel/tools/learning_tools.py +222 -0
- attocode_intel/tools/lsp_tools.py +218 -0
- attocode_intel/tools/maintenance_tools.py +1662 -0
- attocode_intel/tools/navigation_tools.py +220 -0
- attocode_intel/tools/overlay_tools.py +540 -0
- attocode_intel/tools/pin_store.py +432 -0
- attocode_intel/tools/pin_tools.py +266 -0
- attocode_intel/tools/query_constraints_tools.py +262 -0
- attocode_intel/tools/query_history_tools.py +158 -0
- attocode_intel/tools/readiness_tools.py +44 -0
- attocode_intel/tools/rule_tools.py +992 -0
- attocode_intel/tools/search_tools.py +770 -0
- attocode_intel/tools/snapshot_tools.py +624 -0
- attocode_intel/workers/__init__.py +1 -0
- attocode_intel/workers/job_utils.py +111 -0
- attocode_intel/workers/jobs.py +573 -0
- attocode_intel/workers/run.py +30 -0
- attocode_intel/workers/settings.py +61 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Shared utilities for the HTTP API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from fastapi import HTTPException
|
|
9
|
+
from sqlalchemy import select
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
15
|
+
|
|
16
|
+
from attocode_intel.api.auth.context import AuthContext
|
|
17
|
+
from attocode_intel.db.models import Repository
|
|
18
|
+
|
|
19
|
+
# Language detection by extension
|
|
20
|
+
LANG_MAP: dict[str, str] = {
|
|
21
|
+
".py": "python", ".js": "javascript", ".ts": "typescript",
|
|
22
|
+
".tsx": "typescriptreact", ".jsx": "javascriptreact",
|
|
23
|
+
".rs": "rust", ".go": "go", ".java": "java", ".rb": "ruby",
|
|
24
|
+
".c": "c", ".cpp": "cpp", ".h": "c", ".hpp": "cpp", ".metal": "cpp",
|
|
25
|
+
".cs": "csharp", ".swift": "swift", ".kt": "kotlin",
|
|
26
|
+
".sh": "shell", ".bash": "shell", ".zsh": "shell",
|
|
27
|
+
".json": "json", ".yaml": "yaml", ".yml": "yaml",
|
|
28
|
+
".toml": "toml", ".xml": "xml", ".html": "html", ".css": "css",
|
|
29
|
+
".sql": "sql", ".md": "markdown", ".txt": "plaintext",
|
|
30
|
+
".dockerfile": "dockerfile",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Max file size for content retrieval (5 MB)
|
|
34
|
+
MAX_FILE_SIZE = 5 * 1024 * 1024
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def detect_language(path: str) -> str:
|
|
38
|
+
"""Detect language from file extension."""
|
|
39
|
+
_, ext = os.path.splitext(path)
|
|
40
|
+
ext = ext.lower()
|
|
41
|
+
if ext in LANG_MAP:
|
|
42
|
+
return LANG_MAP[ext]
|
|
43
|
+
basename = os.path.basename(path).lower()
|
|
44
|
+
if basename == "dockerfile":
|
|
45
|
+
return "dockerfile"
|
|
46
|
+
if basename == "makefile":
|
|
47
|
+
return "makefile"
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def is_binary(data: bytes) -> bool:
|
|
52
|
+
"""Heuristic binary detection: check for null bytes in first 8KB."""
|
|
53
|
+
return b"\x00" in data[:8192]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def get_repo_by_id(
|
|
57
|
+
project_id: uuid.UUID,
|
|
58
|
+
session: AsyncSession,
|
|
59
|
+
auth: AuthContext | None = None,
|
|
60
|
+
) -> Repository:
|
|
61
|
+
"""Fetch a repository by ID, enforcing optional org isolation.
|
|
62
|
+
|
|
63
|
+
Returns 404 when the repo doesn't exist, or when ``auth.org_id`` is set
|
|
64
|
+
and does not match the repo's org.
|
|
65
|
+
"""
|
|
66
|
+
from attocode_intel.db.models import Repository as _Repository
|
|
67
|
+
|
|
68
|
+
result = await session.execute(select(_Repository).where(_Repository.id == project_id))
|
|
69
|
+
repo = result.scalar_one_or_none()
|
|
70
|
+
if repo is None:
|
|
71
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
72
|
+
if auth and auth.org_id and repo.org_id != auth.org_id:
|
|
73
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
74
|
+
return repo
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def get_repo_by_org(
|
|
78
|
+
org_id: uuid.UUID,
|
|
79
|
+
repo_id: uuid.UUID,
|
|
80
|
+
session: AsyncSession,
|
|
81
|
+
) -> Repository:
|
|
82
|
+
"""Fetch a repository scoped to a specific org.
|
|
83
|
+
|
|
84
|
+
Returns 404 when the (org_id, repo_id) tuple doesn't match a repo.
|
|
85
|
+
"""
|
|
86
|
+
from attocode_intel.db.models import Repository as _Repository
|
|
87
|
+
|
|
88
|
+
result = await session.execute(
|
|
89
|
+
select(_Repository).where(
|
|
90
|
+
_Repository.id == repo_id, _Repository.org_id == org_id,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
repo = result.scalar_one_or_none()
|
|
94
|
+
if repo is None:
|
|
95
|
+
raise HTTPException(status_code=404, detail="Repository not found")
|
|
96
|
+
return repo
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Shared code-intel reproducibility primitives.
|
|
2
|
+
|
|
3
|
+
This package holds substrate shared by both the local stdio MCP side and the
|
|
4
|
+
server HTTP side:
|
|
5
|
+
|
|
6
|
+
- ``hashing``: canonical ``action_hash`` computation for derived artifacts.
|
|
7
|
+
- ``provenance``: ``Provenance`` dataclass recording how an artifact was produced.
|
|
8
|
+
- ``retrieval_pin``: ``RetrievalPin`` format + per-store manifest-hash helpers
|
|
9
|
+
used to make ranked-result tools deterministic.
|
|
10
|
+
|
|
11
|
+
Everything here is pure Python with stdlib-only imports so it can be imported
|
|
12
|
+
from either the local ``attocode.integrations.context.*`` modules or the
|
|
13
|
+
server ``attocode_intel.storage.*`` modules without pulling in SQL,
|
|
14
|
+
FastAPI, or FastMCP dependencies.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from .hashing import (
|
|
20
|
+
ARTIFACT_TYPES,
|
|
21
|
+
canonical_json,
|
|
22
|
+
compute_action_hash,
|
|
23
|
+
compute_config_digest,
|
|
24
|
+
sha256_file,
|
|
25
|
+
)
|
|
26
|
+
from .provenance import LEGACY_INDEXER_NAME, Provenance, legacy_provenance
|
|
27
|
+
from .retrieval_pin import (
|
|
28
|
+
RetrievalPin,
|
|
29
|
+
compute_store_hash,
|
|
30
|
+
make_pin_id,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"ARTIFACT_TYPES",
|
|
35
|
+
"LEGACY_INDEXER_NAME",
|
|
36
|
+
"Provenance",
|
|
37
|
+
"RetrievalPin",
|
|
38
|
+
"canonical_json",
|
|
39
|
+
"compute_action_hash",
|
|
40
|
+
"compute_config_digest",
|
|
41
|
+
"compute_store_hash",
|
|
42
|
+
"legacy_provenance",
|
|
43
|
+
"make_pin_id",
|
|
44
|
+
"sha256_file",
|
|
45
|
+
]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Canonical action-hash computation for derived code-intel artifacts.
|
|
2
|
+
|
|
3
|
+
Given (artifact_type, indexer identity, input blob OID, config), produce a
|
|
4
|
+
deterministic SHA-256 that is byte-identical across machines, processes, and
|
|
5
|
+
the stdio-vs-HTTP split.
|
|
6
|
+
|
|
7
|
+
Any tool that caches or ships a derived artifact should key it by this hash.
|
|
8
|
+
The formula is frozen — changing it silently would invalidate every cached
|
|
9
|
+
artifact across the fleet, so callers should bump the ``v1`` suffix and write
|
|
10
|
+
a migration.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import os
|
|
21
|
+
from collections.abc import Mapping
|
|
22
|
+
|
|
23
|
+
# Version tag baked into every action_hash. Bump only in a coordinated
|
|
24
|
+
# migration that also rebuilds the caches that depend on it.
|
|
25
|
+
ACTION_HASH_SCHEMA = "atto.artifact.v1"
|
|
26
|
+
|
|
27
|
+
# Allowed artifact types. Keeping this closed lets us catch typos at the
|
|
28
|
+
# boundary and gives us a reason to fail loudly on unknown derived artifacts
|
|
29
|
+
# rather than silently producing "works but not shared" hashes.
|
|
30
|
+
ARTIFACT_TYPES: frozenset[str] = frozenset(
|
|
31
|
+
{
|
|
32
|
+
"symbols",
|
|
33
|
+
"references",
|
|
34
|
+
"embedding",
|
|
35
|
+
"ast",
|
|
36
|
+
"deps",
|
|
37
|
+
"repo_map_entry",
|
|
38
|
+
"stack_graph",
|
|
39
|
+
"conventions",
|
|
40
|
+
"learning_ref",
|
|
41
|
+
# Aliases for server-side content_type values:
|
|
42
|
+
"symbol_set_v1",
|
|
43
|
+
"dep_graph_v1",
|
|
44
|
+
"embedding_blob_v1",
|
|
45
|
+
"stack_graph_partial_v1",
|
|
46
|
+
"repo_map_slice_v1",
|
|
47
|
+
"conventions_v1",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Whitelist of config keys that contribute to `config_digest` per artifact
|
|
52
|
+
# type. Anything not in the whitelist is intentionally *excluded* from the
|
|
53
|
+
# hash — it's meta (e.g. user preferences, display options) that doesn't
|
|
54
|
+
# affect the derived output.
|
|
55
|
+
CONFIG_SUBSET: Mapping[str, tuple[str, ...]] = {
|
|
56
|
+
"symbols": (
|
|
57
|
+
"language",
|
|
58
|
+
"parser_kind",
|
|
59
|
+
"parser_version",
|
|
60
|
+
"extract_signatures",
|
|
61
|
+
"extract_docstrings",
|
|
62
|
+
),
|
|
63
|
+
"symbol_set_v1": (
|
|
64
|
+
"language",
|
|
65
|
+
"parser_kind",
|
|
66
|
+
"parser_version",
|
|
67
|
+
"extract_signatures",
|
|
68
|
+
"extract_docstrings",
|
|
69
|
+
),
|
|
70
|
+
"references": (
|
|
71
|
+
"language",
|
|
72
|
+
"parser_kind",
|
|
73
|
+
"parser_version",
|
|
74
|
+
),
|
|
75
|
+
"embedding": (
|
|
76
|
+
"model_name",
|
|
77
|
+
"model_version",
|
|
78
|
+
"dimension",
|
|
79
|
+
"normalize",
|
|
80
|
+
"chunker",
|
|
81
|
+
"chunk_size",
|
|
82
|
+
"chunk_overlap",
|
|
83
|
+
"preprocessor_version",
|
|
84
|
+
),
|
|
85
|
+
"embedding_blob_v1": (
|
|
86
|
+
"model_name",
|
|
87
|
+
"model_version",
|
|
88
|
+
"dimension",
|
|
89
|
+
"normalize",
|
|
90
|
+
"chunker",
|
|
91
|
+
"chunk_size",
|
|
92
|
+
"chunk_overlap",
|
|
93
|
+
"preprocessor_version",
|
|
94
|
+
),
|
|
95
|
+
"ast": (
|
|
96
|
+
"language",
|
|
97
|
+
"parser_kind",
|
|
98
|
+
"parser_version",
|
|
99
|
+
),
|
|
100
|
+
"deps": (
|
|
101
|
+
"language",
|
|
102
|
+
"resolver_strategy",
|
|
103
|
+
"resolver_version",
|
|
104
|
+
"follow_dynamic_imports",
|
|
105
|
+
),
|
|
106
|
+
"dep_graph_v1": (
|
|
107
|
+
"language",
|
|
108
|
+
"resolver_strategy",
|
|
109
|
+
"resolver_version",
|
|
110
|
+
"follow_dynamic_imports",
|
|
111
|
+
),
|
|
112
|
+
"repo_map_entry": (
|
|
113
|
+
"indexer_version",
|
|
114
|
+
"slice_root",
|
|
115
|
+
"max_depth",
|
|
116
|
+
"ranker_version",
|
|
117
|
+
"max_tokens",
|
|
118
|
+
),
|
|
119
|
+
"repo_map_slice_v1": (
|
|
120
|
+
"indexer_version",
|
|
121
|
+
"slice_root",
|
|
122
|
+
"max_depth",
|
|
123
|
+
"ranker_version",
|
|
124
|
+
"max_tokens",
|
|
125
|
+
),
|
|
126
|
+
"stack_graph": (
|
|
127
|
+
"language",
|
|
128
|
+
"sg_grammar_version",
|
|
129
|
+
"rules_version",
|
|
130
|
+
),
|
|
131
|
+
"stack_graph_partial_v1": (
|
|
132
|
+
"language",
|
|
133
|
+
"sg_grammar_version",
|
|
134
|
+
"rules_version",
|
|
135
|
+
),
|
|
136
|
+
"conventions": (
|
|
137
|
+
"rule_pack_version",
|
|
138
|
+
),
|
|
139
|
+
"conventions_v1": (
|
|
140
|
+
"rule_pack_version",
|
|
141
|
+
),
|
|
142
|
+
"learning_ref": ("schema_version",),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def sha256_file(path: str | os.PathLike[str]) -> str:
|
|
147
|
+
"""Compute SHA-256 hex digest of a file, reading in 64 KiB chunks."""
|
|
148
|
+
digest = hashlib.sha256()
|
|
149
|
+
with open(path, "rb") as fh:
|
|
150
|
+
for chunk in iter(lambda: fh.read(65536), b""):
|
|
151
|
+
digest.update(chunk)
|
|
152
|
+
return digest.hexdigest()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def canonical_json(obj: Any) -> bytes:
|
|
156
|
+
"""Stable JSON encoding for hashing.
|
|
157
|
+
|
|
158
|
+
Sorts dict keys, disables non-ASCII escaping (so all bytes are stable
|
|
159
|
+
regardless of locale), no whitespace, no NaN/Infinity. The resulting
|
|
160
|
+
bytes are a canonical form — two processes on different machines
|
|
161
|
+
always produce the same output for equivalent Python objects.
|
|
162
|
+
"""
|
|
163
|
+
return json.dumps(
|
|
164
|
+
obj,
|
|
165
|
+
sort_keys=True,
|
|
166
|
+
ensure_ascii=True,
|
|
167
|
+
separators=(",", ":"),
|
|
168
|
+
allow_nan=False,
|
|
169
|
+
).encode("ascii")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def compute_config_digest(artifact_type: str, config: Mapping[str, Any]) -> str:
|
|
173
|
+
"""Hash a config dict down to a hex digest, using only the whitelisted keys.
|
|
174
|
+
|
|
175
|
+
Unknown keys are *silently ignored* (not an error) because callers legitimately
|
|
176
|
+
pass richer config objects than the hash cares about. Keys in the whitelist
|
|
177
|
+
that are missing from ``config`` default to ``None`` — so adding a new
|
|
178
|
+
config knob is a non-breaking change only when it starts as ``None``.
|
|
179
|
+
"""
|
|
180
|
+
if artifact_type not in ARTIFACT_TYPES:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"unknown artifact_type {artifact_type!r}; allowed: {sorted(ARTIFACT_TYPES)}"
|
|
183
|
+
)
|
|
184
|
+
allowed = CONFIG_SUBSET.get(artifact_type, ())
|
|
185
|
+
subset = {key: config.get(key) for key in allowed}
|
|
186
|
+
return hashlib.sha256(canonical_json(subset)).hexdigest()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def compute_action_hash(
|
|
190
|
+
artifact_type: str,
|
|
191
|
+
*,
|
|
192
|
+
indexer_name: str,
|
|
193
|
+
indexer_version: str,
|
|
194
|
+
input_blob_oid: str,
|
|
195
|
+
config: Mapping[str, Any],
|
|
196
|
+
input_tree_oid: str | None = None,
|
|
197
|
+
) -> str:
|
|
198
|
+
"""Compute the canonical action hash for a derived artifact.
|
|
199
|
+
|
|
200
|
+
Formula::
|
|
201
|
+
|
|
202
|
+
sha256(
|
|
203
|
+
"atto.artifact.v1" || "\\n" ||
|
|
204
|
+
"type=" || artifact_type || "\\n" ||
|
|
205
|
+
"indexer=" || indexer_name || "\\n" ||
|
|
206
|
+
"iversion=" || indexer_version || "\\n" ||
|
|
207
|
+
"input=" || input_blob_oid || "\\n" ||
|
|
208
|
+
"tree=" || input_tree_oid || "\\n" ||
|
|
209
|
+
"config=" || config_digest || "\\n"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
The delimiter is ``\\n`` and values are passed through unchanged — that's
|
|
213
|
+
fine because every field is either a short identifier or an already-hex
|
|
214
|
+
digest, so there is no delimiter ambiguity in practice.
|
|
215
|
+
"""
|
|
216
|
+
if artifact_type not in ARTIFACT_TYPES:
|
|
217
|
+
raise ValueError(
|
|
218
|
+
f"unknown artifact_type {artifact_type!r}; allowed: {sorted(ARTIFACT_TYPES)}"
|
|
219
|
+
)
|
|
220
|
+
config_digest = compute_config_digest(artifact_type, config)
|
|
221
|
+
payload = (
|
|
222
|
+
f"{ACTION_HASH_SCHEMA}\n"
|
|
223
|
+
f"type={artifact_type}\n"
|
|
224
|
+
f"indexer={indexer_name}\n"
|
|
225
|
+
f"iversion={indexer_version}\n"
|
|
226
|
+
f"input={input_blob_oid}\n"
|
|
227
|
+
f"tree={input_tree_oid or ''}\n"
|
|
228
|
+
f"config={config_digest}\n"
|
|
229
|
+
)
|
|
230
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Provenance records describe how a derived code-intel artifact was produced.
|
|
2
|
+
|
|
3
|
+
Every symbol set, embedding, repo-map slice, etc. should carry a ``Provenance``
|
|
4
|
+
record so we can answer:
|
|
5
|
+
|
|
6
|
+
- Which model + version produced this embedding?
|
|
7
|
+
- Which indexer + version extracted these symbols?
|
|
8
|
+
- What blob (and optional tree) was the input?
|
|
9
|
+
- When, and on what host, was it produced?
|
|
10
|
+
|
|
11
|
+
This lets us detect staleness (indexer bumped, model swapped) and provides
|
|
12
|
+
the audit trail for retrieval determinism.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import socket
|
|
19
|
+
import time
|
|
20
|
+
from dataclasses import asdict, dataclass
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
|
|
26
|
+
# Sentinel indexer_name used by the one-shot migration that backfills
|
|
27
|
+
# provenance onto pre-existing rows. Searching for this string is an easy
|
|
28
|
+
# way to find everything that still needs a real indexer pass.
|
|
29
|
+
LEGACY_INDEXER_NAME = "legacy-pre-v2"
|
|
30
|
+
|
|
31
|
+
PROVENANCE_SCHEMA_VERSION = 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class Provenance:
|
|
36
|
+
"""A record of how one derived artifact was produced.
|
|
37
|
+
|
|
38
|
+
Frozen + slotted so it's cheap to build many of these during an index
|
|
39
|
+
run and so accidental mutation is a TypeError.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
schema_version: int
|
|
43
|
+
artifact_type: str
|
|
44
|
+
action_hash: str
|
|
45
|
+
input_blob_oid: str
|
|
46
|
+
indexer_name: str
|
|
47
|
+
indexer_version: str
|
|
48
|
+
producer_host: str
|
|
49
|
+
produced_at: float
|
|
50
|
+
# Embeddings-specific; None for other artifact types.
|
|
51
|
+
model_name: str | None = None
|
|
52
|
+
model_version: str | None = None
|
|
53
|
+
dimension: int | None = None
|
|
54
|
+
preprocessing_digest: str | None = None
|
|
55
|
+
# Optional contextual IDs — server-side sets these, local side leaves None.
|
|
56
|
+
producer_service: str = "attocode-local"
|
|
57
|
+
producer_user_id: str | None = None
|
|
58
|
+
producer_job_id: str | None = None
|
|
59
|
+
input_tree_oid: str | None = None
|
|
60
|
+
# Original config subset that went into the action_hash, as canonical JSON
|
|
61
|
+
# text — kept as a string so the dataclass stays hashable/frozen.
|
|
62
|
+
config_json: str = "{}"
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# Constructors
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def create(
|
|
70
|
+
cls,
|
|
71
|
+
*,
|
|
72
|
+
artifact_type: str,
|
|
73
|
+
action_hash: str,
|
|
74
|
+
input_blob_oid: str,
|
|
75
|
+
indexer_name: str,
|
|
76
|
+
indexer_version: str,
|
|
77
|
+
config: Mapping[str, Any] | None = None,
|
|
78
|
+
model_name: str | None = None,
|
|
79
|
+
model_version: str | None = None,
|
|
80
|
+
dimension: int | None = None,
|
|
81
|
+
preprocessing_digest: str | None = None,
|
|
82
|
+
producer_service: str = "attocode-local",
|
|
83
|
+
producer_user_id: str | None = None,
|
|
84
|
+
producer_job_id: str | None = None,
|
|
85
|
+
input_tree_oid: str | None = None,
|
|
86
|
+
) -> Provenance:
|
|
87
|
+
"""Build a fresh provenance record with ``produced_at`` set to now."""
|
|
88
|
+
config_json = json.dumps(
|
|
89
|
+
dict(config or {}),
|
|
90
|
+
sort_keys=True,
|
|
91
|
+
ensure_ascii=True,
|
|
92
|
+
separators=(",", ":"),
|
|
93
|
+
)
|
|
94
|
+
return cls(
|
|
95
|
+
schema_version=PROVENANCE_SCHEMA_VERSION,
|
|
96
|
+
artifact_type=artifact_type,
|
|
97
|
+
action_hash=action_hash,
|
|
98
|
+
input_blob_oid=input_blob_oid,
|
|
99
|
+
indexer_name=indexer_name,
|
|
100
|
+
indexer_version=indexer_version,
|
|
101
|
+
producer_host=socket.gethostname(),
|
|
102
|
+
produced_at=time.time(),
|
|
103
|
+
model_name=model_name,
|
|
104
|
+
model_version=model_version,
|
|
105
|
+
dimension=dimension,
|
|
106
|
+
preprocessing_digest=preprocessing_digest,
|
|
107
|
+
producer_service=producer_service,
|
|
108
|
+
producer_user_id=producer_user_id,
|
|
109
|
+
producer_job_id=producer_job_id,
|
|
110
|
+
input_tree_oid=input_tree_oid,
|
|
111
|
+
config_json=config_json,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# ------------------------------------------------------------------
|
|
115
|
+
# Serialization
|
|
116
|
+
# ------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def to_dict(self) -> dict[str, Any]:
|
|
119
|
+
"""Return a plain dict suitable for JSON / JSONB storage."""
|
|
120
|
+
return asdict(self)
|
|
121
|
+
|
|
122
|
+
def to_json(self) -> str:
|
|
123
|
+
"""Canonical JSON text — stable for hashing."""
|
|
124
|
+
return json.dumps(
|
|
125
|
+
self.to_dict(),
|
|
126
|
+
sort_keys=True,
|
|
127
|
+
ensure_ascii=True,
|
|
128
|
+
separators=(",", ":"),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_dict(cls, data: Mapping[str, Any]) -> Provenance:
|
|
133
|
+
"""Rebuild a Provenance from its dict form (tolerant of missing fields)."""
|
|
134
|
+
# Copy known fields; unknown keys are silently dropped. Missing
|
|
135
|
+
# required fields get sensible defaults matching a "legacy" marker
|
|
136
|
+
# so we can still roundtrip rows that were inserted with a very old
|
|
137
|
+
# schema.
|
|
138
|
+
return cls(
|
|
139
|
+
schema_version=int(data.get("schema_version", PROVENANCE_SCHEMA_VERSION)),
|
|
140
|
+
artifact_type=str(data.get("artifact_type", "unknown")),
|
|
141
|
+
action_hash=str(data.get("action_hash", "")),
|
|
142
|
+
input_blob_oid=str(data.get("input_blob_oid", "")),
|
|
143
|
+
indexer_name=str(data.get("indexer_name", LEGACY_INDEXER_NAME)),
|
|
144
|
+
indexer_version=str(data.get("indexer_version", "0")),
|
|
145
|
+
producer_host=str(data.get("producer_host", "unknown")),
|
|
146
|
+
produced_at=float(data.get("produced_at", 0.0)),
|
|
147
|
+
model_name=data.get("model_name"),
|
|
148
|
+
model_version=data.get("model_version"),
|
|
149
|
+
dimension=data.get("dimension"),
|
|
150
|
+
preprocessing_digest=data.get("preprocessing_digest"),
|
|
151
|
+
producer_service=str(data.get("producer_service", "attocode-local")),
|
|
152
|
+
producer_user_id=data.get("producer_user_id"),
|
|
153
|
+
producer_job_id=data.get("producer_job_id"),
|
|
154
|
+
input_tree_oid=data.get("input_tree_oid"),
|
|
155
|
+
config_json=str(data.get("config_json", "{}")),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def legacy_provenance(
|
|
160
|
+
artifact_type: str,
|
|
161
|
+
*,
|
|
162
|
+
input_blob_oid: str = "",
|
|
163
|
+
produced_at: float | None = None,
|
|
164
|
+
action_hash: str = "",
|
|
165
|
+
) -> Provenance:
|
|
166
|
+
"""Build a sentinel provenance record for the one-shot backfill.
|
|
167
|
+
|
|
168
|
+
Use this when you have existing rows in a store that pre-date provenance
|
|
169
|
+
tracking. Downstream tools can filter on ``indexer_name == LEGACY_INDEXER_NAME``
|
|
170
|
+
to find rows that still need a real re-index pass.
|
|
171
|
+
"""
|
|
172
|
+
return Provenance(
|
|
173
|
+
schema_version=PROVENANCE_SCHEMA_VERSION,
|
|
174
|
+
artifact_type=artifact_type,
|
|
175
|
+
action_hash=action_hash,
|
|
176
|
+
input_blob_oid=input_blob_oid,
|
|
177
|
+
indexer_name=LEGACY_INDEXER_NAME,
|
|
178
|
+
indexer_version="0",
|
|
179
|
+
producer_host=socket.gethostname(),
|
|
180
|
+
produced_at=produced_at if produced_at is not None else time.time(),
|
|
181
|
+
model_name=None,
|
|
182
|
+
model_version=None,
|
|
183
|
+
dimension=None,
|
|
184
|
+
preprocessing_digest=None,
|
|
185
|
+
producer_service="attocode-backfill",
|
|
186
|
+
producer_user_id=None,
|
|
187
|
+
producer_job_id=None,
|
|
188
|
+
input_tree_oid=None,
|
|
189
|
+
config_json="{}",
|
|
190
|
+
)
|