orchestrata_ultimate.refactor.codemod.persistence.repository
Reference › orchestrata_ultimate › refactor › codemod › persistence › repository
The graph persistence for codemod provenance. CodemodGraphRepository is a protocol with save_run, save_pass, save_findings, save_changes, query_runs, query_findings and query_affected_code. BoltSyncCodemodRepository implements it over a SyncGraphRepository (see _types) and writes inside a unit of work. It stores run, pass, finding and code-change nodes, linked by the relationships HAS_CODEMOD_RUN, HAS_PASS, HAS_FINDING, ABOUT, PRODUCED_BY, BREAKING_CHANGE and CHANGED_IN, using the labels and keys of the canonical schema in the knowledge_graph package. save_run, for example, merges the run node and attaches it to its project by slug. InMemoryCodemodRepository keeps the same data in dictionaries and is marked for testing, not production use. The GraphWriterObserver is its only importer.
At a glance
| Source | src/orchestrata_ultimate/refactor/codemod/persistence/repository.py |
| Lines of code | 304 |
| Classes | 3 |
| Functions | 0 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.knowledge_graph, orchestrata_ultimate.knowledge_graph.canonical_schema, orchestrata_ultimate.refactor.codemod._types, orchestrata_ultimate.refactor.codemod.report |
| Imported by | orchestrata_ultimate.refactor.codemod.observability.graph_writer |
| Tests that import it | tests/refactor/codemod/test_observability.py, tests/refactor/codemod/test_persistence_repository_coverage.py |
Class diagram
Dependency diagram
API
Classes
CodemodGraphRepository (protocol)
Bases: Protocol · Decorators: traces(spec='110', story='US-110-010', reqs=['REQ-110-028']), runtime_checkable · Line 24
CodemodGraphRepository — structural interface for codemod provenance.
| Method | Signature | Summary |
|---|---|---|
save_run |
save_run(run: CodemodRun) -> None |
|
save_pass |
save_pass(run_id: str, pass_: CodemodPass) -> None |
|
save_findings |
save_findings(run_id: str, findings: list[Finding]) -> None |
|
save_changes |
save_changes(run_id: str, changes: list[CodeChange]) -> None |
|
query_runs |
query_runs(project: str, limit: int) -> list[CodemodRun] |
|
query_findings |
query_findings(run_id: str) -> list[Finding] |
|
query_affected_code |
query_affected_code(run_id: str) -> list[str] |
BoltSyncCodemodRepository
Decorators: traces(spec='110', story='US-110-010', reqs=['REQ-110-029']) · Line 43
Bolt-based synchronous implementation of CodemodGraphRepository.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(repo: SyncGraphRepository) -> None |
Wire BoltSyncCodemodRepository's composed collaborators. |
save_run |
save_run(run: CodemodRun) -> None |
MERGE CodemodRun node + HAS_CODEMOD_RUN edge to Project. |
save_pass |
save_pass(run_id: str, pass_: CodemodPass) -> None |
MERGE CodemodPass node + HAS_PASS edge to CodemodRun. |
save_findings |
save_findings(run_id: str, findings: list[Finding]) -> None |
UNWIND batch MERGE CodemodFinding nodes + HAS_FINDING + ABOUT edges. |
save_changes |
save_changes(run_id: str, changes: list[CodeChange]) -> None |
UNWIND batch MERGE CodeChange nodes + PRODUCED_BY + CHANGED_IN edges. |
query_runs |
query_runs(project: str, limit: int = 10) -> list[CodemodRun] |
Query CodemodRun nodes for a project. |
query_findings |
query_findings(run_id: str) -> list[Finding] |
Query CodemodFinding nodes for a run. |
query_affected_code |
query_affected_code(run_id: str) -> list[str] |
Query entity IDs affected by a run's changes. |
InMemoryCodemodRepository
Decorators: traces(spec='110', story='US-110-010', reqs=['REQ-110-029']) · Line 256
In-memory implementation of CodemodGraphRepository for testing.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__() -> None |
Initialize empty in-memory storage. |
save_run |
save_run(run: CodemodRun) -> None |
Store CodemodRun in memory. |
save_pass |
save_pass(run_id: str, pass_: CodemodPass) -> None |
Store CodemodPass in memory. |
save_findings |
save_findings(run_id: str, findings: list[Finding]) -> None |
Store findings in memory. |
save_changes |
save_changes(run_id: str, changes: list[CodeChange]) -> None |
Store changes in memory. |
query_runs |
query_runs(project: str, limit: int = 10) -> list[CodemodRun] |
Query runs by project. |
query_findings |
query_findings(run_id: str) -> list[Finding] |
Query findings by run_id. |
query_affected_code |
query_affected_code(run_id: str) -> list[str] |
Query affected entity IDs by run_id. |
Source
"""SPEC-110 codemod engine — graph persistence repository.
Provides CodemodGraphRepository Protocol and BoltSyncCodemodRepository
implementation for writing codemod provenance to the operational graph DB.
"""
from __future__ import annotations
from typing import Protocol, runtime_checkable
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.knowledge_graph import canonical_schema as cs
from orchestrata_ultimate.refactor.codemod._types import (
CodeChange,
CodemodPass,
CodemodRun,
SyncGraphRepository,
)
from orchestrata_ultimate.refactor.codemod.report import Finding
@traces(spec="110", story="US-110-010", reqs=["REQ-110-028"])
@runtime_checkable
class CodemodGraphRepository(Protocol):
"""CodemodGraphRepository — structural interface for codemod provenance."""
def save_run(self, run: CodemodRun) -> None: ...
def save_pass(self, run_id: str, pass_: CodemodPass) -> None: ...
def save_findings(self, run_id: str, findings: list[Finding]) -> None: ...
def save_changes(self, run_id: str, changes: list[CodeChange]) -> None: ...
def query_runs(self, project: str, limit: int) -> list[CodemodRun]: ...
def query_findings(self, run_id: str) -> list[Finding]: ...
def query_affected_code(self, run_id: str) -> list[str]: ...
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
class BoltSyncCodemodRepository:
"""Bolt-based synchronous implementation of CodemodGraphRepository.
Uses SyncGraphRepository.unit_of_work() for transactional writes.
Writes CodemodRun, CodemodPass, CodemodFinding, CodeChange nodes with
edges: HAS_CODEMOD_RUN, HAS_PASS, HAS_FINDING, ABOUT, PRODUCED_BY,
BREAKING_CHANGE, CHANGED_IN.
"""
def __init__(self, repo: SyncGraphRepository) -> None:
"""Wire BoltSyncCodemodRepository's composed collaborators.
Args:
repo: Injected SyncGraphRepository for graph DB operations.
"""
self._repo = repo
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
def save_run(self, run: CodemodRun) -> None:
"""MERGE CodemodRun node + HAS_CODEMOD_RUN edge to Project."""
cypher = f"""
MERGE (r:{cs.LABEL_CODEMOD_RUN} {{id: $id}})
SET r.started_at = $started_at,
r.completed_at = $completed_at,
r.status = $status,
r.dry_run = $dry_run,
r.pass_count = $pass_count,
r.finding_count = $finding_count,
r.file_count = $file_count,
r.duration_ms = $duration_ms
WITH r
MATCH (p:{cs.LABEL_PROJECT} {{slug: $project}})
MERGE (p)-[:{cs.REL_HAS_CODEMOD_RUN}]->(r)
"""
params = {
"id": run.run_id,
"started_at": run.started_at,
"completed_at": run.completed_at,
"status": run.status,
"dry_run": run.dry_run,
"pass_count": run.pass_count,
"finding_count": run.finding_count,
"file_count": run.file_count,
"duration_ms": run.duration_ms,
"project": run.project,
}
with self._repo.unit_of_work() as uow:
uow.run(cypher, params)
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
def save_pass(self, run_id: str, pass_: CodemodPass) -> None:
"""MERGE CodemodPass node + HAS_PASS edge to CodemodRun."""
cypher = f"""
MERGE (cp:{cs.LABEL_CODEMOD_PASS} {{id: $id}})
SET cp.pass_name = $pass_name,
cp.pass_type = $pass_type,
cp.file_count = $file_count,
cp.duration_ms = $duration_ms
WITH cp
MATCH (r:{cs.LABEL_CODEMOD_RUN} {{id: $run_id}})
MERGE (r)-[:{cs.REL_HAS_PASS}]->(cp)
"""
params = {
"id": pass_.pass_id,
"run_id": run_id,
"pass_name": pass_.pass_name,
"pass_type": pass_.pass_type,
"file_count": pass_.file_count,
"duration_ms": pass_.duration_ms,
}
with self._repo.unit_of_work() as uow:
uow.run(cypher, params)
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
def save_findings(self, run_id: str, findings: list[Finding]) -> None:
"""UNWIND batch MERGE CodemodFinding nodes + HAS_FINDING + ABOUT edges."""
if not findings:
return
cypher = f"""
UNWIND $findings AS f
MERGE (cf:{cs.LABEL_CODEMOD_FINDING} {{id: f.id}})
SET cf.severity = f.severity,
cf.rule = f.rule,
cf.path = f.path,
cf.line = f.line,
cf.message = f.message
WITH cf, f
MATCH (r:{cs.LABEL_CODEMOD_RUN} {{id: $run_id}})
MERGE (r)-[:{cs.REL_HAS_FINDING}]->(cf)
WITH cf, f
OPTIONAL MATCH (m:{cs.LABEL_MODULE} {{path: f.path}})
FOREACH (_ IN CASE WHEN m IS NOT NULL THEN [1] ELSE [] END |
MERGE (cf)-[:{cs.REL_ABOUT}]->(m))
"""
params = {
"run_id": run_id,
"findings": [
{
"id": f"{run_id}:finding:{f.rule}:{f.path}:{f.line}",
"severity": f.severity,
"rule": f.rule,
"path": f.path,
"line": f.line,
"message": f.message,
}
for f in findings
],
}
with self._repo.unit_of_work() as uow:
uow.run(cypher, params)
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
def save_changes(self, run_id: str, changes: list[CodeChange]) -> None:
"""UNWIND batch MERGE CodeChange nodes + PRODUCED_BY + CHANGED_IN edges."""
if not changes:
return
cypher = f"""
UNWIND $changes AS c
MERGE (cc:{cs.LABEL_CHANGE}:CodeChange {{id: c.id}})
SET cc.pass_name = c.pass_name,
cc.description = c.description
WITH cc, c
MATCH (cp:{cs.LABEL_CODEMOD_PASS} {{id: c.pass_id}})
MERGE (cc)-[:{cs.REL_PRODUCED_BY}]->(cp)
WITH cc, c
OPTIONAL MATCH (entity {{id: c.entity_id}})
FOREACH (_ IN CASE WHEN entity IS NOT NULL THEN [1] ELSE [] END |
MERGE (entity)-[:{cs.REL_CHANGED_IN}]->(cc))
"""
params = {
"changes": [
{
"id": f"{run_id}:change:{c.pass_name}:{c.path}",
"pass_id": f"{run_id}:pass:{c.pass_name}",
"pass_name": c.pass_name,
"description": c.description,
"entity_id": str(c.entity_id) if c.entity_id is not None else None,
}
for c in changes
]
}
with self._repo.unit_of_work() as uow:
uow.run(cypher, params)
def query_runs(self, project: str, limit: int = 10) -> list[CodemodRun]:
"""Query CodemodRun nodes for a project."""
cypher = f"""
MATCH (p:{cs.LABEL_PROJECT} {{slug: $project}})-[:{cs.REL_HAS_CODEMOD_RUN}]->(r:{cs.LABEL_CODEMOD_RUN})
RETURN r.id AS id, r.started_at AS started_at, r.completed_at AS completed_at,
r.status AS status, r.dry_run AS dry_run, r.pass_count AS pass_count,
r.finding_count AS finding_count, r.file_count AS file_count,
r.duration_ms AS duration_ms, r.project AS project
ORDER BY r.started_at DESC
LIMIT $limit
"""
params = {"project": project, "limit": limit}
with self._repo.unit_of_work() as uow:
result = uow.run(cypher, params)
return [
CodemodRun(
run_id=record["id"],
started_at=record["started_at"],
completed_at=record["completed_at"],
status=record["status"],
dry_run=record["dry_run"],
pass_count=record["pass_count"],
finding_count=record["finding_count"],
file_count=record["file_count"],
duration_ms=record["duration_ms"],
project=record["project"],
)
for record in result
]
def query_findings(self, run_id: str) -> list[Finding]:
"""Query CodemodFinding nodes for a run."""
cypher = f"""
MATCH (r:{cs.LABEL_CODEMOD_RUN} {{id: $run_id}})-[:{cs.REL_HAS_FINDING}]->(cf:{cs.LABEL_CODEMOD_FINDING})
RETURN cf.severity AS severity, cf.rule AS rule, cf.path AS path,
cf.line AS line, cf.message AS message
"""
params = {"run_id": run_id}
with self._repo.unit_of_work() as uow:
result = uow.run(cypher, params)
return [
Finding(
severity=record["severity"],
rule=record["rule"],
path=record["path"],
line=record["line"],
message=record["message"],
proof_tree=None,
)
for record in result
]
def query_affected_code(self, run_id: str) -> list[str]:
"""Query entity IDs affected by a run's changes."""
cypher = f"""
MATCH (r:{cs.LABEL_CODEMOD_RUN} {{id: $run_id}})-[:{cs.REL_HAS_PASS}]->(cp:{cs.LABEL_CODEMOD_PASS})
MATCH (cc:{cs.LABEL_CHANGE}:CodeChange)-[:{cs.REL_PRODUCED_BY}]->(cp)
MATCH (entity)-[:{cs.REL_CHANGED_IN}]->(cc)
RETURN DISTINCT entity.id AS entity_id
"""
params = {"run_id": run_id}
with self._repo.unit_of_work() as uow:
result = uow.run(cypher, params)
return [record["entity_id"] for record in result if record["entity_id"]]
@traces(spec="110", story="US-110-010", reqs=["REQ-110-029"])
class InMemoryCodemodRepository:
"""In-memory implementation of CodemodGraphRepository for testing.
Stores codemod provenance in memory dictionaries. Not for production use.
"""
def __init__(self) -> None:
"""Initialize empty in-memory storage."""
self._runs: dict[str, CodemodRun] = {}
self._passes: dict[str, list[CodemodPass]] = {}
self._findings: dict[str, list[Finding]] = {}
self._changes: dict[str, list[CodeChange]] = {}
def save_run(self, run: CodemodRun) -> None:
"""Store CodemodRun in memory."""
self._runs[run.run_id] = run
def save_pass(self, run_id: str, pass_: CodemodPass) -> None:
"""Store CodemodPass in memory."""
if run_id not in self._passes:
self._passes[run_id] = []
self._passes[run_id].append(pass_)
def save_findings(self, run_id: str, findings: list[Finding]) -> None:
"""Store findings in memory."""
if run_id not in self._findings:
self._findings[run_id] = []
self._findings[run_id].extend(findings)
def save_changes(self, run_id: str, changes: list[CodeChange]) -> None:
"""Store changes in memory."""
if run_id not in self._changes:
self._changes[run_id] = []
self._changes[run_id].extend(changes)
def query_runs(self, project: str, limit: int = 10) -> list[CodemodRun]:
"""Query runs by project."""
runs = [r for r in self._runs.values() if r.project == project]
runs.sort(key=lambda r: r.started_at, reverse=True)
return runs[:limit]
def query_findings(self, run_id: str) -> list[Finding]:
"""Query findings by run_id."""
return self._findings.get(run_id, [])
def query_affected_code(self, run_id: str) -> list[str]:
"""Query affected entity IDs by run_id."""
changes = self._changes.get(run_id, [])
return [str(c.entity_id) for c in changes if c.entity_id is not None]