OpenSpace/openspace/skill_engine/evolution/audit.py
2026-07-17 11:43:42 +08:00

1029 lines
37 KiB
Python

"""Audit records and read-only audit service for skill evolution."""
from __future__ import annotations
import json
import sqlite3
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any
from openspace.skill_engine.evidence.redaction import redact_text
from openspace.skill_engine.signals.types import (
STATUS_AGGREGATE_ONLY,
TRIGGERABLE_EVIDENCE_STATUSES,
)
EVOLUTION_ACTION_STATUSES: frozenset[str] = frozenset(
{
"committing",
"committed",
"failed",
"committed_reconciled",
"failed_needs_review",
}
)
_MAX_REF_READ_CHARS = 8_000
_DEFAULT_LIST_LIMIT = 100
_MAX_LIST_LIMIT = 500
@dataclass(frozen=True, slots=True)
class EvolutionActionRecord:
action_id: str
decision_id: str
trigger_job_id: str
authoring_id: str
validation_id: str
action_type: str
commit_status: str
skill_id: str | None
parent_skill_ids: list[str]
changed_files: list[str]
evidence_refs: list[str]
staging_dir: str
active_target_dir: str
backup_dir: str | None
failure_reason: str | None
created_at: str
committed_at: str | None
skill_record: Any | None = field(default=None, compare=False, repr=False)
def to_dict(self) -> dict[str, Any]:
data = asdict(self)
data.pop("skill_record", None)
return data
class EvidenceRefAccessError(RuntimeError):
"""Raised when an evidence ref exists but must not be read by the API."""
def __init__(self, reason: str, *, status_code: int = 403) -> None:
super().__init__(reason)
self.reason = reason
self.status_code = status_code
class EvolutionAuditService:
"""Query and rejection surface for evidence-backed evolution audit data."""
def __init__(
self,
evidence_store: Any,
skill_store: Any | None = None,
*,
candidate_store: Any | None = None,
) -> None:
self.evidence_store = evidence_store
self.skill_store = skill_store
self.candidate_store = candidate_store
self._ref_read_root_skill_ids: set[str] = set()
def list_jobs(
self,
status: str | None = None,
limit: int = _DEFAULT_LIST_LIMIT,
) -> list[dict[str, Any]]:
capped_limit = _limit(limit)
normalized_status = _none_if_all(status)
with self._reader() as conn:
if not _table_exists(conn, "trigger_jobs"):
return []
if normalized_status is None:
rows = conn.execute(
"""
SELECT * FROM trigger_jobs
ORDER BY created_at DESC, job_id
LIMIT ?
""",
(capped_limit,),
).fetchall()
else:
rows = conn.execute(
"""
SELECT * FROM trigger_jobs
WHERE status=?
ORDER BY created_at DESC, job_id
LIMIT ?
""",
(normalized_status, capped_limit),
).fetchall()
return [self._job_from_row(conn, row) for row in rows]
def get_job(self, job_id: str) -> dict[str, Any] | None:
with self._reader() as conn:
if not _table_exists(conn, "trigger_jobs"):
return None
row = conn.execute(
"SELECT * FROM trigger_jobs WHERE job_id=?",
(job_id,),
).fetchone()
return self._job_from_row(conn, row) if row is not None else None
def get_packet(self, packet_id: str) -> dict[str, Any] | None:
load_packet = getattr(self.evidence_store, "load_packet", None)
if callable(load_packet):
packet = load_packet(packet_id)
if packet is not None:
return packet.to_dict()
with self._reader() as conn:
if not _table_exists(conn, "evidence_packets"):
return None
row = conn.execute(
"SELECT packet_json FROM evidence_packets WHERE packet_id=?",
(packet_id,),
).fetchone()
if row is None:
return None
data = _json_object(row["packet_json"])
return data or None
def get_decision(self, decision_id: str) -> dict[str, Any] | None:
with self._reader() as conn:
if not _table_exists(conn, "decision_rationales"):
return None
row = conn.execute(
"SELECT * FROM decision_rationales WHERE decision_id=?",
(decision_id,),
).fetchone()
if row is None:
return None
decision = _decision_from_row(conn, row)
decision["admission"] = _latest_admission_for_decision(conn, decision_id)
decision["candidate_ids"] = _ids(
conn,
"evolution_candidates",
"candidate_id",
"decision_id=?",
(decision_id,),
)
decision["action_ids"] = _ids(
conn,
"evolution_actions",
"action_id",
"decision_id=?",
(decision_id,),
)
return decision
def list_candidates(
self,
status: str = "pending",
limit: int = _DEFAULT_LIST_LIMIT,
) -> list[dict[str, Any]]:
normalized_status = _none_if_all(status)
store = self._candidate_store()
candidates = store.list_candidates(
status=normalized_status or "",
limit=_limit(limit),
)
return [_to_dict(candidate) for candidate in candidates]
def get_candidate(self, candidate_id: str) -> dict[str, Any] | None:
store = self._candidate_store()
candidate = store.load_candidate(candidate_id)
return _to_dict(candidate) if candidate is not None else None
def list_review_items(self, limit: int = _DEFAULT_LIST_LIMIT) -> list[dict[str, Any]]:
"""Return the human-facing evolution review queue.
Candidate, admission, and validation rows are inspect-only. Rejecting an
audit candidate is a separate terminal operation and never re-enters the
evolution engine.
"""
capped_limit = _limit(limit)
items: list[dict[str, Any]] = []
try:
pending_candidates = self.list_candidates(status="pending", limit=capped_limit)
except Exception:
pending_candidates = []
for candidate in pending_candidates:
candidate_id = str(candidate.get("candidate_id") or "")
items.append(
{
"item_id": f"candidate:{candidate_id}",
"item_type": "candidate",
"status": str(candidate.get("status") or "pending"),
"title": str(candidate.get("proposed_action") or "Candidate review"),
"summary": _candidate_review_summary(candidate),
"created_at": str(candidate.get("created_at") or ""),
"updated_at": str(candidate.get("updated_at") or candidate.get("created_at") or ""),
"candidate_id": candidate_id,
"decision_id": str(candidate.get("decision_id") or ""),
"admission_id": str(candidate.get("admission_id") or ""),
"packet_id": "",
"validation_id": "",
"action_kind": "inspect",
"approval_available": False,
"blocking_stage": "candidate",
"review_note": (
"Audit-only candidate. It can be inspected or rejected, "
"but it never auto-promotes into a skill."
),
}
)
with self._reader() as conn:
if _table_exists(conn, "admission_results"):
rows = conn.execute(
"""
SELECT * FROM admission_results
WHERE outcome IN ('needs_human_review', 'human_review')
ORDER BY created_at DESC, admission_id
LIMIT ?
""",
(capped_limit,),
).fetchall()
for row in rows:
admission_id = str(row["admission_id"] or "")
failures = _json_list(row["hard_failures_json"])
warnings = _json_list(row["warnings_json"])
items.append(
{
"item_id": f"admission:{admission_id}",
"item_type": "admission",
"status": str(row["outcome"] or ""),
"title": "Admission needs review",
"summary": _review_summary(failures, warnings),
"created_at": str(row["created_at"] or ""),
"updated_at": str(row["created_at"] or ""),
"candidate_id": "",
"decision_id": str(row["decision_id"] or ""),
"admission_id": admission_id,
"packet_id": str(row["packet_id"] or ""),
"validation_id": "",
"action_kind": "inspect",
"approval_available": False,
"blocking_stage": "admission",
"review_note": (
"Admission human review is inspect-only in the "
"dashboard. It cannot be overridden to direct; "
"add evidence or issue an explicit manual evolve "
"request to produce a new job."
),
}
)
if _table_exists(conn, "validation_results"):
rows = conn.execute(
"""
SELECT * FROM validation_results
WHERE outcome IN ('needs_human_review', 'human_review')
ORDER BY checked_at DESC, validation_id
LIMIT ?
""",
(capped_limit,),
).fetchall()
for row in rows:
validation_id = str(row["validation_id"] or "")
failures = _json_list(row["deterministic_failures_json"])
warnings = _json_list(row["semantic_warnings_json"])
items.append(
{
"item_id": f"validation:{validation_id}",
"item_type": "validation",
"status": str(row["outcome"] or ""),
"title": "Validation needs review",
"summary": _review_summary(failures, warnings),
"created_at": str(row["checked_at"] or ""),
"updated_at": str(row["checked_at"] or ""),
"candidate_id": "",
"decision_id": str(row["decision_id"] or ""),
"admission_id": "",
"packet_id": str(row["packet_id"] or ""),
"validation_id": validation_id,
"action_kind": "inspect",
"approval_available": False,
"blocking_stage": "validation",
"review_note": (
"Validation human review is inspect-only in the "
"dashboard. It cannot override validator warnings "
"or commit staged edits; resolve the warning or "
"rerun through a deliberate manual evolution path."
),
}
)
items.sort(key=lambda item: str(item.get("updated_at") or item.get("created_at") or ""), reverse=True)
return items[:capped_limit]
def list_quality_signals(
self,
*,
subject_type: str | None = None,
subject_id: str | None = None,
actionability: str | None = None,
not_triggerable: bool = False,
limit: int = _DEFAULT_LIST_LIMIT,
) -> list[dict[str, Any]]:
capped_limit = _limit(limit)
with self._reader() as conn:
if not _table_exists(conn, "quality_signal_index"):
return []
clauses: list[str] = []
params: list[Any] = []
if subject_type:
clauses.append("subject_type=?")
params.append(subject_type)
if subject_id:
clauses.append("subject_id=?")
params.append(subject_id)
if actionability:
clauses.append("actionability=?")
params.append(actionability)
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
order_by = """
ORDER BY COALESCE(signal_write_watermark, source_watermark, 0) DESC,
updated_at DESC,
signal_id
"""
jobs = _quality_signal_jobs_by_signal_ref(conn)
if not_triggerable:
items: list[dict[str, Any]] = []
offset = 0
page_size = min(_MAX_LIST_LIMIT, max(capped_limit, _DEFAULT_LIST_LIMIT))
while len(items) < capped_limit:
rows = conn.execute(
f"""
SELECT * FROM quality_signal_index
{where}
{order_by}
LIMIT ? OFFSET ?
""",
(*params, page_size, offset),
).fetchall()
if not rows:
break
for row in rows:
item = self._quality_signal_audit_row(conn, row, jobs)
if item.get("not_triggerable_reason"):
items.append(item)
if len(items) >= capped_limit:
break
offset += len(rows)
return items[:capped_limit]
rows = conn.execute(
f"""
SELECT * FROM quality_signal_index
{where}
{order_by}
LIMIT ?
""",
(*params, capped_limit),
).fetchall()
items = [
self._quality_signal_audit_row(conn, row, jobs)
for row in rows
]
return items[:capped_limit]
def list_quality_signal_jobs(
self,
*,
limit: int = _DEFAULT_LIST_LIMIT,
) -> list[dict[str, Any]]:
capped_limit = _limit(limit)
with self._reader() as conn:
if not _table_exists(conn, "trigger_jobs"):
return []
rows = conn.execute(
"""
SELECT * FROM trigger_jobs
WHERE trigger_type='QUALITY_SIGNAL'
ORDER BY created_at DESC, job_id
LIMIT ?
""",
(capped_limit,),
).fetchall()
items: list[dict[str, Any]] = []
for row in rows:
signal_ref_ids = _quality_signal_ref_ids_from_job_row(row)
if signal_ref_ids and _table_exists(conn, "quality_signal_index"):
for signal_ref in signal_ref_ids:
signal_row = conn.execute(
"""
SELECT * FROM quality_signal_index
WHERE ref_id=?
LIMIT 1
""",
(signal_ref,),
).fetchone()
if signal_row is not None:
items.append(
self._quality_signal_audit_row(
conn,
signal_row,
{signal_ref: [row]},
)
)
continue
items.append(_quality_signal_job_only_row(conn, row, signal_ref))
else:
items.append(_quality_signal_job_only_row(conn, row, None))
return items[:capped_limit]
def reject_candidate(self, candidate_id: str, reason: str) -> dict[str, Any]:
store = self._candidate_store()
candidate = store.reject_candidate(candidate_id, reason or "manual reject")
return _to_dict(candidate)
def get_action(self, action_id: str) -> dict[str, Any] | None:
load_action = getattr(self.evidence_store, "load_action", None)
action = load_action(action_id) if callable(load_action) else None
if action is None:
return None
payload = _to_dict(action)
load_validation = getattr(self.evidence_store, "load_validation", None)
if callable(load_validation) and payload.get("validation_id"):
validation = load_validation(str(payload["validation_id"]))
payload["validation"] = _to_dict(validation) if validation is not None else None
decision = self.get_decision(str(payload.get("decision_id") or ""))
if decision is not None:
payload["decision"] = decision
failures = getattr(self.evidence_store, "list_action_failures", None)
if callable(failures):
payload["failures"] = failures(action_id)
return payload
def get_ref(
self,
ref_id: str,
*,
include_preview: bool = True,
) -> dict[str, Any] | None:
get_ref = getattr(self.evidence_store, "get_ref", None)
ref = get_ref(ref_id) if callable(get_ref) else None
if ref is None:
return None
payload = ref.to_dict()
if not include_preview or bool(payload.get("contains_secret")):
payload["preview"] = ""
return payload
def read_ref(self, ref_id: str, max_chars: int = _MAX_REF_READ_CHARS) -> dict[str, Any]:
get_ref = getattr(self.evidence_store, "get_ref", None)
ref = get_ref(ref_id) if callable(get_ref) else None
if ref is None:
raise KeyError(ref_id)
if bool(getattr(ref, "contains_secret", False)):
raise EvidenceRefAccessError("contains_secret")
cap = min(max(0, int(max_chars or _MAX_REF_READ_CHARS)), _MAX_REF_READ_CHARS)
path = _path_from_uri(getattr(ref, "uri", None))
source = "preview"
original_length = len(getattr(ref, "preview", "") or "")
text = getattr(ref, "preview", "") or ""
if path is not None:
resolved = path.expanduser()
self._ensure_ref_read_roots(ref)
if not self._path_read_allowed(resolved):
raise EvidenceRefAccessError("outside_allowed_roots")
if not resolved.is_file():
raise EvidenceRefAccessError("missing_or_not_file", status_code=404)
try:
text = resolved.read_text(encoding="utf-8", errors="replace")
except OSError as exc:
raise EvidenceRefAccessError(str(exc), status_code=404) from exc
source = "file"
original_length = len(text)
redacted = redact_text(text)
truncated = len(redacted) > cap
content = redacted[:cap]
return {
"ref": self.get_ref(ref_id, include_preview=False),
"ref_id": ref_id,
"source": source,
"content": content,
"max_chars": cap,
"original_length": original_length,
"truncated": truncated,
}
def _reader(self) -> Any:
reader = getattr(self.evidence_store, "_reader", None)
if not callable(reader):
raise RuntimeError("EvidenceStore reader is unavailable")
return reader()
def _candidate_store(self) -> Any:
if self.candidate_store is not None:
return self.candidate_store
from openspace.skill_engine.evolution.candidates import EvolutionCandidateStore
self.candidate_store = EvolutionCandidateStore(
evidence_store=self.evidence_store,
)
return self.candidate_store
def _path_read_allowed(self, path: Path) -> bool:
checker = getattr(self.evidence_store, "_path_read_allowed", None)
if callable(checker):
try:
return bool(checker(path))
except Exception:
return False
return False
def _ensure_ref_read_roots(self, ref: Any) -> None:
"""Allow a referenced skill file without scanning every skill at startup."""
if self.skill_store is None:
return
add_root = getattr(self.evidence_store, "add_allowed_read_root", None)
load_record = getattr(self.skill_store, "load_record", None)
if not callable(add_root) or not callable(load_record):
return
for skill_id in _skill_ids_from_ref(ref):
if skill_id in self._ref_read_root_skill_ids:
continue
try:
record = load_record(skill_id)
except Exception:
continue
record_path = getattr(record, "path", None)
if not record_path:
continue
try:
add_root(_skill_record_read_root(record_path))
except Exception:
continue
self._ref_read_root_skill_ids.add(skill_id)
def _job_from_row(self, conn: sqlite3.Connection, row: sqlite3.Row) -> dict[str, Any]:
payload = dict(row)
payload["reason_tags"] = _json_list(payload.pop("reason_tags_json", "[]"))
payload["scope"] = _json_object(payload.pop("scope_json", "{}"))
payload["profile_fallback"] = bool(payload.get("profile_fallback"))
payload.update(_job_links(conn, str(payload.get("job_id") or "")))
return payload
def _quality_signal_audit_row(
self,
conn: sqlite3.Connection,
row: sqlite3.Row,
jobs_by_signal_ref: dict[str, list[sqlite3.Row]],
) -> dict[str, Any]:
ref_id = str(row["ref_id"] or "")
ref = self.get_ref(ref_id, include_preview=False)
metadata = dict((ref or {}).get("metadata") or {})
raw_backrefs = list((ref or {}).get("raw_backrefs") or [])
job_rows = jobs_by_signal_ref.get(ref_id) or []
job_row = job_rows[0] if job_rows else None
admission = (
_latest_admission_for_job(conn, str(job_row["job_id"] or ""))
if job_row is not None
else None
)
not_triggerable_reason = _quality_signal_not_triggerable_reason(
signal_type=str(row["signal_type"] or metadata.get("signal_type") or ""),
actionability=str(row["actionability"] or metadata.get("actionability") or ""),
evidence_status=str(row["evidence_status"] or metadata.get("evidence_status") or ""),
raw_backref_count=len(raw_backrefs),
has_job=job_row is not None,
)
return {
"signal_ref": ref_id,
"signal_type": str(row["signal_type"] or metadata.get("signal_type") or ""),
"subject_type": str(row["subject_type"] or metadata.get("subject_type") or ""),
"subject_id": str(row["subject_id"] or metadata.get("subject_id") or ""),
"tool_key": str(metadata.get("tool_key") or ""),
"skill_id": str(metadata.get("skill_id") or ""),
"actionability": str(row["actionability"] or metadata.get("actionability") or ""),
"evidence_status": str(row["evidence_status"] or metadata.get("evidence_status") or ""),
"merge_key": str(row["merge_key"] or metadata.get("merge_key") or ""),
"raw_backref_count": len(raw_backrefs),
"job_id": str(job_row["job_id"] or "") if job_row is not None else "",
"job_status": str(job_row["status"] or "") if job_row is not None else "",
"admission_status": str((admission or {}).get("outcome") or ""),
"admission_hard_failures": list((admission or {}).get("hard_failures") or []),
"admission_warnings": list((admission or {}).get("warnings") or []),
"not_triggerable_reason": not_triggerable_reason,
}
def _job_links(conn: sqlite3.Connection, job_id: str) -> dict[str, list[str]]:
packet_ids = _ids(
conn,
"evidence_packets",
"packet_id",
"trigger_job_id=?",
(job_id,),
)
decision_ids = _ids(
conn,
"decision_rationales",
"decision_id",
"trigger_job_id=?",
(job_id,),
)
admission_ids = _ids_for_values(
conn,
table="admission_results",
id_column="admission_id",
match_column="decision_id",
values=decision_ids,
)
candidate_ids = _ids_for_values(
conn,
table="evolution_candidates",
id_column="candidate_id",
match_column="decision_id",
values=decision_ids,
)
action_ids = _ids(
conn,
"evolution_actions",
"action_id",
"trigger_job_id=?",
(job_id,),
)
validation_ids = _ids_for_values(
conn,
table="validation_results",
id_column="validation_id",
match_column="decision_id",
values=decision_ids,
)
return {
"packet_ids": packet_ids,
"decision_ids": decision_ids,
"admission_ids": admission_ids,
"candidate_ids": candidate_ids,
"validation_ids": validation_ids,
"action_ids": action_ids,
}
def _quality_signal_jobs_by_signal_ref(
conn: sqlite3.Connection,
) -> dict[str, list[sqlite3.Row]]:
if not _table_exists(conn, "trigger_jobs"):
return {}
rows = conn.execute(
"""
SELECT * FROM trigger_jobs
WHERE trigger_type='QUALITY_SIGNAL'
ORDER BY created_at DESC, job_id
"""
).fetchall()
by_ref: dict[str, list[sqlite3.Row]] = {}
for row in rows:
for ref_id in _quality_signal_ref_ids_from_job_row(row):
by_ref.setdefault(ref_id, []).append(row)
return by_ref
def _quality_signal_ref_ids_from_job_row(row: sqlite3.Row) -> list[str]:
scope = _json_object(row["scope_json"])
refs = _str_values(scope.get("representative_execution_ids"))
return [ref for ref in refs if ref.startswith("quality_signal:")]
def _latest_admission_for_job(
conn: sqlite3.Connection,
job_id: str,
) -> dict[str, Any] | None:
decision_ids = _ids(
conn,
"decision_rationales",
"decision_id",
"trigger_job_id=?",
(job_id,),
)
latest: dict[str, Any] | None = None
for decision_id in decision_ids:
admission = _latest_admission_for_decision(conn, decision_id)
if admission is None:
continue
if latest is None or str(admission.get("created_at") or "") > str(latest.get("created_at") or ""):
latest = admission
return latest
def _quality_signal_job_only_row(
conn: sqlite3.Connection,
row: sqlite3.Row,
signal_ref: str | None,
) -> dict[str, Any]:
admission = _latest_admission_for_job(conn, str(row["job_id"] or ""))
return {
"signal_ref": signal_ref or "",
"signal_type": str(row["reason"] or ""),
"subject_type": "",
"subject_id": "",
"tool_key": "",
"skill_id": "",
"actionability": "",
"evidence_status": "",
"merge_key": "",
"raw_backref_count": 0,
"job_id": str(row["job_id"] or ""),
"job_status": str(row["status"] or ""),
"admission_status": str((admission or {}).get("outcome") or ""),
"admission_hard_failures": list((admission or {}).get("hard_failures") or []),
"admission_warnings": list((admission or {}).get("warnings") or []),
"not_triggerable_reason": "",
}
def _quality_signal_not_triggerable_reason(
*,
signal_type: str,
actionability: str,
evidence_status: str,
raw_backref_count: int,
has_job: bool,
) -> str:
if (
signal_type == "aggregate_without_incident"
or evidence_status == STATUS_AGGREGATE_ONLY
):
return "quality_signal_aggregate_only"
if actionability != "trigger_review":
return "quality_signal_not_trigger_review"
if evidence_status not in TRIGGERABLE_EVIDENCE_STATUSES:
return "quality_signal_incomplete"
if raw_backref_count <= 0:
return "missing_raw_backrefs"
if not has_job:
return "quality_signal_job_missing"
return ""
def _decision_from_row(
conn: sqlite3.Connection,
row: sqlite3.Row,
) -> dict[str, Any]:
decision_id = str(row["decision_id"])
claims = []
if _table_exists(conn, "decision_evidence_claims"):
claim_rows = conn.execute(
"""
SELECT claim, refs_json, confidence
FROM decision_evidence_claims
WHERE decision_id=?
ORDER BY id
""",
(decision_id,),
).fetchall()
claims = [
{
"claim": str(item["claim"] or ""),
"refs": _json_list(item["refs_json"]),
"confidence": str(item["confidence"] or "low"),
}
for item in claim_rows
]
return {
"decision_id": decision_id,
"trigger_job_id": str(row["trigger_job_id"] or ""),
"packet_id": str(row["packet_id"] or ""),
"proposed_action": str(row["proposed_action"] or ""),
"candidate_policy": str(row["candidate_policy"] or ""),
"target_skill_ids": _json_list(row["target_skill_ids_json"]),
"reason_summary": str(row["reason_summary"] or ""),
"reason_tags": _json_list(row["reason_tags_json"]),
"evidence_claims": claims,
"confidence": float(row["confidence"] or 0.0),
"risks": _json_list(row["risks_json"]),
"source_analysis_id": _none_or_str(row["source_analysis_id"]),
"noop_reason": _none_or_str(row["noop_reason"]),
"analyzed_by": str(row["analyzed_by"] or ""),
"created_at": str(row["created_at"] or ""),
}
def _latest_admission_for_decision(
conn: sqlite3.Connection,
decision_id: str,
) -> dict[str, Any] | None:
if not _table_exists(conn, "admission_results"):
return None
row = conn.execute(
"""
SELECT * FROM admission_results
WHERE decision_id=?
ORDER BY created_at DESC, admission_id DESC
LIMIT 1
""",
(decision_id,),
).fetchone()
if row is None:
return None
return {
"admission_id": str(row["admission_id"] or ""),
"decision_id": str(row["decision_id"] or ""),
"packet_id": str(row["packet_id"] or ""),
"outcome": str(row["outcome"] or ""),
"hard_failures": _json_list(row["hard_failures_json"]),
"warnings": _json_list(row["warnings_json"]),
"required_refs_checked": _json_list(row["required_refs_checked_json"]),
"reviewed_by": str(row["reviewed_by"] or ""),
"created_at": str(row["created_at"] or ""),
}
def _ids(
conn: sqlite3.Connection,
table: str,
id_column: str,
where: str,
params: tuple[Any, ...],
) -> list[str]:
if not _table_exists(conn, table):
return []
rows = conn.execute(
f"SELECT {id_column} FROM {table} WHERE {where} ORDER BY {id_column}",
params,
).fetchall()
return [str(row[id_column]) for row in rows if str(row[id_column])]
def _ids_for_values(
conn: sqlite3.Connection,
*,
table: str,
id_column: str,
match_column: str,
values: list[str],
) -> list[str]:
if not values or not _table_exists(conn, table):
return []
placeholders = ",".join("?" for _ in values)
rows = conn.execute(
f"""
SELECT {id_column} FROM {table}
WHERE {match_column} IN ({placeholders})
ORDER BY {id_column}
""",
tuple(values),
).fetchall()
return [str(row[id_column]) for row in rows if str(row[id_column])]
def _candidate_review_summary(candidate: dict[str, Any]) -> str:
needed = [str(item) for item in (candidate.get("needed_evidence") or []) if item]
blocked = str(candidate.get("blocked_reason") or "").strip()
if needed:
return "needs evidence: " + ", ".join(needed[:3])
if blocked:
return blocked
return "pending candidate review"
def _review_summary(primary: list[str], secondary: list[str]) -> str:
values = [str(item) for item in [*primary, *secondary] if str(item or "").strip()]
if not values:
return "needs human review"
return ", ".join(values[:3])
def _skill_ids_from_ref(ref: Any) -> list[str]:
values: list[str] = []
metadata = getattr(ref, "metadata", None)
if isinstance(metadata, dict):
for key in (
"skill_id",
"skill_ids",
"target_skill_id",
"target_skill_ids",
"affected_skill_id",
"affected_skill_ids",
"parent_skill_id",
"parent_skill_ids",
):
values.extend(_str_values(metadata.get(key)))
values.extend(_skill_ids_from_ref_token(getattr(ref, "ref_id", None)))
for backref in _str_values(getattr(ref, "raw_backrefs", None)):
values.extend(_skill_ids_from_ref_token(backref))
result: list[str] = []
seen: set[str] = set()
for value in values:
skill_id = value.strip()
if not skill_id or skill_id in seen:
continue
seen.add(skill_id)
result.append(skill_id)
return result
def _skill_ids_from_ref_token(value: Any) -> list[str]:
text = str(value or "")
for prefix in ("skill_file:", "skill_record:", "skill_event:"):
if text.startswith(prefix):
skill_id = text.removeprefix(prefix).split(":", 1)[0].strip()
return [skill_id] if skill_id else []
return []
def _str_values(value: Any) -> list[str]:
if value is None:
return []
if isinstance(value, (list, tuple, set)):
return [str(item) for item in value if str(item)]
if isinstance(value, str) and value.strip().startswith("["):
try:
loaded = json.loads(value)
except Exception:
loaded = None
if isinstance(loaded, list):
return [str(item) for item in loaded if str(item)]
return [str(value)] if str(value) else []
def _skill_record_read_root(path: Any) -> Path:
resolved = Path(path).expanduser()
if resolved.name == "SKILL.md" or resolved.suffix:
return resolved.parent
return resolved
def _path_from_uri(uri: Any) -> Path | None:
if not uri:
return None
text = str(uri).split("#", 1)[0].strip()
if not text:
return None
if text.startswith("file://"):
from urllib.parse import unquote, urlparse
parsed = urlparse(text)
return Path(unquote(parsed.path))
if "://" in text:
return None
return Path(text)
def _table_exists(conn: sqlite3.Connection, table: str) -> bool:
row = conn.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
(table,),
).fetchone()
return row is not None
def _to_dict(value: Any) -> dict[str, Any]:
if value is None:
return {}
method = getattr(value, "to_dict", None)
if callable(method):
return method()
if isinstance(value, dict):
return dict(value)
if hasattr(value, "__dict__"):
return dict(value.__dict__)
return {"value": value}
def _limit(value: int) -> int:
try:
limit = int(value)
except (TypeError, ValueError):
limit = _DEFAULT_LIST_LIMIT
return max(1, min(limit, _MAX_LIST_LIMIT))
def _none_if_all(value: str | None) -> str | None:
if value is None:
return None
text = str(value).strip()
if not text or text.lower() == "all":
return None
return text
def _json_object(value: Any) -> dict[str, Any]:
if isinstance(value, dict):
return dict(value)
if not value:
return {}
try:
loaded = json.loads(str(value))
except Exception:
return {}
return loaded if isinstance(loaded, dict) else {}
def _json_list(value: Any) -> list[str]:
if isinstance(value, list):
return [str(item) for item in value if str(item)]
if not value:
return []
try:
loaded = json.loads(str(value))
except Exception:
return []
if not isinstance(loaded, list):
return []
return [str(item) for item in loaded if str(item)]
def _none_or_str(value: Any) -> str | None:
if value is None:
return None
text = str(value)
return text if text else None