fix: relocate binary analysis skill

This commit is contained in:
username
2026-07-31 13:25:08 -04:00
parent 0d4a3652e4
commit f66ed48b8c
107 changed files with 17 additions and 2 deletions
@@ -0,0 +1,45 @@
"""Report generation — Markdown, JSON, HTML, PDF.
Provides authoritative Markdown and JSON report generation alongside optional
HTML and PDF renderings. Every report includes methodology and provenance
sections per the validation contract.
Also provides the audit event system for append-only, atomic event logging
to events.jsonl.
"""
from binary_analysis.reporting.audit import (
audit_file_exists,
clear_audit,
read_audit_events,
write_audit_event,
)
from binary_analysis.reporting.generator import (
build_methodology,
build_provenance,
collect_focused_data,
collect_project_data,
collect_triage_data,
generate_html_report,
generate_json_report,
generate_markdown_report,
generate_pdf_report,
write_report,
)
__all__ = [
"audit_file_exists",
"build_methodology",
"build_provenance",
"clear_audit",
"collect_focused_data",
"collect_project_data",
"collect_triage_data",
"generate_html_report",
"generate_json_report",
"generate_markdown_report",
"generate_pdf_report",
"read_audit_events",
"write_audit_event",
"write_report",
]
@@ -0,0 +1,155 @@
"""Audit event persistence — append-only events.jsonl.
Provides atomic audit event writing and reading for the project audit log.
Each event is a single-line JSON object appended atomically. Events are
immutable and append-only; no modification or deletion is supported.
Events include: timestamp, command, args, result (AuditResult enum),
duration_ms, project_id, and optional details.
Key guarantees:
- Atomic append: no partial lines, no interleaving.
- Every line is valid JSON (single-line object).
- Events ordered by timestamp (ISO 8601 with timezone).
- File only grows; never shrinks or overwrites existing entries.
"""
from __future__ import annotations
import json
import os
from datetime import datetime, timezone
from typing import Any
from binary_analysis.domain.enums import AuditResult
from binary_analysis.projects.atomic import atomic_append_text
AUDIT_FILENAME = "events.jsonl"
def _audit_path(project_path: str) -> str:
"""Return the path to the audit events file within a project workspace.
Args:
project_path: Absolute path to the project workspace directory.
Returns:
Full path to the events.jsonl file.
"""
return os.path.join(project_path, "audit", AUDIT_FILENAME)
def write_audit_event(
project_path: str,
command: str,
result: AuditResult,
duration_ms: int,
*,
args: dict[str, Any] | None = None,
project_id: str | None = None,
binary_id: str | None = None,
details: dict[str, Any] | None = None,
) -> None:
"""Atomically append a single audit event to events.jsonl.
Each event is written as a single JSON line. Uses atomic_append_text
to guarantee no partial lines or interleaving.
Args:
project_path: Absolute path to the project workspace directory.
command: The command name (e.g., "project create", "import", "analyze").
result: Outcome from AuditResult enum.
duration_ms: Wall-clock duration in milliseconds.
args: Non-sensitive command arguments (flags, selectors).
project_id: Optional project UUID.
binary_id: Optional binary UUID.
details: Optional additional event details.
"""
timestamp = datetime.now(timezone.utc).isoformat()
event: dict[str, Any] = {
"timestamp": timestamp,
"command": command,
"args": args if args is not None else {},
"result": result.value,
"duration_ms": duration_ms,
}
if project_id is not None:
event["project_id"] = project_id
if binary_id is not None:
event["binary_id"] = binary_id
if details is not None:
event["details"] = details
line = json.dumps(event, ensure_ascii=False)
path = _audit_path(project_path)
atomic_append_text(path, line)
def read_audit_events(project_path: str) -> list[dict[str, Any]]:
"""Read all audit events from events.jsonl, ordered by appearance.
Events are returned in file order (oldest first), which corresponds to
timestamp order since events are appended chronologically.
Args:
project_path: Absolute path to the project workspace directory.
Returns:
List of audit event dicts ordered by timestamp. Empty list if the
file does not exist or is empty.
"""
path = _audit_path(project_path)
if not os.path.exists(path):
return []
events: list[dict[str, Any]] = []
try:
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
events.append(event)
except json.JSONDecodeError:
# Skip corrupted lines but emit a placeholder
events.append(
{
"timestamp": datetime.now(timezone.utc).isoformat(),
"command": "unknown",
"args": {},
"result": AuditResult.FAILED.value,
"duration_ms": 0,
"details": {"error": f"Corrupted audit event: {line[:100]}"},
}
)
except OSError:
return []
return events
def clear_audit(project_path: str) -> None:
"""Remove the audit events file (e.g., on project clean).
Args:
project_path: Absolute path to the project workspace directory.
"""
path = _audit_path(project_path)
if os.path.exists(path):
os.unlink(path)
def audit_file_exists(project_path: str) -> bool:
"""Check if the audit events file exists.
Args:
project_path: Absolute path to the project workspace directory.
Returns:
True if the events.jsonl file exists.
"""
return os.path.exists(_audit_path(project_path))
File diff suppressed because it is too large Load Diff