feat(s52): export Notifier public contract + sync extending.md B11
This commit is contained in:
parent
1267f6c3eb
commit
b84f4d46bb
3 changed files with 48 additions and 2 deletions
|
|
@ -117,8 +117,15 @@ territory for a deployer, with the seam named:
|
|||
layers (`write_verdict`, `promote_verdict`) are last-write-wins per file. The full taxonomy
|
||||
(rejection categories + a rule for conflicting expert verdicts) is deferred until real experts
|
||||
produce conflicting verdicts.
|
||||
- **B11 — expert notification.** `run_project(notify=...)` is a stub seam (`run.py`): pass any
|
||||
callable; no delivery mechanism (e-mail/Teams/webhook) ships with the core.
|
||||
- **B11 — expert notification.** `run_project(notify=...)` remains a plain-callable seam
|
||||
(`run.py` auto-wires no default notifier), but the core now ships the declared `Notifier`
|
||||
contract (`notify.py`, exported from the package top) with three implementations:
|
||||
`ConsoleNotifier`, `FileNotifier` (byte-deterministic JSONL), and `WebhookNotifier` — plus a
|
||||
fail-fast `build_notifier(config, *, allow_egress=...)` factory. The webhook is the ONLY egress
|
||||
point and is fail-closed behind an explicit per-run `allow_egress=True` opt-in (a code kwarg,
|
||||
never a config field — mirroring the ingest layer's `allow_network`). SSRF guards, HMAC
|
||||
signing, and auth headers remain deployer-owned extension points on the injectable
|
||||
`WebhookPost` transport seam.
|
||||
- **U12 — checkpointing / crash-survival of a run.** A run either completes or is re-run; the
|
||||
async verdict inbox (step 7) is the resumable boundary, not intra-run state.
|
||||
- **U14 — OpenTelemetry / observability.** Provenance stamping is the audit trail the core
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@ from portfolio_optimiser.ledger import (
|
|||
SavingsLedger,
|
||||
realize,
|
||||
)
|
||||
from portfolio_optimiser.notify import (
|
||||
ConsoleNotifier,
|
||||
FileNotifier,
|
||||
Notifier,
|
||||
NotifierConfig,
|
||||
NotifyError,
|
||||
NotifyRefused,
|
||||
WebhookNotifier,
|
||||
build_notifier,
|
||||
)
|
||||
from portfolio_optimiser.run import (
|
||||
GoalReached,
|
||||
PortfolioResult,
|
||||
|
|
@ -35,5 +45,14 @@ __all__ = [
|
|||
"GoalConfig",
|
||||
"load_goal_config",
|
||||
"GoalReached",
|
||||
# S5.2 varsling (B11): the declared Notifier contract + deliverable implementations
|
||||
"Notifier",
|
||||
"ConsoleNotifier",
|
||||
"FileNotifier",
|
||||
"WebhookNotifier",
|
||||
"build_notifier",
|
||||
"NotifierConfig",
|
||||
"NotifyError",
|
||||
"NotifyRefused",
|
||||
"__version__",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -129,3 +129,23 @@ def test_config_fail_fast() -> None:
|
|||
|
||||
notifier = build_notifier(webhook_cfg, allow_egress=True)
|
||||
assert isinstance(notifier, WebhookNotifier)
|
||||
|
||||
|
||||
def test_public_contract_exported() -> None:
|
||||
"""The declared B11 contract is public authoring API: importable from the package top and
|
||||
listed in ``__all__`` (the brief-permitted export path — run.py's seam stays byte-intact)."""
|
||||
import portfolio_optimiser
|
||||
|
||||
exported = (
|
||||
"Notifier",
|
||||
"ConsoleNotifier",
|
||||
"FileNotifier",
|
||||
"WebhookNotifier",
|
||||
"build_notifier",
|
||||
"NotifierConfig",
|
||||
"NotifyError",
|
||||
"NotifyRefused",
|
||||
)
|
||||
for name in exported:
|
||||
assert name in portfolio_optimiser.__all__, f"{name} missing from __all__"
|
||||
assert hasattr(portfolio_optimiser, name), f"{name} not importable from package top"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue