|
|
|
@@ -0,0 +1,482 @@
|
|
|
|
|
"""Local-only advisory context bundle planner for Atlas/Hermes.
|
|
|
|
|
|
|
|
|
|
This module intentionally emits a retrieval/authority plan only. It does not call
|
|
|
|
|
Hermes memory/session/RAG/web tools, mutate vector stores, broaden private roots,
|
|
|
|
|
or change live routing.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import ipaddress
|
|
|
|
|
import re
|
|
|
|
|
import time
|
|
|
|
|
import urllib.error
|
|
|
|
|
import urllib.parse
|
|
|
|
|
import urllib.request
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Any, Mapping, Sequence
|
|
|
|
|
|
|
|
|
|
SCHEMA = "atlas_context_gate_plan_v1"
|
|
|
|
|
NPU_BUSY_PATH = Path("/sys/class/accel/accel0/device/npu_busy_time_us")
|
|
|
|
|
DEFAULT_CLASSIFIER_URL = "http://127.0.0.1:18819/v1/classify"
|
|
|
|
|
|
|
|
|
|
AUTHORITY = {
|
|
|
|
|
"may_route": False,
|
|
|
|
|
"may_write_memory": False,
|
|
|
|
|
"may_send_external": False,
|
|
|
|
|
"may_process_private_dirs": False,
|
|
|
|
|
"may_execute_tools": False,
|
|
|
|
|
"may_restart_services": False,
|
|
|
|
|
"may_mutate_vector_db": False,
|
|
|
|
|
"may_change_live_config": False,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GATES = {
|
|
|
|
|
"live_routing_change": "closed_requires_explicit_approval",
|
|
|
|
|
"memory_write": "closed_requires_explicit_approval",
|
|
|
|
|
"outbound_send": "closed_requires_explicit_approval",
|
|
|
|
|
"tool_execution": "closed_requires_explicit_approval",
|
|
|
|
|
"service_restart": "closed_requires_explicit_approval",
|
|
|
|
|
"vector_mutation": "closed_requires_explicit_approval",
|
|
|
|
|
"private_root_broadening": "closed_requires_explicit_approval",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_ALLOWED_SOURCES = {
|
|
|
|
|
"durable_memory",
|
|
|
|
|
"session_search",
|
|
|
|
|
"rag_search",
|
|
|
|
|
"repo_files",
|
|
|
|
|
"live_system",
|
|
|
|
|
"web",
|
|
|
|
|
"no_retrieval",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContextGateError(ValueError):
|
|
|
|
|
"""Raised for invalid requests or unavailable required local stages."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class ClassifierResult:
|
|
|
|
|
labels: Mapping[str, Any]
|
|
|
|
|
npu_busy_delta_us: int | None
|
|
|
|
|
sysfs_npu_busy_delta_us: int | None
|
|
|
|
|
outer_sysfs_delta_us: int | None
|
|
|
|
|
live: bool
|
|
|
|
|
warning: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_npu_busy_time_us(path: Path = NPU_BUSY_PATH) -> int | None:
|
|
|
|
|
try:
|
|
|
|
|
return int(path.read_text(encoding="utf-8").strip())
|
|
|
|
|
except (FileNotFoundError, PermissionError, ValueError, OSError):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _label_value(labels: Mapping[str, Any], name: str, default: Any) -> Any:
|
|
|
|
|
value = labels.get(name, default)
|
|
|
|
|
if isinstance(value, Mapping) and "value" in value:
|
|
|
|
|
return value.get("value", default)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _label_confidence(labels: Mapping[str, Any], name: str, default: float = 0.5) -> float:
|
|
|
|
|
value = labels.get(name)
|
|
|
|
|
if isinstance(value, Mapping):
|
|
|
|
|
try:
|
|
|
|
|
return float(value.get("confidence", default))
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return default
|
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def heuristic_labels(query: str, context: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
|
|
|
"""Small transparent fallback used by tests and explicit offline smoke mode."""
|
|
|
|
|
text = query.lower()
|
|
|
|
|
platform = str((context or {}).get("platform", "unknown")).lower()
|
|
|
|
|
|
|
|
|
|
current_words = ["current", "now", "health", "port", "process", "systemd", "status", "npu", "listening", "logs"]
|
|
|
|
|
prior_words = ["where did we leave", "what did we decide", "previous", "earlier", "handoff", "plan"]
|
|
|
|
|
coding_words = ["implement", "code", "repo", "test", "pytest", "diff", "branch", "hermes"]
|
|
|
|
|
research_words = ["research", "compare", "summarize", "explain", "what is", "how do i"]
|
|
|
|
|
unsafe_words = ["change live routing", "live routing", "restart", "send", "write memory", "reindex", "mutate", "delete"]
|
|
|
|
|
|
|
|
|
|
safety = any(w in text for w in unsafe_words)
|
|
|
|
|
tool_needed = any(w in text for w in current_words + coding_words) or safety
|
|
|
|
|
|
|
|
|
|
if platform == "kanban" or "kanban" in text or any(w in text for w in coding_words):
|
|
|
|
|
category = "coding"
|
|
|
|
|
elif any(w in text for w in current_words):
|
|
|
|
|
category = "devops"
|
|
|
|
|
elif any(w in text for w in research_words + prior_words):
|
|
|
|
|
category = "research"
|
|
|
|
|
else:
|
|
|
|
|
category = "chat"
|
|
|
|
|
|
|
|
|
|
if "remember" in text or "preference" in text:
|
|
|
|
|
memory_candidate = "durable_user_fact"
|
|
|
|
|
elif "convention" in text or "workflow" in text:
|
|
|
|
|
memory_candidate = "workflow_convention"
|
|
|
|
|
else:
|
|
|
|
|
memory_candidate = "none"
|
|
|
|
|
|
|
|
|
|
urgency = "high" if any(w in text for w in ["urgent", "critical", "down", "broken"]) else "normal"
|
|
|
|
|
return {
|
|
|
|
|
"tool_needed": {"value": tool_needed, "confidence": 0.76 if tool_needed else 0.68},
|
|
|
|
|
"memory_candidate": {"value": memory_candidate, "confidence": 0.8 if memory_candidate != "none" else 0.35},
|
|
|
|
|
"urgency": {"value": urgency, "confidence": 0.8 if urgency == "high" else 0.65},
|
|
|
|
|
"workflow_category": {"value": category, "confidence": 0.78 if category != "chat" else 0.7},
|
|
|
|
|
"safety_confirmation_required": {"value": safety, "confidence": 0.9 if safety else 0.2},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _NoClassifierRedirectHandler(urllib.request.HTTPRedirectHandler):
|
|
|
|
|
"""Fail closed instead of following redirects away from a validated local URL."""
|
|
|
|
|
|
|
|
|
|
def redirect_request(self, req, fp, code, msg, headers, newurl): # type: ignore[no-untyped-def]
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_CLASSIFIER_OPENER = urllib.request.build_opener(_NoClassifierRedirectHandler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def classify_live(
|
|
|
|
|
query: str,
|
|
|
|
|
context: Mapping[str, Any] | None = None,
|
|
|
|
|
classifier_url: str = DEFAULT_CLASSIFIER_URL,
|
|
|
|
|
timeout: float = 8.0,
|
|
|
|
|
) -> ClassifierResult:
|
|
|
|
|
classifier_url = validate_classifier_url(classifier_url)
|
|
|
|
|
before = read_npu_busy_time_us()
|
|
|
|
|
payload = {
|
|
|
|
|
"id": f"context-gate-{int(time.time())}",
|
|
|
|
|
"text": query,
|
|
|
|
|
"context": {"platform": (context or {}).get("platform", "cli"), "source": "context_gate"},
|
|
|
|
|
"options": {"include_evidence": False, "include_embedding_debug": False, "dry_run": True},
|
|
|
|
|
}
|
|
|
|
|
req = urllib.request.Request(
|
|
|
|
|
classifier_url,
|
|
|
|
|
data=json.dumps(payload).encode("utf-8"),
|
|
|
|
|
headers={"Content-Type": "application/json"},
|
|
|
|
|
method="POST",
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
with _CLASSIFIER_OPENER.open(req, timeout=timeout) as resp: # noqa: S310 - local configured endpoint only
|
|
|
|
|
raw = resp.read(256_000)
|
|
|
|
|
except (urllib.error.URLError, TimeoutError, OSError) as exc:
|
|
|
|
|
raise ContextGateError(f"classifier_unavailable: {exc}") from exc
|
|
|
|
|
after = read_npu_busy_time_us()
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(raw.decode("utf-8"))
|
|
|
|
|
except json.JSONDecodeError as exc:
|
|
|
|
|
raise ContextGateError("classifier_invalid_json") from exc
|
|
|
|
|
labels = data.get("labels")
|
|
|
|
|
if not isinstance(labels, Mapping):
|
|
|
|
|
raise ContextGateError("classifier_missing_labels")
|
|
|
|
|
outer = after - before if before is not None and after is not None else None
|
|
|
|
|
return ClassifierResult(
|
|
|
|
|
labels=labels,
|
|
|
|
|
npu_busy_delta_us=_as_int_or_none(data.get("npu_busy_delta_us")),
|
|
|
|
|
sysfs_npu_busy_delta_us=_as_int_or_none(data.get("sysfs_npu_busy_delta_us")),
|
|
|
|
|
outer_sysfs_delta_us=outer,
|
|
|
|
|
live=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_classifier_url(classifier_url: str) -> str:
|
|
|
|
|
"""Validate the local-only classifier endpoint before any POST is attempted."""
|
|
|
|
|
parsed = urllib.parse.urlparse(classifier_url)
|
|
|
|
|
if parsed.scheme not in {"http", "https"}:
|
|
|
|
|
raise ContextGateError("invalid_classifier_url:scheme_must_be_http_or_https")
|
|
|
|
|
host = parsed.hostname
|
|
|
|
|
if not host:
|
|
|
|
|
raise ContextGateError("invalid_classifier_url:missing_host")
|
|
|
|
|
host_normalized = host.lower().rstrip(".")
|
|
|
|
|
if host_normalized == "localhost":
|
|
|
|
|
return classifier_url
|
|
|
|
|
try:
|
|
|
|
|
address = ipaddress.ip_address(host_normalized)
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
raise ContextGateError("invalid_classifier_url:host_must_be_loopback") from exc
|
|
|
|
|
if not address.is_loopback:
|
|
|
|
|
raise ContextGateError("invalid_classifier_url:host_must_be_loopback")
|
|
|
|
|
return classifier_url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _as_int_or_none(value: Any) -> int | None:
|
|
|
|
|
try:
|
|
|
|
|
return int(value)
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def classify_offline(query: str, context: Mapping[str, Any] | None = None, warning: str | None = None) -> ClassifierResult:
|
|
|
|
|
return ClassifierResult(
|
|
|
|
|
labels=heuristic_labels(query, context),
|
|
|
|
|
npu_busy_delta_us=None,
|
|
|
|
|
sysfs_npu_busy_delta_us=None,
|
|
|
|
|
outer_sysfs_delta_us=None,
|
|
|
|
|
live=False,
|
|
|
|
|
warning=warning or "offline_heuristic_classifier_no_npu_claim",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_any(text: str, needles: list[str]) -> bool:
|
|
|
|
|
return any(n in text for n in needles)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _source(source: str, action: str, reason: str, priority: int, freshness: str, confidence: float) -> dict[str, Any]:
|
|
|
|
|
assert source in _ALLOWED_SOURCES
|
|
|
|
|
return {
|
|
|
|
|
"source": source,
|
|
|
|
|
"action": action,
|
|
|
|
|
"reason": reason,
|
|
|
|
|
"priority": priority,
|
|
|
|
|
"freshness": freshness,
|
|
|
|
|
"permission": "tool_required_by_authoritative_agent" if source != "no_retrieval" else "none",
|
|
|
|
|
"missing_behavior": "retrieve_or_mark_missing" if source != "no_retrieval" else "skip_retrieval",
|
|
|
|
|
"confidence": round(confidence, 2),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def select_sources(query: str, labels: Mapping[str, Any], context: Mapping[str, Any], max_sources: int) -> list[dict[str, Any]]:
|
|
|
|
|
text = query.lower()
|
|
|
|
|
sources: list[dict[str, Any]] = []
|
|
|
|
|
category = str(_label_value(labels, "workflow_category", "unknown"))
|
|
|
|
|
memory_candidate = str(_label_value(labels, "memory_candidate", "none"))
|
|
|
|
|
tool_needed = bool(_label_value(labels, "tool_needed", False))
|
|
|
|
|
|
|
|
|
|
if tool_needed or _has_any(text, ["current", "now", "health", "port", "process", "systemd", "status", "npu", "listening", "logs", "time", "date"]):
|
|
|
|
|
sources.append(_source("live_system", "inspect_with_terminal_or_domain_tool", "current service/system state requested", 1, "live_required", 0.9))
|
|
|
|
|
|
|
|
|
|
if context.get("repo_path") or category == "coding" or _has_any(text, ["repo", "code", "file", "test", "pytest", "diff", "implementation", "hermes", "atlas"]):
|
|
|
|
|
sources.append(_source("repo_files", "inspect_explicit_repo_paths", "repo-specific implementation or config context", 2, "current_filesystem", 0.84))
|
|
|
|
|
|
|
|
|
|
if _has_any(text, ["where did we leave", "what did we decide", "previous", "earlier", "handoff", "prior", "last time"]):
|
|
|
|
|
sources.append(_source("session_search", "search_prior_sessions_or_kanban_handoffs", "prior decision or handoff requested", 3, "session-era", 0.82))
|
|
|
|
|
|
|
|
|
|
if _has_any(text, ["runbook", "note", "obsidian", "rag", "docs", "knowledge", "plan"]):
|
|
|
|
|
sources.append(_source("rag_search", "query_local_index_read_only", "local docs or indexed knowledge likely useful", 4, "cached_index", 0.76))
|
|
|
|
|
|
|
|
|
|
if memory_candidate != "none" or _has_any(text, ["preference", "remember", "profile", "durable fact"]):
|
|
|
|
|
sources.append(_source("durable_memory", "read_stable_facts_only", "stable preference/environment facts may be relevant", 5, "static", 0.72))
|
|
|
|
|
|
|
|
|
|
if _has_any(text, ["latest", "news", "version", "release", "public", "web"]):
|
|
|
|
|
sources.append(_source("web", "search_public_current_sources", "current external public fact requested", 6, "live_external", 0.7))
|
|
|
|
|
|
|
|
|
|
if not sources:
|
|
|
|
|
sources.append(_source("no_retrieval", "answer_directly", "no factual retrieval dependency detected", 1, "none", 0.78))
|
|
|
|
|
|
|
|
|
|
# Stable priority order and bounded compact plan.
|
|
|
|
|
seen: set[str] = set()
|
|
|
|
|
deduped = []
|
|
|
|
|
for item in sorted(sources, key=lambda x: x["priority"]):
|
|
|
|
|
if item["source"] not in seen:
|
|
|
|
|
seen.add(item["source"])
|
|
|
|
|
deduped.append(item)
|
|
|
|
|
return deduped[:max_sources]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def select_bundle_name(query: str, labels: Mapping[str, Any], context: Mapping[str, Any]) -> str:
|
|
|
|
|
text = query.lower()
|
|
|
|
|
category = str(_label_value(labels, "workflow_category", "unknown"))
|
|
|
|
|
if context.get("platform") == "kanban" or context.get("task_id") or category == "coding":
|
|
|
|
|
return "CodingTaskBundle"
|
|
|
|
|
if category in {"devops", "debugging"} or _has_any(text, ["health", "port", "systemd", "npu", "service", "logs"]):
|
|
|
|
|
return "OpsDebugBundle"
|
|
|
|
|
if category in {"note_taking", "productivity"} or _has_any(text, ["preference", "remember", "profile"]):
|
|
|
|
|
return "PersonalAssistantBundle"
|
|
|
|
|
if "no_retrieval" in [s["source"] for s in select_sources(query, labels, context, 1)]:
|
|
|
|
|
return "SimpleResponseBundle"
|
|
|
|
|
return "ResearchBundle"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _field(field: str, shape: str, source: str, freshness: str, missing: str, privacy: str, confidence: float = 0.8) -> dict[str, Any]:
|
|
|
|
|
return {
|
|
|
|
|
"field": field,
|
|
|
|
|
"shape": shape,
|
|
|
|
|
"source_of_truth": source,
|
|
|
|
|
"freshness": freshness,
|
|
|
|
|
"provenance_required": True,
|
|
|
|
|
"missing_behavior": missing,
|
|
|
|
|
"privacy": privacy,
|
|
|
|
|
"confidence": round(confidence, 2),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_bundle_plan(bundle_name: str, sources: Sequence[Mapping[str, Any]], query: str, labels: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
|
|
safety_required = bool(_label_value(labels, "safety_confirmation_required", False))
|
|
|
|
|
source_names = {s["source"] for s in sources}
|
|
|
|
|
if bundle_name == "OpsDebugBundle":
|
|
|
|
|
required = [
|
|
|
|
|
_field("problem_statement", "compact_text", "user", "request", "mark_missing", "query_text_only"),
|
|
|
|
|
_field("target_scope", "service_repo_or_host", "query_or_classifier", "request", "ask_or_infer_low_confidence", "no_private_paths_beyond_explicit"),
|
|
|
|
|
_field("live_state", "status_table", "live_system", "live_required", "retrieve_or_fail_closed", "no_raw_logs_by_default"),
|
|
|
|
|
_field("safety_gates", "closed_gate_map", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
_field("provenance", "tool_names_and_paths", "executing_agent", "run", "mark_missing", "paths_only"),
|
|
|
|
|
]
|
|
|
|
|
elif bundle_name == "CodingTaskBundle":
|
|
|
|
|
required = [
|
|
|
|
|
_field("repo_root", "absolute_path", "task_or_context", "current", "ask_or_fail", "explicit_path_only"),
|
|
|
|
|
_field("git_state", "branch_dirty_counts", "live_system", "live_required", "retrieve_or_fail_closed", "no_diff_dump_by_default"),
|
|
|
|
|
_field("requirements", "bullet_summary", "user_kanban_files", "current", "retrieve_or_mark_missing", "no_private_snippets"),
|
|
|
|
|
_field("relevant_paths", "path_list", "repo_files", "current_filesystem", "search_narrowly", "paths_only"),
|
|
|
|
|
_field("tests_or_smokes", "command_list", "repo_files", "current_filesystem", "mark_missing", "commands_only"),
|
|
|
|
|
_field("review_gates", "closed_gate_map", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
]
|
|
|
|
|
elif bundle_name == "PersonalAssistantBundle":
|
|
|
|
|
required = [
|
|
|
|
|
_field("user_intent", "compact_text", "user", "request", "mark_missing", "query_text_only"),
|
|
|
|
|
_field("durable_facts_needed", "fact_keys", "durable_memory", "static", "retrieve_or_mark_missing", "no_raw_memory_dump"),
|
|
|
|
|
_field("prior_decisions_needed", "session_refs", "session_search", "session-era", "retrieve_or_mark_missing", "summaries_only"),
|
|
|
|
|
_field("privacy_boundary", "closed_gate_map", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
_field("action_authority", "closed_gate_map", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
]
|
|
|
|
|
elif bundle_name == "SimpleResponseBundle":
|
|
|
|
|
required = []
|
|
|
|
|
else:
|
|
|
|
|
required = [
|
|
|
|
|
_field("research_question", "compact_text", "user", "request", "mark_missing", "query_text_only"),
|
|
|
|
|
_field("source_plan", "ordered_source_list", "context_gate", "run", "mark_missing", "no_private_snippets"),
|
|
|
|
|
_field("evidence_requirements", "provenance_rules", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
_field("freshness_cutoff", "freshness_policy", "classifier_query", "request", "mark_missing", "no_private_data"),
|
|
|
|
|
_field("missing_data_behavior", "policy_enum", "policy", "static", "fail_closed", "no_private_data"),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
blocked = []
|
|
|
|
|
if safety_required or re.search(r"\b(route|routing|restart|send|write memory|reindex|delete|mutate)\b", query.lower()):
|
|
|
|
|
blocked.append(_field("authority_side_effect", "approval_required", "policy", "static", "fail_closed", "no_side_effects_in_v1", 0.95))
|
|
|
|
|
if "rag_search" in source_names:
|
|
|
|
|
blocked.append(_field("vector_db_mutation", "not_allowed", "policy", "static", "fail_closed", "read_only_query_plan", 0.95))
|
|
|
|
|
return {"bundle_name": bundle_name, "required_fields": required, "optional_fields": [], "blocked_fields": blocked}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def summarize_query_class(labels: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
|
|
return {
|
|
|
|
|
"workflow_category": _label_value(labels, "workflow_category", "unknown"),
|
|
|
|
|
"urgency": _label_value(labels, "urgency", "normal"),
|
|
|
|
|
"tool_needed": bool(_label_value(labels, "tool_needed", False)),
|
|
|
|
|
"memory_candidate": _label_value(labels, "memory_candidate", "none"),
|
|
|
|
|
"safety_confirmation_required": bool(_label_value(labels, "safety_confirmation_required", False)),
|
|
|
|
|
"confidence": round(max(
|
|
|
|
|
_label_confidence(labels, "workflow_category", 0.5),
|
|
|
|
|
_label_confidence(labels, "tool_needed", 0.5),
|
|
|
|
|
_label_confidence(labels, "safety_confirmation_required", 0.5),
|
|
|
|
|
), 2),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def npu_proof_from_classifier(result: ClassifierResult, require_npu_proof: bool) -> tuple[dict[str, Any], list[str]]:
|
|
|
|
|
endpoint_delta = result.npu_busy_delta_us
|
|
|
|
|
endpoint_sysfs_delta = result.sysfs_npu_busy_delta_us
|
|
|
|
|
outer_delta = result.outer_sysfs_delta_us
|
|
|
|
|
positive_endpoint_sysfs = endpoint_sysfs_delta is not None and endpoint_sysfs_delta > 0
|
|
|
|
|
positive_outer = outer_delta is not None and outer_delta > 0
|
|
|
|
|
verified = bool(result.live and (positive_endpoint_sysfs or positive_outer))
|
|
|
|
|
warnings: list[str] = []
|
|
|
|
|
if result.warning:
|
|
|
|
|
warnings.append(result.warning)
|
|
|
|
|
if require_npu_proof and not verified:
|
|
|
|
|
warnings.append("npu_proof_inconclusive")
|
|
|
|
|
return {
|
|
|
|
|
"classifier_delta_us": endpoint_delta,
|
|
|
|
|
"classifier_sysfs_delta_us": endpoint_sysfs_delta,
|
|
|
|
|
"outer_sysfs_delta_us": outer_delta,
|
|
|
|
|
"rerank_delta_us": None,
|
|
|
|
|
"verified": verified,
|
|
|
|
|
"required": require_npu_proof,
|
|
|
|
|
"classifier_live": result.live,
|
|
|
|
|
}, warnings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_plan(
|
|
|
|
|
query: str,
|
|
|
|
|
*,
|
|
|
|
|
context: Mapping[str, Any] | None = None,
|
|
|
|
|
options: Mapping[str, Any] | None = None,
|
|
|
|
|
classifier: ClassifierResult | None = None,
|
|
|
|
|
) -> dict[str, Any]:
|
|
|
|
|
if not query or not query.strip():
|
|
|
|
|
raise ContextGateError("query_required")
|
|
|
|
|
context = dict(context or {})
|
|
|
|
|
options = dict(options or {})
|
|
|
|
|
if options.get("dry_run", True) is not True:
|
|
|
|
|
raise ContextGateError("dry_run_must_remain_true_in_v1")
|
|
|
|
|
if options.get("include_private_text", False):
|
|
|
|
|
raise ContextGateError("include_private_text_not_allowed_in_v1")
|
|
|
|
|
max_sources = max(1, min(6, int(options.get("max_sources", 4))))
|
|
|
|
|
require_npu = bool(options.get("require_npu_proof", True))
|
|
|
|
|
if classifier is None:
|
|
|
|
|
classifier = classify_offline(query, context)
|
|
|
|
|
labels = classifier.labels
|
|
|
|
|
source_plan = select_sources(query, labels, context, max_sources)
|
|
|
|
|
bundle_name = select_bundle_name(query, labels, context)
|
|
|
|
|
npu_proof, warnings = npu_proof_from_classifier(classifier, require_npu)
|
|
|
|
|
plan = {
|
|
|
|
|
"schema": SCHEMA,
|
|
|
|
|
"trace_id": options.get("trace_id") or context.get("trace_id"),
|
|
|
|
|
"dry_run": True,
|
|
|
|
|
"ok": True,
|
|
|
|
|
"query_class": summarize_query_class(labels),
|
|
|
|
|
"source_plan": source_plan,
|
|
|
|
|
"bundle_plan": build_bundle_plan(bundle_name, source_plan, query, labels),
|
|
|
|
|
"npu_proof": npu_proof,
|
|
|
|
|
"authority": dict(AUTHORITY),
|
|
|
|
|
"gates": dict(GATES),
|
|
|
|
|
"warnings": warnings,
|
|
|
|
|
}
|
|
|
|
|
validate_plan(plan)
|
|
|
|
|
return plan
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_plan(plan: Mapping[str, Any]) -> None:
|
|
|
|
|
if plan.get("schema") != SCHEMA:
|
|
|
|
|
raise ContextGateError("invalid_schema")
|
|
|
|
|
if plan.get("dry_run") is not True:
|
|
|
|
|
raise ContextGateError("dry_run_missing")
|
|
|
|
|
if plan.get("authority") != AUTHORITY:
|
|
|
|
|
raise ContextGateError("authority_not_closed")
|
|
|
|
|
sources = plan.get("source_plan")
|
|
|
|
|
if not isinstance(sources, list) or not sources:
|
|
|
|
|
raise ContextGateError("source_plan_required")
|
|
|
|
|
for item in sources:
|
|
|
|
|
if item.get("source") not in _ALLOWED_SOURCES:
|
|
|
|
|
raise ContextGateError(f"invalid_source:{item.get('source')}")
|
|
|
|
|
required_blocks = ["query_class", "bundle_plan", "npu_proof", "gates"]
|
|
|
|
|
for block in required_blocks:
|
|
|
|
|
if block not in plan:
|
|
|
|
|
raise ContextGateError(f"missing_block:{block}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compact_line(plan: Mapping[str, Any]) -> str:
|
|
|
|
|
sources = ",".join(str(s["source"]) for s in plan["source_plan"])
|
|
|
|
|
closed = "route,memory,send,tools,restart,vector,private_roots,config"
|
|
|
|
|
warnings = ",".join(plan.get("warnings") or []) or "none"
|
|
|
|
|
return (
|
|
|
|
|
f"ok={str(plan['ok']).lower()} schema={plan['schema']} "
|
|
|
|
|
f"bundle={plan['bundle_plan']['bundle_name']} sources={sources} "
|
|
|
|
|
f"source_count={len(plan['source_plan'])} "
|
|
|
|
|
f"npu_verified={str(plan['npu_proof']['verified']).lower()} "
|
|
|
|
|
f"classifier_delta_us={plan['npu_proof'].get('classifier_delta_us')} "
|
|
|
|
|
f"outer_sysfs_delta_us={plan['npu_proof'].get('outer_sysfs_delta_us')} "
|
|
|
|
|
f"gates=closed:{closed} warnings={warnings}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def compact_json(plan: Mapping[str, Any]) -> str:
|
|
|
|
|
compact = {
|
|
|
|
|
"schema": plan["schema"],
|
|
|
|
|
"ok": plan["ok"],
|
|
|
|
|
"dry_run": plan["dry_run"],
|
|
|
|
|
"bundle_name": plan["bundle_plan"]["bundle_name"],
|
|
|
|
|
"sources": [s["source"] for s in plan["source_plan"]],
|
|
|
|
|
"source_count": len(plan["source_plan"]),
|
|
|
|
|
"query_class": plan["query_class"],
|
|
|
|
|
"npu_proof": plan["npu_proof"],
|
|
|
|
|
"authority": plan["authority"],
|
|
|
|
|
"gates_closed": list(plan["gates"].keys()),
|
|
|
|
|
"warnings": plan.get("warnings", []),
|
|
|
|
|
}
|
|
|
|
|
return json.dumps(compact, sort_keys=True, separators=(",", ":"))
|