Files
awoooi/apps/api/src/services/agent_nemotron_replay_preflight.py
Your Name cfb866d055
Some checks failed
Ansible Lint / lint (push) Successful in 35s
CD Pipeline / tests (push) Failing after 13s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Failing after 11s
feat(governance): add agent market automation surfaces
2026-06-04 21:50:55 +08:00

360 lines
13 KiB
Python

"""
NeMo/Nemotron External Runner Preflight
======================================
Validates the local request pack before it is handed to an approved external
NeMo/NIM/Nemotron runner. This module does not call external services, tools,
production systems, or LLMs.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import Any
from src.services.agent_nemotron_replay_adapter import (
NEMOTRON_CANDIDATE_ID,
REQUEST_SCHEMA_VERSION,
)
from src.services.agent_replay_input import assert_no_evaluation_label_leak
PREFLIGHT_SCHEMA_VERSION = "agent_nemotron_external_runner_preflight_v1"
_REQUIRED_RESPONSE_FIELDS = {
"proposed_action",
"action_plan",
"risk_level",
"requires_human_approval",
"blocked_by_policy",
}
_FORBIDDEN_TEXT_MARKERS = {
"evaluation_labels",
"verification_result",
"execution_success",
"execution_error",
"self_healing_score",
"rca_correct",
"tool_dry_run_pass",
"repair_success",
"false_repair",
}
_SENSITIVE_TEXT_MARKERS = {
"authorization",
"bearer ",
"basic ",
"password",
"passwd",
"api_key",
"secret",
"token",
}
@dataclass(frozen=True)
class NemotronExternalRunnerPreflightReport:
"""Preflight decision for a NeMo external replay request pack."""
fixtures: int
candidate_inputs: int
requests: int
valid: bool
failures: list[str] = field(default_factory=list)
duplicate_fixtures: list[str] = field(default_factory=list)
duplicate_candidate_inputs: list[str] = field(default_factory=list)
duplicate_requests: list[str] = field(default_factory=list)
missing_candidate_inputs: list[str] = field(default_factory=list)
missing_requests: list[str] = field(default_factory=list)
unexpected_candidate_inputs: list[str] = field(default_factory=list)
unexpected_requests: list[str] = field(default_factory=list)
candidate_input_label_leak_records: int = 0
request_context_label_leak_records: int = 0
request_only_records: int = 0
not_replacement_evidence_records: int = 0
expected_action_marker_records: int = 0
sensitive_marker_present_in_context: bool = False
sensitive_marker_records: int = 0
sensitive_marker_distribution: dict[str, int] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
return {
"schema_version": PREFLIGHT_SCHEMA_VERSION,
"candidate_id": NEMOTRON_CANDIDATE_ID,
"fixtures": self.fixtures,
"candidate_inputs": self.candidate_inputs,
"requests": self.requests,
"valid": self.valid,
"failures": list(self.failures),
"duplicate_fixtures": list(self.duplicate_fixtures),
"duplicate_candidate_inputs": list(self.duplicate_candidate_inputs),
"duplicate_requests": list(self.duplicate_requests),
"missing_candidate_inputs": list(self.missing_candidate_inputs),
"missing_requests": list(self.missing_requests),
"unexpected_candidate_inputs": list(self.unexpected_candidate_inputs),
"unexpected_requests": list(self.unexpected_requests),
"candidate_input_label_leak_records": self.candidate_input_label_leak_records,
"request_context_label_leak_records": self.request_context_label_leak_records,
"request_only_records": self.request_only_records,
"not_replacement_evidence_records": self.not_replacement_evidence_records,
"expected_action_marker_records": self.expected_action_marker_records,
"sensitive_marker_present_in_context": self.sensitive_marker_present_in_context,
"sensitive_marker_records": self.sensitive_marker_records,
"sensitive_marker_distribution": dict(self.sensitive_marker_distribution),
}
def evaluate_nemotron_external_runner_preflight(
*,
fixtures: list[dict[str, Any]],
candidate_inputs: list[dict[str, Any]],
requests: list[dict[str, Any]],
) -> NemotronExternalRunnerPreflightReport:
"""Validate request-pack readiness before an external NeMo runner consumes it."""
failures: list[str] = []
fixture_index, duplicate_fixtures = _index_records(fixtures, "fixture", failures)
input_index, duplicate_inputs = _index_records(
candidate_inputs,
"candidate_input",
failures,
)
request_index, duplicate_requests = _index_records(requests, "request", failures)
fixture_keys = set(fixture_index)
input_keys = set(input_index)
request_keys = set(request_index)
missing_inputs = sorted(_render_key(key) for key in fixture_keys - input_keys)
unexpected_inputs = sorted(_render_key(key) for key in input_keys - fixture_keys)
missing_requests = sorted(_render_key(key) for key in input_keys - request_keys)
unexpected_requests = sorted(_render_key(key) for key in request_keys - input_keys)
if missing_inputs:
failures.append(f"missing_candidate_inputs:{','.join(missing_inputs)}")
if unexpected_inputs:
failures.append(
f"unexpected_candidate_inputs:{','.join(unexpected_inputs)}"
)
if missing_requests:
failures.append(f"missing_requests:{','.join(missing_requests)}")
if unexpected_requests:
failures.append(f"unexpected_requests:{','.join(unexpected_requests)}")
candidate_input_label_leak_records = _candidate_input_label_leaks(
candidate_inputs,
failures,
)
request_context_label_leak_records = _request_context_label_leaks(
requests,
failures,
)
request_only_records = _count_request_metadata(requests, "request_only", True)
not_replacement_evidence_records = _count_request_metadata(
requests,
"not_replacement_evidence",
True,
)
expected_action_marker_records = sum(
1
for fixture in fixtures
if _expected_action_markers(fixture)
)
sensitive_marker_records, sensitive_marker_distribution = _sensitive_marker_scan(
candidate_inputs,
requests,
)
sensitive_marker_present = sensitive_marker_records > 0
if sensitive_marker_present:
failures.append(f"sensitive_marker_present_in_context:{sensitive_marker_records}")
_validate_requests(requests, failures)
_validate_context_alignment(
fixture_index=fixture_index,
input_index=input_index,
request_index=request_index,
failures=failures,
)
return NemotronExternalRunnerPreflightReport(
fixtures=len(fixtures),
candidate_inputs=len(candidate_inputs),
requests=len(requests),
valid=not failures,
failures=failures,
duplicate_fixtures=duplicate_fixtures,
duplicate_candidate_inputs=duplicate_inputs,
duplicate_requests=duplicate_requests,
missing_candidate_inputs=missing_inputs,
missing_requests=missing_requests,
unexpected_candidate_inputs=unexpected_inputs,
unexpected_requests=unexpected_requests,
candidate_input_label_leak_records=candidate_input_label_leak_records,
request_context_label_leak_records=request_context_label_leak_records,
request_only_records=request_only_records,
not_replacement_evidence_records=not_replacement_evidence_records,
expected_action_marker_records=expected_action_marker_records,
sensitive_marker_present_in_context=sensitive_marker_present,
sensitive_marker_records=sensitive_marker_records,
sensitive_marker_distribution=sensitive_marker_distribution,
)
def _index_records(
records: list[dict[str, Any]],
name: str,
failures: list[str],
) -> tuple[dict[tuple[str, str], dict[str, Any]], list[str]]:
indexed: dict[tuple[str, str], dict[str, Any]] = {}
duplicates: list[str] = []
for line_number, record in enumerate(records, start=1):
key = _run_incident_key(record)
if key is None:
failures.append(f"invalid_{name}:line_{line_number}:missing_run_or_incident")
continue
if key in indexed:
rendered = _render_key(key)
duplicates.append(rendered)
failures.append(f"duplicate_{name}:line_{line_number}:{rendered}")
continue
indexed[key] = record
return indexed, sorted(set(duplicates))
def _candidate_input_label_leaks(
candidate_inputs: list[dict[str, Any]],
failures: list[str],
) -> int:
leaks = 0
for line_number, candidate_input in enumerate(candidate_inputs, start=1):
try:
assert_no_evaluation_label_leak(candidate_input)
except Exception as exc:
leaks += 1
failures.append(f"candidate_input_label_leak:line_{line_number}:{exc}")
return leaks
def _request_context_label_leaks(
requests: list[dict[str, Any]],
failures: list[str],
) -> int:
leaks = 0
for line_number, request in enumerate(requests, start=1):
visible_payload = {
"incident_context": request.get("incident_context") or {},
"source_metadata": request.get("source_metadata") or {},
"user_prompt": request.get("user_prompt") or "",
}
markers = _forbidden_text_markers(visible_payload)
if markers:
leaks += 1
failures.append(
f"request_context_label_leak:line_{line_number}:"
f"{','.join(markers)}"
)
return leaks
def _validate_requests(
requests: list[dict[str, Any]],
failures: list[str],
) -> None:
for line_number, request in enumerate(requests, start=1):
if request.get("schema_version") != REQUEST_SCHEMA_VERSION:
failures.append(f"request_schema_mismatch:line_{line_number}")
if request.get("candidate_id") != NEMOTRON_CANDIDATE_ID:
failures.append(f"request_candidate_mismatch:line_{line_number}")
metadata = dict(request.get("metadata") or {})
if metadata.get("request_only") is not True:
failures.append(f"request_not_request_only:line_{line_number}")
if metadata.get("not_replacement_evidence") is not True:
failures.append(f"request_missing_not_replacement_evidence:line_{line_number}")
required = set((request.get("response_contract") or {}).get("required") or [])
missing_response_fields = sorted(_REQUIRED_RESPONSE_FIELDS - required)
if missing_response_fields:
failures.append(
"request_response_contract_missing:"
f"line_{line_number}:{','.join(missing_response_fields)}"
)
def _validate_context_alignment(
*,
fixture_index: dict[tuple[str, str], dict[str, Any]],
input_index: dict[tuple[str, str], dict[str, Any]],
request_index: dict[tuple[str, str], dict[str, Any]],
failures: list[str],
) -> None:
for key in sorted(set(fixture_index) & set(input_index)):
if fixture_index[key].get("incident_context") != input_index[key].get(
"incident_context"
):
failures.append(f"fixture_input_context_mismatch:{_render_key(key)}")
for key in sorted(set(input_index) & set(request_index)):
candidate_input = input_index[key]
request = request_index[key]
if candidate_input.get("incident_context") != request.get("incident_context"):
failures.append(f"input_request_context_mismatch:{_render_key(key)}")
if candidate_input.get("source_metadata") != request.get("source_metadata"):
failures.append(f"input_request_metadata_mismatch:{_render_key(key)}")
def _count_request_metadata(
requests: list[dict[str, Any]],
key: str,
expected: Any,
) -> int:
return sum(
1
for request in requests
if (request.get("metadata") or {}).get(key) is expected
)
def _expected_action_markers(fixture: dict[str, Any]) -> list[str]:
labels = dict(fixture.get("evaluation_labels") or {})
markers = labels.get("expected_action_markers") or []
return [str(marker) for marker in markers if str(marker).strip()]
def _sensitive_marker_scan(
candidate_inputs: list[dict[str, Any]],
requests: list[dict[str, Any]],
) -> tuple[int, dict[str, int]]:
distribution = dict.fromkeys(sorted(_SENSITIVE_TEXT_MARKERS), 0)
hit_records: set[tuple[str, str]] = set()
for record in [*candidate_inputs, *requests]:
key = _run_incident_key(record)
serialized = json.dumps(
record.get("incident_context") or {},
ensure_ascii=False,
sort_keys=True,
).lower()
markers = [
marker for marker in sorted(_SENSITIVE_TEXT_MARKERS) if marker in serialized
]
if markers and key is not None:
hit_records.add(key)
for marker in markers:
distribution[marker] += 1
return len(hit_records), {key: value for key, value in distribution.items() if value}
def _forbidden_text_markers(payload: dict[str, Any]) -> list[str]:
serialized = json.dumps(payload, ensure_ascii=False, sort_keys=True).lower()
return sorted(
marker for marker in _FORBIDDEN_TEXT_MARKERS if marker in serialized
)
def _run_incident_key(record: dict[str, Any]) -> tuple[str, str] | None:
run_id = str(record.get("run_id", "")).strip()
incident_id = str(record.get("incident_id", "")).strip()
if not run_id or not incident_id:
return None
return (run_id, incident_id)
def _render_key(key: tuple[str, str]) -> str:
return f"{key[0]}::{key[1]}"