OpenSpace/openspace/skill_engine/evolution/audit.py
2026-06-02 16:28:29 +08:00

1283 lines
46 KiB
Python

"""Audit records and read-only audit service for skill evolution."""
from __future__ import annotations
import asyncio
import inspect
import json
import sqlite3
import threading
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,
)
from .job_completion import (
completion_after_recovery,
completion_from_outcome,
outcome_has_committing_action,
)
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 intervention surface for evidence-backed evolution audit data.
The service intentionally reads audit records and enqueues follow-up trigger
jobs. It does not author, validate, commit, mutate active skills, or refresh
the registry.
"""
def __init__(
self,
evidence_store: Any,
skill_store: Any | None = None,
*,
trigger_store: Any | None = None,
trigger_engine: Any | None = None,
candidate_store: Any | None = None,
evolution_engine: Any | None = None,
) -> None:
self.evidence_store = evidence_store
self.skill_store = skill_store
self.trigger_store = trigger_store or getattr(trigger_engine, "store", None)
self.trigger_engine = trigger_engine
self.candidate_store = candidate_store
self.evolution_engine = evolution_engine
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.
This is a read-only aggregation used by the dashboard. Candidate review
is actionable from the dashboard; admission/validation human-review rows
are surfaced so the operator can inspect the blocked decision path.
"""
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": "request_recheck",
"approval_available": True,
"blocking_stage": "candidate",
"review_note": (
"Approves this pending candidate for a CANDIDATE_RECHECK "
"job. Standalone dashboards queue the job when no live "
"evolution engine is attached."
),
}
)
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 request_candidate_recheck(
self,
candidate_id: str,
*,
run_now: bool = False,
) -> dict[str, Any]:
store = self._candidate_store()
request_recheck = getattr(store, "request_recheck", None)
if not callable(request_recheck):
raise RuntimeError("candidate store does not support request_recheck")
job_id = request_recheck(candidate_id)
outcomes: list[Any] = []
if run_now:
outcomes = self._run_job_now(job_id)
job = self.get_job(job_id)
candidate = store.load_candidate(candidate_id)
recheck_status = _recheck_status(
run_now=run_now,
engine_available=self.evolution_engine is not None,
outcomes=outcomes,
)
return {
"operation": "request_recheck",
"job_id": job_id,
"status": (job or {}).get("status", "pending"),
"job": job,
"run_now": bool(run_now),
"engine_available": self.evolution_engine is not None,
"recheck_status": recheck_status,
"recovery_required": _outcomes_need_recovery(outcomes),
"candidate": _to_dict(candidate) if candidate is not None else None,
"outcomes": [_outcome_summary(outcome) for outcome in outcomes],
}
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,
trigger_store=self.trigger_store,
trigger_engine=self.trigger_engine,
)
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 _run_job_now(self, job_id: str) -> list[Any]:
engine = self.evolution_engine
if engine is None:
return []
claimer = None
if self.trigger_engine is not None:
claimer = getattr(self.trigger_engine, "claim_jobs", None)
if not callable(claimer) and self.trigger_store is not None:
claimer = getattr(self.trigger_store, "claim_jobs", None)
jobs: list[Any] = []
if callable(claimer):
jobs = list(claimer([job_id], worker_id="dashboard") or [])
if not jobs:
fallback = None
if self.trigger_engine is not None:
fallback = getattr(self.trigger_engine, "claim_next", None)
if not callable(fallback) and self.trigger_store is not None:
fallback = getattr(self.trigger_store, "claim_next", None)
if callable(fallback):
jobs = list(
fallback(
limit=1,
worker_id="dashboard",
trigger_types=("CANDIDATE_RECHECK",),
)
or []
)
jobs = [
job
for job in jobs
if str(getattr(job, "job_id", "") or "") == job_id
]
outcomes: list[Any] = []
for job in jobs:
result = engine.process_job(job)
result = _resolve_awaitable_sync(result)
outcomes.append(result)
completion = completion_from_outcome(result)
if completion.needs_recovery:
recovered = self._recover_committing_actions()
completion = completion_after_recovery(result, recovered)
self._complete_run_now_job(job, result, completion=completion)
return outcomes
def _complete_run_now_job(
self,
job: Any,
outcome: Any,
*,
completion: Any | None = None,
) -> Any | None:
complete = None
if self.trigger_engine is not None:
complete = getattr(self.trigger_engine, "complete", None)
if not callable(complete) and self.trigger_store is not None:
complete = getattr(self.trigger_store, "complete", None)
if not callable(complete):
return None
completion = completion or completion_from_outcome(outcome)
complete(
str(getattr(job, "job_id", "") or ""),
status=completion.status,
result_ref=completion.result_ref,
error=completion.error,
)
return completion
def _recover_committing_actions(self) -> list[Any]:
engine = self.evolution_engine
recover = getattr(engine, "recover_committing_actions", None)
if not callable(recover):
return []
result = recover()
return list(_resolve_awaitable_sync(result) or [])
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 _outcome_summary(outcome: Any) -> dict[str, Any]:
decisions = list(getattr(outcome, "decisions", []) or [])
admissions = list(getattr(outcome, "admissions", []) or [])
candidates = list(getattr(outcome, "candidates", []) or [])
actions = list(getattr(outcome, "actions", []) or [])
evolved = list(getattr(outcome, "evolved_skill_records", []) or [])
return {
"job_id": str(getattr(outcome, "job_id", "") or ""),
"status": str(getattr(outcome, "status", "") or ""),
"decision_ids": [
str(getattr(item, "decision_id", "") or "")
for item in decisions
if getattr(item, "decision_id", None)
],
"admission_ids": [
str(getattr(item, "admission_id", "") or "")
for item in admissions
if getattr(item, "admission_id", None)
],
"candidate_ids": [
str(getattr(item, "candidate_id", "") or "")
for item in candidates
if getattr(item, "candidate_id", None)
],
"action_ids": [
str(getattr(item, "action_id", "") or "")
for item in actions
if getattr(item, "action_id", None)
],
"evolved_skill_ids": [
str(getattr(item, "skill_id", "") or "")
for item in evolved
if getattr(item, "skill_id", None)
],
"errors": [str(item) for item in (getattr(outcome, "errors", []) or [])],
}
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 _recheck_status(
*,
run_now: bool,
engine_available: bool,
outcomes: list[Any],
) -> str:
if not run_now:
return "queued_recheck"
if not engine_available:
return "queued_no_engine"
if _outcomes_need_recovery(outcomes):
return "needs_recovery"
if _outcomes_have_committed_action(outcomes):
return "executed_committed"
if outcomes:
return "executed_no_commit"
return "queued_recheck"
def _outcomes_have_committed_action(outcomes: list[Any]) -> bool:
for outcome in outcomes:
for action in getattr(outcome, "actions", []) or []:
status = _field_value(action, "commit_status")
if str(status or "").lower() in {"committed", "committed_reconciled"}:
return True
return False
def _outcomes_need_recovery(outcomes: list[Any]) -> bool:
return any(outcome_has_committing_action(outcome) for outcome in outcomes)
def _field_value(item: Any, name: str) -> Any:
if isinstance(item, dict):
return item.get(name)
return getattr(item, name, None)
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 _resolve_awaitable_sync(value: Any) -> Any:
if not inspect.isawaitable(value):
return value
try:
asyncio.get_running_loop()
except RuntimeError:
return _run_awaitable_in_new_loop(value)
return _run_awaitable_in_thread(value)
async def _await_value(value: Any) -> Any:
return await value
def _run_awaitable_in_thread(value: Any) -> Any:
result_box: dict[str, Any] = {}
def runner() -> None:
try:
result_box["value"] = _run_awaitable_in_new_loop(value)
except BaseException as exc:
result_box["error"] = exc
thread = threading.Thread(
target=runner,
name="openspace-evolution-audit-await",
daemon=True,
)
thread.start()
thread.join()
if "error" in result_box:
raise result_box["error"]
return result_box.get("value")
def _run_awaitable_in_new_loop(value: Any) -> Any:
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(_await_value(value))
finally:
loop.close()
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