Coevolution extension

This commit is contained in:
2026-04-29 01:19:59 +02:00
parent 88afdc09fd
commit 991c34ce52
17 changed files with 764 additions and 4 deletions

View File

@@ -17,6 +17,7 @@ from repo_registry.core.models import (
CapabilitySummary,
ContentChunk,
Evidence,
ExpectationGap,
Feature,
ObservedFact,
Repository,
@@ -47,6 +48,7 @@ class RegistryStore:
connection.executescript(migration_path.read_text(encoding="utf-8"))
self._ensure_content_chunks_table(connection)
self._ensure_approved_source_ref_columns(connection)
self._ensure_expectation_gaps_table(connection)
def connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.database_path)
@@ -92,6 +94,29 @@ class RegistryStore:
"CREATE INDEX IF NOT EXISTS idx_content_chunks_run ON content_chunks(analysis_run_id)"
)
def _ensure_expectation_gaps_table(self, connection: sqlite3.Connection) -> None:
connection.execute(
"""
CREATE TABLE IF NOT EXISTS expectation_gaps (
id INTEGER PRIMARY KEY AUTOINCREMENT,
repository_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
analysis_run_id INTEGER REFERENCES analysis_runs(id) ON DELETE SET NULL,
expected_type TEXT NOT NULL,
expected_name TEXT NOT NULL,
source TEXT NOT NULL,
notes TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'open',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"""
)
connection.execute(
"CREATE INDEX IF NOT EXISTS idx_expectation_gaps_repository ON expectation_gaps(repository_id)"
)
connection.execute(
"CREATE INDEX IF NOT EXISTS idx_expectation_gaps_run ON expectation_gaps(analysis_run_id)"
)
def create_repository(
self,
*,
@@ -1050,6 +1075,93 @@ class RegistryStore:
for row in rows
]
def create_expectation_gap(
self,
repository_id: int,
analysis_run_id: int | None,
*,
expected_type: str,
expected_name: str,
source: str,
notes: str = "",
) -> ExpectationGap:
self.get_repository(repository_id)
if analysis_run_id is not None:
self.get_analysis_run(repository_id, analysis_run_id)
with self.connect() as connection:
cursor = connection.execute(
"""
INSERT INTO expectation_gaps
(repository_id, analysis_run_id, expected_type, expected_name,
source, notes)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
repository_id,
analysis_run_id,
expected_type,
expected_name,
source,
notes,
),
)
gap_id = int(cursor.lastrowid)
log_operation(
"expectation_gap_recorded",
repository_id=repository_id,
analysis_run_id=analysis_run_id,
expectation_gap_id=gap_id,
expected_type=expected_type,
)
return self.get_expectation_gap(repository_id, gap_id)
def get_expectation_gap(
self,
repository_id: int,
expectation_gap_id: int,
) -> ExpectationGap:
with self.connect() as connection:
row = connection.execute(
"""
SELECT id, repository_id, analysis_run_id, expected_type,
expected_name, source, notes, status, created_at
FROM expectation_gaps
WHERE repository_id = ? AND id = ?
""",
(repository_id, expectation_gap_id),
).fetchone()
if row is None:
raise NotFoundError(
f"expectation gap {expectation_gap_id} was not found for repository "
f"{repository_id}"
)
return self._expectation_gap_from_row(row)
def list_expectation_gaps(
self,
repository_id: int,
analysis_run_id: int | None = None,
) -> list[ExpectationGap]:
self.get_repository(repository_id)
params: tuple[int, ...]
where = "WHERE repository_id = ?"
params = (repository_id,)
if analysis_run_id is not None:
where += " AND analysis_run_id = ?"
params = (repository_id, analysis_run_id)
with self.connect() as connection:
rows = connection.execute(
f"""
SELECT id, repository_id, analysis_run_id, expected_type,
expected_name, source, notes, status, created_at
FROM expectation_gaps
{where}
ORDER BY created_at DESC, id DESC
""",
params,
).fetchall()
return [self._expectation_gap_from_row(row) for row in rows]
def fail_analysis_run(
self,
repository_id: int,
@@ -2215,3 +2327,17 @@ class RegistryStore:
end_line=row["end_line"],
text=row["text"],
)
@staticmethod
def _expectation_gap_from_row(row: sqlite3.Row) -> ExpectationGap:
return ExpectationGap(
id=row["id"],
repository_id=row["repository_id"],
analysis_run_id=row["analysis_run_id"],
expected_type=row["expected_type"],
expected_name=row["expected_name"],
source=row["source"],
notes=row["notes"],
status=row["status"],
created_at=row["created_at"],
)