4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
"""Report-delivery adapter registry shared by ``tools`` and ``integrations``.
|
|
|
|
The investigation pipeline used to hard-code six vendor imports inside
|
|
``tools/investigation/reporting/delivery/dispatch.py``. Under the T-4 strict
|
|
layering rules (issue #3352), ``tools`` must not import from ``integrations``
|
|
directly. This module defines the neutral seam:
|
|
|
|
* :class:`ReportDeliveryAdapter` — the protocol every vendor adapter satisfies.
|
|
* :func:`register_delivery_adapter` — vendor adapter modules call this at
|
|
import time to advertise themselves.
|
|
* :func:`iter_delivery_adapters` — the dispatch loop iterates over registered
|
|
adapters and lets each decide whether the current investigation state has
|
|
enough context to deliver.
|
|
|
|
Registration is process-scoped and idempotent (re-registering the same name
|
|
replaces the prior entry). Adapters are ordered by insertion; tests can clear
|
|
the registry via :func:`clear_delivery_adapters`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable, Mapping
|
|
from typing import Any, Protocol, runtime_checkable
|
|
|
|
VendorName = str
|
|
|
|
# The shape passed to every adapter. Kept as a plain ``dict`` alias so the
|
|
# pipeline can drop new keys without every vendor package needing an update.
|
|
DeliveryContext = Mapping[str, Any]
|
|
|
|
|
|
@runtime_checkable
|
|
class ReportDeliveryAdapter(Protocol):
|
|
"""Contract every vendor delivery adapter must satisfy.
|
|
|
|
Adapters are expected to be safe to call unconditionally: if the current
|
|
investigation state does not carry the credentials or context needed to
|
|
deliver, :meth:`deliver` should log a debug line and return ``False``. The
|
|
dispatch loop treats ``False`` as "adapter skipped" and continues.
|
|
"""
|
|
|
|
name: VendorName
|
|
|
|
def deliver(
|
|
self,
|
|
state: DeliveryContext,
|
|
*,
|
|
messages: DeliveryContext,
|
|
blocks: list[dict[str, Any]],
|
|
) -> bool:
|
|
"""Deliver the rendered report to this vendor's channel.
|
|
|
|
``state`` is the raw investigation state (typed as
|
|
:class:`core.state.InvestigationState` at the call site).
|
|
``messages`` carries the pre-rendered per-channel payloads
|
|
(``slack_text``, ``telegram_html`` …). ``blocks`` is the shared list of
|
|
Slack Block Kit blocks that vendors may reuse for interactive replies.
|
|
|
|
Return ``True`` when a delivery was attempted (successful or not — the
|
|
adapter is expected to log its own failures), ``False`` when the
|
|
adapter chose to skip (no credentials, missing context, etc.).
|
|
"""
|
|
|
|
|
|
_adapters: dict[VendorName, ReportDeliveryAdapter] = {}
|
|
|
|
|
|
def register_delivery_adapter(adapter: ReportDeliveryAdapter) -> None:
|
|
"""Register ``adapter`` under its declared :attr:`name`.
|
|
|
|
Registration is idempotent — re-registering an adapter with the same name
|
|
replaces the previous entry so tests can inject stubs and vendors can hot-
|
|
swap implementations without leaking state across processes.
|
|
"""
|
|
_adapters[adapter.name] = adapter
|
|
|
|
|
|
def iter_delivery_adapters() -> Iterable[ReportDeliveryAdapter]:
|
|
"""Return every registered adapter in insertion order.
|
|
|
|
Callers should treat the returned iterable as a snapshot; modifying the
|
|
registry during iteration is not supported.
|
|
"""
|
|
return tuple(_adapters.values())
|
|
|
|
|
|
def get_delivery_adapter(name: VendorName) -> ReportDeliveryAdapter | None:
|
|
"""Return the adapter registered under ``name``, or ``None``."""
|
|
return _adapters.get(name)
|
|
|
|
|
|
def registered_delivery_adapter_names() -> tuple[VendorName, ...]:
|
|
"""Return the names of currently registered adapters (for diagnostics)."""
|
|
return tuple(_adapters.keys())
|
|
|
|
|
|
def clear_delivery_adapters() -> None:
|
|
"""Drop every registered adapter (test isolation helper)."""
|
|
_adapters.clear()
|
|
|
|
|
|
__all__ = [
|
|
"DeliveryContext",
|
|
"ReportDeliveryAdapter",
|
|
"VendorName",
|
|
"clear_delivery_adapters",
|
|
"get_delivery_adapter",
|
|
"iter_delivery_adapters",
|
|
"register_delivery_adapter",
|
|
"registered_delivery_adapter_names",
|
|
]
|