Files
tracer-cloud--opensre/platform/reporting/delivery_registry.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

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",
]