0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
191 lines
7.1 KiB
Python
191 lines
7.1 KiB
Python
"""Centralized error/importance detection — thin Python shim over Rust.
|
|
|
|
Phase 3e.1 ported the keyword data + scoring logic to
|
|
``crates/headroom-core/src/signals/`` (see the trait architecture in
|
|
``signals/README.md``). This module is now a compatibility surface that:
|
|
|
|
1. Pulls the keyword tables out of Rust via
|
|
``headroom._core.keyword_registry_snapshot()`` so the Python side
|
|
never re-declares them and cannot drift from the Rust source of
|
|
truth.
|
|
2. Re-exports the legacy ``frozenset`` and compiled-regex names
|
|
(``ERROR_KEYWORDS``, ``ERROR_PATTERN``, ``PRIORITY_PATTERNS_TEXT``,
|
|
…) so the existing callers in ``search_compressor``,
|
|
``diff_compressor``, and ``intelligent_context`` keep working
|
|
without same-PR refactors.
|
|
3. Delegates ``content_has_error_indicators`` to the Rust
|
|
aho-corasick automaton.
|
|
|
|
Caller migration to the trait API happens in the per-compressor port
|
|
PRs that follow (Phase 3e.2 onward); this shim is the bridge until
|
|
those land.
|
|
|
|
# Bug fixes baked in
|
|
|
|
The Rust implementation fixes two bugs the Python originals carried:
|
|
|
|
* ``ERROR_KEYWORDS`` listed ``timeout``/``abort``/``denied``/
|
|
``rejected`` but ``ERROR_PATTERN`` regex omitted them. The
|
|
recompiled pattern below now includes all four — lines like
|
|
``"FATAL: timeout connecting upstream"`` now flag as errors via
|
|
the regex too.
|
|
* ``token`` was dropped from ``SECURITY_KEYWORDS`` (it false-positived
|
|
on every reference to LLM tokens — input_tokens, tokens_saved, …).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import cast
|
|
|
|
from headroom._core import (
|
|
content_has_error_indicators as _rust_content_has_error_indicators,
|
|
)
|
|
from headroom._core import (
|
|
keyword_registry_snapshot as _rust_keyword_registry_snapshot,
|
|
)
|
|
from headroom._core import (
|
|
score_line as _rust_score_line,
|
|
)
|
|
|
|
|
|
def score_line(line: str, context: str = "text") -> tuple[str | None, float, float]:
|
|
"""Score `line` against the default Rust keyword detector.
|
|
|
|
Returns ``(category | None, priority, confidence)``. ``category`` is
|
|
one of ``error|warning|importance|security|markdown`` or ``None`` if
|
|
nothing matched.
|
|
|
|
Raises :class:`ValueError` for unknown context names. The Rust
|
|
binding returns ``None`` for unknown contexts to dodge a
|
|
pyo3-0.22 + clippy false positive on ``PyResult``-returning
|
|
``#[pyfunction]``s; this shim translates that into the explicit
|
|
Python error every caller would expect.
|
|
"""
|
|
result = _rust_score_line(line, context)
|
|
if result is None:
|
|
raise ValueError(f"unknown importance context: {context}")
|
|
return cast("tuple[str | None, float, float]", result)
|
|
|
|
|
|
_REGISTRY: dict[str, list[str]] = _rust_keyword_registry_snapshot()
|
|
|
|
|
|
def _alternation(words: list[str]) -> str:
|
|
"""Compile a `\b(w1|w2|…)\b` regex source from the Rust-supplied list.
|
|
|
|
The keywords are static (compiled once on import) so we don't need
|
|
`re.escape` for the current set, but using it keeps the shim
|
|
correct if a future Rust update adds a regex meta-character.
|
|
"""
|
|
escaped = [re.escape(w) for w in words]
|
|
return r"\b(" + "|".join(escaped) + r")\b"
|
|
|
|
|
|
# ─── Canonical keyword sets (pulled from Rust at import time) ───────────────
|
|
|
|
ERROR_KEYWORDS: frozenset[str] = frozenset(_REGISTRY["error"])
|
|
|
|
# Importance keywords historically included the error set — preserve that
|
|
# union so consumers iterating the set get the same membership as before.
|
|
IMPORTANCE_KEYWORDS: frozenset[str] = frozenset(
|
|
list(_REGISTRY["error"]) + list(_REGISTRY["importance"]) + list(_REGISTRY["warning"])
|
|
)
|
|
|
|
SECURITY_KEYWORDS: frozenset[str] = frozenset(_REGISTRY["security"])
|
|
|
|
ERROR_INDICATOR_KEYWORDS: tuple[str, ...] = tuple(_REGISTRY["error_indicators"])
|
|
|
|
|
|
# ─── Compiled patterns ──────────────────────────────────────────────────────
|
|
|
|
ERROR_PATTERN: re.Pattern[str] = re.compile(_alternation(_REGISTRY["error"]), re.IGNORECASE)
|
|
WARNING_PATTERN: re.Pattern[str] = re.compile(_alternation(_REGISTRY["warning"]), re.IGNORECASE)
|
|
IMPORTANCE_PATTERN: re.Pattern[str] = re.compile(
|
|
_alternation(_REGISTRY["importance"]), re.IGNORECASE
|
|
)
|
|
SECURITY_PATTERN: re.Pattern[str] = re.compile(_alternation(_REGISTRY["security"]), re.IGNORECASE)
|
|
|
|
|
|
# ─── Per-context priority pattern lists ─────────────────────────────────────
|
|
|
|
PRIORITY_PATTERNS_SEARCH: list[re.Pattern[str]] = [
|
|
ERROR_PATTERN,
|
|
WARNING_PATTERN,
|
|
IMPORTANCE_PATTERN,
|
|
]
|
|
|
|
PRIORITY_PATTERNS_DIFF: list[re.Pattern[str]] = [
|
|
ERROR_PATTERN,
|
|
IMPORTANCE_PATTERN,
|
|
SECURITY_PATTERN,
|
|
]
|
|
|
|
# Markdown structural prefixes: matched on whole lines, anchored with `^`.
|
|
# Pulled from Rust so the prefix table can't drift either.
|
|
PRIORITY_PATTERNS_TEXT: list[re.Pattern[str]] = [
|
|
ERROR_PATTERN,
|
|
IMPORTANCE_PATTERN,
|
|
*(re.compile("^" + re.escape(prefix)) for prefix in _REGISTRY["markdown_prefixes"]),
|
|
]
|
|
|
|
|
|
# ─── Triage helper ──────────────────────────────────────────────────────────
|
|
|
|
|
|
def content_has_error_indicators(text: str) -> bool:
|
|
"""Fast keyword check — does `text` contain any error indicator?
|
|
|
|
Substring match (no word boundary). Distinct from the strict line
|
|
scoring in :mod:`headroom._core.score_line` because the triage
|
|
callsite (e.g. message-signature classification) cares about
|
|
Python tracebacks and similar substrings more than connection
|
|
states.
|
|
"""
|
|
return bool(_rust_content_has_error_indicators(text))
|
|
|
|
|
|
def content_has_strong_error_indicators(text: str) -> bool:
|
|
"""Stricter triage for compression-protection gates.
|
|
|
|
:func:`content_has_error_indicators` substring-matches a single
|
|
keyword, which false-positives on benign outputs that merely
|
|
mention errors — grep hits, ``"errors": []`` JSON fields,
|
|
``error_handler.py`` filenames, ``except Exception`` in file
|
|
reads. Protection gates exempt content from compression entirely,
|
|
so a lax match there silently costs savings on the hot path.
|
|
|
|
Require at least two DISTINCT indicator keywords: genuine failure
|
|
output nearly always pairs the failure kind with a second
|
|
indicator (``Traceback`` + ``ValueError``, ``fatal`` +
|
|
``crash``), while passing mentions rarely do. Misses here are
|
|
safe — downstream compressors (LogCompressor) still preserve
|
|
error lines.
|
|
"""
|
|
lowered = text.lower()
|
|
hits = 0
|
|
for keyword in ERROR_INDICATOR_KEYWORDS:
|
|
if keyword in lowered:
|
|
hits += 1
|
|
if hits >= 2:
|
|
return True
|
|
return False
|
|
|
|
|
|
__all__ = [
|
|
"ERROR_KEYWORDS",
|
|
"IMPORTANCE_KEYWORDS",
|
|
"SECURITY_KEYWORDS",
|
|
"ERROR_INDICATOR_KEYWORDS",
|
|
"ERROR_PATTERN",
|
|
"WARNING_PATTERN",
|
|
"IMPORTANCE_PATTERN",
|
|
"SECURITY_PATTERN",
|
|
"PRIORITY_PATTERNS_SEARCH",
|
|
"PRIORITY_PATTERNS_DIFF",
|
|
"PRIORITY_PATTERNS_TEXT",
|
|
"content_has_error_indicators",
|
|
"content_has_strong_error_indicators",
|
|
"score_line",
|
|
]
|