feat(portfolio): K10 — notification/notifier seam, opt-in webhook egress (parity row 23) [skip-docs]
S5.2-analog. New notify.py: Notifier protocol + console/file/webhook sinks. The webhook (the one transport that leaves the machine) fires ONLY behind an explicit per-run opt-in flag (--allow-webhook-egress), mirroring ingest-spec §8 (the flag is a run argument, never a config field). Transport is injected — canned in the suite (NULL socket), real transport behind one seam function default_webhook_transport; an AST grep-guard proves no network path exists outside that seam. run.py (both outcomes — a budget stop notifies too) and hitl.py (read-only preserved) share the same opt-in-gated CLI seam, refusing a webhook-without-opt-in before any spend. Payload shape is stack-local (no shared notification spec; divergence documented). Two new load-bearing test files (18 tests): opt-in gate + payload structure + the grep-guard + run/hitl emit wiring + run-level opt-in threading, each detach-proven RED. 544 -> 562 green, full gate clean (ruff+format+mypy strict, 26 src files). README sync (test count x2 + notify.py module note + load-bearing omtale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RiTwaKLesgcwXx2mDviqpt
This commit is contained in:
parent
b9dd479865
commit
a2acfc0f98
6 changed files with 863 additions and 11 deletions
22
README.md
22
README.md
|
|
@ -13,7 +13,7 @@ human-in-the-loop, and the system learns from the verdicts.
|
|||
> **Status:** the D7 build (S5–S10) is complete, and the deterministic **ingest layer**
|
||||
> (CSV and SQL source types) has since been added in front of the loop. The deterministic
|
||||
> backbone, the agentic loop, the learning loop, and the ingest connectors are wired seam by
|
||||
> seam, each proven by load-bearing tests (544 tests, all running offline without an API
|
||||
> seam, each proven by load-bearing tests (562 tests, all running offline without an API
|
||||
> key). The programme's single budgeted **live model run has been executed and validated** —
|
||||
> its artifacts are committed under [`runs/s10/`](runs/s10/) (see below).
|
||||
|
||||
|
|
@ -141,6 +141,17 @@ description, never from its code)
|
|||
now; K13 formalizes the dimension catalog) to an expert via a schema-validated table
|
||||
(`nøkkel→ekspert`, fail-fast) with an optional default; an unmatched measure is UNROUTED.
|
||||
`uv run python -m portfolio_optimiser_claude.hitl pending|route`.
|
||||
- `notify.py` — deliverable notification sinks that never break the no-silent-egress
|
||||
invariant (§8): `console` and `file` deliver locally, `webhook` is the one transport that
|
||||
leaves the machine and fires ONLY behind an explicit per-run opt-in flag
|
||||
(`--allow-webhook-egress`) — mirroring the ingest-spec §8 rule that *the flag is a run
|
||||
argument, never a config field, so the config cannot grant itself network access*. The
|
||||
webhook transport is injected: the suite passes a canned transport (no socket is ever
|
||||
opened), and the real transport lives behind one seam function that the suite never calls (a
|
||||
grep-guard proves no network path exists elsewhere in the module). `run.py` (on both
|
||||
outcomes — a budget stop notifies too) and `hitl.py` (read-only preserved) share the same
|
||||
opt-in-gated CLI seam, refusing a webhook-without-opt-in *before* any spend. The payload
|
||||
shape is stack-local (no shared notification spec across the siblings).
|
||||
|
||||
### Load-bearing tests (§11)
|
||||
|
||||
|
|
@ -164,7 +175,12 @@ artifacts and stops before the first model call — a call-counting client prove
|
|||
red the moment the stop seam is detached),
|
||||
`test_hitl_loadbearing.py` (a proposal with no verdict is listed pending and disappears once
|
||||
an inbox or promoted verdict shares its id — red the moment the id-join filter is detached —
|
||||
and hitl never writes any layer, proven by a before/after byte snapshot), and
|
||||
and hitl never writes any layer, proven by a before/after byte snapshot),
|
||||
`test_notify_loadbearing.py` and `test_notify_seam_loadbearing.py` (a webhook without the
|
||||
per-run opt-in flag refuses fail-fast and its transport never fires — red the moment the gate
|
||||
is detached — the canned transport receives the structured payload, an AST grep-guard proves
|
||||
no network path lives outside the one injectable seam function, and the run/hitl entrances
|
||||
emit on their outcomes while hitl stays read-only), and
|
||||
`test_sdk_isolation.py` (local config cannot capture the checker).
|
||||
|
||||
## The ingest layer — CSV and SQL, in front of the loop
|
||||
|
|
@ -227,7 +243,7 @@ Python ≥3.10 · [`claude-agent-sdk`](https://pypi.org/project/claude-agent-sdk
|
|||
|
||||
```bash
|
||||
uv sync # install dependencies
|
||||
uv run pytest # 544 tests — run without any API key and without network
|
||||
uv run pytest # 562 tests — run without any API key and without network
|
||||
uv run ruff check . && uv run ruff format --check .
|
||||
uv run mypy src # strict
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue