stable asset queries, lexical search, filters, contextual entity and relationship retrieval, permission-aware fail-closed behavior, source-grounded snippets, feedback capture, and KPI hooks

This commit is contained in:
2026-05-06 16:27:03 +02:00
parent 80a3e59701
commit 1e3c6fe34a
13 changed files with 3173 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ from kontextual_engine.core import (
MetadataSchema,
MetadataSchemaAssignment,
RepresentationKind,
RetrievalFeedbackRecord,
Sensitivity,
)
from kontextual_engine.errors import NotFoundError, ValidationError
@@ -38,6 +39,7 @@ class InMemoryAssetRegistryRepository:
relationships: dict[str, CoreRelationship] = field(default_factory=dict)
versions: dict[str, list[AssetVersion]] = field(default_factory=dict)
audit_events: dict[str, AuditEvent] = field(default_factory=dict)
retrieval_feedback: dict[str, RetrievalFeedbackRecord] = field(default_factory=dict)
idempotency_records: dict[str, IdempotencyRecord] = field(default_factory=dict)
ingestion_jobs: dict[str, IngestionJob] = field(default_factory=dict)
@@ -253,6 +255,24 @@ class InMemoryAssetRegistryRepository:
events = [event for event in events if event.correlation_id == correlation_id]
return sorted(events, key=lambda event: event.occurred_at)
def save_retrieval_feedback(self, record: RetrievalFeedbackRecord) -> RetrievalFeedbackRecord:
self.get_actor(record.actor_id)
self.retrieval_feedback[record.feedback_id] = record
return record
def list_retrieval_feedback(
self,
*,
correlation_id: str | None = None,
label: str | None = None,
) -> list[RetrievalFeedbackRecord]:
records: Iterable[RetrievalFeedbackRecord] = self.retrieval_feedback.values()
if correlation_id is not None:
records = [record for record in records if record.correlation_id == correlation_id]
if label is not None:
records = [record for record in records if record.label.value == label]
return sorted(records, key=lambda record: (record.created_at, record.feedback_id))
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
self.idempotency_records[record.key] = record
return record

View File

@@ -24,6 +24,7 @@ from kontextual_engine.core import (
MetadataSchemaAssignment,
RepresentationKind,
RelationshipTargetKind,
RetrievalFeedbackRecord,
Sensitivity,
)
from kontextual_engine.errors import NotFoundError, ValidationError
@@ -466,6 +467,56 @@ class SQLiteAssetRegistryRepository:
rows = self._all(f"select payload from audit_events{where} order by occurred_at, rowid", tuple(params))
return [AuditEvent.from_dict(_loads(row["payload"])) for row in rows]
def save_retrieval_feedback(self, record: RetrievalFeedbackRecord) -> RetrievalFeedbackRecord:
try:
with self._connect() as conn:
conn.execute(
"""
insert into retrieval_feedback (id, label, actor_id, correlation_id, created_at, payload)
values (?, ?, ?, ?, ?, ?)
on conflict(id) do update set
label=excluded.label,
actor_id=excluded.actor_id,
correlation_id=excluded.correlation_id,
created_at=excluded.created_at,
payload=excluded.payload
""",
(
record.feedback_id,
record.label.value,
record.actor_id,
record.correlation_id,
record.created_at,
_json(record.to_dict()),
),
)
except sqlite3.IntegrityError as exc:
if _is_foreign_key_error(exc):
raise ValidationError(
"Retrieval feedback references an unknown actor",
details={"actor_id": record.actor_id, "feedback_id": record.feedback_id},
) from exc
raise
return record
def list_retrieval_feedback(
self,
*,
correlation_id: str | None = None,
label: str | None = None,
) -> list[RetrievalFeedbackRecord]:
clauses = []
params: list[Any] = []
if correlation_id is not None:
clauses.append("correlation_id = ?")
params.append(correlation_id)
if label is not None:
clauses.append("label = ?")
params.append(label)
where = f" where {' and '.join(clauses)}" if clauses else ""
rows = self._all(f"select payload from retrieval_feedback{where} order by created_at, id", tuple(params))
return [RetrievalFeedbackRecord.from_dict(_loads(row["payload"])) for row in rows]
def save_idempotency_record(self, record: IdempotencyRecord) -> IdempotencyRecord:
with self._connect() as conn:
conn.execute(
@@ -620,6 +671,15 @@ class SQLiteAssetRegistryRepository:
payload text not null,
foreign key(actor_id) references actors(id)
);
create table if not exists retrieval_feedback (
id text primary key,
label text not null,
actor_id text not null,
correlation_id text not null,
created_at text not null,
payload text not null,
foreign key(actor_id) references actors(id)
);
create table if not exists idempotency_records (
key text primary key,
operation text not null,
@@ -647,6 +707,8 @@ class SQLiteAssetRegistryRepository:
create index if not exists idx_versions_asset on asset_versions(asset_id);
create index if not exists idx_audit_target on audit_events(target);
create index if not exists idx_audit_correlation on audit_events(correlation_id);
create index if not exists idx_retrieval_feedback_label on retrieval_feedback(label);
create index if not exists idx_retrieval_feedback_correlation on retrieval_feedback(correlation_id);
create index if not exists idx_ingestion_jobs_status on ingestion_jobs(status);
create index if not exists idx_ingestion_jobs_correlation on ingestion_jobs(correlation_id);
"""